html terminal
webhook
bendn 2023-06-07
parent e9fa249 · commit d2f19e4
-rw-r--r--Cargo.lock410
-rw-r--r--Cargo.toml7
-rw-r--r--src/logging.rs48
-rw-r--r--src/main.rs4
-rw-r--r--src/process.rs51
-rw-r--r--src/server.rs135
-rw-r--r--src/webhook.rs207
-rw-r--r--src/websocket.rs81
8 files changed, 818 insertions, 125 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 62a603d..de1791c 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -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"
diff --git a/Cargo.toml b/Cargo.toml
index fdeaa20..4e294f5 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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()
+ }
+}