Second round of rework to make more podcast clients happy

* Prefix the feed handler path with '/feed'
* Mount both handlers under `/` now that they have a prefix in the path
* Provide the backend to the feed and download handler in their paths
* Use the ID as filename download handler, also add an extension
This commit is contained in:
Paul van Tilburg 2022-05-24 09:58:57 +02:00
parent a4546c1641
commit 85cc0b06b2
Signed by: paul
GPG Key ID: C6DE073EDA9EEC4D

View File

@ -7,12 +7,14 @@
)] )]
#![deny(missing_docs)] #![deny(missing_docs)]
use std::path::PathBuf;
use chrono::{DateTime, NaiveDateTime, Utc}; use chrono::{DateTime, NaiveDateTime, Utc};
use reqwest::Url;
use rocket::fairing::AdHoc; use rocket::fairing::AdHoc;
use rocket::http::uri::Absolute;
use rocket::response::Redirect; use rocket::response::Redirect;
use rocket::serde::{Deserialize, Serialize}; use rocket::serde::{Deserialize, Serialize};
use rocket::{get, routes, Build, Responder, Rocket, State}; use rocket::{get, routes, uri, Build, Responder, Rocket, State};
use rss::extension::itunes::ITunesItemExtensionBuilder; use rss::extension::itunes::ITunesItemExtensionBuilder;
use rss::{ use rss::{
CategoryBuilder, ChannelBuilder, EnclosureBuilder, GuidBuilder, ImageBuilder, ItemBuilder, CategoryBuilder, ChannelBuilder, EnclosureBuilder, GuidBuilder, ImageBuilder, ItemBuilder,
@ -24,6 +26,7 @@ pub(crate) mod mixcloud;
#[derive(Debug, Deserialize, Serialize)] #[derive(Debug, Deserialize, Serialize)]
#[serde(crate = "rocket::serde")] #[serde(crate = "rocket::serde")]
pub(crate) struct Config { pub(crate) struct Config {
/// The URL at which the application is hosted or proxied from.
#[serde(default)] #[serde(default)]
url: String, url: String,
} }
@ -33,9 +36,22 @@ pub(crate) struct Config {
#[response(content_type = "application/xml")] #[response(content_type = "application/xml")]
struct RssFeed(String); struct RssFeed(String);
/// Retrieves a download using youtube-dl and redirection.
#[get("/download/<backend>/<file..>")]
pub(crate) async fn download(file: PathBuf, backend: &str) -> Option<Redirect> {
match backend {
"mixcloud" => {
let key = format!("/{}/", file.with_extension("").to_string_lossy());
mixcloud::redirect_url(&key).await.map(Redirect::to)
}
_ => None,
}
}
/// Handler for retrieving the RSS feed of an Mixcloud user. /// Handler for retrieving the RSS feed of an Mixcloud user.
#[get("/<username>")] #[get("/feed/<backend>/<username>")]
async fn feed(username: &str, config: &State<Config>) -> Option<RssFeed> { async fn feed(backend: &str, username: &str, config: &State<Config>) -> Option<RssFeed> {
let user = mixcloud::get_user(username).await?; let user = mixcloud::get_user(username).await?;
let cloudcasts = mixcloud::get_cloudcasts(username).await?; let cloudcasts = mixcloud::get_cloudcasts(username).await?;
let mut last_build = DateTime::<Utc>::from_utc(NaiveDateTime::from_timestamp(0, 0), Utc); let mut last_build = DateTime::<Utc>::from_utc(NaiveDateTime::from_timestamp(0, 0), Utc);
@ -49,10 +65,12 @@ async fn feed(username: &str, config: &State<Config>) -> Option<RssFeed> {
.into_iter() .into_iter()
.map(|cloudcast| { .map(|cloudcast| {
let slug = cloudcast.slug; let slug = cloudcast.slug;
let mut url = Url::parse(&config.url).unwrap(); let mut file = PathBuf::from(cloudcast.key.trim_end_matches('/'));
url.set_path(&format!("{}/download", &url.path()[1..])); file.set_extension("m4a"); // FIXME: Don't hardcode the extension!
url.query_pairs_mut().append_pair("backend", "mixcloud"); let url = uri!(
url.query_pairs_mut().append_pair("id", &cloudcast.key); Absolute::parse(&config.url).expect("valid URL"),
download(backend = backend, file = file)
);
let description = format!("Taken from Mixcloud: {}", cloudcast.url); let description = format!("Taken from Mixcloud: {}", cloudcast.url);
let keywords = cloudcast let keywords = cloudcast
.tags .tags
@ -72,12 +90,10 @@ async fn feed(username: &str, config: &State<Config>) -> Option<RssFeed> {
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let length = mixcloud::estimated_file_size(cloudcast.audio_length);
let enclosure = EnclosureBuilder::default() let enclosure = EnclosureBuilder::default()
.url(url) .url(url.to_string())
.length(format!( .length(format!("{}", length))
"{}",
mixcloud::estimated_file_size(cloudcast.audio_length)
))
.mime_type(String::from(mixcloud::default_file_type())) .mime_type(String::from(mixcloud::default_file_type()))
.build(); .build();
let guid = GuidBuilder::default().value(slug).permalink(false).build(); let guid = GuidBuilder::default().value(slug).permalink(false).build();
@ -123,19 +139,9 @@ async fn feed(username: &str, config: &State<Config>) -> Option<RssFeed> {
Some(feed) Some(feed)
} }
/// Retrieves a download using youtube-dl.
#[get("/?<backend>&<id>")]
pub(crate) async fn download(backend: &str, id: &str) -> Option<Redirect> {
match backend {
"mixcloud" => mixcloud::redirect_url(id).await.map(Redirect::to),
_ => None,
}
}
/// Sets up Rocket. /// Sets up Rocket.
pub fn setup() -> Rocket<Build> { pub fn setup() -> Rocket<Build> {
rocket::build() rocket::build()
.mount("/mixcloud", routes![feed]) .mount("/", routes![download, feed])
.mount("/download", routes![download])
.attach(AdHoc::config::<Config>()) .attach(AdHoc::config::<Config>())
} }