podal/src/main.rs

164 lines
4.2 KiB
Rust
Raw Normal View History

2023-07-23 18:50:59 +01:00
// 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 {
2023-07-23 20:09:55 +01:00
f.write_str(self.details.to_string().as_str())
2023-07-23 18:50:59 +01:00
}
}
impl From<String> for Error {
fn from(details: String) -> Self {
Self { details }
}
}
2023-07-23 18:50:59 +01:00
impl From<std::io::Error> for Error {
fn from(value: std::io::Error) -> Self {
Self {
details: value.to_string(),
}
}
}
impl From<atom_syndication::Error> for Error {
fn from(value: atom_syndication::Error) -> Self {
Self {
details: value.to_string(),
}
}
}
impl From<reqwest::Error> for Error {
fn from(value: reqwest::Error) -> Self {
Self {
details: value.to_string(),
}
}
}
//
// RESULTS
//
type Result<T> = std::result::Result<T, Error>;
//
// MAIN
//
fn main() -> Result<()> {
println!("Podal");
let subscriptions = "subscriptions.txt";
let history = "downloaded.txt";
for channel_name in lines_from(subscriptions)? {
let channel_name = channel_name?;
let feed_url = get_feed_url(channel_name)?;
2023-07-23 18:50:59 +01:00
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_name: String) -> Result<String> {
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!("https://www.youtube.com/{}", channel_name);
2023-07-23 18:50:59 +01:00
let response = reqwest::blocking::get(channel_url)?;
2023-07-23 20:09:55 +01:00
let rss_url = scraper::Html::parse_document(&response.text()?)
.select(&scraper::Selector::parse("link[title='RSS']").unwrap())
2023-07-23 18:50:59 +01:00
.next()
.unwrap()
.value()
.attr("href")
2023-07-23 20:09:55 +01:00
.unwrap()
.to_string();
2023-07-23 18:50:59 +01:00
println!("rss_url: {}", rss_url);
Ok(rss_url)
}
fn get_link(item: &Entry) -> Option<Link> {
item.links().get(0).cloned()
}
// read list of rss feed URLs from file 'feeds.txt'
fn lines_from(file_name: &str) -> Result<std::io::Lines<std::io::BufReader<std::fs::File>>> {
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<Feed> {
let content = reqwest::blocking::get(url)?.bytes()?;
2023-07-23 20:21:17 +01:00
let channel = Feed::read_from(&content[..])?;
2023-07-23 18:50:59 +01:00
Ok(channel)
}
fn is_already_downloaded(link: &Link, file_name: &str) -> Result<bool> {
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;
2023-07-23 20:21:36 +01:00
let cmd = "yt-dlp";
2023-07-23 18:50:59 +01:00
println!("download audio for {}", link.href());
2023-07-23 20:21:36 +01:00
println!("{} --extract-audio --audio-format mp3 {}", cmd, &link.href);
let mut child = Command::new(cmd)
2023-07-23 18:50:59 +01:00
.arg("--extract-audio")
.arg("--audio-format")
.arg("mp3")
.arg(&link.href)
.spawn()
2023-07-23 18:50:59 +01:00
.expect("Failed to execute command");
child.wait()?;
2023-07-23 18:50:59 +01:00
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)
2023-07-23 18:50:59 +01:00
.open(file_name)
.unwrap();
writeln!(file, "{}", link.href)?;
Ok(())
}