From 757edd214f841e8d95e4c5430d7ead7a0e8fecbb Mon Sep 17 00:00:00 2001 From: Jokler Date: Thu, 30 Jan 2020 15:55:41 +0100 Subject: Spawn actix-web server with access to the bot Additionally replace all Mutexes with RwLocks. Hopefully this makes it possible for the web server to serve many requests at once since they would just hold read locks. --- Cargo.lock | 622 ++++++++++++++++++++++++++++++++++++++++++++++-- Cargo.toml | 3 + config.toml.example | 4 + log4rs.yml | 4 + src/audio_player.rs | 10 +- src/bot/master.rs | 42 +++- src/bot/music.rs | 38 +-- src/main.rs | 21 +- src/teamspeak/bbcode.rs | 6 +- src/teamspeak/mod.rs | 6 +- src/web_server.rs | 60 +++++ 11 files changed, 757 insertions(+), 59 deletions(-) create mode 100644 src/web_server.rs diff --git a/Cargo.lock b/Cargo.lock index fe0ea26..e5b821c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,5 +1,298 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +[[package]] +name = "actix" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4af87564ff659dee8f9981540cac9418c45e910c8072fdedd643a262a38fcaf" +dependencies = [ + "actix-http", + "actix-rt", + "actix_derive", + "bitflags", + "bytes 0.5.3", + "crossbeam-channel", + "derive_more 0.99.2", + "futures 0.3.1", + "lazy_static", + "log", + "parking_lot 0.10.0", + "pin-project", + "smallvec 1.1.0", + "tokio 0.2.7", + "tokio-util", + "trust-dns-proto 0.18.0-alpha.2", + "trust-dns-resolver 0.18.0-alpha.2", +] + +[[package]] +name = "actix-codec" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09e55f0a5c2ca15795035d90c46bd0e73a5123b72f68f12596d6ba5282051380" +dependencies = [ + "bitflags", + "bytes 0.5.3", + "futures-core", + "futures-sink", + "log", + "tokio 0.2.7", + "tokio-util", +] + +[[package]] +name = "actix-connect" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c95cc9569221e9802bf4c377f6c18b90ef10227d787611decf79fd47d2a8e76c" +dependencies = [ + "actix-codec", + "actix-rt", + "actix-service", + "actix-utils", + "derive_more 0.99.2", + "either", + "futures 0.3.1", + "http 0.2.0", + "log", + "trust-dns-proto 0.18.0-alpha.2", + "trust-dns-resolver 0.18.0-alpha.2", +] + +[[package]] +name = "actix-http" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c16664cc4fdea8030837ad5a845eb231fb93fc3c5c171edfefb52fad92ce9019" +dependencies = [ + "actix-codec", + "actix-connect", + "actix-rt", + "actix-service", + "actix-threadpool", + "actix-utils", + "base64 0.11.0", + "bitflags", + "brotli2", + "bytes 0.5.3", + "chrono", + "copyless", + "derive_more 0.99.2", + "either", + "encoding_rs", + "failure", + "flate2", + "futures-channel", + "futures-core", + "futures-util", + "fxhash", + "h2 0.2.1", + "http 0.2.0", + "httparse", + "indexmap", + "language-tags", + "lazy_static", + "log", + "mime", + "percent-encoding 2.1.0", + "pin-project", + "rand 0.7.3", + "regex", + "serde", + "serde_json", + "serde_urlencoded 0.6.1", + "sha1", + "slab", + "time", +] + +[[package]] +name = "actix-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21705adc76bbe4bc98434890e73a89cd00c6015e5704a60bb6eea6c3b72316b6" +dependencies = [ + "quote 1.0.2", + "syn 1.0.13", +] + +[[package]] +name = "actix-router" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d7a10ca4d94e8c8e7a87c5173aba1b97ba9a6563ca02b0e1cd23531093d3ec8" +dependencies = [ + "bytestring", + "http 0.2.0", + "log", + "regex", + "serde", +] + +[[package]] +name = "actix-rt" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f6a0a55507046441a496b2f0d26a84a65e67c8cafffe279072412f624b5fb6d" +dependencies = [ + "actix-macros", + "actix-threadpool", + "copyless", + "futures 0.3.1", + "tokio 0.2.7", +] + +[[package]] +name = "actix-server" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d3455eaac03ca3e49d7b822eb35c884b861f715627254ccbe4309d08f1841a" +dependencies = [ + "actix-codec", + "actix-rt", + "actix-service", + "actix-utils", + "futures 0.3.1", + "log", + "mio", + "mio-uds", + "net2", + "num_cpus", + "slab", +] + +[[package]] +name = "actix-service" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e4fc95dfa7e24171b2d0bb46b85f8ab0e8499e4e3caec691fc4ea65c287564" +dependencies = [ + "futures-util", + "pin-project", +] + +[[package]] +name = "actix-testing" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48494745b72d0ea8ff0cf874aaf9b622a3ee03d7081ee0c04edea4f26d32c911" +dependencies = [ + "actix-macros", + "actix-rt", + "actix-server", + "actix-service", + "futures 0.3.1", + "log", + "net2", +] + +[[package]] +name = "actix-threadpool" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf4082192601de5f303013709ff84d81ca6a1bc4af7fb24f367a500a23c6e84e" +dependencies = [ + "derive_more 0.99.2", + "futures-channel", + "lazy_static", + "log", + "num_cpus", + "parking_lot 0.10.0", + "threadpool", +] + +[[package]] +name = "actix-tls" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4e5b4faaf105e9a6d389c606c298dcdb033061b00d532af9df56ff3a54995a8" +dependencies = [ + "actix-codec", + "actix-rt", + "actix-service", + "actix-utils", + "derive_more 0.99.2", + "either", + "futures 0.3.1", + "log", +] + +[[package]] +name = "actix-utils" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcf8f5631bf01adec2267808f00e228b761c60c0584cc9fa0b5364f41d147f4e" +dependencies = [ + "actix-codec", + "actix-rt", + "actix-service", + "bitflags", + "bytes 0.5.3", + "either", + "futures 0.3.1", + "log", + "pin-project", + "slab", +] + +[[package]] +name = "actix-web" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3158e822461040822f0dbf1735b9c2ce1f95f93b651d7a7aded00b1efbb1f635" +dependencies = [ + "actix-codec", + "actix-http", + "actix-macros", + "actix-router", + "actix-rt", + "actix-server", + "actix-service", + "actix-testing", + "actix-threadpool", + "actix-tls", + "actix-utils", + "actix-web-codegen", + "awc", + "bytes 0.5.3", + "derive_more 0.99.2", + "encoding_rs", + "futures 0.3.1", + "fxhash", + "log", + "mime", + "net2", + "pin-project", + "regex", + "serde", + "serde_json", + "serde_urlencoded 0.6.1", + "time", + "url 2.1.0", +] + +[[package]] +name = "actix-web-codegen" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de0878b30e62623770a4713a6338329fd0119703bafc211d3e4144f4d4a7bdd5" +dependencies = [ + "proc-macro2 1.0.7", + "quote 1.0.2", + "syn 1.0.13", +] + +[[package]] +name = "actix_derive" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b95aceadaf327f18f0df5962fedc1bde2f870566a0b9f65c89508a3b1f79334c" +dependencies = [ + "proc-macro2 1.0.7", + "quote 1.0.2", + "syn 1.0.13", +] + [[package]] name = "adler32" version = "1.0.4" @@ -98,6 +391,17 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cff77d8686867eceff3105329d4698d96c2391c176d5d03adc90c7389162b5b8" +[[package]] +name = "async-trait" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8df72488e87761e772f14ae0c2480396810e51b2c2ade912f97f0f7e5b95e3c" +dependencies = [ + "proc-macro2 1.0.7", + "quote 1.0.2", + "syn 1.0.13", +] + [[package]] name = "atty" version = "0.2.14" @@ -115,6 +419,29 @@ version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2" +[[package]] +name = "awc" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7601d4d1d7ef2335d6597a41b5fe069f6ab799b85f53565ab390e7b7065aac5" +dependencies = [ + "actix-codec", + "actix-http", + "actix-rt", + "actix-service", + "base64 0.11.0", + "bytes 0.5.3", + "derive_more 0.99.2", + "futures-core", + "log", + "mime", + "percent-encoding 2.1.0", + "rand 0.7.3", + "serde", + "serde_json", + "serde_urlencoded 0.6.1", +] + [[package]] name = "backtrace" version = "0.3.40" @@ -146,6 +473,12 @@ dependencies = [ "byteorder", ] +[[package]] +name = "base64" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7" + [[package]] name = "bit-vec" version = "0.5.1" @@ -178,6 +511,26 @@ dependencies = [ "generic-array", ] +[[package]] +name = "brotli-sys" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4445dea95f4c2b41cde57cc9fee236ae4dbae88d8fcbdb4750fc1bb5d86aaecd" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "brotli2" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cb036c3eade309815c15ddbacec5b22c4d1f3983a774ab2eac2e3e9ea85568e" +dependencies = [ + "brotli-sys", + "libc", +] + [[package]] name = "bstr" version = "0.2.8" @@ -225,6 +578,15 @@ version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10004c15deb332055f7a4a208190aed362cf9a7c2f6ab70a305fba50e1105f38" +[[package]] +name = "bytestring" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc267467f58ef6cc8874064c62a0423eb0d099362c8a23edd1c6d044f46eead4" +dependencies = [ + "bytes 0.5.3", +] + [[package]] name = "c2-chacha" version = "0.2.3" @@ -345,6 +707,12 @@ dependencies = [ "url 1.7.2", ] +[[package]] +name = "copyless" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ff9c56c9fb2a49c05ef0e431485a22400af20d33226dc0764d891d09e724127" + [[package]] name = "core-foundation" version = "0.6.4" @@ -370,6 +738,15 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crossbeam-channel" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acec9a3b0b3559f15aee4f90746c4e5e293b701c0f7d3925d24e01645267b68c" +dependencies = [ + "crossbeam-utils 0.7.0", +] + [[package]] name = "crossbeam-deque" version = "0.7.2" @@ -500,6 +877,17 @@ dependencies = [ "syn 0.15.44", ] +[[package]] +name = "derive_more" +version = "0.99.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2159be042979966de68315bce7034bb000c775f22e3e834e1c52ff78f041cae8" +dependencies = [ + "proc-macro2 1.0.7", + "quote 1.0.2", + "syn 1.0.13", +] + [[package]] name = "digest" version = "0.8.1" @@ -575,6 +963,18 @@ dependencies = [ "syn 0.15.44", ] +[[package]] +name = "enum-as-inner" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "900a6c7fbe523f4c2884eaf26b57b81bb69b6810a01a236390a7ac021d09492e" +dependencies = [ + "heck", + "proc-macro2 1.0.7", + "quote 1.0.2", + "syn 1.0.13", +] + [[package]] name = "error-chain" version = "0.12.1" @@ -853,6 +1253,15 @@ dependencies = [ "slab", ] +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + [[package]] name = "generic-array" version = "0.12.3" @@ -1067,7 +1476,7 @@ dependencies = [ "bytes 0.4.12", "fnv", "futures 0.1.29", - "http", + "http 0.1.21", "indexmap", "log", "slab", @@ -1075,6 +1484,25 @@ dependencies = [ "tokio-io", ] +[[package]] +name = "h2" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9433d71e471c1736fd5a61b671fc0b148d7a2992f666c958d03cd8feb3b88d1" +dependencies = [ + "bytes 0.5.3", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http 0.2.0", + "indexmap", + "log", + "slab", + "tokio 0.2.7", + "tokio-util", +] + [[package]] name = "heck" version = "0.3.1" @@ -1114,6 +1542,17 @@ dependencies = [ "itoa", ] +[[package]] +name = "http" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b708cc7f06493459026f53b9a61a7a121a5d1ec6238dee58ea4941132b30156b" +dependencies = [ + "bytes 0.5.3", + "fnv", + "itoa", +] + [[package]] name = "http-body" version = "0.1.0" @@ -1122,7 +1561,7 @@ checksum = "6741c859c1b2463a423a1dbce98d418e6c3c3fc720fb0d45528657320920292d" dependencies = [ "bytes 0.4.12", "futures 0.1.29", - "http", + "http 0.1.21", "tokio-buf", ] @@ -1156,8 +1595,8 @@ dependencies = [ "bytes 0.4.12", "futures 0.1.29", "futures-cpupool", - "h2", - "http", + "h2 0.1.26", + "http 0.1.21", "http-body", "httparse", "iovec", @@ -1276,6 +1715,12 @@ dependencies = [ "winapi-build", ] +[[package]] +name = "language-tags" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a91d884b6667cd606bb5a69aa0c99ba811a115fc68915e7056ec08a46e93199a" + [[package]] name = "lazy_static" version = "1.4.0" @@ -1686,6 +2131,16 @@ dependencies = [ "rustc_version", ] +[[package]] +name = "parking_lot" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92e98c49ab0b7ce5b222f2cc9193fc4efe11c6d0bd4f648e374684a6857b1cfc" +dependencies = [ + "lock_api 0.3.3", + "parking_lot_core 0.7.0", +] + [[package]] name = "parking_lot_core" version = "0.2.14" @@ -1726,6 +2181,20 @@ dependencies = [ "winapi 0.3.8", ] +[[package]] +name = "parking_lot_core" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7582838484df45743c8434fbff785e8edf260c28748353d44bc0da32e0ceabf1" +dependencies = [ + "cfg-if", + "cloudabi", + "libc", + "redox_syscall", + "smallvec 1.1.0", + "winapi 0.3.8", +] + [[package]] name = "paste" version = "0.1.6" @@ -1760,6 +2229,26 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" +[[package]] +name = "pin-project" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7804a463a8d9572f13453c516a5faea534a2403d7ced2f0c7e100eeff072772c" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "385322a45f2ecf3410c68d2a549a4a2685e8051d0f278e39743ff4e451cb9b3f" +dependencies = [ + "proc-macro2 1.0.7", + "quote 1.0.2", + "syn 1.0.13", +] + [[package]] name = "pin-project-lite" version = "0.1.2" @@ -1782,6 +2271,9 @@ checksum = "05da548ad6865900e60eaba7f589cc0783590a92e940c26953ff81ddbab2d677" name = "pokebot" version = "0.1.1" dependencies = [ + "actix", + "actix-rt", + "actix-web", "byte-slice-cast", "futures 0.1.29", "futures-preview", @@ -2155,14 +2647,14 @@ version = "0.9.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f88643aea3c1343c804950d7bf983bd2067f5ab59db6d613a08e05572f2714ab" dependencies = [ - "base64", + "base64 0.10.1", "bytes 0.4.12", "cookie", "cookie_store", "encoding_rs", "flate2", "futures 0.1.29", - "http", + "http 0.1.21", "hyper", "hyper-tls", "log", @@ -2171,7 +2663,7 @@ dependencies = [ "native-tls", "serde", "serde_json", - "serde_urlencoded", + "serde_urlencoded 0.5.5", "time", "tokio 0.1.22", "tokio-executor", @@ -2199,7 +2691,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ca4eaef519b494d1f2848fc602d18816fed808a981aedf4f1f00ceb7c9d32cf" dependencies = [ - "base64", + "base64 0.10.1", "blake2b_simd", "crossbeam-utils 0.6.6", ] @@ -2336,6 +2828,18 @@ dependencies = [ "url 1.7.2", ] +[[package]] +name = "serde_urlencoded" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ec5d77e2d4c73717816afac02670d5c4f534ea95ed430442cad02e7a6e32c97" +dependencies = [ + "dtoa", + "itoa", + "serde", + "url 2.1.0", +] + [[package]] name = "serde_yaml" version = "0.8.11" @@ -2348,6 +2852,12 @@ dependencies = [ "yaml-rust", ] +[[package]] +name = "sha1" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2579985fda508104f7587689507983eadd6a6e84dd35d6d115361f530916fa0d" + [[package]] name = "signal-hook" version = "0.1.12" @@ -2632,6 +3142,15 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "threadpool" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2f0c90a5f3459330ac8bc0d2f879c693bb7a2f59689c1083fc4ef83834da865" +dependencies = [ + "num_cpus", +] + [[package]] name = "time" version = "0.1.42" @@ -2675,11 +3194,17 @@ checksum = "e61fe9bd8b36108a3c679bb6de916e39595a7a7f18c5e314c0014bb1b6ba13f7" dependencies = [ "bytes 0.5.3", "fnv", + "futures-core", "iovec", "lazy_static", + "libc", "memchr", "mio", + "mio-uds", "pin-project-lite", + "signal-hook-registry", + "slab", + "winapi 0.3.8", ] [[package]] @@ -2887,6 +3412,20 @@ dependencies = [ "tokio-reactor", ] +[[package]] +name = "tokio-util" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "571da51182ec208780505a32528fc5512a8fe1443ab960b3f2f3ef093cd16930" +dependencies = [ + "bytes 0.5.3", + "futures-core", + "futures-sink", + "log", + "pin-project-lite", + "tokio 0.2.7", +] + [[package]] name = "toml" version = "0.5.5" @@ -2909,7 +3448,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5559ebdf6c2368ddd11e20b11d6bbaf9e46deb803acd7815e93f5a7b4a6d2901" dependencies = [ "byteorder", - "enum-as-inner", + "enum-as-inner 0.2.1", "failure", "futures 0.1.29", "idna 0.1.5", @@ -2927,6 +3466,26 @@ dependencies = [ "url 1.7.2", ] +[[package]] +name = "trust-dns-proto" +version = "0.18.0-alpha.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a7f3a2ab8a919f5eca52a468866a67ed7d3efa265d48a652a9a3452272b413f" +dependencies = [ + "async-trait", + "enum-as-inner 0.3.0", + "failure", + "futures 0.3.1", + "idna 0.2.0", + "lazy_static", + "log", + "rand 0.7.3", + "smallvec 1.1.0", + "socket2", + "tokio 0.2.7", + "url 2.1.0", +] + [[package]] name = "trust-dns-resolver" version = "0.11.1" @@ -2944,7 +3503,26 @@ dependencies = [ "smallvec 0.6.13", "tokio 0.1.22", "tokio-executor", - "trust-dns-proto", + "trust-dns-proto 0.7.4", +] + +[[package]] +name = "trust-dns-resolver" +version = "0.18.0-alpha.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f90b1502b226f8b2514c6d5b37bafa8c200d7ca4102d57dc36ee0f3b7a04a2f" +dependencies = [ + "cfg-if", + "failure", + "futures 0.3.1", + "ipconfig", + "lazy_static", + "log", + "lru-cache", + "resolv-conf", + "smallvec 1.1.0", + "tokio 0.2.7", + "trust-dns-proto 0.18.0-alpha.2", ] [[package]] @@ -2967,9 +3545,9 @@ name = "ts-bookkeeping" version = "0.1.0" source = "git+https://github.com/ReSpeak/tsclientlib#7d82374088c11d3d3171ed07bfef16f4d9482b26" dependencies = [ - "base64", + "base64 0.10.1", "chrono", - "derive_more", + "derive_more 0.14.1", "failure", "heck", "itertools", @@ -2986,11 +3564,11 @@ name = "tsclientlib" version = "0.1.0" source = "git+https://github.com/ReSpeak/tsclientlib#7d82374088c11d3d3171ed07bfef16f4d9482b26" dependencies = [ - "base64", + "base64 0.10.1", "bytes 0.4.12", "chashmap", "chrono", - "derive_more", + "derive_more 0.14.1", "failure", "futures 0.1.29", "futures 0.3.1", @@ -3009,8 +3587,8 @@ dependencies = [ "tokio 0.1.22", "tokio 0.2.7", "tokio-threadpool", - "trust-dns-proto", - "trust-dns-resolver", + "trust-dns-proto 0.7.4", + "trust-dns-resolver 0.11.1", "ts-bookkeeping", "tsproto", "tsproto-packets", @@ -3024,13 +3602,13 @@ source = "git+https://github.com/ReSpeak/tsclientlib#7d82374088c11d3d3171ed07bfe dependencies = [ "aes", "arrayref", - "base64", + "base64 0.10.1", "bitflags", "byteorder", "bytes 0.4.12", "chrono", "curve25519-dalek", - "derive_more", + "derive_more 0.14.1", "eax", "failure", "flakebi-ring", @@ -3061,11 +3639,11 @@ version = "0.1.0" source = "git+https://github.com/ReSpeak/tsclientlib#7d82374088c11d3d3171ed07bfef16f4d9482b26" dependencies = [ "arrayref", - "base64", + "base64 0.10.1", "bitflags", "byteorder", "bytes 0.4.12", - "derive_more", + "derive_more 0.14.1", "failure", "nom", "num-derive", @@ -3080,7 +3658,7 @@ name = "tsproto-structs" version = "0.1.0" source = "git+https://github.com/ReSpeak/tsclientlib#7d82374088c11d3d3171ed07bfef16f4d9482b26" dependencies = [ - "base64", + "base64 0.10.1", "csv", "heck", "lazy_static", @@ -3095,7 +3673,7 @@ name = "tsproto-types" version = "0.1.0" source = "git+https://github.com/ReSpeak/tsclientlib#7d82374088c11d3d3171ed07bfef16f4d9482b26" dependencies = [ - "base64", + "base64 0.10.1", "bitflags", "chrono", "heck", diff --git a/Cargo.toml b/Cargo.toml index 7f7aeb5..1fdb258 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,3 +35,6 @@ 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" diff --git a/config.toml.example b/config.toml.example index 8545dfe..50cec56 100644 --- a/config.toml.example +++ b/config.toml.example @@ -5,6 +5,10 @@ address = "localhost" # Channel for the master bot channel = "Lobby" +# Web server settings +domain = "localhost" +bind_address = "127.0.0.1:45538" + # Names for the music bots names = ["MusicBot"] diff --git a/log4rs.yml b/log4rs.yml index 77d8eef..b0d0ab8 100644 --- a/log4rs.yml +++ b/log4rs.yml @@ -23,3 +23,7 @@ root: loggers: tokio_reactor: level: warn + actix_web: + level: trace + actix_server: + level: debug diff --git a/src/audio_player.rs b/src/audio_player.rs index 9ed645d..cdb04d7 100644 --- a/src/audio_player.rs +++ b/src/audio_player.rs @@ -10,7 +10,7 @@ use gstreamer_audio::{StreamVolume, StreamVolumeFormat}; use crate::bot::{MusicBotMessage, State}; use glib::BoolError; use log::{debug, error, info, warn}; -use std::sync::{Arc, Mutex}; +use std::sync::{Arc, RwLock}; use tokio02::sync::mpsc::UnboundedSender; static GST_INIT: Once = Once::new(); @@ -34,7 +34,7 @@ pub struct AudioPlayer { http_src: gst::Element, volume: gst::Element, - sender: Arc>>, + sender: Arc>>, } fn make_element(factoryname: &str, display_name: &str) -> Result { @@ -83,7 +83,7 @@ fn add_decode_bin_new_pad_callback( impl AudioPlayer { pub fn new( - sender: Arc>>, + sender: Arc>>, callback: Option>, ) -> Result { GST_INIT.call_once(|| gst::init().unwrap()); @@ -280,13 +280,13 @@ impl AudioPlayer { warn!("Failed to send \"quit\" app event: {}", e); } - let sender = self.sender.lock().unwrap(); + let sender = self.sender.read().unwrap(); sender.send(MusicBotMessage::Quit(reason)).unwrap(); } fn send_state(&self, state: State) { info!("Sending state {:?} to application", state); - let sender = self.sender.lock().unwrap(); + let sender = self.sender.read().unwrap(); sender.send(MusicBotMessage::StateChange(state)).unwrap(); } diff --git a/src/bot/master.rs b/src/bot/master.rs index 2488064..bc38cca 100644 --- a/src/bot/master.rs +++ b/src/bot/master.rs @@ -1,12 +1,13 @@ use std::collections::HashMap; use std::future::Future; -use std::sync::{Arc, Mutex}; +use std::sync::{Arc, RwLock}; use futures::future::{FutureExt, TryFutureExt}; use futures01::future::Future as Future01; use log::info; use rand::{rngs::SmallRng, seq::SliceRandom, SeedableRng}; use serde::{Deserialize, Serialize}; +use tokio02::sync::mpsc::UnboundedSender; use tsclientlib::{ClientId, ConnectOptions, Identity, MessageTarget}; use crate::audio_player::AudioPlayerError; @@ -18,8 +19,9 @@ use crate::bot::{MusicBot, MusicBotArgs, MusicBotMessage}; pub struct MasterBot { config: Arc, - music_bots: Arc>, + music_bots: Arc>, teamspeak: Arc, + sender: Arc>>, } struct MusicBots { @@ -32,7 +34,7 @@ struct MusicBots { impl MasterBot { pub async fn new(args: MasterArgs) -> (Arc, impl Future) { let (tx, mut rx) = tokio02::sync::mpsc::unbounded_channel(); - let tx = Arc::new(Mutex::new(tx)); + let tx = Arc::new(RwLock::new(tx)); info!("Starting in TeamSpeak mode"); let mut con_config = ConnectOptions::new(args.address.clone()) @@ -65,7 +67,7 @@ impl MasterBot { let name_count = config.names.len(); let id_count = config.ids.len(); - let music_bots = Arc::new(Mutex::new(MusicBots { + let music_bots = Arc::new(RwLock::new(MusicBots { rng: SmallRng::from_entropy(), available_names: (0..name_count).collect(), available_ids: (0..id_count).collect(), @@ -76,6 +78,7 @@ impl MasterBot { config, music_bots, teamspeak: connection, + sender: tx.clone(), }); bot.teamspeak @@ -83,8 +86,12 @@ impl MasterBot { let cbot = bot.clone(); let msg_loop = async move { - loop { + 'outer: loop { while let Some(msg) = rx.recv().await { + if let MusicBotMessage::Quit(reason) = msg { + cbot.teamspeak.disconnect(&reason); + break 'outer; + } cbot.on_message(msg).await.unwrap(); } } @@ -115,7 +122,7 @@ impl MasterBot { ref mut available_names, ref mut available_ids, ref connected_bots, - } = &mut *self.music_bots.lock().expect("Mutex was not poisoned"); + } = &mut *self.music_bots.write().expect("RwLock was not poisoned"); for (_, bot) in connected_bots { if bot.my_channel() == channel { @@ -163,7 +170,7 @@ impl MasterBot { let cmusic_bots = self.music_bots.clone(); let disconnect_cb = Box::new(move |n, name_index, id_index| { - let mut music_bots = cmusic_bots.lock().expect("Mutex was not poisoned"); + let mut music_bots = cmusic_bots.write().expect("RwLock was not poisoned"); music_bots.connected_bots.remove(&n); music_bots.available_names.push(name_index); music_bots.available_ids.push(id_index); @@ -188,7 +195,7 @@ impl MasterBot { if let Some(bot_args) = self.build_bot_args_for(id) { let (bot, fut) = MusicBot::new(bot_args).await; tokio::spawn(fut.unit_error().boxed().compat().map(|_| ())); - let mut music_bots = self.music_bots.lock().expect("Mutex was not poisoned"); + let mut music_bots = self.music_bots.write().expect("RwLock was not poisoned"); music_bots .connected_bots .insert(bot.name().to_string(), bot); @@ -205,6 +212,25 @@ impl MasterBot { Ok(()) } + + pub fn names(&self) -> Vec { + let music_bots = self.music_bots.read().unwrap(); + + music_bots + .connected_bots + .iter() + .map(|(_, b)| b.name().to_owned()) + .collect() + } + + pub fn quit(&self, reason: String) { + let music_bots = self.music_bots.read().unwrap(); + for (_, bot) in &music_bots.connected_bots { + bot.quit(reason.clone()) + } + let sender = self.sender.read().unwrap(); + sender.send(MusicBotMessage::Quit(reason)).unwrap(); + } } #[derive(Debug, Serialize, Deserialize)] diff --git a/src/bot/music.rs b/src/bot/music.rs index 2539695..a23ed3b 100644 --- a/src/bot/music.rs +++ b/src/bot/music.rs @@ -1,6 +1,6 @@ use std::future::Future; use std::io::BufRead; -use std::sync::{Arc, Mutex}; +use std::sync::{Arc, RwLock}; use std::thread; use humantime; @@ -71,8 +71,8 @@ pub struct MusicBot { name: String, player: Arc, teamspeak: Option>, - playlist: Arc>, - state: Arc>, + playlist: Arc>, + state: Arc>, } pub struct MusicBotArgs { @@ -90,7 +90,7 @@ pub struct MusicBotArgs { impl MusicBot { pub async fn new(args: MusicBotArgs) -> (Arc, impl Future) { let (tx, mut rx) = tokio02::sync::mpsc::unbounded_channel(); - let tx = Arc::new(Mutex::new(tx)); + let tx = Arc::new(RwLock::new(tx)); let (player, connection) = if args.local { info!("Starting in CLI mode"); let audio_player = AudioPlayer::new(tx.clone(), None).unwrap(); @@ -127,7 +127,7 @@ impl MusicBot { player.set_volume(0.5).unwrap(); let player = Arc::new(player); - let playlist = Arc::new(Mutex::new(Playlist::new())); + let playlist = Arc::new(RwLock::new(Playlist::new())); spawn_gstreamer_thread(player.clone(), tx.clone()); @@ -140,7 +140,7 @@ impl MusicBot { player, teamspeak: connection, playlist, - state: Arc::new(Mutex::new(State::Stopped)), + state: Arc::new(RwLock::new(State::Stopped)), }); let cbot = bot.clone(); @@ -190,7 +190,7 @@ impl MusicBot { Ok(metadata) => { info!("Found audio url: {}", metadata.url); - let mut playlist = self.playlist.lock().expect("Mutex was not poisoned"); + let mut playlist = self.playlist.write().expect("RwLock was not poisoned"); playlist.push(metadata.clone()); if !self.player.is_started() { @@ -269,7 +269,7 @@ impl MusicBot { async fn on_command(&self, command: Command) -> Result<(), AudioPlayerError> { match command { Command::Play => { - let playlist = self.playlist.lock().expect("Mutex was not poisoned"); + let playlist = self.playlist.read().expect("RwLock was not poisoned"); if !self.player.is_started() { if !playlist.is_empty() { @@ -303,7 +303,7 @@ impl MusicBot { } } Command::Next => { - let playlist = self.playlist.lock().expect("Mutex was not poisoned"); + let playlist = self.playlist.read().expect("RwLock was not poisoned"); if !playlist.is_empty() { info!("Skipping to next track"); self.player.stop_current()?; @@ -314,8 +314,8 @@ impl MusicBot { } Command::Clear => { self.playlist - .lock() - .expect("Mutex was not poisoned") + .write() + .expect("RwLock was not poisoned") .clear(); } Command::Volume { percent: volume } => { @@ -331,7 +331,7 @@ impl MusicBot { } fn on_state(&self, state: State) -> Result<(), AudioPlayerError> { - let mut current_state = self.state.lock().unwrap(); + let mut current_state = self.state.write().unwrap(); if *current_state != state { match state { State::Playing => { @@ -345,7 +345,11 @@ impl MusicBot { self.set_description(""); } State::EndOfStream => { - let next_track = self.playlist.lock().expect("Mutex was not poisoned").pop(); + let next_track = self + .playlist + .write() + .expect("RwLock was not poisoned") + .pop(); if let Some(request) = next_track { info!("Advancing playlist"); @@ -401,7 +405,7 @@ impl MusicBot { } } -fn spawn_stdin_reader(tx: Arc>>) { +fn spawn_stdin_reader(tx: Arc>>) { debug!("Spawning stdin reader thread"); thread::Builder::new() .name(String::from("stdin reader")) @@ -421,7 +425,7 @@ fn spawn_stdin_reader(tx: Arc>>) { text: line, }); - let tx = tx.lock().unwrap(); + let tx = tx.read().unwrap(); tx.send(message).unwrap(); } }) @@ -430,7 +434,7 @@ fn spawn_stdin_reader(tx: Arc>>) { fn spawn_gstreamer_thread( player: Arc, - tx: Arc>>, + tx: Arc>>, ) { thread::Builder::new() .name(String::from("gstreamer polling")) @@ -439,7 +443,7 @@ fn spawn_gstreamer_thread( break; } - tx.lock() + tx.read() .unwrap() .send(MusicBotMessage::StateChange(State::EndOfStream)) .unwrap(); diff --git a/src/main.rs b/src/main.rs index 922162f..2559a2a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,9 +1,10 @@ use std::fs::File; use std::io::{Read, Write}; use std::path::PathBuf; +use std::thread; use futures::future::{FutureExt, TryFutureExt}; -use log::{debug, info}; +use log::{debug, error, info}; use structopt::clap::AppSettings; use structopt::StructOpt; use tsclientlib::Identity; @@ -13,6 +14,7 @@ mod bot; mod command; mod playlist; mod teamspeak; +mod web_server; mod youtube_dl; use bot::{MasterArgs, MasterBot, MusicBot, MusicBotArgs}; @@ -116,7 +118,22 @@ fn run() -> Result<(), Box> { }; MusicBot::new(bot_args).await.1.await; } else { - MasterBot::new(bot_args).await.1.await; + let domain = bot_args.domain.clone(); + let bind_address = bot_args.bind_address.clone(); + let (bot, fut) = MasterBot::new(bot_args).await; + + thread::spawn(|| { + let web_args = web_server::WebServerArgs { + domain, + bind_address, + bot, + }; + if let Err(e) = web_server::start(web_args) { + error!("Error in web server: {}", e); + } + }); + + fut.await; } } .unit_error() diff --git a/src/teamspeak/bbcode.rs b/src/teamspeak/bbcode.rs index 28be08a..91d576a 100644 --- a/src/teamspeak/bbcode.rs +++ b/src/teamspeak/bbcode.rs @@ -1,4 +1,4 @@ -use std::fmt::{Formatter, Display, Error}; +use std::fmt::{Display, Error, Formatter}; #[allow(dead_code)] pub enum BbCode<'a> { @@ -14,7 +14,9 @@ impl<'a> Display for BbCode<'a> { BbCode::Bold(text) => fmt.write_fmt(format_args!("[B]{}[/B]", text))?, BbCode::Italic(text) => fmt.write_fmt(format_args!("[I]{}[/I]", text))?, BbCode::Underline(text) => fmt.write_fmt(format_args!("[U]{}[/U]", text))?, - BbCode::Link(text, url) => fmt.write_fmt(format_args!("[URL={}]{}[/URL]", url, text))?, + BbCode::Link(text, url) => { + fmt.write_fmt(format_args!("[URL={}]{}[/URL]", url, text))? + } }; Ok(()) diff --git a/src/teamspeak/mod.rs b/src/teamspeak/mod.rs index 5ac0d44..7551e77 100644 --- a/src/teamspeak/mod.rs +++ b/src/teamspeak/mod.rs @@ -1,4 +1,4 @@ -use std::sync::{Arc, Mutex}; +use std::sync::{Arc, RwLock}; use std::time::{Duration, Instant}; use futures::compat::Future01CompatExt; @@ -76,7 +76,7 @@ fn get_message<'a>(event: &Event) -> Option { impl TeamSpeakConnection { pub async fn new( - tx: Arc>>, + tx: Arc>>, options: ConnectOptions, ) -> Result { let conn = Connection::new(options).compat().await?; @@ -89,7 +89,7 @@ impl TeamSpeakConnection { if let ConEvents(_conn, events) = e { for event in *events { if let Some(msg) = get_message(event) { - let tx = tx.lock().expect("Mutex was not poisoned"); + let tx = tx.read().expect("RwLock was not poisoned"); // Ignore the result because the receiver might get dropped first. let _ = tx.send(msg); } diff --git a/src/web_server.rs b/src/web_server.rs new file mode 100644 index 0000000..1edbc50 --- /dev/null +++ b/src/web_server.rs @@ -0,0 +1,60 @@ +use std::sync::Arc; + +use actix::{Actor, Addr, Handler, Message, SyncArbiter, SyncContext}; +use actix_web::{get, middleware::Logger, web, App, HttpResponse, HttpServer, Responder}; + +use crate::bot::MasterBot; + +struct GetNames; + +impl Message for GetNames { + type Result = Result, ()>; +} + +#[get("/")] +async fn index(bot: web::Data>) -> impl Responder { + let names = bot.send(GetNames).await.unwrap().unwrap(); + HttpResponse::Ok().body(&format!("Music bots connected: {}", names.join(", "))) +} + +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 = SyncArbiter::start(4, move || BotExecutor(cbot.clone())); + + HttpServer::new(move || { + App::new() + .data(bot_addr.clone()) + .wrap(Logger::default()) + .service(index) + }) + .bind(args.bind_address)? + .run() + .await?; + + args.bot.quit(String::from("Stopping")); + + Ok(()) +} + +pub struct BotExecutor(pub Arc); + +impl Actor for BotExecutor { + type Context = SyncContext; +} + +impl Handler for BotExecutor { + type Result = Result, ()>; + + fn handle(&mut self, _: GetNames, _: &mut Self::Context) -> Self::Result { + let bot = &self.0; + + Ok(bot.names()) + } +} -- cgit v1.2.3-70-g09d2 From ca4c0158f417b87f04313053a3f656f2de4e803b Mon Sep 17 00:00:00 2001 From: Jokler Date: Fri, 31 Jan 2020 22:34:31 +0100 Subject: Make titles of audio metdata non optional --- src/bot/music.rs | 18 ++++++------------ src/youtube_dl.rs | 2 +- 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/src/bot/music.rs b/src/bot/music.rs index a23ed3b..920f1cb 100644 --- a/src/bot/music.rs +++ b/src/bot/music.rs @@ -173,13 +173,8 @@ impl MusicBot { } fn start_playing_audio(&self, metadata: AudioMetadata) { - if let Some(title) = metadata.title { - self.send_message(&format!("Playing {}", ts::underline(&title))); - self.set_description(&format!("Currently playing '{}'", title)); - } else { - self.send_message("Playing unknown title"); - self.set_description("Currently playing"); - } + 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.play().unwrap(); @@ -198,11 +193,10 @@ impl MusicBot { self.start_playing_audio(request); } } else { - if let Some(title) = metadata.title { - self.send_message(&format!("Added {} to playlist", ts::underline(&title))); - } else { - self.send_message("Added to playlist"); - } + self.send_message(&format!( + "Added {} to playlist", + ts::underline(&metadata.title) + )); } } Err(e) => { diff --git a/src/youtube_dl.rs b/src/youtube_dl.rs index c6012f0..b62d4b3 100644 --- a/src/youtube_dl.rs +++ b/src/youtube_dl.rs @@ -9,7 +9,7 @@ use log::debug; #[derive(Serialize, Deserialize, Clone, Debug)] pub struct AudioMetadata { pub url: String, - pub title: Option, + pub title: String, } pub async fn get_audio_download_url(uri: String) -> Result { -- cgit v1.2.3-70-g09d2 From 2831c2b60cb61a14c7efee4ab5c0389eb3ad5469 Mon Sep 17 00:00:00 2001 From: Jokler Date: Sun, 2 Feb 2020 19:50:33 +0100 Subject: Add a very basic template using available info --- Cargo.lock | 123 ++++++++++++++++++++++++++++++++++++++++++++++++++-- Cargo.toml | 8 ++-- src/audio_player.rs | 29 ++++++++++++- src/bot/master.rs | 22 +++++++--- src/bot/music.rs | 32 +++++++++++++- src/playlist.rs | 10 +++++ src/web_server.rs | 63 +++++++++++++++++++-------- src/youtube_dl.rs | 28 ++++++++++++ static/.gitkeep | 0 templates/base.htm | 14 ++++++ templates/index.htm | 27 ++++++++++++ templates/song.htm | 10 +++++ 12 files changed, 334 insertions(+), 32 deletions(-) create mode 100644 static/.gitkeep create mode 100644 templates/base.htm create mode 100644 templates/index.htm create mode 100644 templates/song.htm diff --git a/Cargo.lock b/Cargo.lock index e5b821c..13ef6a1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -59,6 +59,26 @@ dependencies = [ "trust-dns-resolver 0.18.0-alpha.2", ] +[[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" @@ -391,6 +411,54 @@ version = "0.5.1" 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" @@ -1571,6 +1639,12 @@ version = "1.3.4" 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" @@ -1972,6 +2046,16 @@ dependencies = [ "version_check 0.1.5", ] +[[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" @@ -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", @@ -3796,6 +3882,37 @@ dependencies = [ "rand 0.6.5", ] +[[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" @@ -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, volume: gst::Element, sender: Arc>>, + currently_playing: RwLock>, } fn make_element(factoryname: &str, display_name: &str) -> Result { @@ -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 { + 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 { + pub fn bot_datas(&self) -> Vec { 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 { } } -#[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 { + self.player.currently_playing() + } + + pub fn playlist_to_vec(&self) -> Vec { + 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 { + 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, ()>; -} - -#[get("/")] -async fn index(bot: web::Data>) -> 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; } -impl Handler for BotExecutor { - type Result = Result, ()>; +impl Handler for BotExecutor { + type Result = Result, ()>; - 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, ()>; +} + +#[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, + pub playlist: Vec, +} + +#[get("/")] +async fn index(bot: web::Data>) -> 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, + #[serde(default, deserialize_with = "duration_deserialize")] + #[serde(serialize_with = "duration_serialize")] + pub duration: Option, + #[serde(skip)] + pub added_by: String, +} + +fn duration_serialize(d: &Option, s: S) -> Result +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, D::Error> +where + D: serde::Deserializer<'de>, +{ + let dur: Option = Deserialize::deserialize(deserializer)?; + + Ok(dur.map(|v| Duration::from_secs_f64(v))) } pub async fn get_audio_download_url(uri: String) -> Result { diff --git a/static/.gitkeep b/static/.gitkeep new file mode 100644 index 0000000..e69de29 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 @@ + + + + + + + {% block title %}{{ title }} - PokeBot{% endblock %} + + +
+ {% block content %}{% endblock %} +
+ + 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 %} +

Bots

+
    + {% for bot in bots %} +

    {{ bot.name }}

    +
    State: {{ bot.state }}
    +
    Volume: {{ bot.volume * 100.0 }}%
    + {% match bot.currently_playing %} + {% when Some with (current) %} + Currently playing: + {% let item = current %} + {% include "song.htm" %} + {% when None %} + {% endmatch %} + + {% for item in bot.playlist %} +
  • + {% include "song.htm" %} +
  • + {% endfor %} + {% endfor %} +
+{% 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 @@ +{{ item.title }} +{% match item.duration %} + {% when Some with (duration) %} + {% let secs = duration.as_secs() %} + {% let mins = secs / 60 %} + {% let submin_secs = secs % 60 %} + ({{ "{:02}"|format(mins) }}:{{ "{:02}"|format(submin_secs) }}) + {% when None %} + (--:--) +{% endmatch %} -- cgit v1.2.3-70-g09d2 From 5eea11a03c11551091b2c72f48590aec7f5410f0 Mon Sep 17 00:00:00 2001 From: Jokler Date: Mon, 3 Feb 2020 01:14:05 +0100 Subject: Add a json /api/bots endpoint for data retrieval --- Cargo.lock | 1 + Cargo.toml | 1 + src/bot/master.rs | 14 +++++++++ src/bot/music.rs | 3 +- src/web_server.rs | 90 ++++++++++++++++++++++++++++++++++++++++++++++--------- 5 files changed, 94 insertions(+), 15 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 13ef6a1..9117117 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2361,6 +2361,7 @@ dependencies = [ "actix-web", "askama", "byte-slice-cast", + "derive_more 0.99.2", "futures 0.1.29", "futures-preview", "futures-util", diff --git a/Cargo.toml b/Cargo.toml index 15b80f7..d271d7f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,3 +40,4 @@ 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"] } +derive_more = "0.99.2" diff --git a/src/bot/master.rs b/src/bot/master.rs index 10a7572..67867ef 100644 --- a/src/bot/master.rs +++ b/src/bot/master.rs @@ -213,6 +213,20 @@ impl MasterBot { Ok(()) } + pub fn bot_data(&self, name: String) -> Option { + let music_bots = self.music_bots.read().unwrap(); + + let bot = music_bots.connected_bots.get(&name)?; + + Some(crate::web_server::BotData { + name: name, + state: bot.state(), + volume: bot.volume(), + currently_playing: bot.currently_playing(), + playlist: bot.playlist_to_vec(), + }) + } + pub fn bot_datas(&self) -> Vec { let music_bots = self.music_bots.read().unwrap(); diff --git a/src/bot/music.rs b/src/bot/music.rs index d53e4a8..0def280 100644 --- a/src/bot/music.rs +++ b/src/bot/music.rs @@ -5,6 +5,7 @@ use std::thread; use humantime; use log::{debug, info}; +use serde::Serialize; use structopt::StructOpt; use tokio02::sync::mpsc::UnboundedSender; use tsclientlib::{data, ChannelId, ClientId, ConnectOptions, Identity, Invoker, MessageTarget}; @@ -44,7 +45,7 @@ fn parse_seek(mut amount: &str) -> Result { } } -#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize)] pub enum State { Playing, Paused, diff --git a/src/web_server.rs b/src/web_server.rs index 94f043a..02c57e7 100644 --- a/src/web_server.rs +++ b/src/web_server.rs @@ -1,9 +1,13 @@ use std::sync::Arc; use actix::{Actor, Addr, Handler, Message, SyncArbiter, SyncContext}; -use actix_web::{get, middleware::Logger, web, App, HttpServer, Responder}; +use actix_web::{ + get, middleware::Logger, web, App, HttpResponse, HttpServer, Responder, ResponseError, +}; use askama::actix_web::TemplateIntoResponse; use askama::Template; +use derive_more::Display; +use serde::Serialize; use crate::bot::MasterBot; use crate::youtube_dl::AudioMetadata; @@ -24,6 +28,7 @@ pub async fn start(args: WebServerArgs) -> std::io::Result<()> { .data(bot_addr.clone()) .wrap(Logger::default()) .service(index) + .service(web::scope("/api").service(get_bot_list).service(get_bot)) .service(actix_files::Files::new("/static", "static/")) }) .bind(args.bind_address)? @@ -41,29 +46,47 @@ impl Actor for BotExecutor { type Context = SyncContext; } -impl Handler for BotExecutor { +struct BotDataListRequest; + +impl Message for BotDataListRequest { + // A plain Vec does not work for some reason + type Result = Result, ()>; +} + +impl Handler for BotExecutor { type Result = Result, ()>; - fn handle(&mut self, _: PlaylistRequest, _: &mut Self::Context) -> Self::Result { + fn handle(&mut self, _: BotDataListRequest, _: &mut Self::Context) -> Self::Result { let bot = &self.0; Ok(bot.bot_datas()) } } -struct PlaylistRequest; +struct BotDataRequest(String); -impl Message for PlaylistRequest { - type Result = Result, ()>; +impl Message for BotDataRequest { + type Result = Option; +} + +impl Handler for BotExecutor { + type Result = Option; + + fn handle(&mut self, r: BotDataRequest, _: &mut Self::Context) -> Self::Result { + let name = r.0; + let bot = &self.0; + + bot.bot_data(name) + } } #[derive(Template)] #[template(path = "index.htm")] -struct PlaylistTemplate<'a> { +struct OverviewTemplate<'a> { bots: &'a [BotData], } -#[derive(Debug)] +#[derive(Debug, Serialize)] pub struct BotData { pub name: String, pub state: crate::bot::State, @@ -74,16 +97,55 @@ pub struct BotData { #[get("/")] async fn index(bot: web::Data>) -> impl Responder { - let bot_datas = match bot.send(PlaylistRequest).await.unwrap() { + let bot_datas = match bot.send(BotDataListRequest).await.unwrap() { Ok(data) => data, - Err(_) => { - //error!("Playlist error: {}", e); - Vec::with_capacity(0) - } + Err(_) => Vec::with_capacity(0), }; - PlaylistTemplate { + OverviewTemplate { bots: &bot_datas[..], } .into_response() } + +#[get("/bots")] +async fn get_bot_list(bot: web::Data>) -> impl Responder { + let bot_datas = match bot.send(BotDataListRequest).await.unwrap() { + Ok(data) => data, + Err(_) => Vec::with_capacity(0), + }; + + web::Json(bot_datas) +} + +#[derive(Serialize)] +struct ApiError { + error: String, + description: String, +} + +#[derive(Debug, Display)] +enum ApiErrorKind { + #[display(fmt = "Not Found")] + NotFound, +} + +impl ResponseError for ApiErrorKind { + fn error_response(&self) -> HttpResponse { + match *self { + ApiErrorKind::NotFound => HttpResponse::NotFound().json(ApiError { + error: self.to_string(), + description: String::from("The requested resource was not found"), + }), + } + } +} + +#[get("/bots/{name}")] +async fn get_bot(bot: web::Data>, name: web::Path) -> impl Responder { + if let Some(bot_data) = bot.send(BotDataRequest(name.into_inner())).await.unwrap() { + Ok(web::Json(bot_data)) + } else { + Err(ApiErrorKind::NotFound) + } +} -- cgit v1.2.3-70-g09d2 From 84804836f5c1e782c77f1bbf676177151558e008 Mon Sep 17 00:00:00 2001 From: Jokler Date: Sat, 22 Feb 2020 22:46:06 +0100 Subject: Add tmtu mode as a front-end --- src/audio_player.rs | 6 ++ src/bot/master.rs | 18 +++++ src/bot/music.rs | 16 ++-- src/web_server.rs | 104 +++++++++++++++++++++++++- src/web_server/front_end_cookie.rs | 60 +++++++++++++++ templates/index.htm | 5 ++ templates/song.htm | 11 +-- templates/tmtu/index.htm | 145 +++++++++++++++++++++++++++++++++++++ 8 files changed, 350 insertions(+), 15 deletions(-) create mode 100644 src/web_server/front_end_cookie.rs create mode 100644 templates/tmtu/index.htm 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 { + self.pipeline + .query_position::() + .and_then(|t| t.0.map(|v| Duration::from_nanos(v))) + } + pub fn currently_playing(&self) -> Option { 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 { + 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, #[serde(default = "default_verbose")] pub verbose: u8, + pub domain: String, + pub bind_address: String, pub names: Vec, pub id: Identity, pub ids: Vec, @@ -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 { + self.player.position() + } + pub fn currently_playing(&self) -> Option { self.player.currently_playing() } @@ -278,7 +284,7 @@ impl MusicBot { let tokens = msg[1..].split_whitespace().collect::>(); 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; } +#[derive(Deserialize)] +#[serde(rename_all = "kebab-case")] +struct FrontEndForm { + front_end: FrontEnd, +} + +#[post("/front-end")] +async fn post_front_end(form: web::Form) -> Result { + 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, ()>; +} + +impl Handler for BotExecutor { + type Result = Result, ()>; + + 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, + bot: Option, +} + #[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>) -> impl Responder { +async fn index(bot: web::Data>, front: FrontEnd) -> Result { + match front { + FrontEnd::Lazy => lazy_index(bot).await, + FrontEnd::Tmtu => tmtu_index(bot).await, + } +} + +async fn lazy_index(bot: web::Data>) -> Result { 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>) -> impl Responder { .into_response() } +async fn tmtu_index(bot: web::Data>) -> Result { + 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>, + name: web::Path, + front: FrontEnd, +) -> Result { + 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>) -> impl Responder { let bot_datas = match bot.send(BotDataListRequest).await.unwrap() { @@ -149,3 +231,19 @@ async fn get_bot(bot: web::Data>, name: web::Path) -> Err(ApiErrorKind::NotFound) } } + +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("--:--")) + } + } +} 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>; + 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() +} diff --git a/templates/index.htm b/templates/index.htm index 2584603..3183b52 100644 --- a/templates/index.htm +++ b/templates/index.htm @@ -4,6 +4,11 @@ {% block content %}

Bots

+
+ + +
+
    {% for bot in bots %}

    {{ bot.name }}

    diff --git a/templates/song.htm b/templates/song.htm index 93f4fec..072567a 100644 --- a/templates/song.htm +++ b/templates/song.htm @@ -1,10 +1,7 @@ {{ item.title }} -{% match item.duration %} - {% when Some with (duration) %} - {% let secs = duration.as_secs() %} - {% let mins = secs / 60 %} - {% let submin_secs = secs % 60 %} - ({{ "{:02}"|format(mins) }}:{{ "{:02}"|format(submin_secs) }}) +({{ item.duration|fmt_duration }}) +{% match item.thumbnail %} + {% when Some with (thumbnail) %} + {% when None %} - (--:--) {% endmatch %} diff --git a/templates/tmtu/index.htm b/templates/tmtu/index.htm new file mode 100644 index 0000000..e7d1922 --- /dev/null +++ b/templates/tmtu/index.htm @@ -0,0 +1,145 @@ + + + + tmtu mode + + + + + + + + {% match bot %} + {% when Some with (bot) %} + + + + + + + {% when None %} + {% endmatch %} +
    +

    PokeBot

    +

    A web interface for inspecting currently playing audio in PokeBot. Select an instance of the bot to view it's playlist and history.

    + +
    +

    Status

    +
    + {% match bot.currently_playing %} + {% when Some with (current) %} +

    Currently playing: {{ current.title }}

    +

    {{ bot.position|fmt_duration }} / {{ current.duration|fmt_duration }} + {% match current.duration %} + {% when Some with (duration) %} + {% let position %} + {% match bot.position %} + {% when Some with (pos) %} + {% let position = pos.as_secs_f64() %} + {% when None %} + {% let position = 0.0 %} + {% endmatch %} + {% let progress = position / duration.as_secs_f64() %} + {% let percent = progress * 100.0 %} +

    + {% when None %} +

    + {% endmatch %} + {% when None %} + {% endmatch %} +
    +
    +

    Playlist

    + + + + + + + + {% for item in bot.playlist %} + + + + + + + {% endfor %} +
    #tracklengthadded by
    {{ loop.index }}{{ item.title }} + {% let duration = item.duration %} + {{ duration|fmt_duration }} + {{ item.added_by }}
    +
    + + -- cgit v1.2.3-70-g09d2 From 2273403d5f7c0e8994d7cefcfe641475e76f1d87 Mon Sep 17 00:00:00 2001 From: Jokler Date: Sun, 9 Feb 2020 18:52:36 +0100 Subject: Split web_server module into separate components --- src/bot/master.rs | 1 - src/web_server.rs | 185 +++++-------------------------------- src/web_server/api.rs | 48 ++++++++++ src/web_server/bot_executor.rs | 63 +++++++++++++ src/web_server/default.rs | 24 +++++ src/web_server/front_end_cookie.rs | 8 +- src/web_server/tmtu.rs | 41 ++++++++ src/youtube_dl.rs | 11 --- templates/tmtu/index.htm | 4 +- 9 files changed, 205 insertions(+), 180 deletions(-) create mode 100644 src/web_server/api.rs create mode 100644 src/web_server/bot_executor.rs create mode 100644 src/web_server/default.rs create mode 100644 src/web_server/tmtu.rs diff --git a/src/bot/master.rs b/src/bot/master.rs index 9b33744..755aaa1 100644 --- a/src/bot/master.rs +++ b/src/bot/master.rs @@ -215,7 +215,6 @@ impl MasterBot { pub fn bot_data(&self, name: String) -> Option { let music_bots = self.music_bots.read().unwrap(); - let bot = music_bots.connected_bots.get(&name)?; Some(crate::web_server::BotData { diff --git a/src/web_server.rs b/src/web_server.rs index 80c914c..0342a38 100644 --- a/src/web_server.rs +++ b/src/web_server.rs @@ -1,20 +1,21 @@ use std::sync::Arc; use std::time::Duration; -use actix::{Actor, Addr, Handler, Message, SyncArbiter, SyncContext}; +use actix::{Addr, SyncArbiter}; use actix_web::{ - get, http::header, middleware::Logger, post, web, App, Error, HttpResponse, HttpServer, - Responder, ResponseError, + get, http::header, middleware::Logger, post, web, App, HttpResponse, HttpServer, Responder, }; -use askama::actix_web::TemplateIntoResponse; -use askama::Template; -use derive_more::Display; 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 { @@ -33,9 +34,13 @@ pub async fn start(args: WebServerArgs) -> std::io::Result<()> { .data(bot_addr.clone()) .wrap(Logger::default()) .service(index) - .service(tmtu_bot) + .service(get_bot) .service(post_front_end) - .service(web::scope("/api").service(get_bot_list).service(get_bot)) + .service( + web::scope("/api") + .service(api::get_bot_list) + .service(api::get_bot), + ) .service(actix_files::Files::new("/static", "static/")) }) .bind(args.bind_address)? @@ -47,12 +52,6 @@ pub async fn start(args: WebServerArgs) -> std::io::Result<()> { Ok(()) } -pub struct BotExecutor(pub Arc); - -impl Actor for BotExecutor { - type Context = SyncContext; -} - #[derive(Deserialize)] #[serde(rename_all = "kebab-case")] struct FrontEndForm { @@ -60,74 +59,10 @@ struct FrontEndForm { } #[post("/front-end")] -async fn post_front_end(form: web::Form) -> Result { +async fn post_front_end(form: web::Form) -> impl Responder { 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, ()>; -} - -impl Handler for BotExecutor { - type Result = Result, ()>; - - fn handle(&mut self, _: BotNameListRequest, _: &mut Self::Context) -> Self::Result { - let bot = &self.0; - - Ok(bot.bot_names()) - } -} - -struct BotDataListRequest; - -impl Message for BotDataListRequest { - // A plain Vec does not work for some reason - type Result = Result, ()>; -} - -impl Handler for BotExecutor { - type Result = Result, ()>; - - fn handle(&mut self, _: BotDataListRequest, _: &mut Self::Context) -> Self::Result { - let bot = &self.0; - - Ok(bot.bot_datas()) - } -} - -struct BotDataRequest(String); - -impl Message for BotDataRequest { - type Result = Option; -} - -impl Handler for BotExecutor { - type Result = Option; - - fn handle(&mut self, r: BotDataRequest, _: &mut Self::Context) -> Self::Result { - let name = r.0; - let bot = &self.0; - - bot.bot_data(name) - } -} - -#[derive(Template)] -#[template(path = "index.htm")] -struct OverviewTemplate<'a> { - bots: &'a [BotData], -} - -#[derive(Template)] -#[template(path = "tmtu/index.htm")] -struct TmtuTemplate { - bot_names: Vec, - bot: Option, -} - #[derive(Debug, Serialize)] pub struct BotData { pub name: String, @@ -139,96 +74,22 @@ pub struct BotData { } #[get("/")] -async fn index(bot: web::Data>, front: FrontEnd) -> Result { +async fn index(bot: web::Data>, front: FrontEnd) -> impl Responder { match front { - FrontEnd::Lazy => lazy_index(bot).await, - FrontEnd::Tmtu => tmtu_index(bot).await, - } -} - -async fn lazy_index(bot: web::Data>) -> Result { - let bot_datas = match bot.send(BotDataListRequest).await.unwrap() { - Ok(data) => data, - Err(_) => Vec::with_capacity(0), - }; - - OverviewTemplate { - bots: &bot_datas[..], - } - .into_response() -} - -async fn tmtu_index(bot: web::Data>) -> Result { - let bot_names = bot.send(BotNameListRequest).await.unwrap().unwrap(); - - TmtuTemplate { - bot_names, - bot: None, + FrontEnd::Default => default::index(bot).await, + FrontEnd::Tmtu => tmtu::index(bot).await, } - .into_response() } -#[get("/tmtu/{name}")] -async fn tmtu_bot( +#[get("/bot/{name}")] +async fn get_bot( bot: web::Data>, name: web::Path, front: FrontEnd, -) -> Result { - 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>) -> impl Responder { - let bot_datas = match bot.send(BotDataListRequest).await.unwrap() { - Ok(data) => data, - Err(_) => Vec::with_capacity(0), - }; - - web::Json(bot_datas) -} - -#[derive(Serialize)] -struct ApiError { - error: String, - description: String, -} - -#[derive(Debug, Display)] -enum ApiErrorKind { - #[display(fmt = "Not Found")] - NotFound, -} - -impl ResponseError for ApiErrorKind { - fn error_response(&self) -> HttpResponse { - match *self { - ApiErrorKind::NotFound => HttpResponse::NotFound().json(ApiError { - error: self.to_string(), - description: String::from("The requested resource was not found"), - }), - } - } -} - -#[get("/bots/{name}")] -async fn get_bot(bot: web::Data>, name: web::Path) -> impl Responder { - if let Some(bot_data) = bot.send(BotDataRequest(name.into_inner())).await.unwrap() { - Ok(web::Json(bot_data)) - } else { - Err(ApiErrorKind::NotFound) +) -> impl Responder { + match front { + FrontEnd::Tmtu => tmtu::get_bot(bot, name.into_inner()).await, + FrontEnd::Default => Ok(HttpResponse::Found().header(header::LOCATION, "/").finish()), } } diff --git a/src/web_server/api.rs b/src/web_server/api.rs new file mode 100644 index 0000000..4deedad --- /dev/null +++ b/src/web_server/api.rs @@ -0,0 +1,48 @@ +use actix::Addr; +use actix_web::{get, web, HttpResponse, Responder, ResponseError}; +use derive_more::Display; +use serde::Serialize; + +use crate::web_server::{BotDataListRequest, BotDataRequest, BotExecutor}; + +#[get("/bots")] +pub async fn get_bot_list(bot: web::Data>) -> impl Responder { + let bot_datas = match bot.send(BotDataListRequest).await.unwrap() { + Ok(data) => data, + Err(_) => Vec::with_capacity(0), + }; + + web::Json(bot_datas) +} + +#[get("/bots/{name}")] +pub async fn get_bot(bot: web::Data>, name: web::Path) -> impl Responder { + if let Some(bot_data) = bot.send(BotDataRequest(name.into_inner())).await.unwrap() { + Ok(web::Json(bot_data)) + } else { + Err(ApiErrorKind::NotFound) + } +} + +#[derive(Serialize)] +struct ApiError { + error: String, + description: String, +} + +#[derive(Debug, Display)] +enum ApiErrorKind { + #[display(fmt = "Not Found")] + NotFound, +} + +impl ResponseError for ApiErrorKind { + fn error_response(&self) -> HttpResponse { + match *self { + ApiErrorKind::NotFound => HttpResponse::NotFound().json(ApiError { + error: self.to_string(), + description: String::from("The requested resource was not found"), + }), + } + } +} diff --git a/src/web_server/bot_executor.rs b/src/web_server/bot_executor.rs new file mode 100644 index 0000000..fde3c08 --- /dev/null +++ b/src/web_server/bot_executor.rs @@ -0,0 +1,63 @@ +use std::sync::Arc; + +use actix::{Actor, Handler, Message, SyncContext}; + +use crate::bot::MasterBot; +use crate::web_server::BotData; + +pub struct BotExecutor(pub Arc); + +impl Actor for BotExecutor { + type Context = SyncContext; +} + +pub struct BotNameListRequest; + +impl Message for BotNameListRequest { + // A plain Vec does not work for some reason + type Result = Result, ()>; +} + +impl Handler for BotExecutor { + type Result = Result, ()>; + + fn handle(&mut self, _: BotNameListRequest, _: &mut Self::Context) -> Self::Result { + let bot = &self.0; + + Ok(bot.bot_names()) + } +} + +pub struct BotDataListRequest; + +impl Message for BotDataListRequest { + // A plain Vec does not work for some reason + type Result = Result, ()>; +} + +impl Handler for BotExecutor { + type Result = Result, ()>; + + fn handle(&mut self, _: BotDataListRequest, _: &mut Self::Context) -> Self::Result { + let bot = &self.0; + + Ok(bot.bot_datas()) + } +} + +pub struct BotDataRequest(pub String); + +impl Message for BotDataRequest { + type Result = Option; +} + +impl Handler for BotExecutor { + type Result = Option; + + fn handle(&mut self, r: BotDataRequest, _: &mut Self::Context) -> Self::Result { + let name = r.0; + let bot = &self.0; + + bot.bot_data(name) + } +} diff --git a/src/web_server/default.rs b/src/web_server/default.rs new file mode 100644 index 0000000..b3c8291 --- /dev/null +++ b/src/web_server/default.rs @@ -0,0 +1,24 @@ +use actix::Addr; +use actix_web::{web, Error, HttpResponse}; +use askama::actix_web::TemplateIntoResponse; +use askama::Template; + +use crate::web_server::{filters, BotData, BotDataListRequest, BotExecutor}; + +#[derive(Template)] +#[template(path = "index.htm")] +struct OverviewTemplate<'a> { + bots: &'a [BotData], +} + +pub async fn index(bot: web::Data>) -> Result { + let bot_datas = match bot.send(BotDataListRequest).await.unwrap() { + Ok(data) => data, + Err(_) => Vec::with_capacity(0), + }; + + OverviewTemplate { + bots: &bot_datas[..], + } + .into_response() +} diff --git a/src/web_server/front_end_cookie.rs b/src/web_server/front_end_cookie.rs index 0207933..4812d0d 100644 --- a/src/web_server/front_end_cookie.rs +++ b/src/web_server/front_end_cookie.rs @@ -10,7 +10,7 @@ use serde::Deserialize; #[derive(PartialEq, Deserialize)] #[serde(rename_all = "lowercase")] pub enum FrontEnd { - Lazy, + Default, Tmtu, } @@ -19,7 +19,7 @@ impl FrontEnd { fn cookie(&self) -> String { let name = match self { - FrontEnd::Lazy => "lazy", + FrontEnd::Default => "default", FrontEnd::Tmtu => "tmtu", }; @@ -39,7 +39,7 @@ impl FromRequest for FrontEnd { let mut split = c.split('='); if Some(Self::COOKIE_NAME) == split.next() { match split.next() { - Some("lazy") => return ok(FrontEnd::Lazy), + Some("default") => return ok(FrontEnd::Default), Some("tmtu") => return ok(FrontEnd::Tmtu), _ => (), } @@ -48,7 +48,7 @@ impl FromRequest for FrontEnd { } } - ok(FrontEnd::Lazy) + ok(FrontEnd::Default) } } diff --git a/src/web_server/tmtu.rs b/src/web_server/tmtu.rs new file mode 100644 index 0000000..0645ee4 --- /dev/null +++ b/src/web_server/tmtu.rs @@ -0,0 +1,41 @@ +use actix::Addr; +use actix_web::{http::header, web, Error, HttpResponse}; +use askama::actix_web::TemplateIntoResponse; +use askama::Template; + +use crate::web_server::{filters, BotData, BotDataRequest, BotExecutor, BotNameListRequest}; + +#[derive(Template)] +#[template(path = "tmtu/index.htm")] +struct TmtuTemplate { + bot_names: Vec, + bot: Option, +} + +pub async fn index(bot: web::Data>) -> Result { + let bot_names = bot.send(BotNameListRequest).await.unwrap().unwrap(); + + TmtuTemplate { + bot_names, + bot: None, + } + .into_response() +} + +pub async fn get_bot( + bot: web::Data>, + name: String, +) -> Result { + let bot_names = bot.send(BotNameListRequest).await.unwrap().unwrap(); + + if let Some(bot) = bot.send(BotDataRequest(name)).await.unwrap() { + TmtuTemplate { + bot_names, + bot: Some(bot), + } + .into_response() + } else { + // TODO to 404 or not to 404 + Ok(HttpResponse::Found().header(header::LOCATION, "/").finish()) + } +} diff --git a/src/youtube_dl.rs b/src/youtube_dl.rs index 99e50e7..89b1477 100644 --- a/src/youtube_dl.rs +++ b/src/youtube_dl.rs @@ -15,22 +15,11 @@ pub struct AudioMetadata { pub title: String, pub thumbnail: Option, #[serde(default, deserialize_with = "duration_deserialize")] - #[serde(serialize_with = "duration_serialize")] pub duration: Option, #[serde(skip)] pub added_by: String, } -fn duration_serialize(d: &Option, s: S) -> Result -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, D::Error> where D: serde::Deserializer<'de>, diff --git a/templates/tmtu/index.htm b/templates/tmtu/index.htm index e7d1922..785e653 100644 --- a/templates/tmtu/index.htm +++ b/templates/tmtu/index.htm @@ -75,9 +75,9 @@ {% endmatch %} {% for name in bot_names %} {% if name.clone() == bot_name %} -
  • {{ name }}
  • +
  • {{ name }}
  • {% else %} -
  • {{ name }}
  • +
  • {{ name }}
  • {% endif %} {% endfor %} -- cgit v1.2.3-70-g09d2 From 1e0fb0a8fab5c9f2f8a58c2637830522863ef04f Mon Sep 17 00:00:00 2001 From: Jokler Date: Sun, 9 Feb 2020 22:19:38 +0100 Subject: Webserver: Add API documentation and basic css --- src/web_server.rs | 12 +++++ static/fonts/.gitkeep | 0 static/style.css | 63 +++++++++++++++++++++++++ templates/base.htm | 1 + templates/docs/api.htm | 126 +++++++++++++++++++++++++++++++++++++++++++++++++ templates/index.htm | 4 ++ 6 files changed, 206 insertions(+) create mode 100644 static/fonts/.gitkeep create mode 100644 static/style.css create mode 100644 templates/docs/api.htm diff --git a/src/web_server.rs b/src/web_server.rs index 0342a38..0097fb1 100644 --- a/src/web_server.rs +++ b/src/web_server.rs @@ -5,6 +5,8 @@ use actix::{Addr, SyncArbiter}; use actix_web::{ get, http::header, middleware::Logger, post, web, App, HttpResponse, HttpServer, Responder, }; +use askama::actix_web::TemplateIntoResponse; +use askama::Template; use serde::{Deserialize, Serialize}; use crate::bot::MasterBot; @@ -41,6 +43,7 @@ pub async fn start(args: WebServerArgs) -> std::io::Result<()> { .service(api::get_bot_list) .service(api::get_bot), ) + .service(web::scope("/docs").service(get_api_docs)) .service(actix_files::Files::new("/static", "static/")) }) .bind(args.bind_address)? @@ -93,6 +96,15 @@ async fn get_bot( } } +#[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; diff --git a/static/fonts/.gitkeep b/static/fonts/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/static/style.css b/static/style.css new file mode 100644 index 0000000..09a985c --- /dev/null +++ b/static/style.css @@ -0,0 +1,63 @@ +@font-face { + font-family: 'roboto-regular'; + src: url('fonts/Roboto-Regular.ttf') format('truetype'); +} + +@font-face { + font-family: 'roboto-light'; + src: url('fonts/Roboto-Light.ttf') format('truetype'); +} + +@font-face { + font-family: 'roboto-bold'; + src: url('fonts/Roboto-Bold.ttf') format('truetype'); +} + +body { + background-color: #151515; +} + +main { + margin: auto; + max-width: 800px; + padding: 1em; + background-color: #202020; + color: #eee; + font-family: 'roboto-regular', Arial; +} + +nav > a { + font-size: 1.4rem; +} + +a, a:visited { + color: #eee; +} + +a:hover { + color: #ccc; +} + +pre { + font-size: 0.9rem; + font-family: monospace; + background-color: #151515; + overflow: auto; + padding: 1em; +} + +.code-background { + background-color: #151515; +} + +.code-normal { + color: #c0c5ce; +} + +.code-string { + color:#a3be8c; +} + +.code-number { + color: #d08770; +} diff --git a/templates/base.htm b/templates/base.htm index 7810f21..b8b2f49 100644 --- a/templates/base.htm +++ b/templates/base.htm @@ -4,6 +4,7 @@ + {% block title %}{{ title }} - PokeBot{% endblock %} diff --git a/templates/docs/api.htm b/templates/docs/api.htm new file mode 100644 index 0000000..a973272 --- /dev/null +++ b/templates/docs/api.htm @@ -0,0 +1,126 @@ +{% extends "base.htm" %} + +{% block title %}API Documentation{% endblock %} + +{% block content %} +

    API Documentation

    + + +

    Bot list

    +

    Show a list of all bots.

    + +

    URL: /api/bots

    +

    Method: GET

    +

    Auth required: NO

    + +

    Success Response

    + +

    Code: 200 OK

    + +

    Content example

    + + +
    +[
    +  {
    +    "name": "MusicBot",
    +    "state": "Playing",
    +    "volume": 0.5,
    +    "position": {
    +      "secs": 10,
    +      "nanos": 63573687
    +    },
    +    "currently_playing": {
    +      "url": "<temp_url>",
    +      "webpage_url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
    +      "title": "Rick Astley - Never Gonna Give You Up (Video)",
    +      "thumbnail": "https://i.ytimg.com/vi/dQw4w9WgXcQ/maxresdefault.jpg",
    +      "duration": {
    +        "secs": 212,
    +        "nanos": 0
    +      }
    +    },
    +    "playlist": [
    +      {
    +        "url": "<temp_url>",
    +        "webpage_url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
    +        "title": "Rick Astley - Never Gonna Give You Up (Video)",
    +        "thumbnail": "https://i.ytimg.com/vi/dQw4w9WgXcQ/maxresdefault.jpg",
    +        "duration": {
    +          "secs": 212,
    +          "nanos": 0
    +        }
    +      }
    +    ]
    +  }
    +]
    +
    + + +

    Show Bot

    +

    Show a specific bot.

    + +

    URL: /api/bots/:botname

    +

    Method: GET

    +

    Auth required: NO

    + +

    Success Response

    +

    Code: 200 OK

    + +

    Content example

    + + +
    +{
    +  "name": "MusicBot",
    +  "state": "Playing",
    +  "volume": 0.5,
    +  "position": {
    +    "secs": 142,
    +    "nanos": 690911766
    +  },
    +  "currently_playing": {
    +    "url": "<temp_url>",
    +    "webpage_url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
    +    "title": "Rick Astley - Never Gonna Give You Up (Video)",
    +    "thumbnail": "https://i.ytimg.com/vi/dQw4w9WgXcQ/maxresdefault.jpg",
    +    "duration": {
    +      "secs": 212,
    +      "nanos": 0
    +    }
    +  },
    +  "playlist": [
    +    {
    +      "url": "<temp_url>",
    +      "webpage_url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
    +      "title": "Rick Astley - Never Gonna Give You Up (Video)",
    +      "thumbnail": "https://i.ytimg.com/vi/dQw4w9WgXcQ/maxresdefault.jpg",
    +      "duration": {
    +        "secs": 212,
    +        "nanos": 0
    +      }
    +    }
    +  ]
    +}
    +
    + +

    Error Response

    + +

    Condition: If ':botname' is not connected to TeamSpeak.

    + +

    Code: 404 NOT FOUND

    + +Content: + + +
    +{
    +  "error": "Not Found",
    +  "description": "The requested resource was not found"
    +}
    +
    + +{% endblock %} diff --git a/templates/index.htm b/templates/index.htm index 3183b52..eed31f3 100644 --- a/templates/index.htm +++ b/templates/index.htm @@ -8,6 +8,10 @@ +
      {% for bot in bots %} -- cgit v1.2.3-70-g09d2 From b9edd85baae9ee483a2d403c641801adc585d26d Mon Sep 17 00:00:00 2001 From: Jokler Date: Thu, 20 Feb 2020 05:20:24 +0100 Subject: Fix quit message when nothing is playing --- src/audio_player.rs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/audio_player.rs b/src/audio_player.rs index 4bcab56..d231c72 100644 --- a/src/audio_player.rs +++ b/src/audio_player.rs @@ -108,6 +108,12 @@ impl AudioPlayer { pipeline.add(&audio_bin)?; + // The documentation says that we have to make sure to handle + // all messages if auto flushing is deactivated. + // I hope our way of reading messages is good enough. + // + // https://gstreamer.freedesktop.org/documentation/gstreamer/gstpipeline.html#gst_pipeline_set_auto_flush_bus + pipeline.set_auto_flush_bus(false); pipeline.set_state(gst::State::Ready)?; Ok(AudioPlayer { @@ -306,11 +312,11 @@ impl AudioPlayer { pub fn quit(&self, reason: String) { info!("Quitting audio player"); - if let Err(e) = self + if let Err(_) = self .bus .post(&gst::Message::new_application(gst::Structure::new_empty("quit")).build()) { - warn!("Failed to send \"quit\" app event: {}", e); + warn!("Tried to send \"quit\" app event on flushing bus."); } let sender = self.sender.read().unwrap(); @@ -395,7 +401,7 @@ impl AudioPlayer { } } _ => { - //debug!("{:?}", msg) + //debug!("Unhandled message on bus: {:?}", msg) } }; } -- cgit v1.2.3-70-g09d2 From 326cfa543c6263818aad7dec4a869bc8139ec14c Mon Sep 17 00:00:00 2001 From: Jokler Date: Sat, 22 Feb 2020 18:53:13 +0100 Subject: Move web server related files into a folder --- askama.toml | 3 + src/web_server.rs | 2 +- static/.gitkeep | 0 static/fonts/.gitkeep | 0 static/style.css | 63 ---------------- templates/base.htm | 15 ---- templates/docs/api.htm | 126 ------------------------------- templates/index.htm | 36 --------- templates/song.htm | 7 -- templates/tmtu/index.htm | 145 ------------------------------------ web_server/static/fonts/.gitkeep | 0 web_server/static/style.css | 63 ++++++++++++++++ web_server/templates/base.htm | 15 ++++ web_server/templates/docs/api.htm | 126 +++++++++++++++++++++++++++++++ web_server/templates/index.htm | 36 +++++++++ web_server/templates/song.htm | 7 ++ web_server/templates/tmtu/index.htm | 145 ++++++++++++++++++++++++++++++++++++ 17 files changed, 396 insertions(+), 393 deletions(-) create mode 100644 askama.toml delete mode 100644 static/.gitkeep delete mode 100644 static/fonts/.gitkeep delete mode 100644 static/style.css delete mode 100644 templates/base.htm delete mode 100644 templates/docs/api.htm delete mode 100644 templates/index.htm delete mode 100644 templates/song.htm delete mode 100644 templates/tmtu/index.htm create mode 100644 web_server/static/fonts/.gitkeep create mode 100644 web_server/static/style.css create mode 100644 web_server/templates/base.htm create mode 100644 web_server/templates/docs/api.htm create mode 100644 web_server/templates/index.htm create mode 100644 web_server/templates/song.htm create mode 100644 web_server/templates/tmtu/index.htm diff --git a/askama.toml b/askama.toml new file mode 100644 index 0000000..fceb790 --- /dev/null +++ b/askama.toml @@ -0,0 +1,3 @@ +[general] +# Directories to search for templates, relative to the crate root. +dirs = ["web_server/templates"] diff --git a/src/web_server.rs b/src/web_server.rs index 0097fb1..01233f2 100644 --- a/src/web_server.rs +++ b/src/web_server.rs @@ -44,7 +44,7 @@ pub async fn start(args: WebServerArgs) -> std::io::Result<()> { .service(api::get_bot), ) .service(web::scope("/docs").service(get_api_docs)) - .service(actix_files::Files::new("/static", "static/")) + .service(actix_files::Files::new("/static", "web_server/static/")) }) .bind(args.bind_address)? .run() diff --git a/static/.gitkeep b/static/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/static/fonts/.gitkeep b/static/fonts/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/static/style.css b/static/style.css deleted file mode 100644 index 09a985c..0000000 --- a/static/style.css +++ /dev/null @@ -1,63 +0,0 @@ -@font-face { - font-family: 'roboto-regular'; - src: url('fonts/Roboto-Regular.ttf') format('truetype'); -} - -@font-face { - font-family: 'roboto-light'; - src: url('fonts/Roboto-Light.ttf') format('truetype'); -} - -@font-face { - font-family: 'roboto-bold'; - src: url('fonts/Roboto-Bold.ttf') format('truetype'); -} - -body { - background-color: #151515; -} - -main { - margin: auto; - max-width: 800px; - padding: 1em; - background-color: #202020; - color: #eee; - font-family: 'roboto-regular', Arial; -} - -nav > a { - font-size: 1.4rem; -} - -a, a:visited { - color: #eee; -} - -a:hover { - color: #ccc; -} - -pre { - font-size: 0.9rem; - font-family: monospace; - background-color: #151515; - overflow: auto; - padding: 1em; -} - -.code-background { - background-color: #151515; -} - -.code-normal { - color: #c0c5ce; -} - -.code-string { - color:#a3be8c; -} - -.code-number { - color: #d08770; -} diff --git a/templates/base.htm b/templates/base.htm deleted file mode 100644 index b8b2f49..0000000 --- a/templates/base.htm +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - {% block title %}{{ title }} - PokeBot{% endblock %} - - -
      - {% block content %}{% endblock %} -
      - - diff --git a/templates/docs/api.htm b/templates/docs/api.htm deleted file mode 100644 index a973272..0000000 --- a/templates/docs/api.htm +++ /dev/null @@ -1,126 +0,0 @@ -{% extends "base.htm" %} - -{% block title %}API Documentation{% endblock %} - -{% block content %} -

      API Documentation

      - - -

      Bot list

      -

      Show a list of all bots.

      - -

      URL: /api/bots

      -

      Method: GET

      -

      Auth required: NO

      - -

      Success Response

      - -

      Code: 200 OK

      - -

      Content example

      - - -
      -[
      -  {
      -    "name": "MusicBot",
      -    "state": "Playing",
      -    "volume": 0.5,
      -    "position": {
      -      "secs": 10,
      -      "nanos": 63573687
      -    },
      -    "currently_playing": {
      -      "url": "<temp_url>",
      -      "webpage_url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
      -      "title": "Rick Astley - Never Gonna Give You Up (Video)",
      -      "thumbnail": "https://i.ytimg.com/vi/dQw4w9WgXcQ/maxresdefault.jpg",
      -      "duration": {
      -        "secs": 212,
      -        "nanos": 0
      -      }
      -    },
      -    "playlist": [
      -      {
      -        "url": "<temp_url>",
      -        "webpage_url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
      -        "title": "Rick Astley - Never Gonna Give You Up (Video)",
      -        "thumbnail": "https://i.ytimg.com/vi/dQw4w9WgXcQ/maxresdefault.jpg",
      -        "duration": {
      -          "secs": 212,
      -          "nanos": 0
      -        }
      -      }
      -    ]
      -  }
      -]
      -
      - - -

      Show Bot

      -

      Show a specific bot.

      - -

      URL: /api/bots/:botname

      -

      Method: GET

      -

      Auth required: NO

      - -

      Success Response

      -

      Code: 200 OK

      - -

      Content example

      - - -
      -{
      -  "name": "MusicBot",
      -  "state": "Playing",
      -  "volume": 0.5,
      -  "position": {
      -    "secs": 142,
      -    "nanos": 690911766
      -  },
      -  "currently_playing": {
      -    "url": "<temp_url>",
      -    "webpage_url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
      -    "title": "Rick Astley - Never Gonna Give You Up (Video)",
      -    "thumbnail": "https://i.ytimg.com/vi/dQw4w9WgXcQ/maxresdefault.jpg",
      -    "duration": {
      -      "secs": 212,
      -      "nanos": 0
      -    }
      -  },
      -  "playlist": [
      -    {
      -      "url": "<temp_url>",
      -      "webpage_url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
      -      "title": "Rick Astley - Never Gonna Give You Up (Video)",
      -      "thumbnail": "https://i.ytimg.com/vi/dQw4w9WgXcQ/maxresdefault.jpg",
      -      "duration": {
      -        "secs": 212,
      -        "nanos": 0
      -      }
      -    }
      -  ]
      -}
      -
      - -

      Error Response

      - -

      Condition: If ':botname' is not connected to TeamSpeak.

      - -

      Code: 404 NOT FOUND

      - -Content: - - -
      -{
      -  "error": "Not Found",
      -  "description": "The requested resource was not found"
      -}
      -
      - -{% endblock %} diff --git a/templates/index.htm b/templates/index.htm deleted file mode 100644 index eed31f3..0000000 --- a/templates/index.htm +++ /dev/null @@ -1,36 +0,0 @@ -{% extends "base.htm" %} - -{% block title %}Overview{% endblock %} - -{% block content %} -

      Bots

      -
      - - -
      - - -
        - {% for bot in bots %} -

        {{ bot.name }}

        -
        State: {{ bot.state }}
        -
        Volume: {{ bot.volume * 100.0 }}%
        - {% match bot.currently_playing %} - {% when Some with (current) %} - Currently playing: - {% let item = current %} - {% include "song.htm" %} - {% when None %} - {% endmatch %} - - {% for item in bot.playlist %} -
      • - {% include "song.htm" %} -
      • - {% endfor %} - {% endfor %} -
      -{% endblock %} diff --git a/templates/song.htm b/templates/song.htm deleted file mode 100644 index 072567a..0000000 --- a/templates/song.htm +++ /dev/null @@ -1,7 +0,0 @@ -{{ item.title }} -({{ item.duration|fmt_duration }}) -{% match item.thumbnail %} - {% when Some with (thumbnail) %} - - {% when None %} -{% endmatch %} diff --git a/templates/tmtu/index.htm b/templates/tmtu/index.htm deleted file mode 100644 index 785e653..0000000 --- a/templates/tmtu/index.htm +++ /dev/null @@ -1,145 +0,0 @@ - - - - tmtu mode - - - - - - - - {% match bot %} - {% when Some with (bot) %} - - - - - - - {% when None %} - {% endmatch %} -
      -

      PokeBot

      -

      A web interface for inspecting currently playing audio in PokeBot. Select an instance of the bot to view it's playlist and history.

      - -
      -

      Status

      -
      - {% match bot.currently_playing %} - {% when Some with (current) %} -

      Currently playing: {{ current.title }}

      -

      {{ bot.position|fmt_duration }} / {{ current.duration|fmt_duration }} - {% match current.duration %} - {% when Some with (duration) %} - {% let position %} - {% match bot.position %} - {% when Some with (pos) %} - {% let position = pos.as_secs_f64() %} - {% when None %} - {% let position = 0.0 %} - {% endmatch %} - {% let progress = position / duration.as_secs_f64() %} - {% let percent = progress * 100.0 %} -

      - {% when None %} -

      - {% endmatch %} - {% when None %} - {% endmatch %} -
      -
      -

      Playlist

      - - - - - - - - {% for item in bot.playlist %} - - - - - - - {% endfor %} -
      #tracklengthadded by
      {{ loop.index }}{{ item.title }} - {% let duration = item.duration %} - {{ duration|fmt_duration }} - {{ item.added_by }}
      -
      - - diff --git a/web_server/static/fonts/.gitkeep b/web_server/static/fonts/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/web_server/static/style.css b/web_server/static/style.css new file mode 100644 index 0000000..09a985c --- /dev/null +++ b/web_server/static/style.css @@ -0,0 +1,63 @@ +@font-face { + font-family: 'roboto-regular'; + src: url('fonts/Roboto-Regular.ttf') format('truetype'); +} + +@font-face { + font-family: 'roboto-light'; + src: url('fonts/Roboto-Light.ttf') format('truetype'); +} + +@font-face { + font-family: 'roboto-bold'; + src: url('fonts/Roboto-Bold.ttf') format('truetype'); +} + +body { + background-color: #151515; +} + +main { + margin: auto; + max-width: 800px; + padding: 1em; + background-color: #202020; + color: #eee; + font-family: 'roboto-regular', Arial; +} + +nav > a { + font-size: 1.4rem; +} + +a, a:visited { + color: #eee; +} + +a:hover { + color: #ccc; +} + +pre { + font-size: 0.9rem; + font-family: monospace; + background-color: #151515; + overflow: auto; + padding: 1em; +} + +.code-background { + background-color: #151515; +} + +.code-normal { + color: #c0c5ce; +} + +.code-string { + color:#a3be8c; +} + +.code-number { + color: #d08770; +} diff --git a/web_server/templates/base.htm b/web_server/templates/base.htm new file mode 100644 index 0000000..b8b2f49 --- /dev/null +++ b/web_server/templates/base.htm @@ -0,0 +1,15 @@ + + + + + + + + {% block title %}{{ title }} - PokeBot{% endblock %} + + +
      + {% block content %}{% endblock %} +
      + + diff --git a/web_server/templates/docs/api.htm b/web_server/templates/docs/api.htm new file mode 100644 index 0000000..a973272 --- /dev/null +++ b/web_server/templates/docs/api.htm @@ -0,0 +1,126 @@ +{% extends "base.htm" %} + +{% block title %}API Documentation{% endblock %} + +{% block content %} +

      API Documentation

      + + +

      Bot list

      +

      Show a list of all bots.

      + +

      URL: /api/bots

      +

      Method: GET

      +

      Auth required: NO

      + +

      Success Response

      + +

      Code: 200 OK

      + +

      Content example

      + + +
      +[
      +  {
      +    "name": "MusicBot",
      +    "state": "Playing",
      +    "volume": 0.5,
      +    "position": {
      +      "secs": 10,
      +      "nanos": 63573687
      +    },
      +    "currently_playing": {
      +      "url": "<temp_url>",
      +      "webpage_url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
      +      "title": "Rick Astley - Never Gonna Give You Up (Video)",
      +      "thumbnail": "https://i.ytimg.com/vi/dQw4w9WgXcQ/maxresdefault.jpg",
      +      "duration": {
      +        "secs": 212,
      +        "nanos": 0
      +      }
      +    },
      +    "playlist": [
      +      {
      +        "url": "<temp_url>",
      +        "webpage_url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
      +        "title": "Rick Astley - Never Gonna Give You Up (Video)",
      +        "thumbnail": "https://i.ytimg.com/vi/dQw4w9WgXcQ/maxresdefault.jpg",
      +        "duration": {
      +          "secs": 212,
      +          "nanos": 0
      +        }
      +      }
      +    ]
      +  }
      +]
      +
      + + +

      Show Bot

      +

      Show a specific bot.

      + +

      URL: /api/bots/:botname

      +

      Method: GET

      +

      Auth required: NO

      + +

      Success Response

      +

      Code: 200 OK

      + +

      Content example

      + + +
      +{
      +  "name": "MusicBot",
      +  "state": "Playing",
      +  "volume": 0.5,
      +  "position": {
      +    "secs": 142,
      +    "nanos": 690911766
      +  },
      +  "currently_playing": {
      +    "url": "<temp_url>",
      +    "webpage_url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
      +    "title": "Rick Astley - Never Gonna Give You Up (Video)",
      +    "thumbnail": "https://i.ytimg.com/vi/dQw4w9WgXcQ/maxresdefault.jpg",
      +    "duration": {
      +      "secs": 212,
      +      "nanos": 0
      +    }
      +  },
      +  "playlist": [
      +    {
      +      "url": "<temp_url>",
      +      "webpage_url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
      +      "title": "Rick Astley - Never Gonna Give You Up (Video)",
      +      "thumbnail": "https://i.ytimg.com/vi/dQw4w9WgXcQ/maxresdefault.jpg",
      +      "duration": {
      +        "secs": 212,
      +        "nanos": 0
      +      }
      +    }
      +  ]
      +}
      +
      + +

      Error Response

      + +

      Condition: If ':botname' is not connected to TeamSpeak.

      + +

      Code: 404 NOT FOUND

      + +Content: + + +
      +{
      +  "error": "Not Found",
      +  "description": "The requested resource was not found"
      +}
      +
      + +{% endblock %} diff --git a/web_server/templates/index.htm b/web_server/templates/index.htm new file mode 100644 index 0000000..eed31f3 --- /dev/null +++ b/web_server/templates/index.htm @@ -0,0 +1,36 @@ +{% extends "base.htm" %} + +{% block title %}Overview{% endblock %} + +{% block content %} +

      Bots

      +
      + + +
      + + +
        + {% for bot in bots %} +

        {{ bot.name }}

        +
        State: {{ bot.state }}
        +
        Volume: {{ bot.volume * 100.0 }}%
        + {% match bot.currently_playing %} + {% when Some with (current) %} + Currently playing: + {% let item = current %} + {% include "song.htm" %} + {% when None %} + {% endmatch %} + + {% for item in bot.playlist %} +
      • + {% include "song.htm" %} +
      • + {% endfor %} + {% endfor %} +
      +{% endblock %} diff --git a/web_server/templates/song.htm b/web_server/templates/song.htm new file mode 100644 index 0000000..072567a --- /dev/null +++ b/web_server/templates/song.htm @@ -0,0 +1,7 @@ +{{ item.title }} +({{ item.duration|fmt_duration }}) +{% match item.thumbnail %} + {% when Some with (thumbnail) %} + + {% when None %} +{% endmatch %} diff --git a/web_server/templates/tmtu/index.htm b/web_server/templates/tmtu/index.htm new file mode 100644 index 0000000..785e653 --- /dev/null +++ b/web_server/templates/tmtu/index.htm @@ -0,0 +1,145 @@ + + + + tmtu mode + + + + + + + + {% match bot %} + {% when Some with (bot) %} + + + + + + + {% when None %} + {% endmatch %} +
      +

      PokeBot

      +

      A web interface for inspecting currently playing audio in PokeBot. Select an instance of the bot to view it's playlist and history.

      + +
      +

      Status

      +
      + {% match bot.currently_playing %} + {% when Some with (current) %} +

      Currently playing: {{ current.title }}

      +

      {{ bot.position|fmt_duration }} / {{ current.duration|fmt_duration }} + {% match current.duration %} + {% when Some with (duration) %} + {% let position %} + {% match bot.position %} + {% when Some with (pos) %} + {% let position = pos.as_secs_f64() %} + {% when None %} + {% let position = 0.0 %} + {% endmatch %} + {% let progress = position / duration.as_secs_f64() %} + {% let percent = progress * 100.0 %} +

      + {% when None %} +

      + {% endmatch %} + {% when None %} + {% endmatch %} +
      +
      +

      Playlist

      + + + + + + + + {% for item in bot.playlist %} + + + + + + + {% endfor %} +
      #tracklengthadded by
      {{ loop.index }}{{ item.title }} + {% let duration = item.duration %} + {{ duration|fmt_duration }} + {{ item.added_by }}
      +
      + + -- cgit v1.2.3-70-g09d2