diff --git a/Dockerfile b/Dockerfile index f7ce4f8..9338e18 100644 --- a/Dockerfile +++ b/Dockerfile @@ -18,17 +18,21 @@ FROM debian:bookworm-slim RUN apt-get update && apt-get install -y --no-install-recommends \ ca-certificates ffmpeg python3 python3-pip python3-venv \ + firefox-esr xvfb x11vnc novnc websockify sqlite3 \ && rm -rf /var/lib/apt/lists/* RUN python3 -m venv /opt/venv \ && /opt/venv/bin/pip install --no-cache-dir ytmusicapi yt-dlp ENV PATH="/opt/venv/bin:$PATH" +# Ensure absolute latest yt-dlp (pip cache may lag behind releases) +RUN /opt/venv/bin/pip install --no-cache-dir --upgrade yt-dlp WORKDIR /app COPY --from=backend /build/target/release/shanty . COPY --from=backend /build/shanty-web/static ./static COPY --from=backend /build/shanty-dl/scripts/ytmusic_search.py /usr/share/shanty/ +COPY --from=backend /build/shanty-dl/scripts/cookie_manager.py /usr/share/shanty/ RUN mkdir -p /config /data /music @@ -37,7 +41,7 @@ ENV SHANTY_DATABASE_URL=sqlite:///data/shanty.db?mode=rwc ENV SHANTY_LIBRARY_PATH=/music ENV SHANTY_DOWNLOAD_PATH=/data/downloads -EXPOSE 8085 +EXPOSE 8085 6080 VOLUME ["/config", "/data", "/music"] diff --git a/compose.yml b/compose.yml index ad006ba..8b92ce5 100644 --- a/compose.yml +++ b/compose.yml @@ -3,6 +3,7 @@ services: build: . ports: - "8085:8085" + - "6080:6080" # noVNC for YouTube login volumes: - ./config:/config - shanty-data:/data diff --git a/shanty-config/src/lib.rs b/shanty-config/src/lib.rs index 5e9c1ea..b7ee423 100644 --- a/shanty-config/src/lib.rs +++ b/shanty-config/src/lib.rs @@ -66,13 +66,25 @@ pub struct DownloadConfig { #[serde(default)] pub cookies_path: Option, - /// Requests per hour (unauthenticated). Actual YouTube limit is ~500. + /// Requests per hour (unauthenticated). Actual YouTube limit is ~300. #[serde(default = "default_rate_limit")] pub rate_limit: u32, /// Requests per hour (with cookies). Actual YouTube limit is ~2000. #[serde(default = "default_rate_limit_auth")] pub rate_limit_auth: u32, + + /// Enable automatic cookie refresh via headless Firefox. + #[serde(default)] + pub cookie_refresh_enabled: bool, + + /// How often to refresh cookies (hours). + #[serde(default = "default_cookie_refresh_hours")] + pub cookie_refresh_hours: u32, + + /// Port for noVNC during interactive YouTube login. + #[serde(default = "default_vnc_port")] + pub vnc_port: u16, } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -127,6 +139,9 @@ impl Default for DownloadConfig { cookies_path: None, rate_limit: default_rate_limit(), rate_limit_auth: default_rate_limit_auth(), + cookie_refresh_enabled: false, + cookie_refresh_hours: default_cookie_refresh_hours(), + vnc_port: default_vnc_port(), } } } @@ -183,7 +198,7 @@ fn default_true() -> bool { true } fn default_rate_limit() -> u32 { - 450 + 250 } fn default_rate_limit_auth() -> u32 { 1800 @@ -191,6 +206,19 @@ fn default_rate_limit_auth() -> u32 { fn default_concurrency() -> usize { 4 } +fn default_cookie_refresh_hours() -> u32 { + 6 +} +fn default_vnc_port() -> u16 { + 6080 +} + +/// Return the application data directory (e.g. ~/.local/share/shanty). +pub fn data_dir() -> PathBuf { + dirs::data_dir() + .unwrap_or_else(|| PathBuf::from(".")) + .join("shanty") +} // --- Loading and Saving --- diff --git a/shanty-dl b/shanty-dl index 0e5195e..c12dba8 160000 --- a/shanty-dl +++ b/shanty-dl @@ -1 +1 @@ -Subproject commit 0e5195e64c945194c7263e3e4d645214fab1e8e6 +Subproject commit c12dba886e93eedb19102590799ec5039b748503 diff --git a/shanty-web b/shanty-web index c8e7860..fed86c9 160000 --- a/shanty-web +++ b/shanty-web @@ -1 +1 @@ -Subproject commit c8e78606b114d928ffc1b3dddac2a660841da8cc +Subproject commit fed86c9e858f55809550ce6c3e421e4c0f4bf5fc diff --git a/src/main.rs b/src/main.rs index 04b4546..1480971 100644 --- a/src/main.rs +++ b/src/main.rs @@ -67,8 +67,12 @@ async fn main() -> anyhow::Result<()> { config: std::sync::Arc::new(tokio::sync::RwLock::new(config)), config_path, tasks: TaskManager::new(), + firefox_login: tokio::sync::Mutex::new(None), }); + // Start background cookie refresh task + shanty_web::cookie_refresh::spawn(state.config.clone()); + // Resolve static files directory let static_dir = std::env::current_exe() .ok()