Add cookies file support for Docker; auto-detect /data/cookies.txt
This commit is contained in:
@@ -66,6 +66,7 @@ def on_startup():
|
|||||||
key TEXT PRIMARY KEY,
|
key TEXT PRIMARY KEY,
|
||||||
value TEXT NOT NULL
|
value TEXT NOT NULL
|
||||||
)""",
|
)""",
|
||||||
|
"ALTER TABLE user_settings ADD COLUMN cookies_file TEXT DEFAULT ''",
|
||||||
"ALTER TABLE user_settings ADD COLUMN feed_weight_recency REAL DEFAULT 5.0",
|
"ALTER TABLE user_settings ADD COLUMN feed_weight_recency REAL DEFAULT 5.0",
|
||||||
"ALTER TABLE user_settings ADD COLUMN feed_weight_affinity REAL DEFAULT 5.0",
|
"ALTER TABLE user_settings ADD COLUMN feed_weight_affinity REAL DEFAULT 5.0",
|
||||||
"ALTER TABLE user_settings ADD COLUMN feed_weight_channel REAL DEFAULT 5.0",
|
"ALTER TABLE user_settings ADD COLUMN feed_weight_channel REAL DEFAULT 5.0",
|
||||||
@@ -142,6 +143,7 @@ def on_startup():
|
|||||||
if first_user_settings:
|
if first_user_settings:
|
||||||
ytdlp_service.set_max_concurrent(first_user_settings.max_concurrent_downloads)
|
ytdlp_service.set_max_concurrent(first_user_settings.max_concurrent_downloads)
|
||||||
ytdlp_service.set_cookies_browser(first_user_settings.cookies_browser or "")
|
ytdlp_service.set_cookies_browser(first_user_settings.cookies_browser or "")
|
||||||
|
ytdlp_service.set_cookies_file(first_user_settings.cookies_file or "")
|
||||||
finally:
|
finally:
|
||||||
db.close()
|
db.close()
|
||||||
|
|
||||||
|
|||||||
@@ -109,6 +109,7 @@ class UserSettings(Base):
|
|||||||
mark_watched_at_percent = Column(Integer, default=90) # 50–100
|
mark_watched_at_percent = Column(Integer, default=90) # 50–100
|
||||||
auto_download_on_sync = Column(Boolean, default=False)
|
auto_download_on_sync = Column(Boolean, default=False)
|
||||||
cookies_browser = Column(String, default="") # chrome / firefox / etc., "" = disabled
|
cookies_browser = Column(String, default="") # chrome / firefox / etc., "" = disabled
|
||||||
|
cookies_file = Column(String, default="") # path to Netscape cookies.txt, "" = disabled
|
||||||
theater_mode = Column(Boolean, default=False)
|
theater_mode = Column(Boolean, default=False)
|
||||||
discovery_regions = Column(String, default="US,SE") # comma-separated ISO country codes
|
discovery_regions = Column(String, default="US,SE") # comma-separated ISO country codes
|
||||||
calm_mode = Column(Boolean, default=False)
|
calm_mode = Column(Boolean, default=False)
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ class SettingsOut(BaseModel):
|
|||||||
mark_watched_at_percent: int
|
mark_watched_at_percent: int
|
||||||
auto_download_on_sync: bool
|
auto_download_on_sync: bool
|
||||||
cookies_browser: str = ""
|
cookies_browser: str = ""
|
||||||
|
cookies_file: str = ""
|
||||||
theater_mode: bool = False
|
theater_mode: bool = False
|
||||||
discovery_regions: str = "US,SE"
|
discovery_regions: str = "US,SE"
|
||||||
calm_mode: bool = False
|
calm_mode: bool = False
|
||||||
@@ -41,6 +42,7 @@ class SettingsPatch(BaseModel):
|
|||||||
mark_watched_at_percent: Optional[int] = Field(None, ge=50, le=100)
|
mark_watched_at_percent: Optional[int] = Field(None, ge=50, le=100)
|
||||||
auto_download_on_sync: Optional[bool] = None
|
auto_download_on_sync: Optional[bool] = None
|
||||||
cookies_browser: Optional[str] = None
|
cookies_browser: Optional[str] = None
|
||||||
|
cookies_file: Optional[str] = None
|
||||||
theater_mode: Optional[bool] = None
|
theater_mode: Optional[bool] = None
|
||||||
discovery_regions: Optional[str] = None
|
discovery_regions: Optional[str] = None
|
||||||
calm_mode: Optional[bool] = None
|
calm_mode: Optional[bool] = None
|
||||||
@@ -91,6 +93,9 @@ def update_settings(
|
|||||||
if body.cookies_browser is not None and body.cookies_browser in VALID_BROWSERS:
|
if body.cookies_browser is not None and body.cookies_browser in VALID_BROWSERS:
|
||||||
s.cookies_browser = body.cookies_browser
|
s.cookies_browser = body.cookies_browser
|
||||||
ytdlp.set_cookies_browser(body.cookies_browser)
|
ytdlp.set_cookies_browser(body.cookies_browser)
|
||||||
|
if body.cookies_file is not None:
|
||||||
|
s.cookies_file = body.cookies_file.strip()
|
||||||
|
ytdlp.set_cookies_file(body.cookies_file)
|
||||||
if body.theater_mode is not None:
|
if body.theater_mode is not None:
|
||||||
s.theater_mode = body.theater_mode
|
s.theater_mode = body.theater_mode
|
||||||
if body.discovery_regions is not None:
|
if body.discovery_regions is not None:
|
||||||
|
|||||||
@@ -401,8 +401,11 @@ def predicted_file_path(video_id: str) -> Path:
|
|||||||
_SEMAPHORE = threading.Semaphore(3)
|
_SEMAPHORE = threading.Semaphore(3)
|
||||||
_semaphore_lock = threading.Lock()
|
_semaphore_lock = threading.Lock()
|
||||||
_cookies_browser: str = ""
|
_cookies_browser: str = ""
|
||||||
|
_cookies_file: str = ""
|
||||||
_cookies_lock = threading.Lock()
|
_cookies_lock = threading.Lock()
|
||||||
|
|
||||||
|
_AUTO_COOKIES_PATHS = ["/data/cookies.txt"]
|
||||||
|
|
||||||
|
|
||||||
def set_max_concurrent(n: int) -> None:
|
def set_max_concurrent(n: int) -> None:
|
||||||
global _SEMAPHORE
|
global _SEMAPHORE
|
||||||
@@ -416,10 +419,27 @@ def set_cookies_browser(browser: str) -> None:
|
|||||||
_cookies_browser = browser.strip().lower()
|
_cookies_browser = browser.strip().lower()
|
||||||
|
|
||||||
|
|
||||||
|
def set_cookies_file(path: str) -> None:
|
||||||
|
global _cookies_file
|
||||||
|
with _cookies_lock:
|
||||||
|
_cookies_file = path.strip()
|
||||||
|
|
||||||
|
|
||||||
def _cookie_args() -> list[str]:
|
def _cookie_args() -> list[str]:
|
||||||
with _cookies_lock:
|
with _cookies_lock:
|
||||||
|
cf = _cookies_file
|
||||||
b = _cookies_browser
|
b = _cookies_browser
|
||||||
return ["--cookies-from-browser", b] if b else []
|
# Prefer explicit cookies file
|
||||||
|
if cf and Path(cf).exists():
|
||||||
|
return ["--cookies", cf]
|
||||||
|
# Auto-detect cookies.txt in well-known Docker locations
|
||||||
|
for candidate in _AUTO_COOKIES_PATHS:
|
||||||
|
if Path(candidate).exists():
|
||||||
|
return ["--cookies", candidate]
|
||||||
|
# Fall back to browser (works in local dev, not in Docker)
|
||||||
|
if b:
|
||||||
|
return ["--cookies-from-browser", b]
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
def start_download(
|
def start_download(
|
||||||
|
|||||||
@@ -241,9 +241,27 @@ export default function SettingsPage() {
|
|||||||
|
|
||||||
{/* YouTube authentication */}
|
{/* YouTube authentication */}
|
||||||
<Section title="YouTube authentication">
|
<Section title="YouTube authentication">
|
||||||
|
<div className="px-5 py-4 flex flex-col gap-3">
|
||||||
|
<div>
|
||||||
|
<p className="text-sm font-medium text-zinc-200">Cookies file</p>
|
||||||
|
<p className="text-xs text-zinc-500 mt-0.5">
|
||||||
|
Recommended for Docker. Export your YouTube cookies as <span className="text-zinc-400 font-mono text-[11px]">cookies.txt</span> using
|
||||||
|
the <span className="text-zinc-400">"Get cookies.txt LOCALLY"</span> browser extension, then
|
||||||
|
place the file at <span className="text-zinc-400 font-mono text-[11px]">/data/cookies.txt</span> inside
|
||||||
|
the data volume — it will be picked up automatically. Or enter a custom path below.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="e.g. /data/cookies.txt"
|
||||||
|
value={s?.cookies_file ?? ""}
|
||||||
|
onChange={(e) => set({ cookies_file: e.target.value })}
|
||||||
|
className="bg-zinc-800 text-zinc-200 text-sm rounded-lg px-3 py-2 border border-zinc-700 focus:outline-none focus:border-accent font-mono placeholder:text-zinc-600 placeholder:font-sans"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<Row
|
<Row
|
||||||
label="Browser cookies"
|
label="Browser cookies"
|
||||||
hint="Pass cookies from your browser to bypass bot detection. You must be signed in to YouTube in that browser."
|
hint="Only works outside Docker. Pass cookies from a local browser install to bypass bot detection."
|
||||||
>
|
>
|
||||||
<select
|
<select
|
||||||
value={s?.cookies_browser ?? ""}
|
value={s?.cookies_browser ?? ""}
|
||||||
|
|||||||
Reference in New Issue
Block a user