2022-05-20 16:18:13 +02:00
|
|
|
#![doc = include_str!("../README.md")]
|
2022-05-17 11:15:22 +02:00
|
|
|
#![warn(
|
|
|
|
clippy::all,
|
2022-05-26 20:03:44 +02:00
|
|
|
missing_copy_implementations,
|
2022-05-17 11:15:22 +02:00
|
|
|
missing_debug_implementations,
|
|
|
|
rust_2018_idioms,
|
2022-05-26 20:03:44 +02:00
|
|
|
rustdoc::broken_intra_doc_links,
|
|
|
|
trivial_numeric_casts
|
2022-05-17 11:15:22 +02:00
|
|
|
)]
|
2022-05-20 16:18:13 +02:00
|
|
|
#![deny(missing_docs)]
|
2022-05-17 11:15:22 +02:00
|
|
|
|
2022-05-24 09:58:57 +02:00
|
|
|
use std::path::PathBuf;
|
|
|
|
|
2022-05-20 16:15:33 +02:00
|
|
|
use rocket::fairing::AdHoc;
|
2022-05-26 19:57:16 +02:00
|
|
|
use rocket::http::Status;
|
2022-05-23 22:21:38 +02:00
|
|
|
use rocket::response::Redirect;
|
2022-05-20 16:15:33 +02:00
|
|
|
use rocket::serde::{Deserialize, Serialize};
|
2022-08-14 10:15:59 +02:00
|
|
|
use rocket::{get, routes, Build, Request, Responder, Rocket, State};
|
2022-05-24 11:04:27 +02:00
|
|
|
use rocket_dyn_templates::{context, Template};
|
2022-05-17 11:15:22 +02:00
|
|
|
|
2022-08-15 20:17:34 +02:00
|
|
|
use crate::backends::Backend;
|
2022-08-13 15:14:15 +02:00
|
|
|
|
|
|
|
pub(crate) mod backends;
|
2022-08-14 10:15:59 +02:00
|
|
|
pub(crate) mod feed;
|
2022-05-17 11:15:22 +02:00
|
|
|
|
2022-05-26 19:57:16 +02:00
|
|
|
/// The possible errors that can occur.
|
|
|
|
#[derive(Debug, thiserror::Error)]
|
|
|
|
pub(crate) enum Error {
|
2022-06-05 21:58:02 +02:00
|
|
|
/// A standard I/O error occurred.
|
2022-05-26 19:57:16 +02:00
|
|
|
#[error("IO error: {0}")]
|
|
|
|
Io(#[from] std::io::Error),
|
|
|
|
|
2022-06-05 21:58:02 +02:00
|
|
|
/// No redirect URL found in item metadata.
|
2022-05-26 19:57:16 +02:00
|
|
|
#[error("No redirect URL found")]
|
|
|
|
NoRedirectUrlFound,
|
|
|
|
|
2022-06-05 21:58:02 +02:00
|
|
|
/// A (reqwest) HTTP error occurred.
|
2022-05-26 19:57:16 +02:00
|
|
|
#[error("HTTP error: {0}")]
|
|
|
|
Request(#[from] reqwest::Error),
|
|
|
|
|
2022-06-05 21:58:02 +02:00
|
|
|
/// Unsupported back-end encountered.
|
|
|
|
#[error("Unsupported back-end: {0}")]
|
2022-05-26 19:57:16 +02:00
|
|
|
UnsupportedBackend(String),
|
2022-05-26 20:37:27 +02:00
|
|
|
|
2022-06-05 21:58:02 +02:00
|
|
|
/// A URL parse error occurred.
|
2022-05-26 22:06:22 +02:00
|
|
|
#[error("URL parse error: {0}")]
|
|
|
|
UrlParse(#[from] url::ParseError),
|
|
|
|
|
2022-06-05 21:58:02 +02:00
|
|
|
/// An error occurred in youtube-dl.
|
|
|
|
#[error("Youtube-dl failed: {0}")]
|
2022-05-26 20:37:27 +02:00
|
|
|
YoutubeDl(#[from] youtube_dl::Error),
|
2022-05-26 19:57:16 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
impl<'r, 'o: 'r> rocket::response::Responder<'r, 'o> for Error {
|
|
|
|
fn respond_to(self, _request: &'r Request<'_>) -> rocket::response::Result<'o> {
|
|
|
|
eprintln!("💥 Encountered error: {}", self);
|
|
|
|
|
|
|
|
match self {
|
|
|
|
Error::NoRedirectUrlFound => Err(Status::NotFound),
|
|
|
|
_ => Err(Status::InternalServerError),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Result type that defaults to [`Error`] as the default error type.
|
|
|
|
pub(crate) type Result<T, E = Error> = std::result::Result<T, E>;
|
|
|
|
|
2022-05-20 16:15:33 +02:00
|
|
|
/// The extra application specific configuration.
|
|
|
|
#[derive(Debug, Deserialize, Serialize)]
|
|
|
|
#[serde(crate = "rocket::serde")]
|
|
|
|
pub(crate) struct Config {
|
2022-10-17 19:47:29 +02:00
|
|
|
/// The public URL at which the application is hosted or proxied from.
|
2022-05-20 16:15:33 +02:00
|
|
|
#[serde(default)]
|
2022-10-17 19:47:29 +02:00
|
|
|
public_url: String,
|
2022-05-20 16:15:33 +02:00
|
|
|
}
|
|
|
|
|
2022-05-17 11:15:22 +02:00
|
|
|
/// A Rocket responder wrapper type for RSS feeds.
|
|
|
|
#[derive(Responder)]
|
|
|
|
#[response(content_type = "application/xml")]
|
|
|
|
struct RssFeed(String);
|
|
|
|
|
2022-05-26 19:57:16 +02:00
|
|
|
/// Retrieves a download by redirecting to the URL resolved by the selected back-end.
|
2022-08-15 20:17:34 +02:00
|
|
|
#[get("/download/<backend_id>/<file..>")]
|
|
|
|
pub(crate) async fn get_download(file: PathBuf, backend_id: &str) -> Result<Redirect> {
|
|
|
|
let backend = backends::get(backend_id)?;
|
|
|
|
|
|
|
|
backend.redirect_url(&file).await.map(Redirect::to)
|
2022-05-24 09:58:57 +02:00
|
|
|
}
|
|
|
|
|
2022-08-13 15:14:15 +02:00
|
|
|
/// Handler for retrieving the RSS feed of a channel on a certain back-end.
|
2022-05-27 22:47:36 +02:00
|
|
|
///
|
|
|
|
/// The limit parameter determines the maximum of items that can be in the feed.
|
2022-08-15 20:17:34 +02:00
|
|
|
#[get("/feed/<backend_id>/<channel_id>?<limit>")]
|
2022-08-14 10:15:59 +02:00
|
|
|
async fn get_feed(
|
2022-08-15 20:17:34 +02:00
|
|
|
backend_id: &str,
|
2022-08-13 15:14:15 +02:00
|
|
|
channel_id: &str,
|
2022-05-27 22:31:17 +02:00
|
|
|
limit: Option<usize>,
|
|
|
|
config: &State<Config>,
|
|
|
|
) -> Result<RssFeed> {
|
2022-08-15 20:17:34 +02:00
|
|
|
let backend = backends::get(backend_id)?;
|
|
|
|
let channel = backend.channel(channel_id, limit).await?;
|
|
|
|
let feed = feed::construct(backend_id, config, channel);
|
2022-05-24 10:35:10 +02:00
|
|
|
|
2022-08-14 10:15:59 +02:00
|
|
|
Ok(RssFeed(feed.to_string()))
|
2022-05-17 11:15:22 +02:00
|
|
|
}
|
|
|
|
|
2022-05-24 11:04:27 +02:00
|
|
|
/// Returns a simple index page that explains the usage.
|
|
|
|
#[get("/")]
|
2022-08-14 10:15:59 +02:00
|
|
|
pub(crate) async fn get_index(config: &State<Config>) -> Template {
|
2022-10-17 19:47:29 +02:00
|
|
|
Template::render("index", context! { url: &config.public_url })
|
2022-05-24 11:04:27 +02:00
|
|
|
}
|
|
|
|
|
2022-05-17 11:15:22 +02:00
|
|
|
/// Sets up Rocket.
|
|
|
|
pub fn setup() -> Rocket<Build> {
|
|
|
|
rocket::build()
|
2022-08-14 10:15:59 +02:00
|
|
|
.mount("/", routes![get_download, get_feed, get_index])
|
2022-05-20 16:15:33 +02:00
|
|
|
.attach(AdHoc::config::<Config>())
|
2022-05-24 11:04:27 +02:00
|
|
|
.attach(Template::fairing())
|
2022-05-17 11:15:22 +02:00
|
|
|
}
|