premier push master
This commit is contained in:
parent
2704414239
commit
b252d2c872
1674
Cargo.lock
generated
Normal file
1674
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
22
Cargo.toml
Normal file
22
Cargo.toml
Normal file
@ -0,0 +1,22 @@
|
||||
[package]
|
||||
name = "src"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
actix-web = "4"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
anyhow = "1.0"
|
||||
uuid = { version = "1", features = ["v4"] }
|
||||
dotenvy = "0.15"
|
||||
futures-util = "0.3.31"
|
||||
url = "2.5.7"
|
||||
|
||||
[[bin]]
|
||||
name = "src"
|
||||
path = "./src/main.rs"
|
||||
|
||||
|
||||
uuid = { version = "1", features = ["v4"] }
|
||||
50
src/handlers/download.rs
Normal file
50
src/handlers/download.rs
Normal file
@ -0,0 +1,50 @@
|
||||
use actix_web::{post, web, HttpResponse, Responder};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use crate::services::youtube::download_from_youtube;
|
||||
use crate::services::converter::convert_file;
|
||||
use crate::utils::validate::validate_request;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct DownloadRequest {
|
||||
pub url: String,
|
||||
pub format: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct ApiResponse {
|
||||
status: String,
|
||||
message: String,
|
||||
file: Option<String>,
|
||||
}
|
||||
|
||||
#[post("/download")]
|
||||
pub async fn download(req: web::Json<DownloadRequest>) -> impl Responder {
|
||||
if let Err(msg) = validate_request(&req.url, &req.format) {
|
||||
return HttpResponse::BadRequest().json(serde_json::json!({
|
||||
"status": "error",
|
||||
"message": msg,
|
||||
"file": null
|
||||
}));
|
||||
}
|
||||
match download_from_youtube(&req.url, &req.format).await {
|
||||
Ok(file_path) => {
|
||||
match convert_file(&file_path, &req.format).await {
|
||||
Ok(final_file) => HttpResponse::Ok().json(ApiResponse {
|
||||
status: "ok".to_string(),
|
||||
message: "Téléchargement et conversion réussis".to_string(),
|
||||
file: Some(final_file),
|
||||
}),
|
||||
Err(e) => HttpResponse::InternalServerError().json(ApiResponse {
|
||||
status: "error".to_string(),
|
||||
message: format!("Erreur conversion: {}", e),
|
||||
file: None,
|
||||
}),
|
||||
}
|
||||
}
|
||||
Err(e) => HttpResponse::InternalServerError().json(ApiResponse {
|
||||
status: "error".to_string(),
|
||||
message: format!("Erreur téléchargement: {}", e),
|
||||
file: None,
|
||||
}),
|
||||
}
|
||||
}
|
||||
1
src/handlers/mod.rs
Normal file
1
src/handlers/mod.rs
Normal file
@ -0,0 +1 @@
|
||||
pub mod download;
|
||||
27
src/main.rs
Normal file
27
src/main.rs
Normal file
@ -0,0 +1,27 @@
|
||||
mod routes;
|
||||
mod handlers;
|
||||
mod services;
|
||||
mod middleware;
|
||||
mod utils;
|
||||
|
||||
use actix_web::{App, HttpServer};
|
||||
use middleware::auth::ApiKeyMiddleware;
|
||||
|
||||
|
||||
|
||||
|
||||
#[actix_web::main]
|
||||
async fn main() -> std::io::Result<()> {
|
||||
dotenvy::dotenv().ok();
|
||||
|
||||
println!(" API Jellyfin sur http://192.168.1.107:8080");
|
||||
|
||||
HttpServer::new(|| {
|
||||
App::new()
|
||||
.wrap(ApiKeyMiddleware) //
|
||||
.configure(routes::config)
|
||||
})
|
||||
.bind("192.168.1.107:8080")?
|
||||
.run()
|
||||
.await
|
||||
}
|
||||
73
src/middleware/auth.rs
Normal file
73
src/middleware/auth.rs
Normal file
@ -0,0 +1,73 @@
|
||||
use actix_web::{
|
||||
dev::{Service, ServiceRequest, ServiceResponse, Transform},
|
||||
body::{BoxBody, MessageBody},
|
||||
Error, HttpResponse,
|
||||
};
|
||||
use futures_util::future::{ok, Ready, LocalBoxFuture};
|
||||
use std::rc::Rc;
|
||||
use std::env;
|
||||
|
||||
pub struct ApiKeyMiddleware;
|
||||
|
||||
impl<S, B> Transform<S, ServiceRequest> for ApiKeyMiddleware
|
||||
where
|
||||
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error> + 'static,
|
||||
B: MessageBody + 'static, // ✅ contrainte ajoutée
|
||||
{
|
||||
type Response = ServiceResponse<BoxBody>; // ✅ on fige le type
|
||||
type Error = Error;
|
||||
type Transform = ApiKeyMiddlewareInner<S>;
|
||||
type InitError = ();
|
||||
type Future = Ready<Result<Self::Transform, Self::InitError>>;
|
||||
|
||||
fn new_transform(&self, service: S) -> Self::Future {
|
||||
ok(ApiKeyMiddlewareInner {
|
||||
service: Rc::new(service),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ApiKeyMiddlewareInner<S> {
|
||||
service: Rc<S>,
|
||||
}
|
||||
|
||||
impl<S, B> Service<ServiceRequest> for ApiKeyMiddlewareInner<S>
|
||||
where
|
||||
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error> + 'static,
|
||||
B: MessageBody + 'static, // ✅ même contrainte ici
|
||||
{
|
||||
type Response = ServiceResponse<BoxBody>; // ✅ réponse homogène
|
||||
type Error = Error;
|
||||
type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
|
||||
|
||||
fn poll_ready(
|
||||
&self,
|
||||
ctx: &mut std::task::Context<'_>,
|
||||
) -> std::task::Poll<Result<(), Self::Error>> {
|
||||
self.service.poll_ready(ctx)
|
||||
}
|
||||
|
||||
fn call(&self, req: ServiceRequest) -> Self::Future {
|
||||
let auth_header = req.headers().get("Authorization").cloned();
|
||||
let service = self.service.clone();
|
||||
|
||||
Box::pin(async move {
|
||||
let expected_key = env::var("API_KEY").unwrap_or_else(|_| "changeme".to_string());
|
||||
|
||||
if let Some(value) = auth_header {
|
||||
if value == format!("Bearer {}", expected_key) {
|
||||
// ✅ convertit en BoxBody
|
||||
return service.call(req).await.map(|res| res.map_into_boxed_body());
|
||||
}
|
||||
}
|
||||
|
||||
let (req, _) = req.into_parts();
|
||||
|
||||
let res = HttpResponse::Unauthorized()
|
||||
.body("Unauthorized")
|
||||
.map_into_boxed_body();
|
||||
|
||||
Ok(ServiceResponse::new(req, res))
|
||||
})
|
||||
}
|
||||
}
|
||||
1
src/middleware/mod.rs
Normal file
1
src/middleware/mod.rs
Normal file
@ -0,0 +1 @@
|
||||
pub mod auth;
|
||||
7
src/routes.rs
Normal file
7
src/routes.rs
Normal file
@ -0,0 +1,7 @@
|
||||
use actix_web::web;
|
||||
|
||||
use crate::handlers::download::download;
|
||||
|
||||
pub fn config(cfg: &mut web::ServiceConfig) {
|
||||
cfg.service(download);
|
||||
}
|
||||
19
src/services/converter.rs
Normal file
19
src/services/converter.rs
Normal file
@ -0,0 +1,19 @@
|
||||
use std::process::Command;
|
||||
use anyhow::Result;
|
||||
use uuid::Uuid;
|
||||
|
||||
pub async fn convert_file(input: &str, format: &str) -> Result<String> {
|
||||
let output_file = format!("/tmp/{}_conv.{}", Uuid::new_v4(), format);
|
||||
|
||||
let status = Command::new("ffmpeg")
|
||||
.arg("-y")
|
||||
.arg("-i").arg(input)
|
||||
.arg(&output_file)
|
||||
.status()?;
|
||||
|
||||
if status.success() {
|
||||
Ok(output_file)
|
||||
} else {
|
||||
Err(anyhow::anyhow!("ffmpeg conversion failed"))
|
||||
}
|
||||
}
|
||||
7
src/services/jellyfin.rs
Normal file
7
src/services/jellyfin.rs
Normal file
@ -0,0 +1,7 @@
|
||||
pub async fn upload_to_jellyfin(file_path: &str) -> Result<(), anyhow::Error> {
|
||||
|
||||
|
||||
|
||||
println!("Fichier {} envoyé à Jellyfin", file_path);
|
||||
Ok(())
|
||||
}
|
||||
3
src/services/mod.rs
Normal file
3
src/services/mod.rs
Normal file
@ -0,0 +1,3 @@
|
||||
pub mod youtube;
|
||||
pub mod jellyfin;
|
||||
pub mod converter;
|
||||
23
src/services/youtube.rs
Normal file
23
src/services/youtube.rs
Normal file
@ -0,0 +1,23 @@
|
||||
use std::process::Command;
|
||||
use anyhow::Result;
|
||||
use uuid::Uuid;
|
||||
|
||||
pub async fn download_from_youtube(url: &str, format: &str) -> Result<String> {
|
||||
let output_file = format!("/tmp/{}_out.{}", Uuid::new_v4(), format);
|
||||
|
||||
let status = Command::new("yt-dlp")
|
||||
.arg("-x")
|
||||
.arg("--audio-format")
|
||||
.arg(format)
|
||||
.arg("--no-playlist")
|
||||
.arg("-o")
|
||||
.arg(&output_file)
|
||||
.arg(url)
|
||||
.status()?;
|
||||
|
||||
if status.success() {
|
||||
Ok(output_file)
|
||||
} else {
|
||||
Err(anyhow::anyhow!("yt-dlp failed with status: {:?}", status))
|
||||
}
|
||||
}
|
||||
3
src/utils/filename.rs
Normal file
3
src/utils/filename.rs
Normal file
@ -0,0 +1,3 @@
|
||||
pub fn generate_temp_filename(extension: &str) -> String {
|
||||
format!("/tmp/file_{}.{}", uuid::Uuid::new_v4(), extension)
|
||||
}
|
||||
2
src/utils/mod.rs
Normal file
2
src/utils/mod.rs
Normal file
@ -0,0 +1,2 @@
|
||||
pub mod validate;
|
||||
pub mod filename;
|
||||
19
src/utils/validate.rs
Normal file
19
src/utils/validate.rs
Normal file
@ -0,0 +1,19 @@
|
||||
use url::Url;
|
||||
|
||||
pub fn validate_request(url: &str, format: &str) -> Result<(), String> {
|
||||
if let Ok(parsed) = Url::parse(url) {
|
||||
match parsed.domain() {
|
||||
Some("youtube.com") | Some("www.youtube.com") | Some("youtu.be") => {}
|
||||
_ => return Err("URL non autorisée.".into()),
|
||||
}
|
||||
} else {
|
||||
return Err("URL invalide.".into());
|
||||
}
|
||||
|
||||
let allowed = ["mp3", "flac", "wav"];
|
||||
if !allowed.contains(&format) {
|
||||
return Err("Format non supporté.".into());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user