split main.rs into lib.rs and main.rs
This commit is contained in:
parent
97ce2b374f
commit
43be49910b
2 changed files with 165 additions and 161 deletions
163
src/lib.rs
Normal file
163
src/lib.rs
Normal file
|
@ -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<FromUtf8Error> for Error {
|
||||||
|
fn from(value: FromUtf8Error) -> Self {
|
||||||
|
Self {
|
||||||
|
details: value.to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<String> for Error {
|
||||||
|
fn from(details: String) -> Self {
|
||||||
|
Self { details }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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
|
||||||
|
//
|
||||||
|
pub type Result<T> = std::result::Result<T, Error>;
|
||||||
|
|
||||||
|
//
|
||||||
|
// 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<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!("{}{}", 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<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()?;
|
||||||
|
let channel = Feed::read_from(&content[..])?;
|
||||||
|
Ok(channel)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_already_downloaded(link: &Link, file_name: &str) -> Result<bool> {
|
||||||
|
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(())
|
||||||
|
}
|
163
src/main.rs
163
src/main.rs
|
@ -1,170 +1,11 @@
|
||||||
// https://www.phind.com/agent?cache=clke9xk39001cmj085upzho1t
|
fn main() -> podal::Result<()> {
|
||||||
|
|
||||||
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<FromUtf8Error> for Error {
|
|
||||||
fn from(value: FromUtf8Error) -> Self {
|
|
||||||
Self {
|
|
||||||
details: value.to_string(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl From<String> for Error {
|
|
||||||
fn from(details: String) -> Self {
|
|
||||||
Self { details }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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");
|
println!("Podal");
|
||||||
let subscriptions = "subscriptions.txt";
|
let subscriptions = "subscriptions.txt";
|
||||||
let history = "downloaded.txt";
|
let history = "downloaded.txt";
|
||||||
let site = "https://www.youtube.com/";
|
let site = "https://www.youtube.com/";
|
||||||
|
|
||||||
for channel_name in lines_from(subscriptions)? {
|
podal::run(subscriptions, history, site)?;
|
||||||
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)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
println!("Done");
|
println!("Done");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_feed_url(site: &str, channel_name: &str) -> 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!("{}{}", 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<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()?;
|
|
||||||
let channel = Feed::read_from(&content[..])?;
|
|
||||||
Ok(channel)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_already_downloaded(link: &Link, file_name: &str) -> Result<bool> {
|
|
||||||
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(())
|
|
||||||
}
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue