From 43be49910b24f8b7038c6baeb645f1e17fe5a19e Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Tue, 25 Jul 2023 10:33:41 +0100 Subject: [PATCH] split main.rs into lib.rs and main.rs --- src/lib.rs | 163 ++++++++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 163 +--------------------------------------------------- 2 files changed, 165 insertions(+), 161 deletions(-) create mode 100644 src/lib.rs diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..60f9ca5 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,163 @@ +// https://www.phind.com/agent?cache=clke9xk39001cmj085upzho1t + +use std::{fmt::Display, fs::File, string::FromUtf8Error}; + +use atom_syndication::{Entry, Feed, Link}; + +// +// ERRORS +// +#[derive(Debug)] +pub struct Error { + details: String, +} +impl Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(self.details.to_string().as_str()) + } +} +impl From for Error { + fn from(value: FromUtf8Error) -> Self { + Self { + details: value.to_string(), + } + } +} +impl From for Error { + fn from(details: String) -> Self { + Self { details } + } +} +impl From for Error { + fn from(value: std::io::Error) -> Self { + Self { + details: value.to_string(), + } + } +} +impl From for Error { + fn from(value: atom_syndication::Error) -> Self { + Self { + details: value.to_string(), + } + } +} +impl From for Error { + fn from(value: reqwest::Error) -> Self { + Self { + details: value.to_string(), + } + } +} + +// +// RESULTS +// +pub type Result = std::result::Result; + +// +// MAIN +// +pub fn run(subscriptions: &str, history: &str, site: &str) -> Result<()> { + for channel_name in lines_from(subscriptions)? { + let channel_name = channel_name?; + println!("Channel: {}", channel_name); + let feed_url = get_feed_url(site, &channel_name)?; + for entry in get_feed(feed_url)?.entries() { + if let Some(link) = get_link(entry) { + if !is_already_downloaded(&link, history)? { + println!("Downloading {}: {}", &channel_name, entry.title().as_str()); + download_audio(&link)?; + mark_as_downloaded(&link, history)?; + } + } + } + } + Ok(()) +} + +fn get_feed_url(site: &str, channel_name: &str) -> Result { + if let Some(channel_prefix) = channel_name.chars().next() { + if channel_prefix != '@' { + return Err(format!("Channel Name must begin with an '@': {}", channel_name).into()); + } + } + let channel_url = format!("{}{}", site, channel_name); + let response = reqwest::blocking::get(channel_url)?; + let rss_url = scraper::Html::parse_document(&response.text()?) + .select(&scraper::Selector::parse("link[title='RSS']").unwrap()) + .next() + .unwrap() + .value() + .attr("href") + .unwrap() + .to_string(); + Ok(rss_url) +} + +fn get_link(item: &Entry) -> Option { + item.links().get(0).cloned() +} + +// read list of rss feed URLs from file 'feeds.txt' +fn lines_from(file_name: &str) -> Result>> { + use std::io::{BufRead, BufReader}; + + let file = File::open(file_name)?; + let reader = BufReader::new(file); + Ok(reader.lines()) +} + +// fetch the RSS feed +fn get_feed(url: String) -> Result { + let content = reqwest::blocking::get(url)?.bytes()?; + let channel = Feed::read_from(&content[..])?; + Ok(channel) +} + +fn is_already_downloaded(link: &Link, file_name: &str) -> Result { + use std::io::{BufRead, BufReader}; + + if let Ok(file) = File::open(file_name) { + let reader = BufReader::new(file); + for line in reader.lines() { + if line? == link.href { + return Ok(true); // is already downloaded + } + } + } + Ok(false) // is not already downloaded +} + +fn download_audio(link: &Link) -> Result<()> { + use std::process::Command; + + let cmd = "yt-dlp"; + // println!("{} --extract-audio --audio-format mp3 {}", cmd, &link.href); + let output = Command::new(cmd) + .arg("--extract-audio") + .arg("--audio-format") + .arg("mp3") + .arg(&link.href) + .output()?; + if !output.stderr.is_empty() { + eprintln!("Error: {}", String::from_utf8(output.stderr)?); + println!("{}", String::from_utf8(output.stdout)?); + } + Ok(()) +} + +fn mark_as_downloaded(link: &Link, file_name: &str) -> Result<()> { + use std::fs::OpenOptions; + use std::io::prelude::*; + + let mut file = OpenOptions::new() + .write(true) + .append(true) + .create(true) + .open(file_name) + .unwrap(); + + writeln!(file, "{}", link.href)?; + Ok(()) +} diff --git a/src/main.rs b/src/main.rs index 46cef06..83b79ec 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,170 +1,11 @@ -// https://www.phind.com/agent?cache=clke9xk39001cmj085upzho1t - -use std::{fmt::Display, fs::File, string::FromUtf8Error}; - -use atom_syndication::{Entry, Feed, Link}; - -// -// ERRORS -// -#[derive(Debug)] -struct Error { - details: String, -} -impl Display for Error { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str(self.details.to_string().as_str()) - } -} -impl From for Error { - fn from(value: FromUtf8Error) -> Self { - Self { - details: value.to_string(), - } - } -} -impl From for Error { - fn from(details: String) -> Self { - Self { details } - } -} -impl From for Error { - fn from(value: std::io::Error) -> Self { - Self { - details: value.to_string(), - } - } -} -impl From for Error { - fn from(value: atom_syndication::Error) -> Self { - Self { - details: value.to_string(), - } - } -} -impl From for Error { - fn from(value: reqwest::Error) -> Self { - Self { - details: value.to_string(), - } - } -} - -// -// RESULTS -// -type Result = std::result::Result; - -// -// MAIN -// -fn main() -> Result<()> { +fn main() -> podal::Result<()> { println!("Podal"); let subscriptions = "subscriptions.txt"; let history = "downloaded.txt"; let site = "https://www.youtube.com/"; - for channel_name in lines_from(subscriptions)? { - let channel_name = channel_name?; - println!("Channel: {}", channel_name); - let feed_url = get_feed_url(site, &channel_name)?; - for entry in get_feed(feed_url)?.entries() { - if let Some(link) = get_link(entry) { - if !is_already_downloaded(&link, history)? { - println!("Downloading {}: {}", &channel_name, entry.title().as_str()); - download_audio(&link)?; - mark_as_downloaded(&link, history)?; - } - } - } - } + podal::run(subscriptions, history, site)?; println!("Done"); Ok(()) } - -fn get_feed_url(site: &str, channel_name: &str) -> Result { - if let Some(channel_prefix) = channel_name.chars().next() { - if channel_prefix != '@' { - return Err(format!("Channel Name must begin with an '@': {}", channel_name).into()); - } - } - let channel_url = format!("{}{}", site, channel_name); - let response = reqwest::blocking::get(channel_url)?; - let rss_url = scraper::Html::parse_document(&response.text()?) - .select(&scraper::Selector::parse("link[title='RSS']").unwrap()) - .next() - .unwrap() - .value() - .attr("href") - .unwrap() - .to_string(); - Ok(rss_url) -} - -fn get_link(item: &Entry) -> Option { - item.links().get(0).cloned() -} - -// read list of rss feed URLs from file 'feeds.txt' -fn lines_from(file_name: &str) -> Result>> { - use std::io::{BufRead, BufReader}; - - let file = File::open(file_name)?; - let reader = BufReader::new(file); - Ok(reader.lines()) -} - -// fetch the RSS feed -fn get_feed(url: String) -> Result { - let content = reqwest::blocking::get(url)?.bytes()?; - let channel = Feed::read_from(&content[..])?; - Ok(channel) -} - -fn is_already_downloaded(link: &Link, file_name: &str) -> Result { - use std::io::{BufRead, BufReader}; - - if let Ok(file) = File::open(file_name) { - let reader = BufReader::new(file); - for line in reader.lines() { - if line? == link.href { - return Ok(true); // is already downloaded - } - } - } - Ok(false) // is not already downloaded -} - -fn download_audio(link: &Link) -> Result<()> { - use std::process::Command; - - let cmd = "yt-dlp"; - // println!("{} --extract-audio --audio-format mp3 {}", cmd, &link.href); - let output = Command::new(cmd) - .arg("--extract-audio") - .arg("--audio-format") - .arg("mp3") - .arg(&link.href) - .output()?; - if !output.stderr.is_empty() { - eprintln!("Error: {}", String::from_utf8(output.stderr)?); - println!("{}", String::from_utf8(output.stdout)?); - } - Ok(()) -} - -fn mark_as_downloaded(link: &Link, file_name: &str) -> Result<()> { - use std::fs::OpenOptions; - use std::io::prelude::*; - - let mut file = OpenOptions::new() - .write(true) - .append(true) - .create(true) - .open(file_name) - .unwrap(); - - writeln!(file, "{}", link.href)?; - Ok(()) -}