html terminal
webhook
| -rw-r--r-- | Cargo.lock | 410 | ||||
| -rw-r--r-- | Cargo.toml | 7 | ||||
| -rw-r--r-- | src/logging.rs | 48 | ||||
| -rw-r--r-- | src/main.rs | 4 | ||||
| -rw-r--r-- | src/process.rs | 51 | ||||
| -rw-r--r-- | src/server.rs | 135 | ||||
| -rw-r--r-- | src/webhook.rs | 207 | ||||
| -rw-r--r-- | src/websocket.rs | 81 |
8 files changed, 818 insertions, 125 deletions
@@ -32,6 +32,12 @@ dependencies = [ ] [[package]] +name = "arrayvec" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" + +[[package]] name = "async-channel" version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -295,6 +301,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" [[package]] +name = "core-foundation" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" + +[[package]] name = "cpufeatures" version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -364,7 +386,7 @@ checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" dependencies = [ "errno-dragonfly", "libc", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -399,6 +421,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] name = "form_urlencoded" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -545,6 +582,25 @@ dependencies = [ ] [[package]] +name = "h2" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d357c7ae988e7d2182f7d7871d0b963962420b0678b0997ce7de72001aeab782" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] name = "hashbrown" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -609,6 +665,7 @@ dependencies = [ "futures-channel", "futures-core", "futures-util", + "h2", "http", "http-body", "httparse", @@ -623,6 +680,19 @@ dependencies = [ ] [[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes", + "hyper", + "native-tls", + "tokio", + "tokio-native-tls", +] + +[[package]] name = "idna" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -659,7 +729,7 @@ checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" dependencies = [ "hermit-abi 0.3.1", "libc", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -769,7 +839,25 @@ checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" dependencies = [ "libc", "wasi", - "windows-sys", + "windows-sys 0.48.0", +] + +[[package]] +name = "native-tls" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" +dependencies = [ + "lazy_static", + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", ] [[package]] @@ -799,6 +887,60 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9670a07f94779e00908f3e686eab508878ebb390ba6e604d3a284c00e8d0487b" [[package]] +name = "openssl" +version = "0.10.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69b3f656a17a6cbc115b5c7a40c616947d213ba182135b014d6051b73ab6f019" +dependencies = [ + "bitflags", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.18", +] + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-src" +version = "111.26.0+1.1.1u" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efc62c9f12b22b8f5208c23a7200a442b2e5999f8bdf80233852122b5a4f6f37" +dependencies = [ + "cc", +] + +[[package]] +name = "openssl-sys" +version = "0.9.88" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2ce0f250f34a308dcfdbb351f511359857d4ed2134ba715a4eadd46e1ffd617" +dependencies = [ + "cc", + "libc", + "openssl-src", + "pkg-config", + "vcpkg", +] + +[[package]] name = "panel" version = "0.1.0" dependencies = [ @@ -806,10 +948,13 @@ dependencies = [ "async-std", "axum", "futures", + "futures-util", "minify-html", "paste", + "strip-ansi-escapes", "tokio", "tokio-stream", + "webhook", ] [[package]] @@ -874,6 +1019,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] +name = "pkg-config" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" + +[[package]] name = "polling" version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -886,7 +1037,7 @@ dependencies = [ "libc", "log", "pin-project-lite", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -944,10 +1095,19 @@ dependencies = [ ] [[package]] +name = "redox_syscall" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +dependencies = [ + "bitflags", +] + +[[package]] name = "regex" -version = "1.8.3" +version = "1.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81ca098a9821bd52d6b24fd8b10bd081f47d39c22778cafaa75a2857a62c6390" +checksum = "d0ab3ca65655bb1e41f2a8c8cd662eb4fb035e67c3f78da1d61dffe89d07300f" dependencies = [ "aho-corasick 1.0.2", "memchr", @@ -986,7 +1146,7 @@ dependencies = [ "io-lifetimes", "libc", "linux-raw-sys", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -1002,6 +1162,38 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" [[package]] +name = "schannel" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "713cfb06c7059f3588fb8044c0fad1d09e3c01d225e25b9220dbfdcf16dbb1b3" +dependencies = [ + "windows-sys 0.42.0", +] + +[[package]] +name = "security-framework" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fc758eb7bffce5b308734e9b0c1468893cae9ff70ebf13e7090be8dcbcc83a8" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f51d0c0d83bec45f16480d0ce0058397a69e48fcdc52d1dc8855fb68acbd31a7" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] name = "semver" version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1012,6 +1204,20 @@ name = "serde" version = "1.0.163" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2113ab51b87a539ae008b5c6c02dc020ffa39afd2d83cffcb3f4eb2722cebec2" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.163" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c805777e3930c8883389c602315a24224bcc738b63905ef87cd1420353ea93e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.18", +] [[package]] name = "serde_json" @@ -1085,6 +1291,15 @@ dependencies = [ ] [[package]] +name = "strip-ansi-escapes" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "011cbb39cf7c1f62871aea3cc46e5817b0937b49e9447370c93cacbe93a766d8" +dependencies = [ + "vte", +] + +[[package]] name = "syn" version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1113,6 +1328,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" [[package]] +name = "tempfile" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9fbec84f381d5795b08656e4912bec604d162bff9291d6189a78f4c8ab87998" +dependencies = [ + "cfg-if", + "fastrand", + "redox_syscall", + "rustix", + "windows-sys 0.45.0", +] + +[[package]] name = "thiserror" version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1162,7 +1390,7 @@ dependencies = [ "signal-hook-registry", "socket2", "tokio-macros", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -1177,6 +1405,16 @@ dependencies = [ ] [[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] name = "tokio-stream" version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1200,6 +1438,20 @@ dependencies = [ ] [[package]] +name = "tokio-util" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "806fe8c2c87eccc8b3267cbae29ed3ab2d0bd37fca70ab622e46aaa9375ddb7d" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", + "tracing", +] + +[[package]] name = "tower" version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1318,18 +1570,51 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" [[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + +[[package]] name = "value-bag" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4d330786735ea358f3bc09eea4caa098569c1c93f342d9aca0514915022fe7e" [[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] name = "version_check" version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] +name = "vte" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6cbce692ab4ca2f1f3047fcf732430249c0e971bfdd2b234cf2c47ad93af5983" +dependencies = [ + "arrayvec", + "utf8parse", + "vte_generate_state_changes", +] + +[[package]] +name = "vte_generate_state_changes" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d257817081c7dffcdbab24b9e62d2def62e2ff7d00b1c20062551e6cccc145ff" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] name = "waker-fn" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1428,6 +1713,18 @@ dependencies = [ ] [[package]] +name = "webhook" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09d801ea0225da29d32c85b21d0cb12ed628783f5fa1fbe226e586a3ef6ca96f" +dependencies = [ + "hyper", + "hyper-tls", + "serde", + "serde_json", +] + +[[package]] name = "winapi" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1451,11 +1748,50 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-sys" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "windows-targets", + "windows-targets 0.48.0", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", ] [[package]] @@ -1464,53 +1800,95 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.48.0", + "windows_aarch64_msvc 0.48.0", + "windows_i686_gnu 0.48.0", + "windows_i686_msvc 0.48.0", + "windows_x86_64_gnu 0.48.0", + "windows_x86_64_gnullvm 0.48.0", + "windows_x86_64_msvc 0.48.0", ] [[package]] name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" [[package]] name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" [[package]] name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_gnu" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" [[package]] name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" [[package]] name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" [[package]] name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" [[package]] name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" @@ -24,4 +24,11 @@ tokio = { version = "1.28.2", features = [ "rt-multi-thread", "process", ] } +webhook = "2.1.2" tokio-stream = "0.1.14" +futures-util = "0.3.28" +strip-ansi-escapes = "0.1.1" + +[profile.release] +lto = true +strip = true diff --git a/src/logging.rs b/src/logging.rs index c21a80b..1a151f4 100644 --- a/src/logging.rs +++ b/src/logging.rs @@ -5,11 +5,11 @@ macro_rules! define_print { #[allow(unused_macros)] macro_rules! $name { () => {{ - #[cfg(debug_assertions)] + // #[cfg(debug_assertions)] println!($start) }}; ($fmt:literal) => {{ - #[cfg(debug_assertions)] + // #[cfg(debug_assertions)] println!(concat!($start, " {}"), format!($fmt)) }}; } @@ -20,12 +20,12 @@ macro_rules! define_print { #[allow(unused_macros)] macro_rules! $name { () => {{ - #[cfg(debug_assertions)] + // #[cfg(debug_assertions)] println!($start); $b }}; ($fmt:literal) => {{ - #[cfg(debug_assertions)] + // #[cfg(debug_assertions)] println!(concat!($start, " {}"), format!($fmt)); $b }}; @@ -33,13 +33,47 @@ macro_rules! define_print { ); }; ($prefix:expr) => { - define_print!(cont, concat!($prefix, " ", ">>"), continue); define_print!(fail, concat!($prefix, " ", "!!"), break); define_print!(flush, concat!($prefix, " ", "<<")); - define_print!(noinput, concat!($prefix, " ", "!<")); define_print!(nooutput, concat!($prefix, " ", "!>")); - define_print!(nextiter, concat!($prefix, " ", "--")); define_print!(input, concat!($prefix, " ", "<")); define_print!(output, concat!($prefix, " ", ">")); }; } + +#[macro_export] +macro_rules! dummy_print { + ($name:ident, $start:expr) => { + paste::paste!( + #[allow(unused_macros)] + macro_rules! $name { + () => { + () + }; + ($fmt:literal) => { + () + }; + } + ); + }; + ($name:ident, $start:expr, $b:expr) => { + paste::paste!( + #[allow(unused_macros)] + macro_rules! $name { + () => {{ + $b + }}; + ($fmt:literal) => {{ + $b + }}; + } + ); + }; + ($prefix:expr) => { + dummy_print!(fail, concat!($prefix, " ", "!!"), break); + dummy_print!(flush, concat!($prefix, " ", "<<")); + dummy_print!(nooutput, concat!($prefix, " ", "!>")); + dummy_print!(input, concat!($prefix, " ", "<")); + dummy_print!(output, concat!($prefix, " ", ">")); + }; +} diff --git a/src/main.rs b/src/main.rs index cc78a47..2ab9345 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,6 +6,8 @@ use std::str::FromStr; mod logging; mod process; mod server; +mod webhook; +mod websocket; use process::*; use server::*; @@ -15,7 +17,7 @@ use std::net::SocketAddr; async fn main() { let process = Process::spawn( std::env::var("SERVER_DIR") - .unwrap_or("~/mserv".replace("~", &std::env::var("HOME").unwrap_or("/root".into()))) + .unwrap_or("~/mserv".replace('~', &std::env::var("HOME").unwrap_or("/root".into()))) .into(), ); Server::spawn( diff --git a/src/process.rs b/src/process.rs index de30047..80113db 100644 --- a/src/process.rs +++ b/src/process.rs @@ -1,15 +1,19 @@ use ansi_to_html::convert_escaped; use std::process::Stdio; +use std::sync::Arc; use std::{ffi::OsString, time::Duration}; use tokio::io::{AsyncReadExt, AsyncWriteExt, BufReader}; use tokio::process::{Child, ChildStdin, ChildStdout, Command}; use tokio::sync::broadcast; use tokio::sync::broadcast::error::TryRecvError; use tokio::task::JoinHandle; + +use crate::server::State; pub struct Process { _inner: Child, input: Option<broadcast::Receiver<String>>, - output: Option<broadcast::Sender<String>>, + html_output: Option<broadcast::Sender<String>>, + plain_output: Option<broadcast::Sender<String>>, stdout: BufReader<ChildStdout>, stdin: ChildStdin, } @@ -32,37 +36,48 @@ impl Process { stdin: p.stdin.take().unwrap(), _inner: p, input: None, - output: None, + html_output: None, + plain_output: None, } } pub fn input(mut self, input: broadcast::Receiver<String>) -> Self { self.input = Some(input); - return self; + self + } + + pub fn html_output(mut self, output: broadcast::Sender<String>) -> Self { + self.html_output = Some(output); + self } - pub fn output(mut self, output: broadcast::Sender<String>) -> Self { - self.output = Some(output); - return self; + pub fn plain_output(mut self, output: broadcast::Sender<String>) -> Self { + self.plain_output = Some(output); + self + } + + pub fn with_state(self, state: &Arc<State>) -> Self { + self.html_output(state.stdout_html.clone()) + .plain_output(state.stdout_plain.clone()) } pub fn link(mut self) -> JoinHandle<()> { define_print!("process"); let mut input = self.input.unwrap(); - let output = self.output.unwrap(); + let html_output = self.html_output.unwrap(); + let plain_output = self.plain_output.unwrap(); tokio::spawn(async move { let mut stdout = [0; 4096]; loop { - nextiter!(); - if output.receiver_count() == 0 { + if html_output.receiver_count() + plain_output.receiver_count() == 0 { async_std::task::sleep(Duration::from_millis(500)).await; - cont!(); + continue; } match input.try_recv() { Err(e) => match e { - TryRecvError::Empty => noinput!(), TryRecvError::Closed => fail!("closed"), - TryRecvError::Lagged(_) => noinput!("lagged"), + TryRecvError::Lagged(_) => continue, + TryRecvError::Empty => {} }, Ok(mut s) => { input!("{s}"); @@ -75,13 +90,21 @@ impl Process { let string = { let n = tokio::select! { n = {self.stdout.read(&mut stdout)} => n.unwrap(), - _ = async_std::task::sleep(Duration::from_millis(500)) => cont!() + _ = async_std::task::sleep(Duration::from_millis(500)) => continue, }; String::from_utf8_lossy(&stdout[..n]).into_owned() }; for line in string.lines() { output!("{line}"); - output.send(ansi2html(&line)).unwrap(); + } + if plain_output.receiver_count() > 0 { + let stripped = + String::from_utf8_lossy(&strip_ansi_escapes::strip(&string).unwrap()) + .into_owned(); + plain_output.send(stripped).unwrap(); + } + if html_output.receiver_count() > 0 { + html_output.send(ansi2html(&string)).unwrap(); } nooutput!(); async_std::task::sleep(Duration::from_millis(500)).await; diff --git a/src/server.rs b/src/server.rs index 0cf6bc1..756c984 100644 --- a/src/server.rs +++ b/src/server.rs @@ -1,8 +1,11 @@ -use crate::process::Process; +use crate::websocket::WebSocket; +use crate::{process::Process, webhook::Webhook}; +use axum::http::StatusCode; +use axum::response::{Redirect, Response}; use axum::{ extract::{ - ws::{Message, WebSocket, WebSocketUpgrade}, - Path, State as WsState, + ws::{Message, WebSocketUpgrade}, + Path, State as StateW, }, http::header::CONTENT_TYPE, response::{AppendHeaders, Html, IntoResponse}, @@ -12,25 +15,30 @@ use axum::{ use futures::sink::SinkExt; use minify_html::{minify, Cfg}; use paste::paste; +use std::sync::Mutex; use std::{ net::SocketAddr, sync::{Arc, OnceLock}, - time::{Duration, Instant}, }; -use tokio::sync::broadcast::{self, error::TryRecvError}; -use tokio_stream::StreamExt; +use tokio::sync::broadcast; -struct State { +pub struct State { // sent from the process to the websockets - stdout: broadcast::Sender<String>, - // sent by websockets to the process - stdin: broadcast::Sender<String>, + pub stdout_html: broadcast::Sender<String>, + pub stdout_plain: broadcast::Sender<String>, + // sent to the process + pub stdin: broadcast::Sender<String>, } impl State { fn new(stdin: broadcast::Sender<String>) -> Self { - let (stdout, _rx) = broadcast::channel(5); - Self { stdin, stdout } + let (stdout_html, _) = broadcast::channel(16); + let (stdout_plain, _) = broadcast::channel(2); + Self { + stdin, + stdout_html, + stdout_plain, + } } } @@ -77,7 +85,8 @@ impl Server { .route("/panel", html!(panel)) .route("/plaguess.png", png!(plaguess)) .route("/favicon.ico", png!(logo32)) - .route("/connect/:id", get(connect)) + .route("/connect/:id", get(connect_ws)) + .route("/hook/*k", get(connect_wh)) .with_state(state.clone()); let mut server_handle = tokio::spawn(async move { AxumServer::bind(&addr) @@ -85,7 +94,7 @@ impl Server { .await .unwrap() }); - let mut process_handle = proc.input(stdin).output(state.stdout.clone()).link(); + let mut process_handle = proc.input(stdin).with_state(&state).link(); tokio::select! { _ = (&mut server_handle) => process_handle.abort(), _ = (&mut process_handle) => server_handle.abort(), @@ -109,89 +118,41 @@ pub fn from_utf8(v: &[u8]) -> &str { unreachable!("invalid utf8") } -async fn connect( +fn matches(id: &str) -> bool { + std::env::var("ID").as_deref().unwrap_or("4") == id +} + +async fn connect_ws( ws: WebSocketUpgrade, - WsState(state): WsState<Arc<State>>, + StateW(state): StateW<Arc<State>>, Path(id): Path<String>, ) -> impl IntoResponse { ws.on_upgrade(|socket| async move { - if std::env::var("ID").unwrap_or_else(|_| "4".to_string()) != id { + if !matches(&id) { let mut s = futures::stream::StreamExt::split(socket).0; let _ = s.send(Message::Text("correct id".to_string())).await; return; } - setup(socket, state).await; + WebSocket::new(socket, state).await.wait().await; }) } -async fn setup(stream: WebSocket, state: Arc<State>) { - let (mut sender, mut reciever) = futures::stream::StreamExt::split(stream); - let mut stdout = state.stdout.subscribe(); - let ws_task = tokio::spawn(async move { - define_print!("websocket"); - let mut last: Option<Instant> = None; - let mut waiting: usize = 0; - loop { - nextiter!(); - let out = stdout.try_recv(); - let now = Instant::now(); - match out { - Err(e) => match e { - TryRecvError::Closed => fail!("closed"), - _ => { - if let Some(earlier) = last { - let since = now.duration_since(earlier).as_millis(); - if since > 600 || waiting > 10 { - last.take(); - sender.flush().await.unwrap(); - waiting = 0; - flush!(); - } - } - noinput!(); - // async_std::task::sleep(Duration::from_millis(500)).await; - // cont!(); - } - }, - Ok(m) => { - input!("{m}"); - if let Err(e) = sender.feed(Message::Text(m)).await { - fail!("{e}"); - }; - last = Some(now); - waiting += 1; - } - } - match tokio::select! { - next = reciever.next() => next, - _ = async_std::task::sleep(Duration::from_millis(100)) => cont!(), - } { - Some(r) => match r { - Ok(m) => { - if let Message::Text(m) = m { - output!("{m}"); - state.stdin.send(m).unwrap(); - } - } - Err(e) => { - fail!("{e}"); - } - }, - None => { - nooutput!() - } - } - async_std::task::sleep(Duration::from_millis(100)).await; - cont!(); +async fn connect_wh(StateW(state): StateW<Arc<State>>, Path(params): Path<String>) -> Response { + static WEBHOOK: Mutex<Option<Webhook>> = Mutex::new(None); //one slot + let (id, url) = { + match params.split_once('/') { + None => return StatusCode::BAD_REQUEST.into_response(), + Some((a, b)) => (a, b), } - }); - // let mut recv_task = tokio::spawn(async move { - // while let Some(Ok(Message::Text(m))) = reciever.try_next().await { - // println!("ws sent {m}"); - // state.stdin.send(m).unwrap(); - // } - // }); - - ws_task.await.unwrap(); - println!("websocket !! finish"); + }; + if !matches(id) { + return StatusCode::FORBIDDEN.into_response(); + } + if let Some(w) = WEBHOOK.lock().unwrap().as_ref() { + if w.running() { + return StatusCode::LOCKED.into_response(); + } + } + *WEBHOOK.lock().unwrap() = Some(Webhook::new(state.stdout_plain.subscribe(), url).await); + Redirect::to("/panel").into_response() } diff --git a/src/webhook.rs b/src/webhook.rs new file mode 100644 index 0000000..275cdf9 --- /dev/null +++ b/src/webhook.rs @@ -0,0 +1,207 @@ +use std::{ + sync::Arc, + time::{Duration, Instant}, +}; + +use tokio::{ + sync::broadcast::{self, error::TryRecvError}, + task::JoinHandle, +}; +use webhook::{client::WebhookClient, models::Message}; + +macro_rules! send { + ($client:ident, $block:expr) => {{ + let client = $client.clone(); + let mut m = Message::new(); + let mut bloc: Box<dyn FnMut(&mut Message) -> &mut Message> = Box::new($block); + bloc(&mut m); + m.allow_mentions(None, None, None, false); + tokio::spawn(async move { + client.send_message(&m).await.unwrap(); + }); + // handle goes out of scope, detatched + }}; +} + +pub struct Webhook(JoinHandle<()>); +impl Webhook { + pub async fn new(mut stdout: broadcast::Receiver<String>, url: &str) -> Self { + let client = Arc::new(WebhookClient::new(url)); + Self(tokio::spawn(async move { + define_print!("webhook"); + let mut last: Option<Instant> = None; + let mut feed: Vec<String> = vec![]; + loop { + let out = stdout.try_recv(); + let now = Instant::now(); + match out { + Err(e) => match e { + TryRecvError::Closed => fail!("closed"), + TryRecvError::Lagged(_) => continue, + TryRecvError::Empty => { + if let Some(earlier) = last { + let since = now.duration_since(earlier).as_secs(); + if since > 2 || feed.len() > 15 { + last.take(); + flush::<MindustryStyle>(feed, &client).await; + feed = vec![]; + flush!(); + } + } + async_std::task::sleep(Duration::from_millis(20)).await; + continue; + } + }, + Ok(m) => { + for line in m.lines() { + feed.push(line.to_string()); + input!("{line}"); + } + last = Some(now); + } + } + async_std::task::sleep(Duration::from_millis(20)).await; + } + })) + } + + pub fn running(&self) -> bool { + !self.0.is_finished() + } +} + +/// functions ordered by call order +pub trait OutputStyle { + /// first step + fn fix(raw_line: String) -> String; + /// ignore completely? + fn ignore(line: &str) -> bool; + /// is there no user + fn unnamed(line: &str) -> bool; + /// get the user and the content + fn split(line: &str) -> Option<(String, String)>; +} + +macro_rules! s { + ($line:expr, $e:ident) => { + $line.starts_with(stringify!($e)) + }; + ($line:expr, $e:expr) => { + $line.starts_with($e) + }; +} +pub struct MindustryStyle; +impl OutputStyle for MindustryStyle { + fn fix(raw_line: String) -> String { + raw_line.split(' ').skip(3).collect::<Vec<&str>>().join(" ") + } + fn ignore(line: &str) -> bool { + line.trim().is_empty() + || line.contains("requested trace info") + || s!(line, Server) + || s!(line, Connected) + || s!(line, PLAGUE) + || s!(line, "1 mods loaded") + } + + fn unnamed(line: &str) -> bool { + s!(line, [' ', '\t']) + } + + fn split(line: &str) -> Option<(String, String)> { + if let Some(n) = line.find(':') { + if n < 12 { + if let Some((u, c)) = line.split_once(':') { + return Some(( + unify(u).trim_start_matches('<').to_owned(), + unify(c).trim_end_matches('>').to_owned(), + )); + } + } + } + if let Some(index) = line.find("has") { + if line.contains("connected") { + let player = &line[..index]; + return Some((unify(player).trim().to_owned(), "joined".to_owned())); + } + } + None + } +} + +async fn flush<Style: OutputStyle>(feed: Vec<String>, client: &Arc<WebhookClient>) { + let mut current: Option<String> = None; + let mut message: Option<String> = None; + let mut unnamed: Option<String> = None; + + // this code is very game dependent + for line in feed.into_iter() { + // [0-0-0-0 0-0-0-0] [i] ... -> ... + let line: String = Style::fix(line); + if Style::ignore(&line) { + continue; + } + if Style::unnamed(&line) { + unnamed.madd(line); + continue; + } + + if let Some((name, msg)) = Style::split(&line) { + if !(msg.is_empty() || name.is_empty()) { + if let Some(n) = current.as_ref() { + if n == &name { + message.madd_panic(&msg); + continue; + } else { + let message = message.take().unwrap(); + send!(client, |m| m.content(&message).username(n)); + current.take(); + } + } + current = Some(name.to_owned()); + message = Some(msg.to_owned()); + // interrupt + if let Some(msg) = unnamed.take() { + send!(client, |m| m.username("server").content(&msg)); + } + continue; + } + } + unnamed.madd(line); + } + // leftovers + if let Some(n) = current.as_ref() { + let message = message.take().unwrap(); + send!(client, |m| m.content(&message).username(n)); + } + if let Some(msg) = unnamed.as_ref() { + send!(client, |m| m.username("server").content(msg)); + } +} +/// latin > extended a > kill +fn unify(s: &str) -> String { + s.chars() + .map(|c| if (c as u32) < 384 { c } else { ' ' }) + .collect() +} + +trait Madd { + fn madd(&mut self, line: String); + fn madd_panic(&mut self, line: &str); +} + +// cant impl addassign because no impl for other types because +impl Madd for Option<String> { + fn madd(&mut self, line: String) { + match self.take() { + Some(x) => *self = Some(x + "\n" + &line), + None => *self = Some(line), + } + } + fn madd_panic(&mut self, line: &str) { + match self.take() { + Some(x) => *self = Some(x + "\n" + line), + None => unreachable!(), + } + } +} diff --git a/src/websocket.rs b/src/websocket.rs new file mode 100644 index 0000000..75591d4 --- /dev/null +++ b/src/websocket.rs @@ -0,0 +1,81 @@ +use crate::server::State; +use axum::extract::ws::{Message, WebSocket as RealWebSocket}; +use futures_util::SinkExt; +use std::{ + sync::Arc, + time::{Duration, Instant}, +}; +use tokio::{sync::broadcast::error::TryRecvError, task::JoinHandle}; +use tokio_stream::StreamExt; + +pub struct WebSocket(JoinHandle<()>); +impl WebSocket { + pub async fn new(stream: RealWebSocket, state: Arc<State>) -> Self { + let (mut sender, mut reciever) = futures::stream::StreamExt::split(stream); + let mut stdout = state.stdout_html.subscribe(); + let ws_task = tokio::spawn(async move { + dummy_print!("websocket"); + let mut last: Option<Instant> = None; + let mut waiting: usize = 0; + loop { + let out = stdout.try_recv(); + let now = Instant::now(); + match out { + Err(e) => match e { + TryRecvError::Closed => fail!("closed"), + TryRecvError::Lagged(_) => continue, // no delay + _ => { + if let Some(earlier) = last { + let since = now.duration_since(earlier).as_millis(); + if since > 200 || waiting > 15 { + last.take(); + sender.flush().await.unwrap(); + waiting = 0; + flush!(); + } + } + } + }, + Ok(m) => { + #[allow(unused_variables)] + for line in m.lines() { + input!("{line}"); + if let Err(e) = sender.feed(Message::Text(line.to_owned())).await { + fail!("{e}"); + }; + waiting += 1; + } + last = Some(now); + } + } + match tokio::select! { + next = reciever.next() => next, + _ = async_std::task::sleep(Duration::from_millis(20)) =>continue, + } { + Some(r) => match r { + Ok(m) => { + if let Message::Text(m) = m { + output!("{m}"); + state.stdin.send(m).unwrap(); + } + } + #[allow(unused_variables)] + Err(e) => { + fail!("{e}"); + } + }, + None => { + nooutput!(); + } + } + async_std::task::sleep(Duration::from_millis(20)).await; + continue; + } + }); + Self(ws_task) + } + + pub async fn wait(self) { + self.0.await.unwrap() + } +} |