// https://www.phind.com/agent?cache=clke9xk39001cmj085upzho1t use std::{fmt::Display, fs::File}; 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(std::format!("{}", self.details).as_str()) } } 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<()> { println!("Podal"); let subscriptions = "subscriptions.txt"; let history = "downloaded.txt"; for channel_url in lines_from(subscriptions)? { let channel_url = channel_url?; let feed_url = get_feed_url(channel_url)?; for entry in get_feed(feed_url)?.entries() { if let Some(link) = get_link(entry) { if !is_already_downloaded(&link, history)? { download_audio(&link)?; mark_as_downloaded(&link, history)?; } } } } println!("Done"); Ok(()) } fn get_feed_url(channel_url: String) -> Result { let response = reqwest::blocking::get(channel_url)?; let document = scraper::Html::parse_document(&response.text()?); let selector = scraper::Selector::parse("link[title='RSS']").unwrap(); let channel_id = document .select(&selector) .next() .unwrap() .value() .attr("href") .unwrap(); let rss_url = format!("{}", channel_id); println!("rss_url: {}", rss_url); 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 { // println!("get_channel({})", url); let content = reqwest::blocking::get(url)?.bytes()?; // println!("got content:"); // println!("{}", content); let channel = atom_syndication::Feed::read_from(&content[..])?; // println!("channel: {:#?}", channel); Ok(channel) } fn is_already_downloaded(link: &Link, file_name: &str) -> Result { use std::io::{BufRead, BufReader}; println!("is already downloaded? {}", link.href); if let Ok(file) = File::open(file_name) { let reader = BufReader::new(file); for line in reader.lines() { if line? == link.href { println!("Yes!"); return Ok(true); // is already downloaded } } } println!("No!"); Ok(false) // is not already downloaded } fn download_audio(link: &Link) -> Result<()> { use std::process::Command; println!("download audio for {}", link.href()); let output = Command::new("youtube-dl") .arg("--extract-audio") .arg("--audio-format") .arg("mp3") .arg(&link.href) .output() .expect("Failed to execute command"); println!("Output: {:#?}", output); 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) .open(file_name) .unwrap(); writeln!(file, "{}", link.href)?; Ok(()) }