diff options
| author | Jokler <jokler@protonmail.com> | 2020-02-22 22:46:06 +0100 |
|---|---|---|
| committer | Jokler <jokler@protonmail.com> | 2020-02-22 23:20:10 +0100 |
| commit | 84804836f5c1e782c77f1bbf676177151558e008 (patch) | |
| tree | 55f0ee9664018f6ed0cc41d2cfcf13ca3e0ffe60 /src | |
| parent | 5eea11a03c11551091b2c72f48590aec7f5410f0 (diff) | |
| download | pokebot-84804836f5c1e782c77f1bbf676177151558e008.tar.gz pokebot-84804836f5c1e782c77f1bbf676177151558e008.zip | |
Add tmtu mode as a front-end
Diffstat (limited to 'src')
| -rw-r--r-- | src/audio_player.rs | 6 | ||||
| -rw-r--r-- | src/bot/master.rs | 18 | ||||
| -rw-r--r-- | src/bot/music.rs | 16 | ||||
| -rw-r--r-- | src/web_server.rs | 104 | ||||
| -rw-r--r-- | src/web_server/front_end_cookie.rs | 60 |
5 files changed, 196 insertions, 8 deletions
diff --git a/src/audio_player.rs b/src/audio_player.rs index 4df213f..4bcab56 100644 --- a/src/audio_player.rs +++ b/src/audio_player.rs @@ -223,6 +223,12 @@ impl AudioPlayer { *self.volume_f64.read().unwrap() } + pub fn position(&self) -> Option<Duration> { + self.pipeline + .query_position::<gst::ClockTime>() + .and_then(|t| t.0.map(|v| Duration::from_nanos(v))) + } + pub fn currently_playing(&self) -> Option<AudioMetadata> { self.currently_playing.read().unwrap().clone() } diff --git a/src/bot/master.rs b/src/bot/master.rs index 67867ef..9b33744 100644 --- a/src/bot/master.rs +++ b/src/bot/master.rs @@ -222,6 +222,7 @@ impl MasterBot { name: name, state: bot.state(), volume: bot.volume(), + position: bot.position(), currently_playing: bot.currently_playing(), playlist: bot.playlist_to_vec(), }) @@ -237,6 +238,7 @@ impl MasterBot { name: name.clone(), state: bot.state(), volume: bot.volume(), + position: bot.position(), currently_playing: bot.currently_playing(), playlist: bot.playlist_to_vec(), }; @@ -247,6 +249,18 @@ impl MasterBot { result } + pub fn bot_names(&self) -> Vec<String> { + let music_bots = self.music_bots.read().unwrap(); + + let len = music_bots.connected_bots.len(); + let mut result = Vec::with_capacity(len); + for (name, _) in &music_bots.connected_bots { + result.push(name.clone()); + } + + result + } + pub fn quit(&self, reason: String) { let music_bots = self.music_bots.read().unwrap(); for (_, bot) in &music_bots.connected_bots { @@ -267,6 +281,8 @@ pub struct MasterArgs { pub channel: Option<String>, #[serde(default = "default_verbose")] pub verbose: u8, + pub domain: String, + pub bind_address: String, pub names: Vec<String>, pub id: Identity, pub ids: Vec<Identity>, @@ -301,6 +317,8 @@ impl MasterArgs { ids: self.ids, local, address, + domain: self.domain, + bind_address: self.bind_address, id: self.id, channel, verbose, diff --git a/src/bot/music.rs b/src/bot/music.rs index 0def280..41976e5 100644 --- a/src/bot/music.rs +++ b/src/bot/music.rs @@ -2,6 +2,7 @@ use std::future::Future; use std::io::BufRead; use std::sync::{Arc, RwLock}; use std::thread; +use std::time::Duration; use humantime; use log::{debug, info}; @@ -193,9 +194,10 @@ impl MusicBot { self.player.play().unwrap(); } - pub async fn add_audio(&self, url: String) { + pub async fn add_audio(&self, url: String, user: String) { match crate::youtube_dl::get_audio_download_url(url).await { - Ok(metadata) => { + Ok(mut metadata) => { + metadata.added_by = user; info!("Found audio url: {}", metadata.url); let mut playlist = self.playlist.write().expect("RwLock was not poisoned"); @@ -232,6 +234,10 @@ impl MusicBot { self.player.volume() } + pub fn position(&self) -> Option<Duration> { + self.player.position() + } + pub fn currently_playing(&self) -> Option<AudioMetadata> { self.player.currently_playing() } @@ -278,7 +284,7 @@ impl MusicBot { let tokens = msg[1..].split_whitespace().collect::<Vec<_>>(); match Command::from_iter_safe(&tokens) { - Ok(args) => self.on_command(args).await?, + Ok(args) => self.on_command(args, message.invoker).await?, Err(e) if e.kind == structopt::clap::ErrorKind::HelpDisplayed => { self.send_message(&format!("\n{}", e.message)); } @@ -289,7 +295,7 @@ impl MusicBot { Ok(()) } - async fn on_command(&self, command: Command) -> Result<(), AudioPlayerError> { + async fn on_command(&self, command: Command, invoker: Invoker) -> Result<(), AudioPlayerError> { match command { Command::Play => { let playlist = self.playlist.read().expect("RwLock was not poisoned"); @@ -306,7 +312,7 @@ impl MusicBot { // strip bbcode tags from url let url = url.replace("[URL]", "").replace("[/URL]", ""); - self.add_audio(url.to_string()).await; + self.add_audio(url.to_string(), invoker.name).await; } Command::Pause => { self.player.pause()?; diff --git a/src/web_server.rs b/src/web_server.rs index 02c57e7..80c914c 100644 --- a/src/web_server.rs +++ b/src/web_server.rs @@ -1,17 +1,22 @@ use std::sync::Arc; +use std::time::Duration; use actix::{Actor, Addr, Handler, Message, SyncArbiter, SyncContext}; use actix_web::{ - get, middleware::Logger, web, App, HttpResponse, HttpServer, Responder, ResponseError, + get, http::header, middleware::Logger, post, web, App, Error, HttpResponse, HttpServer, + Responder, ResponseError, }; use askama::actix_web::TemplateIntoResponse; use askama::Template; use derive_more::Display; -use serde::Serialize; +use serde::{Deserialize, Serialize}; use crate::bot::MasterBot; use crate::youtube_dl::AudioMetadata; +mod front_end_cookie; +use front_end_cookie::FrontEnd; + pub struct WebServerArgs { pub domain: String, pub bind_address: String, @@ -28,6 +33,8 @@ pub async fn start(args: WebServerArgs) -> std::io::Result<()> { .data(bot_addr.clone()) .wrap(Logger::default()) .service(index) + .service(tmtu_bot) + .service(post_front_end) .service(web::scope("/api").service(get_bot_list).service(get_bot)) .service(actix_files::Files::new("/static", "static/")) }) @@ -46,6 +53,34 @@ impl Actor for BotExecutor { type Context = SyncContext<Self>; } +#[derive(Deserialize)] +#[serde(rename_all = "kebab-case")] +struct FrontEndForm { + front_end: FrontEnd, +} + +#[post("/front-end")] +async fn post_front_end(form: web::Form<FrontEndForm>) -> Result<HttpResponse, Error> { + front_end_cookie::set_front_end(form.into_inner().front_end).await +} + +struct BotNameListRequest; + +impl Message for BotNameListRequest { + // A plain Vec does not work for some reason + type Result = Result<Vec<String>, ()>; +} + +impl Handler<BotNameListRequest> for BotExecutor { + type Result = Result<Vec<String>, ()>; + + fn handle(&mut self, _: BotNameListRequest, _: &mut Self::Context) -> Self::Result { + let bot = &self.0; + + Ok(bot.bot_names()) + } +} + struct BotDataListRequest; impl Message for BotDataListRequest { @@ -86,17 +121,32 @@ struct OverviewTemplate<'a> { bots: &'a [BotData], } +#[derive(Template)] +#[template(path = "tmtu/index.htm")] +struct TmtuTemplate { + bot_names: Vec<String>, + bot: Option<BotData>, +} + #[derive(Debug, Serialize)] pub struct BotData { pub name: String, pub state: crate::bot::State, pub volume: f64, + pub position: Option<Duration>, pub currently_playing: Option<AudioMetadata>, pub playlist: Vec<AudioMetadata>, } #[get("/")] -async fn index(bot: web::Data<Addr<BotExecutor>>) -> impl Responder { +async fn index(bot: web::Data<Addr<BotExecutor>>, front: FrontEnd) -> Result<HttpResponse, Error> { + match front { + FrontEnd::Lazy => lazy_index(bot).await, + FrontEnd::Tmtu => tmtu_index(bot).await, + } +} + +async fn lazy_index(bot: web::Data<Addr<BotExecutor>>) -> Result<HttpResponse, Error> { let bot_datas = match bot.send(BotDataListRequest).await.unwrap() { Ok(data) => data, Err(_) => Vec::with_capacity(0), @@ -108,6 +158,38 @@ async fn index(bot: web::Data<Addr<BotExecutor>>) -> impl Responder { .into_response() } +async fn tmtu_index(bot: web::Data<Addr<BotExecutor>>) -> Result<HttpResponse, Error> { + let bot_names = bot.send(BotNameListRequest).await.unwrap().unwrap(); + + TmtuTemplate { + bot_names, + bot: None, + } + .into_response() +} + +#[get("/tmtu/{name}")] +async fn tmtu_bot( + bot: web::Data<Addr<BotExecutor>>, + name: web::Path<String>, + front: FrontEnd, +) -> Result<HttpResponse, Error> { + if front != FrontEnd::Tmtu { + return Ok(HttpResponse::Found().header(header::LOCATION, "/").finish()); + } + + let bot_names = bot.send(BotNameListRequest).await.unwrap().unwrap(); + if let Some(bot) = bot.send(BotDataRequest(name.into_inner())).await.unwrap() { + TmtuTemplate { + bot_names, + bot: Some(bot), + } + .into_response() + } else { + Ok(HttpResponse::Found().header(header::LOCATION, "/").finish()) + } +} + #[get("/bots")] async fn get_bot_list(bot: web::Data<Addr<BotExecutor>>) -> impl Responder { let bot_datas = match bot.send(BotDataListRequest).await.unwrap() { @@ -149,3 +231,19 @@ async fn get_bot(bot: web::Data<Addr<BotExecutor>>, name: web::Path<String>) -> Err(ApiErrorKind::NotFound) } } + +mod filters { + use std::time::Duration; + + pub fn fmt_duration(duration: &Option<Duration>) -> Result<String, askama::Error> { + 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("--:--")) + } + } +} diff --git a/src/web_server/front_end_cookie.rs b/src/web_server/front_end_cookie.rs new file mode 100644 index 0000000..0207933 --- /dev/null +++ b/src/web_server/front_end_cookie.rs @@ -0,0 +1,60 @@ +use futures::future::{ok, Ready}; + +use actix_web::{ + dev::Payload, + http::header::{COOKIE, LOCATION, SET_COOKIE}, + FromRequest, HttpRequest, HttpResponse, +}; +use serde::Deserialize; + +#[derive(PartialEq, Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum FrontEnd { + Lazy, + Tmtu, +} + +impl FrontEnd { + const COOKIE_NAME: &'static str = "front-end"; + + fn cookie(&self) -> String { + let name = match self { + FrontEnd::Lazy => "lazy", + FrontEnd::Tmtu => "tmtu", + }; + + format!("{}={}", Self::COOKIE_NAME, name) + } +} + +impl FromRequest for FrontEnd { + type Error = (); + type Future = Ready<Result<Self, ()>>; + type Config = (); + + fn from_request(req: &HttpRequest, _payload: &mut Payload) -> Self::Future { + for header in req.headers().get_all(COOKIE) { + if let Ok(value) = header.to_str() { + for c in value.split(';').map(|s| s.trim()) { + let mut split = c.split('='); + if Some(Self::COOKIE_NAME) == split.next() { + match split.next() { + Some("lazy") => return ok(FrontEnd::Lazy), + Some("tmtu") => return ok(FrontEnd::Tmtu), + _ => (), + } + } + } + } + } + + ok(FrontEnd::Lazy) + } +} + +pub fn set_front_end(front: FrontEnd) -> HttpResponse { + HttpResponse::Found() + .header(SET_COOKIE, front.cookie()) + .header(LOCATION, "/") + .finish() +} |
