mirror of
https://git.nerdvpn.de/NerdVPN.de/invidious
synced 2026-02-14 22:51:42 +01:00
Fix a lot of stupid git rebase errors
This commit is contained in:
@@ -9,21 +9,6 @@ Based upon patches from:
|
|||||||
https://github.com/yewtudotbe/invidious-custom
|
https://github.com/yewtudotbe/invidious-custom
|
||||||
https://git.nadeko.net/Fijxu/invidious
|
https://git.nadeko.net/Fijxu/invidious
|
||||||
|
|
||||||
<<<<<<< HEAD
|
|
||||||
# IMPORTANT
|
|
||||||
The instructions below are outdated.
|
|
||||||
YouTube is currently quadrupling down on trying to block Invidious instances, so there are almost daily changes to my server setup.
|
|
||||||
I haven't found the time yet to provide new/better instructions, so the instructions below should be taken with some grain of salt.
|
|
||||||
=======
|
|
||||||
# Requirements - you need to set this up before running my fork
|
|
||||||
1. HAProxy 2.x either run on the host or added to the docker-compose.yaml with my config under `etc/haproxy/haproxy.cfg`
|
|
||||||
2. PostgreSQL 16 server either run on the host or added to the docker-compose.yaml
|
|
||||||
3. (optional bvut highly recommended) PGBouncer to pool the connections to PostgreSQL. You'll otherwise run into lots of db pool errors under high loads
|
|
||||||
4. Nginx with my configs under etc/nginx
|
|
||||||
5. Valid ssl certificate for your domain. I recommend using dehydrated: https://github.com/dehydrated-io/dehydrated
|
|
||||||
6. Docker compose V2
|
|
||||||
>>>>>>> 9de2789 (Change to multiple backends)
|
|
||||||
|
|
||||||
# Build instructions
|
# Build instructions
|
||||||
|
|
||||||
Do this in a separate dir (for example /srv/invidious) under non-root permissions
|
Do this in a separate dir (for example /srv/invidious) under non-root permissions
|
||||||
|
|||||||
Executable
+2
@@ -0,0 +1,2 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
docker network create --subnet=192.42.6.0/24 --ipv6 --opt com.docker.network.bridge.name=inv_proxy invidious_proxy
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
global
|
||||||
|
log /dev/log local0
|
||||||
|
log /dev/log local1 notice
|
||||||
|
chroot /var/lib/haproxy
|
||||||
|
stats socket /run/haproxy/admin.sock mode 660 level admin
|
||||||
|
stats timeout 30s
|
||||||
|
user haproxy
|
||||||
|
group haproxy
|
||||||
|
maxconn 4000
|
||||||
|
daemon
|
||||||
|
|
||||||
|
defaults
|
||||||
|
log global
|
||||||
|
mode http
|
||||||
|
option httplog
|
||||||
|
option dontlognull
|
||||||
|
errorfile 400 /etc/haproxy/errors/400.http
|
||||||
|
errorfile 403 /etc/haproxy/errors/403.http
|
||||||
|
errorfile 408 /etc/haproxy/errors/408.http
|
||||||
|
errorfile 500 /etc/haproxy/errors/500.http
|
||||||
|
errorfile 502 /etc/haproxy/errors/502.http
|
||||||
|
#errorfile 502 /etc/haproxy/errors/502-invidious.http
|
||||||
|
errorfile 503 /etc/haproxy/errors/503.http
|
||||||
|
errorfile 504 /etc/haproxy/errors/504.http
|
||||||
|
timeout connect 10s
|
||||||
|
timeout check 10s
|
||||||
|
timeout client 15s
|
||||||
|
timeout server 15s
|
||||||
|
timeout http-request 30s
|
||||||
|
|
||||||
|
frontend statistics
|
||||||
|
bind :::8404 v4v6
|
||||||
|
mode http
|
||||||
|
http-request use-service prometheus-exporter if { path /metrics }
|
||||||
|
stats enable
|
||||||
|
stats uri /stats
|
||||||
|
stats refresh 30s
|
||||||
|
no log
|
||||||
|
|
||||||
|
frontend invidious
|
||||||
|
bind 192.42.6.1:8118
|
||||||
|
mode http
|
||||||
|
|
||||||
|
default_backend invidious_proxy
|
||||||
|
|
||||||
|
backend invidious_proxy
|
||||||
|
mode http
|
||||||
|
cookie COMPANION_ID insert indirect nocache httponly secure
|
||||||
|
balance leastconn
|
||||||
|
#balance hdr(X-Forwarded-For)
|
||||||
|
hash-type consistent
|
||||||
|
|
||||||
|
retry-on all-retryable-errors
|
||||||
|
|
||||||
|
# Ipv6
|
||||||
|
server proxy_1 192.42.6.11:8282 cookie 0
|
||||||
|
server proxy_2 192.42.6.12:8282 cookie 1
|
||||||
|
server proxy_3 192.42.6.13:8282 cookie 2
|
||||||
|
server proxy_4 192.42.6.14:8282 cookie 3
|
||||||
|
server proxy_5 192.42.6.15:8282 cookie 4
|
||||||
|
server proxy_6 192.42.6.16:8282 cookie 5
|
||||||
|
server proxy_7 192.42.6.17:8282 cookie 6
|
||||||
|
server proxy_8 192.42.6.18:8282 cookie 7
|
||||||
|
|
||||||
|
# IPv4
|
||||||
|
#server proxy_1 192.42.6.200:8282 cookie 10
|
||||||
|
#server proxy_2 192.42.6.201:8282 cookie 11
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
location = /favicon.ico {
|
||||||
|
allow all;
|
||||||
|
log_not_found off;
|
||||||
|
access_log off;
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
location = /robots.txt {
|
||||||
|
add_header Content-Type text/plain;
|
||||||
|
root /srv/nginx/public;
|
||||||
|
allow all;
|
||||||
|
log_not_found off;
|
||||||
|
access_log off;
|
||||||
|
}
|
||||||
@@ -0,0 +1,577 @@
|
|||||||
|
From aecf481a4e569f20e08294734162412a3ad9c83a Mon Sep 17 00:00:00 2001
|
||||||
|
From: Fijxu <fijxu@nadeko.net>
|
||||||
|
Date: Fri, 6 Jun 2025 12:27:08 -0400
|
||||||
|
Subject: [PATCH] feat: Support multiple companion backends
|
||||||
|
|
||||||
|
Stripped down version of what inv.nadeko.net Invidious instance does to
|
||||||
|
balance and allow the user to choose a backend.
|
||||||
|
---
|
||||||
|
locales/en-US.json | 3 +-
|
||||||
|
src/invidious.cr | 14 +++-
|
||||||
|
src/invidious/config.cr | 8 ++
|
||||||
|
src/invidious/helpers/backend_info.cr | 90 +++++++++++++++++++++
|
||||||
|
src/invidious/jobs/backend_checker.cr | 14 ++++
|
||||||
|
src/invidious/routes/backend_switcher.cr | 16 ++++
|
||||||
|
src/invidious/routes/before_all.cr | 53 +++++++++++-
|
||||||
|
src/invidious/routes/watch.cr | 2 +-
|
||||||
|
src/invidious/routing.cr | 1 +
|
||||||
|
src/invidious/user/cookies.cr | 26 ++++++
|
||||||
|
src/invidious/videos.cr | 10 +--
|
||||||
|
src/invidious/videos/parser.cr | 8 +-
|
||||||
|
src/invidious/views/template.ecr | 31 +++++++
|
||||||
|
src/invidious/yt_backend/connection_pool.cr | 9 ++-
|
||||||
|
src/invidious/yt_backend/youtube_api.cr | 12 ++-
|
||||||
|
15 files changed, 274 insertions(+), 23 deletions(-)
|
||||||
|
create mode 100644 src/invidious/helpers/backend_info.cr
|
||||||
|
create mode 100644 src/invidious/jobs/backend_checker.cr
|
||||||
|
create mode 100644 src/invidious/routes/backend_switcher.cr
|
||||||
|
|
||||||
|
diff --git a/locales/en-US.json b/locales/en-US.json
|
||||||
|
index 3f42a509..0b752d93 100644
|
||||||
|
--- a/locales/en-US.json
|
||||||
|
+++ b/locales/en-US.json
|
||||||
|
@@ -502,5 +502,6 @@
|
||||||
|
"carousel_go_to": "Go to slide `x`",
|
||||||
|
"timeline_parse_error_placeholder_heading": "Unable to parse item",
|
||||||
|
"timeline_parse_error_placeholder_message": "Invidious encountered an error while trying to parse this item. For more information see below:",
|
||||||
|
- "timeline_parse_error_show_technical_details": "Show technical details"
|
||||||
|
+ "timeline_parse_error_show_technical_details": "Show technical details",
|
||||||
|
+ "backend_unavailable": "The backend you selected is unavailable. You have been redirected to another one"
|
||||||
|
}
|
||||||
|
diff --git a/src/invidious.cr b/src/invidious.cr
|
||||||
|
index 69f8a26c..c3071be1 100644
|
||||||
|
--- a/src/invidious.cr
|
||||||
|
+++ b/src/invidious.cr
|
||||||
|
@@ -96,9 +96,11 @@ YT_POOL = YoutubeConnectionPool.new(YT_URL, capacity: CONFIG.pool_size)
|
||||||
|
|
||||||
|
GGPHT_POOL = YoutubeConnectionPool.new(URI.parse("https://yt3.ggpht.com"), capacity: CONFIG.pool_size)
|
||||||
|
|
||||||
|
-COMPANION_POOL = CompanionConnectionPool.new(
|
||||||
|
- capacity: CONFIG.pool_size
|
||||||
|
-)
|
||||||
|
+COMPANION_POOL = [] of CompanionConnectionPool
|
||||||
|
+
|
||||||
|
+CONFIG.invidious_companion.each do |companion|
|
||||||
|
+ COMPANION_POOL << CompanionConnectionPool.new(companion, capacity: CONFIG.pool_size)
|
||||||
|
+end
|
||||||
|
|
||||||
|
# CLI
|
||||||
|
Kemal.config.extra_options do |parser|
|
||||||
|
@@ -203,6 +205,12 @@ Invidious::Jobs.register Invidious::Jobs::ClearExpiredItemsJob.new
|
||||||
|
|
||||||
|
Invidious::Jobs.register Invidious::Jobs::InstanceListRefreshJob.new
|
||||||
|
|
||||||
|
+if CONFIG.invidious_companion.present?
|
||||||
|
+ Invidious::Jobs.register Invidious::Jobs::CheckBackend.new
|
||||||
|
+else
|
||||||
|
+ LOGGER.info("jobs: Disabling CheckBackend job. invidious-companion and their respective external video playback proxies (if set on invidious-companion) will not be checked")
|
||||||
|
+end
|
||||||
|
+
|
||||||
|
Invidious::Jobs.start_all
|
||||||
|
|
||||||
|
def popular_videos
|
||||||
|
diff --git a/src/invidious/config.cr b/src/invidious/config.cr
|
||||||
|
index 4d69854c..4879b7f0 100644
|
||||||
|
--- a/src/invidious/config.cr
|
||||||
|
+++ b/src/invidious/config.cr
|
||||||
|
@@ -82,6 +82,8 @@ class Config
|
||||||
|
|
||||||
|
@[YAML::Field(converter: Preferences::URIConverter)]
|
||||||
|
property public_url : URI = URI.parse("")
|
||||||
|
+
|
||||||
|
+ property note : String = ""
|
||||||
|
end
|
||||||
|
|
||||||
|
# Number of threads to use for crawling videos from channels (for updating subscriptions)
|
||||||
|
@@ -183,6 +185,12 @@ class Config
|
||||||
|
# Playlist length limit
|
||||||
|
property playlist_length_limit : Int32 = 500
|
||||||
|
|
||||||
|
+ # Fijxu companion modifications
|
||||||
|
+
|
||||||
|
+ property server_id_cookie_name : String = "COMPANION_ID"
|
||||||
|
+ property check_backends_interval : Int32 = 30
|
||||||
|
+ property backend_name_prefix : String = "Backend"
|
||||||
|
+
|
||||||
|
def disabled?(option)
|
||||||
|
case disabled = CONFIG.disable_proxy
|
||||||
|
when Bool
|
||||||
|
diff --git a/src/invidious/helpers/backend_info.cr b/src/invidious/helpers/backend_info.cr
|
||||||
|
new file mode 100644
|
||||||
|
index 00000000..f576245e
|
||||||
|
--- /dev/null
|
||||||
|
+++ b/src/invidious/helpers/backend_info.cr
|
||||||
|
@@ -0,0 +1,90 @@
|
||||||
|
+module BackendInfo
|
||||||
|
+ extend self
|
||||||
|
+
|
||||||
|
+ enum Status
|
||||||
|
+ Dead = 0
|
||||||
|
+ Working = 1
|
||||||
|
+ end
|
||||||
|
+
|
||||||
|
+ @@status : Array(Int32) = Array.new(CONFIG.invidious_companion.size, Status::Dead.to_i)
|
||||||
|
+ @@csp : Array(String) = Array.new(CONFIG.invidious_companion.size, "")
|
||||||
|
+ @@working_ends : Array(Int32) = Array(Int32).new(0)
|
||||||
|
+ @@csp_mutex : Mutex = Mutex.new
|
||||||
|
+ @@check_mutex : Mutex = Mutex.new
|
||||||
|
+
|
||||||
|
+ def check_backends
|
||||||
|
+ check_companion()
|
||||||
|
+ LOGGER.debug("Invidious companion: New working_ends \"#{@@working_ends}\"")
|
||||||
|
+ LOGGER.debug("Invidious companion: New status \"#{@@status}\"")
|
||||||
|
+ end
|
||||||
|
+
|
||||||
|
+ private def check_companion
|
||||||
|
+ # Create Channels the size of CONFIG.invidious_companion
|
||||||
|
+ comp_size = CONFIG.invidious_companion.size
|
||||||
|
+ channels = Channel(Nil).new(comp_size)
|
||||||
|
+ updated_ends = Array(Int32).new(0)
|
||||||
|
+ updated_status = Array(Int32).new(CONFIG.invidious_companion.size, 0)
|
||||||
|
+
|
||||||
|
+ LOGGER.debug("Invidious companion: comp_size \"#{comp_size}\"")
|
||||||
|
+ CONFIG.invidious_companion.each_with_index do |companion, index|
|
||||||
|
+ spawn do
|
||||||
|
+ begin
|
||||||
|
+ client = HTTP::Client.new(companion.private_url)
|
||||||
|
+ client.connect_timeout = 10.seconds
|
||||||
|
+ response = client.get("/healthz")
|
||||||
|
+ if response.status_code == 200
|
||||||
|
+ @@check_mutex.synchronize do
|
||||||
|
+ updated_status[index] = Status::Working.to_i
|
||||||
|
+ updated_ends.push(index)
|
||||||
|
+ end
|
||||||
|
+ generate_csp([companion.public_url.to_s], index)
|
||||||
|
+ else
|
||||||
|
+ @@check_mutex.synchronize do
|
||||||
|
+ updated_status[index] = Status::Dead.to_i
|
||||||
|
+ end
|
||||||
|
+ end
|
||||||
|
+ rescue
|
||||||
|
+ @@check_mutex.synchronize do
|
||||||
|
+ updated_status[index] = Status::Dead.to_i
|
||||||
|
+ end
|
||||||
|
+ ensure
|
||||||
|
+ LOGGER.trace("Invidious companion: Done Index: \"#{index}\"")
|
||||||
|
+ channels.send(nil)
|
||||||
|
+ end
|
||||||
|
+ end
|
||||||
|
+ end
|
||||||
|
+ # Wait until we receive a signal from them all
|
||||||
|
+ LOGGER.debug("Invidious companion: Updating working_ends")
|
||||||
|
+ comp_size.times { channels.receive }
|
||||||
|
+ @@working_ends = updated_ends.sort!
|
||||||
|
+ @@status = updated_status
|
||||||
|
+ end
|
||||||
|
+
|
||||||
|
+ private def generate_csp(companion_url : Array(String), index : Int32? = nil)
|
||||||
|
+ @@csp_mutex.synchronize do
|
||||||
|
+ @@csp[index] = ""
|
||||||
|
+ companion_url.each do |url|
|
||||||
|
+ @@csp[index] += " #{url}"
|
||||||
|
+ end
|
||||||
|
+ end
|
||||||
|
+ end
|
||||||
|
+
|
||||||
|
+ def get_status
|
||||||
|
+ # Shouldn't need to lock since we never edit this array, only change the pointer.
|
||||||
|
+ return @@status
|
||||||
|
+ end
|
||||||
|
+
|
||||||
|
+ def get_working_ends
|
||||||
|
+ # Shouldn't need to lock since we never edit this array, only change the pointer.
|
||||||
|
+ return @@working_ends
|
||||||
|
+ end
|
||||||
|
+
|
||||||
|
+ def get_csp(index : Int32)
|
||||||
|
+ # A little mutex to prevent sending a partial CSP header
|
||||||
|
+ # Not sure if this is necessary. But if the @@csp[index] is being assigned
|
||||||
|
+ # at the same time when it's being accessed, a data race will appear
|
||||||
|
+ @@csp_mutex.synchronize do
|
||||||
|
+ return @@csp[index], @@csp[index]
|
||||||
|
+ end
|
||||||
|
+ end
|
||||||
|
+end
|
||||||
|
diff --git a/src/invidious/jobs/backend_checker.cr b/src/invidious/jobs/backend_checker.cr
|
||||||
|
new file mode 100644
|
||||||
|
index 00000000..fdfa0a45
|
||||||
|
--- /dev/null
|
||||||
|
+++ b/src/invidious/jobs/backend_checker.cr
|
||||||
|
@@ -0,0 +1,14 @@
|
||||||
|
+class Invidious::Jobs::CheckBackend < Invidious::Jobs::BaseJob
|
||||||
|
+ def initialize
|
||||||
|
+ end
|
||||||
|
+
|
||||||
|
+ def begin
|
||||||
|
+ loop do
|
||||||
|
+ LOGGER.info("Backend Checker: Starting")
|
||||||
|
+ BackendInfo.check_backends
|
||||||
|
+ LOGGER.info("Backend Checker: Done, sleeping for #{CONFIG.check_backends_interval} seconds")
|
||||||
|
+ sleep CONFIG.check_backends_interval.seconds
|
||||||
|
+ Fiber.yield
|
||||||
|
+ end
|
||||||
|
+ end
|
||||||
|
+end
|
||||||
|
diff --git a/src/invidious/routes/backend_switcher.cr b/src/invidious/routes/backend_switcher.cr
|
||||||
|
new file mode 100644
|
||||||
|
index 00000000..e4702729
|
||||||
|
--- /dev/null
|
||||||
|
+++ b/src/invidious/routes/backend_switcher.cr
|
||||||
|
@@ -0,0 +1,16 @@
|
||||||
|
+{% skip_file if flag?(:api_only) %}
|
||||||
|
+
|
||||||
|
+module Invidious::Routes::BackendSwitcher
|
||||||
|
+ def self.switch(env)
|
||||||
|
+ referer = get_referer(env, unroll: false)
|
||||||
|
+ backend_id = env.params.query["backend_id"]?.try &.to_i
|
||||||
|
+
|
||||||
|
+ if backend_id.nil?
|
||||||
|
+ return error_template(400, "Backend ID is required")
|
||||||
|
+ end
|
||||||
|
+
|
||||||
|
+ env.response.cookies[CONFIG.server_id_cookie_name] = Invidious::User::Cookies.server_id(env.request.headers["Host"], backend_id)
|
||||||
|
+
|
||||||
|
+ env.redirect referer
|
||||||
|
+ end
|
||||||
|
+end
|
||||||
|
diff --git a/src/invidious/routes/before_all.cr b/src/invidious/routes/before_all.cr
|
||||||
|
index b5269668..6fb2eef4 100644
|
||||||
|
--- a/src/invidious/routes/before_all.cr
|
||||||
|
+++ b/src/invidious/routes/before_all.cr
|
||||||
|
@@ -1,6 +1,9 @@
|
||||||
|
module Invidious::Routes::BeforeAll
|
||||||
|
def self.handle(env)
|
||||||
|
preferences = Preferences.from_json("{}")
|
||||||
|
+ host = env.request.headers["Host"]
|
||||||
|
+ scheme = env.request.headers["X-Forwarded-Proto"]? || ("https" if CONFIG.https_only) || "http"
|
||||||
|
+ env.set "scheme", scheme
|
||||||
|
|
||||||
|
begin
|
||||||
|
if prefs_cookie = env.request.cookies["PREFS"]?
|
||||||
|
@@ -20,6 +23,50 @@ module Invidious::Routes::BeforeAll
|
||||||
|
env.response.headers["X-XSS-Protection"] = "1; mode=block"
|
||||||
|
env.response.headers["X-Content-Type-Options"] = "nosniff"
|
||||||
|
|
||||||
|
+ extra_media_csp = ""
|
||||||
|
+ extra_connect_csp = ""
|
||||||
|
+
|
||||||
|
+ if CONFIG.invidious_companion.present?
|
||||||
|
+ if !{
|
||||||
|
+ "/sb/",
|
||||||
|
+ "/vi/",
|
||||||
|
+ "/s_p/",
|
||||||
|
+ "/yts/",
|
||||||
|
+ "/ggpht/",
|
||||||
|
+ }.any? { |r| env.request.resource.starts_with? r }
|
||||||
|
+ if !env.request.cookies[CONFIG.server_id_cookie_name]?
|
||||||
|
+ env.response.cookies[CONFIG.server_id_cookie_name] = Invidious::User::Cookies.server_id(host)
|
||||||
|
+ end
|
||||||
|
+
|
||||||
|
+ begin
|
||||||
|
+ current_companion = env.request.cookies[CONFIG.server_id_cookie_name].value.try &.to_i
|
||||||
|
+ rescue
|
||||||
|
+ working_ends = BackendInfo.get_working_ends
|
||||||
|
+ current_companion = working_ends.sample
|
||||||
|
+ end
|
||||||
|
+
|
||||||
|
+ if current_companion > CONFIG.invidious_companion.size
|
||||||
|
+ current_companion = current_companion % CONFIG.invidious_companion.size
|
||||||
|
+ env.response.cookies[CONFIG.server_id_cookie_name] = Invidious::User::Cookies.server_id(host, current_companion)
|
||||||
|
+ end
|
||||||
|
+
|
||||||
|
+ companion_status = BackendInfo.get_status
|
||||||
|
+
|
||||||
|
+ if companion_status[current_companion] != BackendInfo::Status::Working.to_i
|
||||||
|
+ current_companion = 0 if current_companion == companion_status.size - 1
|
||||||
|
+ alive_companion = companion_status.index(BackendInfo::Status::Working.to_i, offset: current_companion)
|
||||||
|
+ if alive_companion
|
||||||
|
+ env.set "companion_switched", true
|
||||||
|
+ current_companion = alive_companion
|
||||||
|
+ env.response.cookies[CONFIG.server_id_cookie_name] = Invidious::User::Cookies.server_id(host, current_companion)
|
||||||
|
+ end
|
||||||
|
+ end
|
||||||
|
+
|
||||||
|
+ env.set "current_companion", current_companion
|
||||||
|
+ extra_media_csp, extra_connect_csp = BackendInfo.get_csp(current_companion)
|
||||||
|
+ end
|
||||||
|
+ end
|
||||||
|
+
|
||||||
|
# Only allow the pages at /embed/* to be embedded
|
||||||
|
if env.request.resource.starts_with?("/embed")
|
||||||
|
frame_ancestors = "'self' file: http: https:"
|
||||||
|
@@ -33,11 +80,11 @@ module Invidious::Routes::BeforeAll
|
||||||
|
"default-src 'none'",
|
||||||
|
"script-src 'self'",
|
||||||
|
"style-src 'self' 'unsafe-inline'",
|
||||||
|
- "img-src 'self' data:",
|
||||||
|
+ "img-src 'self' data: " + "#{scheme}://#{env.request.headers["Host"]?}",
|
||||||
|
"font-src 'self' data:",
|
||||||
|
- "connect-src 'self'",
|
||||||
|
+ "connect-src 'self'" + extra_connect_csp,
|
||||||
|
"manifest-src 'self'",
|
||||||
|
- "media-src 'self' blob:",
|
||||||
|
+ "media-src 'self' blob:" + extra_media_csp,
|
||||||
|
"child-src 'self' blob:",
|
||||||
|
"frame-src 'self'",
|
||||||
|
"frame-ancestors " + frame_ancestors,
|
||||||
|
diff --git a/src/invidious/routes/watch.cr b/src/invidious/routes/watch.cr
|
||||||
|
index e777b3f1..7adb98ea 100644
|
||||||
|
--- a/src/invidious/routes/watch.cr
|
||||||
|
+++ b/src/invidious/routes/watch.cr
|
||||||
|
@@ -52,7 +52,7 @@ module Invidious::Routes::Watch
|
||||||
|
env.params.query.delete_all("listen")
|
||||||
|
|
||||||
|
begin
|
||||||
|
- video = get_video(id, region: params.region)
|
||||||
|
+ video = get_video(id, region: params.region, env: env)
|
||||||
|
rescue ex : NotFoundException
|
||||||
|
LOGGER.error("get_video not found: #{id} : #{ex.message}")
|
||||||
|
return error_template(404, ex)
|
||||||
|
diff --git a/src/invidious/routing.cr b/src/invidious/routing.cr
|
||||||
|
index 46b71f1f..769a7f71 100644
|
||||||
|
--- a/src/invidious/routing.cr
|
||||||
|
+++ b/src/invidious/routing.cr
|
||||||
|
@@ -21,6 +21,7 @@ module Invidious::Routing
|
||||||
|
get "/privacy", Routes::Misc, :privacy
|
||||||
|
get "/licenses", Routes::Misc, :licenses
|
||||||
|
get "/redirect", Routes::Misc, :cross_instance_redirect
|
||||||
|
+ get "/switchbackend", Routes::BackendSwitcher, :switch
|
||||||
|
|
||||||
|
self.register_channel_routes
|
||||||
|
self.register_watch_routes
|
||||||
|
diff --git a/src/invidious/user/cookies.cr b/src/invidious/user/cookies.cr
|
||||||
|
index 654efc15..affcfdca 100644
|
||||||
|
--- a/src/invidious/user/cookies.cr
|
||||||
|
+++ b/src/invidious/user/cookies.cr
|
||||||
|
@@ -35,5 +35,31 @@ struct Invidious::User
|
||||||
|
samesite: HTTP::Cookie::SameSite::Lax
|
||||||
|
)
|
||||||
|
end
|
||||||
|
+
|
||||||
|
+ # Backend (CONFIG.server_id_cookie_name) cookie
|
||||||
|
+ # Parameter "domain" comes from the global config
|
||||||
|
+ def server_id(domain : String?, server_id : Int32? = nil) : HTTP::Cookie
|
||||||
|
+ if server_id.nil?
|
||||||
|
+ server_id = rand(CONFIG.invidious_companion.size)
|
||||||
|
+ end
|
||||||
|
+ # Strip the port from the domain if it's being accessed from another port
|
||||||
|
+ # Browsers will reject the cookie if it contains the port number. This is
|
||||||
|
+ # because `example.com:3000` is not the same as `example.com` on a cookie.
|
||||||
|
+ domain = domain.split(":")[0]
|
||||||
|
+ # Not secure if it's being accessed from I2P
|
||||||
|
+ # Browsers expect the domain to include https. On I2P there is no HTTPS
|
||||||
|
+ if domain.not_nil!.split(".").last == "i2p"
|
||||||
|
+ @@secure = false
|
||||||
|
+ end
|
||||||
|
+ return HTTP::Cookie.new(
|
||||||
|
+ name: CONFIG.server_id_cookie_name,
|
||||||
|
+ domain: domain,
|
||||||
|
+ path: "/",
|
||||||
|
+ value: server_id.to_s,
|
||||||
|
+ secure: @@secure,
|
||||||
|
+ http_only: true,
|
||||||
|
+ samesite: HTTP::Cookie::SameSite::Lax
|
||||||
|
+ )
|
||||||
|
+ end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
diff --git a/src/invidious/videos.cr b/src/invidious/videos.cr
|
||||||
|
index 348a0a66..404f5fed 100644
|
||||||
|
--- a/src/invidious/videos.cr
|
||||||
|
+++ b/src/invidious/videos.cr
|
||||||
|
@@ -294,7 +294,7 @@ struct Video
|
||||||
|
predicate_bool upcoming, isUpcoming
|
||||||
|
end
|
||||||
|
|
||||||
|
-def get_video(id, refresh = true, region = nil, force_refresh = false)
|
||||||
|
+def get_video(id, refresh = true, region = nil, force_refresh = false, env : HTTP::Server::Context | Nil = nil)
|
||||||
|
if (video = Invidious::Database::Videos.select(id)) && !region
|
||||||
|
# If record was last updated over 10 minutes ago, or video has since premiered,
|
||||||
|
# refresh (expire param in response lasts for 6 hours)
|
||||||
|
@@ -304,7 +304,7 @@ def get_video(id, refresh = true, region = nil, force_refresh = false)
|
||||||
|
force_refresh ||
|
||||||
|
video.schema_version != Video::SCHEMA_VERSION # cache control
|
||||||
|
begin
|
||||||
|
- video = fetch_video(id, region)
|
||||||
|
+ video = fetch_video(id, region, env)
|
||||||
|
Invidious::Database::Videos.update(video)
|
||||||
|
rescue ex
|
||||||
|
Invidious::Database::Videos.delete(id)
|
||||||
|
@@ -312,7 +312,7 @@ def get_video(id, refresh = true, region = nil, force_refresh = false)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
- video = fetch_video(id, region)
|
||||||
|
+ video = fetch_video(id, region, env)
|
||||||
|
Invidious::Database::Videos.insert(video) if !region
|
||||||
|
end
|
||||||
|
|
||||||
|
@@ -320,10 +320,10 @@ def get_video(id, refresh = true, region = nil, force_refresh = false)
|
||||||
|
rescue DB::Error
|
||||||
|
# Avoid common `DB::PoolRetryAttemptsExceeded` error and friends
|
||||||
|
# Note: All DB errors inherit from `DB::Error`
|
||||||
|
- return fetch_video(id, region)
|
||||||
|
+ return fetch_video(id, region, env)
|
||||||
|
end
|
||||||
|
|
||||||
|
-def fetch_video(id, region)
|
||||||
|
+def fetch_video(id, region, env)
|
||||||
|
info = extract_video_info(video_id: id)
|
||||||
|
|
||||||
|
if reason = info["reason"]?
|
||||||
|
diff --git a/src/invidious/videos/parser.cr b/src/invidious/videos/parser.cr
|
||||||
|
index feb58440..c4f946e8 100644
|
||||||
|
--- a/src/invidious/videos/parser.cr
|
||||||
|
+++ b/src/invidious/videos/parser.cr
|
||||||
|
@@ -58,7 +58,7 @@ def parse_related_video(related : JSON::Any) : Hash(String, JSON::Any)?
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
-def extract_video_info(video_id : String)
|
||||||
|
+def extract_video_info(video_id : String, env : HTTP::Server::Context | Nil = nil)
|
||||||
|
# Init client config for the API
|
||||||
|
client_config = YoutubeAPI::ClientConfig.new
|
||||||
|
|
||||||
|
@@ -116,7 +116,7 @@ def extract_video_info(video_id : String)
|
||||||
|
players_fallback.each do |player_fallback|
|
||||||
|
client_config.client_type = player_fallback
|
||||||
|
|
||||||
|
- next if !(player_fallback_response = try_fetch_streaming_data(video_id, client_config))
|
||||||
|
+ next if !(player_fallback_response = try_fetch_streaming_data(video_id, client_config, env))
|
||||||
|
|
||||||
|
if player_fallback_response.dig?("streamingData", "adaptiveFormats", 0, "url")
|
||||||
|
streaming_data = player_response["streamingData"].as_h
|
||||||
|
@@ -159,9 +159,9 @@ def extract_video_info(video_id : String)
|
||||||
|
return params
|
||||||
|
end
|
||||||
|
|
||||||
|
-def try_fetch_streaming_data(id : String, client_config : YoutubeAPI::ClientConfig) : Hash(String, JSON::Any)?
|
||||||
|
+def try_fetch_streaming_data(id : String, client_config : YoutubeAPI::ClientConfig, env : HTTP::Server::Context | Nil = nil) : Hash(String, JSON::Any)?
|
||||||
|
LOGGER.debug("try_fetch_streaming_data: [#{id}] Using #{client_config.client_type} client.")
|
||||||
|
- response = YoutubeAPI.player(video_id: id, params: "2AMB", client_config: client_config)
|
||||||
|
+ response = YoutubeAPI.player(video_id: id, params: "2AMB", client_config: client_config, env: env)
|
||||||
|
|
||||||
|
playability_status = response["playabilityStatus"]["status"]
|
||||||
|
LOGGER.debug("try_fetch_streaming_data: [#{id}] Got playabilityStatus == #{playability_status}.")
|
||||||
|
diff --git a/src/invidious/views/template.ecr b/src/invidious/views/template.ecr
|
||||||
|
index 9904b4fc..ad6c1c0e 100644
|
||||||
|
--- a/src/invidious/views/template.ecr
|
||||||
|
+++ b/src/invidious/views/template.ecr
|
||||||
|
@@ -104,6 +104,37 @@
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
+ <%
|
||||||
|
+ if CONFIG.invidious_companion.present?
|
||||||
|
+ current_backend = env.get?("current_companion").try &.as(Int32)
|
||||||
|
+ scheme = env.get("scheme")
|
||||||
|
+ companion_switched = env.get?("companion_switched")
|
||||||
|
+ status = BackendInfo.get_status
|
||||||
|
+ %>
|
||||||
|
+ <div class="h-box" style="margin-bottom: 10px; font-size:0.85em; text-align: center;">
|
||||||
|
+ <b>Backend</b><br />
|
||||||
|
+ <% current_page = env.get("current_page") %>
|
||||||
|
+ <% CONFIG.invidious_companion.each_with_index do | companion, index | %>
|
||||||
|
+ <a href="/switchbackend?backend_id=<%= index.to_s %>&referer=<%= current_page %>" style="<%= current_backend == index ? "text-decoration-line: underline; font-weight:bold;" : "" %> display: inline-block;">
|
||||||
|
+ <%= HTML.escape(CONFIG.backend_name_prefix + (index + 1).to_s) %> <%= HTML.escape(companion.note) %>
|
||||||
|
+ <span style="font-size: 1rem; color:
|
||||||
|
+ <% if status[index] == BackendInfo::Status::Dead.to_i %> #fd4848; <% end %>
|
||||||
|
+ <% if status[index] == BackendInfo::Status::Working.to_i %> #42ae3c; <% end %>
|
||||||
|
+ ">•</span>
|
||||||
|
+ </a>
|
||||||
|
+ <% if !(index == CONFIG.invidious_companion.size-1) %>
|
||||||
|
+ <span> | </span>
|
||||||
|
+ <% end %>
|
||||||
|
+ <% end %>
|
||||||
|
+ </div>
|
||||||
|
+ <% end %>
|
||||||
|
+
|
||||||
|
+ <% if companion_switched %>
|
||||||
|
+ <div class="h-box">
|
||||||
|
+ <p><%= translate(locale, "backend_unavailable") %></p>
|
||||||
|
+ </div>
|
||||||
|
+ <% end %>
|
||||||
|
+
|
||||||
|
<% if CONFIG.banner %>
|
||||||
|
<div class="h-box">
|
||||||
|
<h3><%= CONFIG.banner %></h3>
|
||||||
|
diff --git a/src/invidious/yt_backend/connection_pool.cr b/src/invidious/yt_backend/connection_pool.cr
|
||||||
|
index 0daed46c..4c7227c4 100644
|
||||||
|
--- a/src/invidious/yt_backend/connection_pool.cr
|
||||||
|
+++ b/src/invidious/yt_backend/connection_pool.cr
|
||||||
|
@@ -49,7 +49,7 @@ end
|
||||||
|
struct CompanionConnectionPool
|
||||||
|
property pool : DB::Pool(HTTP::Client)
|
||||||
|
|
||||||
|
- def initialize(capacity = 5, timeout = 5.0)
|
||||||
|
+ def initialize(companion, capacity = 5, timeout = 10.0)
|
||||||
|
options = DB::Pool::Options.new(
|
||||||
|
initial_pool_size: 0,
|
||||||
|
max_pool_size: capacity,
|
||||||
|
@@ -58,7 +58,6 @@ struct CompanionConnectionPool
|
||||||
|
)
|
||||||
|
|
||||||
|
@pool = DB::Pool(HTTP::Client).new(options) do
|
||||||
|
- companion = CONFIG.invidious_companion.sample
|
||||||
|
next make_client(companion.private_url, use_http_proxy: false)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
@@ -71,8 +70,10 @@ struct CompanionConnectionPool
|
||||||
|
rescue ex
|
||||||
|
conn.close
|
||||||
|
|
||||||
|
- companion = CONFIG.invidious_companion.sample
|
||||||
|
- conn = make_client(companion.private_url, use_http_proxy: false)
|
||||||
|
+ scheme = "http"
|
||||||
|
+ same_companion = "#{scheme}://#{conn.host}:#{conn.port}"
|
||||||
|
+
|
||||||
|
+ conn = make_client(URI.parse(same_companion), use_http_proxy: false)
|
||||||
|
|
||||||
|
response = yield conn
|
||||||
|
ensure
|
||||||
|
diff --git a/src/invidious/yt_backend/youtube_api.cr b/src/invidious/yt_backend/youtube_api.cr
|
||||||
|
index b40092a1..1b37c4c9 100644
|
||||||
|
--- a/src/invidious/yt_backend/youtube_api.cr
|
||||||
|
+++ b/src/invidious/yt_backend/youtube_api.cr
|
||||||
|
@@ -465,6 +465,7 @@ module YoutubeAPI
|
||||||
|
*, # Force the following parameters to be passed by name
|
||||||
|
params : String,
|
||||||
|
client_config : ClientConfig | Nil = nil,
|
||||||
|
+ env : HTTP::Server::Context | Nil = nil,
|
||||||
|
)
|
||||||
|
# Playback context, separate because it can be different between clients
|
||||||
|
playback_ctx = {
|
||||||
|
@@ -501,7 +502,7 @@ module YoutubeAPI
|
||||||
|
end
|
||||||
|
|
||||||
|
if CONFIG.invidious_companion.present?
|
||||||
|
- return self._post_invidious_companion("/youtubei/v1/player", data)
|
||||||
|
+ return self._post_invidious_companion("/youtubei/v1/player", data, env)
|
||||||
|
else
|
||||||
|
return self._post_json("/youtubei/v1/player", data, client_config)
|
||||||
|
end
|
||||||
|
@@ -682,6 +683,7 @@ module YoutubeAPI
|
||||||
|
def _post_invidious_companion(
|
||||||
|
endpoint : String,
|
||||||
|
data : Hash,
|
||||||
|
+ env : HTTP::Server::Context | Nil,
|
||||||
|
) : Hash(String, JSON::Any)
|
||||||
|
headers = HTTP::Headers{
|
||||||
|
"Content-Type" => "application/json; charset=UTF-8",
|
||||||
|
@@ -695,7 +697,13 @@ module YoutubeAPI
|
||||||
|
# Send the POST request
|
||||||
|
|
||||||
|
begin
|
||||||
|
- response = COMPANION_POOL.client &.post(endpoint, headers: headers, body: data.to_json)
|
||||||
|
+ if env.nil?
|
||||||
|
+ working_ends = BackendInfo.get_working_ends
|
||||||
|
+ current_companion = working_ends.sample
|
||||||
|
+ else
|
||||||
|
+ current_companion = env.get("current_companion").as(Int32)
|
||||||
|
+ end
|
||||||
|
+ response = COMPANION_POOL[current_companion].client &.post(endpoint, headers: headers, body: data.to_json)
|
||||||
|
body = response.body
|
||||||
|
if (response.status_code != 200)
|
||||||
|
raise Exception.new(
|
||||||
|
--
|
||||||
|
2.49.0
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
From 236a2c70d3974f5a1fc55a5bc8047ff24d94bce2 Mon Sep 17 00:00:00 2001
|
||||||
|
From: =?UTF-8?q?=C3=89milien=20Devos?= <github@emiliendevos.be>
|
||||||
|
Date: Tue, 16 Aug 2022 06:51:52 +0000
|
||||||
|
Subject: [PATCH] more csp
|
||||||
|
|
||||||
|
---
|
||||||
|
src/invidious/routes/before_all.cr | 6 +++---
|
||||||
|
1 file changed, 3 insertions(+), 3 deletions(-)
|
||||||
|
|
||||||
|
diff --git a/src/invidious/routes/before_all.cr b/src/invidious/routes/before_all.cr
|
||||||
|
index 8e2a253f..5c7cc959 100644
|
||||||
|
--- a/src/invidious/routes/before_all.cr
|
||||||
|
+++ b/src/invidious/routes/before_all.cr
|
||||||
|
@@ -23,9 +23,9 @@ module Invidious::Routes::BeforeAll
|
||||||
|
# Allow media resources to be loaded from google servers
|
||||||
|
# TODO: check if *.youtube.com can be removed
|
||||||
|
if CONFIG.disabled?("local") || !preferences.local
|
||||||
|
- extra_media_csp = " https://*.googlevideo.com:443 https://*.youtube.com:443"
|
||||||
|
+ extra_media_csp = " https://*.googlevideo.com:443 https://*.youtube.com:443 https://*.invidious.nerdvpn.de:443"
|
||||||
|
else
|
||||||
|
- extra_media_csp = ""
|
||||||
|
+ extra_media_csp = " https://*.invidious.nerdvpn.de:443"
|
||||||
|
end
|
||||||
|
|
||||||
|
# Only allow the pages at /embed/* to be embedded
|
||||||
|
@@ -43,7 +43,7 @@ module Invidious::Routes::BeforeAll
|
||||||
|
"style-src 'self' 'unsafe-inline'",
|
||||||
|
"img-src 'self' data:",
|
||||||
|
"font-src 'self' data:",
|
||||||
|
- "connect-src 'self'",
|
||||||
|
+ "connect-src 'self' https://*.invidious.nerdvpn.de:443",
|
||||||
|
"manifest-src 'self'",
|
||||||
|
"media-src 'self' blob:" + extra_media_csp,
|
||||||
|
"child-src 'self' blob:",
|
||||||
|
--
|
||||||
|
2.36.1
|
||||||
|
|
||||||
@@ -0,0 +1,151 @@
|
|||||||
|
From 7c4c24f829b4eeb8ca035b4a9d77eef53ca8aefa Mon Sep 17 00:00:00 2001
|
||||||
|
From: Emilien Devos <contact@emiliendevos.be>
|
||||||
|
Date: Sat, 10 Jun 2023 22:21:38 +0200
|
||||||
|
Subject: [PATCH 1/1] use redis for video cache
|
||||||
|
|
||||||
|
---
|
||||||
|
shard.lock | 8 ++++++++
|
||||||
|
shard.yml | 2 ++
|
||||||
|
src/invidious.cr | 8 +++++++-
|
||||||
|
src/invidious/config.cr | 3 +++
|
||||||
|
src/invidious/database/videos.cr | 16 +++++++++++++---
|
||||||
|
src/invidious/videos.cr | 2 +-
|
||||||
|
6 files changed, 34 insertions(+), 5 deletions(-)
|
||||||
|
|
||||||
|
diff --git a/shard.lock b/shard.lock
|
||||||
|
index 397bd8bc..8c599d1b 100644
|
||||||
|
--- a/shard.lock
|
||||||
|
+++ b/shard.lock
|
||||||
|
@@ -36,6 +36,10 @@ shards:
|
||||||
|
git: https://github.com/will/crystal-pg.git
|
||||||
|
version: 0.28.0
|
||||||
|
|
||||||
|
+ pool:
|
||||||
|
+ git: https://github.com/ysbaddaden/pool.git
|
||||||
|
+ version: 0.2.4
|
||||||
|
+
|
||||||
|
protodec:
|
||||||
|
git: https://github.com/iv-org/protodec.git
|
||||||
|
version: 0.1.5
|
||||||
|
@@ -44,6 +48,10 @@ shards:
|
||||||
|
git: https://github.com/luislavena/radix.git
|
||||||
|
version: 0.4.1
|
||||||
|
|
||||||
|
+ redis:
|
||||||
|
+ git: https://github.com/stefanwille/crystal-redis.git
|
||||||
|
+ version: 2.9.1
|
||||||
|
+
|
||||||
|
spectator:
|
||||||
|
git: https://github.com/icy-arctic-fox/spectator.git
|
||||||
|
version: 0.10.6
|
||||||
|
diff --git a/shard.yml b/shard.yml
|
||||||
|
index 367f7c73..c46e1c42 100644
|
||||||
|
--- a/shard.yml
|
||||||
|
+++ b/shard.yml
|
||||||
|
@@ -31,6 +31,8 @@ dependencies:
|
||||||
|
http_proxy:
|
||||||
|
github: mamantoha/http_proxy
|
||||||
|
version: ~> 0.10.3
|
||||||
|
+ redis:
|
||||||
|
+ github: stefanwille/crystal-redis
|
||||||
|
|
||||||
|
development_dependencies:
|
||||||
|
spectator:
|
||||||
|
diff --git a/src/invidious.cr b/src/invidious.cr
|
||||||
|
index 3804197e..e2d40597 100644
|
||||||
|
--- a/src/invidious.cr
|
||||||
|
+++ b/src/invidious.cr
|
||||||
|
@@ -31,6 +31,7 @@ require "xml"
|
||||||
|
require "yaml"
|
||||||
|
require "compress/zip"
|
||||||
|
require "protodec/utils"
|
||||||
|
+require "redis"
|
||||||
|
|
||||||
|
require "./invidious/database/*"
|
||||||
|
require "./invidious/database/migrations/*"
|
||||||
|
@@ -60,7 +61,12 @@ alias IV = Invidious
|
||||||
|
CONFIG = Config.load
|
||||||
|
HMAC_KEY = CONFIG.hmac_key
|
||||||
|
|
||||||
|
-PG_DB = DB.open CONFIG.database_url
|
||||||
|
+PG_DB = DB.open CONFIG.database_url
|
||||||
|
+REDIS_DB = Redis::PooledClient.new(unixsocket: CONFIG.redis_socket || nil, url: CONFIG.redis_url || nil, database: CONFIG.redis_db || 0, password: CONFIG.redis_auth || nil)
|
||||||
|
+
|
||||||
|
+if REDIS_DB.ping
|
||||||
|
+ puts "Connected to redis"
|
||||||
|
+end
|
||||||
|
ARCHIVE_URL = URI.parse("https://archive.org")
|
||||||
|
PUBSUB_URL = URI.parse("https://pubsubhubbub.appspot.com")
|
||||||
|
REDDIT_URL = URI.parse("https://www.reddit.com")
|
||||||
|
diff --git a/src/invidious/config.cr b/src/invidious/config.cr
|
||||||
|
index c4ddcdb3..5b6c576c 100644
|
||||||
|
--- a/src/invidious/config.cr
|
||||||
|
+++ b/src/invidious/config.cr
|
||||||
|
@@ -77,6 +77,11 @@ class Config
|
||||||
|
# Used for crawling channels: threads should check all videos uploaded by a channel
|
||||||
|
property full_refresh : Bool = false
|
||||||
|
|
||||||
|
+ property redis_url : String?
|
||||||
|
+ property redis_socket : String?
|
||||||
|
+ property redis_auth : String?
|
||||||
|
+ property redis_db : Int32?
|
||||||
|
+
|
||||||
|
# Jobs config structure. See jobs.cr and jobs/base_job.cr
|
||||||
|
property jobs = Invidious::Jobs::JobsConfig.new
|
||||||
|
|
||||||
|
diff --git a/src/invidious/database/videos.cr b/src/invidious/database/videos.cr
|
||||||
|
index 695f5b33..776ef5b1 100644
|
||||||
|
--- a/src/invidious/database/videos.cr
|
||||||
|
+++ b/src/invidious/database/videos.cr
|
||||||
|
@@ -10,7 +10,8 @@ module Invidious::Database::Videos
|
||||||
|
ON CONFLICT (id) DO NOTHING
|
||||||
|
SQL
|
||||||
|
|
||||||
|
- PG_DB.exec(request, video.id, video.info.to_json, video.updated)
|
||||||
|
+ REDIS_DB.set(video.id, video.info.to_json, ex: 3600)
|
||||||
|
+ REDIS_DB.set(video.id + ":time", video.updated, ex: 3600)
|
||||||
|
end
|
||||||
|
|
||||||
|
def delete(id)
|
||||||
|
@@ -19,7 +20,8 @@ module Invidious::Database::Videos
|
||||||
|
WHERE id = $1
|
||||||
|
SQL
|
||||||
|
|
||||||
|
- PG_DB.exec(request, id)
|
||||||
|
+ REDIS_DB.del(id)
|
||||||
|
+ REDIS_DB.del(id + ":time")
|
||||||
|
end
|
||||||
|
|
||||||
|
def delete_expired
|
||||||
|
@@ -47,6 +49,14 @@ module Invidious::Database::Videos
|
||||||
|
WHERE id = $1
|
||||||
|
SQL
|
||||||
|
|
||||||
|
- return PG_DB.query_one?(request, id, as: Video)
|
||||||
|
+ if ((info = REDIS_DB.get(id)) && (time = REDIS_DB.get(id + ":time")))
|
||||||
|
+ return Video.new({
|
||||||
|
+ id: id,
|
||||||
|
+ info: JSON.parse(info).as_h,
|
||||||
|
+ updated: Time.parse(time, "%Y-%m-%d %H:%M:%S %z", Time::Location::UTC),
|
||||||
|
+ })
|
||||||
|
+ else
|
||||||
|
+ return nil
|
||||||
|
+ end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
diff --git a/src/invidious/videos.cr b/src/invidious/videos.cr
|
||||||
|
index 6d0cf9ba..dde35291 100644
|
||||||
|
--- a/src/invidious/videos.cr
|
||||||
|
+++ b/src/invidious/videos.cr
|
||||||
|
@@ -400,7 +400,7 @@ def get_video(id, refresh = true, region = nil, force_refresh = false)
|
||||||
|
video.schema_version != Video::SCHEMA_VERSION # cache control
|
||||||
|
begin
|
||||||
|
video = fetch_video(id, region, env)
|
||||||
|
- Invidious::Database::Videos.update(video)
|
||||||
|
+ Invidious::Database::Videos.insert(video)
|
||||||
|
rescue ex
|
||||||
|
Invidious::Database::Videos.delete(id)
|
||||||
|
raise ex
|
||||||
|
--
|
||||||
|
2.46.0
|
||||||
|
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
diff --git a/src/invidious/yt_backend/youtube_api.cr b/src/invidious/yt_backend/youtube_api.cr
|
||||||
|
index 8f5aa61d..f97a8732 100644
|
||||||
|
--- a/src/invidious/yt_backend/youtube_api.cr
|
||||||
|
+++ b/src/invidious/yt_backend/youtube_api.cr
|
||||||
|
@@ -638,9 +638,7 @@ module YoutubeAPI
|
||||||
|
body = YT_POOL.client() do |client|
|
||||||
|
client.post(url, headers: headers, body: data.to_json) do |response|
|
||||||
|
if response.status_code != 200
|
||||||
|
- raise InfoException.new("Error: non 200 status code. Youtube API returned \
|
||||||
|
- status code #{response.status_code}. See <a href=\"https://docs.invidious.io/youtube-errors-explained/\"> \
|
||||||
|
- https://docs.invidious.io/youtube-errors-explained/</a> for troubleshooting.")
|
||||||
|
+ raise InfoException.new("<h2>Error loading video</h2><p style='font-size: 0.8em; margin: -20px 0 20px 0;'>Received error from YouTube: #{response.status_code}</p>")
|
||||||
|
end
|
||||||
|
self._decompress(response.body_io, response.headers["Content-Encoding"]?)
|
||||||
|
end
|
||||||
@@ -0,0 +1,793 @@
|
|||||||
|
diff --git a/locales/ar.json b/locales/ar.json
|
||||||
|
index 94103c29..427ddaf9 100644
|
||||||
|
--- a/locales/ar.json
|
||||||
|
+++ b/locales/ar.json
|
||||||
|
@@ -436,6 +436,7 @@
|
||||||
|
"Vietnamese (auto-generated)": "فيتنامي (تم إنشاؤه تلقائيًا)",
|
||||||
|
"crash_page_report_issue": "إذا لم يساعد أي مما سبق، يرجى فتح <a href=\"`x`\"> مشكلة جديدة على GitHub </a> (ويفضل أن يكون باللغة الإنجليزية) وتضمين النص التالي في رسالتك (لا تترجم هذا النص):",
|
||||||
|
"crash_page_read_the_faq": "قراءة <a href=\"`x`\"> الأسئلة المتكررة (الأسئلة الشائعة) </a>",
|
||||||
|
+ "preferences_hide_shorts_label": "إخفاء YouTube Shorts: ",
|
||||||
|
"preferences_watch_history_label": "تمكين سجل المشاهدة: ",
|
||||||
|
"English (United Kingdom)": "الإنجليزية (المملكة المتحدة)",
|
||||||
|
"Cantonese (Hong Kong)": "الكانتونية (هونغ كونغ)",
|
||||||
|
diff --git a/locales/bg.json b/locales/bg.json
|
||||||
|
index 5c99d98f..acf5a6f7 100644
|
||||||
|
--- a/locales/bg.json
|
||||||
|
+++ b/locales/bg.json
|
||||||
|
@@ -95,6 +95,7 @@
|
||||||
|
"search_filters_sort_option_rating": "Рейтинг",
|
||||||
|
"Manage subscriptions": "Управление на абонаментите",
|
||||||
|
"preferences_quality_dash_option_720p": "720p",
|
||||||
|
+ "preferences_hide_shorts_label": "Скрий YouTube Shorts: ",
|
||||||
|
"preferences_watch_history_label": "Активирай историята на гледане: ",
|
||||||
|
"user_saved_playlists": "`x` запази плейлисти",
|
||||||
|
"preferences_extend_desc_label": "Автоматично разшири описанието на видеото ",
|
||||||
|
diff --git a/locales/bn.json b/locales/bn.json
|
||||||
|
index 501a1ca3..e236bcc8 100644
|
||||||
|
--- a/locales/bn.json
|
||||||
|
+++ b/locales/bn.json
|
||||||
|
@@ -61,6 +61,7 @@
|
||||||
|
"generic_videos_count_plural": "{{count}}টি ভিডিও",
|
||||||
|
"generic_subscribers_count": "{{count}}জন অনুসরণকারী",
|
||||||
|
"generic_subscribers_count_plural": "{{count}}জন অনুসরণকারী",
|
||||||
|
+ "preferences_hide_shorts_label": "YouTube Shorts লুকান: ",
|
||||||
|
"preferences_watch_history_label": "দেখার ইতিহাস চালু করো: ",
|
||||||
|
"preferences_quality_option_dash": "ড্যাশ (সময়োপযোগী মান)",
|
||||||
|
"preferences_quality_dash_option_auto": "স্বয়ংক্রিয়",
|
||||||
|
diff --git a/locales/bn_BD.json b/locales/bn_BD.json
|
||||||
|
index a82b0da7..78c664fa 100644
|
||||||
|
--- a/locales/bn_BD.json
|
||||||
|
+++ b/locales/bn_BD.json
|
||||||
|
@@ -45,6 +45,7 @@
|
||||||
|
"Register": "নিবন্ধন",
|
||||||
|
"E-mail": "ই-মেইল",
|
||||||
|
"Preferences": "পছন্দসমূহ",
|
||||||
|
+ "preferences_hide_shorts_label": "YouTube Shorts লুকান: ",
|
||||||
|
"preferences_category_player": "প্লেয়ারের পছন্দসমূহ",
|
||||||
|
"preferences_video_loop_label": "সর্বদা লুপ: ",
|
||||||
|
"preferences_autoplay_label": "স্বয়ংক্রিয় চালু: ",
|
||||||
|
diff --git a/locales/ca.json b/locales/ca.json
|
||||||
|
index 474d6a3c..2c442c81 100644
|
||||||
|
--- a/locales/ca.json
|
||||||
|
+++ b/locales/ca.json
|
||||||
|
@@ -443,6 +443,7 @@
|
||||||
|
"Trending": "Tendència",
|
||||||
|
"Updated `x` ago": "Actualitzat fa `x`",
|
||||||
|
"Haitian Creole": "Crioll Haitià",
|
||||||
|
+ "preferences_hide_shorts_label": "Amaga YouTube Shorts: ",
|
||||||
|
"preferences_watch_history_label": "Habilita historial de reproduccions: ",
|
||||||
|
"generic_count_hours": "{{count}} hora",
|
||||||
|
"generic_count_hours_plural": "{{count}} hores",
|
||||||
|
diff --git a/locales/cs.json b/locales/cs.json
|
||||||
|
index 41f3db5c..08bb3f8a 100644
|
||||||
|
--- a/locales/cs.json
|
||||||
|
+++ b/locales/cs.json
|
||||||
|
@@ -338,6 +338,7 @@
|
||||||
|
"generic_subscribers_count_0": "{{count}} odběratel",
|
||||||
|
"generic_subscribers_count_1": "{{count}} odběratelé",
|
||||||
|
"generic_subscribers_count_2": "{{count}} odběratelů",
|
||||||
|
+ "preferences_hide_shorts_label": "Skrýt YouTube Shorts: ",
|
||||||
|
"preferences_watch_history_label": "Povolit historii sledování: ",
|
||||||
|
"preferences_quality_dash_option_240p": "240p",
|
||||||
|
"preferences_region_label": "Země obsahu: ",
|
||||||
|
diff --git a/locales/cy.json b/locales/cy.json
|
||||||
|
index eb391572..55bcc3d8 100644
|
||||||
|
--- a/locales/cy.json
|
||||||
|
+++ b/locales/cy.json
|
||||||
|
@@ -151,6 +151,7 @@
|
||||||
|
"preferences_category_player": "Hoffterau'r chwaraeydd",
|
||||||
|
"preferences_autoplay_label": "Chwarae'n awtomatig: ",
|
||||||
|
"preferences_local_label": "Llwytho fideos drwy ddirprwy weinydd: ",
|
||||||
|
+ "preferences_hide_shorts_label": "Cuddio YouTube Shorts: ",
|
||||||
|
"preferences_watch_history_label": "Galluogi hanes gwylio: ",
|
||||||
|
"preferences_speed_label": "Cyflymder rhagosodedig: ",
|
||||||
|
"preferences_quality_label": "Ansawdd fideos: ",
|
||||||
|
diff --git a/locales/da.json b/locales/da.json
|
||||||
|
index 9cbb446a..86e3445d 100644
|
||||||
|
--- a/locales/da.json
|
||||||
|
+++ b/locales/da.json
|
||||||
|
@@ -418,6 +418,7 @@
|
||||||
|
"crash_page_refresh": "forsøgte at <a href=\"`x`\">opdatere siden</a>",
|
||||||
|
"generic_playlists_count": "{{count}} spilleliste",
|
||||||
|
"generic_playlists_count_plural": "{{count}} spillelister",
|
||||||
|
+ "preferences_hide_shorts_label": "Skjul YouTube Shorts: ",
|
||||||
|
"preferences_watch_history_label": "Aktiver afspilningshistorik: ",
|
||||||
|
"tokens_count": "{{count}} token",
|
||||||
|
"tokens_count_plural": "{{count}} tokens",
|
||||||
|
diff --git a/locales/de.json b/locales/de.json
|
||||||
|
index e51d40b9..70ab245f 100644
|
||||||
|
--- a/locales/de.json
|
||||||
|
+++ b/locales/de.json
|
||||||
|
@@ -432,6 +432,7 @@
|
||||||
|
"Chinese (Hong Kong)": "Chinesisch (Hong Kong)",
|
||||||
|
"generic_playlists_count": "{{count}} Wiedergabeliste",
|
||||||
|
"generic_playlists_count_plural": "{{count}} Wiedergabelisten",
|
||||||
|
+ "preferences_hide_shorts_label": "YouTube-Shorts ausblenden: ",
|
||||||
|
"preferences_watch_history_label": "Wiedergabeverlauf aktivieren: ",
|
||||||
|
"English (United Kingdom)": "Englisch (Vereinigtes Königreich)",
|
||||||
|
"English (United States)": "Englisch (Vereinigte Staaten)",
|
||||||
|
diff --git a/locales/el.json b/locales/el.json
|
||||||
|
index e5a89a44..47ff3239 100644
|
||||||
|
--- a/locales/el.json
|
||||||
|
+++ b/locales/el.json
|
||||||
|
@@ -440,6 +440,7 @@
|
||||||
|
"videoinfo_youTube_embed_link": "Ενσωμάτωση",
|
||||||
|
"videoinfo_invidious_embed_link": "Σύνδεσμος Ενσωμάτωσης",
|
||||||
|
"search_filters_type_option_show": "Μπάρα προόδου διαβάσματος",
|
||||||
|
+ "preferences_hide_shorts_label": "Απόκρυψη YouTube Shorts: ",
|
||||||
|
"preferences_watch_history_label": "Ενεργοποίηση ιστορικού παρακολούθησης: ",
|
||||||
|
"search_filters_title": "Φίλτρο",
|
||||||
|
"search_message_no_results": "Δε βρέθηκαν αποτελέσματα.",
|
||||||
|
diff --git a/locales/en-US.json b/locales/en-US.json
|
||||||
|
index 0b752d93..401ab9f2 100644
|
||||||
|
--- a/locales/en-US.json
|
||||||
|
+++ b/locales/en-US.json
|
||||||
|
@@ -76,6 +76,7 @@
|
||||||
|
"preferences_continue_autoplay_label": "Autoplay next video: ",
|
||||||
|
"preferences_listen_label": "Listen by default: ",
|
||||||
|
"preferences_local_label": "Proxy videos: ",
|
||||||
|
+ "preferences_hide_shorts_label": "Hide YouTube-Shorts: ",
|
||||||
|
"preferences_watch_history_label": "Enable watch history: ",
|
||||||
|
"preferences_speed_label": "Default speed: ",
|
||||||
|
"preferences_quality_label": "Preferred video quality: ",
|
||||||
|
diff --git a/locales/eo.json b/locales/eo.json
|
||||||
|
index 7276c890..d5f3b7d4 100644
|
||||||
|
--- a/locales/eo.json
|
||||||
|
+++ b/locales/eo.json
|
||||||
|
@@ -425,6 +425,7 @@
|
||||||
|
"comments_points_count": "{{count}} poento",
|
||||||
|
"comments_points_count_plural": "{{count}} poentoj",
|
||||||
|
"Cantonese (Hong Kong)": "Kantona (Honkongo)",
|
||||||
|
+ "preferences_hide_shorts_label": "Kaŝi YouTube Shorts: ",
|
||||||
|
"preferences_watch_history_label": "Ebligi vidohistorion: ",
|
||||||
|
"preferences_quality_option_small": "Eta",
|
||||||
|
"generic_playlists_count": "{{count}} ludlisto",
|
||||||
|
diff --git a/locales/es.json b/locales/es.json
|
||||||
|
index 46217943..1ce51dd6 100644
|
||||||
|
--- a/locales/es.json
|
||||||
|
+++ b/locales/es.json
|
||||||
|
@@ -465,6 +465,7 @@
|
||||||
|
"Korean (auto-generated)": "Coreano (generados automáticamente)",
|
||||||
|
"Spanish (Mexico)": "Español (México)",
|
||||||
|
"Spanish (auto-generated)": "Español (generados automáticamente)",
|
||||||
|
+ "preferences_hide_shorts_label": "Ocultar YouTube Shorts: ",
|
||||||
|
"preferences_watch_history_label": "Habilitar historial de reproducciones: ",
|
||||||
|
"search_message_no_results": "No se han encontrado resultados.",
|
||||||
|
"search_message_change_filters_or_query": "Pruebe ampliar la consulta de búsqueda y/o a cambiar los filtros.",
|
||||||
|
diff --git a/locales/et.json b/locales/et.json
|
||||||
|
index 7f652810..f2a41b92 100644
|
||||||
|
--- a/locales/et.json
|
||||||
|
+++ b/locales/et.json
|
||||||
|
@@ -46,6 +46,7 @@
|
||||||
|
"Register": "Registreeru",
|
||||||
|
"E-mail": "E-post",
|
||||||
|
"Preferences": "Eelistused",
|
||||||
|
+ "preferences_hide_shorts_label": "Peida YouTube Shorts: ",
|
||||||
|
"preferences_category_player": "Mängija eelistused",
|
||||||
|
"preferences_continue_autoplay_label": "Mängi järgmine video automaatselt: ",
|
||||||
|
"preferences_quality_label": "Eelistatud videokvaliteet: ",
|
||||||
|
diff --git a/locales/eu.json b/locales/eu.json
|
||||||
|
index fbca537b..11e5972b 100644
|
||||||
|
--- a/locales/eu.json
|
||||||
|
+++ b/locales/eu.json
|
||||||
|
@@ -119,6 +119,7 @@
|
||||||
|
"Belarusian": "Bielorrusiara",
|
||||||
|
"Burmese": "Burmesera",
|
||||||
|
"Chinese (Simplified)": "Txinera (sinplifikatua)",
|
||||||
|
+ "preferences_hide_shorts_label": "Ezkutatu YouTube Shorts: ",
|
||||||
|
"preferences_watch_history_label": "Baimendu historia ikusi ",
|
||||||
|
"generic_videos_count": "{{count}}bideo",
|
||||||
|
"generic_videos_count_plural": "{{count}}bideoak",
|
||||||
|
diff --git a/locales/fa.json b/locales/fa.json
|
||||||
|
index 2326370d..4662c771 100644
|
||||||
|
--- a/locales/fa.json
|
||||||
|
+++ b/locales/fa.json
|
||||||
|
@@ -422,6 +422,7 @@
|
||||||
|
"search_filters_title": "پالایه",
|
||||||
|
"Chinese (Hong Kong)": "چینی (هنگکنگ)",
|
||||||
|
"Dutch (auto-generated)": "هلندی (تولید خودکار)",
|
||||||
|
+ "preferences_hide_shorts_label": "مخفی کردن YouTube Shorts: ",
|
||||||
|
"preferences_watch_history_label": "فعالسازی تاریخچهی پخش ",
|
||||||
|
"Indonesian (auto-generated)": "اندونزیایی (تولید خودکار)",
|
||||||
|
"English (United States)": "انگلیسی (ایالات متحده)",
|
||||||
|
diff --git a/locales/fi.json b/locales/fi.json
|
||||||
|
index 13fef6de..804aa4c1 100644
|
||||||
|
--- a/locales/fi.json
|
||||||
|
+++ b/locales/fi.json
|
||||||
|
@@ -432,6 +432,7 @@
|
||||||
|
"German (auto-generated)": "saksa (automaattisesti luotu)",
|
||||||
|
"Portuguese (auto-generated)": "portugali (automaattisesti luotu)",
|
||||||
|
"Russian (auto-generated)": "Venäjä (automaattisesti luotu)",
|
||||||
|
+ "preferences_hide_shorts_label": "Piilota YouTube Shorts: ",
|
||||||
|
"preferences_watch_history_label": "Ota katseluhistoria käyttöön: ",
|
||||||
|
"English (United Kingdom)": "englanti (Iso-Britannia)",
|
||||||
|
"English (United States)": "englanti (Yhdysvallat)",
|
||||||
|
diff --git a/locales/fr.json b/locales/fr.json
|
||||||
|
index 49aa09df..3076623a 100644
|
||||||
|
--- a/locales/fr.json
|
||||||
|
+++ b/locales/fr.json
|
||||||
|
@@ -477,6 +477,7 @@
|
||||||
|
"Vietnamese (auto-generated)": "Vietnamien (auto-généré)",
|
||||||
|
"Russian (auto-generated)": "Russe (auto-généré)",
|
||||||
|
"Spanish (Spain)": "Espagnol (Espagne)",
|
||||||
|
+ "preferences_hide_shorts_label": "Masquer YouTube Shorts: ",
|
||||||
|
"preferences_watch_history_label": "Activer l'historique de visionnage : ",
|
||||||
|
"search_filters_title": "Filtres",
|
||||||
|
"search_message_change_filters_or_query": "Essayez d'élargir votre recherche et/ou de changer les filtres.",
|
||||||
|
diff --git a/locales/he.json b/locales/he.json
|
||||||
|
index 6fee93b2..62c19b02 100644
|
||||||
|
--- a/locales/he.json
|
||||||
|
+++ b/locales/he.json
|
||||||
|
@@ -45,6 +45,7 @@
|
||||||
|
"Register": "הרשמה",
|
||||||
|
"E-mail": "דוא״ל",
|
||||||
|
"Preferences": "העדפות",
|
||||||
|
+ "preferences_hide_shorts_label": "הסתר YouTube Shorts: ",
|
||||||
|
"preferences_category_player": "העדפות הנגן",
|
||||||
|
"preferences_autoplay_label": "ניגון אוטומטי: ",
|
||||||
|
"preferences_continue_label": "ניגון הסרטון הבא כברירת מחדל: ",
|
||||||
|
diff --git a/locales/hi.json b/locales/hi.json
|
||||||
|
index 0a1c09dd..63afc443 100644
|
||||||
|
--- a/locales/hi.json
|
||||||
|
+++ b/locales/hi.json
|
||||||
|
@@ -90,6 +90,7 @@
|
||||||
|
"preferences_continue_autoplay_label": "अगला वीडियो अपने आप चलाएँ: ",
|
||||||
|
"preferences_listen_label": "डिफ़ॉल्ट से सुनें: ",
|
||||||
|
"preferences_local_label": "प्रॉक्सी वीडियो: ",
|
||||||
|
+ "preferences_hide_shorts_label": "YouTube Shorts छुपाएं: ",
|
||||||
|
"preferences_watch_history_label": "देखने का इतिहास सक्षम करें: ",
|
||||||
|
"preferences_speed_label": "वीडियो चलाने की डिफ़ॉल्ट रफ़्तार: ",
|
||||||
|
"preferences_quality_label": "वीडियो की प्राथमिक क्वालिटी: ",
|
||||||
|
diff --git a/locales/hr.json b/locales/hr.json
|
||||||
|
index 6adbcdc3..7b1d701d 100644
|
||||||
|
--- a/locales/hr.json
|
||||||
|
+++ b/locales/hr.json
|
||||||
|
@@ -467,6 +467,7 @@
|
||||||
|
"Korean (auto-generated)": "Korejski (automatski generirano)",
|
||||||
|
"Portuguese (auto-generated)": "Portugalski (automatski generirano)",
|
||||||
|
"Spanish (auto-generated)": "Španjolski (automatski generirano)",
|
||||||
|
+ "preferences_hide_shorts_label": "Sakrij YouTube Shorts: ",
|
||||||
|
"preferences_watch_history_label": "Aktiviraj povijest gledanja: ",
|
||||||
|
"search_filters_title": "Filtri",
|
||||||
|
"search_filters_date_option_none": "Bilo koji datum",
|
||||||
|
diff --git a/locales/hu-HU.json b/locales/hu-HU.json
|
||||||
|
index 8fbdd82f..2571c9d9 100644
|
||||||
|
--- a/locales/hu-HU.json
|
||||||
|
+++ b/locales/hu-HU.json
|
||||||
|
@@ -452,6 +452,7 @@
|
||||||
|
"French (auto-generated)": "francia (automatikusan generált)",
|
||||||
|
"Vietnamese (auto-generated)": "vietnámi (automatikusan generált)",
|
||||||
|
"search_filters_title": "Szűrők",
|
||||||
|
+ "preferences_hide_shorts_label": "YouTube Shorts elrejtése: ",
|
||||||
|
"preferences_watch_history_label": "Megnézett videók naplózása: ",
|
||||||
|
"search_message_no_results": "Nincs találat.",
|
||||||
|
"search_message_change_filters_or_query": "Próbálj meg bővebben rákeresni vagy a szűrőkön állítani.",
|
||||||
|
diff --git a/locales/ia.json b/locales/ia.json
|
||||||
|
index 236ec4b4..84c29150 100644
|
||||||
|
--- a/locales/ia.json
|
||||||
|
+++ b/locales/ia.json
|
||||||
|
@@ -25,6 +25,7 @@
|
||||||
|
"youtube": "YouTube",
|
||||||
|
"LIVE": "IN DIRECTO",
|
||||||
|
"reddit": "Reddit",
|
||||||
|
+ "preferences_hide_shorts_label": "Celar YouTube Shorts: ",
|
||||||
|
"preferences_category_player": "Preferentias de reproductor",
|
||||||
|
"Preferences": "Preferentias",
|
||||||
|
"preferences_quality_dash_option_auto": "Automatic",
|
||||||
|
diff --git a/locales/id.json b/locales/id.json
|
||||||
|
index 4c6e8548..6f9870de 100644
|
||||||
|
--- a/locales/id.json
|
||||||
|
+++ b/locales/id.json
|
||||||
|
@@ -408,6 +408,7 @@
|
||||||
|
"crash_page_you_found_a_bug": "Sepertinya kamu telah menemukan masalah di invidious!",
|
||||||
|
"crash_page_before_reporting": "Sebelum melaporkan masalah, pastikan anda memiliki:",
|
||||||
|
"English (United States)": "Inggris (US)",
|
||||||
|
+ "preferences_hide_shorts_label": "Sembunyikan YouTube Shorts: ",
|
||||||
|
"preferences_watch_history_label": "Aktifkan riwayat tontonan: ",
|
||||||
|
"English (United Kingdom)": "Inggris (UK)",
|
||||||
|
"search_filters_title": "Saring",
|
||||||
|
diff --git a/locales/is.json b/locales/is.json
|
||||||
|
index 28cacf31..264cb5f5 100644
|
||||||
|
--- a/locales/is.json
|
||||||
|
+++ b/locales/is.json
|
||||||
|
@@ -311,6 +311,7 @@
|
||||||
|
"Playlists": "Spilunarlistar",
|
||||||
|
"channel_tab_community_label": "Samfélag",
|
||||||
|
"Current version: ": "Núverandi útgáfa: ",
|
||||||
|
+ "preferences_hide_shorts_label": "Fela YouTube Shorts: ",
|
||||||
|
"preferences_watch_history_label": "Virkja áhorfsferil: ",
|
||||||
|
"Chinese (China)": "Kínverska (Kína)",
|
||||||
|
"Turkish (auto-generated)": "Tyrkneska (sjálfvirkt útbúið)",
|
||||||
|
diff --git a/locales/it.json b/locales/it.json
|
||||||
|
index c7143ef6..15c2b85e 100644
|
||||||
|
--- a/locales/it.json
|
||||||
|
+++ b/locales/it.json
|
||||||
|
@@ -447,6 +447,7 @@
|
||||||
|
"Popular enabled: ": "Popolare attivato: ",
|
||||||
|
"English (United Kingdom)": "Inglese (Regno Unito)",
|
||||||
|
"Portuguese (Brazil)": "Portoghese (Brasile)",
|
||||||
|
+ "preferences_hide_shorts_label": "Nascondi YouTube Shorts: ",
|
||||||
|
"preferences_watch_history_label": "Attiva cronologia di riproduzione: ",
|
||||||
|
"French (auto-generated)": "Francese (generati automaticamente)",
|
||||||
|
"search_message_use_another_instance": "Puoi anche <a href=\"`x`\">cercare in un'altra istanza</a>.",
|
||||||
|
diff --git a/locales/ja.json b/locales/ja.json
|
||||||
|
index c4b82486..37c98713 100644
|
||||||
|
--- a/locales/ja.json
|
||||||
|
+++ b/locales/ja.json
|
||||||
|
@@ -439,6 +439,7 @@
|
||||||
|
"user_saved_playlists": "`x`個の保存済みの再生リスト",
|
||||||
|
"crash_page_you_found_a_bug": "Invidious のバグのようです!",
|
||||||
|
"crash_page_refresh": "<a href=\"`x`\">ページを更新</a>を試す",
|
||||||
|
+ "preferences_hide_shorts_label": "YouTube Shorts を非表示にする: ",
|
||||||
|
"preferences_watch_history_label": "再生履歴を有効化 ",
|
||||||
|
"search_filters_date_option_none": "すべて",
|
||||||
|
"search_filters_type_option_all": "すべての種類",
|
||||||
|
diff --git a/locales/ko.json b/locales/ko.json
|
||||||
|
index 0224955f..d5a38de7 100644
|
||||||
|
--- a/locales/ko.json
|
||||||
|
+++ b/locales/ko.json
|
||||||
|
@@ -407,6 +407,7 @@
|
||||||
|
"Spanish (auto-generated)": "스페인어 (자동 생성됨)",
|
||||||
|
"preferences_quality_dash_option_1080p": "1080p",
|
||||||
|
"preferences_quality_dash_option_worst": "최저",
|
||||||
|
+ "preferences_hide_shorts_label": "YouTube Shorts 숨기기: ",
|
||||||
|
"preferences_watch_history_label": "시청 기록 저장: ",
|
||||||
|
"invidious": "인비디어스",
|
||||||
|
"preferences_quality_option_small": "낮음",
|
||||||
|
diff --git a/locales/lmo.json b/locales/lmo.json
|
||||||
|
index 9d2fe2a8..cce7774a 100644
|
||||||
|
--- a/locales/lmo.json
|
||||||
|
+++ b/locales/lmo.json
|
||||||
|
@@ -183,6 +183,7 @@
|
||||||
|
"preferences_continue_autoplay_label": "Fa partì en automatico el video seguént: ",
|
||||||
|
"preferences_listen_label": "Modalità de sól audio preimpostà: ",
|
||||||
|
"preferences_local_label": "Proxy par i video: ",
|
||||||
|
+ "preferences_hide_shorts_label": "Scund YouTube Shorts: ",
|
||||||
|
"preferences_watch_history_label": "Ativà la istoria de reproduzion: ",
|
||||||
|
"preferences_speed_label": "Velocità preimpostà: ",
|
||||||
|
"preferences_volume_label": "Volume del reprodutòr: ",
|
||||||
|
diff --git a/locales/lt.json b/locales/lt.json
|
||||||
|
index 740be7b6..7a9c3455 100644
|
||||||
|
--- a/locales/lt.json
|
||||||
|
+++ b/locales/lt.json
|
||||||
|
@@ -373,6 +373,7 @@
|
||||||
|
"generic_subscriptions_count_0": "{{count}} prenumerata",
|
||||||
|
"generic_subscriptions_count_1": "{{count}} prenumeratos",
|
||||||
|
"generic_subscriptions_count_2": "{{count}} prenumeratų",
|
||||||
|
+ "preferences_hide_shorts_label": "Slėpti YouTube Shorts: ",
|
||||||
|
"preferences_watch_history_label": "Įgalinti žiūrėjimo istoriją: ",
|
||||||
|
"preferences_quality_dash_option_1080p": "1080p",
|
||||||
|
"invidious": "Invidious",
|
||||||
|
diff --git a/locales/lv.json b/locales/lv.json
|
||||||
|
index a867c8f3..b53707a4 100644
|
||||||
|
--- a/locales/lv.json
|
||||||
|
+++ b/locales/lv.json
|
||||||
|
@@ -27,6 +27,7 @@
|
||||||
|
"Import YouTube subscriptions": "Ievietot YouTube CSV vai OPML abonementus",
|
||||||
|
"E-mail": "E-pasts",
|
||||||
|
"Preferences": "Iestatījumi",
|
||||||
|
+ "preferences_hide_shorts_label": "Paslēpt YouTube Shorts: ",
|
||||||
|
"preferences_category_player": "Atskaņotāja iestatījumi",
|
||||||
|
"preferences_quality_option_hd720": "HD - 720p",
|
||||||
|
"preferences_quality_option_medium": "Vidēja",
|
||||||
|
diff --git a/locales/nb-NO.json b/locales/nb-NO.json
|
||||||
|
index 38402fed..a81744e6 100644
|
||||||
|
--- a/locales/nb-NO.json
|
||||||
|
+++ b/locales/nb-NO.json
|
||||||
|
@@ -436,6 +436,7 @@
|
||||||
|
"Spanish (Spain)": "Spansk (Spania)",
|
||||||
|
"Spanish (auto-generated)": "Spansk (laget automatisk)",
|
||||||
|
"Vietnamese (auto-generated)": "Vietnamesisk (laget automatisk)",
|
||||||
|
+ "preferences_hide_shorts_label": "Skjul YouTube Shorts: ",
|
||||||
|
"preferences_watch_history_label": "Aktiver seerhistorikk: ",
|
||||||
|
"Chinese": "Kinesisk",
|
||||||
|
"Chinese (China)": "Kinesisk (Kina)",
|
||||||
|
diff --git a/locales/nl.json b/locales/nl.json
|
||||||
|
index e9ce7674..507bcb7c 100644
|
||||||
|
--- a/locales/nl.json
|
||||||
|
+++ b/locales/nl.json
|
||||||
|
@@ -409,6 +409,7 @@
|
||||||
|
"generic_subscriptions_count_plural": "{{count}} abonnementen",
|
||||||
|
"subscriptions_unseen_notifs_count": "{{count}} ongeziene melding",
|
||||||
|
"subscriptions_unseen_notifs_count_plural": "{{count}} ongeziene meldingen",
|
||||||
|
+ "preferences_hide_shorts_label": "Verberg YouTube Shorts: ",
|
||||||
|
"preferences_watch_history_label": "Kijkgeschiedenis inschakelen: ",
|
||||||
|
"crash_page_switch_instance": "geprobeerd hebt om <a href=\"`x`\">een andere instantie te gebruiken</a>",
|
||||||
|
"Portuguese (auto-generated)": "Portugees (automatisch gegenereerd)",
|
||||||
|
diff --git a/locales/or.json b/locales/or.json
|
||||||
|
index 948610f1..ba7e9d56 100644
|
||||||
|
--- a/locales/or.json
|
||||||
|
+++ b/locales/or.json
|
||||||
|
@@ -16,6 +16,7 @@
|
||||||
|
"last": "ଗତ",
|
||||||
|
"New password": "ନୂଆ ପାସ୍ୱର୍ଡ଼",
|
||||||
|
"preferences_quality_dash_option_1440p": "୧୪୪୦ପି",
|
||||||
|
+ "preferences_hide_shorts_label": "YouTube Shorts ଲୁଚାନ୍ତୁ: ",
|
||||||
|
"preferences_quality_dash_option_360p": "୩୬୦ପି",
|
||||||
|
"preferences_quality_option_medium": "ମଧ୍ୟମ",
|
||||||
|
"preferences_quality_dash_option_1080p": "୧୦୮୦ପି",
|
||||||
|
diff --git a/locales/pl.json b/locales/pl.json
|
||||||
|
index d78b7a95..d1dcc62c 100644
|
||||||
|
--- a/locales/pl.json
|
||||||
|
+++ b/locales/pl.json
|
||||||
|
@@ -472,6 +472,7 @@
|
||||||
|
"error_video_not_in_playlist": "Żądany film nie istnieje na tej playliście. <a href=\"`x`\">Kliknij tutaj, aby przejść do strony głównej playlisty.</a>",
|
||||||
|
"Popular enabled: ": "Popularne włączone: ",
|
||||||
|
"search_message_no_results": "Nie znaleziono wyników.",
|
||||||
|
+ "preferences_hide_shorts_label": "Ukryj YouTube Shorts: ",
|
||||||
|
"preferences_watch_history_label": "Włącz historię oglądania: ",
|
||||||
|
"search_filters_apply_button": "Zastosuj wybrane filtry",
|
||||||
|
"search_message_change_filters_or_query": "Spróbuj poszerzyć zapytanie wyszukiwania i/lub zmienić filtry.",
|
||||||
|
diff --git a/locales/pt-BR.json b/locales/pt-BR.json
|
||||||
|
index 1eb3c989..77b4ee4a 100644
|
||||||
|
--- a/locales/pt-BR.json
|
||||||
|
+++ b/locales/pt-BR.json
|
||||||
|
@@ -445,6 +445,7 @@
|
||||||
|
"Video unavailable": "Vídeo indisponível",
|
||||||
|
"videoinfo_started_streaming_x_ago": "Iniciou a transmissão a `x`",
|
||||||
|
"search_filters_title": "Filtro",
|
||||||
|
+ "preferences_hide_shorts_label": "Ocultar YouTube Shorts: ",
|
||||||
|
"preferences_watch_history_label": "Ativar histórico de exibição: ",
|
||||||
|
"search_message_no_results": "Nenhum resultado encontrado.",
|
||||||
|
"search_message_change_filters_or_query": "Tente ampliar sua consulta de pesquisa e/ou alterar os filtros.",
|
||||||
|
diff --git a/locales/pt-PT.json b/locales/pt-PT.json
|
||||||
|
index 449bde77..3538bb1a 100644
|
||||||
|
--- a/locales/pt-PT.json
|
||||||
|
+++ b/locales/pt-PT.json
|
||||||
|
@@ -402,6 +402,7 @@
|
||||||
|
"preferences_region_label": "País do conteúdo: ",
|
||||||
|
"preferences_quality_dash_option_1440p": "1440p",
|
||||||
|
"preferences_quality_dash_option_720p": "720p",
|
||||||
|
+ "preferences_hide_shorts_label": "Ocultar YouTube Shorts: ",
|
||||||
|
"preferences_watch_history_label": "Ativar histórico de reprodução: ",
|
||||||
|
"preferences_quality_dash_option_best": "Melhor",
|
||||||
|
"preferences_quality_dash_option_worst": "Pior",
|
||||||
|
diff --git a/locales/pt.json b/locales/pt.json
|
||||||
|
index 6438e15b..397aa006 100644
|
||||||
|
--- a/locales/pt.json
|
||||||
|
+++ b/locales/pt.json
|
||||||
|
@@ -471,6 +471,7 @@
|
||||||
|
"search_filters_apply_button": "Aplicar filtros selecionados",
|
||||||
|
"Spanish (auto-generated)": "Espanhol (gerado automaticamente)",
|
||||||
|
"Spanish (Mexico)": "Espanhol (México)",
|
||||||
|
+ "preferences_hide_shorts_label": "Ocultar YouTube Shorts: ",
|
||||||
|
"preferences_watch_history_label": "Ativar histórico de reprodução: ",
|
||||||
|
"Chinese (China)": "Chinês (China)",
|
||||||
|
"Russian (auto-generated)": "Russo (gerado automaticamente)",
|
||||||
|
diff --git a/locales/ro.json b/locales/ro.json
|
||||||
|
index ccbeef63..6c39b1f8 100644
|
||||||
|
--- a/locales/ro.json
|
||||||
|
+++ b/locales/ro.json
|
||||||
|
@@ -329,6 +329,7 @@
|
||||||
|
"subscriptions_unseen_notifs_count_2": "{{count}} de notificări neverificate",
|
||||||
|
"crash_page_refresh": "încercat să <a href=\"`x`\">reîmprospătați pagina</a>",
|
||||||
|
"crash_page_switch_instance": "am încercat să <a href=\"`x`\">folosim o altă instanță</a>",
|
||||||
|
+ "preferences_hide_shorts_label": "Ascunde YouTube Shorts: ",
|
||||||
|
"preferences_watch_history_label": "Activează istoricul: ",
|
||||||
|
"invidious": "Invidious",
|
||||||
|
"preferences_vr_mode_label": "Videoclipuri interactive de 360 de grade (necesită WebGL): ",
|
||||||
|
diff --git a/locales/ru.json b/locales/ru.json
|
||||||
|
index 906f00fc..a42a9d82 100644
|
||||||
|
--- a/locales/ru.json
|
||||||
|
+++ b/locales/ru.json
|
||||||
|
@@ -468,6 +468,7 @@
|
||||||
|
"Video unavailable": "Видео недоступно",
|
||||||
|
"preferences_save_player_pos_label": "Запоминать позицию: ",
|
||||||
|
"preferences_region_label": "Страна источник ",
|
||||||
|
+ "preferences_hide_shorts_label": "Скрыть YouTube Shorts: ",
|
||||||
|
"preferences_watch_history_label": "Включить историю просмотров: ",
|
||||||
|
"search_filters_title": "Фильтр",
|
||||||
|
"search_filters_duration_option_none": "Любой длины",
|
||||||
|
diff --git a/locales/si.json b/locales/si.json
|
||||||
|
index 4637cbd2..f9103ea5 100644
|
||||||
|
--- a/locales/si.json
|
||||||
|
+++ b/locales/si.json
|
||||||
|
@@ -40,6 +40,7 @@
|
||||||
|
"preferences_continue_label": "මීලඟට වාදනය කරන්න: ",
|
||||||
|
"preferences_continue_autoplay_label": "මීළඟ වීඩියෝව ස්වයංක්රීයව ධාවනය කරන්න: ",
|
||||||
|
"preferences_local_label": "Proxy වීඩියෝ: ",
|
||||||
|
+ "preferences_hide_shorts_label": "YouTube Shorts සඟවන්න: ",
|
||||||
|
"preferences_watch_history_label": "නැරඹුම් ඉතිහාසය සබල කරන්න: ",
|
||||||
|
"preferences_speed_label": "පෙරනිමි වේගය: ",
|
||||||
|
"preferences_quality_option_dash": "DASH (අනුවර්තිත ගුණත්වය)",
|
||||||
|
diff --git a/locales/sk.json b/locales/sk.json
|
||||||
|
index 8add0f57..e90a0852 100644
|
||||||
|
--- a/locales/sk.json
|
||||||
|
+++ b/locales/sk.json
|
||||||
|
@@ -108,6 +108,7 @@
|
||||||
|
"preferences_quality_dash_label": "Preferovaná video kvalita DASH: ",
|
||||||
|
"preferences_quality_option_dash": "DASH (adaptívna kvalita)",
|
||||||
|
"preferences_quality_option_small": "Malá",
|
||||||
|
+ "preferences_hide_shorts_label": "Skryť YouTube Shorts: ",
|
||||||
|
"preferences_watch_history_label": "Zapnúť históriu pozerania: ",
|
||||||
|
"preferences_quality_dash_option_240p": "240p",
|
||||||
|
"preferences_quality_dash_option_1080p": "1080p",
|
||||||
|
diff --git a/locales/sl.json b/locales/sl.json
|
||||||
|
index c36ad522..11465ae4 100644
|
||||||
|
--- a/locales/sl.json
|
||||||
|
+++ b/locales/sl.json
|
||||||
|
@@ -272,6 +272,7 @@
|
||||||
|
"An alternative front-end to YouTube": "Alternativni vmesnik za YouTube",
|
||||||
|
"preferences_category_player": "Nastavitve predvajalnika",
|
||||||
|
"preferences_continue_label": "Privzeto predvajaj naslednjega: ",
|
||||||
|
+ "preferences_hide_shorts_label": "Skrij YouTube Shorts: ",
|
||||||
|
"preferences_watch_history_label": "Omogoči zgodovino ogledov: ",
|
||||||
|
"preferences_quality_option_medium": "srednja",
|
||||||
|
"preferences_quality_option_dash": "DASH (prilagodljiva kakovost)",
|
||||||
|
diff --git a/locales/sq.json b/locales/sq.json
|
||||||
|
index cdf4b605..6de0e267 100644
|
||||||
|
--- a/locales/sq.json
|
||||||
|
+++ b/locales/sq.json
|
||||||
|
@@ -444,6 +444,7 @@
|
||||||
|
"error_video_not_in_playlist": "Videoja e kërkuar s’ekziston në këtë luajlistë. <a href=\"`x`\">Klikoni këtu për faqen hyrëse të luajlistës.</a>",
|
||||||
|
"search_message_use_another_instance": "Mundeni edhe të <a href=\"`x`\">kërkoni në një instancë tjetër</a>.",
|
||||||
|
"search_filters_date_label": "Datë ngarkimi",
|
||||||
|
+ "preferences_hide_shorts_label": "Fshih YouTube Shorts: ",
|
||||||
|
"preferences_watch_history_label": "Aktivizo historik parjesh: ",
|
||||||
|
"Top enabled: ": "Me kryesueset të aktivizuara: ",
|
||||||
|
"preferences_video_loop_label": "Përsërite gjithmonë: ",
|
||||||
|
diff --git a/locales/sr.json b/locales/sr.json
|
||||||
|
index c6614ba5..4c9f98da 100644
|
||||||
|
--- a/locales/sr.json
|
||||||
|
+++ b/locales/sr.json
|
||||||
|
@@ -394,6 +394,7 @@
|
||||||
|
"generic_count_minutes_1": "{{count}} minuta",
|
||||||
|
"generic_count_minutes_2": "{{count}} minuta",
|
||||||
|
"preferences_quality_dash_option_720p": "720p",
|
||||||
|
+ "preferences_hide_shorts_label": "Сакриј YouTube Shorts: ",
|
||||||
|
"preferences_watch_history_label": "Omogući istoriju gledanja: ",
|
||||||
|
"user_saved_playlists": "Sačuvanih plejlista: `x`",
|
||||||
|
"Spanish (Spain)": "Španski (Španija)",
|
||||||
|
diff --git a/locales/sr_Cyrl.json b/locales/sr_Cyrl.json
|
||||||
|
index e6ab0f35..1d52b578 100644
|
||||||
|
--- a/locales/sr_Cyrl.json
|
||||||
|
+++ b/locales/sr_Cyrl.json
|
||||||
|
@@ -394,6 +394,7 @@
|
||||||
|
"generic_count_minutes_1": "{{count}} минута",
|
||||||
|
"generic_count_minutes_2": "{{count}} минута",
|
||||||
|
"preferences_quality_dash_option_720p": "720p",
|
||||||
|
+ "preferences_hide_shorts_label": "Сакриј YouTube Shorts: ",
|
||||||
|
"preferences_watch_history_label": "Омогући историју гледања: ",
|
||||||
|
"user_saved_playlists": "Сачуваних плејлиста: `x`",
|
||||||
|
"Spanish (Spain)": "Шпански (Шпанија)",
|
||||||
|
diff --git a/locales/sv-SE.json b/locales/sv-SE.json
|
||||||
|
index 8f050d98..86d6996d 100644
|
||||||
|
--- a/locales/sv-SE.json
|
||||||
|
+++ b/locales/sv-SE.json
|
||||||
|
@@ -384,6 +384,7 @@
|
||||||
|
"generic_count_minutes": "{{count}}minut",
|
||||||
|
"generic_count_minutes_plural": "{{count}}minuter",
|
||||||
|
"preferences_quality_dash_option_720p": "720p",
|
||||||
|
+ "preferences_hide_shorts_label": "Dölj YouTube Shorts: ",
|
||||||
|
"preferences_watch_history_label": "Aktivera visningshistorik: ",
|
||||||
|
"user_saved_playlists": "`x` sparade spellistor",
|
||||||
|
"Spanish (Spain)": "Spanska (Spanien)",
|
||||||
|
diff --git a/locales/ta.json b/locales/ta.json
|
||||||
|
index 89e58668..2c0ca849 100644
|
||||||
|
--- a/locales/ta.json
|
||||||
|
+++ b/locales/ta.json
|
||||||
|
@@ -54,6 +54,7 @@
|
||||||
|
"preferences_autoplay_label": "தன்னியக்க: ",
|
||||||
|
"preferences_continue_label": "இயல்பாக அடுத்து விளையாடுங்கள்: ",
|
||||||
|
"preferences_local_label": "பதிலாள் வீடியோக்கள்: ",
|
||||||
|
+ "preferences_hide_shorts_label": "YouTube Shorts மறைக்கவும் : ",
|
||||||
|
"preferences_watch_history_label": "கண்காணிப்பு வரலாற்றை இயக்கு: ",
|
||||||
|
"preferences_speed_label": "இயல்புநிலை வேகம்: ",
|
||||||
|
"preferences_quality_label": "விருப்பமான வீடியோ தரம்: ",
|
||||||
|
diff --git a/locales/tr.json b/locales/tr.json
|
||||||
|
index cf3f8987..8fbfa848 100644
|
||||||
|
--- a/locales/tr.json
|
||||||
|
+++ b/locales/tr.json
|
||||||
|
@@ -451,6 +451,7 @@
|
||||||
|
"Portuguese (auto-generated)": "Portekizce (Otomatik Oluşturuldu)",
|
||||||
|
"Spanish (Spain)": "İspanyolca (İspanya)",
|
||||||
|
"Vietnamese (auto-generated)": "Vietnamca (Otomatik Oluşturuldu)",
|
||||||
|
+ "preferences_hide_shorts_label": "YouTube Shorts gizle: ",
|
||||||
|
"preferences_watch_history_label": "İzleme Geçmişini Etkinleştir: ",
|
||||||
|
"search_message_use_another_instance": "Ayrıca <a href=\"`x`\">başka bir örnekte arayabilirsiniz</a>.",
|
||||||
|
"search_filters_type_option_all": "Herhangi Bir Tür",
|
||||||
|
diff --git a/locales/uk.json b/locales/uk.json
|
||||||
|
index b99923e2..b1b8da03 100644
|
||||||
|
--- a/locales/uk.json
|
||||||
|
+++ b/locales/uk.json
|
||||||
|
@@ -362,6 +362,7 @@
|
||||||
|
"user_created_playlists": "Створено списків відтворення: `x`",
|
||||||
|
"user_saved_playlists": "Збережено списків відтворення: `x`",
|
||||||
|
"Video unavailable": "Відео недоступне",
|
||||||
|
+ "preferences_hide_shorts_label": "Сховати YouTube Shorts: ",
|
||||||
|
"preferences_watch_history_label": "Історія переглядів: ",
|
||||||
|
"preferences_quality_dash_label": "Бажана DASH-якість відео: ",
|
||||||
|
"preferences_quality_dash_option_144p": "144p",
|
||||||
|
diff --git a/locales/vi.json b/locales/vi.json
|
||||||
|
index 9c4a5a15..0d8a7eec 100644
|
||||||
|
--- a/locales/vi.json
|
||||||
|
+++ b/locales/vi.json
|
||||||
|
@@ -343,6 +343,7 @@
|
||||||
|
"Song: ": "Ca khúc: ",
|
||||||
|
"Premieres in `x`": "Trình chiếu ở `x`",
|
||||||
|
"preferences_quality_dash_option_worst": "Tệ nhất",
|
||||||
|
+ "preferences_hide_shorts_label": "Ẩn YouTube Shorts: ",
|
||||||
|
"preferences_watch_history_label": "Bật lịch sử video đã xem ",
|
||||||
|
"preferences_quality_option_hd720": "HD720",
|
||||||
|
"unsubscribe": "hủy đăng kí",
|
||||||
|
diff --git a/locales/zh-CN.json b/locales/zh-CN.json
|
||||||
|
index f3bc660b..f4d51449 100644
|
||||||
|
--- a/locales/zh-CN.json
|
||||||
|
+++ b/locales/zh-CN.json
|
||||||
|
@@ -435,6 +435,7 @@
|
||||||
|
"French (auto-generated)": "法语 (自动生成)",
|
||||||
|
"Turkish (auto-generated)": "土耳其语 (自动生成)",
|
||||||
|
"Spanish (Spain)": "西班牙语 (西班牙)",
|
||||||
|
+ "preferences_hide_shorts_label": "隐藏 YouTube Shorts: ",
|
||||||
|
"preferences_watch_history_label": "启用观看历史: ",
|
||||||
|
"search_message_use_another_instance": "你也可以 <a href=\"`x`\">在另一实例上搜索</a>。",
|
||||||
|
"search_filters_title": "过滤器",
|
||||||
|
diff --git a/locales/zh-TW.json b/locales/zh-TW.json
|
||||||
|
index 77805349..23852f40 100644
|
||||||
|
--- a/locales/zh-TW.json
|
||||||
|
+++ b/locales/zh-TW.json
|
||||||
|
@@ -435,6 +435,7 @@
|
||||||
|
"Portuguese (Brazil)": "葡萄牙語(巴西)",
|
||||||
|
"Japanese (auto-generated)": "日語(自動產生)",
|
||||||
|
"Portuguese (auto-generated)": "葡萄牙語(自動產生)",
|
||||||
|
+ "preferences_hide_shorts_label": "隱藏 YouTube Shorts: ",
|
||||||
|
"preferences_watch_history_label": "啟用觀看紀錄: ",
|
||||||
|
"search_message_change_filters_or_query": "嘗試擴大您的查詢字詞與/或變更過濾條件。",
|
||||||
|
"search_filters_apply_button": "套用選定的過濾條件",
|
||||||
|
diff --git a/src/invidious/config.cr b/src/invidious/config.cr
|
||||||
|
index 6a36c335..c889a776 100644
|
||||||
|
--- a/src/invidious/config.cr
|
||||||
|
+++ b/src/invidious/config.cr
|
||||||
|
@@ -31,6 +31,7 @@ struct ConfigPreferences
|
||||||
|
property listen : Bool = false
|
||||||
|
property local : Bool = false
|
||||||
|
property locale : String = "en-US"
|
||||||
|
+ property hide_shorts : Bool = false
|
||||||
|
property watch_history : Bool = true
|
||||||
|
property max_results : Int32 = 40
|
||||||
|
property notifications_only : Bool = false
|
||||||
|
diff --git a/src/invidious/routes/preferences.cr b/src/invidious/routes/preferences.cr
|
||||||
|
index 39ca77c0..74c78dcc 100644
|
||||||
|
--- a/src/invidious/routes/preferences.cr
|
||||||
|
+++ b/src/invidious/routes/preferences.cr
|
||||||
|
@@ -140,6 +140,10 @@ module Invidious::Routes::PreferencesRoute
|
||||||
|
unseen_only ||= "off"
|
||||||
|
unseen_only = unseen_only == "on"
|
||||||
|
|
||||||
|
+ hide_shorts = env.params.body["hide_shorts"]?.try &.as(String)
|
||||||
|
+ hide_shorts ||= "off"
|
||||||
|
+ hide_shorts = hide_shorts == "on"
|
||||||
|
+
|
||||||
|
notifications_only = env.params.body["notifications_only"]?.try &.as(String)
|
||||||
|
notifications_only ||= "off"
|
||||||
|
notifications_only = notifications_only == "on"
|
||||||
|
@@ -174,6 +178,7 @@ module Invidious::Routes::PreferencesRoute
|
||||||
|
speed: speed,
|
||||||
|
thin_mode: thin_mode,
|
||||||
|
unseen_only: unseen_only,
|
||||||
|
+ hide_shorts: hide_shorts,
|
||||||
|
video_loop: video_loop,
|
||||||
|
volume: volume,
|
||||||
|
extend_desc: extend_desc,
|
||||||
|
diff --git a/src/invidious/user/preferences.cr b/src/invidious/user/preferences.cr
|
||||||
|
index 0a8525f3..76bb00de 100644
|
||||||
|
--- a/src/invidious/user/preferences.cr
|
||||||
|
+++ b/src/invidious/user/preferences.cr
|
||||||
|
@@ -52,6 +52,7 @@ struct Preferences
|
||||||
|
property speed : Float32 = CONFIG.default_user_preferences.speed
|
||||||
|
property thin_mode : Bool = CONFIG.default_user_preferences.thin_mode
|
||||||
|
property unseen_only : Bool = CONFIG.default_user_preferences.unseen_only
|
||||||
|
+ property hide_shorts : Bool = CONFIG.default_user_preferences.hide_shorts
|
||||||
|
property video_loop : Bool = CONFIG.default_user_preferences.video_loop
|
||||||
|
property extend_desc : Bool = CONFIG.default_user_preferences.extend_desc
|
||||||
|
property volume : Int32 = CONFIG.default_user_preferences.volume
|
||||||
|
diff --git a/src/invidious/users.cr b/src/invidious/users.cr
|
||||||
|
index 0b2d1ef5..529228ac 100644
|
||||||
|
--- a/src/invidious/users.cr
|
||||||
|
+++ b/src/invidious/users.cr
|
||||||
|
@@ -54,37 +54,69 @@ def get_subscription_feed(user, max_results = 40, page = 1)
|
||||||
|
|
||||||
|
# "SELECT cv.* FROM channel_videos cv JOIN users ON cv.ucid = any(users.subscriptions) WHERE users.email = $1 AND published > now() - interval '1 month' ORDER BY published DESC"
|
||||||
|
# "SELECT DISTINCT ON (cv.ucid) cv.* FROM channel_videos cv JOIN users ON cv.ucid = any(users.subscriptions) WHERE users.email = ? AND NOT cv.id = any(users.watched) AND published > now() - interval '1 month' ORDER BY ucid, published DESC"
|
||||||
|
- videos = PG_DB.query_all("SELECT DISTINCT ON (cv.ucid) cv.* " \
|
||||||
|
- "FROM channel_videos cv " \
|
||||||
|
- "JOIN users ON cv.ucid = any(users.subscriptions) " \
|
||||||
|
- "WHERE users.email = $1 AND NOT cv.id = any(users.watched) AND published > now() - interval '1 month' " \
|
||||||
|
- "ORDER BY ucid, published DESC", user.email, as: ChannelVideo)
|
||||||
|
+ if user.preferences.hide_shorts
|
||||||
|
+ videos = PG_DB.query_all("SELECT DISTINCT ON (cv.ucid) cv.* " \
|
||||||
|
+ "FROM channel_videos cv " \
|
||||||
|
+ "JOIN users ON cv.ucid = any(users.subscriptions) " \
|
||||||
|
+ "WHERE users.email = $1 AND NOT cv.id = any(users.watched) AND published > now() - interval '1 month' AND cv.length_seconds > 180 " \
|
||||||
|
+ "ORDER BY ucid, published DESC", user.email, as: ChannelVideo)
|
||||||
|
+ else
|
||||||
|
+ videos = PG_DB.query_all("SELECT DISTINCT ON (cv.ucid) cv.* " \
|
||||||
|
+ "FROM channel_videos cv " \
|
||||||
|
+ "JOIN users ON cv.ucid = any(users.subscriptions) " \
|
||||||
|
+ "WHERE users.email = $1 AND NOT cv.id = any(users.watched) AND published > now() - interval '1 month' " \
|
||||||
|
+ "ORDER BY ucid, published DESC", user.email, as: ChannelVideo)
|
||||||
|
+ end
|
||||||
|
else
|
||||||
|
# Show latest video from each channel
|
||||||
|
|
||||||
|
- videos = PG_DB.query_all("SELECT DISTINCT ON (cv.ucid) cv.* " \
|
||||||
|
- "FROM channel_videos cv " \
|
||||||
|
- "JOIN users ON cv.ucid = any(users.subscriptions) " \
|
||||||
|
- "WHERE users.email = $1 AND published > now() - interval '1 month' " \
|
||||||
|
- "ORDER BY ucid, published DESC", user.email, as: ChannelVideo)
|
||||||
|
+ if user.preferences.hide_shorts
|
||||||
|
+ videos = PG_DB.query_all("SELECT DISTINCT ON (cv.ucid) cv.* " \
|
||||||
|
+ "FROM channel_videos cv " \
|
||||||
|
+ "JOIN users ON cv.ucid = any(users.subscriptions) " \
|
||||||
|
+ "WHERE users.email = $1 AND published > now() - interval '1 month' AND cv.length_seconds > 180 " \
|
||||||
|
+ "ORDER BY ucid, published DESC", user.email, as: ChannelVideo)
|
||||||
|
+ else
|
||||||
|
+ videos = PG_DB.query_all("SELECT DISTINCT ON (cv.ucid) cv.* " \
|
||||||
|
+ "FROM channel_videos cv " \
|
||||||
|
+ "JOIN users ON cv.ucid = any(users.subscriptions) " \
|
||||||
|
+ "WHERE users.email = $1 AND published > now() - interval '1 month' " \
|
||||||
|
+ "ORDER BY ucid, published DESC", user.email, as: ChannelVideo)
|
||||||
|
+ end
|
||||||
|
end
|
||||||
|
|
||||||
|
videos.sort_by!(&.published).reverse!
|
||||||
|
else
|
||||||
|
if user.preferences.unseen_only
|
||||||
|
# Only show unwatched
|
||||||
|
- videos = PG_DB.query_all("SELECT cv.* " \
|
||||||
|
- "FROM channel_videos cv " \
|
||||||
|
- "JOIN users ON cv.ucid = any(users.subscriptions) " \
|
||||||
|
- "WHERE users.email = $1 AND NOT cv.id = any(users.watched) AND published > now() - interval '1 month' " \
|
||||||
|
- "ORDER BY published DESC LIMIT $2 OFFSET $3", user.email, limit, offset, as: ChannelVideo)
|
||||||
|
+ if user.preferences.hide_shorts
|
||||||
|
+ videos = PG_DB.query_all("SELECT cv.* " \
|
||||||
|
+ "FROM channel_videos cv " \
|
||||||
|
+ "JOIN users ON cv.ucid = any(users.subscriptions) " \
|
||||||
|
+ "WHERE users.email = $1 AND NOT cv.id = any(users.watched) AND published > now() - interval '1 month' AND cv.length_seconds > 180 " \
|
||||||
|
+ "ORDER BY published DESC LIMIT $2 OFFSET $3", user.email, limit, offset, as: ChannelVideo)
|
||||||
|
+ else
|
||||||
|
+ videos = PG_DB.query_all("SELECT cv.* " \
|
||||||
|
+ "FROM channel_videos cv " \
|
||||||
|
+ "JOIN users ON cv.ucid = any(users.subscriptions) " \
|
||||||
|
+ "WHERE users.email = $1 AND NOT cv.id = any(users.watched) AND published > now() - interval '1 month' " \
|
||||||
|
+ "ORDER BY published DESC LIMIT $2 OFFSET $3", user.email, limit, offset, as: ChannelVideo)
|
||||||
|
+ end
|
||||||
|
else
|
||||||
|
# Sort subscriptions as normal
|
||||||
|
- videos = PG_DB.query_all("SELECT cv.* " \
|
||||||
|
- "FROM channel_videos cv " \
|
||||||
|
- "JOIN users ON cv.ucid = any(users.subscriptions) " \
|
||||||
|
- "WHERE users.email = $1 AND published > now() - interval '1 month' " \
|
||||||
|
- "ORDER BY published DESC LIMIT $2 OFFSET $3", user.email, limit, offset, as: ChannelVideo)
|
||||||
|
+ if user.preferences.hide_shorts
|
||||||
|
+ videos = PG_DB.query_all("SELECT cv.* " \
|
||||||
|
+ "FROM channel_videos cv " \
|
||||||
|
+ "JOIN users ON cv.ucid = any(users.subscriptions) " \
|
||||||
|
+ "WHERE users.email = $1 AND published > now() - interval '1 month' AND cv.length_seconds > 180 " \
|
||||||
|
+ "ORDER BY published DESC LIMIT $2 OFFSET $3", user.email, limit, offset, as: ChannelVideo)
|
||||||
|
+ else
|
||||||
|
+ videos = PG_DB.query_all("SELECT cv.* " \
|
||||||
|
+ "FROM channel_videos cv " \
|
||||||
|
+ "JOIN users ON cv.ucid = any(users.subscriptions) " \
|
||||||
|
+ "WHERE users.email = $1 AND published > now() - interval '1 month' " \
|
||||||
|
+ "ORDER BY published DESC LIMIT $2 OFFSET $3", user.email, limit, offset, as: ChannelVideo)
|
||||||
|
+ end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
diff --git a/src/invidious/views/user/preferences.ecr b/src/invidious/views/user/preferences.ecr
|
||||||
|
index cf8b5593..83847bea 100644
|
||||||
|
--- a/src/invidious/views/user/preferences.ecr
|
||||||
|
+++ b/src/invidious/views/user/preferences.ecr
|
||||||
|
@@ -216,6 +216,11 @@
|
||||||
|
<input name="watch_history" id="watch_history" type="checkbox" <% if preferences.watch_history %>checked<% end %>>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
+ <div class="pure-control-group">
|
||||||
|
+ <label for="hide_shorts"><%= translate(locale, "preferences_hide_shorts_label") %></label>
|
||||||
|
+ <input name="hide_shorts" id="hide_shorts" type="checkbox" <% if preferences.hide_shorts %>checked<% end %>>
|
||||||
|
+ </div>
|
||||||
|
+
|
||||||
|
<div class="pure-control-group">
|
||||||
|
<label for="annotations_subscribed"><%= translate(locale, "preferences_annotations_subscribed_label") %></label>
|
||||||
|
<input name="annotations_subscribed" id="annotations_subscribed" type="checkbox" <% if preferences.annotations_subscribed %>checked<% end %>>
|
||||||
Reference in New Issue
Block a user