initial implementation
This commit is contained in:
parent
ec1170faa6
commit
d56523ea4f
3 changed files with 1792 additions and 0 deletions
1626
Cargo.lock
generated
Normal file
1626
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
11
Cargo.toml
Normal file
11
Cargo.toml
Normal file
|
@ -0,0 +1,11 @@
|
|||
[package]
|
||||
name = "podal"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
atom_syndication = "0.12.1"
|
||||
reqwest = { version = "0.11.18", features = ["json", "blocking"] }
|
||||
scraper = "0.17.1"
|
155
src/main.rs
Normal file
155
src/main.rs
Normal file
|
@ -0,0 +1,155 @@
|
|||
// 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<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_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<String> {
|
||||
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<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> {
|
||||
// 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<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;
|
||||
|
||||
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(())
|
||||
}
|
Loading…
Add table
Reference in a new issue