From d7d7a812aa2b66e836a7b973f6ec003532cbdf51 Mon Sep 17 00:00:00 2001 From: mbround18 Date: Tue, 22 Oct 2024 21:28:20 -0700 Subject: [PATCH] Add player joined and left notifications with player.list --- Cargo.lock | 243 +++++++++++++-------------- Dockerfile.valheim | 4 +- src/odin/Cargo.toml | 1 + src/odin/commands/logs.rs | 136 ++++++++-------- src/odin/commands/stop.rs | 4 - src/odin/lib.rs | 2 + src/odin/log_filters/mod.rs | 5 +- src/odin/log_filters/player.rs | 289 +++++++++++++++++++++++++++++++-- src/odin/log_filters/probes.rs | 14 ++ src/odin/main.rs | 2 +- src/odin/utils/environment.rs | 6 +- src/scripts/entrypoint.sh | 2 + 12 files changed, 485 insertions(+), 223 deletions(-) create mode 100644 src/odin/log_filters/probes.rs diff --git a/Cargo.lock b/Cargo.lock index 2a70586d..a710a1d8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -16,9 +16,9 @@ dependencies = [ [[package]] name = "addr2line" -version = "0.24.1" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5fb1d8e4442bd405fdfd1dacb42792696b0cf9cb15882e5d097b742a676d375" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" dependencies = [ "gimli", ] @@ -131,6 +131,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "anyhow" +version = "1.0.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c042108f3ed77fd83760a5fd79b53be043192bb3b9dba91d8c574c0ada7850c8" + [[package]] name = "arbitrary" version = "1.3.2" @@ -230,9 +236,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.7.2" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" +checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da" [[package]] name = "bzip2" @@ -296,9 +302,9 @@ checksum = "7b02b629252fe8ef6460461409564e2c21d0c8e77e0944f3d189ff06c4e932ad" [[package]] name = "cc" -version = "1.1.24" +version = "1.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "812acba72f0a070b003d3697490d2b55b837230ae7c6c6497f05cc2ddbb8d938" +checksum = "c2e7962b54006dcfcc61cb72735f4d89bb97061dd6a7ed882ec6b8ee53714c6f" dependencies = [ "jobserver", "libc", @@ -338,9 +344,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.19" +version = "4.5.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7be5744db7978a28d9df86a214130d106a89ce49644cbc4e3f0c22c3fba30615" +checksum = "b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8" dependencies = [ "clap_builder", "clap_derive", @@ -348,9 +354,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.19" +version = "4.5.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5fbc17d3ef8278f55b282b2a2e75ae6f6c7d4bb70ed3d0382375104bfafdb4b" +checksum = "19bc80abd44e4bed93ca373a0704ccbd1b710dc5749406201bb018272808dc54" dependencies = [ "anstream", "anstyle", @@ -654,15 +660,6 @@ dependencies = [ "miniz_oxide", ] -[[package]] -name = "fluent-uri" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17c704e9dbe1ddd863da1e6ff3567795087b1eb201ce80d8fa81162e1516500d" -dependencies = [ - "bitflags 1.3.2", -] - [[package]] name = "fnv" version = "1.0.7" @@ -701,9 +698,9 @@ dependencies = [ [[package]] name = "futures" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" dependencies = [ "futures-channel", "futures-core", @@ -716,9 +713,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", "futures-sink", @@ -726,15 +723,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] name = "futures-executor" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" dependencies = [ "futures-core", "futures-task", @@ -743,27 +740,27 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" [[package]] name = "futures-sink" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" [[package]] name = "futures-task" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" [[package]] name = "futures-util" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ "futures-channel", "futures-core", @@ -799,9 +796,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.31.0" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32085ea23f3234fc7846555e85283ba4de91e21016dc0455a16286d87a292d64" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "glob" @@ -1025,9 +1022,9 @@ dependencies = [ [[package]] name = "hyper" -version = "0.14.30" +version = "0.14.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a152ddd61dfaec7273fe8419ab357f33aee0d914c5f4efbf0d96fa749eea5ec9" +checksum = "8c08302e8fa335b151b788c775ff56e7a03ae64ff85c548ee820fecb70356e85" dependencies = [ "bytes", "futures-channel", @@ -1049,9 +1046,9 @@ dependencies = [ [[package]] name = "hyper" -version = "1.4.1" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50dfd22e0e76d0f662d429a5f80fcaf3855009297eab6a0a9f8543834744ba05" +checksum = "bbbff0a806a4728c99295b254c8838933b5b082d75e3cb70c8dab21fdfbcfa9a" dependencies = [ "bytes", "futures-channel", @@ -1076,7 +1073,7 @@ checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333" dependencies = [ "futures-util", "http 1.1.0", - "hyper 1.4.1", + "hyper 1.5.0", "hyper-util", "rustls", "rustls-pki-types", @@ -1097,7 +1094,7 @@ dependencies = [ "futures-util", "http 1.1.0", "http-body 1.0.1", - "hyper 1.4.1", + "hyper 1.5.0", "pin-project-lite", "socket2", "tokio", @@ -1203,9 +1200,9 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.10.0" +version = "2.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "187674a687eed5fe42285b40c6291f9a01517d415fad1c3cbc6a9f778af7fcd4" +checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708" [[package]] name = "is_terminal_polyfill" @@ -1230,18 +1227,18 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.70" +version = "0.3.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" +checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9" dependencies = [ "wasm-bindgen", ] [[package]] name = "json-patch" -version = "2.0.0" +version = "3.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b1fb8864823fad91877e6caea0baca82e49e8db50f8e5c9f9a453e27d3330fc" +checksum = "863726d7afb6bc2590eeff7135d923545e5e964f004c2ccf8716c25e70a86f08" dependencies = [ "jsonptr", "serde", @@ -1251,11 +1248,10 @@ dependencies = [ [[package]] name = "jsonptr" -version = "0.4.7" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c6e529149475ca0b2820835d3dce8fcc41c6b943ca608d32f35b449255e4627" +checksum = "5dea2b27dd239b2556ed7a25ba842fe47fd602e7fc7433c2a8d6106d4d9edd70" dependencies = [ - "fluent-uri", "serde", "serde_json", ] @@ -1288,9 +1284,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.159" +version = "0.2.161" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" +checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1" [[package]] name = "libredox" @@ -1441,7 +1437,7 @@ dependencies = [ "http 1.1.0", "http-body 1.0.1", "http-body-util", - "hyper 1.4.1", + "hyper 1.5.0", "hyper-util", "log", "rand", @@ -1515,9 +1511,9 @@ dependencies = [ [[package]] name = "object" -version = "0.36.4" +version = "0.36.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "084f1a5821ac4c651660a94a7153d27ac9d8a53736203f58b31945ded098070a" +checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" dependencies = [ "memchr", ] @@ -1527,6 +1523,7 @@ name = "odin" version = "2.1.0" dependencies = [ "a2s", + "anyhow", "cached", "cargo-husky", "cc", @@ -1564,12 +1561,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.20.1" +version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82881c4be219ab5faaf2ad5e5e5ecdff8c66bd7402ca3160975c93b24961afd1" -dependencies = [ - "portable-atomic", -] +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "parking_lot" @@ -1612,9 +1606,9 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pest" -version = "2.7.13" +version = "2.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdbef9d1d47087a895abd220ed25eb4ad973a5e26f6a4367b038c25e28dfc2d9" +checksum = "879952a81a83930934cbf1786752d6dedc3b1f29e8f8fb2ad1d0a36f377cf442" dependencies = [ "memchr", "thiserror", @@ -1623,9 +1617,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.7.13" +version = "2.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d3a6e3394ec80feb3b6393c725571754c6188490265c61aaf260810d6b95aa0" +checksum = "d214365f632b123a47fd913301e14c946c61d1c183ee245fa76eb752e59a02dd" dependencies = [ "pest", "pest_generator", @@ -1633,9 +1627,9 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.7.13" +version = "2.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94429506bde1ca69d1b5601962c73f4172ab4726571a59ea95931218cb0e930e" +checksum = "eb55586734301717aea2ac313f50b2eb8f60d2fc3dc01d190eefa2e625f60c4e" dependencies = [ "pest", "pest_meta", @@ -1646,9 +1640,9 @@ dependencies = [ [[package]] name = "pest_meta" -version = "2.7.13" +version = "2.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac8a071862e93690b6e34e9a5fb8e33ff3734473ac0245b27232222c4906a33f" +checksum = "b75da2a70cf4d9cb76833c990ac9cd3923c9a8905a8929789ce347c84564d03d" dependencies = [ "once_cell", "pest", @@ -1657,18 +1651,18 @@ dependencies = [ [[package]] name = "pin-project" -version = "1.1.5" +version = "1.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" +checksum = "baf123a161dde1e524adf36f90bc5d8d3462824a9c43553ad07a8183161189ec" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.5" +version = "1.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" +checksum = "a4502d8515ca9f32f1fb543d987f63d95a14934883db45bdb48060b6b69257f8" dependencies = [ "proc-macro2", "quote", @@ -1693,12 +1687,6 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" -[[package]] -name = "portable-atomic" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc9c68a3f6da06753e9335d63e27f6b9754dd1920d941135b7ea8224f141adb2" - [[package]] name = "powerfmt" version = "0.2.0" @@ -1742,9 +1730,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.86" +version = "1.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" dependencies = [ "unicode-ident", ] @@ -1908,7 +1896,7 @@ dependencies = [ "http 1.1.0", "http-body 1.0.1", "http-body-util", - "hyper 1.4.1", + "hyper 1.5.0", "hyper-rustls", "hyper-util", "ipnet", @@ -1979,9 +1967,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.13" +version = "0.23.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2dabaac7466917e566adb06783a81ca48944c6898a1b08b9374106dd671f4c8" +checksum = "5fbb44d7acc4e873d613422379f69f237a1b141928c02f6bc6ccfddddc2d7993" dependencies = [ "once_cell", "ring", @@ -2002,9 +1990,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e696e35370c65c9c541198af4543ccd580cf17fc25d8e05c5a242b202488c55" +checksum = "16f1201b3c9a7ee8039bcadc17b7e605e2945b27eee7631788c1bd2b0643674b" [[package]] name = "rustls-webpki" @@ -2034,9 +2022,9 @@ dependencies = [ [[package]] name = "scc" -version = "2.2.0" +version = "2.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "836f1e0f4963ef5288b539b643b35e043e76a32d0f4e47e67febf69576527f50" +checksum = "f2c1f7fc6deb21665a9060dfc7d271be784669295a31babdcd4dd2c79ae8cbfb" dependencies = [ "sdd", ] @@ -2055,24 +2043,24 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "sdd" -version = "3.0.3" +version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60a7b59a5d9b0099720b417b6325d91a52cbf5b3dcb5041d864be53eefa58abc" +checksum = "49c1eeaf4b6a87c7479688c6d52b9f1153cedd3c489300564f932b065c6eab95" [[package]] name = "serde" -version = "1.0.210" +version = "1.0.213" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" +checksum = "3ea7893ff5e2466df8d720bb615088341b295f849602c6956047f8f80f0e9bc1" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.210" +version = "1.0.213" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" +checksum = "7e85ad2009c50b58e87caa8cd6dac16bdf511bbfb7af6c33df902396aa480fa5" dependencies = [ "proc-macro2", "quote", @@ -2081,9 +2069,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.128" +version = "1.0.132" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" +checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03" dependencies = [ "itoa", "memchr", @@ -2105,9 +2093,9 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.10.0" +version = "3.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9720086b3357bcb44fce40117d769a4d068c70ecfa190850a980a71755f66fcc" +checksum = "8e28bdad6db2b8340e449f7108f020b3b092e8583a9e3fb82713e1d4e71fe817" dependencies = [ "base64 0.22.1", "chrono", @@ -2123,9 +2111,9 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.10.0" +version = "3.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f1abbfe725f27678f4663bcacb75a83e829fd464c25d78dd038a3a29e307cec" +checksum = "9d846214a9854ef724f3da161b426242d8de7c1fc7de2f89bb1efcb154dca79d" dependencies = [ "darling", "proc-macro2", @@ -2252,9 +2240,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.79" +version = "2.0.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" +checksum = "83540f837a8afc019423a8edb95b52a8effe46957ee402287f4292fae35be021" dependencies = [ "proc-macro2", "quote", @@ -2336,18 +2324,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.64" +version = "1.0.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" +checksum = "5d11abd9594d9b38965ef50805c5e469ca9cc6f197f883f717e0269a3057b3d5" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.64" +version = "1.0.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" +checksum = "ae71770322cbd277e69d762a16c444af02aa0575ac0d174f0b9562d3b37f8602" dependencies = [ "proc-macro2", "quote", @@ -2402,9 +2390,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.40.0" +version = "1.41.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998" +checksum = "145f3413504347a2be84393cc8a7d2fb4d863b375909ea59f2158261aa258bbb" dependencies = [ "backtrace", "bytes", @@ -2530,12 +2518,9 @@ checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" [[package]] name = "unicase" -version = "2.7.0" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" -dependencies = [ - "version_check", -] +checksum = "7e51b68083f157f853b6379db119d1c1be0e6e4dec98101079dec41f6f5cf6df" [[package]] name = "unicode-bidi" @@ -2623,7 +2608,7 @@ dependencies = [ "futures-util", "headers", "http 0.2.12", - "hyper 0.14.30", + "hyper 0.14.31", "log", "mime", "mime_guess", @@ -2649,9 +2634,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.93" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" +checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e" dependencies = [ "cfg-if", "once_cell", @@ -2660,9 +2645,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.93" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" +checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358" dependencies = [ "bumpalo", "log", @@ -2675,9 +2660,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.43" +version = "0.4.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61e9300f63a621e96ed275155c108eb6f843b6a26d053f122ab69724559dc8ed" +checksum = "cc7ec4f8827a71586374db3e87abdb5a2bb3a15afed140221307c3ec06b1f63b" dependencies = [ "cfg-if", "js-sys", @@ -2687,9 +2672,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.93" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" +checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2697,9 +2682,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.93" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" +checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" dependencies = [ "proc-macro2", "quote", @@ -2710,15 +2695,15 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.93" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" +checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" [[package]] name = "web-sys" -version = "0.3.70" +version = "0.3.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26fdeaafd9bd129f65e7c031593c24d62186301e0c72c8978fa1678be7d532c0" +checksum = "f6488b90108c040df0fe62fa815cbdee25124641df01814dd7282749234c6112" dependencies = [ "js-sys", "wasm-bindgen", diff --git a/Dockerfile.valheim b/Dockerfile.valheim index abae9269..6c52b3fa 100644 --- a/Dockerfile.valheim +++ b/Dockerfile.valheim @@ -108,7 +108,9 @@ ENV PORT="2456" \ BACKUP_LOCATION="/home/steam/backups" \ WEBHOOK_STATUS_SUCCESSFUL="1" \ WEBHOOK_STATUS_FAILED="1" \ - BEPINEX_RELEASES_URL="https://thunderstore.io/api/experimental/package/denikson/BepInExPack_Valheim/" + BEPINEX_RELEASES_URL="https://thunderstore.io/api/experimental/package/denikson/BepInExPack_Valheim/" \ + WEBHOOK_STATUS_JOINED="1" \ + WEBHOOK_STATUS_LEFT="1" # Copy scripts and set permissions COPY --chmod=${EXPECTED_OCTAL} --chown=steam:steam ./src/scripts/*.sh /home/steam/scripts/ diff --git a/src/odin/Cargo.toml b/src/odin/Cargo.toml index d6f35228..495b7d94 100644 --- a/src/odin/Cargo.toml +++ b/src/odin/Cargo.toml @@ -54,6 +54,7 @@ tokio = { version = "1", features = ["full"] } notify = "6.1.1" json-patch = "*" cached = "0" +anyhow = "1.0.91" [dev-dependencies] once_cell = "1.19.0" diff --git a/src/odin/commands/logs.rs b/src/odin/commands/logs.rs index b6f9af99..4583f1f3 100644 --- a/src/odin/commands/logs.rs +++ b/src/odin/commands/logs.rs @@ -1,45 +1,55 @@ -use crate::notifications::enums::notification_event::NotificationEvent; -use crate::notifications::enums::player::PlayerStatus::{Joined, Left}; +use crate::log_filters::{handle_launch_probes, handle_player_events}; use crate::utils::common_paths::log_directory; -use log::{error, info, warn}; use crate::utils::environment::is_env_var_truthy; +use anyhow::{Context, Result}; +use log::{error, info, warn}; use std::collections::HashMap; use std::ffi::OsStr; +use std::fs; use std::fs::{read_to_string, File}; -use std::io::{BufRead, Read}; -use std::path::{Path, PathBuf}; -use std::{error, fs}; +use std::io::prelude::*; +use std::io::{BufRead, BufReader, Seek, SeekFrom}; +use std::path::PathBuf; +use std::sync::Arc; use tokio::task; -/// Processes a line of text and generates appropriate log messages and notifications. -/// -/// # Arguments -/// -/// * `path` - A `PathBuf` representing the path of the file being processed. -/// * `line` - A `String` containing the line of text to be processed. -fn handle_line(path: PathBuf, line: String) { - if line.is_empty() { +/// Struct to keep track of each file's state, including its last read line position. +#[derive(Clone)] +struct FileTracker { + path: PathBuf, + last_position: u64, +} + +impl FileTracker { + fn new(path: PathBuf) -> Self { + Self { + path, + last_position: 0, + } + } +} + +/// Processes a line of text from the log and generates appropriate log messages and notifications. +fn handle_line(path: &PathBuf, line: &str) { + if line.trim().is_empty() { return; } if line.contains("[Info : Unity Log]") { - // skipping duplicate lines return; } if is_env_var_truthy("PLAYER_EVENT_NOTIFICATIONS") { - if line.contains("I HAVE ARRIVED!") { - - - NotificationEvent::Player(Joined).send_notification(None); - } - - if line.contains("Player disconnected") { - NotificationEvent::Player(Left).send_notification(None); - } + handle_player_events(line); } - let file_name = Path::new(&path).file_name().unwrap().to_str().unwrap(); + let file_name = match path.file_name().and_then(|name| name.to_str()) { + Some(name) => name, + None => { + error!("Failed to extract file name from path: {:?}", path); + return; + } + }; if line.contains("WARNING") { warn!("[{}]: {}", file_name, line); @@ -49,44 +59,39 @@ fn handle_line(path: PathBuf, line: String) { info!("[{}]: {}", file_name, line); } - if line.contains("Game server connected") { - NotificationEvent::Start(crate::notifications::enums::event_status::EventStatus::Successful) - .send_notification(None); - } - - if line.contains("Steam manager on destroy") { - NotificationEvent::Stop(crate::notifications::enums::event_status::EventStatus::Successful) - .send_notification(None); - info!("The game server has been stopped"); - } -} - -fn read_file(file_name: String) -> Vec { - let path = Path::new(&file_name); - if !path.exists() { - return String::from("Not Found!").into(); - } - let mut file_content = Vec::new(); - let mut file = File::open(&file_name).expect("Unable to open file"); - file.read_to_end(&mut file_content).expect("Unable to read"); - file_content + handle_launch_probes(line); } -async fn tail_file(path: PathBuf) -> Result<(), Box> { - let mut last_line = 0; - let file = path.to_str().ok_or("Invalid file path")?; +/// Tails the given log file asynchronously, processing new lines as they are written to the file. +async fn tail_file(mut file_tracker: FileTracker) -> Result<()> { + let file = File::open(&file_tracker.path).context("Unable to open file for tailing")?; + let mut reader = BufReader::new(file); + reader + .seek(SeekFrom::Start(file_tracker.last_position)) + .context("Failed to seek to start position")?; loop { - let content = read_file(file.to_string()); - let current_line = content.lines().count(); - - if current_line > last_line { - content - .lines() - .skip(last_line) - .for_each(|line| handle_line(path.clone(), line.unwrap_or_default())); + let mut new_lines = Vec::new(); + for line_result in reader.by_ref().lines() { + match line_result { + Ok(line) => { + new_lines.push(line); + } + Err(e) => { + error!( + "Failed to read line from file '{}': {}", + file_tracker.path.display(), + e + ); + } + } + } - last_line = current_line; + if !new_lines.is_empty() { + file_tracker.last_position = reader.stream_position()?; + for line in new_lines { + handle_line(&file_tracker.path, &line); + } } tokio::time::sleep(std::time::Duration::from_millis(100)).await; @@ -95,10 +100,11 @@ async fn tail_file(path: PathBuf) -> Result<(), Box> { pub async fn watch_logs(log_path: String) { let mut handles = Vec::new(); - let mut watched_files = HashMap::new(); + let mut watched_files: HashMap = HashMap::new(); + let log_path = Arc::new(log_path); loop { - let paths = fs::read_dir(&log_path) + let paths = fs::read_dir(&*log_path) .expect("Could not read log directory") .filter_map(Result::ok) .map(|entry| entry.path()) @@ -107,12 +113,10 @@ pub async fn watch_logs(log_path: String) { for path in paths { if path.is_file() { watched_files.entry(path.clone()).or_insert_with(|| { - let handle = task::spawn(async move { - if let Err(e) = tail_file(path).await { - error!("Error tailing file: {:?}", e); - } - }); + let tracker = FileTracker::new(path.clone()); + let handle = task::spawn(tail_file(tracker.clone())); handles.push(handle); + tracker }); } } @@ -137,7 +141,7 @@ pub fn print_logs(log_path: String, lines: Option) { .take(lines.unwrap_or(10) as usize) .collect::>(); for line in lines.iter().rev() { - handle_line(path.clone(), line.to_string()); + handle_line(&path, line); } } } diff --git a/src/odin/commands/stop.rs b/src/odin/commands/stop.rs index 1bd0e923..17921633 100644 --- a/src/odin/commands/stop.rs +++ b/src/odin/commands/stop.rs @@ -2,12 +2,9 @@ use log::{debug, error, info}; use std::process::exit; -use crate::notifications::enums::event_status::EventStatus; -use crate::notifications::enums::notification_event::NotificationEvent; use crate::{constants, server, utils::get_working_dir}; pub fn invoke(dry_run: bool) { - NotificationEvent::Stop(EventStatus::Running).send_notification(None); debug!("Stopping server, directory needs to be where the server executable is located."); info!( "Stopping server, using working directory {}", @@ -23,5 +20,4 @@ pub fn invoke(dry_run: bool) { } server::blocking_shutdown(); } - NotificationEvent::Stop(EventStatus::Successful).send_notification(None); } diff --git a/src/odin/lib.rs b/src/odin/lib.rs index 6f53cf94..87197ad5 100644 --- a/src/odin/lib.rs +++ b/src/odin/lib.rs @@ -11,3 +11,5 @@ pub mod server; pub mod steamcmd; pub mod traits; pub mod utils; + +mod log_filters; diff --git a/src/odin/log_filters/mod.rs b/src/odin/log_filters/mod.rs index 4a77162b..ae4952c4 100644 --- a/src/odin/log_filters/mod.rs +++ b/src/odin/log_filters/mod.rs @@ -1,4 +1,5 @@ -use regex::Regex; - mod player; +mod probes; +pub use player::handle_player_events; +pub use probes::handle_launch_probes; diff --git a/src/odin/log_filters/player.rs b/src/odin/log_filters/player.rs index 71d63527..4c5084e6 100644 --- a/src/odin/log_filters/player.rs +++ b/src/odin/log_filters/player.rs @@ -1,30 +1,287 @@ -use regex::Regex; +use crate::files::FileManager; use crate::notifications::enums::notification_event::NotificationEvent; -use crate::notifications::enums::player::PlayerStatus::Joined; +use crate::notifications::enums::player::PlayerStatus::{Joined, Left}; +use chrono::Utc; +use log::{debug, error, info}; +use regex::Regex; +use serde::{Deserialize, Serialize}; +use std::fmt::Display; +use std::num::ParseIntError; +#[derive(Serialize, Deserialize, Debug)] struct Player { - + id: u64, + name: String, + last_seen: i64, +} + +impl Clone for Player { + fn clone(&self) -> Self { + Player { + id: self.id, + name: String::from(&self.name), + last_seen: self.last_seen, + } + } +} + +impl Default for Player { + fn default() -> Self { + let now = Utc::now(); + let epoch = now.timestamp(); + + Player { + id: 0, + name: "Unknown".to_string(), + last_seen: epoch, + } + } +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct PlayerList { + players: Vec, +} + +impl PlayerList { + fn save(&self) -> bool { + self.write(self.to_string()) + } + + fn update_or_push(&mut self, player: Player) { + if let Some(existing_player) = self.players.iter_mut().find(|p| p.id == player.id) { + existing_player.last_seen = player.last_seen; + } else { + self.players.push(player); + } + } + + pub fn joined_event(id: u64, name: String) { + let mut list = PlayerList::default(); + let now = Utc::now(); + let last_seen = now.timestamp(); + + NotificationEvent::Player(Joined) + .send_notification(Some(format!("Player {name} has joined the adventure!"))); + + list.update_or_push(Player { + id, + name, + last_seen, + }); + + list.save(); + } + + pub fn left_event(id: u64) { + let list = PlayerList::default(); + if let Some(player) = list.players.iter().find(|player| player.id.eq(&id)) { + let name = &player.name; + NotificationEvent::Player(Left) + .send_notification(Some(format!("Player {name} has left the adventure"))) + } + } +} + +impl Default for PlayerList { + #[cfg(not(test))] + fn default() -> Self { + let list = PlayerList { players: vec![] }; + let read_list = list.read(); + + if read_list.is_empty() { + list + } else { + PlayerList::from(read_list) + } + } + + #[cfg(test)] + fn default() -> Self { + PlayerList { players: vec![] } + } +} + +impl FileManager for PlayerList { + fn path(&self) -> String { + format!( + "{}/player.list", + crate::utils::common_paths::saves_directory() + ) + } +} + +impl From for PlayerList { + fn from(value: String) -> Self { + if value.trim().is_empty() { + PlayerList::default() + } else { + serde_json::from_str(&value).expect("Failed to parse player list! Was it modified?") + } + } } -struct PlayerList { - players: Vec<> +impl Display for PlayerList { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}", + serde_json::to_string_pretty(&self).expect("Failed to return string of PlayerList") + ) + } } +/// Handles player-related events such as joining or leaving. +/// It uses regex to extract information from log lines and triggers appropriate events. +/// +/// # Arguments +/// * `line` - A `&str` representing a single line from the log. +pub fn handle_player_events(line: &str) { + // Regex to capture player joining event with player name and ID + let joined_regex = Regex::new( + r"\d{2}/\d{2}/\d{4} \d{2}:\d{2}:\d{2}: Got character ZDOID from (.*) : (\d+(?::\d+)?)", + ) + .expect("Failed to compile joined_regex"); + + // Regex to capture player leaving event with player ID + let left_regex = Regex::new( + r"\d{2}/\d{2}/\d{4} \d{2}:\d{2}:\d{2}: Destroying abandoned non persistent zdo (\d+:3) owner \d+" + ).expect("Failed to compile left_regex"); + + // Handle player joining event + if let Some(captures) = joined_regex.captures(line) { + debug!("Matched joining event: '{:?}'", captures); + match extract_player_details(&captures) { + Ok((name, id)) => { + info!("Player '{}' with ID '{}' is joining", name, id); + PlayerList::joined_event(id, name); + } + Err(e) => error!("Failed to process joining event line '{}': {}", line, e), + } + } + + // Handle player leaving event + if let Some(captures) = left_regex.captures(line) { + debug!("Matched leaving event: '{:?}'", captures); + match extract_player_id(captures.get(1).map(|m| m.as_str())) { + Ok(id) => { + info!("Player with ID '{}' is leaving", id); + PlayerList::left_event(id); + } + Err(e) => error!("Failed to process leaving event line '{}': {}", line, e), + } + } +} +/// Extracts the player name and ID from regex captures for a joining event. +fn extract_player_details(captures: ®ex::Captures) -> Result<(String, u64), String> { + debug!("Extracting player details from captures: '{:?}'", captures); + let name = captures + .get(1) + .ok_or("Missing player name")? + .as_str() + .to_string(); + let id_str = captures.get(2).ok_or("Missing player ID")?.as_str(); + match extract_player_id(Some(id_str)) { + Ok(id) => Ok((name, id)), + Err(e) => Err(format!("Failed to parse player ID: {}", e)), + } +} -pub fn player_joined(message: &str) { - let re = Regex::new(r"(.*?)").unwrap(); - if let Some(captures) = re.captures(message) { - let name = captures.get(1).map_or("", |m| m.as_str()); - NotificationEvent::Player(Joined).send_notification(Some(format!("Player {name} has joined the server!"))); +/// Extracts the player ID from a string, optionally splitting it if it contains a colon. +fn extract_player_id(id_str: Option<&str>) -> Result { + debug!("Extracting player ID from string: '{:?}'", id_str); + match id_str { + Some(id) => id + .split(':') + .next() + .ok_or_else(|| "ID split failed: Missing ':' separator".to_string())? + .parse::() + .map_err(|e: ParseIntError| format!("ID parsing failed: {}", e)), + None => Err("Player ID not found".to_string()), } } -pub fn player_left(message: &str) { - let re = Regex::new(r"(.*?)").unwrap(); +#[cfg(test)] +mod tests { + use super::*; + use chrono::Utc; + use mockall::automock; - if let Some(captures) = re.captures(message) { - let name = captures.get(1).map_or("", |m| m.as_str()); - NotificationEvent::Player(Left).send_notification(Some(format!("Player {name} has left the server!"))); + #[automock] + pub trait NotificationEventTrait { + #[allow(dead_code)] + fn send_notification(&self, message: Option); } -} \ No newline at end of file + + #[test] + fn test_update_or_push() { + let mut player_list = PlayerList { players: vec![] }; + let player = Player { + id: 1, + name: "Player1".to_string(), + last_seen: Utc::now().timestamp(), + }; + + player_list.update_or_push(player.clone()); + assert_eq!(player_list.players.len(), 1); + assert_eq!(player_list.players[0].id, player.id); + assert_eq!(player_list.players[0].name, player.name); + + let updated_player = Player { + id: 1, + name: "Player1".to_string(), + last_seen: Utc::now().timestamp() + 100, + }; + player_list.update_or_push(updated_player.clone()); + assert_eq!(player_list.players.len(), 1); + assert_eq!(player_list.players[0].last_seen, updated_player.last_seen); + } + + #[test] + fn test_joined_event() { + let id = 1; + let name = "Player1".to_string(); + + PlayerList::joined_event(id, name); + } + + #[test] + fn test_left_event() { + let id = 1; + let player = Player { + id, + name: "Player1".to_string(), + last_seen: Utc::now().timestamp(), + }; + + let player_list = PlayerList { + players: vec![player], + }; + player_list.save(); + + PlayerList::left_event(id); + } + + #[test] + fn test_player_default() { + let player = Player::default(); + assert_eq!(player.id, 0); + assert_eq!(player.name, "Unknown"); + } + + #[test] + fn test_player_list_default() { + let player_list = PlayerList::default(); + assert_eq!(player_list.players.len(), 0); + } + + #[test] + fn test_player_list_save() { + let player_list = PlayerList { + players: vec![Player::default()], + }; + let result = player_list.save(); + assert!(result); + } +} diff --git a/src/odin/log_filters/probes.rs b/src/odin/log_filters/probes.rs new file mode 100644 index 00000000..30870e7d --- /dev/null +++ b/src/odin/log_filters/probes.rs @@ -0,0 +1,14 @@ +use crate::notifications::enums::{ + event_status::EventStatus, notification_event::NotificationEvent, +}; +use log::debug; + +pub fn handle_launch_probes(line: &str) { + if line.contains("Game server connected") { + debug!("Detected 'Game server connected'. Sending Start notification."); + NotificationEvent::Start(EventStatus::Successful).send_notification(None); + } else if line.contains("Steam manager on destroy") { + debug!("Detected 'Steam manager on destroy'. Sending Stop notification."); + NotificationEvent::Stop(EventStatus::Successful).send_notification(None); + } +} diff --git a/src/odin/main.rs b/src/odin/main.rs index 42d43dbd..aa165d1a 100644 --- a/src/odin/main.rs +++ b/src/odin/main.rs @@ -16,6 +16,7 @@ mod constants; mod errors; mod executable; mod files; +pub mod log_filters; mod logger; mod messages; mod mods; @@ -24,7 +25,6 @@ pub mod server; mod steamcmd; pub mod traits; pub mod utils; -mod log_filters; #[tokio::main] async fn main() { diff --git a/src/odin/utils/environment.rs b/src/odin/utils/environment.rs index 29b5c6c0..ca5c8920 100644 --- a/src/odin/utils/environment.rs +++ b/src/odin/utils/environment.rs @@ -1,5 +1,5 @@ -use cached::proc_macro::cached; use crate::utils::parse_truthy::parse_truthy; +use cached::proc_macro::cached; use std::env; pub fn fetch_var(name: &str, default: &str) -> String { @@ -11,9 +11,7 @@ pub fn fetch_var(name: &str, default: &str) -> String { value } } - Err(_) => { - String::from(default) - } + Err(_) => String::from(default), } } diff --git a/src/scripts/entrypoint.sh b/src/scripts/entrypoint.sh index 114e4d71..5c6cb990 100644 --- a/src/scripts/entrypoint.sh +++ b/src/scripts/entrypoint.sh @@ -98,6 +98,8 @@ setup_cron_env() { "BEPINEX_DOWNLOAD_URL" "BEPINEX_FULL_RELEASES_URL" "PLAYER_EVENT_NOTIFICATIONS" + "WEBHOOK_STATUS_JOINED" + "WEBHOOK_STATUS_LEFT" "BETA_BRANCH" "BETA_BRANCH_PASSWORD" "HTTP_PORT"