use std::sync::Arc; use std::time::Duration; use actix::{Actor, Addr}; use actix_web::{get, middleware::Logger, post, web, App, HttpServer, Responder}; use askama::Template; use askama_actix::TemplateIntoResponse; use serde::{Deserialize, Serialize}; use crate::bot::MasterBot; use crate::youtube_dl::AudioMetadata; mod api; mod bot_executor; mod default; mod front_end_cookie; mod tmtu; pub use bot_executor::*; use front_end_cookie::FrontEnd; pub struct WebServerArgs { pub domain: String, pub bind_address: String, pub bot: Arc, } #[actix_rt::main] pub async fn start(args: WebServerArgs) -> std::io::Result<()> { let cbot = args.bot.clone(); let bot_addr: Addr = BotExecutor(cbot.clone()).start(); HttpServer::new(move || { App::new() .data(bot_addr.clone()) .wrap(Logger::default()) .service(index) .service(get_bot) .service(post_front_end) .service( web::scope("/api") .service(api::get_bot_list) .service(api::get_bot), ) .service(web::scope("/docs").service(get_api_docs)) .service(actix_files::Files::new("/static", "web_server/static/")) }) .bind(args.bind_address)? .run() .await?; args.bot.quit(String::from("Stopping")); Ok(()) } #[derive(Deserialize)] #[serde(rename_all = "kebab-case")] struct FrontEndForm { front_end: FrontEnd, } #[post("/front-end")] async fn post_front_end(form: web::Form) -> impl Responder { front_end_cookie::set_front_end(form.into_inner().front_end).await } #[derive(Debug, Serialize)] pub struct BotData { pub name: String, pub state: crate::bot::State, pub volume: f64, pub position: Option, pub currently_playing: Option, pub playlist: Vec, } #[get("/")] async fn index(bot: web::Data>, front: FrontEnd) -> impl Responder { match front { FrontEnd::Default => default::index(bot).await, FrontEnd::Tmtu => tmtu::index(bot).await, } } #[get("/bot/{name}")] async fn get_bot( bot: web::Data>, name: web::Path, front: FrontEnd, ) -> impl Responder { match front { FrontEnd::Default => default::get_bot(bot, name.into_inner()).await, FrontEnd::Tmtu => tmtu::get_bot(bot, name.into_inner()).await, } } #[derive(Template)] #[template(path = "docs/api.htm")] struct ApiDocsTemplate; #[get("/api")] async fn get_api_docs() -> impl Responder { ApiDocsTemplate.into_response() } mod filters { use std::time::Duration; pub fn fmt_duration(duration: &Option) -> Result { if let Some(duration) = duration { let secs = duration.as_secs(); let mins = secs / 60; let submin_secs = secs % 60; Ok(format!("{:02}:{:02}", mins, submin_secs)) } else { Ok(String::from("--:--")) } } }