From 5b997a81272e66edc594d07af60e220ceeeb7ff9 Mon Sep 17 00:00:00 2001 From: Shayne Hartford Date: Wed, 13 Nov 2024 22:45:36 -0500 Subject: [PATCH] 0.7.0 - DiscordTrackerPlugin --- Cargo.lock | 242 +++++++++++------- Cargo.toml | 5 +- README.md | 8 +- rust-toolchain.toml | 4 +- .../commands/handlers/discord.rs | 29 ++- src/commands/handlers/minecraft.rs | 139 ++++++++++ src/commands/handlers/mod.rs | 28 ++ .../commands/handlers/prelude.rs | 0 src/commands/mod.rs | 69 +++++ src/{plugins => }/commands/pearl.rs | 42 ++- src/{plugins => }/commands/playtime.rs | 38 +-- src/{plugins => }/commands/prelude.rs | 0 src/{plugins => }/commands/seen.rs | 23 +- src/{plugins => }/commands/whitelist.rs | 61 +++-- src/{ncr.rs => encryption.rs} | 13 +- src/lib.rs | 61 +++-- src/main.rs | 7 +- src/plugins/auto_pearl.rs | 44 ++-- src/plugins/commands/handlers/minecraft.rs | 160 ------------ src/plugins/commands/handlers/mod.rs | 4 - src/plugins/commands/mod.rs | 79 ------ src/plugins/discord_tracker.rs | 180 +++++++++++++ src/plugins/mod.rs | 4 +- src/plugins/pearl_tracker.rs | 43 ++-- src/plugins/prelude.rs | 2 +- src/settings.rs | 39 +-- 26 files changed, 778 insertions(+), 546 deletions(-) rename src/{plugins => }/commands/handlers/discord.rs (78%) create mode 100644 src/commands/handlers/minecraft.rs create mode 100644 src/commands/handlers/mod.rs rename src/{plugins => }/commands/handlers/prelude.rs (100%) create mode 100644 src/commands/mod.rs rename src/{plugins => }/commands/pearl.rs (82%) rename src/{plugins => }/commands/playtime.rs (76%) rename src/{plugins => }/commands/prelude.rs (100%) rename src/{plugins => }/commands/seen.rs (87%) rename src/{plugins => }/commands/whitelist.rs (75%) rename src/{ncr.rs => encryption.rs} (96%) delete mode 100644 src/plugins/commands/handlers/minecraft.rs delete mode 100644 src/plugins/commands/handlers/mod.rs delete mode 100644 src/plugins/commands/mod.rs create mode 100644 src/plugins/discord_tracker.rs diff --git a/Cargo.lock b/Cargo.lock index 57067a6..398493a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -76,9 +76,9 @@ dependencies = [ [[package]] name = "allocator-api2" -version = "0.2.18" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" +checksum = "45862d1c77f2228b9e10bc609d5bc203d86ebc9b87ad8d5d5167a6c9abf739d9" [[package]] name = "android-tzdata" @@ -183,8 +183,8 @@ checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "azalea" -version = "0.10.1" -source = "git+https://github.com/Shays-Forks/azalea.git#ca2b7fbf4c9d8404c0b0aaac07cb695a22f9c4e8" +version = "0.10.3+mc1.21.1" +source = "git+https://github.com/Shays-Forks/azalea.git#aa4ef187b446fb1bef1ca1cbfffd95a60ff17d9f" dependencies = [ "anyhow", "async-trait", @@ -214,7 +214,8 @@ dependencies = [ "parking_lot", "priority-queue", "rustc-hash 2.0.0", - "thiserror 1.0.68", + "serde", + "thiserror 1.0.69", "tokio", "tracing", "uuid", @@ -222,8 +223,8 @@ dependencies = [ [[package]] name = "azalea-auth" -version = "0.10.1" -source = "git+https://github.com/Shays-Forks/azalea.git#ca2b7fbf4c9d8404c0b0aaac07cb695a22f9c4e8" +version = "0.10.3+mc1.21.1" +source = "git+https://github.com/Shays-Forks/azalea.git#aa4ef187b446fb1bef1ca1cbfffd95a60ff17d9f" dependencies = [ "azalea-buf", "azalea-crypto", @@ -236,7 +237,7 @@ dependencies = [ "rsa", "serde", "serde_json", - "thiserror 1.0.68", + "thiserror 1.0.69", "tokio", "tracing", "uuid", @@ -244,8 +245,8 @@ dependencies = [ [[package]] name = "azalea-block" -version = "0.10.2" -source = "git+https://github.com/Shays-Forks/azalea.git#ca2b7fbf4c9d8404c0b0aaac07cb695a22f9c4e8" +version = "0.10.3+mc1.21.1" +source = "git+https://github.com/Shays-Forks/azalea.git#aa4ef187b446fb1bef1ca1cbfffd95a60ff17d9f" dependencies = [ "azalea-block-macros", "azalea-buf", @@ -254,8 +255,8 @@ dependencies = [ [[package]] name = "azalea-block-macros" -version = "0.10.2" -source = "git+https://github.com/Shays-Forks/azalea.git#ca2b7fbf4c9d8404c0b0aaac07cb695a22f9c4e8" +version = "0.10.3+mc1.21.1" +source = "git+https://github.com/Shays-Forks/azalea.git#aa4ef187b446fb1bef1ca1cbfffd95a60ff17d9f" dependencies = [ "proc-macro2", "quote", @@ -264,8 +265,8 @@ dependencies = [ [[package]] name = "azalea-brigadier" -version = "0.10.2" -source = "git+https://github.com/Shays-Forks/azalea.git#ca2b7fbf4c9d8404c0b0aaac07cb695a22f9c4e8" +version = "0.10.3+mc1.21.1" +source = "git+https://github.com/Shays-Forks/azalea.git#aa4ef187b446fb1bef1ca1cbfffd95a60ff17d9f" dependencies = [ "azalea-buf", "azalea-chat", @@ -274,22 +275,22 @@ dependencies = [ [[package]] name = "azalea-buf" -version = "0.10.2" -source = "git+https://github.com/Shays-Forks/azalea.git#ca2b7fbf4c9d8404c0b0aaac07cb695a22f9c4e8" +version = "0.10.3+mc1.21.1" +source = "git+https://github.com/Shays-Forks/azalea.git#aa4ef187b446fb1bef1ca1cbfffd95a60ff17d9f" dependencies = [ "azalea-buf-macros", "byteorder", "serde_json", "simdnbt", - "thiserror 1.0.68", + "thiserror 1.0.69", "tracing", "uuid", ] [[package]] name = "azalea-buf-macros" -version = "0.10.0" -source = "git+https://github.com/Shays-Forks/azalea.git#ca2b7fbf4c9d8404c0b0aaac07cb695a22f9c4e8" +version = "0.10.3+mc1.21.1" +source = "git+https://github.com/Shays-Forks/azalea.git#aa4ef187b446fb1bef1ca1cbfffd95a60ff17d9f" dependencies = [ "proc-macro2", "quote", @@ -298,8 +299,8 @@ dependencies = [ [[package]] name = "azalea-chat" -version = "0.10.2" -source = "git+https://github.com/Shays-Forks/azalea.git#ca2b7fbf4c9d8404c0b0aaac07cb695a22f9c4e8" +version = "0.10.3+mc1.21.1" +source = "git+https://github.com/Shays-Forks/azalea.git#aa4ef187b446fb1bef1ca1cbfffd95a60ff17d9f" dependencies = [ "azalea-buf", "azalea-language", @@ -313,8 +314,8 @@ dependencies = [ [[package]] name = "azalea-client" -version = "0.10.2" -source = "git+https://github.com/Shays-Forks/azalea.git#ca2b7fbf4c9d8404c0b0aaac07cb695a22f9c4e8" +version = "0.10.3+mc1.21.1" +source = "git+https://github.com/Shays-Forks/azalea.git#aa4ef187b446fb1bef1ca1cbfffd95a60ff17d9f" dependencies = [ "anyhow", "async-trait", @@ -346,7 +347,7 @@ dependencies = [ "serde", "serde_json", "simdnbt", - "thiserror 1.0.68", + "thiserror 1.0.69", "tokio", "tracing", "uuid", @@ -354,8 +355,8 @@ dependencies = [ [[package]] name = "azalea-core" -version = "0.10.2" -source = "git+https://github.com/Shays-Forks/azalea.git#ca2b7fbf4c9d8404c0b0aaac07cb695a22f9c4e8" +version = "0.10.3+mc1.21.1" +source = "git+https://github.com/Shays-Forks/azalea.git#aa4ef187b446fb1bef1ca1cbfffd95a60ff17d9f" dependencies = [ "azalea-buf", "azalea-registry", @@ -371,8 +372,8 @@ dependencies = [ [[package]] name = "azalea-crypto" -version = "0.10.0" -source = "git+https://github.com/Shays-Forks/azalea.git#ca2b7fbf4c9d8404c0b0aaac07cb695a22f9c4e8" +version = "0.10.3+mc1.21.1" +source = "git+https://github.com/Shays-Forks/azalea.git#aa4ef187b446fb1bef1ca1cbfffd95a60ff17d9f" dependencies = [ "aes", "azalea-buf", @@ -388,8 +389,8 @@ dependencies = [ [[package]] name = "azalea-entity" -version = "0.10.2" -source = "git+https://github.com/Shays-Forks/azalea.git#ca2b7fbf4c9d8404c0b0aaac07cb695a22f9c4e8" +version = "0.10.3+mc1.21.1" +source = "git+https://github.com/Shays-Forks/azalea.git#aa4ef187b446fb1bef1ca1cbfffd95a60ff17d9f" dependencies = [ "azalea-block", "azalea-buf", @@ -405,15 +406,15 @@ dependencies = [ "nohash-hasher", "parking_lot", "simdnbt", - "thiserror 1.0.68", + "thiserror 1.0.69", "tracing", "uuid", ] [[package]] name = "azalea-inventory" -version = "0.10.2" -source = "git+https://github.com/Shays-Forks/azalea.git#ca2b7fbf4c9d8404c0b0aaac07cb695a22f9c4e8" +version = "0.10.3+mc1.21.1" +source = "git+https://github.com/Shays-Forks/azalea.git#aa4ef187b446fb1bef1ca1cbfffd95a60ff17d9f" dependencies = [ "azalea-buf", "azalea-chat", @@ -426,8 +427,8 @@ dependencies = [ [[package]] name = "azalea-inventory-macros" -version = "0.10.0" -source = "git+https://github.com/Shays-Forks/azalea.git#ca2b7fbf4c9d8404c0b0aaac07cb695a22f9c4e8" +version = "0.10.3+mc1.21.1" +source = "git+https://github.com/Shays-Forks/azalea.git#aa4ef187b446fb1bef1ca1cbfffd95a60ff17d9f" dependencies = [ "proc-macro2", "quote", @@ -436,8 +437,8 @@ dependencies = [ [[package]] name = "azalea-language" -version = "0.10.0" -source = "git+https://github.com/Shays-Forks/azalea.git#ca2b7fbf4c9d8404c0b0aaac07cb695a22f9c4e8" +version = "0.10.3+mc1.21.1" +source = "git+https://github.com/Shays-Forks/azalea.git#aa4ef187b446fb1bef1ca1cbfffd95a60ff17d9f" dependencies = [ "once_cell", "serde", @@ -446,8 +447,8 @@ dependencies = [ [[package]] name = "azalea-physics" -version = "0.10.2" -source = "git+https://github.com/Shays-Forks/azalea.git#ca2b7fbf4c9d8404c0b0aaac07cb695a22f9c4e8" +version = "0.10.3+mc1.21.1" +source = "git+https://github.com/Shays-Forks/azalea.git#aa4ef187b446fb1bef1ca1cbfffd95a60ff17d9f" dependencies = [ "azalea-block", "azalea-core", @@ -466,8 +467,8 @@ dependencies = [ [[package]] name = "azalea-protocol" -version = "0.10.2" -source = "git+https://github.com/Shays-Forks/azalea.git#ca2b7fbf4c9d8404c0b0aaac07cb695a22f9c4e8" +version = "0.10.3+mc1.21.1" +source = "git+https://github.com/Shays-Forks/azalea.git#aa4ef187b446fb1bef1ca1cbfffd95a60ff17d9f" dependencies = [ "async-recursion", "azalea-auth", @@ -494,7 +495,7 @@ dependencies = [ "serde_json", "simdnbt", "socks5-impl", - "thiserror 1.0.68", + "thiserror 1.0.69", "tokio", "tokio-util", "tracing", @@ -504,8 +505,8 @@ dependencies = [ [[package]] name = "azalea-protocol-macros" -version = "0.10.0" -source = "git+https://github.com/Shays-Forks/azalea.git#ca2b7fbf4c9d8404c0b0aaac07cb695a22f9c4e8" +version = "0.10.3+mc1.21.1" +source = "git+https://github.com/Shays-Forks/azalea.git#aa4ef187b446fb1bef1ca1cbfffd95a60ff17d9f" dependencies = [ "proc-macro2", "quote", @@ -514,8 +515,8 @@ dependencies = [ [[package]] name = "azalea-registry" -version = "0.10.2" -source = "git+https://github.com/Shays-Forks/azalea.git#ca2b7fbf4c9d8404c0b0aaac07cb695a22f9c4e8" +version = "0.10.3+mc1.21.1" +source = "git+https://github.com/Shays-Forks/azalea.git#aa4ef187b446fb1bef1ca1cbfffd95a60ff17d9f" dependencies = [ "azalea-buf", "azalea-registry-macros", @@ -525,8 +526,8 @@ dependencies = [ [[package]] name = "azalea-registry-macros" -version = "0.10.0" -source = "git+https://github.com/Shays-Forks/azalea.git#ca2b7fbf4c9d8404c0b0aaac07cb695a22f9c4e8" +version = "0.10.3+mc1.21.1" +source = "git+https://github.com/Shays-Forks/azalea.git#aa4ef187b446fb1bef1ca1cbfffd95a60ff17d9f" dependencies = [ "proc-macro2", "quote", @@ -535,8 +536,8 @@ dependencies = [ [[package]] name = "azalea-world" -version = "0.10.2" -source = "git+https://github.com/Shays-Forks/azalea.git#ca2b7fbf4c9d8404c0b0aaac07cb695a22f9c4e8" +version = "0.10.3+mc1.21.1" +source = "git+https://github.com/Shays-Forks/azalea.git#aa4ef187b446fb1bef1ca1cbfffd95a60ff17d9f" dependencies = [ "azalea-block", "azalea-buf", @@ -551,7 +552,7 @@ dependencies = [ "serde", "serde_json", "simdnbt", - "thiserror 1.0.68", + "thiserror 1.0.69", "tracing", "uuid", ] @@ -650,7 +651,7 @@ dependencies = [ "fixedbitset", "rustc-hash 1.1.0", "serde", - "thiserror 1.0.68", + "thiserror 1.0.69", "thread_local", ] @@ -726,7 +727,7 @@ dependencies = [ "glam", "serde", "smol_str", - "thiserror 1.0.68", + "thiserror 1.0.69", ] [[package]] @@ -767,7 +768,7 @@ dependencies = [ "bevy_reflect", "bevy_utils", "crossbeam-channel", - "thiserror 1.0.68", + "thiserror 1.0.69", ] [[package]] @@ -783,10 +784,10 @@ dependencies = [ "nonmax", "petgraph", "smallvec", - "thiserror 1.0.68", + "thiserror 1.0.69", "tracing", "uuid", - "web-time", + "web-time 0.2.4", ] [[package]] @@ -893,9 +894,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.1.37" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40545c26d092346d8a8dab71ee48e7685a7a9cba76e634790c215b41a4a7b4cf" +checksum = "1aeb932158bd710538c73702db6945cb68a8fb08c519e6e12706b94263b36db8" dependencies = [ "shlex", ] @@ -954,9 +955,9 @@ dependencies = [ [[package]] name = "command_attr" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88da8d7e9fe6f30d8e3fcf72d0f84102b49de70fece952633e8439e89bdc7631" +checksum = "6fcc89439e1bb4e19050a9586a767781a3060000d2f3296fd2a40597ad9421c5" dependencies = [ "proc-macro2", "quote", @@ -1021,9 +1022,9 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "cpufeatures" -version = "0.2.14" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0" +checksum = "0ca741a962e1b0bff6d724a1a0958b686406e853bb14061f218562e1896f95e6" dependencies = [ "libc", ] @@ -1157,7 +1158,7 @@ dependencies = [ "derive-macros", "duplicate", "serde_yaml", - "thiserror 1.0.68", + "thiserror 1.0.69", "toml", ] @@ -2551,9 +2552,9 @@ dependencies = [ [[package]] name = "quinn" -version = "0.11.5" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c7c5fdde3cdae7203427dc4f0a68fe0ed09833edc525a03456b153b79828684" +checksum = "62e96808277ec6f97351a2380e6c25114bc9e67037775464979f3037c92d05ef" dependencies = [ "bytes", "pin-project-lite", @@ -2562,26 +2563,29 @@ dependencies = [ "rustc-hash 2.0.0", "rustls 0.23.16", "socket2", - "thiserror 1.0.68", + "thiserror 2.0.3", "tokio", "tracing", ] [[package]] name = "quinn-proto" -version = "0.11.8" +version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fadfaed2cd7f389d0161bb73eeb07b7b78f8691047a6f3e73caaeae55310a4a6" +checksum = "a2fe5ef3495d7d2e377ff17b1a8ce2ee2ec2a18cde8b6ad6619d65d0701c135d" dependencies = [ "bytes", + "getrandom", "rand", "ring", "rustc-hash 2.0.0", "rustls 0.23.16", + "rustls-pki-types", "slab", - "thiserror 1.0.68", + "thiserror 2.0.3", "tinyvec", "tracing", + "web-time 1.1.0", ] [[package]] @@ -2654,7 +2658,7 @@ checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.8", + "regex-automata 0.4.9", "regex-syntax 0.8.5", ] @@ -2669,9 +2673,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.8" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", @@ -2852,9 +2856,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.39" +version = "0.38.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "375116bee2be9ed569afe2154ea6a99dfdffd257f533f187498c2a8f5feaf4ee" +checksum = "99e4ea3e1cdc4b559b8e5650f9c8e5998e3e5c1343b4eaf034565f32318d63c0" dependencies = [ "bitflags 2.6.0", "errno", @@ -2927,6 +2931,9 @@ name = "rustls-pki-types" version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "16f1201b3c9a7ee8039bcadc17b7e605e2945b27eee7631788c1bd2b0643674b" +dependencies = [ + "web-time 1.1.0", +] [[package]] name = "rustls-webpki" @@ -2949,6 +2956,12 @@ dependencies = [ "untrusted", ] +[[package]] +name = "rustversion" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248" + [[package]] name = "ryu" version = "1.0.18" @@ -3001,9 +3014,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.214" +version = "1.0.215" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f55c3193aca71c12ad7890f1785d2b73e1b9f63a0bbc353c08ef26fe03fc56b5" +checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" dependencies = [ "serde_derive", ] @@ -3019,9 +3032,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.214" +version = "1.0.215" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de523f781f095e28fa605cdce0f8307e451cc0fd14e2eb4cd2e98a355b147766" +checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" dependencies = [ "proc-macro2", "quote", @@ -3106,9 +3119,9 @@ dependencies = [ [[package]] name = "serenity" -version = "0.12.2" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "880a04106592d0a8f5bdacb1d935889bfbccb4a14f7074984d9cd857235d34ac" +checksum = "e8f0088eeaf12d92d0a7b6cbbde5c14a2ca9016f51f71c4b54ebcf66c69dc472" dependencies = [ "arrayvec", "async-trait", @@ -3185,7 +3198,7 @@ dependencies = [ [[package]] name = "shaysbot" -version = "0.6.5" +version = "0.7.0" dependencies = [ "anyhow", "azalea", @@ -3196,12 +3209,13 @@ dependencies = [ "lazy-regex", "ncr", "num-traits", - "parking_lot", + "semver", "serde", "serde_with", "serenity", "smart-default", "structstruck", + "strum", "terminal-link", "tokio", "tracing", @@ -3255,7 +3269,7 @@ dependencies = [ "flate2", "simd_cesu8", "simdnbt-derive", - "thiserror 1.0.68", + "thiserror 1.0.69", "valence_nbt", ] @@ -3285,7 +3299,7 @@ dependencies = [ "chrono", "num-bigint", "num-traits", - "thiserror 1.0.68", + "thiserror 1.0.69", ] [[package]] @@ -3369,7 +3383,7 @@ dependencies = [ "bytes", "percent-encoding", "serde", - "thiserror 2.0.0", + "thiserror 2.0.3", "tokio", ] @@ -3422,6 +3436,28 @@ dependencies = [ "venial", ] +[[package]] +name = "strum" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.87", +] + [[package]] name = "subtle" version = "2.6.1" @@ -3524,27 +3560,27 @@ checksum = "253bcead4f3aa96243b0f8fa46f9010e87ca23bd5d0c723d474ff1d2417bbdf8" [[package]] name = "thiserror" -version = "1.0.68" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02dd99dc800bbb97186339685293e1cc5d9df1f8fae2d0aecd9ff1c77efea892" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ - "thiserror-impl 1.0.68", + "thiserror-impl 1.0.69", ] [[package]] name = "thiserror" -version = "2.0.0" +version = "2.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15291287e9bff1bc6f9ff3409ed9af665bec7a5fc8ac079ea96be07bca0e2668" +checksum = "c006c85c7651b3cf2ada4584faa36773bd07bac24acfb39f3c431b36d7e667aa" dependencies = [ - "thiserror-impl 2.0.0", + "thiserror-impl 2.0.3", ] [[package]] name = "thiserror-impl" -version = "1.0.68" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7c61ec9a6f64d2793d8a45faba21efbe3ced62a886d44c36a009b2b519b4c7e" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", @@ -3553,9 +3589,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.0" +version = "2.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22efd00f33f93fa62848a7cab956c3d38c8d43095efda1decfc2b3a5dc0b8972" +checksum = "f077553d607adc1caf65430528a576c757a71ed73944b66ebb58ef2bbd243568" dependencies = [ "proc-macro2", "quote", @@ -3887,7 +3923,7 @@ dependencies = [ "once_cell", "rand", "smallvec", - "thiserror 1.0.68", + "thiserror 1.0.69", "tinyvec", "tokio", "tracing", @@ -3907,7 +3943,7 @@ dependencies = [ "parking_lot", "rand", "smallvec", - "thiserror 1.0.68", + "thiserror 1.0.69", "tokio", "tracing", "trust-dns-proto", @@ -3935,7 +3971,7 @@ dependencies = [ "rustls 0.22.4", "rustls-pki-types", "sha1", - "thiserror 1.0.68", + "thiserror 1.0.69", "url", "utf-8", ] @@ -4270,6 +4306,16 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "webpki-roots" version = "0.25.4" diff --git a/Cargo.toml b/Cargo.toml index a3ef7d2..a20027d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "shaysbot" -version = "0.6.5" +version = "0.7.0" authors = ["Shayne Hartford "] edition = "2021" description = "My personal Minecraft bot using Azalea" @@ -23,12 +23,13 @@ lazy-regex = "3" #ncr = { path = "../ncr-rs", features = ["cfb8", "ecb", "gcm"] } ncr = { git = "https://github.com/Shays-Forks/ncr-rs.git", features = ["cfb8", "ecb", "gcm"] } num-traits = "0.2" -parking_lot = "0.12" +semver = "1" serde = "1" serde_with = "3" serenity = "0.12" smart-default = "0.7" structstruck = "0.4" +strum = { version = "0.26", features = ["derive"] } terminal-link = "0.1" tokio = { version = "1", features = ["full"] } tracing = "0.1" diff --git a/README.md b/README.md index 9a4d183..d6b5bfa 100644 --- a/README.md +++ b/README.md @@ -23,10 +23,12 @@ No Chat Reports (NCR) encryption to ensure secure and private communication. - [**AutoLook**](src/plugins/auto_look.rs) - Automatically looks at specific targets - [**AutoPearl**](src/plugins/auto_pearl.rs) - Handles automatic ender pearl throwing - [**AutoTotem**](src/plugins/auto_totem.rs) - Automatically equips totems for survival +- [**DiscordTracker**](src/plugins/pearl_tracker.rs) - Tracks visual range events like players and shulkers - [**PearlTracker**](src/plugins/pearl_tracker.rs) - Tracks and manages ender pearl cooldowns and usage ### Commands -- [**Pearl**](src/plugins/commands/pearl.rs) - Manages ender pearl-related commands and tracking -- [**Playtime**](src/plugins/commands/playtime.rs) - Tracks and displays player playtime statistics (2b2t.vc) -- [**Seen**](src/plugins/commands/seen.rs) - Shows when players were last seen online (2b2t.vc) +- [**Pearl**](src/commands/pearl.rs) - Manages ender pearl-related commands and tracking +- [**Playtime**](src/commands/playtime.rs) - Tracks and displays player playtime statistics (2b2t.vc) +- [**Seen**](src/commands/seen.rs) - Shows when players were last seen online (2b2t.vc) +- [**Whitelist**](src/commands/whitelist.rs) - Whitelist and link Minecraft and Discord accounts diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 5d56faf..bb1f569 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,2 +1,2 @@ -[toolchain] -channel = "nightly" +[toolchain] # Failing to build +channel = "nightly-2024-11-11" diff --git a/src/plugins/commands/handlers/discord.rs b/src/commands/handlers/discord.rs similarity index 78% rename from src/plugins/commands/handlers/discord.rs rename to src/commands/handlers/discord.rs index 47587b2..50de811 100644 --- a/src/plugins/commands/handlers/discord.rs +++ b/src/commands/handlers/discord.rs @@ -1,3 +1,5 @@ +use std::collections::VecDeque; + use azalea::{ app::{App, Plugin, Update}, ecs::prelude::*, @@ -7,7 +9,7 @@ use bevy_discord::{bot::events::BMessage, http::DiscordHttpResource, runtime::to use serenity::json::json; use crate::{ - plugins::commands::{CommandEvent, CommandSender, CommandSource, Registry, WhisperEvent}, + commands::{CommandEvent, CommandSender, CommandSource, Commands, WhisperEvent}, settings::Settings, }; @@ -23,7 +25,6 @@ pub fn handle_message_event( mut command_events: EventWriter, mut message_events: EventReader, mut query: Query, With)>, - registry: Res, settings: Res, ) { for event in message_events.read() { @@ -32,13 +33,7 @@ pub fn handle_message_event( }; let message = event.new_message.clone(); - let Some((args, command)) = - registry.find_command(&message.content, &settings.command_prefix) - else { - continue; - }; - // Check if whitelist is enabled and if the user is whitelisted. if !settings.whitelist.is_empty() && !settings .whitelist @@ -51,8 +46,9 @@ pub fn handle_message_event( let user_id = message.author.id.to_string(); tokio_runtime().spawn(async move { let content = [ - String::from("[404] Your Discord account isn't linked to a Minecraft account."), - format!("Message the bot in-game '{prefix}whitelist link {user_id}' to link."), + String::from("[404] Your Discord and Minecraft accounts aren't linked,"), + format!("In Game: `{prefix}whitelist link {user_id}`"), + format!("Discord: `{prefix}whitelist link (auth.aristois.net)`"), ]; let map = json!({ @@ -67,10 +63,19 @@ pub fn handle_message_event( continue; } + let mut args = message.content.split(' ').collect::>(); + let Some(alias) = args.pop_front() else { + continue; /* Command Missing */ + }; + + let Some(command) = Commands::find(alias) else { + continue; /* Command Invalid */ + }; + command_events.send(CommandEvent { entity, - args, - command: *command, + args: args.into_iter().map(String::from).collect(), + command, source: CommandSource::Discord(message.channel_id), sender: CommandSender::Discord(message.author.id), }); diff --git a/src/commands/handlers/minecraft.rs b/src/commands/handlers/minecraft.rs new file mode 100644 index 0000000..d477625 --- /dev/null +++ b/src/commands/handlers/minecraft.rs @@ -0,0 +1,139 @@ +use std::collections::VecDeque; + +use azalea::{ + app::{App, Plugin, Update}, + chat::{handle_send_chat_event, ChatPacketKind, ChatReceivedEvent, SendChatKindEvent}, + ecs::prelude::*, + TabList, +}; +use ncr::AesKey; + +use crate::{ + commands::{ + handlers::Cooldown, + CommandEvent, + CommandSender, + CommandSource, + Commands, + WhisperEvent, + }, + encryption::{find_encryption, try_encrypt, KEY}, + settings::Settings, +}; + +pub struct MinecraftCommandsPlugin; + +impl Plugin for MinecraftCommandsPlugin { + fn build(&self, app: &mut App) { + app.insert_resource(Cooldown::default()) + .add_event::() + .add_event::() + .add_systems( + Update, + ( + handle_chat_received_event, + handle_minecraft_whisper_event.before(handle_send_chat_event), + ) + .chain(), + ); + } +} + +pub fn handle_chat_received_event( + mut events: EventReader, + mut command_events: EventWriter, + mut cooldown: ResMut, + query: Query<&TabList>, + settings: Res, +) { + let Ok(tab_list) = query.get_single() else { + return; + }; + + for event in events.read() { + let (username, content) = event.packet.split_sender_and_content(); + let (username, content) = if let Some(username) = username { + (username, content) /* Vanilla Server Format */ + } else if let Some((_whole, username, content)) = regex_captures!( + r"^(?:\[.+\] )?([a-zA-Z_0-9]{1,16}) (?:> )?(?:whispers: |-> )?(.+)$", + &content + ) { + (username.to_string(), content.to_string()) + } else { + continue; + }; + + let Some((uuid, _)) = tab_list.iter().find(|(_, i)| i.profile.name == username) else { + continue; + }; + + if !settings.whitelist.is_empty() && !settings.whitelist.contains_key(uuid) { + continue; + } + + let key = AesKey::decode_base64(&settings.encryption.key).unwrap_or_else(|_| KEY.clone()); + let (encryption, content) = find_encryption(&content, &key); + + let mut args = content.split(' ').collect::>(); + let Some(alias) = args.pop_front() else { + continue; /* Command Missing */ + }; + + let Some(command) = Commands::find(&alias.replace(&settings.command_prefix, "")) else { + continue; /* Command Invalid */ + }; + + if cooldown.check(&username, settings.command_cooldown) { + continue; /* Command Cooldown */ + } + + command_events.send(CommandEvent { + entity: event.entity, + args: args.into_iter().map(String::from).collect(), + command, + sender: CommandSender::Minecraft(*uuid), + source: CommandSource::Minecraft(encryption), + }); + } +} + +pub fn handle_minecraft_whisper_event( + mut chat_kind_events: EventWriter, + mut whisper_events: EventReader, + query: Query<&TabList>, + settings: Res, +) { + let Ok(tab_list) = query.get_single() else { + return; + }; + + if settings.disable_responses { + return; + } + + for mut event in whisper_events.read().cloned() { + #[rustfmt::skip] + let ( + CommandSource::Minecraft(type_encryption), + CommandSender::Minecraft(uuid) + ) = (event.source, event.sender) else { + continue; + }; + + let Some(username) = tab_list + .iter() + .find(|(_, info)| info.profile.uuid == uuid) + .map(|(_, info)| info.profile.name.clone()) + else { + continue; /* Player Offline */ + }; + + try_encrypt(&mut event.content, &settings.encryption, type_encryption); + + chat_kind_events.send(SendChatKindEvent { + entity: event.entity, + kind: ChatPacketKind::Command, + content: format!("w {username} {}", event.content), + }); + } +} diff --git a/src/commands/handlers/mod.rs b/src/commands/handlers/mod.rs new file mode 100644 index 0000000..f462eb4 --- /dev/null +++ b/src/commands/handlers/mod.rs @@ -0,0 +1,28 @@ +use std::{ + collections::HashMap, + time::{Duration, Instant}, +}; + +use azalea::{ecs::prelude::*, prelude::*}; + +pub mod prelude; + +mod discord; +mod minecraft; + +#[derive(Default, Resource)] +pub struct Cooldown(HashMap); + +impl Cooldown { + fn check(&mut self, sender: &str, duration: Duration) -> bool { + if let Some(instant) = self.0.get(sender) { + if instant.elapsed() < duration { + return true; + } + } else { + self.0.insert(sender.to_owned(), Instant::now()); + } + + false + } +} diff --git a/src/plugins/commands/handlers/prelude.rs b/src/commands/handlers/prelude.rs similarity index 100% rename from src/plugins/commands/handlers/prelude.rs rename to src/commands/handlers/prelude.rs diff --git a/src/commands/mod.rs b/src/commands/mod.rs new file mode 100644 index 0000000..1848c76 --- /dev/null +++ b/src/commands/mod.rs @@ -0,0 +1,69 @@ +pub mod handlers; +pub mod prelude; + +mod pearl; +mod playtime; +mod seen; +mod whitelist; + +use std::collections::VecDeque; + +use azalea::{ecs::prelude::*, prelude::*}; +use serenity::all::{ChannelId, UserId}; +use strum::IntoEnumIterator; +use uuid::Uuid; + +use crate::{commands::prelude::*, encryption::EncryptionType}; + +pub trait Command { + fn aliases(&self) -> Vec<&'static str>; +} + +/// Compile time checked list of commands +#[derive(Clone, Copy, Debug, Eq, PartialEq, EnumIter)] +pub enum Commands { + Pearl(PearlCommandPlugin), + Playtime(PlaytimeCommandPlugin), + Seen(SeenCommandPlugin), + Whitelist(WhitelistCommandPlugin), +} + +impl Commands { + fn find(alias: &str) -> Option { + Self::iter().find(|cmds| match cmds { + Self::Pearl(cmd) => cmd.aliases().contains(&alias), + Self::Playtime(cmd) => cmd.aliases().contains(&alias), + Self::Seen(cmd) => cmd.aliases().contains(&alias), + Self::Whitelist(cmd) => cmd.aliases().contains(&alias), + }) + } +} + +#[derive(Clone, Copy, Debug)] +pub enum CommandSender { + Discord(UserId), + Minecraft(Uuid), +} + +#[derive(Clone, Copy, Debug)] +pub enum CommandSource { + Discord(ChannelId), + Minecraft(Option), +} + +#[derive(Clone, Debug, Event)] +pub struct CommandEvent { + pub entity: Entity, + pub args: VecDeque, + pub command: Commands, + pub sender: CommandSender, + pub source: CommandSource, +} + +#[derive(Clone, Debug, Event)] +pub struct WhisperEvent { + pub entity: Entity, + pub content: String, + pub sender: CommandSender, + pub source: CommandSource, +} diff --git a/src/plugins/commands/pearl.rs b/src/commands/pearl.rs similarity index 82% rename from src/plugins/commands/pearl.rs rename to src/commands/pearl.rs index 53c3bf1..a2aed79 100644 --- a/src/plugins/commands/pearl.rs +++ b/src/commands/pearl.rs @@ -1,5 +1,5 @@ use azalea::{ - app::{App, Plugin, Startup, Update}, + app::{App, Plugin, Update}, ecs::prelude::*, entity::Position, BlockPos, @@ -8,17 +8,25 @@ use azalea::{ use handlers::prelude::*; use crate::{ - plugins::{commands::prelude::*, prelude::*}, + commands::{prelude::*, Commands}, + plugins::prelude::*, trapdoors::Trapdoors, Settings, }; /// Pearl Stasis Command +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] pub struct PearlCommandPlugin; +impl Command for PearlCommandPlugin { + fn aliases(&self) -> Vec<&'static str> { + vec!["pearl", "tp", "teleport", "warp", "home"] + } +} + impl Plugin for PearlCommandPlugin { fn build(&self, app: &mut App) { - app.add_systems(Startup, handle_pearl_register).add_systems( + app.add_systems( Update, handle_pearl_command_event .ambiguous_with_all() @@ -30,12 +38,6 @@ impl Plugin for PearlCommandPlugin { } } -pub fn handle_pearl_register(mut registry: ResMut) { - for alias in ["pearl", "tp", "teleport", "pull", "here", "home", "warp"] { - registry.register(alias, Command::Pearl); - } -} - pub fn handle_pearl_command_event( mut command_events: EventReader, mut pearl_events: EventWriter, @@ -45,9 +47,9 @@ pub fn handle_pearl_command_event( trapdoors: Res, ) { for mut event in command_events.read().cloned() { - if event.command != Command::Pearl { + let Commands::Pearl(_plugin) = event.command else { continue; - } + }; let Ok((tab_list, position)) = query.get(event.entity) else { continue; @@ -60,7 +62,7 @@ pub fn handle_pearl_command_event( content: String::new(), }; - let sender = match event.sender { + let uuid = match event.sender { CommandSender::Minecraft(username) => username, CommandSender::Discord(user_id) => { let Some(username) = event.args.pop_front() else { @@ -73,6 +75,8 @@ pub fn handle_pearl_command_event( .iter() .find(|(_, info)| info.profile.name == username) else { + whisper_event.content = format!("[404] {username} is not online"); + whisper_events.send(whisper_event); continue; }; @@ -94,25 +98,15 @@ pub fn handle_pearl_command_event( } } - username + uuid.clone() } }; - let Some(uuid) = tab_list - .iter() - .find(|(_, info)| info.profile.name == sender) - .map(|(uuid, _)| uuid) - else { - whisper_event.content = format!("[404] {sender} is not online"); - whisper_events.send(whisper_event); - continue; - }; - let Some(trapdoor) = trapdoors .0 .clone() .into_values() - .filter(|trapdoor| &trapdoor.owner_uuid == uuid) + .filter(|trapdoor| trapdoor.owner_uuid == uuid) .min_by_key(|trapdoor| { let shared_count = trapdoors .0 diff --git a/src/plugins/commands/playtime.rs b/src/commands/playtime.rs similarity index 76% rename from src/plugins/commands/playtime.rs rename to src/commands/playtime.rs index 313dfed..41d13e9 100644 --- a/src/plugins/commands/playtime.rs +++ b/src/commands/playtime.rs @@ -1,33 +1,35 @@ use std::time::Duration; use azalea::{ - app::{App, Plugin, Startup, Update}, + app::{App, Plugin, Update}, ecs::prelude::*, }; use handlers::prelude::*; use serde::Deserialize; -use crate::plugins::commands::prelude::*; +use crate::commands::{prelude::*, Commands}; /// 2B2T Playtime Command +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] pub struct PlaytimeCommandPlugin; -impl Plugin for PlaytimeCommandPlugin { - fn build(&self, app: &mut App) { - app.add_systems(Startup, handle_playtime_register) - .add_systems( - Update, - handle_playtime_command_event - .ambiguous_with_all() - .before(handle_discord_whisper_event) - .before(handle_minecraft_whisper_event) - .after(handle_chat_received_event), - ); +impl Command for PlaytimeCommandPlugin { + fn aliases(&self) -> Vec<&'static str> { + vec!["playtime"] } } -pub fn handle_playtime_register(mut registry: ResMut) { - registry.register("playtime", Command::Playtime); +impl Plugin for PlaytimeCommandPlugin { + fn build(&self, app: &mut App) { + app.add_systems( + Update, + handle_playtime_command_event + .ambiguous_with_all() + .before(handle_discord_whisper_event) + .before(handle_minecraft_whisper_event) + .after(handle_chat_received_event), + ); + } } pub fn handle_playtime_command_event( @@ -35,13 +37,13 @@ pub fn handle_playtime_command_event( mut whisper_events: EventWriter, ) { for event in command_events.read() { - if event.command != Command::Playtime { + let Commands::Seen(_plugin) = event.command else { continue; - } + }; let mut whisper_event = WhisperEvent { entity: event.entity, - source: event.source.clone(), + source: event.source, sender: event.sender.clone(), content: String::new(), }; diff --git a/src/plugins/commands/prelude.rs b/src/commands/prelude.rs similarity index 100% rename from src/plugins/commands/prelude.rs rename to src/commands/prelude.rs diff --git a/src/plugins/commands/seen.rs b/src/commands/seen.rs similarity index 87% rename from src/plugins/commands/seen.rs rename to src/commands/seen.rs index af86c6d..811ee84 100644 --- a/src/plugins/commands/seen.rs +++ b/src/commands/seen.rs @@ -1,21 +1,28 @@ use std::time::Duration; use azalea::{ - app::{App, Plugin, Startup, Update}, + app::{App, Plugin, Update}, ecs::prelude::*, }; use chrono::{DateTime, Utc}; use handlers::prelude::*; use serde::Deserialize; -use crate::plugins::commands::prelude::*; +use crate::commands::{prelude::*, Commands}; /// 2B2T Seen Command +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] pub struct SeenCommandPlugin; +impl Command for SeenCommandPlugin { + fn aliases(&self) -> Vec<&'static str> { + vec!["seen"] + } +} + impl Plugin for SeenCommandPlugin { fn build(&self, app: &mut App) { - app.add_systems(Startup, handle_seen_register).add_systems( + app.add_systems( Update, handle_seen_command_event .ambiguous_with_all() @@ -26,22 +33,18 @@ impl Plugin for SeenCommandPlugin { } } -pub fn handle_seen_register(mut registry: ResMut) { - registry.register("seen", Command::Seen); -} - pub fn handle_seen_command_event( mut command_events: EventReader, mut whisper_events: EventWriter, ) { for event in command_events.read() { - if event.command != Command::Seen { + let Commands::Seen(_plugin) = event.command else { continue; - } + }; let mut whisper_event = WhisperEvent { entity: event.entity, - source: event.source.clone(), + source: event.source, sender: event.sender.clone(), content: String::new(), }; diff --git a/src/plugins/commands/whitelist.rs b/src/commands/whitelist.rs similarity index 75% rename from src/plugins/commands/whitelist.rs rename to src/commands/whitelist.rs index 2b5792e..7d19b09 100644 --- a/src/plugins/commands/whitelist.rs +++ b/src/commands/whitelist.rs @@ -1,5 +1,5 @@ use azalea::{ - app::{App, Plugin, Startup, Update}, + app::{App, Plugin, Update}, ecs::prelude::*, PlayerInfo, TabList, @@ -9,27 +9,32 @@ use handlers::prelude::*; use serde::Deserialize; use uuid::Uuid; -use crate::{plugins::commands::prelude::*, Settings}; +use crate::{ + commands::{prelude::*, Commands}, + Settings, +}; /// Whitelist command +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] pub struct WhitelistCommandPlugin; -impl Plugin for WhitelistCommandPlugin { - fn build(&self, app: &mut App) { - app.add_systems(Startup, handle_whitelist_register) - .add_systems( - Update, - handle_whitelist_command_event - .ambiguous_with_all() - .before(handle_discord_whisper_event) - .before(handle_minecraft_whisper_event) - .after(handle_chat_received_event), - ); +impl Command for WhitelistCommandPlugin { + fn aliases(&self) -> Vec<&'static str> { + vec!["whitelist"] } } -pub fn handle_whitelist_register(mut registry: ResMut) { - registry.register("whitelist", Command::Whitelist); +impl Plugin for WhitelistCommandPlugin { + fn build(&self, app: &mut App) { + app.add_systems( + Update, + handle_whitelist_command_event + .ambiguous_with_all() + .before(handle_discord_whisper_event) + .before(handle_minecraft_whisper_event) + .after(handle_chat_received_event), + ); + } } /// # Panics @@ -41,9 +46,9 @@ pub fn handle_whitelist_command_event( query: Query<&TabList>, ) { for event in command_events.read() { - if event.command != Command::Whitelist { + let Commands::Whitelist(_plugin) = event.command else { continue; - } + }; let Ok(tab_list) = query.get_single() else { continue; @@ -52,23 +57,22 @@ pub fn handle_whitelist_command_event( let mut args = event.args.clone(); let mut whisper_event = WhisperEvent { entity: event.entity, - source: event.source.clone(), + source: event.source, sender: event.sender.clone(), content: String::new(), }; let Some(action) = args.pop_front() else { - whisper_event.content = - String::from("[400] Missing Action: 'add', 'remove', or 'link'"); + whisper_event.content = String::from("[400] Missing Action: 'add', 'remove', 'link'"); whisper_events.send(whisper_event); continue; }; let user = args.pop_front(); whisper_event.content = match action.as_ref() { - "add" => handle_add(&mut settings, tab_list, user), - "remove" => handle_remove(&mut settings, tab_list, user), - "link" => handle_link(&mut settings, tab_list, user, &event.sender), + "add" => handle_add(&mut settings, user, tab_list), + "remove" => handle_remove(&mut settings, user, tab_list), + "link" => handle_link(&mut settings, user, &event.sender), _ => String::from("[400] Invalid Action: 'add', 'remove', or 'link'"), }; @@ -76,7 +80,7 @@ pub fn handle_whitelist_command_event( } } -fn handle_add(settings: &mut ResMut, tab_list: &TabList, user: Option) -> String { +fn handle_add(settings: &mut ResMut, user: Option, tab_list: &TabList) -> String { let Some(player_name) = user else { return String::from("[400] Missing Minecraft player name"); }; @@ -97,8 +101,8 @@ fn handle_add(settings: &mut ResMut, tab_list: &TabList, user: Option< fn handle_remove( settings: &mut ResMut, - tab_list: &TabList, user: Option, + tab_list: &TabList, ) -> String { let Some(player_name) = user else { return String::from("[400] Missing Minecraft player name"); @@ -120,7 +124,6 @@ fn handle_remove( fn handle_link( settings: &mut ResMut, - tab_list: &TabList, user: Option, sender: &CommandSender, ) -> String { @@ -147,15 +150,11 @@ fn handle_link( String::from("[200] Successfully linked") } - CommandSender::Minecraft(sender) => { + CommandSender::Minecraft(uuid) => { let Some(user_id) = user else { return String::from("[400] Missing Discord user id"); }; - let Some((uuid, _info)) = try_find_player(tab_list, sender) else { - return String::from("[404] Sender not found"); - }; - settings.whitelist.insert(*uuid, Some(user_id)); settings.save().expect("Failed to save settings"); diff --git a/src/ncr.rs b/src/encryption.rs similarity index 96% rename from src/ncr.rs rename to src/encryption.rs index f6c35ba..57e1bd4 100644 --- a/src/ncr.rs +++ b/src/encryption.rs @@ -110,14 +110,13 @@ pub fn find_encryption(content: &str, key: &AesKey) -> (Option, (None, String::from(content)) } -#[must_use] pub fn try_encrypt( + content: &mut String, chat_encryption: &ChatEncryption, type_encryption: Option, - content: String, -) -> String { +) { if chat_encryption.mode == EncryptionMode::Never { - return content; + return; } let key = AesKey::decode_base64(&chat_encryption.key).unwrap_or_else(|_| KEY.clone()); @@ -125,13 +124,11 @@ pub fn try_encrypt( if let Some(encryption) = type_encryption { if let Ok(ciphertext) = encryption.encrypt(&plaintext, &key) { - return ciphertext; + *content = ciphertext; } } else if chat_encryption.mode == EncryptionMode::Always { if let Ok(ciphertext) = Cfb8Encryption(NewBase64rEncoding).encrypt(&plaintext, &key) { - return ciphertext; + *content = ciphertext; } } - - content } diff --git a/src/lib.rs b/src/lib.rs index eb28724..ba89b4d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,5 @@ #![feature(trivial_bounds)] -extern crate core; #[macro_use] extern crate lazy_regex; #[macro_use] @@ -8,15 +7,19 @@ extern crate serde_with; #[macro_use] extern crate structstruck; #[macro_use] +extern crate strum; +#[macro_use] extern crate tracing; -pub mod ncr; +pub mod commands; +pub mod encryption; pub mod plugins; pub mod settings; pub mod trapdoors; use std::ops::{AddAssign, RemAssign}; +use anyhow::{bail, Result}; use azalea::{ ecs::prelude::*, prelude::*, @@ -25,37 +28,49 @@ use azalea::{ }; use bevy_discord::bot::{DiscordBotConfig, DiscordBotPlugin}; use derive_config::{DeriveTomlConfig, DeriveYamlConfig}; -use handlers::prelude::*; use num_traits::{Bounded, One}; -use plugins::prelude::*; -use serenity::prelude::*; +use semver::Version; +use serenity::{all::ChannelId, prelude::*}; use url::Url; pub use crate::{ - plugins::ShaysPluginGroup, + commands::handlers::prelude::*, + plugins::{prelude::*, ShaysPluginGroup}, settings::Settings, trapdoors::{Trapdoor, Trapdoors}, }; pub const CARGO_PKG_VERSION: &str = env!("CARGO_PKG_VERSION"); pub const CARGO_PKG_HOMEPAGE: &str = env!("CARGO_PKG_HOMEPAGE"); +pub const CARGO_PKG_REPOSITORY: &str = env!("CARGO_PKG_REPOSITORY"); -/// # Check for updates using GitHub's latest release link redirect +/// # Get the remote version using GitHub's latest release link redirect /// /// # Errors /// Will return `Err` if `ureq::get` fails. -pub fn check_for_updates() -> anyhow::Result { +pub fn get_remote_version() -> Result { let response = ureq::get(CARGO_PKG_HOMEPAGE).call()?; if let Ok(parsed_url) = Url::parse(response.get_url()) { if let Some(segments) = parsed_url.path_segments() { if let Some(remote_version) = segments.last() { - return Ok(remote_version > CARGO_PKG_VERSION); + return Ok(remote_version.parse()?); } } } - Ok(false) + bail!("Failed to get the remote version") +} + +/// # Check for updates using GitHub's latest release link redirect +/// +/// # Errors +/// Will return `Err` if `ureq::get` fails. +pub fn check_for_updates() -> Result { + let local_version = CARGO_PKG_VERSION.parse()?; + let remote_version = get_remote_version()?; + + Ok(remote_version > local_version) } #[derive(Clone, Component, Default, Resource)] @@ -66,19 +81,20 @@ pub struct SwarmState; /// # Errors /// Will return `Err` if `ClientBuilder::start` fails. #[allow(clippy::future_not_send)] -pub async fn start() -> anyhow::Result<()> { +pub async fn start() -> Result<()> { let settings = Settings::load().unwrap_or_else(|error| { - eprintln!("Error loading settings: {error}"); + error!("Error loading settings: {error}"); Settings::default() }); let trapdoors = Trapdoors::load().unwrap_or_else(|error| { - eprintln!("Error loading trapdoors: {error}"); + error!("Error loading trapdoors: {error}"); Trapdoors::default() }); - let address = settings.server_address.clone(); let token = settings.discord_token.clone(); + let channel = settings.discord_channel; + let address = settings.server_address.clone(); let account = if settings.online_mode { Account::microsoft(&settings.account_username).await? } else { @@ -93,11 +109,18 @@ pub async fn start() -> anyhow::Result<()> { .add_plugins((ShaysPluginGroup, MinecraftCommandsPlugin)); if !token.is_empty() { + let intents = GatewayIntents::GUILD_MESSAGES | GatewayIntents::MESSAGE_CONTENT; let config = DiscordBotConfig::default() - .gateway_intents(GatewayIntents::GUILD_MESSAGES | GatewayIntents::MESSAGE_CONTENT) + .gateway_intents(intents) .token(token); - client = client.add_plugins((DiscordBotPlugin::new(config), DiscordCommandsPlugin)); + client = client + .add_plugins(DiscordBotPlugin::new(config)) + .add_plugins(DiscordCommandsPlugin); + + if channel != ChannelId::default() { + client = client.add_plugins(DiscordTrackerPlugin); + } } client.start(address.as_str()).await? @@ -105,11 +128,7 @@ pub async fn start() -> anyhow::Result<()> { /// # Errors /// Will return `Err` if `Swarm::add_with_opts` fails. -pub async fn swarm_handler( - mut swarm: Swarm, - event: SwarmEvent, - state: SwarmState, -) -> anyhow::Result<()> { +pub async fn swarm_handler(mut swarm: Swarm, event: SwarmEvent, state: SwarmState) -> Result<()> { match event { SwarmEvent::Chat(chat_packet) => info!("{}", chat_packet.message().to_ansi()), SwarmEvent::Disconnect(account, options) => { diff --git a/src/main.rs b/src/main.rs index 0de5a0a..9606ac5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,11 +1,12 @@ -use shaysbot::CARGO_PKG_HOMEPAGE; +use shaysbot::{CARGO_PKG_HOMEPAGE, CARGO_PKG_REPOSITORY}; use terminal_link::Link; #[tokio::main(flavor = "current_thread")] async fn main() -> anyhow::Result<()> { if shaysbot::check_for_updates()? { - let text = "An update is available"; - let link = Link::new(text, CARGO_PKG_HOMEPAGE); + let version = shaysbot::get_remote_version()?; + let text = format!("An update is available: {CARGO_PKG_REPOSITORY}/releases/tag/{version}"); + let link = Link::new(&text, CARGO_PKG_HOMEPAGE); println!("{link}"); } diff --git a/src/plugins/auto_pearl.rs b/src/plugins/auto_pearl.rs index 7f66345..dbe7565 100644 --- a/src/plugins/auto_pearl.rs +++ b/src/plugins/auto_pearl.rs @@ -10,7 +10,7 @@ use azalea::{ mining::MiningSet, packet_handling::game::{handle_send_packet_event, SendPacketEvent}, pathfinder::{ - goals::ReachBlockPosGoal, + goals::{RadiusGoal, ReachBlockPosGoal}, goto_listener, moves::default_move, GotoEvent, @@ -30,7 +30,7 @@ use azalea::{ }; use uuid::Uuid; -use crate::Settings; +use crate::{settings::IdleGoal, Settings}; /// Automatically pull stasis chamber pearls pub struct AutoPearlPlugin; @@ -111,11 +111,11 @@ pub fn handle_pearl_pull_event( mut pearl_pending_events: ResMut, mut pearl_pull_events: EventReader, mut send_packet_events: EventWriter, - mut query: Query<(&Pathfinder, &TabList, &InstanceHolder)>, + mut query: Query<(&Pathfinder, &TabList)>, settings: Res, ) { for event in pearl_pull_events.read() { - let Ok((pathfinder, tab_list, holder)) = query.get_mut(event.entity) else { + let Ok((pathfinder, tab_list)) = query.get_mut(event.entity) else { continue; }; @@ -129,7 +129,6 @@ pub fn handle_pearl_pull_event( continue; } - // Flip the trapdoor let packet = ServerboundGamePacket::UseItemOn(ServerboundUseItemOnPacket { hand: InteractionHand::MainHand, block_hit: BlockHit { @@ -150,30 +149,17 @@ pub fn handle_pearl_pull_event( packet, }); - // Return to the idle position - let pos = settings.idle_pos; - if pos.x == -0.0 && pos.y == -0.0 && pos.z == -0.0 { - continue; - }; - - #[allow(clippy::cast_possible_truncation)] - let pos = BlockPos { - x: pos.x as i32, - y: pos.y as i32, - z: pos.z as i32, - }; - - let goal = ReachBlockPosGoal { - chunk_storage: holder.instance.read().chunks.clone(), - pos, - }; - - goto_events.send(GotoEvent { - entity: event.entity, - goal: Arc::new(goal), - successors_fn: default_move, - allow_mining: false, - }); + if settings.idle != IdleGoal::default() { + goto_events.send(GotoEvent { + entity: event.entity, + allow_mining: false, + successors_fn: default_move, + goal: Arc::new(RadiusGoal { + pos: settings.idle.pos, + radius: settings.idle.radius + 1.0, + }), + }); + } } } diff --git a/src/plugins/commands/handlers/minecraft.rs b/src/plugins/commands/handlers/minecraft.rs deleted file mode 100644 index b0fbf2e..0000000 --- a/src/plugins/commands/handlers/minecraft.rs +++ /dev/null @@ -1,160 +0,0 @@ -use std::{ - collections::HashMap, - time::{Duration, Instant}, -}; - -use azalea::{ - app::{App, Plugin, Update}, - chat::{handle_send_chat_event, ChatPacketKind, ChatReceivedEvent, SendChatKindEvent}, - ecs::prelude::*, - prelude::*, - TabList, -}; -use ncr::AesKey; - -use crate::{ - ncr::{find_encryption, try_encrypt, KEY}, - plugins::commands::{CommandEvent, CommandSender, CommandSource, Registry, WhisperEvent}, - settings::Settings, -}; - -#[derive(Resource, Default)] -pub struct CommandCooldowns { - cooldowns: HashMap, -} - -impl CommandCooldowns { - fn check_cooldown(&self, player: &str, duration: Duration) -> bool { - if let Some(last_use) = self.cooldowns.get(player) { - if last_use.elapsed() < duration { - return false; - } - } - true - } - - fn update_cooldown(&mut self, player: String) { - self.cooldowns.insert(player, Instant::now()); - } - - fn cleanup_expired(&mut self, duration: Duration) { - self.cooldowns.retain(|_, time| time.elapsed() < duration); - } -} - -pub struct MinecraftCommandsPlugin; - -impl Plugin for MinecraftCommandsPlugin { - fn build(&self, app: &mut App) { - app.add_event::() - .add_event::() - .insert_resource(Registry::default()) - .insert_resource(CommandCooldowns::default()) - .add_systems( - Update, - ( - handle_chat_received_event, - handle_minecraft_whisper_event.before(handle_send_chat_event), - cleanup_cooldowns, - ) - .chain(), - ); - } -} - -fn cleanup_cooldowns(mut cooldowns: ResMut, settings: Res) { - let duration = Duration::from_secs(settings.command_cooldown); - cooldowns.cleanup_expired(duration); -} - -pub fn handle_chat_received_event( - mut events: EventReader, - mut command_events: EventWriter, - mut cooldowns: ResMut, - query: Query<&TabList>, - registry: Res, - settings: Res, -) { - for event in events.read() { - let (sender, content) = event.packet.split_sender_and_content(); - let (sender, content) = if let Some(sender) = sender { - (sender, content) - } else if let Some((_whole, sender, content)) = regex_captures!( - r"^(?:\[.+\] )?([a-zA-Z_0-9]{1,16}) (?:> )?(?:whispers: |-> )?(.+)$", - &content - ) { - (sender.to_string(), content.to_string()) - } else { - continue; - }; - - let key = AesKey::decode_base64(&settings.encryption.key).unwrap_or_else(|_| KEY.clone()); - let (encryption, content) = find_encryption(&content, &key); - let Some((args, command)) = registry.find_command(&content, &settings.command_prefix) - else { - continue; - }; - - if !settings.whitelist.is_empty() { - let Ok(tab_list) = query.get_single() else { - continue; - }; - - let Some((uuid, _info)) = tab_list - .iter() - .find(|(_, info)| info.profile.name == sender) - else { - continue; /* Not Online */ - }; - - if !settings.whitelist.contains_key(uuid) { - continue; /* Not Whitelisted */ - } - } - - // Check cooldown - let duration = Duration::from_secs(settings.command_cooldown); - if !cooldowns.check_cooldown(&sender, duration) { - continue; - } - - // Update cooldown - cooldowns.update_cooldown(sender.clone()); - - command_events.send(CommandEvent { - entity: event.entity, - args, - command: *command, - sender: CommandSender::Minecraft(sender), - source: CommandSource::Minecraft(encryption), - }); - } -} - -pub fn handle_minecraft_whisper_event( - mut chat_kind_events: EventWriter, - mut whisper_events: EventReader, - settings: Res, -) { - for event in whisper_events.read().cloned() { - let CommandSender::Minecraft(sender) = event.sender else { - continue; - }; - - let CommandSource::Minecraft(type_encryption) = event.source else { - continue; - }; - - if settings.disable_responses { - continue; - } - - let content = try_encrypt(&settings.encryption, type_encryption, event.content); - - chat_kind_events.send(SendChatKindEvent { - entity: event.entity, - kind: ChatPacketKind::Command, - content: format!("w {sender} {content}"), - }); - } -} diff --git a/src/plugins/commands/handlers/mod.rs b/src/plugins/commands/handlers/mod.rs deleted file mode 100644 index e5ad414..0000000 --- a/src/plugins/commands/handlers/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -pub mod prelude; - -mod discord; -mod minecraft; diff --git a/src/plugins/commands/mod.rs b/src/plugins/commands/mod.rs deleted file mode 100644 index a70dfbd..0000000 --- a/src/plugins/commands/mod.rs +++ /dev/null @@ -1,79 +0,0 @@ -pub mod handlers; -pub mod prelude; - -mod pearl; -mod playtime; -mod seen; -mod whitelist; - -use std::collections::{HashMap, VecDeque}; - -use azalea::{ecs::prelude::*, prelude::*}; -use serenity::all::{ChannelId, UserId}; - -use crate::ncr::EncryptionType; - -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub enum Command { - Pearl, - Playtime, - Seen, - Whitelist, -} - -#[derive(Clone, Debug)] -pub enum CommandSender { - Discord(UserId), - Minecraft(String), -} - -#[derive(Clone, Debug)] -pub enum CommandSource { - Discord(ChannelId), - Minecraft(Option), -} - -#[derive(Clone, Debug, Event)] -pub struct CommandEvent { - pub entity: Entity, - pub args: VecDeque, - pub command: Command, - pub sender: CommandSender, - pub source: CommandSource, -} - -#[derive(Clone, Debug, Event)] -pub struct WhisperEvent { - pub entity: Entity, - pub content: String, - pub sender: CommandSender, - pub source: CommandSource, -} - -#[derive(Default, Resource)] -pub struct Registry(HashMap); - -impl Registry { - pub fn register(&mut self, alias: &str, command: Command) { - self.0.insert(alias.into(), command); - } - - pub fn find_command( - &self, - content: &str, - prefix: &str, - ) -> Option<(VecDeque, &Command)> { - let mut args = content - .split(' ') - .map(String::from) - .collect::>(); - - let alias = args.pop_front()?; - let (_, command) = self - .0 - .iter() - .find(|cmd| format!("{}{}", prefix, cmd.0) == alias)?; - - Some((args, command)) - } -} diff --git a/src/plugins/discord_tracker.rs b/src/plugins/discord_tracker.rs new file mode 100644 index 0000000..5a5cc97 --- /dev/null +++ b/src/plugins/discord_tracker.rs @@ -0,0 +1,180 @@ +use std::collections::HashMap; + +use azalea::{ + app::{App, Plugin, Update}, + auth::game_profile::GameProfile, + blocks::{Block, BlockState}, + ecs::prelude::*, + packet_handling::game::PacketEvent, + prelude::*, + protocol::packets::game::ClientboundGamePacket, + registry::EntityKind, + BlockPos, + TabList, +}; +use bevy_discord::{http::DiscordHttpResource, runtime::tokio_runtime}; +use serenity::json::json; + +use crate::Settings; + +pub struct DiscordTrackerPlugin; + +impl Plugin for DiscordTrackerPlugin { + fn build(&self, app: &mut App) { + app.insert_resource(BlockStates::default()) + .insert_resource(PlayerProfiles::default()) + .add_systems( + Update, + ( + handle_add_entity_packet, + handle_block_break_packet, + handle_block_update_packet, + handle_remove_entities_packet, + ), + ); + } +} + +#[derive(Default, Resource)] +pub struct BlockStates(HashMap); + +#[derive(Default, Resource)] +pub struct PlayerProfiles(HashMap); + +fn handle_add_entity_packet( + mut packet_events: EventReader, + mut player_profiles: ResMut, + discord: Res, + settings: Res, + query: Query<&TabList>, +) { + for event in packet_events.read() { + let ClientboundGamePacket::AddEntity(packet) = event.packet.as_ref() else { + continue; + }; + + if packet.entity_type != EntityKind::Player { + continue; + } + + let Ok(tab_list) = query.get(event.entity) else { + continue; + }; + + let Some((_, info)) = tab_list.iter().find(|(uuid, _)| uuid == &&packet.uuid) else { + continue; + }; + + let profile = info.profile.clone(); + let username = profile.name.clone(); + player_profiles.0.insert(packet.data, profile); + + let client = discord.client(); + let channel_id = settings.discord_channel; + tokio_runtime().spawn(async move { + let map = json!({ + "content": format!("{username} has entered visual range"), + }); + + if let Err(error) = client.send_message(channel_id, vec![], &map).await { + error!("{error}"); + }; + }); + } +} + +fn handle_block_break_packet( + mut packet_events: EventReader, + block_states: Res, + discord: Res, + settings: Res, +) { + for event in packet_events.read() { + let ClientboundGamePacket::BlockDestruction(packet) = event.packet.as_ref() else { + continue; + }; + + let Some(block_state) = block_states.0.get(&packet.pos) else { + continue; + }; + + let block = Box::::from(*block_state); + let block_id = block.id(); + if block_id.ends_with("shulker_box") { + let client = discord.client(); + let channel_id = settings.discord_channel; + tokio_runtime().spawn(async move { + let map = json!({ + "content": format!("{block_id} was mined in visual range"), + }); + + if let Err(error) = client.send_message(channel_id, vec![], &map).await { + error!("{error}"); + }; + }); + } + } +} + +fn handle_block_update_packet( + mut packet_events: EventReader, + mut block_states: ResMut, + discord: Res, + settings: Res, +) { + for event in packet_events.read() { + let ClientboundGamePacket::BlockUpdate(packet) = event.packet.as_ref() else { + continue; + }; + + block_states.0.insert(packet.pos, packet.block_state); + + let block = Box::::from(packet.block_state); + let block_id = block.id(); + if block_id.ends_with("shulker_box") { + let client = discord.client(); + let channel_id = settings.discord_channel; + tokio_runtime().spawn(async move { + let map = json!({ + "content": format!("{block_id} was placed in visual range"), + }); + + if let Err(error) = client.send_message(channel_id, vec![], &map).await { + error!("{error}"); + }; + }); + } + } +} + +fn handle_remove_entities_packet( + mut packet_events: EventReader, + mut player_profiles: ResMut, + discord: Res, + settings: Res, +) { + for event in packet_events.read() { + let ClientboundGamePacket::RemoveEntities(packet) = event.packet.as_ref() else { + continue; + }; + + for entity_id in &packet.entity_ids { + let Some(profile) = player_profiles.0.remove(entity_id) else { + continue; + }; + + let client = discord.client(); + let username = profile.name.clone(); + let channel_id = settings.discord_channel; + tokio_runtime().spawn(async move { + let map = json!({ + "content": format!("{username} has exited visual range"), + }); + + if let Err(error) = client.send_message(channel_id, vec![], &map).await { + error!("{error}"); + }; + }); + } + } +} diff --git a/src/plugins/mod.rs b/src/plugins/mod.rs index 6a14971..3b7b58f 100644 --- a/src/plugins/mod.rs +++ b/src/plugins/mod.rs @@ -1,6 +1,6 @@ use azalea::app::{PluginGroup, PluginGroupBuilder}; -use crate::plugins::prelude::*; +use crate::{commands::prelude::*, plugins::prelude::*}; pub mod prelude; @@ -10,7 +10,7 @@ mod auto_exit; mod auto_look; mod auto_pearl; mod auto_totem; -mod commands; +mod discord_tracker; mod pearl_tracker; pub struct ShaysPluginGroup; diff --git a/src/plugins/pearl_tracker.rs b/src/plugins/pearl_tracker.rs index be18670..a037a46 100644 --- a/src/plugins/pearl_tracker.rs +++ b/src/plugins/pearl_tracker.rs @@ -3,8 +3,8 @@ use azalea::{ blocks::{properties::Open, Block}, ecs::prelude::*, entity::{metadata::Player, Position}, + events::packet_listener, packet_handling::game::PacketEvent, - packet_listener, prelude::*, protocol::packets::game::ClientboundGamePacket, registry::EntityKind, @@ -24,7 +24,7 @@ pub struct PearlTrackerPlugin; impl Plugin for PearlTrackerPlugin { fn build(&self, app: &mut App) { - app.insert_resource(PacketEvents::default()).add_systems( + app.add_event::().add_systems( PostUpdate, ( handle_packet_events.before(packet_listener), @@ -36,15 +36,15 @@ impl Plugin for PearlTrackerPlugin { } } -#[derive(Default, Resource)] -pub struct PacketEvents(pub Vec); +#[derive(Clone, Event)] +pub struct ResendPacketEvent(PacketEvent); pub fn handle_packet_events( - mut events: ResMut, + mut resend_packet_events: EventReader, mut packet_events: EventWriter, ) { - for event in events.0.drain(..) { - packet_events.send(event); + for event in resend_packet_events.read().cloned() { + packet_events.send(event.0); } } @@ -52,13 +52,13 @@ pub fn handle_packet_events( /// Will panic if `MinecraftEntityId` is out of bounds. /// Will panic of `Settings::save` fails. pub fn handle_add_entity_packet( - mut events: EventReader, - mut packet_events: ResMut, + mut packet_events: EventReader, + mut resend_packet_events: EventWriter, mut query: Query<&InstanceHolder>, mut trapdoors: ResMut, profiles: Query<(&MinecraftEntityId, &GameProfileComponent), With>, ) { - for event in events.read() { + for event in packet_events.read().cloned() { let Ok(holder) = query.get_mut(event.entity) else { continue; }; @@ -77,28 +77,27 @@ pub fn handle_add_entity_packet( let owner_uuid = if packet.data == 0 { Uuid::max() /* Player is offline */ - } else if let Some((_, profile)) = profiles - .iter() - .find(|(id, _)| i32::try_from(id.0).unwrap() == packet.data) - { + } else if let Some((_, profile)) = profiles.iter().find(|(id, _)| id.0 == packet.data) { info!("{}'s pearl at {block_pos}", profile.name); profile.uuid } else { // The owner's uuid was sent, but the owner wasn't found in the entity list // Send the event back and try again next update until the owner is received - packet_events.0.push(event.clone()); + resend_packet_events.send(ResendPacketEvent(event)); continue; }; let new_trapdoor = Trapdoor::new(block_pos, packet.id, owner_uuid); - if let Some(old_trapdoor) = trapdoors.0.get_mut(&packet.uuid) { - if owner_uuid != Uuid::max() { - *old_trapdoor = new_trapdoor; - } - } else { - trapdoors.0.insert(packet.uuid, new_trapdoor); - } + trapdoors + .0 + .entry(packet.uuid) + .and_modify(|old_trapdoor| { + if owner_uuid != Uuid::max() { + *old_trapdoor = new_trapdoor; + } + }) + .or_insert(new_trapdoor); trapdoors.save().expect("Failed to save trapdoors"); } diff --git a/src/plugins/prelude.rs b/src/plugins/prelude.rs index 37b9607..16e3e78 100644 --- a/src/plugins/prelude.rs +++ b/src/plugins/prelude.rs @@ -5,6 +5,6 @@ pub use super::{ auto_look::*, auto_pearl::*, auto_totem::*, - commands::prelude::*, + discord_tracker::*, pearl_tracker::*, }; diff --git a/src/settings.rs b/src/settings.rs index e597c90..08f3340 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -1,4 +1,4 @@ -use std::collections::HashMap; +use std::{collections::HashMap, time::Duration}; use azalea::{ app::{App, Plugin}, @@ -7,7 +7,8 @@ use azalea::{ }; use derive_config::DeriveTomlConfig; use serde::{Deserialize, Serialize}; -use serde_with::NoneAsEmptyString; +use serde_with::{DisplayFromStr, DurationSeconds, NoneAsEmptyString}; +use serenity::all::ChannelId; use smart_default::SmartDefault; use uuid::Uuid; @@ -21,7 +22,7 @@ pub enum EncryptionMode { strike! { #[strikethrough[serde_as]] - #[strikethrough[derive(Clone, Deserialize, Serialize, SmartDefault)]] + #[strikethrough[derive(Clone, PartialEq, Deserialize, Serialize, SmartDefault)]] #[strikethrough[serde(default)]] #[derive(DeriveTomlConfig, Resource)] pub struct Settings { @@ -32,35 +33,43 @@ strike! { /// Disable in-game command responses globally. #[default(false)] - #[serde(alias = "quiet")] /* Deprecated: 0.6.0 */ pub disable_responses: bool, /// Minecraft online-mode auth. #[default(true)] - #[serde(alias = "online")] /* Deprecated: 0.6.0 */ pub online_mode: bool, /// Minecraft account username. #[default("ShaysBot")] - #[serde(alias = "username")] /* Deprecated: 0.6.0 */ pub account_username: String, /// Minecraft server address. #[default("play.vengeancecraft.net")] pub server_address: String, - /// Discord client token. (Optional) - #[default("")] - pub discord_token: String, - /// Chat command prefix. #[default("!")] - #[serde(alias = "chat_prefix")] /* Deprecated: 0.6.0 */ pub command_prefix: String, /// Command cooldown in seconds. - #[default(5)] - pub command_cooldown: u64, + #[default(Duration::from_secs(10))] + #[serde_as(as = "DurationSeconds")] + pub command_cooldown: Duration, + + /// Discord client token for commands and events. (Optional) + #[default("")] + pub discord_token: String, + + /// Discord channel id for events. (Optional) + #[serde_as(as = "DisplayFromStr")] + pub discord_channel: ChannelId, + + /// Target location to idle at after pearling. + pub idle: pub struct IdleGoal { + #[serde_as(as = "DisplayFromStr")] + pub pos: Vec3, + pub radius: f32, + }, /// Chat encryption using the NCR (No Chat Reports) mod. pub encryption: pub struct ChatEncryption { @@ -73,10 +82,6 @@ strike! { pub mode: EncryptionMode, }, - /// Target location to idle at fater pearling. - #[default(Vec3::new(-0.0, -0.0, -0.0))] - pub idle_pos: Vec3, - /// Minecraft and Discord users allowed to use the bot. /// The whitelist is disabled if it's empty. #[serde_as(as = "HashMap<_, NoneAsEmptyString>")]