From 8dcd80b9fecf56cc0410c695e1f6009b62027971 Mon Sep 17 00:00:00 2001 From: yggverse Date: Wed, 8 Apr 2026 12:05:46 +0300 Subject: [PATCH] initial commit --- Cargo.lock | 30 ++++++++++++++++++++- Cargo.toml | 3 ++- example/config.toml | 4 +++ src/config.rs | 5 ++++ src/main.rs | 16 +++++++----- src/provider.rs | 63 +++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 113 insertions(+), 8 deletions(-) create mode 100644 src/provider.rs diff --git a/Cargo.lock b/Cargo.lock index 448030f..35d5fe6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -635,6 +635,16 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "http_req" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92208b0986f414edaca8ede2c6962c979309346a9e8e19d07d0a7879aae1549e" +dependencies = [ + "native-tls", + "unicase", +] + [[package]] name = "httparse" version = "1.10.1" @@ -851,6 +861,17 @@ dependencies = [ "serde_core", ] +[[package]] +name = "invidious" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa2cff01a915c4780785828f6bec4ad5f8d03f34307d4183ca210830a8a6d88d" +dependencies = [ + "http_req", + "serde", + "serde_json", +] + [[package]] name = "ipnet" version = "2.12.0" @@ -1980,6 +2001,12 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" +[[package]] +name = "unicase" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" + [[package]] name = "unicode-ident" version = "1.0.24" @@ -2458,12 +2485,13 @@ dependencies = [ [[package]] name = "ytd" -version = "0.3.2" +version = "0.4.0" dependencies = [ "anyhow", "chrono", "clap", "env_logger", + "invidious", "log", "redb", "regex", diff --git a/Cargo.toml b/Cargo.toml index d25631d..e309bc4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ytd" -version = "0.3.2" +version = "0.4.0" edition = "2024" license = "MIT" readme = "README.md" @@ -14,6 +14,7 @@ anyhow = "1.0.102" chrono = "0.4.44" clap = { version = "4.6", features = ["derive"] } env_logger = "0.11.10" +invidious = "0.7.8" log = "0.4.29" redb = "4.0.0" regex = "1.12.3" diff --git a/example/config.toml b/example/config.toml index 605d4b9..8620bf6 100644 --- a/example/config.toml +++ b/example/config.toml @@ -21,6 +21,10 @@ sleep = 1 # exec = "" # stdout_contains = [""] +# Supported values: youtube | invidious + +provider = "youtube" + # Channels queue config [channel.test] diff --git a/src/config.rs b/src/config.rs index a5c3620..4824d96 100644 --- a/src/config.rs +++ b/src/config.rs @@ -12,6 +12,11 @@ pub struct Command { #[derive(Deserialize)] pub struct Config { + /// Supported values: + /// * youtube + /// * invidious + pub provider: String, + /// Channels parsed from the config file pub channel: HashMap, /// Persist processed entries between sessions pub database: PathBuf, diff --git a/src/main.rs b/src/main.rs index b44ff57..b56050d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,7 @@ mod argument; mod config; mod database; +mod provider; use argument::Argument; use chrono::Local; @@ -8,8 +9,8 @@ use clap::Parser; use config::Config; use database::Database; use log::*; +use provider::Provider; use regex::Regex; -use rustypipe::client::RustyPipe; use std::{env::var, process::Command, thread, time::Duration}; #[tokio::main] @@ -34,7 +35,11 @@ async fn main() { let update = config.update.map(Duration::from_secs); let sleep = config.sleep.map(Duration::from_secs); let mut database = Database::new(&config.database).unwrap(); - let rp = RustyPipe::new(); + let client = if "youtube" == config.provider.to_lowercase() { + Provider::youtube() + } else { + Provider::invidious() + }; let channel_item_id_regex = Regex::new(r"^[A-z0-9_-]{11}$").unwrap(); loop { @@ -45,11 +50,10 @@ async fn main() { info!("begin {} channels update...", config.channel.len()); for (c, channel) in &config.channel { debug!("get `{c}` ({})...", channel.id); - match rp.query().channel_videos(&channel.id).await { - Ok(result) => { - let items = result.content.items; + match client.videos(&channel.id).await { + Ok(items) => { debug!( - "received {:?} items to handle, limit: {:?}...", + "received {} items to handle, limit: {:?}...", items.len(), channel.items_limit ); diff --git a/src/provider.rs b/src/provider.rs new file mode 100644 index 0000000..6e1dcf9 --- /dev/null +++ b/src/provider.rs @@ -0,0 +1,63 @@ +use anyhow::{Result, anyhow}; +use invidious::{ClientSync, ClientSyncTrait}; +use rustypipe::client::RustyPipe; + +pub struct Video { + pub id: String, + pub name: String, + pub is_live: bool, + pub is_short: bool, + pub is_upcoming: bool, +} + +pub enum Provider { + Youtube(RustyPipe), + Invidious(ClientSync), +} + +impl Provider { + pub fn youtube() -> Self { + Self::Youtube(RustyPipe::new()) + } + pub fn invidious() -> Self { + Self::Invidious(ClientSync::default()) + } + pub async fn videos(&self, channel_id: &str) -> Result> { + let mut videos = Vec::new(); + match self { + Provider::Youtube(client) => { + for video in client + .query() + .channel_videos(channel_id) + .await? + .content + .items + { + videos.push(Video { + id: video.id, + name: video.name, + is_live: video.is_live, + is_short: video.is_short, + is_upcoming: video.is_upcoming, + }) + } + } + Provider::Invidious(client) => { + for video in client + .channel_videos(channel_id, None) + .map_err(|e| anyhow!("{e:?}"))? + .videos + { + videos.push(Video { + id: video.id, + name: video.title, + is_live: video.live, + is_short: video.length <= 60, + is_upcoming: video.upcoming, + }) + } + } + } + Ok(videos) + } +}