aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJokler <jokler@protonmail.com>2020-02-02 19:50:33 +0100
committerJokler <jokler@protonmail.com>2020-02-22 23:20:10 +0100
commit2831c2b60cb61a14c7efee4ab5c0389eb3ad5469 (patch)
tree835f1abad6e234f6d74d4be999f690709954be89
parentca4c0158f417b87f04313053a3f656f2de4e803b (diff)
downloadpokebot-2831c2b60cb61a14c7efee4ab5c0389eb3ad5469.tar.gz
pokebot-2831c2b60cb61a14c7efee4ab5c0389eb3ad5469.zip
Add a very basic template using available info
-rw-r--r--Cargo.lock123
-rw-r--r--Cargo.toml8
-rw-r--r--src/audio_player.rs29
-rw-r--r--src/bot/master.rs22
-rw-r--r--src/bot/music.rs32
-rw-r--r--src/playlist.rs10
-rw-r--r--src/web_server.rs63
-rw-r--r--src/youtube_dl.rs28
-rw-r--r--static/.gitkeep0
-rw-r--r--templates/base.htm14
-rw-r--r--templates/index.htm27
-rw-r--r--templates/song.htm10
12 files changed, 334 insertions, 32 deletions
diff --git a/Cargo.lock b/Cargo.lock
index e5b821c..13ef6a1 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -60,6 +60,26 @@ dependencies = [
]
[[package]]
+name = "actix-files"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "301482841d3d74483a446ead63cb7d362e187d2c8b603f13d91995621ea53c46"
+dependencies = [
+ "actix-http",
+ "actix-service",
+ "actix-web",
+ "bitflags",
+ "bytes 0.5.3",
+ "derive_more 0.99.2",
+ "futures 0.3.1",
+ "log",
+ "mime",
+ "mime_guess",
+ "percent-encoding 2.1.0",
+ "v_htmlescape",
+]
+
+[[package]]
name = "actix-http"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -392,6 +412,54 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cff77d8686867eceff3105329d4698d96c2391c176d5d03adc90c7389162b5b8"
[[package]]
+name = "askama"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "10a1fb9e41eb366cbcd267da2094be5b7e62fdbca9f82091e7503e80f885050d"
+dependencies = [
+ "actix-web",
+ "askama_derive",
+ "askama_escape",
+ "askama_shared",
+ "bytes 0.5.3",
+ "futures 0.3.1",
+ "mime",
+ "mime_guess",
+]
+
+[[package]]
+name = "askama_derive"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e1012c270085fa35ece6a48a569544fde85b6d9ee41074c7b706cc912a03f939"
+dependencies = [
+ "askama_shared",
+ "nom 5.1.0",
+ "proc-macro2 1.0.7",
+ "quote 1.0.2",
+ "syn 1.0.13",
+]
+
+[[package]]
+name = "askama_escape"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a577aeba5fec1aafb9f195d98cfcc38a78b588e4ebf9b15f62ca1c7aa33795a"
+
+[[package]]
+name = "askama_shared"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ee517f4e33c27b129928e71d8a044d54c513e72e0b72ec5c4f5f1823e9de353"
+dependencies = [
+ "askama_escape",
+ "humansize",
+ "num-traits",
+ "serde",
+ "toml",
+]
+
+[[package]]
name = "async-trait"
version = "0.1.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1572,6 +1640,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd179ae861f0c2e53da70d892f5f3029f9594be0c41dc5269cd371691b1dc2f9"
[[package]]
+name = "humansize"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6cab2627acfc432780848602f3f558f7e9dd427352224b0d9324025796d2a5e"
+
+[[package]]
name = "humantime"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1973,6 +2047,16 @@ dependencies = [
]
[[package]]
+name = "nom"
+version = "5.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c433f4d505fe6ce7ff78523d2fa13a0b9f2690e181fc26168bcbe5ccc5d14e07"
+dependencies = [
+ "memchr",
+ "version_check 0.1.5",
+]
+
+[[package]]
name = "num-bigint"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2272,8 +2356,10 @@ name = "pokebot"
version = "0.1.1"
dependencies = [
"actix",
+ "actix-files",
"actix-rt",
"actix-web",
+ "askama",
"byte-slice-cast",
"futures 0.1.29",
"futures-preview",
@@ -3077,7 +3163,7 @@ version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "58898aa9e9462043aa48c62021ba284a6906d210bde13602e65be8329c364d33"
dependencies = [
- "nom",
+ "nom 4.2.3",
"proc-macro2 0.4.30",
"quote 0.6.13",
"syn 0.15.44",
@@ -3645,7 +3731,7 @@ dependencies = [
"bytes 0.4.12",
"derive_more 0.14.1",
"failure",
- "nom",
+ "nom 4.2.3",
"num-derive",
"num-traits",
"rental",
@@ -3797,6 +3883,37 @@ dependencies = [
]
[[package]]
+name = "v_escape"
+version = "0.7.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "660b101c07b5d0863deb9e7fb3138777e858d6d2a79f9e6049a27d1cc77c6da6"
+dependencies = [
+ "v_escape_derive",
+]
+
+[[package]]
+name = "v_escape_derive"
+version = "0.5.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c2ca2a14bc3fc5b64d188b087a7d3a927df87b152e941ccfbc66672e20c467ae"
+dependencies = [
+ "nom 4.2.3",
+ "proc-macro2 1.0.7",
+ "quote 1.0.2",
+ "syn 1.0.13",
+]
+
+[[package]]
+name = "v_htmlescape"
+version = "0.4.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e33e939c0d8cf047514fb6ba7d5aac78bc56677a6938b2ee67000b91f2e97e41"
+dependencies = [
+ "cfg-if",
+ "v_escape",
+]
+
+[[package]]
name = "vcpkg"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3926,7 +4043,7 @@ version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3bb43f70885151e629e2a19ce9e50bd730fd436cfd4b666894c9ce4de9141164"
dependencies = [
- "nom",
+ "nom 4.2.3",
]
[[package]]
diff --git a/Cargo.toml b/Cargo.toml
index 1fdb258..15b80f7 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -34,7 +34,9 @@ gstreamer-audio = "0.15.0"
byte-slice-cast = "0.3.5"
serde_json = "1.0.44"
serde = "1.0.104"
-rand = { version = "0.7.3", features = ["small_rng"] }
-actix-web = "2.0.0"
-actix-rt = "1.0.0"
actix = "0.9.0"
+actix-rt = "1.0.0"
+actix-web = "2.0.0"
+actix-files = "0.2.1"
+askama = { version = "0.9.0", features = ["with-actix-web"] }
+rand = { version = "0.7.3", features = ["small_rng"] }
diff --git a/src/audio_player.rs b/src/audio_player.rs
index cdb04d7..4df213f 100644
--- a/src/audio_player.rs
+++ b/src/audio_player.rs
@@ -13,6 +13,8 @@ use log::{debug, error, info, warn};
use std::sync::{Arc, RwLock};
use tokio02::sync::mpsc::UnboundedSender;
+use crate::youtube_dl::AudioMetadata;
+
static GST_INIT: Once = Once::new();
#[derive(Copy, Clone, Debug)]
@@ -33,8 +35,10 @@ pub struct AudioPlayer {
bus: gst::Bus,
http_src: gst::Element,
+ volume_f64: RwLock<f64>,
volume: gst::Element,
sender: Arc<RwLock<UnboundedSender<MusicBotMessage>>>,
+ currently_playing: RwLock<Option<AudioMetadata>>,
}
fn make_element(factoryname: &str, display_name: &str) -> Result<gst::Element, AudioPlayerError> {
@@ -111,8 +115,10 @@ impl AudioPlayer {
bus,
http_src,
+ volume_f64: RwLock::new(0.0),
volume,
sender,
+ currently_playing: RwLock::new(None),
})
}
@@ -173,7 +179,16 @@ impl AudioPlayer {
Ok((audio_bin, volume, ghost_pad))
}
- pub fn set_source_url(&self, location: String) -> Result<(), AudioPlayerError> {
+ pub fn set_metadata(&self, data: AudioMetadata) -> Result<(), AudioPlayerError> {
+ self.set_source_url(data.url.clone())?;
+
+ let mut currently_playing = self.currently_playing.write().unwrap();
+ *currently_playing = Some(data);
+
+ Ok(())
+ }
+
+ fn set_source_url(&self, location: String) -> Result<(), AudioPlayerError> {
info!("Setting location URI: {}", location);
self.http_src.set_property("location", &location)?;
@@ -181,6 +196,7 @@ impl AudioPlayer {
}
pub fn set_volume(&self, volume: f64) -> Result<(), AudioPlayerError> {
+ *self.volume_f64.write().unwrap() = volume;
let db = 50.0 * volume.log10();
info!("Setting volume: {} -> {} dB", volume, db);
@@ -203,9 +219,20 @@ impl AudioPlayer {
}
}
+ pub fn volume(&self) -> f64 {
+ *self.volume_f64.read().unwrap()
+ }
+
+ pub fn currently_playing(&self) -> Option<AudioMetadata> {
+ self.currently_playing.read().unwrap().clone()
+ }
+
pub fn reset(&self) -> Result<(), AudioPlayerError> {
info!("Setting pipeline state to null");
+ let mut currently_playing = self.currently_playing.write().unwrap();
+ *currently_playing = None;
+
self.pipeline.set_state(gst::State::Null)?;
Ok(())
diff --git a/src/bot/master.rs b/src/bot/master.rs
index bc38cca..10a7572 100644
--- a/src/bot/master.rs
+++ b/src/bot/master.rs
@@ -213,14 +213,24 @@ impl MasterBot {
Ok(())
}
- pub fn names(&self) -> Vec<String> {
+ pub fn bot_datas(&self) -> Vec<crate::web_server::BotData> {
let music_bots = self.music_bots.read().unwrap();
- music_bots
- .connected_bots
- .iter()
- .map(|(_, b)| b.name().to_owned())
- .collect()
+ let len = music_bots.connected_bots.len();
+ let mut result = Vec::with_capacity(len);
+ for (name, bot) in &music_bots.connected_bots {
+ let bot_data = crate::web_server::BotData {
+ name: name.clone(),
+ state: bot.state(),
+ volume: bot.volume(),
+ currently_playing: bot.currently_playing(),
+ playlist: bot.playlist_to_vec(),
+ };
+
+ result.push(bot_data);
+ }
+
+ result
}
pub fn quit(&self, reason: String) {
diff --git a/src/bot/music.rs b/src/bot/music.rs
index 920f1cb..d53e4a8 100644
--- a/src/bot/music.rs
+++ b/src/bot/music.rs
@@ -44,7 +44,7 @@ fn parse_seek(mut amount: &str) -> Result<Seek, ()> {
}
}
-#[derive(Debug, PartialEq, Eq)]
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum State {
Playing,
Paused,
@@ -52,6 +52,18 @@ pub enum State {
EndOfStream,
}
+impl std::fmt::Display for State {
+ fn fmt(&self, fmt: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
+ match self {
+ State::Playing => write!(fmt, "Playing"),
+ State::Paused => write!(fmt, "Paused"),
+ State::Stopped | State::EndOfStream => write!(fmt, "Stopped"),
+ }?;
+
+ Ok(())
+ }
+}
+
#[derive(Debug)]
pub enum MusicBotMessage {
TextMessage(Message),
@@ -176,7 +188,7 @@ impl MusicBot {
self.send_message(&format!("Playing {}", ts::underline(&metadata.title)));
self.set_description(&format!("Currently playing '{}'", metadata.title));
self.player.reset().unwrap();
- self.player.set_source_url(metadata.url).unwrap();
+ self.player.set_metadata(metadata).unwrap();
self.player.play().unwrap();
}
@@ -211,6 +223,22 @@ impl MusicBot {
&self.name
}
+ pub fn state(&self) -> State {
+ *self.state.read().expect("RwLock was not poisoned")
+ }
+
+ pub fn volume(&self) -> f64 {
+ self.player.volume()
+ }
+
+ pub fn currently_playing(&self) -> Option<AudioMetadata> {
+ self.player.currently_playing()
+ }
+
+ pub fn playlist_to_vec(&self) -> Vec<AudioMetadata> {
+ self.playlist.read().unwrap().to_vec()
+ }
+
pub fn my_channel(&self) -> ChannelId {
self.teamspeak
.as_ref()
diff --git a/src/playlist.rs b/src/playlist.rs
index 87c1c98..445f8a5 100644
--- a/src/playlist.rs
+++ b/src/playlist.rs
@@ -28,6 +28,16 @@ impl Playlist {
res
}
+ pub fn to_vec(&self) -> Vec<AudioMetadata> {
+ let (a, b) = self.data.as_slices();
+
+ let mut res = a.to_vec();
+ res.extend_from_slice(b);
+ res.reverse();
+
+ res
+ }
+
pub fn is_empty(&self) -> bool {
self.data.is_empty()
}
diff --git a/src/web_server.rs b/src/web_server.rs
index 1edbc50..94f043a 100644
--- a/src/web_server.rs
+++ b/src/web_server.rs
@@ -1,21 +1,12 @@
use std::sync::Arc;
use actix::{Actor, Addr, Handler, Message, SyncArbiter, SyncContext};
-use actix_web::{get, middleware::Logger, web, App, HttpResponse, HttpServer, Responder};
+use actix_web::{get, middleware::Logger, web, App, HttpServer, Responder};
+use askama::actix_web::TemplateIntoResponse;
+use askama::Template;
use crate::bot::MasterBot;
-
-struct GetNames;
-
-impl Message for GetNames {
- type Result = Result<Vec<String>, ()>;
-}
-
-#[get("/")]
-async fn index(bot: web::Data<Addr<BotExecutor>>) -> impl Responder {
- let names = bot.send(GetNames).await.unwrap().unwrap();
- HttpResponse::Ok().body(&format!("Music bots connected: {}", names.join(", ")))
-}
+use crate::youtube_dl::AudioMetadata;
pub struct WebServerArgs {
pub domain: String,
@@ -33,6 +24,7 @@ pub async fn start(args: WebServerArgs) -> std::io::Result<()> {
.data(bot_addr.clone())
.wrap(Logger::default())
.service(index)
+ .service(actix_files::Files::new("/static", "static/"))
})
.bind(args.bind_address)?
.run()
@@ -49,12 +41,49 @@ impl Actor for BotExecutor {
type Context = SyncContext<Self>;
}
-impl Handler<GetNames> for BotExecutor {
- type Result = Result<Vec<String>, ()>;
+impl Handler<PlaylistRequest> for BotExecutor {
+ type Result = Result<Vec<BotData>, ()>;
- fn handle(&mut self, _: GetNames, _: &mut Self::Context) -> Self::Result {
+ fn handle(&mut self, _: PlaylistRequest, _: &mut Self::Context) -> Self::Result {
let bot = &self.0;
- Ok(bot.names())
+ Ok(bot.bot_datas())
+ }
+}
+
+struct PlaylistRequest;
+
+impl Message for PlaylistRequest {
+ type Result = Result<Vec<BotData>, ()>;
+}
+
+#[derive(Template)]
+#[template(path = "index.htm")]
+struct PlaylistTemplate<'a> {
+ bots: &'a [BotData],
+}
+
+#[derive(Debug)]
+pub struct BotData {
+ pub name: String,
+ pub state: crate::bot::State,
+ pub volume: f64,
+ pub currently_playing: Option<AudioMetadata>,
+ pub playlist: Vec<AudioMetadata>,
+}
+
+#[get("/")]
+async fn index(bot: web::Data<Addr<BotExecutor>>) -> impl Responder {
+ let bot_datas = match bot.send(PlaylistRequest).await.unwrap() {
+ Ok(data) => data,
+ Err(_) => {
+ //error!("Playlist error: {}", e);
+ Vec::with_capacity(0)
+ }
+ };
+
+ PlaylistTemplate {
+ bots: &bot_datas[..],
}
+ .into_response()
}
diff --git a/src/youtube_dl.rs b/src/youtube_dl.rs
index b62d4b3..99e50e7 100644
--- a/src/youtube_dl.rs
+++ b/src/youtube_dl.rs
@@ -1,3 +1,5 @@
+use std::time::Duration;
+
use futures::compat::Future01CompatExt;
use std::process::{Command, Stdio};
use tokio_process::CommandExt;
@@ -9,7 +11,33 @@ use log::debug;
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct AudioMetadata {
pub url: String,
+ pub webpage_url: String,
pub title: String,
+ pub thumbnail: Option<String>,
+ #[serde(default, deserialize_with = "duration_deserialize")]
+ #[serde(serialize_with = "duration_serialize")]
+ pub duration: Option<Duration>,
+ #[serde(skip)]
+ pub added_by: String,
+}
+
+fn duration_serialize<S>(d: &Option<Duration>, s: S) -> Result<S::Ok, S::Error>
+where
+ S: serde::Serializer,
+{
+ match d {
+ Some(d) => s.serialize_some(&d.as_secs_f64()),
+ None => s.serialize_none(),
+ }
+}
+
+fn duration_deserialize<'de, D>(deserializer: D) -> Result<Option<Duration>, D::Error>
+where
+ D: serde::Deserializer<'de>,
+{
+ let dur: Option<f64> = Deserialize::deserialize(deserializer)?;
+
+ Ok(dur.map(|v| Duration::from_secs_f64(v)))
}
pub async fn get_audio_download_url(uri: String) -> Result<AudioMetadata, String> {
diff --git a/static/.gitkeep b/static/.gitkeep
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/static/.gitkeep
diff --git a/templates/base.htm b/templates/base.htm
new file mode 100644
index 0000000..7810f21
--- /dev/null
+++ b/templates/base.htm
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="UTF-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <meta http-equiv="X-UA-Compatible" content="ie=edge">
+ <title>{% block title %}{{ title }} - PokeBot{% endblock %}</title>
+ </head>
+ <body>
+ <main>
+ {% block content %}{% endblock %}
+ </main>
+ </body>
+</html>
diff --git a/templates/index.htm b/templates/index.htm
new file mode 100644
index 0000000..2584603
--- /dev/null
+++ b/templates/index.htm
@@ -0,0 +1,27 @@
+{% extends "base.htm" %}
+
+{% block title %}Overview{% endblock %}
+
+{% block content %}
+<h1>Bots</h1>
+<ul>
+ {% for bot in bots %}
+ <h2>{{ bot.name }}</h1>
+ <div>State: {{ bot.state }}</div>
+ <div>Volume: {{ bot.volume * 100.0 }}%</div>
+ {% match bot.currently_playing %}
+ {% when Some with (current) %}
+ <span>Currently playing:</span>
+ {% let item = current %}
+ {% include "song.htm" %}
+ {% when None %}
+ {% endmatch %}
+
+ {% for item in bot.playlist %}
+ <li>
+ {% include "song.htm" %}
+ </li>
+ {% endfor %}
+ {% endfor %}
+</ul>
+{% endblock %}
diff --git a/templates/song.htm b/templates/song.htm
new file mode 100644
index 0000000..93f4fec
--- /dev/null
+++ b/templates/song.htm
@@ -0,0 +1,10 @@
+<a href="{{ item.webpage_url }}">{{ item.title }}</a>
+{% match item.duration %}
+ {% when Some with (duration) %}
+ {% let secs = duration.as_secs() %}
+ {% let mins = secs / 60 %}
+ {% let submin_secs = secs % 60 %}
+ <span>({{ "{:02}"|format(mins) }}:{{ "{:02}"|format(submin_secs) }})</span>
+ {% when None %}
+ <span>(--:--)</span>
+{% endmatch %}