From 0af649ec58e8ddaffda9b6094ef1df97ec316d08 Mon Sep 17 00:00:00 2001 From: Berkus Decker Date: Tue, 19 Aug 2025 12:21:04 +0300 Subject: [PATCH 01/26] =?UTF-8?q?=F0=9F=91=A8=F0=9F=8F=BC=E2=80=8D?= =?UTF-8?q?=F0=9F=92=BB=20Add=20`just=20run`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Justfile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Justfile b/Justfile index 07f01eb1..fa0b3e44 100644 --- a/Justfile +++ b/Justfile @@ -4,7 +4,10 @@ build: cargo build --release test: cargo test +run: + cargo run --bin bot alias d := deploy alias b := build alias t := test +alias r := run From 24b69b3d6244e9c925fc924f8ee55bbdef797ab4 Mon Sep 17 00:00:00 2001 From: Berkus Decker Date: Thu, 28 Aug 2025 21:58:23 +0300 Subject: [PATCH 02/26] =?UTF-8?q?=F0=9F=91=A8=F0=9F=8F=BC=E2=80=8D?= =?UTF-8?q?=F0=9F=92=BB=20Add=20agent=20config?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AGENT.md | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 AGENT.md diff --git a/AGENT.md b/AGENT.md new file mode 100644 index 00000000..87e03376 --- /dev/null +++ b/AGENT.md @@ -0,0 +1,44 @@ +# Communication Guidelines + +## Avoid Sycophantic Language +- **NEVER** use phrases like "You're absolutely right!", "You're absolutely correct!", "Excellent point!", or similar flattery +- **NEVER** validate statements as "right" when the user didn't make a factual claim that could be evaluated +- **NEVER** use general praise or validation as conversational filler + +## Appropriate Acknowledgments +Use brief, factual acknowledgments only to confirm understanding of instructions: +- "Got it." +- "Ok, that makes sense." +- "I understand." +- "I see the issue." + +These should only be used when: +1. You genuinely understand the instruction and its reasoning +2. The acknowledgment adds clarity about what you'll do next +3. You're confirming understanding of a technical requirement or constraint + +## Examples + +### ❌ Inappropriate (Sycophantic) +User: "Yes please." +Assistant: "You're absolutely right! That's a great decision." + +User: "Let's remove this unused code." +Assistant: "Excellent point! You're absolutely correct that we should clean this up." + +### ✅ Appropriate (Brief Acknowledgment) +User: "Yes please." +Assistant: "Got it." [proceeds with the requested action] + +User: "Let's remove this unused code." +Assistant: "I'll remove the unused code path." [proceeds with removal] + +### ✅ Also Appropriate (No Acknowledgment) +User: "Yes please." +Assistant: [proceeds directly with the requested action] + +## Rationale +- Maintains professional, technical communication +- Avoids artificial validation of non-factual statements +- Focuses on understanding and execution rather than praise +- Prevents misrepresenting user statements as claims that could be "right" or "wrong" From 7c0110eb8de9ad743f5390143ce626ecd7dd157e Mon Sep 17 00:00:00 2001 From: Berkus Decker Date: Mon, 4 Nov 2024 20:47:59 +0200 Subject: [PATCH 03/26] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Move=20sources=20und?= =?UTF-8?q?er=20bot/?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove useless feature that breaks aquamarine (include_dir.metadata) Reorg lib files to avoid circular deps --- Cargo.lock | 2029 +++++++++++++---- Cargo.toml | 61 +- bot/Cargo.toml | 29 + bot/src/actors/bot_actor.rs | 290 +++ bot/src/actors/mod.rs | 2 + bot/src/actors/reminder_actor.rs | 213 ++ {src => bot/src}/bin/bot.rs | 7 +- .../src}/commands/activities_command.rs | 2 +- {src => bot/src}/commands/cancel_command.rs | 4 +- {src => bot/src}/commands/chatid_command.rs | 2 +- {src => bot/src}/commands/d2week_command.rs | 4 +- {src => bot/src}/commands/dweek_command.rs | 4 +- {src => bot/src}/commands/edit_command.rs | 2 +- {src => bot/src}/commands/editguar_command.rs | 4 +- {src => bot/src}/commands/help_command.rs | 2 +- {src => bot/src}/commands/join_command.rs | 1 - {src => bot/src}/commands/lfg_command.rs | 4 +- {src => bot/src}/commands/list_command.rs | 1 + {src => bot/src}/commands/manage_command.rs | 8 +- {src => bot/src}/commands/mod.rs | 70 +- {src => bot/src}/commands/psn_command.rs | 0 {src => bot/src}/commands/uptime_command.rs | 2 +- {src => bot/src}/commands/whois_command.rs | 2 +- {src => bot/src}/lib.rs | 100 +- {src => bot/src}/models.rs | 0 {src => bot/src}/schema.patch | 0 {src => bot/src}/schema.rs | 0 bot/src/schema.rs.orig | 86 + lib/Cargo.toml | 15 + src/datetime/mod.rs => lib/src/datetime.rs | 9 +- lib/src/lib.rs | 2 + {src => lib/src}/services/destiny_schedule.rs | 31 +- lib/src/services/mod.rs | 1 + src/bot_actor.rs | 266 --- src/errors.rs | 8 - src/services/mod.rs | 5 - src/services/reminder.rs | 39 - src/services/reminder_actor.rs | 146 -- 38 files changed, 2354 insertions(+), 1097 deletions(-) create mode 100644 bot/Cargo.toml create mode 100644 bot/src/actors/bot_actor.rs create mode 100644 bot/src/actors/mod.rs create mode 100644 bot/src/actors/reminder_actor.rs rename {src => bot/src}/bin/bot.rs (95%) rename {src => bot/src}/commands/activities_command.rs (99%) rename {src => bot/src}/commands/cancel_command.rs (96%) rename {src => bot/src}/commands/chatid_command.rs (91%) rename {src => bot/src}/commands/d2week_command.rs (85%) rename {src => bot/src}/commands/dweek_command.rs (85%) rename {src => bot/src}/commands/edit_command.rs (99%) rename {src => bot/src}/commands/editguar_command.rs (97%) rename {src => bot/src}/commands/help_command.rs (91%) rename {src => bot/src}/commands/join_command.rs (99%) rename {src => bot/src}/commands/lfg_command.rs (97%) rename {src => bot/src}/commands/list_command.rs (98%) rename {src => bot/src}/commands/manage_command.rs (97%) rename {src => bot/src}/commands/mod.rs (81%) rename {src => bot/src}/commands/psn_command.rs (100%) rename {src => bot/src}/commands/uptime_command.rs (96%) rename {src => bot/src}/commands/whois_command.rs (96%) rename {src => bot/src}/lib.rs (64%) rename {src => bot/src}/models.rs (100%) rename {src => bot/src}/schema.patch (100%) rename {src => bot/src}/schema.rs (100%) create mode 100644 bot/src/schema.rs.orig create mode 100644 lib/Cargo.toml rename src/datetime/mod.rs => lib/src/datetime.rs (97%) create mode 100644 lib/src/lib.rs rename {src => lib/src}/services/destiny_schedule.rs (81%) create mode 100644 lib/src/services/mod.rs delete mode 100644 src/bot_actor.rs delete mode 100644 src/errors.rs delete mode 100644 src/services/mod.rs delete mode 100644 src/services/reminder.rs delete mode 100644 src/services/reminder_actor.rs diff --git a/Cargo.lock b/Cargo.lock index 81964deb..47a4181b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -19,35 +19,26 @@ checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "aegl_bot" -version = "0.3.0" +version = "0.4.0" dependencies = [ + "anyhow", "chrono", - "chrono-english", "chrono-tz 0.10.4", - "diesel", - "diesel_derives_extra", - "diesel_derives_traits", - "diesel_logger", - "diesel_migrations", "dotenv", "fern", - "futures", - "futures-retry", "include_dir", - "itertools", + "itertools 0.14.0", "log", "paste", "procfs", - "r2d2", "regex", "riker", + "sea-orm", "serde 1.0.219", - "serde_json", "teloxide", "tera", - "thousands", "tokio", - "tokio-util 0.6.10", + "two_timer", ] [[package]] @@ -59,6 +50,17 @@ dependencies = [ "const-random", ] +[[package]] +name = "ahash" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" +dependencies = [ + "getrandom 0.2.16", + "once_cell", + "version_check", +] + [[package]] name = "aho-corasick" version = "1.1.3" @@ -68,6 +70,18 @@ dependencies = [ "memchr", ] +[[package]] +name = "aliasable" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd" + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + [[package]] name = "android-tzdata" version = "0.1.1" @@ -83,6 +97,26 @@ dependencies = [ "libc", ] +[[package]] +name = "anyhow" +version = "1.0.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100" + +[[package]] +name = "aquamarine" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21cc1548309245035eb18aa7f0967da6bc65587005170c56e6ef2788a4cf3f4e" +dependencies = [ + "include_dir", + "itertools 0.10.5", + "proc-macro-error", + "proc-macro2 1.0.101", + "quote 1.0.40", + "syn 2.0.106", +] + [[package]] name = "arc-swap" version = "1.7.1" @@ -95,15 +129,52 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "async-stream" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" +dependencies = [ + "proc-macro2 1.0.101", + "quote 1.0.40", + "syn 2.0.106", +] + [[package]] name = "async-trait" -version = "0.1.88" +version = "0.1.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ - "proc-macro2 1.0.95", + "proc-macro2 1.0.101", "quote 1.0.40", - "syn 2.0.104", + "syn 2.0.106", +] + +[[package]] +name = "atoi" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528" +dependencies = [ + "num-traits 0.2.19", ] [[package]] @@ -119,7 +190,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" dependencies = [ "addr2line", - "cfg-if 1.0.1", + "cfg-if 1.0.3", "libc", "miniz_oxide", "object", @@ -133,6 +204,32 @@ version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "base64ct" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" + +[[package]] +name = "bigdecimal" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a22f228ab7a1b23027ccc6c350b72868017af7ea8356fbdf19f8d991c690013" +dependencies = [ + "autocfg", + "libm", + "num-bigint", + "num-integer", + "num-traits 0.2.19", + "serde 1.0.219", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -141,9 +238,24 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.9.1" +version = "2.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a65b545ab31d687cff52899d4890855fec459eb6afe0da6417b8a18da87aa29" +dependencies = [ + "serde 1.0.219", +] + +[[package]] +name = "bitvec" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] [[package]] name = "block-buffer" @@ -154,6 +266,29 @@ dependencies = [ "generic-array", ] +[[package]] +name = "borsh" +version = "1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad8646f98db542e39fc66e68a20b2144f6a732636df7c2354e74645faaa433ce" +dependencies = [ + "borsh-derive", + "cfg_aliases", +] + +[[package]] +name = "borsh-derive" +version = "1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdd1d3c0c2f5833f22386f252fe8ed005c7f59fdcddeef025c01b4c3b9fd9ac3" +dependencies = [ + "once_cell", + "proc-macro-crate", + "proc-macro2 1.0.101", + "quote 1.0.40", + "syn 2.0.106", +] + [[package]] name = "bstr" version = "1.12.0" @@ -170,6 +305,28 @@ version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" +[[package]] +name = "bytecheck" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23cdc57ce23ac53c931e88a43d06d070a6fd142f2617be5855eb75efc9beb1c2" +dependencies = [ + "bytecheck_derive", + "ptr_meta", + "simdutf8", +] + +[[package]] +name = "bytecheck_derive" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3db406d29fbcd95542e92559bed4d8ad92636d1ca8b3b72ede10b4bcc010e659" +dependencies = [ + "proc-macro2 1.0.101", + "quote 1.0.40", + "syn 1.0.109", +] + [[package]] name = "byteorder" version = "1.5.0" @@ -184,9 +341,9 @@ checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "cc" -version = "1.2.31" +version = "1.2.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3a42d84bb6b69d3a8b3eaacf0d88f179e1929695e1ad012b6cf64d9caaa5fd2" +checksum = "3ee0f8803222ba5a7e2777dd72ca451868909b1ac410621b676adf07280e9b5f" dependencies = [ "shlex", ] @@ -199,9 +356,15 @@ checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" [[package]] name = "cfg-if" -version = "1.0.1" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "chrono" @@ -213,20 +376,11 @@ dependencies = [ "iana-time-zone", "js-sys", "num-traits 0.2.19", + "serde 1.0.219", "wasm-bindgen", "windows-link", ] -[[package]] -name = "chrono-english" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f73d909da7eb4a7d88c679c3f5a1bc09d965754e0adb2e7627426cef96a00d6f" -dependencies = [ - "chrono", - "scanlex", -] - [[package]] name = "chrono-tz" version = "0.9.0" @@ -259,15 +413,6 @@ dependencies = [ "phf_codegen", ] -[[package]] -name = "cloudabi" -version = "0.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" -dependencies = [ - "bitflags 1.3.2", -] - [[package]] name = "colored" version = "2.2.0" @@ -278,6 +423,15 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "config" version = "0.10.1" @@ -294,6 +448,12 @@ dependencies = [ "yaml-rust", ] +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + [[package]] name = "const-random" version = "0.1.18" @@ -345,13 +505,28 @@ dependencies = [ "libc", ] +[[package]] +name = "crc" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9710d3b3739c2e349eb44fe848ad0b7c8cb1e42bd87ee49371df2f7acaf3e675" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" + [[package]] name = "crc32fast" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" dependencies = [ - "cfg-if 1.0.1", + "cfg-if 1.0.3", ] [[package]] @@ -360,23 +535,8 @@ version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" dependencies = [ - "crossbeam-epoch 0.9.18", - "crossbeam-utils 0.8.21", -] - -[[package]] -name = "crossbeam-epoch" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "058ed274caafc1f60c4997b5fc07bf7dc7cca454af7c6e81edffe5f33f70dace" -dependencies = [ - "autocfg", - "cfg-if 0.1.10", - "crossbeam-utils 0.7.2", - "lazy_static", - "maybe-uninit", - "memoffset", - "scopeguard", + "crossbeam-epoch", + "crossbeam-utils", ] [[package]] @@ -385,18 +545,16 @@ version = "0.9.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" dependencies = [ - "crossbeam-utils 0.8.21", + "crossbeam-utils", ] [[package]] -name = "crossbeam-utils" -version = "0.7.2" +name = "crossbeam-queue" +version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8" +checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" dependencies = [ - "autocfg", - "cfg-if 0.1.10", - "lazy_static", + "crossbeam-utils", ] [[package]] @@ -439,7 +597,7 @@ checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610" dependencies = [ "fnv", "ident_case", - "proc-macro2 1.0.95", + "proc-macro2 1.0.101", "quote 1.0.40", "strsim", "syn 1.0.109", @@ -462,97 +620,50 @@ version = "3.11.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0f260e2fc850179ef410018660006951c1b55b79e8087e87111a2c388994b9b5" dependencies = [ - "ahash", + "ahash 0.3.8", "cfg-if 0.1.10", "num_cpus", ] [[package]] -name = "derive_more" -version = "0.99.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6edb4b64a43d977b8e99788fe3a04d483834fba1215a7e02caa415b626497f7f" -dependencies = [ - "convert_case", - "proc-macro2 1.0.95", - "quote 1.0.40", - "rustc_version", - "syn 2.0.104", -] - -[[package]] -name = "deunicode" -version = "1.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abd57806937c9cc163efc8ea3910e00a62e2aeb0b8119f1793a978088f8f6b04" - -[[package]] -name = "diesel" -version = "1.4.8" +name = "der" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b28135ecf6b7d446b43e27e225622a038cc4e2930a1022f51cdb97ada19b8e4d" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" dependencies = [ - "bitflags 1.3.2", - "byteorder", - "chrono", - "diesel_derives", - "pq-sys", - "r2d2", - "serde_json", + "const-oid", + "pem-rfc7468", + "zeroize", ] [[package]] -name = "diesel_derives" -version = "1.4.1" +name = "deranged" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45f5098f628d02a7a0f68ddba586fb61e80edec3bdc1be3b921f4ceec60858d3" +checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" dependencies = [ - "proc-macro2 1.0.95", - "quote 1.0.40", - "syn 1.0.109", + "powerfmt", + "serde 1.0.219", ] [[package]] -name = "diesel_derives_extra" -version = "0.2.0" +name = "derive_more" +version = "0.99.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6ed24b0993147beb58b8725b1180012e2320a96d15889600df0cd31afd3c15b" +checksum = "6edb4b64a43d977b8e99788fe3a04d483834fba1215a7e02caa415b626497f7f" dependencies = [ - "diesel", - "diesel_derives_traits", - "proc-macro2 1.0.95", + "convert_case", + "proc-macro2 1.0.101", "quote 1.0.40", - "syn 1.0.109", -] - -[[package]] -name = "diesel_derives_traits" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62d0822c6cb583609dc780222064ec55d69228d2163cf69dd4f9b11fa9b8eb99" -dependencies = [ - "diesel", -] - -[[package]] -name = "diesel_logger" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1793935ad14586bf2aa51574a7157932640c345205ccfb2db431846d846e3db7" -dependencies = [ - "diesel", - "log", + "rustc_version", + "syn 2.0.106", ] [[package]] -name = "diesel_migrations" -version = "1.4.0" +name = "deunicode" +version = "1.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf3cde8413353dc7f5d72fa8ce0b99a560a359d2c5ef1e5817ca731cd9008f4c" -dependencies = [ - "migrations_internals", - "migrations_macros", -] +checksum = "abd57806937c9cc163efc8ea3910e00a62e2aeb0b8119f1793a978088f8f6b04" [[package]] name = "digest" @@ -561,7 +672,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", + "const-oid", "crypto-common", + "subtle", ] [[package]] @@ -570,9 +683,9 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ - "proc-macro2 1.0.95", + "proc-macro2 1.0.101", "quote 1.0.40", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -581,11 +694,29 @@ version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" +[[package]] +name = "dotenvy" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" + +[[package]] +name = "dptree" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d81175dab5ec79c30e0576df2ed2c244e1721720c302000bb321b107e82e265c" +dependencies = [ + "futures", +] + [[package]] name = "either" version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +dependencies = [ + "serde 1.0.219", +] [[package]] name = "encoding_rs" @@ -593,7 +724,7 @@ version = "0.8.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" dependencies = [ - "cfg-if 1.0.1", + "cfg-if 1.0.3", ] [[package]] @@ -602,6 +733,16 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" +[[package]] +name = "erasable" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "437cfb75878119ed8265685c41a115724eae43fb7cc5a0bf0e4ecc3b803af1c4" +dependencies = [ + "autocfg", + "scopeguard", +] + [[package]] name = "errno" version = "0.3.13" @@ -612,6 +753,28 @@ dependencies = [ "windows-sys 0.60.2", ] +[[package]] +name = "etcetera" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" +dependencies = [ + "cfg-if 1.0.3", + "home", + "windows-sys 0.48.0", +] + +[[package]] +name = "event-listener" +version = "5.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + [[package]] name = "fastrand" version = "2.3.0" @@ -639,15 +802,14 @@ dependencies = [ ] [[package]] -name = "flurry" -version = "0.3.1" +name = "flume" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c0a35f7b50e99185a2825541946252f669f3c3ca77801357cd682a1b356bb3e" +checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" dependencies = [ - "ahash", - "crossbeam-epoch 0.8.2", - "num_cpus", - "parking_lot 0.10.2", + "futures-core", + "futures-sink", + "spin", ] [[package]] @@ -656,6 +818,12 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + [[package]] name = "foreign-types" version = "0.3.2" @@ -680,6 +848,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + [[package]] name = "futures" version = "0.3.31" @@ -724,7 +898,18 @@ dependencies = [ ] [[package]] -name = "futures-io" +name = "futures-intrusive" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f" +dependencies = [ + "futures-core", + "lock_api", + "parking_lot", +] + +[[package]] +name = "futures-io" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" @@ -735,20 +920,9 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ - "proc-macro2 1.0.95", + "proc-macro2 1.0.101", "quote 1.0.40", - "syn 2.0.104", -] - -[[package]] -name = "futures-retry" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fde5a672a61f96552aa5ed9fd9c81c3fbdae4be9b1e205d6eaf17c83705adc0f" -dependencies = [ - "futures", - "pin-project-lite", - "tokio", + "syn 2.0.106", ] [[package]] @@ -797,7 +971,7 @@ version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" dependencies = [ - "cfg-if 1.0.1", + "cfg-if 1.0.3", "libc", "wasi 0.9.0+wasi-snapshot-preview1", ] @@ -808,7 +982,7 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ - "cfg-if 1.0.1", + "cfg-if 1.0.3", "libc", "wasi 0.11.1+wasi-snapshot-preview1", ] @@ -819,7 +993,7 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" dependencies = [ - "cfg-if 1.0.1", + "cfg-if 1.0.3", "libc", "r-efi", "wasi 0.14.2+wasi-0.2.4", @@ -833,9 +1007,9 @@ checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "glob" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" [[package]] name = "globset" @@ -856,7 +1030,7 @@ version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bf760ebf69878d9fd8f110c89703d90ce35095324d1f1edcb595c63945ee757" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.2", "ignore", "walkdir", ] @@ -876,15 +1050,50 @@ dependencies = [ "indexmap", "slab", "tokio", - "tokio-util 0.7.15", + "tokio-util", "tracing", ] [[package]] name = "hashbrown" -version = "0.15.4" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +dependencies = [ + "ahash 0.7.8", +] + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] + +[[package]] +name = "hashlink" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" +checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" +dependencies = [ + "hashbrown 0.15.5", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hermit-abi" @@ -898,6 +1107,33 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "home" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" +dependencies = [ + "windows-sys 0.59.0", +] + [[package]] name = "http" version = "0.2.12" @@ -1147,7 +1383,7 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7cab85a7ed0bd5f0e76d93846e0147172bed2e2d3f859bcc33a8d9699cad1a75" dependencies = [ - "proc-macro2 1.0.95", + "proc-macro2 1.0.101", "quote 1.0.40", ] @@ -1158,7 +1394,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.15.5", +] + +[[package]] +name = "inherent" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c38228f24186d9cc68c729accb4d413be9eaed6ad07ff79e0270d9e56f3de13" +dependencies = [ + "proc-macro2 1.0.101", + "quote 1.0.40", + "syn 2.0.106", ] [[package]] @@ -1167,8 +1414,8 @@ version = "0.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d93587f37623a1a17d94ef2bc9ada592f5465fe7732084ab7beefabe5c77c0c4" dependencies = [ - "bitflags 2.9.1", - "cfg-if 1.0.1", + "bitflags 2.9.2", + "cfg-if 1.0.3", "libc", ] @@ -1178,6 +1425,15 @@ version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + [[package]] name = "itertools" version = "0.14.0" @@ -1208,6 +1464,9 @@ name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +dependencies = [ + "spin", +] [[package]] name = "lexical-core" @@ -1215,18 +1474,18 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6607c62aa161d23d17a9072cc5da0be67cdfc89d3afb1e8d9c842bebc2525ffe" dependencies = [ - "arrayvec", + "arrayvec 0.5.2", "bitflags 1.3.2", - "cfg-if 1.0.1", + "cfg-if 1.0.3", "ryu", "static_assertions", ] [[package]] name = "libc" -version = "0.2.174" +version = "0.2.175" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" +checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" [[package]] name = "libm" @@ -1234,6 +1493,27 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" +[[package]] +name = "libredox" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "391290121bad3d37fbddad76d8f5d1c1c314cfc646d143d7e07a3086ddff0ce3" +dependencies = [ + "bitflags 2.9.2", + "libc", + "redox_syscall", +] + +[[package]] +name = "libsqlite3-sys" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149" +dependencies = [ + "pkg-config", + "vcpkg", +] + [[package]] name = "linked-hash-map" version = "0.3.0" @@ -1268,15 +1548,6 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" -[[package]] -name = "lock_api" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4da24a77a3d8a6d4862d95f72e6fdb9c09a643ecdb402d754004a557f2bec75" -dependencies = [ - "scopeguard", -] - [[package]] name = "lock_api" version = "0.4.13" @@ -1294,10 +1565,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" [[package]] -name = "maybe-uninit" -version = "2.0.0" +name = "md-5" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if 1.0.3", + "digest", +] [[package]] name = "memchr" @@ -1305,36 +1580,6 @@ version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" -[[package]] -name = "memoffset" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "043175f069eda7b85febe4a74abbaeff828d9f8b448515d3151a14a3542811aa" -dependencies = [ - "autocfg", -] - -[[package]] -name = "migrations_internals" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b4fc84e4af020b837029e017966f86a1c2d5e83e64b589963d5047525995860" -dependencies = [ - "diesel", -] - -[[package]] -name = "migrations_macros" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9753f12909fd8d923f75ae5c3258cae1ed3c8ec052e1b38c93c21a6d157f789c" -dependencies = [ - "migrations_internals", - "proc-macro2 1.0.95", - "quote 1.0.40", - "syn 1.0.109", -] - [[package]] name = "mime" version = "0.3.17" @@ -1388,12 +1633,6 @@ dependencies = [ "tempfile", ] -[[package]] -name = "never" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c96aba5aa877601bb3f6dd6a63a969e1f82e60646e81e71b14496995e9853c91" - [[package]] name = "nom" version = "5.1.3" @@ -1405,6 +1644,59 @@ dependencies = [ "version_check", ] +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits 0.2.19", +] + +[[package]] +name = "num-bigint-dig" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" +dependencies = [ + "byteorder", + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits 0.2.19", + "rand 0.8.5", + "smallvec", + "zeroize", +] + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits 0.2.19", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits 0.2.19", +] + [[package]] name = "num-traits" version = "0.1.43" @@ -1421,6 +1713,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", + "libm", ] [[package]] @@ -1454,8 +1747,8 @@ version = "0.10.73" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8" dependencies = [ - "bitflags 2.9.1", - "cfg-if 1.0.1", + "bitflags 2.9.2", + "cfg-if 1.0.3", "foreign-types", "libc", "once_cell", @@ -1469,9 +1762,9 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ - "proc-macro2 1.0.95", + "proc-macro2 1.0.101", "quote 1.0.40", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -1493,37 +1786,52 @@ dependencies = [ ] [[package]] -name = "parking_lot" -version = "0.10.2" +name = "ordered-float" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3a704eb390aafdc107b0e392f56a82b668e3a71366993b5340f5833fd62505e" +checksum = "7bb71e1b3fa6ca1c61f383464aaf2bb0e2f8e772a1f01d486832464de363b951" dependencies = [ - "lock_api 0.3.4", - "parking_lot_core 0.7.3", + "num-traits 0.2.19", ] [[package]] -name = "parking_lot" -version = "0.12.4" +name = "ouroboros" +version = "0.18.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" +checksum = "1e0f050db9c44b97a94723127e6be766ac5c340c48f2c4bb3ffa11713744be59" dependencies = [ - "lock_api 0.4.13", - "parking_lot_core 0.9.11", + "aliasable", + "ouroboros_macro", + "static_assertions", ] [[package]] -name = "parking_lot_core" -version = "0.7.3" +name = "ouroboros_macro" +version = "0.18.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b93f386bb233083c799e6e642a9d73db98c24a5deeb95ffc85bf281255dffc98" +checksum = "3c7028bdd3d43083f6d8d4d5187680d0d3560d54df4cc9d752005268b41e64d0" dependencies = [ - "cfg-if 0.1.10", - "cloudabi", - "libc", - "redox_syscall 0.1.57", - "smallvec", - "winapi", + "heck 0.4.1", + "proc-macro2 1.0.101", + "proc-macro2-diagnostics", + "quote 1.0.40", + "syn 2.0.106", +] + +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + +[[package]] +name = "parking_lot" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" +dependencies = [ + "lock_api", + "parking_lot_core", ] [[package]] @@ -1532,9 +1840,9 @@ version = "0.9.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" dependencies = [ - "cfg-if 1.0.1", + "cfg-if 1.0.3", "libc", - "redox_syscall 0.5.17", + "redox_syscall", "smallvec", "windows-targets 0.52.6", ] @@ -1554,6 +1862,15 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + [[package]] name = "percent-encoding" version = "2.3.1" @@ -1567,7 +1884,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1db05f56d34358a8b1066f67cbb203ee3e7ed2ba674a6263a1d5ec6db2204323" dependencies = [ "memchr", - "thiserror 2.0.12", + "thiserror 2.0.16", "ucd-trie", ] @@ -1589,9 +1906,9 @@ checksum = "87e404e638f781eb3202dc82db6760c8ae8a1eeef7fb3fa8264b2ef280504966" dependencies = [ "pest", "pest_meta", - "proc-macro2 1.0.95", + "proc-macro2 1.0.101", "quote 1.0.40", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -1604,6 +1921,15 @@ dependencies = [ "sha2", ] +[[package]] +name = "pgvector" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc58e2d255979a31caa7cabfa7aac654af0354220719ab7a68520ae7a91e8c0b" +dependencies = [ + "serde 1.0.219", +] + [[package]] name = "phf" version = "0.11.3" @@ -1660,6 +1986,18 @@ dependencies = [ "siphasher", ] +[[package]] +name = "pidgin" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28a2a37d2d894d50891a12fba6dd7b84292caf2f14fd0086a2af2404be2d8ebb" +dependencies = [ + "lazy_static", + "regex", + "serde 1.0.219", + "serde_regex", +] + [[package]] name = "pin-project" version = "1.1.10" @@ -1675,9 +2013,9 @@ version = "1.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" dependencies = [ - "proc-macro2 1.0.95", + "proc-macro2 1.0.101", "quote 1.0.40", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -1692,6 +2030,27 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pkcs1" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" +dependencies = [ + "der", + "pkcs8", + "spki", +] + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + [[package]] name = "pkg-config" version = "0.3.32" @@ -1707,6 +2066,12 @@ dependencies = [ "zerovec", ] +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "ppv-lite86" version = "0.2.21" @@ -1717,61 +2082,140 @@ dependencies = [ ] [[package]] -name = "pq-sys" -version = "0.4.8" +name = "proc-macro-crate" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31c0052426df997c0cbd30789eb44ca097e3541717a7b8fa36b1c464ee7edebd" +checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35" dependencies = [ - "vcpkg", + "toml_edit", ] [[package]] -name = "proc-macro2" -version = "0.4.30" +name = "proc-macro-error" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" dependencies = [ - "unicode-xid", + "proc-macro-error-attr", + "proc-macro2 1.0.101", + "quote 1.0.40", + "syn 1.0.109", + "version_check", ] [[package]] -name = "proc-macro2" -version = "1.0.95" +name = "proc-macro-error-attr" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" dependencies = [ - "unicode-ident", + "proc-macro2 1.0.101", + "quote 1.0.40", + "version_check", ] [[package]] -name = "procfs" -version = "0.17.0" +name = "proc-macro-error-attr2" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc5b72d8145275d844d4b5f6d4e1eef00c8cd889edb6035c21675d1bb1f45c9f" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" dependencies = [ - "bitflags 2.9.1", - "chrono", - "flate2", - "hex", - "procfs-core", - "rustix 0.38.44", + "proc-macro2 1.0.101", + "quote 1.0.40", ] [[package]] -name = "procfs-core" -version = "0.17.0" +name = "proc-macro-error2" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "239df02d8349b06fc07398a3a1697b06418223b1c7725085e801e7c0fc6a12ec" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" dependencies = [ - "bitflags 2.9.1", - "chrono", - "hex", + "proc-macro-error-attr2", + "proc-macro2 1.0.101", + "quote 1.0.40", + "syn 2.0.106", ] [[package]] -name = "quote" -version = "0.6.13" -source = "registry+https://github.com/rust-lang/crates.io-index" +name = "proc-macro2" +version = "0.4.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "proc-macro2" +version = "1.0.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "proc-macro2-diagnostics" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" +dependencies = [ + "proc-macro2 1.0.101", + "quote 1.0.40", + "syn 2.0.106", + "version_check", + "yansi", +] + +[[package]] +name = "procfs" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc5b72d8145275d844d4b5f6d4e1eef00c8cd889edb6035c21675d1bb1f45c9f" +dependencies = [ + "bitflags 2.9.2", + "chrono", + "flate2", + "hex", + "procfs-core", + "rustix 0.38.44", +] + +[[package]] +name = "procfs-core" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "239df02d8349b06fc07398a3a1697b06418223b1c7725085e801e7c0fc6a12ec" +dependencies = [ + "bitflags 2.9.2", + "chrono", + "hex", +] + +[[package]] +name = "ptr_meta" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0738ccf7ea06b608c10564b31debd4f5bc5e197fc8bfe088f68ae5ce81e7a4f1" +dependencies = [ + "ptr_meta_derive", +] + +[[package]] +name = "ptr_meta_derive" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac" +dependencies = [ + "proc-macro2 1.0.101", + "quote 1.0.40", + "syn 1.0.109", +] + +[[package]] +name = "quote" +version = "0.6.13" +source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1" dependencies = [ "proc-macro2 0.4.30", @@ -1783,7 +2227,7 @@ version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ - "proc-macro2 1.0.95", + "proc-macro2 1.0.101", ] [[package]] @@ -1793,15 +2237,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] -name = "r2d2" -version = "0.8.10" +name = "radium" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51de85fb3fb6524929c8a2eb85e6b6d363de4e8c48f9e2c2eac4944abc181c93" -dependencies = [ - "log", - "parking_lot 0.12.4", - "scheduled-thread-pool", -] +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" [[package]] name = "rand" @@ -1875,10 +2314,13 @@ dependencies = [ ] [[package]] -name = "redox_syscall" -version = "0.1.57" +name = "rc-box" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" +checksum = "897fecc9fac6febd4408f9e935e86df739b0023b625e610e0357535b9c8adad0" +dependencies = [ + "erasable", +] [[package]] name = "redox_syscall" @@ -1886,7 +2328,7 @@ version = "0.5.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.2", ] [[package]] @@ -1918,13 +2360,22 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +[[package]] +name = "rend" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71fe3824f5629716b1589be05dacd749f6aa084c87e00e016714a8cdfccc997c" +dependencies = [ + "bytecheck", +] + [[package]] name = "reqwest" version = "0.11.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" dependencies = [ - "base64", + "base64 0.21.7", "bytes", "encoding_rs", "futures-core", @@ -1951,7 +2402,7 @@ dependencies = [ "system-configuration", "tokio", "tokio-native-tls", - "tokio-util 0.7.15", + "tokio-util", "tower-service", "url", "wasm-bindgen", @@ -1979,7 +2430,7 @@ dependencies = [ "slog", "slog-scope", "slog-stdlog", - "uuid", + "uuid 0.8.2", ] [[package]] @@ -1993,12 +2444,91 @@ dependencies = [ "syn 0.15.44", ] +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if 1.0.3", + "getrandom 0.2.16", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rkyv" +version = "0.7.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9008cd6385b9e161d8229e1f6549dd23c3d022f132a2ea37ac3a10ac4935779b" +dependencies = [ + "bitvec", + "bytecheck", + "bytes", + "hashbrown 0.12.3", + "ptr_meta", + "rend", + "rkyv_derive", + "seahash", + "tinyvec", + "uuid 1.18.0", +] + +[[package]] +name = "rkyv_derive" +version = "0.7.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "503d1d27590a2b0a3a4ca4c94755aa2875657196ecbf401a42eff41d7de532c0" +dependencies = [ + "proc-macro2 1.0.101", + "quote 1.0.40", + "syn 1.0.109", +] + +[[package]] +name = "rsa" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78928ac1ed176a5ca1d17e578a1825f3d81ca54cf41053a592584b020cfd691b" +dependencies = [ + "const-oid", + "digest", + "num-bigint-dig", + "num-integer", + "num-traits 0.2.19", + "pkcs1", + "pkcs8", + "rand_core 0.6.4", + "signature", + "spki", + "subtle", + "zeroize", +] + [[package]] name = "rust-ini" version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e52c148ef37f8c375d49d5a73aa70713125b7f19095948a923f80afdeb22ec2" +[[package]] +name = "rust_decimal" +version = "1.37.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b203a6425500a03e0919c42d3c47caca51e79f1132046626d2c8871c5092035d" +dependencies = [ + "arrayvec 0.7.6", + "borsh", + "bytes", + "num-traits 0.2.19", + "rand 0.8.5", + "rkyv", + "serde 1.0.219", + "serde_json", +] + [[package]] name = "rustc-demangle" version = "0.1.26" @@ -2020,7 +2550,7 @@ version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.2", "errno", "libc", "linux-raw-sys 0.4.15", @@ -2033,27 +2563,61 @@ version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.2", "errno", "libc", "linux-raw-sys 0.9.4", "windows-sys 0.60.2", ] +[[package]] +name = "rustls" +version = "0.23.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ebcbd2f03de0fc1122ad9bb24b127a5a6cd51d72604a3f3c50ac459762b6cc" +dependencies = [ + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + [[package]] name = "rustls-pemfile" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" dependencies = [ - "base64", + "base64 0.21.7", +] + +[[package]] +name = "rustls-pki-types" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" +dependencies = [ + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a17884ae0c1b773f1ccd2bd4a8c72f16da897310a98b0e84bf349ad5ead92fc" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", ] [[package]] name = "rustversion" -version = "1.0.21" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "ryu" @@ -2070,12 +2634,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "scanlex" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "088c5d71572124929ea7549a8ce98e1a6fd33d0a38367b09027b382e67c033db" - [[package]] name = "schannel" version = "0.1.27" @@ -2086,19 +2644,104 @@ dependencies = [ ] [[package]] -name = "scheduled-thread-pool" -version = "0.2.7" +name = "scopeguard" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cbc66816425a074528352f5789333ecff06ca41b36b0b0efdfbb29edc391a19" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "sea-bae" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f694a6ab48f14bc063cfadff30ab551d3c7e46d8f81836c51989d548f44a2a25" dependencies = [ - "parking_lot 0.12.4", + "heck 0.4.1", + "proc-macro-error2", + "proc-macro2 1.0.101", + "quote 1.0.40", + "syn 2.0.106", ] [[package]] -name = "scopeguard" -version = "1.2.0" +name = "sea-orm" +version = "1.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +checksum = "34963b2d68331ef5fbc8aa28a53781471c15f90ba1ad4f2689d21ce8b9a9d1f1" +dependencies = [ + "async-stream", + "async-trait", + "bigdecimal", + "chrono", + "futures-util", + "log", + "ouroboros", + "pgvector", + "rust_decimal", + "sea-orm-macros", + "sea-query", + "sea-query-binder", + "serde 1.0.219", + "serde_json", + "sqlx", + "strum", + "thiserror 2.0.16", + "time", + "tracing", + "url", + "uuid 1.18.0", +] + +[[package]] +name = "sea-orm-macros" +version = "1.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a489127c872766445b4e28f846825f89a076ac3af2591d1365503a68f93e974c" +dependencies = [ + "heck 0.5.0", + "proc-macro2 1.0.101", + "quote 1.0.40", + "sea-bae", + "syn 2.0.106", + "unicode-ident", +] + +[[package]] +name = "sea-query" +version = "0.32.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a5d1c518eaf5eda38e5773f902b26ab6d5e9e9e2bb2349ca6c64cf96f80448c" +dependencies = [ + "bigdecimal", + "chrono", + "inherent", + "ordered-float", + "rust_decimal", + "serde_json", + "time", + "uuid 1.18.0", +] + +[[package]] +name = "sea-query-binder" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0019f47430f7995af63deda77e238c17323359af241233ec768aba1faea7608" +dependencies = [ + "bigdecimal", + "chrono", + "rust_decimal", + "sea-query", + "serde_json", + "sqlx", + "time", + "uuid 1.18.0", +] + +[[package]] +name = "seahash" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" [[package]] name = "security-framework" @@ -2106,7 +2749,7 @@ version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.2", "core-foundation", "core-foundation-sys", "libc", @@ -2163,16 +2806,16 @@ version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ - "proc-macro2 1.0.95", + "proc-macro2 1.0.101", "quote 1.0.40", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] name = "serde_json" -version = "1.0.142" +version = "1.0.143" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "030fedb782600dcbd6f02d479bf0d817ac3bb40d644745b769d6a96bc3afc5a7" +checksum = "d401abef1d108fbd9cbaebc3e46611f4b1021f714a0597a71f41ee463f5f4a5a" dependencies = [ "itoa", "memchr", @@ -2180,6 +2823,16 @@ dependencies = [ "serde 1.0.219", ] +[[package]] +name = "serde_regex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8136f1a4ea815d7eac4101cfd0b16dc0cb5e1fe1b8609dfd728058656b7badf" +dependencies = [ + "regex", + "serde 1.0.219", +] + [[package]] name = "serde_test" version = "0.8.23" @@ -2201,6 +2854,16 @@ dependencies = [ "serde 1.0.219", ] +[[package]] +name = "serde_with" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "678b5a069e50bf00ecd22d0cd8ddf7c236f68581b03db652061ed5eb13a312ff" +dependencies = [ + "serde 1.0.219", + "serde_with_macros", +] + [[package]] name = "serde_with_macros" version = "1.5.2" @@ -2208,18 +2871,29 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e182d6ec6f05393cc0e5ed1bf81ad6db3a8feedf8ee515ecdd369809bcce8082" dependencies = [ "darling", - "proc-macro2 1.0.95", + "proc-macro2 1.0.101", "quote 1.0.40", "syn 1.0.109", ] +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if 1.0.3", + "cpufeatures", + "digest", +] + [[package]] name = "sha2" version = "0.10.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ - "cfg-if 1.0.1", + "cfg-if 1.0.3", "cpufeatures", "digest", ] @@ -2232,13 +2906,29 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "signal-hook-registry" -version = "1.4.5" +version = "1.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" +checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b" dependencies = [ "libc", ] +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest", + "rand_core 0.6.4", +] + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + [[package]] name = "siphasher" version = "1.0.1" @@ -2247,9 +2937,9 @@ checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" [[package]] name = "slab" -version = "0.4.10" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d" +checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" [[package]] name = "slog" @@ -2258,61 +2948,292 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8347046d4ebd943127157b94d63abb990fcf729dc4e9978927fdf4ac3c998d06" [[package]] -name = "slog-scope" -version = "4.4.0" +name = "slog-scope" +version = "4.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f95a4b4c3274cd2869549da82b57ccc930859bdbf5bcea0424bc5f140b3c786" +dependencies = [ + "arc-swap", + "lazy_static", + "slog", +] + +[[package]] +name = "slog-stdlog" +version = "4.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6706b2ace5bbae7291d3f8d2473e2bfab073ccd7d03670946197aec98471fa3e" +dependencies = [ + "log", + "slog", + "slog-scope", +] + +[[package]] +name = "slug" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "882a80f72ee45de3cc9a5afeb2da0331d58df69e4e7d8eeb5d3c7784ae67e724" +dependencies = [ + "deunicode", + "wasm-bindgen", +] + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +dependencies = [ + "serde 1.0.219", +] + +[[package]] +name = "socket2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "socket2" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "sqlx" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fefb893899429669dcdd979aff487bd78f4064e5e7907e4269081e0ef7d97dc" +dependencies = [ + "sqlx-core", + "sqlx-macros", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", +] + +[[package]] +name = "sqlx-core" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee6798b1838b6a0f69c007c133b8df5866302197e404e8b6ee8ed3e3a5e68dc6" +dependencies = [ + "base64 0.22.1", + "bigdecimal", + "bytes", + "chrono", + "crc", + "crossbeam-queue", + "either", + "event-listener", + "futures-core", + "futures-intrusive", + "futures-io", + "futures-util", + "hashbrown 0.15.5", + "hashlink", + "indexmap", + "log", + "memchr", + "once_cell", + "percent-encoding", + "rust_decimal", + "rustls", + "serde 1.0.219", + "serde_json", + "sha2", + "smallvec", + "thiserror 2.0.16", + "time", + "tokio", + "tokio-stream", + "tracing", + "url", + "uuid 1.18.0", + "webpki-roots 0.26.11", +] + +[[package]] +name = "sqlx-macros" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f95a4b4c3274cd2869549da82b57ccc930859bdbf5bcea0424bc5f140b3c786" +checksum = "a2d452988ccaacfbf5e0bdbc348fb91d7c8af5bee192173ac3636b5fb6e6715d" dependencies = [ - "arc-swap", - "lazy_static", - "slog", + "proc-macro2 1.0.101", + "quote 1.0.40", + "sqlx-core", + "sqlx-macros-core", + "syn 2.0.106", ] [[package]] -name = "slog-stdlog" -version = "4.1.1" +name = "sqlx-macros-core" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6706b2ace5bbae7291d3f8d2473e2bfab073ccd7d03670946197aec98471fa3e" +checksum = "19a9c1841124ac5a61741f96e1d9e2ec77424bf323962dd894bdb93f37d5219b" dependencies = [ - "log", - "slog", - "slog-scope", + "dotenvy", + "either", + "heck 0.5.0", + "hex", + "once_cell", + "proc-macro2 1.0.101", + "quote 1.0.40", + "serde 1.0.219", + "serde_json", + "sha2", + "sqlx-core", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", + "syn 2.0.106", + "tokio", + "url", ] [[package]] -name = "slug" -version = "0.1.6" +name = "sqlx-mysql" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "882a80f72ee45de3cc9a5afeb2da0331d58df69e4e7d8eeb5d3c7784ae67e724" +checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526" dependencies = [ - "deunicode", - "wasm-bindgen", + "atoi", + "base64 0.22.1", + "bigdecimal", + "bitflags 2.9.2", + "byteorder", + "bytes", + "chrono", + "crc", + "digest", + "dotenvy", + "either", + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "generic-array", + "hex", + "hkdf", + "hmac", + "itoa", + "log", + "md-5", + "memchr", + "once_cell", + "percent-encoding", + "rand 0.8.5", + "rsa", + "rust_decimal", + "serde 1.0.219", + "sha1", + "sha2", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror 2.0.16", + "time", + "tracing", + "uuid 1.18.0", + "whoami", ] [[package]] -name = "smallvec" -version = "1.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" - -[[package]] -name = "socket2" -version = "0.5.10" +name = "sqlx-postgres" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" +checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46" dependencies = [ - "libc", - "windows-sys 0.52.0", + "atoi", + "base64 0.22.1", + "bigdecimal", + "bitflags 2.9.2", + "byteorder", + "chrono", + "crc", + "dotenvy", + "etcetera", + "futures-channel", + "futures-core", + "futures-util", + "hex", + "hkdf", + "hmac", + "home", + "itoa", + "log", + "md-5", + "memchr", + "num-bigint", + "once_cell", + "rand 0.8.5", + "rust_decimal", + "serde 1.0.219", + "serde_json", + "sha2", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror 2.0.16", + "time", + "tracing", + "uuid 1.18.0", + "whoami", ] [[package]] -name = "socket2" -version = "0.6.0" +name = "sqlx-sqlite" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807" +checksum = "c2d12fe70b2c1b4401038055f90f151b78208de1f9f89a7dbfd41587a10c3eea" dependencies = [ - "libc", - "windows-sys 0.59.0", + "atoi", + "chrono", + "flume", + "futures-channel", + "futures-core", + "futures-executor", + "futures-intrusive", + "futures-util", + "libsqlite3-sys", + "log", + "percent-encoding", + "serde 1.0.219", + "serde_urlencoded", + "sqlx-core", + "thiserror 2.0.16", + "time", + "tracing", + "url", + "uuid 1.18.0", ] [[package]] @@ -2327,12 +3248,35 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "stringprep" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1" +dependencies = [ + "unicode-bidi", + "unicode-normalization", + "unicode-properties", +] + [[package]] name = "strsim" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +[[package]] +name = "strum" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + [[package]] name = "syn" version = "0.15.44" @@ -2350,18 +3294,18 @@ version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ - "proc-macro2 1.0.95", + "proc-macro2 1.0.101", "quote 1.0.40", "unicode-ident", ] [[package]] name = "syn" -version = "2.0.104" +version = "2.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" +checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" dependencies = [ - "proc-macro2 1.0.95", + "proc-macro2 1.0.101", "quote 1.0.40", "unicode-ident", ] @@ -2378,9 +3322,9 @@ version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ - "proc-macro2 1.0.95", + "proc-macro2 1.0.101", "quote 1.0.40", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -2404,37 +3348,57 @@ dependencies = [ "libc", ] +[[package]] +name = "take_mut" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f764005d11ee5f36500a149ace24e00e3da98b0158b3e2d53a7495660d3f4d60" + +[[package]] +name = "takecell" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20f34339676cdcab560c9a82300c4c2581f68b9369aedf0fae86f2ff9565ff3e" + +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + [[package]] name = "teloxide" -version = "0.5.3" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9964854e5ec3a5a44a9f50ebb7641327f1084ab4fc37a6c4a23cc011a388dc2e" +checksum = "5f79dd283eb21b90451c03fa7c7f83b9985130efb876b33bad89a2c208ccbc16" dependencies = [ - "async-trait", + "aquamarine", "bytes", "derive_more", - "flurry", + "dptree", + "either", "futures", "log", "mime", "pin-project", "serde 1.0.219", "serde_json", - "serde_with_macros", "teloxide-core", "teloxide-macros", "thiserror 1.0.69", "tokio", "tokio-stream", - "tokio-util 0.6.10", + "tokio-util", + "url", ] [[package]] name = "teloxide-core" -version = "0.3.4" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "114c9057a3a2f74d937ece64029b362f583a69fb4b7405722c6dc03cd5bb4658" +checksum = "9e1642a7ef10e7af63b8298c8d13c0f986d4fc646d42649ff060359607f62f69" dependencies = [ + "bitflags 1.3.2", "bytes", "chrono", "derive_more", @@ -2442,42 +3406,45 @@ dependencies = [ "futures", "log", "mime", - "never", "once_cell", "pin-project", + "rc-box", "reqwest", "serde 1.0.219", "serde_json", - "serde_with_macros", + "serde_with", + "take_mut", + "takecell", "thiserror 1.0.69", "tokio", - "tokio-util 0.6.10", + "tokio-util", "url", - "uuid", + "uuid 1.18.0", ] [[package]] name = "teloxide-macros" -version = "0.4.1" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fb7e97b8bef2231aea6643558147c7f9c112675c4ca49f24d8fac2edff1216d" +checksum = "7e2d33d809c3e7161a9ab18bedddf98821245014f0a78fa4d2c9430b2ec018c1" dependencies = [ - "proc-macro2 1.0.95", + "heck 0.4.1", + "proc-macro2 1.0.101", "quote 1.0.40", "syn 1.0.109", ] [[package]] name = "tempfile" -version = "3.20.0" +version = "3.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" +checksum = "15b61f8f20e3a6f7e0649d825294eaf317edce30f82cf6026e7e4cb9222a7d1e" dependencies = [ "fastrand", "getrandom 0.3.3", "once_cell", "rustix 1.0.8", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -2513,11 +3480,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.12" +version = "2.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0" dependencies = [ - "thiserror-impl 2.0.12", + "thiserror-impl 2.0.16", ] [[package]] @@ -2526,27 +3493,52 @@ version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ - "proc-macro2 1.0.95", + "proc-macro2 1.0.101", "quote 1.0.40", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] name = "thiserror-impl" -version = "2.0.12" +version = "2.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960" dependencies = [ - "proc-macro2 1.0.95", + "proc-macro2 1.0.101", "quote 1.0.40", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] -name = "thousands" -version = "0.2.0" +name = "time" +version = "0.3.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde 1.0.219", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bf63baf9f5039dadc247375c29eb13706706cfde997d0330d05aa63a77d8820" +checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" + +[[package]] +name = "time-macros" +version = "0.2.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49" +dependencies = [ + "num-conv", + "time-core", +] [[package]] name = "tiny-keccak" @@ -2567,6 +3559,21 @@ dependencies = [ "zerovec", ] +[[package]] +name = "tinyvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + [[package]] name = "tokio" version = "1.47.1" @@ -2592,9 +3599,9 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ - "proc-macro2 1.0.95", + "proc-macro2 1.0.101", "quote 1.0.40", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -2620,38 +3627,41 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.6.10" +version = "0.7.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36943ee01a6d67977dd3f84a5a1d2efeb4ada3a1ae771cadfaa535d9d9fc6507" +checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5" dependencies = [ "bytes", "futures-core", "futures-sink", - "log", "pin-project-lite", "tokio", ] [[package]] -name = "tokio-util" -version = "0.7.15" +name = "toml" +version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" dependencies = [ - "bytes", - "futures-core", - "futures-sink", - "pin-project-lite", - "tokio", + "serde 1.0.219", ] [[package]] -name = "toml" -version = "0.5.11" +name = "toml_datetime" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" + +[[package]] +name = "toml_edit" +version = "0.22.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ - "serde 1.0.219", + "indexmap", + "toml_datetime", + "winnow", ] [[package]] @@ -2666,10 +3676,23 @@ version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ + "log", "pin-project-lite", + "tracing-attributes", "tracing-core", ] +[[package]] +name = "tracing-attributes" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" +dependencies = [ + "proc-macro2 1.0.101", + "quote 1.0.40", + "syn 2.0.106", +] + [[package]] name = "tracing-core" version = "0.1.34" @@ -2685,6 +3708,19 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "two_timer" +version = "2.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75e22270f53d0cc86e580617796a681e89d5d5c22fa3774d70ba1592915404ee" +dependencies = [ + "chrono", + "lazy_static", + "pidgin", + "regex", + "serde_json", +] + [[package]] name = "typenum" version = "1.18.0" @@ -2753,18 +3789,45 @@ version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" +[[package]] +name = "unicode-bidi" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" + [[package]] name = "unicode-ident" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +[[package]] +name = "unicode-normalization" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-properties" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0" + [[package]] name = "unicode-xid" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + [[package]] name = "url" version = "2.5.4" @@ -2792,6 +3855,18 @@ dependencies = [ "getrandom 0.2.16", ] +[[package]] +name = "uuid" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f33196643e165781c20a5ead5582283a7dacbb87855d867fbc2df3f81eddc1be" +dependencies = [ + "getrandom 0.3.3", + "js-sys", + "serde 1.0.219", + "wasm-bindgen", +] + [[package]] name = "vcpkg" version = "0.2.15" @@ -2844,13 +3919,19 @@ dependencies = [ "wit-bindgen-rt", ] +[[package]] +name = "wasite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" + [[package]] name = "wasm-bindgen" version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" dependencies = [ - "cfg-if 1.0.1", + "cfg-if 1.0.3", "once_cell", "rustversion", "wasm-bindgen-macro", @@ -2864,9 +3945,9 @@ checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" dependencies = [ "bumpalo", "log", - "proc-macro2 1.0.95", + "proc-macro2 1.0.101", "quote 1.0.40", - "syn 2.0.104", + "syn 2.0.106", "wasm-bindgen-shared", ] @@ -2876,7 +3957,7 @@ version = "0.4.50" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" dependencies = [ - "cfg-if 1.0.1", + "cfg-if 1.0.3", "js-sys", "once_cell", "wasm-bindgen", @@ -2899,9 +3980,9 @@ version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ - "proc-macro2 1.0.95", + "proc-macro2 1.0.101", "quote 1.0.40", - "syn 2.0.104", + "syn 2.0.106", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -2939,35 +4020,41 @@ dependencies = [ ] [[package]] -name = "winapi" -version = "0.3.9" +name = "webpki-roots" +version = "0.26.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", + "webpki-roots 1.0.2", ] [[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" +name = "webpki-roots" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +checksum = "7e8983c3ab33d6fb807cfcdad2491c4ea8cbc8ed839181c7dfd9c67c83e261b2" +dependencies = [ + "rustls-pki-types", +] [[package]] -name = "winapi-util" -version = "0.1.9" +name = "whoami" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +checksum = "5d4a4db5077702ca3015d3d02d74974948aba2ad9e12ab7df718ee64ccd7e97d" dependencies = [ - "windows-sys 0.59.0", + "libredox", + "wasite", ] [[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" +name = "winapi-util" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +checksum = "0978bf7171b3d90bac376700cb56d606feb40f251a475a5d6634613564460b22" +dependencies = [ + "windows-sys 0.60.2", +] [[package]] name = "windows-core" @@ -2988,9 +4075,9 @@ version = "0.60.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" dependencies = [ - "proc-macro2 1.0.95", + "proc-macro2 1.0.101", "quote 1.0.40", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -2999,9 +4086,9 @@ version = "0.59.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" dependencies = [ - "proc-macro2 1.0.95", + "proc-macro2 1.0.101", "quote 1.0.40", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -3250,13 +4337,22 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" +[[package]] +name = "winnow" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95" +dependencies = [ + "memchr", +] + [[package]] name = "winreg" version = "0.50.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" dependencies = [ - "cfg-if 1.0.1", + "cfg-if 1.0.3", "windows-sys 0.48.0", ] @@ -3266,7 +4362,7 @@ version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.2", ] [[package]] @@ -3275,6 +4371,15 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + [[package]] name = "yaml-rust" version = "0.4.5" @@ -3284,6 +4389,12 @@ dependencies = [ "linked-hash-map 0.5.6", ] +[[package]] +name = "yansi" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" + [[package]] name = "yoke" version = "0.8.0" @@ -3302,9 +4413,9 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" dependencies = [ - "proc-macro2 1.0.95", + "proc-macro2 1.0.101", "quote 1.0.40", - "syn 2.0.104", + "syn 2.0.106", "synstructure", ] @@ -3323,9 +4434,9 @@ version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" dependencies = [ - "proc-macro2 1.0.95", + "proc-macro2 1.0.101", "quote 1.0.40", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -3343,12 +4454,18 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ - "proc-macro2 1.0.95", + "proc-macro2 1.0.101", "quote 1.0.40", - "syn 2.0.104", + "syn 2.0.106", "synstructure", ] +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" + [[package]] name = "zerotrie" version = "0.2.2" @@ -3362,9 +4479,9 @@ dependencies = [ [[package]] name = "zerovec" -version = "0.11.2" +version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428" +checksum = "e7aa2bd55086f1ab526693ecbe444205da57e25f4489879da80635a46d90e73b" dependencies = [ "yoke", "zerofrom", @@ -3377,7 +4494,7 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" dependencies = [ - "proc-macro2 1.0.95", + "proc-macro2 1.0.101", "quote 1.0.40", - "syn 2.0.104", + "syn 2.0.106", ] diff --git a/Cargo.toml b/Cargo.toml index 6ac1f9bd..4b86e78a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,44 +1,35 @@ -[package] -authors = ["Berkus Decker "] -edition = "2021" -name = "aegl_bot" -version = "0.3.0" +[workspace] +members = ["bot"] +resolver = "2" -[dependencies] -regex = "1" +[workspace.dependencies] +anyhow = "1" chrono = "0.4" chrono-tz = "0.10" -chrono-english = "0.1" -diesel_derives_extra = "0.2" -diesel_derives_traits = "0.2" -#diesel-derive-more = "1.1" -diesel_logger = "0.1" -diesel_migrations = "1.4" # -- retry with "extern crate" macros -diesel = { version = "1.4", features = [ - "postgres", - "chrono", - "serde_json", - "r2d2", -] } -r2d2 = "0.8" +culpa = "1.0" dotenv = "0.15" -#anyhow = "1.0" -futures = "0.3" -serde = { version = "1.0", features = ["derive"] } -serde_json = "1.0" -teloxide = { version = "0.5", features = ["macros"] } -log = "0.4" fern = { version = "0.7", features = ["colored"] } -futures-retry = "0.6" +include_dir = { version = "0.7.4", features = ["glob", "nightly"] } itertools = "0.14" +libbot = { path = "./lib" } +log = "0.4" +paste = "1" # plurals = "0.3" -thousands = "0.2.0" -tokio = { version = "1.12", features = ["macros", "rt-multi-thread"] } -tokio-util = { version = "0.6", features = ["codec"] } +regex = "1" riker = "0.4" -paste = "1" +sea-orm = { version = "1.1", features = [ + "macros", + "runtime-tokio-rustls", + "sqlx-postgres", + "with-chrono", +] } +sea-orm-migration = { version = "1.1", features = [ + "runtime-tokio-rustls", + "sqlx-postgres", + "with-chrono", +] } +serde = { version = "1.0", features = ["derive"] } +teloxide = { version = "0.13", features = ["macros"] } tera = "1" -include_dir = { version = "0.7.4", features = ["glob", "metadata", "nightly"] } - -[target.'cfg(target_os="linux")'.dependencies] -procfs = "0.17" +tokio = { version = "1", features = ["macros", "rt-multi-thread"] } +two_timer = "2.2" # doesn't parse "in 3 hours" diff --git a/bot/Cargo.toml b/bot/Cargo.toml new file mode 100644 index 00000000..8fda6469 --- /dev/null +++ b/bot/Cargo.toml @@ -0,0 +1,29 @@ +[package] +authors = ["Berkus Decker "] +edition = "2021" +name = "aegl_bot" +version = "0.4.0" +publish = false + +[dependencies] +anyhow.workspace = true +chrono-tz.workspace = true +chrono.workspace = true +dotenv.workspace = true +fern.workspace = true +futures.workspace = true +include_dir.workspace = true +itertools.workspace = true +libbot.workspace = true +log.workspace = true +paste.workspace = true +regex.workspace = true +riker.workspace = true +serde.workspace = true +teloxide.workspace = true +tera.workspace = true +tokio.workspace = true +two_timer.workspace = true + +[target.'cfg(target_os="linux")'.dependencies] +procfs = "0.17" diff --git a/bot/src/actors/bot_actor.rs b/bot/src/actors/bot_actor.rs new file mode 100644 index 00000000..a082b9b4 --- /dev/null +++ b/bot/src/actors/bot_actor.rs @@ -0,0 +1,290 @@ +use { + crate::{ + actors::reminder_actor::{ + ReminderActor, ScheduleNextDay, ScheduleNextMinute, ScheduleNextWeek, + }, + commands::*, + BotCommand, BotConnection, + }, + kameo::{ + actor::ActorRef, + error::Infallible, + message::{Context, Message}, + Actor, + }, + std::fmt::Formatter, + teloxide::{ + prelude::*, + types::{ChatId, ParseMode}, + }, + tokio::sync::broadcast, +}; + +pub struct BotActor { + pub bot: Bot, + bot_name: String, + lfg_chat_id: i64, + update_sender: broadcast::Sender, + connection_pool: BotConnection, + commands_list: Vec<(String, String)>, +} + +unsafe impl Send for BotActor {} + +impl std::fmt::Debug for BotActor { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "BotActor") + } +} + +#[derive(Debug, Clone)] +pub struct ActorUpdateMessage { + pub requester: Bot, + pub update: teloxide::types::Message, +} + +impl ActorUpdateMessage { + pub fn new(requester: Bot, update: teloxide::types::Message) -> Self { + Self { requester, update } + } +} + +impl BotActor { + // Public API + + pub async fn new( + name: &str, + bot: Bot, + update_sender: broadcast::Sender, + lfg_chat_id: i64, + ) -> Self { + let connection_pool = crate::establish_db_connection().await.unwrap(); + BotActor { + bot, + bot_name: name.to_string(), + lfg_chat_id, + update_sender, + connection_pool, + commands_list: vec![], + } + } + + pub fn list_commands(&self) -> Vec<(String, String)> { + self.commands_list.clone() + } + + // Internal helpers + + // fn handle_error(error: anyhow::Error) -> RetryPolicy { + // // count errors + // log::error!("handle_error"); + // match error.downcast_ref::() { + // Some(te) => { + // log::error!("Telegram error: {}, retrying connection.", te); + // RetryPolicy::WaitRetry(Duration::from_secs(30)) + // } + // None => { + // log::error!("handle_error didn't match, real error {:?}", error); + // //handle_error didnt match, real error Io(Custom { kind: Other, error: StringError("failed to lookup address information: nodename nor servname provided, or not known") }) + // RetryPolicy::ForwardError(error) + // } + // } + // } +} + +use crate::commands::match_command; + +impl Actor for BotActor { + type Args = Self; + type Error = Infallible; + + async fn on_start(args: Self::Args, actor_ref: ActorRef) -> Result { + macro_rules! new_command { + ($T:ident, $args:expr) => { + let cmd = $T::spawn($T::new( + actor_ref.clone(), + $args.bot_name.clone(), + $args.connection_pool.clone(), + )); + $args + .commands_list + .push(($T::prefix().into(), $T::description().into())); + + // Subscribe to updates + let mut update_receiver = $args.update_sender.subscribe(); + let cmd_clone = cmd.clone(); + let bot_name = $args.bot_name.clone(); + tokio::spawn(async move { + while let Ok(msg) = update_receiver.recv().await { + if let (Some(_), _) = + match_command(msg.update.text(), $T::prefix(), &bot_name) + { + let _ = cmd_clone.tell(msg).await; + } + } + }); + }; + } + + let mut bot_actor = args; + + new_command!(ActivitiesCommand, bot_actor); + new_command!(CancelCommand, bot_actor); + new_command!(ChatidCommand, bot_actor); + new_command!(D1weekCommand, bot_actor); + new_command!(D2weekCommand, bot_actor); + new_command!(EditCommand, bot_actor); + new_command!(EditGuardianCommand, bot_actor); + new_command!(HelpCommand, bot_actor); + new_command!(JoinCommand, bot_actor); + new_command!(LfgCommand, bot_actor); + new_command!(ListCommand, bot_actor); + new_command!(ManageCommand, bot_actor); + new_command!(PsnCommand, bot_actor); + new_command!(UptimeCommand, bot_actor); + new_command!(WhoisCommand, bot_actor); + + // Create reminder tasks actor + let reminders = ReminderActor::spawn(ReminderActor::new( + actor_ref.clone(), + bot_actor.lfg_chat_id, + bot_actor.connection_pool.clone(), + )); + + // Schedule first run, the actor handler will reschedule. + let _ = reminders.tell(ScheduleNextMinute).await; + let _ = reminders.tell(ScheduleNextDay).await; + let _ = reminders.tell(ScheduleNextWeek).await; + + Ok(bot_actor) + } +} + +impl BotActor { + pub async fn create( + bot_name: String, + bot: Bot, + update_sender: broadcast::Sender, + lfg_chat: i64, + ) -> Self { + Self::new(&bot_name, bot, update_sender, lfg_chat).await + } +} + +#[derive(Clone, Debug)] +pub enum Format { + Plain, + Markdown, + Html, +} + +#[derive(Clone, Debug)] +pub enum Notify { + Off, + On, +} + +#[derive(Clone, Debug)] +pub struct SendMessage(pub String, pub ChatId, pub Format, pub Notify); + +#[derive(Clone, Debug)] +pub struct SendMessageReply(pub String, pub ActorUpdateMessage, pub Format, pub Notify); + +#[derive(Clone, Debug)] +pub struct ListCommands(pub ActorUpdateMessage); + +impl Message for BotActor { + type Reply = (); + + async fn handle( + &mut self, + msg: SendMessage, + _ctx: &mut Context, + ) -> Self::Reply { + log::debug!("SendMessage: {}", &msg.0); + let resp = self + .bot + .send_message(msg.1, msg.0) + .disable_notification(match msg.3 { + Notify::On => false, + Notify::Off => true, + }) + .link_preview_options(teloxide::types::LinkPreviewOptions { + is_disabled: true, + url: None, + prefer_small_media: false, + prefer_large_media: false, + show_above_text: false, + }); + + let resp = match msg.2 { + Format::Html => resp.parse_mode(ParseMode::Html), + Format::Markdown => resp.parse_mode(ParseMode::MarkdownV2), + Format::Plain => resp, + }; + + let _ = resp.send().await; + } +} + +impl Message for BotActor { + type Reply = (); + + async fn handle( + &mut self, + msg: SendMessageReply, + _ctx: &mut Context, + ) -> Self::Reply { + log::debug!("SendMessageReply: {}", &msg.0); + let message = msg.1; + + let fut = self + .bot + .send_message(message.update.chat.id, msg.0) + .reply_parameters(teloxide::types::ReplyParameters::new(message.update.id)) + .disable_notification(match msg.3 { + Notify::On => false, + Notify::Off => true, + }) + .link_preview_options(teloxide::types::LinkPreviewOptions { + is_disabled: true, + url: None, + prefer_small_media: false, + prefer_large_media: false, + show_above_text: false, + }); + + let fut = match msg.2 { + Format::Html => fut.parse_mode(ParseMode::Html), + Format::Markdown => fut.parse_mode(ParseMode::MarkdownV2), + Format::Plain => fut, + }; + + let _ = fut.send().await; + } +} + +impl Message for BotActor { + type Reply = (); + + async fn handle( + &mut self, + msg: ListCommands, + ctx: &mut Context, + ) -> Self::Reply { + log::debug!("ListCommands"); + let message = msg.0; + + let mut sorted_cmds = self.list_commands(); + sorted_cmds.sort_by_key(|v| v.0.clone()); + let reply = sorted_cmds.into_iter().fold( + "Help 🚑\nThese are the registered commands for this Bot:\n\n".into(), + |acc, pair| format!("{}{} — {}\n\n", acc, pair.0, pair.1), + ); + + let _ = ctx + .actor_ref() + .tell(SendMessageReply(reply, message, Format::Html, Notify::Off)) + .try_send(); // @todo use unbounded mailbox for bot_actor? prolly not + } +} diff --git a/bot/src/actors/mod.rs b/bot/src/actors/mod.rs new file mode 100644 index 00000000..cf7c1736 --- /dev/null +++ b/bot/src/actors/mod.rs @@ -0,0 +1,2 @@ +pub mod bot_actor; +pub mod reminder_actor; diff --git a/bot/src/actors/reminder_actor.rs b/bot/src/actors/reminder_actor.rs new file mode 100644 index 00000000..78911561 --- /dev/null +++ b/bot/src/actors/reminder_actor.rs @@ -0,0 +1,213 @@ +use { + crate::actors::bot_actor::{Format, Notify, SendMessage}, + chrono::Timelike, + culpa::throws, + entity::prelude::*, + kameo::{actor::ActorRef, error::Infallible, message::*, Actor}, + libbot::{ + datetime::{d2_reset_time, reference_date, start_at_time, start_at_weekday_time}, + services::destiny_schedule::{this_week_in_d1, this_week_in_d2}, + }, + sea_orm::DatabaseConnection, + teloxide::types::ChatId, +}; + +#[derive(Clone)] +pub struct ReminderActor { + bot_ref: ActorRef, + lfg_chat: i64, + connection_pool: DatabaseConnection, +} + +impl Actor for ReminderActor { + type Args = Self; + type Error = Infallible; + + async fn on_start(args: Self::Args, _actor_ref: ActorRef) -> Result { + Ok(args) + } +} + +impl ReminderActor { + pub fn new( + bot_ref: ActorRef, + lfg_chat: i64, + connection_pool: DatabaseConnection, + ) -> Self { + Self { + bot_ref, + lfg_chat, + connection_pool, + } + } + + fn connection(&self) -> &DatabaseConnection { + &self.connection_pool + } +} + +#[derive(Clone, Debug)] +pub struct Reminders; + +#[derive(Clone, Debug)] +pub struct DailyReset; + +#[derive(Clone, Debug)] +pub struct WeeklyReset; + +impl Message for ReminderActor { + type Reply = (); + + async fn handle( + &mut self, + _msg: Reminders, + ctx: &mut Context, + ) -> Self::Reply { + let bot_ref = self.bot_ref.clone(); + let connection = self.connection(); + let lfg_chat = self.lfg_chat; + + let found = PlannedActivities::upcoming_activities_alert(connection).await; + + if let Some(upcoming_events) = found { + // @Todo: this text should be populated in tera template in `bot` + let text = upcoming_events + .into_iter() + .fold("Activities starting soon:\n\n".to_owned(), |acc, event| { + acc + &format!("Activity {} starting soon\n\n", event.id) + }); + + let _ = bot_ref + .tell(SendMessage( + text, + ChatId(lfg_chat), + Format::Html, + Notify::On, + )) + .await; + } + + let _ = ctx.actor_ref().tell(ScheduleNextMinute).await; + } +} + +// 1. Daily resets at 20:00 MSK (17:00 UTC) every day +#[throws(kameo::error::SendError)] +pub async fn daily_reset(bot: ActorRef, lfg_chat: ChatId) { + bot.tell(SendMessage( + "⚡️ Daily reset".into(), + lfg_chat, + Format::Plain, + Notify::Off, + )) + .await?; +} + +impl Message for ReminderActor { + type Reply = anyhow::Result<()>; + + #[throws(anyhow::Error)] + async fn handle(&mut self, _msg: DailyReset, ctx: &mut Context) { + daily_reset(self.bot_ref.clone(), ChatId(self.lfg_chat)).await?; + ctx.actor_ref().tell(ScheduleNextDay).await?; + } +} + +// 2. Weekly (main) resets at 20:00 msk every Tue +// 6. On main reset: change in Dreaming City curse +// dreaming city on 3-week schedule +// 7. On main reset: change in Dreaming City Ascendant Challenges +// dreaming city challenges on 6-week schedule +#[throws(kameo::error::SendError)] +pub async fn major_weekly_reset( + bot: ActorRef, + lfg_chat: ChatId, +) { + let msg = format!( + "⚡️ Weekly reset:\n\n{d1week}\n\n{d2week}", + d1week = this_week_in_d1(), + d2week = this_week_in_d2(), + ); + bot.tell(SendMessage(msg, lfg_chat, Format::Markdown, Notify::Off)) + .await?; +} + +impl Message for ReminderActor { + type Reply = anyhow::Result<()>; + + #[throws(anyhow::Error)] + async fn handle(&mut self, _msg: WeeklyReset, ctx: &mut Context) { + major_weekly_reset(self.bot_ref.clone(), ChatId(self.lfg_chat)).await?; + ctx.actor_ref().tell(ScheduleNextWeek).await?; + } +} + +#[derive(Clone, Debug)] +pub struct ScheduleNextMinute; + +#[derive(Clone, Debug)] +pub struct ScheduleNextDay; + +#[derive(Clone, Debug)] +pub struct ScheduleNextWeek; + +impl Message for ReminderActor { + type Reply = anyhow::Result<()>; + + #[throws(anyhow::Error)] + async fn handle(&mut self, _msg: ScheduleNextMinute, ctx: &mut Context) { + let target_time = (reference_date() + chrono::Duration::minutes(1)) + .with_second(0) + .unwrap(); + let actor_ref = ctx.actor_ref().clone(); + + let now = std::time::SystemTime::now(); + let target_system_time = + std::time::UNIX_EPOCH + std::time::Duration::from_secs(target_time.timestamp() as u64); + if let Ok(duration) = target_system_time.duration_since(now) { + tokio::time::sleep(duration).await; + actor_ref.tell(Reminders).try_send()?; + } + } +} + +impl Message for ReminderActor { + type Reply = (); + async fn handle( + &mut self, + _msg: ScheduleNextDay, + ctx: &mut Context, + ) -> Self::Reply { + let target_time = start_at_time(reference_date(), d2_reset_time()); + let actor_ref = ctx.actor_ref().clone(); + + let now = std::time::SystemTime::now(); + let target_system_time = + std::time::UNIX_EPOCH + std::time::Duration::from_secs(target_time.timestamp() as u64); + if let Ok(duration) = target_system_time.duration_since(now) { + tokio::time::sleep(duration).await; + let _ = actor_ref.tell(DailyReset).await; + } + } +} + +impl Message for ReminderActor { + type Reply = (); + async fn handle( + &mut self, + _msg: ScheduleNextWeek, + ctx: &mut Context, + ) -> Self::Reply { + let target_time = + start_at_weekday_time(reference_date(), chrono::Weekday::Tue, d2_reset_time()); + let actor_ref = ctx.actor_ref().clone(); + + let now = std::time::SystemTime::now(); + let target_system_time = + std::time::UNIX_EPOCH + std::time::Duration::from_secs(target_time.timestamp() as u64); + if let Ok(duration) = target_system_time.duration_since(now) { + tokio::time::sleep(duration).await; + let _ = actor_ref.tell(WeeklyReset).await; + } + } +} diff --git a/src/bin/bot.rs b/bot/src/bin/bot.rs similarity index 95% rename from src/bin/bot.rs rename to bot/src/bin/bot.rs index 49a62e68..a91d1c29 100644 --- a/src/bin/bot.rs +++ b/bot/src/bin/bot.rs @@ -4,7 +4,10 @@ // (There are now several rust impls including https://lib.rs/crates/two_timer and https://lib.rs/crates/intervalle) use { - aegl_bot::bot_actor::{ActorUpdateMessage, BotActor, UpdateMessage}, + aegl_bot::{ + actors::bot_actor::{ActorUpdateMessage, BotActor}, + establish_db_connection, + }, dotenv::dotenv, // riker::prelude::*, doesn't work here! riker::actors::{channel, ActorRefFactory, ActorSystem, ChannelRef, Publish, Tell}, @@ -76,7 +79,7 @@ async fn main() { dotenv().ok(); setup_logging().expect("failed to initialize logging"); - aegl_bot::datetime::bot_start_time(); // Mark start timestamp + libbot::datetime::bot_start_time(); // Mark start timestamp // TimeZone.setDefault(TimeZone.getTimeZone(config.getString("bot.timezone"))) let bot_name = env::var("TELEGRAM_BOT_NAME").expect("TELEGRAM_BOT_NAME must be set"); diff --git a/src/commands/activities_command.rs b/bot/src/commands/activities_command.rs similarity index 99% rename from src/commands/activities_command.rs rename to bot/src/commands/activities_command.rs index 36c8eeba..008a20ec 100644 --- a/src/commands/activities_command.rs +++ b/bot/src/commands/activities_command.rs @@ -1,6 +1,6 @@ use { crate::{ - bot_actor::{ActorUpdateMessage, Format, Notify, SendMessageReply}, + actors::bot_actor::{ActorUpdateMessage, Format, Notify, SendMessageReply}, commands::{admin_check, match_command}, models::{Activity, ActivityShortcut, NewActivity, NewActivityShortcut}, BotCommand, diff --git a/src/commands/cancel_command.rs b/bot/src/commands/cancel_command.rs similarity index 96% rename from src/commands/cancel_command.rs rename to bot/src/commands/cancel_command.rs index 1cb60d37..55d917d4 100644 --- a/src/commands/cancel_command.rs +++ b/bot/src/commands/cancel_command.rs @@ -1,13 +1,13 @@ use { crate::{ - bot_actor::{ActorUpdateMessage, Format, Notify, SendMessageReply}, + actors::bot_actor::ActorUpdateMessage, commands::{decapitalize, match_command, validate_username}, - datetime::{format_start_time, reference_date}, models::PlannedActivity, BotCommand, }, chrono::Duration, diesel_derives_traits::Model, + libbot::datetime::{format_start_time, reference_date}, riker::actors::Tell, }; diff --git a/src/commands/chatid_command.rs b/bot/src/commands/chatid_command.rs similarity index 91% rename from src/commands/chatid_command.rs rename to bot/src/commands/chatid_command.rs index 389106cc..130a12a8 100644 --- a/src/commands/chatid_command.rs +++ b/bot/src/commands/chatid_command.rs @@ -1,6 +1,6 @@ use { crate::{ - bot_actor::{ActorUpdateMessage, Format, Notify, SendMessageReply}, + actors::bot_actor::{ActorUpdateMessage, Format, Notify, SendMessageReply}, commands::match_command, BotCommand, }, diff --git a/src/commands/d2week_command.rs b/bot/src/commands/d2week_command.rs similarity index 85% rename from src/commands/d2week_command.rs rename to bot/src/commands/d2week_command.rs index 1364f80d..8d6d1f0c 100644 --- a/src/commands/d2week_command.rs +++ b/bot/src/commands/d2week_command.rs @@ -1,10 +1,10 @@ use { crate::{ - bot_actor::{ActorUpdateMessage, Format, Notify, SendMessageReply}, + actors::bot_actor::{ActorUpdateMessage, Format, Notify, SendMessageReply}, commands::match_command, - services::this_week_in_d2, BotCommand, }, + libbot::services::destiny_schedule::this_week_in_d2, riker::actors::Tell, }; diff --git a/src/commands/dweek_command.rs b/bot/src/commands/dweek_command.rs similarity index 85% rename from src/commands/dweek_command.rs rename to bot/src/commands/dweek_command.rs index a9217e90..8d0b7737 100644 --- a/src/commands/dweek_command.rs +++ b/bot/src/commands/dweek_command.rs @@ -1,10 +1,10 @@ use { crate::{ - bot_actor::{ActorUpdateMessage, Format, Notify, SendMessageReply}, + actors::bot_actor::{ActorUpdateMessage, Format, Notify, SendMessageReply}, commands::match_command, - services::this_week_in_d1, BotCommand, }, + libbot::services::destiny_schedule::this_week_in_d1, riker::actors::Tell, }; diff --git a/src/commands/edit_command.rs b/bot/src/commands/edit_command.rs similarity index 99% rename from src/commands/edit_command.rs rename to bot/src/commands/edit_command.rs index 03c99e70..50274bf4 100644 --- a/src/commands/edit_command.rs +++ b/bot/src/commands/edit_command.rs @@ -2,7 +2,6 @@ use { crate::{ bot_actor::{ActorUpdateMessage, Format, Notify, SendMessageReply}, commands::{match_command, validate_username}, - datetime::reference_date, models::{ActivityShortcut, PlannedActivity}, BotCommand, }, @@ -11,6 +10,7 @@ use { chrono_tz::Europe::Moscow, diesel_derives_traits::Model, riker::actors::Tell, + libbot::datetime::reference_date, }; command_actor!(EditCommand, [ActorUpdateMessage]); diff --git a/src/commands/editguar_command.rs b/bot/src/commands/editguar_command.rs similarity index 97% rename from src/commands/editguar_command.rs rename to bot/src/commands/editguar_command.rs index 257bf1df..40d93667 100644 --- a/src/commands/editguar_command.rs +++ b/bot/src/commands/editguar_command.rs @@ -1,6 +1,6 @@ use { crate::{ - bot_actor::{ActorUpdateMessage, Format, Notify, SendMessageReply}, + actors::bot_actor::ActorUpdateMessage, commands::{admin_check, guardian_lookup, match_command, validate_username}, render_template, BotCommand, }, @@ -102,7 +102,7 @@ impl Receive for EditGuardianCommand { .psn_clan .clone() .map(|s| format!("[{}] ", s)) - .unwrap_or("".into()), + .unwrap_or_default(), name = guardian.format_name(), email = guardian.email.clone().unwrap_or("".into()), admin = if guardian.is_superadmin { diff --git a/src/commands/help_command.rs b/bot/src/commands/help_command.rs similarity index 91% rename from src/commands/help_command.rs rename to bot/src/commands/help_command.rs index 7043f8dc..a0a5f21e 100644 --- a/src/commands/help_command.rs +++ b/bot/src/commands/help_command.rs @@ -1,6 +1,6 @@ use { crate::{ - bot_actor::{ActorUpdateMessage, ListCommands}, + actors::bot_actor::{ActorUpdateMessage, ListCommands}, commands::match_command, BotCommand, }, diff --git a/src/commands/join_command.rs b/bot/src/commands/join_command.rs similarity index 99% rename from src/commands/join_command.rs rename to bot/src/commands/join_command.rs index 041ee6bf..a1381ef5 100644 --- a/src/commands/join_command.rs +++ b/bot/src/commands/join_command.rs @@ -97,7 +97,6 @@ impl Receive for JoinCommand { .expect("Unexpected error saving group joiner"); // join/joined template - let guar_name = guardian.to_string(); let act_name = planned.activity(&connection).format_name(); let act_time = decapitalize(&format_start_time(planned.start, reference_date())); diff --git a/src/commands/lfg_command.rs b/bot/src/commands/lfg_command.rs similarity index 97% rename from src/commands/lfg_command.rs rename to bot/src/commands/lfg_command.rs index 4a178997..360f6b92 100644 --- a/src/commands/lfg_command.rs +++ b/bot/src/commands/lfg_command.rs @@ -1,8 +1,7 @@ use { crate::{ - bot_actor::{ActorUpdateMessage, Format, Notify, SendMessageReply}, + actors::bot_actor::{ActorUpdateMessage, Format, Notify, SendMessageReply}, commands::{match_command, validate_username}, - datetime::{format_start_time, reference_date}, models::{Activity, ActivityShortcut, NewPlannedActivity, NewPlannedActivityMember}, BotCommand, }, @@ -11,6 +10,7 @@ use { chrono_tz::Europe::Moscow, diesel::{self, prelude::*}, diesel_derives_traits::{Model, NewModel}, + libbot::datetime::{format_start_time, reference_date}, riker::actors::Tell, }; diff --git a/src/commands/list_command.rs b/bot/src/commands/list_command.rs similarity index 98% rename from src/commands/list_command.rs rename to bot/src/commands/list_command.rs index 895a05f2..27428bcd 100644 --- a/src/commands/list_command.rs +++ b/bot/src/commands/list_command.rs @@ -2,6 +2,7 @@ use { crate::{ bot_actor::{ActorUpdateMessage, Format, Notify, SendMessageReply}, commands::{match_command, validate_username}, + datetime::nowtz, models::PlannedActivity, render_template, BotCommand, }, diff --git a/src/commands/manage_command.rs b/bot/src/commands/manage_command.rs similarity index 97% rename from src/commands/manage_command.rs rename to bot/src/commands/manage_command.rs index c21144f4..1b6c7ddf 100644 --- a/src/commands/manage_command.rs +++ b/bot/src/commands/manage_command.rs @@ -1,6 +1,6 @@ use { crate::{ - bot_actor::{ActorUpdateMessage, Format, Notify, SendMessageReply}, + actors::bot_actor::{ActorUpdateMessage, Format, Notify, SendMessageReply}, commands::{admin_check, guardian_lookup, match_command}, BotCommand, }, @@ -126,8 +126,10 @@ impl Receive for ManageCommand { // } impl ManageCommand { fn list_admins_subcommand(&self, message: &ActorUpdateMessage) { - use crate::{models::Guardian, schema::guardians::dsl::*}; - use diesel::prelude::*; + use { + crate::{models::Guardian, schema::guardians::dsl::*}, + diesel::prelude::*, + }; let connection = self.connection(); diff --git a/src/commands/mod.rs b/bot/src/commands/mod.rs similarity index 81% rename from src/commands/mod.rs rename to bot/src/commands/mod.rs index 85d5ebea..9d3e3bf8 100644 --- a/src/commands/mod.rs +++ b/bot/src/commands/mod.rs @@ -1,6 +1,6 @@ use { crate::{ - bot_actor::{ActorUpdateMessage, BotActorMsg, Format, Notify, SendMessageReply}, + actors::bot_actor::{ActorUpdateMessage, Format, Notify, SendMessageReply}, models::Guardian, schema::guardians::dsl::*, DbConnection, @@ -21,19 +21,71 @@ macro_rules! command_actor { #[derive(Clone)] #[actor($($msgs)*)] pub struct $name { - bot_ref: ActorRef, + bot_ref: ActorRef<$crate::actors::bot_actor::BotActor>, bot_name: String, connection_pool: DbConnPool, } + impl NamedActor for $name { + fn actor_name() -> String { std::stringify!($name).into() } + } + impl $name { pub fn connection(&self) -> BotConnection { self.connection_pool.get().unwrap() } - } - impl NamedActor for $name { - fn actor_name() -> String { std::stringify!($name).into() } + pub fn new( + bot_ref: ActorRef<$crate::actors::bot_actor::BotActor>, + bot_name: String, + connection_pool: BotConnection, + ) -> Self { + Self { + bot_ref, + bot_name, + connection_pool, + } + } + + pub fn connection(&self) -> &BotConnection { + &self.connection_pool + } + + #[allow(dead_code, reason = "help_command doesn't use those")] + async fn send_reply_with_format( + &self, + message: &$crate::actors::bot_actor::ActorUpdateMessage, + reply: S, + format: $crate::actors::bot_actor::Format, + ) where + S: Into, + { + let _ = self + .bot_ref + .tell($crate::actors::bot_actor::SendMessageReply( + reply.into(), + message.clone(), + format, + $crate::actors::bot_actor::Notify::Off, + )) + .await; + } + + #[allow(dead_code, reason = "help_command doesn't use those")] + async fn send_reply( + &self, + message: &$crate::actors::bot_actor::ActorUpdateMessage, + reply: S, + ) where + S: Into, + { + self.send_reply_with_format( + message, + reply, + $crate::actors::bot_actor::Format::Plain, + ) + .await; + } } impl Actor for $name { @@ -91,8 +143,8 @@ pub fn decapitalize(s: &str) -> String { } /// Return a guardian record if message author is registered in Guardians table, `None` otherwise. -pub fn validate_username( - bot: &ActorRef, +pub async fn validate_username( + bot: &ActorRef, message: &ActorUpdateMessage, connection: &DbConnection, ) -> Option { @@ -147,8 +199,8 @@ pub fn validate_username( } /// Return a guardian record if message author is an admin user, `None` otherwise. -pub fn admin_check( - bot: &ActorRef, +pub async fn admin_check( + bot: &ActorRef, message: &ActorUpdateMessage, connection: &DbConnection, ) -> Option { diff --git a/src/commands/psn_command.rs b/bot/src/commands/psn_command.rs similarity index 100% rename from src/commands/psn_command.rs rename to bot/src/commands/psn_command.rs diff --git a/src/commands/uptime_command.rs b/bot/src/commands/uptime_command.rs similarity index 96% rename from src/commands/uptime_command.rs rename to bot/src/commands/uptime_command.rs index 18dcbdcd..6cc3c1d6 100644 --- a/src/commands/uptime_command.rs +++ b/bot/src/commands/uptime_command.rs @@ -50,7 +50,7 @@ impl Receive for UptimeCommand { fn receive(&mut self, _ctx: &Context, msg: ActorUpdateMessage, _sender: Sender) { if let (Some(_), _) = match_command(msg.update.text(), Self::prefix(), &self.bot_name) { - let uptime = crate::datetime::format_uptime(); + let uptime = libbot::datetime::format_uptime(); let message = format!("- ⏰ Started {uptime}\n{}", get_process_info()); self.bot_ref.tell( SendMessageReply(message, msg, Format::Plain, Notify::Off), diff --git a/src/commands/whois_command.rs b/bot/src/commands/whois_command.rs similarity index 96% rename from src/commands/whois_command.rs rename to bot/src/commands/whois_command.rs index 1436de3f..49d687a2 100644 --- a/src/commands/whois_command.rs +++ b/bot/src/commands/whois_command.rs @@ -1,6 +1,6 @@ use { crate::{ - bot_actor::{ActorUpdateMessage, Format, Notify, SendMessageReply}, + actors::bot_actor::ActorUpdateMessage, commands::{guardian_lookup, match_command, validate_username}, BotCommand, }, diff --git a/src/lib.rs b/bot/src/lib.rs similarity index 64% rename from src/lib.rs rename to bot/src/lib.rs index ce86c31c..d945deea 100644 --- a/src/lib.rs +++ b/bot/src/lib.rs @@ -1,64 +1,17 @@ -// #![feature(nll)] // features from edition-2018 -// #![feature(type_alias_enum_variants)] // #![allow(proc_macro_derive_resolution_fallback)] // see https://github.com/rust-lang/rust/issues/50504 #![warn(unused_imports)] // during development #![feature(type_ascription)] #![expect(non_local_definitions)] // Old diesel macros -#[macro_use] -extern crate diesel; -#[macro_use] -extern crate diesel_derives_extra; +use { + diesel::pg::PgConnection, diesel_logger::LoggingConnection, r2d2::Pool, + sea_orm::DatabaseConnection, +}; -use {diesel::pg::PgConnection, diesel_logger::LoggingConnection, r2d2::Pool}; - -pub mod bot_actor; +pub mod actors; pub mod commands; -pub mod datetime; pub mod models; pub mod schema; -pub mod services; - -static TEMPLATE_FILES: std::sync::LazyLock> = - std::sync::LazyLock::new(|| include_dir::include_dir!("$CARGO_MANIFEST_DIR/templates")); - -pub(crate) static TEMPLATES: std::sync::LazyLock = std::sync::LazyLock::new(|| { - let mut tera = tera::Tera::default(); - for file in TEMPLATE_FILES.find("**/*.tera").unwrap() { - if let Some(template) = file.as_file() { - tera.add_raw_template( - template.path().with_extension("").to_str().unwrap(), // drop .tera extension - template.contents_utf8().unwrap(), - ) - .unwrap(); - } - } - tera -}); - -#[allow( - clippy::crate_in_macro_def, - reason = "We refer to this specific TEMPLATES instance in this specific crate" -)] -#[macro_export] -macro_rules! render_template { - ($template:expr) => { - { - crate::TEMPLATES.render($template, &tera::Context::new()) - .map_err(|e| format!("Failed to render template '{}': {}", $template, e)) - } - }; - ($template:expr, $(($key:expr,$value:expr)),+) => { - { - let mut context = tera::Context::new(); - $( - context.insert($key, $value); - )* - crate::TEMPLATES.render($template, &context) - .map_err(|e| format!("Failed to render template '{}': {}", $template, e)) - } - }; -} // TODO: only BotConnection should be public pub type DbConnection = LoggingConnection; @@ -71,7 +24,7 @@ pub trait NamedActor { pub trait BotCommand { /// Print command usage instructions. - // fn usage(&self, bot: &BotMenu, message: &UpdateWithCx, Message>); + // fn usage(&self, bot: &BotMenu, message: &UpdateWithCx, Message>); /// Return command prefix to match. /// To support sub-commands the prefix for root commands should start with '/'. fn prefix() -> &'static str; @@ -113,15 +66,15 @@ pub fn establish_db_connection() -> DbConnPool { // self.clone_box() // } // } - +/* #[cfg(test)] mod tests { - // use super::*; + use super::*; // Command is prefix of another command. - // struct PrefixCommand; + struct PrefixCommand; - // struct PrefixTwoCommand; + struct PrefixTwoCommand; // impl PrefixCommand { // pub fn new() -> Box { @@ -129,15 +82,15 @@ mod tests { // } // } - // impl BotCommand for PrefixCommand { - // fn prefix() -> &'static str { - // "/prefix" - // } + impl BotCommand for PrefixCommand { + fn prefix() -> &'static str { + "/prefix" + } - // fn description() -> &'static str { - // "Test" - // } - // } + fn description() -> &'static str { + "Test" + } + } // impl PrefixTwoCommand { // pub fn new() -> Box { @@ -145,15 +98,15 @@ mod tests { // } // } - // impl BotCommand for PrefixTwoCommand { - // fn prefix() -> &'static str { - // "/prefixtwo" - // } + impl BotCommand for PrefixTwoCommand { + fn prefix() -> &'static str { + "/prefixtwo" + } - // fn description() -> &'static str { - // "Test two" - // } - // } + fn description() -> &'static str { + "Test two" + } + } // #[test] // fn test_command_insertion_order1() { @@ -209,3 +162,4 @@ mod tests { // tokio::run(retry); // } } +*/ diff --git a/src/models.rs b/bot/src/models.rs similarity index 100% rename from src/models.rs rename to bot/src/models.rs diff --git a/src/schema.patch b/bot/src/schema.patch similarity index 100% rename from src/schema.patch rename to bot/src/schema.patch diff --git a/src/schema.rs b/bot/src/schema.rs similarity index 100% rename from src/schema.rs rename to bot/src/schema.rs diff --git a/bot/src/schema.rs.orig b/bot/src/schema.rs.orig new file mode 100644 index 00000000..5bf5353b --- /dev/null +++ b/bot/src/schema.rs.orig @@ -0,0 +1,86 @@ +table! { + activities (id) { + id -> Int4, + name -> Text, + mode -> Nullable, + min_fireteam_size -> Int4, + max_fireteam_size -> Int4, + min_light -> Nullable, + min_level -> Nullable, + } +} + +table! { + activityshortcuts (id) { + id -> Int4, + name -> Text, + game -> Text, + link -> Int4, + } +} + +table! { + alerts (id) { + id -> Int4, + guid -> Text, + title -> Text, + #[sql_name = "type"] + type_ -> Text, + startdate -> Timestamptz, + expirydate -> Nullable, + faction -> Nullable, + flavor -> Nullable, + } +} + +table! { + guardians (id) { + id -> Int4, + telegram_name -> Text, + telegram_id -> Int8, + psn_name -> Text, + email -> Nullable, + psn_clan -> Nullable, + created_at -> Timestamptz, + updated_at -> Timestamptz, + deleted_at -> Nullable, + tokens -> Nullable, + pending_activation_code -> Nullable, + is_admin -> Bool, + is_superadmin -> Bool, + } +} + +table! { + plannedactivities (id) { + id -> Int4, + author_id -> Int4, + activity_id -> Int4, + details -> Nullable, + start -> Timestamptz, + } +} + +table! { + plannedactivitymembers (id) { + id -> Int4, + planned_activity_id -> Int4, + user_id -> Int4, + added -> Timestamptz, + } +} + +joinable!(activityshortcuts -> activities (link)); +joinable!(plannedactivities -> activities (activity_id)); +joinable!(plannedactivities -> guardians (author_id)); +joinable!(plannedactivitymembers -> guardians (user_id)); +joinable!(plannedactivitymembers -> plannedactivities (planned_activity_id)); + +allow_tables_to_appear_in_same_query!( + activities, + activityshortcuts, + alerts, + guardians, + plannedactivities, + plannedactivitymembers, +); diff --git a/lib/Cargo.toml b/lib/Cargo.toml new file mode 100644 index 00000000..8f0c9032 --- /dev/null +++ b/lib/Cargo.toml @@ -0,0 +1,15 @@ +[package] +authors = ["Berkus Decker "] +edition = "2021" +name = "libbot" +version = "0.4.0" +publish = false + +[lib] +name = "libbot" + +[dependencies] +chrono.workspace = true +chrono-tz.workspace = true +culpa.workspace = true +# entity.workspace = true diff --git a/src/datetime/mod.rs b/lib/src/datetime.rs similarity index 97% rename from src/datetime/mod.rs rename to lib/src/datetime.rs index 0c19d2a8..1d65f108 100644 --- a/src/datetime/mod.rs +++ b/lib/src/datetime.rs @@ -1,18 +1,11 @@ use { chrono::{prelude::*, DateTime, Duration, TimeZone, Utc}, chrono_tz::{Europe::Moscow, Tz}, - diesel::{helper_types::AsExprOf, sql_types::Timestamptz}, std::fmt::Write, }; -// Diesel farts, see issues/1752 -pub fn nowtz() -> AsExprOf { - use diesel::{dsl::now, IntoSql}; - now.into_sql::() -} - // All internal date representation and storage is in UTC. -// MSK time used only to parse input time and to display times. +// MSK time zone is used only to parse input time and to display times. pub type BotDateTime = chrono::DateTime; pub type BotTime = chrono::NaiveTime; // UTC diff --git a/lib/src/lib.rs b/lib/src/lib.rs new file mode 100644 index 00000000..f96448a3 --- /dev/null +++ b/lib/src/lib.rs @@ -0,0 +1,2 @@ +pub mod datetime; +pub mod services; diff --git a/src/services/destiny_schedule.rs b/lib/src/services/destiny_schedule.rs similarity index 81% rename from src/services/destiny_schedule.rs rename to lib/src/services/destiny_schedule.rs index 0edfc3c9..4f0b41b2 100644 --- a/src/services/destiny_schedule.rs +++ b/lib/src/services/destiny_schedule.rs @@ -1,10 +1,6 @@ use { - crate::{ - bot_actor::{BotActorMsg, Format, Notify, SendMessage}, - datetime::{reference_date, BotDateTime}, - }, + crate::datetime::{reference_date, BotDateTime}, chrono::{DateTime, Duration, TimeZone, Utc}, - riker::{actor::Tell, actors::ActorRef}, std::sync::LazyLock, }; // use plurals::{Lang, Plural}; @@ -40,14 +36,6 @@ fn raid_week_number(now: BotDateTime) -> i64 { // plural: "weeks", // }; -// 1. Daily resets at 20:00 MSK (17:00 UTC) every day -pub fn daily_reset(bot: ActorRef, chat_id: teloxide::types::ChatId) { - bot.tell( - SendMessage("⚡️ Daily reset".into(), chat_id, Format::Plain, Notify::Off), - None, - ); -} - pub fn dreaming_city_cycle() -> String { let curses: [&'static str; 3] = ["Weak Curse", "Growing Curse", "Strongest Curse"]; let urls: [&'static str; 3] = [ @@ -97,23 +85,6 @@ pub fn ascendant_challenge_cycle() -> String { ) } -// 2. Weekly (main) resets at 20:00 msk every Tue -// 6. On main reset: change in Dreaming City curse -// dreaming city on 3-week schedule -// 7. On main reset: change in Dreaming City Ascendant Challenges -// dreaming city challenges on 6-week schedule -pub fn major_weekly_reset(bot: ActorRef, chat_id: teloxide::types::ChatId) { - let msg = format!( - "⚡️ Weekly reset:\n\n{d1week}\n\n{d2week}", - d1week = this_week_in_d1(), - d2week = this_week_in_d2(), - ); - bot.tell( - SendMessage(msg, chat_id, Format::Markdown, Notify::Off), - None, - ); -} - pub fn this_week_in_d1() -> String { format!("This week in Destiny 1:\n\n{}", raid_cycle()) } diff --git a/lib/src/services/mod.rs b/lib/src/services/mod.rs new file mode 100644 index 00000000..f600498a --- /dev/null +++ b/lib/src/services/mod.rs @@ -0,0 +1 @@ +pub mod destiny_schedule; diff --git a/src/bot_actor.rs b/src/bot_actor.rs deleted file mode 100644 index 36817254..00000000 --- a/src/bot_actor.rs +++ /dev/null @@ -1,266 +0,0 @@ -use { - crate::{ - commands::*, - establish_db_connection, - services::reminder_actor::{ - ReminderActor, ScheduleNextDay, ScheduleNextMinute, ScheduleNextWeek, - }, - BotCommand, DbConnPool, NamedActor, - }, - riker::actors::{ - actor, Actor, ActorFactoryArgs, ActorRefFactory, BasicActorRef, ChannelRef, Context, - Receive, Sender, Subscribe, Tell, - }, - std::fmt::Formatter, - teloxide::{ - prelude::*, - types::{ChatId, ParseMode}, - }, -}; - -#[derive(Clone)] -#[actor(SendMessage, SendMessageReply, ListCommands)] -pub struct BotActor { - pub bot: Bot, - bot_name: String, - lfg_chat_id: i64, - update_channel: ChannelRef, - connection_pool: DbConnPool, - commands_list: Vec<(String, String)>, -} - -unsafe impl Send for BotActor {} - -impl std::fmt::Debug for BotActor { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "BotActor") - } -} - -pub type UpdateMessage = UpdateWithCx; -pub type ActorUpdateMessage = ActorUpdateWithCx; - -// Manually derived version of UpdateWithCx<_, _> -#[derive(Debug, Clone)] -pub struct ActorUpdateWithCx { - pub requester: R, - pub update: Upd, -} - -impl From for ActorUpdateMessage { - fn from(m: UpdateMessage) -> Self { - Self { - requester: m.requester, - update: m.update, - } - } -} - -impl BotActor { - // Public API - - pub fn new( - name: &str, - bot: Bot, - chan: ChannelRef, - lfg_chat_id: i64, - ) -> Self { - BotActor { - bot, - bot_name: name.to_string(), - lfg_chat_id, - update_channel: chan, - connection_pool: establish_db_connection(), - commands_list: vec![], - } - } - - pub fn list_commands(&self) -> Vec<(String, String)> { - self.commands_list.clone() - } - - // Internal helpers - - // fn handle_error(error: anyhow::Error) -> RetryPolicy { - // // count errors - // log::error!("handle_error"); - // match error.downcast_ref::() { - // Some(te) => { - // log::error!("Telegram error: {}, retrying connection.", te); - // RetryPolicy::WaitRetry(Duration::from_secs(30)) - // } - // None => { - // log::error!("handle_error didn't match, real error {:?}", error); - // //handle_error didnt match, real error Io(Custom { kind: Other, error: StringError("failed to lookup address information: nodename nor servname provided, or not known") }) - // RetryPolicy::ForwardError(error) - // } - // } - // } -} - -impl Actor for BotActor { - type Msg = BotActorMsg; - - /// Register all bot commands and subscribe them to the system notification channel. - fn pre_start(&mut self, ctx: &Context) { - macro_rules! new_command { - ($T:ident) => { - let cmd = ctx - .actor_of_args::<$T, _>(&$T::actor_name(), (ctx.myself().clone(), self.bot_name.clone(), self.connection_pool.clone())) - .unwrap(); // FIXME: panics in pre_start do not cause actor restart, so this is faulty! - self.commands_list.push(($T::prefix().into(), $T::description().into())); - self.update_channel.tell( - Subscribe { - actor: Box::new(cmd), - topic: "raw-commands".into(), - }, - None, - ); - } - } - - new_command!(ActivitiesCommand); - new_command!(CancelCommand); - new_command!(ChatidCommand); - new_command!(D1weekCommand); - new_command!(D2weekCommand); - new_command!(EditCommand); - new_command!(EditGuardianCommand); - new_command!(HelpCommand); - new_command!(JoinCommand); - new_command!(LfgCommand); - new_command!(ListCommand); - new_command!(ManageCommand); - new_command!(PsnCommand); - new_command!(UptimeCommand); - new_command!(WhoisCommand); - - // Create reminder tasks actor - let reminders = ctx - .actor_of_args::( - "reminders", - (ctx.myself(), self.lfg_chat_id, self.connection_pool.clone()), - ) - .unwrap(); - // Schedule first run, the actor handler will reschedule. - reminders.tell(ScheduleNextMinute, None); - reminders.tell(ScheduleNextDay, None); - reminders.tell(ScheduleNextWeek, None); - } - - fn recv(&mut self, ctx: &Context, msg: Self::Msg, sender: Sender) { - self.receive(ctx, msg, sender); - } -} - -impl ActorFactoryArgs<(String, Bot, ChannelRef, i64)> for BotActor { - fn create_args( - (bot_name, bot, chan, lfg_chat): (String, Bot, ChannelRef, i64), - ) -> Self { - Self::new(&bot_name, bot, chan, lfg_chat) - } -} - -#[derive(Clone, Debug)] -pub enum Format { - Plain, - Markdown, - Html, -} - -#[derive(Clone, Debug)] -pub enum Notify { - Off, - On, -} - -#[derive(Clone, Debug)] -pub struct SendMessage(pub String, pub ChatId, pub Format, pub Notify); - -#[derive(Clone, Debug)] -pub struct SendMessageReply(pub String, pub ActorUpdateMessage, pub Format, pub Notify); - -#[derive(Clone, Debug)] -pub struct ListCommands(pub ActorUpdateMessage); - -impl Receive for BotActor { - type Msg = BotActorMsg; - - fn receive(&mut self, _ctx: &Context, msg: SendMessage, _sender: Sender) { - log::debug!("SendMessage: {}", &msg.0); - let resp = self - .bot - .send_message(msg.1, msg.0) - .disable_notification(match msg.3 { - Notify::On => false, - Notify::Off => true, - }) - .disable_web_page_preview(true); - - let resp = match msg.2 { - Format::Html => resp.parse_mode(ParseMode::Html), - Format::Markdown => resp.parse_mode(ParseMode::MarkdownV2), - Format::Plain => resp, - }; - - let rt = tokio::runtime::Builder::new_current_thread() - .enable_all() - .build() - .unwrap(); - - rt.block_on(resp.send()).unwrap(); - } -} - -impl Receive for BotActor { - type Msg = BotActorMsg; - - fn receive(&mut self, _ctx: &Context, msg: SendMessageReply, _sender: Sender) { - log::debug!("SendMessageReply: {}", &msg.0); - let message = msg.1; - - let fut = self - .bot - .send_message(message.update.chat_id(), msg.0) - .reply_to_message_id(message.update.id) - .disable_notification(match msg.3 { - Notify::On => false, - Notify::Off => true, - }) - .disable_web_page_preview(true); - - let fut = match msg.2 { - Format::Html => fut.parse_mode(ParseMode::Html), - Format::Markdown => fut.parse_mode(ParseMode::MarkdownV2), - Format::Plain => fut, - }; - - let rt = tokio::runtime::Builder::new_current_thread() - .enable_all() - .build() - .unwrap(); - - rt.block_on(fut.send()).unwrap(); - } -} - -impl Receive for BotActor { - type Msg = BotActorMsg; - - fn receive(&mut self, ctx: &Context, msg: ListCommands, _sender: Sender) { - log::debug!("ListCommands"); - let message = msg.0; - - let mut sorted_cmds = self.list_commands(); - sorted_cmds.sort_by_key(|v| v.0.clone()); - let reply = sorted_cmds.into_iter().fold( - "Help 🚑\nThese are the registered commands for this Bot:\n\n".into(), - |acc, pair| format!("{}{} — {}\n\n", acc, pair.0, pair.1), - ); - - ctx.myself.tell( - SendMessageReply(reply, message, Format::Html, Notify::Off), - None, - ); - } -} diff --git a/src/errors.rs b/src/errors.rs deleted file mode 100644 index 6792d697..00000000 --- a/src/errors.rs +++ /dev/null @@ -1,8 +0,0 @@ -/// Implement failure Fail for various types used in the bot -/// @todo use anyhow -use failure::Error; - -#[derive(Debug, Fail)] -enum BotError { - DbError(diesel::result::Error), -} diff --git a/src/services/mod.rs b/src/services/mod.rs deleted file mode 100644 index 5002bebf..00000000 --- a/src/services/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -pub mod destiny_schedule; -pub use self::destiny_schedule::*; -pub mod reminder; -pub use self::reminder::*; -pub mod reminder_actor; diff --git a/src/services/reminder.rs b/src/services/reminder.rs deleted file mode 100644 index 9fde7fbb..00000000 --- a/src/services/reminder.rs +++ /dev/null @@ -1,39 +0,0 @@ -use { - crate::{ - bot_actor::{BotActorMsg, Format, Notify, SendMessage}, - datetime::reference_date, - models::PlannedActivity, - BotConnection, - }, - riker::{actor::Tell, actors::ActorRef}, - teloxide::types::ChatId, -}; - -pub fn check(bot: ActorRef, connection: BotConnection, chat_id: ChatId) { - // log::info!("reminder check at {}", reference_date()); - - let reference = reference_date(); - - let upcoming_events: Vec = PlannedActivity::upcoming_activities(&connection) - .into_iter() - .filter(|event| { - if event.start > reference { - matches!((event.start - reference).num_minutes(), 60 | 15 | 0) - } else { - false - } - }) - .collect(); - - if upcoming_events.is_empty() { - return; - } - - let text = upcoming_events - .into_iter() - .fold("Activities starting soon:\n\n".to_owned(), |acc, event| { - acc + &format!("{}\n\n", event.to_string(&connection, None)) - }); - - bot.tell(SendMessage(text, chat_id, Format::Html, Notify::On), None); -} diff --git a/src/services/reminder_actor.rs b/src/services/reminder_actor.rs deleted file mode 100644 index 9768693d..00000000 --- a/src/services/reminder_actor.rs +++ /dev/null @@ -1,146 +0,0 @@ -use { - crate::{ - bot_actor::BotActorMsg, - datetime::{d2_reset_time, reference_date, start_at_time, start_at_weekday_time}, - services::{destiny_schedule, reminder}, - BotConnection, DbConnPool, - }, - chrono::Timelike, - riker::{ - actors::{ - actor, Actor, ActorFactoryArgs, ActorRef, BasicActorRef, Context, Receive, Sender, Tell, - }, - system::Timer, - }, - teloxide::types::ChatId, -}; - -#[actor( - Reminders, - DailyReset, - WeeklyReset, - ScheduleNextMinute, - ScheduleNextDay, - ScheduleNextWeek -)] -pub struct ReminderActor { - bot_ref: ActorRef, - lfg_chat: i64, - connection_pool: DbConnPool, -} - -impl ReminderActor { - pub fn connection(&self) -> BotConnection { - self.connection_pool.get().unwrap() - } -} - -impl Actor for ReminderActor { - type Msg = ReminderActorMsg; - - fn recv(&mut self, ctx: &Context, msg: Self::Msg, sender: Sender) { - self.receive(ctx, msg, sender); - } -} - -impl ActorFactoryArgs<(ActorRef, i64, DbConnPool)> for ReminderActor { - fn create_args( - (bot_ref, lfg_chat, connection_pool): (ActorRef, i64, DbConnPool), - ) -> Self { - Self { - bot_ref, - lfg_chat, - connection_pool, - } - } -} - -#[derive(Clone, Debug)] -pub struct Reminders; - -#[derive(Clone, Debug)] -pub struct DailyReset; - -#[derive(Clone, Debug)] -pub struct WeeklyReset; - -impl Receive for ReminderActor { - type Msg = ReminderActorMsg; - - fn receive(&mut self, ctx: &Context, _msg: Reminders, _sender: Sender) { - reminder::check( - self.bot_ref.clone(), - self.connection(), - ChatId::Id(self.lfg_chat), - ); - ctx.myself().tell(ScheduleNextMinute, None); - } -} - -impl Receive for ReminderActor { - type Msg = ReminderActorMsg; - - fn receive(&mut self, ctx: &Context, _msg: DailyReset, _sender: Sender) { - destiny_schedule::daily_reset(self.bot_ref.clone(), ChatId::Id(self.lfg_chat)); - ctx.myself().tell(ScheduleNextDay, None); - } -} - -impl Receive for ReminderActor { - type Msg = ReminderActorMsg; - - fn receive(&mut self, ctx: &Context, _msg: WeeklyReset, _sender: Sender) { - destiny_schedule::major_weekly_reset(self.bot_ref.clone(), ChatId::Id(self.lfg_chat)); - ctx.myself().tell(ScheduleNextWeek, None); - } -} - -#[derive(Clone, Debug)] -pub struct ScheduleNextMinute; - -#[derive(Clone, Debug)] -pub struct ScheduleNextDay; - -#[derive(Clone, Debug)] -pub struct ScheduleNextWeek; - -impl Receive for ReminderActor { - type Msg = ReminderActorMsg; - - fn receive(&mut self, ctx: &Context, _msg: ScheduleNextMinute, _sender: Sender) { - ctx.schedule_at_time( - (reference_date() + chrono::Duration::minutes(1)) - .with_second(0) - .unwrap(), - ctx.myself(), - None, - Reminders, - ); - } -} - -impl Receive for ReminderActor { - type Msg = ReminderActorMsg; - - fn receive(&mut self, ctx: &Context, _msg: ScheduleNextDay, _sender: Sender) { - ctx.schedule_at_time( - start_at_time(reference_date(), d2_reset_time()), - ctx.myself(), - None, - DailyReset, - ); - } -} - -impl Receive for ReminderActor { - type Msg = ReminderActorMsg; - - fn receive(&mut self, ctx: &Context, _msg: ScheduleNextWeek, _sender: Sender) { - ctx.schedule_at_time( - start_at_weekday_time(reference_date(), chrono::Weekday::Tue, d2_reset_time()), - ctx.myself(), - None, - WeeklyReset, - ); - } -} From 0c530968fb3917b4429a2bdc890deb21bd1670cd Mon Sep 17 00:00:00 2001 From: Berkus Decker Date: Mon, 4 Nov 2024 20:55:53 +0200 Subject: [PATCH 04/26] =?UTF-8?q?=F0=9F=97=83=EF=B8=8F=20Add=20sea-orm=20e?= =?UTF-8?q?ntity=20crate?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 35 ++ Cargo.toml | 3 +- TODO.md | 37 +- bot/Cargo.toml | 1 + bot/src/lib.rs | 2 - bot/src/models.rs | 624 --------------------------- bot/src/schema.patch | 20 - bot/src/schema.rs | 86 ---- bot/src/schema.rs.orig | 86 ---- diesel.toml | 3 - entity/Cargo.toml | 17 + entity/src/activities.rs | 89 ++++ entity/src/activityshortcuts.rs | 61 +++ entity/src/alerts.rs | 173 ++++++++ entity/src/guardians.rs | 116 +++++ entity/src/lib.rs | 41 ++ entity/src/plannedactivities.rs | 314 ++++++++++++++ entity/src/plannedactivitymembers.rs | 125 ++++++ entity/src/prelude.rs | 8 + 19 files changed, 1018 insertions(+), 823 deletions(-) delete mode 100644 bot/src/models.rs delete mode 100644 bot/src/schema.patch delete mode 100644 bot/src/schema.rs delete mode 100644 bot/src/schema.rs.orig delete mode 100644 diesel.toml create mode 100644 entity/Cargo.toml create mode 100644 entity/src/activities.rs create mode 100644 entity/src/activityshortcuts.rs create mode 100644 entity/src/alerts.rs create mode 100644 entity/src/guardians.rs create mode 100644 entity/src/lib.rs create mode 100644 entity/src/plannedactivities.rs create mode 100644 entity/src/plannedactivitymembers.rs create mode 100644 entity/src/prelude.rs diff --git a/Cargo.lock b/Cargo.lock index 47a4181b..15cf18da 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -25,6 +25,7 @@ dependencies = [ "chrono", "chrono-tz 0.10.4", "dotenv", + "entity", "fern", "include_dir", "itertools 0.14.0", @@ -579,6 +580,26 @@ dependencies = [ "typenum", ] +[[package]] +name = "culpa" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ae0bfe9317b1cb4ff5a56d766ee4b157b3e1f47f11979253570e88d10fd1fd3" +dependencies = [ + "culpa-macros", +] + +[[package]] +name = "culpa-macros" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1234e1717066d3c71dcf89b75e7b586299e41204d361db56ec51e6ded5014279" +dependencies = [ + "proc-macro2 1.0.101", + "quote 1.0.40", + "syn 2.0.106", +] + [[package]] name = "darling" version = "0.13.4" @@ -727,6 +748,20 @@ dependencies = [ "cfg-if 1.0.3", ] +[[package]] +name = "entity" +version = "0.4.0" +dependencies = [ + "anyhow", + "chrono", + "culpa", + "dotenv", + "regex", + "sea-orm", + "serde 1.0.219", + "tokio", +] + [[package]] name = "equivalent" version = "1.0.2" diff --git a/Cargo.toml b/Cargo.toml index 4b86e78a..8f7443be 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace] -members = ["bot"] +members = ["bot", "entity"] resolver = "2" [workspace.dependencies] @@ -8,6 +8,7 @@ chrono = "0.4" chrono-tz = "0.10" culpa = "1.0" dotenv = "0.15" +entity = { version = "0.4", path = "./entity" } fern = { version = "0.7", features = ["colored"] } include_dir = { version = "0.7.4", features = ["glob", "nightly"] } itertools = "0.14" diff --git a/TODO.md b/TODO.md index 66c5eb00..398936d0 100644 --- a/TODO.md +++ b/TODO.md @@ -1,5 +1,41 @@ More features: +`Impl`s for the SeaORM entities: + +```rust +// src/entity/pet.rs + +// Model - for views and other state/display things +impl Model { + pub fn display_name(&self) -> String { + format!("Pet: {} (ID: {})", self.name, self.id) + } +} + +use sea_orm::{DatabaseConnection, EntityTrait, QueryFilter, ColumnTrait}; + +// Entity - for reading data from DB +impl Entity { + pub async fn find_by_name( + db: &DatabaseConnection, + name: &str, + ) -> sea_orm::Result> { + Entity::find() + .filter(Column::Name.eq(name)) + .one(db) + .await + } +} + +// ActiveModel - for changing data in the DB +impl ActiveModel { + pub fn set_name(&mut self, name: String) { + self.name = sea_orm::ActiveValue::Set(name); + } +} +``` + + - [ ] Add waiting-list for activities. - [ ] Mark past WF events with "(ended) " prefix. @@ -40,4 +76,3 @@ Remember to use `async fn`!! Send function: - https://github.com/bytesnake/telebot/blob/master/telebot-derive/src/lib.rs#L239 - diff --git a/bot/Cargo.toml b/bot/Cargo.toml index 8fda6469..ee0d480d 100644 --- a/bot/Cargo.toml +++ b/bot/Cargo.toml @@ -10,6 +10,7 @@ anyhow.workspace = true chrono-tz.workspace = true chrono.workspace = true dotenv.workspace = true +entity.workspace = true fern.workspace = true futures.workspace = true include_dir.workspace = true diff --git a/bot/src/lib.rs b/bot/src/lib.rs index d945deea..c4c62d0b 100644 --- a/bot/src/lib.rs +++ b/bot/src/lib.rs @@ -10,8 +10,6 @@ use { pub mod actors; pub mod commands; -pub mod models; -pub mod schema; // TODO: only BotConnection should be public pub type DbConnection = LoggingConnection; diff --git a/bot/src/models.rs b/bot/src/models.rs deleted file mode 100644 index 52647056..00000000 --- a/bot/src/models.rs +++ /dev/null @@ -1,624 +0,0 @@ -//================================================================================================= -// DB Models and Tera templates -//================================================================================================= - -use { - crate::{ - datetime::{format_start_time, reference_date}, - render_template, - schema::*, - DbConnection, - }, - chrono::{prelude::*, Duration}, - diesel::{ExpressionMethods, OptionalExtension, QueryDsl, RunQueryDsl}, - diesel_derives_traits::Model, - serde::{Deserialize, Serialize}, - serde_json::Value, - std::{fmt, sync::LazyLock}, -}; - -//------------------------------------------------------------------------------------------------- -// ActivityShortcut -//------------------------------------------------------------------------------------------------- - -#[derive(Debug, Queryable, Identifiable, AsChangeset, Associations, Model)] -#[table_name = "activityshortcuts"] -#[belongs_to(Activity, foreign_key = "link")] -pub struct ActivityShortcut { - pub id: i32, - pub name: String, - pub game: String, - pub link: i32, -} - -#[derive(Clone, Insertable, NewModel)] -#[table_name = "activityshortcuts"] -#[model(ActivityShortcut)] -pub struct NewActivityShortcut { - pub name: String, - pub game: String, - pub link: i32, -} - -impl ActivityShortcut { - pub fn find_one_by_name( - connection: &DbConnection, - act_name: &str, - ) -> diesel::result::QueryResult> { - use crate::schema::activityshortcuts::dsl::*; - - ::table() - .filter(name.eq(act_name)) - .get_result::(connection) - .optional() - } -} - -//------------------------------------------------------------------------------------------------- -// Activity -//------------------------------------------------------------------------------------------------- - -#[derive(Debug, Queryable, Identifiable, AsChangeset, Model)] -#[table_name = "activities"] -pub struct Activity { - pub id: i32, - pub name: String, - pub mode: Option, - pub min_fireteam_size: i32, - pub max_fireteam_size: i32, - pub min_light: Option, - pub min_level: Option, -} - -#[derive(Clone, Insertable, NewModel)] -#[table_name = "activities"] -#[model(Activity)] -pub struct NewActivity { - pub name: String, - pub mode: Option, - pub min_fireteam_size: i32, - pub max_fireteam_size: i32, - pub min_light: Option, - pub min_level: Option, -} - -impl Activity { - pub fn format_name(&self) -> String { - format!("{} {}", self.name, self.mode.clone().unwrap_or_default()) - } -} - -//------------------------------------------------------------------------------------------------- -// Alert -//------------------------------------------------------------------------------------------------- - -#[derive(Debug, Queryable, Identifiable, AsChangeset, Model)] -pub struct Alert { - pub id: i32, - pub guid: String, - pub title: String, - pub kind: String, - #[column_name = "startdate"] - pub start_date: DateTime, - #[column_name = "expirydate"] - pub expiry_date: Option>, - pub faction: Option, - pub flavor: Option, -} - -#[derive(Clone, Insertable, NewModel)] -#[table_name = "alerts"] -#[model(Alert)] -pub struct NewAlert { - pub guid: String, - pub title: String, - pub kind: Option, - #[column_name = "startdate"] - pub start_date: Option>, - #[column_name = "expirydate"] - pub expiry_date: Option>, - pub faction: Option, - pub flavor: Option, -} - -impl fmt::Display for Alert { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!( - f, - "{} {} {}", - self.type_icon(), - self.reward_icon(), - self.title - ) - } -} - -impl Alert { - pub fn is_important(&self) -> bool { - self.is_forma() - || self.is_nitain() - || self.is_orokin_reactor() - || (self.expiry_date.is_some() - && self.expiry_date.unwrap() - self.start_date >= Duration::minutes(90)) - } - - pub fn type_icon(&self) -> String { - match self.kind.as_str() { - "Alert" => "✊".into(), - "Invasion" => "🐛".into(), - "Outbreak" => "⛓".into(), - _ => format!("⁉️ {}", self.kind), - } - } - - pub fn reward_icon(&self) -> String { - if self.is_forma() { - "⚖" - } else if self.is_nitain() { - "✨" - } else if self.is_orokin_reactor() { - "🏮" - } else if self.is_endo() { - "🔮" - } else if self.is_blueprint() { - "🗿" - } else if self.is_resource() { - "🔋" - } else if self.is_mod() { - "⚙" - } else if self.is_aura() { - "❄️" - } else if self.is_credits() { - "💰" - } else { - "" - } - .into() - } - - pub fn is_blueprint(&self) -> bool { - self.title.contains("(Blueprint)") - } - - pub fn is_resource(&self) -> bool { - self.title.contains("(Resource)") - } - - pub fn is_mod(&self) -> bool { - self.title.contains("(Mod)") - } - - pub fn is_aura(&self) -> bool { - self.title.contains("(Aura)") - } - - pub fn is_credits(&self) -> bool { - static CREDITS: LazyLock = - LazyLock::new(|| regex::Regex::new(r"^\d+cr ").unwrap()); - CREDITS.is_match(&self.title) - } - - pub fn is_forma(&self) -> bool { - self.title.contains("Forma") - } - - pub fn is_nitain(&self) -> bool { - self.title.contains("Nitain Extract") - } - - pub fn is_orokin_reactor(&self) -> bool { - self.title.contains("Orokin Reactor") - } - - pub fn is_endo(&self) -> bool { - self.title.contains("ENDO") - } -} - -//------------------------------------------------------------------------------------------------- -// Guardian -//------------------------------------------------------------------------------------------------- - -#[derive(Debug, Clone, Queryable, Identifiable, AsChangeset, Model)] -pub struct Guardian { - pub id: i32, - pub telegram_name: String, - pub telegram_id: i64, - pub psn_name: String, - pub email: Option, - pub psn_clan: Option, - pub created_at: DateTime, - pub updated_at: DateTime, - pub deleted_at: Option>, - pub tokens: Option, - pub pending_activation_code: Option, - pub is_admin: bool, - pub is_superadmin: bool, -} - -#[derive(Insertable, NewModel)] -#[table_name = "guardians"] -#[model(Guardian)] -pub struct NewGuardian<'a> { - pub telegram_name: &'a str, - pub telegram_id: i64, - pub psn_name: &'a str, -} - -impl Guardian { - pub fn format_name(&self) -> String { - format!("{} (t.me/{})", self.psn_name, self.telegram_name) - } - - pub fn names(&self) -> (String, String) { - (self.telegram_name.clone(), self.psn_name.clone()) - } -} - -impl fmt::Display for Guardian { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{} (t.me/{})", self.psn_name, self.telegram_name) - } -} - -//------------------------------------------------------------------------------------------------- -// PlannedActivity -//------------------------------------------------------------------------------------------------- - -// class PlannedActivity(id: EntityID) : IntEntity(id) { -// var author by Guardian referencedOn PlannedActivities.authorId -// var activity by Activity referencedOn PlannedActivities.activityId -// var start by PlannedActivities.start -// var details by PlannedActivities.details -// val members by PlannedActivityMember referrersOn PlannedActivityMembers.plannedActivityId - -#[derive(Debug, Queryable, Identifiable, AsChangeset, Associations, Model)] -#[belongs_to(Guardian, foreign_key = "author_id")] -#[belongs_to(Activity, foreign_key = "activity_id")] -#[table_name = "plannedactivities"] -pub struct PlannedActivity { - pub id: i32, - pub author_id: i32, // refs Guardians - pub activity_id: i32, // refs Activities - pub details: Option, - pub start: DateTime, -} - -// Output information -#[derive(Serialize, Deserialize)] -pub struct PlannedActivityTemplate { - pub id: i32, - pub name: String, - pub details: String, - pub members: Vec, - pub count: usize, - pub time: String, - pub fireteam_full: bool, - pub fireteam_joined: bool, - pub join_link: String, - pub leave_link: String, -} - -#[derive(Insertable, NewModel)] -#[table_name = "plannedactivities"] -#[model(PlannedActivity)] -pub struct NewPlannedActivity { - pub author_id: i32, // refs Guardians - pub activity_id: i32, // refs Activities - pub start: DateTime, -} - -impl PlannedActivity { - pub fn to_template( - &self, - guardian: Option<&Guardian>, - connection: &DbConnection, - ) -> PlannedActivityTemplate { - let activity = self.activity(connection); - - let count = activity.max_fireteam_size as usize - self.members_count(connection); - - PlannedActivityTemplate { - id: self.id, - name: activity.format_name(), - details: self.format_details(), - members: self - .members(connection) - .into_iter() - .map(|m| m.to_template(connection)) - .collect(), - count, - time: format_start_time(self.start, reference_date()), - fireteam_full: count == 0, - join_link: self.join_prompt(connection), - fireteam_joined: self.find_member(connection, guardian).is_some(), - leave_link: self.cancel_link(), - } - } - - pub fn upcoming_activities(connection: &DbConnection) -> Vec { - use { - crate::{datetime::nowtz, schema::plannedactivities::dsl::*}, - diesel::dsl::IntervalDsl, - }; - - plannedactivities - .filter(start.ge(nowtz() - 60_i32.minutes())) - .order(start.asc()) - .load::(connection) - .expect("TEMP failed to load planned activities @FIXME") - } - - pub fn author(&self, connection: &DbConnection) -> Option { - Guardian::find_one(connection, &self.author_id) - .expect("Failed to load PlannedActivity author") - } - - pub fn activity(&self, connection: &DbConnection) -> Activity { - Activity::find_one(connection, &self.activity_id) - .expect("Failed to load associated Activity") - .expect("PlannedActivity without Activity shouldn't exist") - } - - pub fn members(&self, connection: &DbConnection) -> Vec { - use crate::schema::plannedactivitymembers::dsl::*; - plannedactivitymembers - .filter(planned_activity_id.eq(self.id)) - .order(added.asc()) - .load::(connection) - .expect("Failed to load PlannedActivity members") - } - - pub fn members_count(&self, connection: &DbConnection) -> usize { - //@TODO replace with proper diesel query - self.members(connection).len() - } - - pub fn join_link(&self) -> String { - format!("/join{}", self.id) - } - - pub fn cancel_link(&self) -> String { - format!("/cancel{}", self.id) - } - - pub fn join_prompt(&self, connection: &DbConnection) -> String { - if self.is_full(connection) { - "This activity fireteam is full.".into() - } else { - let count = self.activity(connection).max_fireteam_size as usize - - self.members_count(connection); - format!( - "Enter `{joinLink}` to join this group. Up to {count} more can join.", - joinLink = self.join_link(), - count = count - ) - } - } - - pub fn is_full(&self, connection: &DbConnection) -> bool { - self.members(connection).len() >= self.activity(connection).max_fireteam_size as usize - } - - pub fn requires_more_members(&self, connection: &DbConnection) -> bool { - self.members(connection).len() < self.activity(connection).min_fireteam_size as usize - } - - pub fn format_details(&self) -> String { - self.details.clone().map(|s| s + "\n").unwrap_or_default() - } - - pub fn members_formatted(&self, connection: &DbConnection, joiner: &str) -> String { - self.members(connection) - .into_iter() - .map(|guardian| guardian.format_name(connection)) - .collect::>() - .as_slice() - .join(joiner) - } - - pub fn members_formatted_list(&self, connection: &DbConnection) -> String { - self.members_formatted(connection, ", ") - } - - pub fn members_formatted_column(&self, connection: &DbConnection) -> String { - self.members_formatted(connection, "\n") - } - - pub fn find_member( - &self, - connection: &DbConnection, - guardian: Option<&Guardian>, - ) -> Option { - use crate::schema::plannedactivitymembers::dsl::*; - - guardian.and_then(|g| { - plannedactivitymembers - .filter(user_id.eq(g.id)) - .filter(planned_activity_id.eq(self.id)) - .first::(connection) - .optional() - .expect("Failed to run SQL") - }) - } - - // Makes a telegram Html formatted display. - pub fn to_string(&self, connection: &DbConnection, g: Option<&Guardian>) -> String { - let event = self.to_template(g, connection); - render_template!("list/event", ("event", &event)) - .expect("Failed to render list event template") - } -} - -//------------------------------------------------------------------------------------------------- -// PlannedActivityMember -//------------------------------------------------------------------------------------------------- - -#[derive(Debug, Queryable, Identifiable, AsChangeset, Associations, Model)] -#[belongs_to(Guardian, foreign_key = "user_id")] -#[belongs_to(Activity, foreign_key = "planned_activity_id")] -#[table_name = "plannedactivitymembers"] -pub struct PlannedActivityMember { - pub id: i32, - pub planned_activity_id: i32, - pub user_id: i32, - pub added: DateTime, -} - -#[derive(Serialize, Deserialize)] -pub struct ActivityMemberTemplate { - pub psn_name: String, - pub telegram_name: String, - pub icon: String, -} - -#[derive(Insertable, NewModel)] -#[table_name = "plannedactivitymembers"] -#[model(PlannedActivityMember)] -pub struct NewPlannedActivityMember { - pub planned_activity_id: i32, - pub user_id: i32, - pub added: DateTime, -} - -impl PlannedActivityMember { - pub fn format_name(&self, connection: &DbConnection) -> String { - Guardian::find_one(connection, &self.user_id) - .expect("Failed to load associated Guardian") - .expect("Failed to find associated activity member") - .format_name() - } - - pub fn to_template(&self, connection: &DbConnection) -> ActivityMemberTemplate { - let (telegram_name, psn_name) = Guardian::find_one(connection, &self.user_id) - .expect("Failed to load associated Guardian") - .expect("Failed to find associated activity member") - .names(); - ActivityMemberTemplate { - psn_name, - telegram_name, - icon: self.icon(), - } - } - - pub fn icon(&self) -> String { - static ICON_POOL: LazyLock> = LazyLock::new(|| { - vec![ - "💂🏻", - "🕵🏼", - "🧑🏽‍🏭", - "🧑‍💻", - "🧑🏼‍🚒", - "🧑🏾‍🚀", - "🥷🏾", - "🥷🏻", - "🧙🏽", - "🧝🏼", - "🧌", - "🧛🏼", - "🧟", - ] - }); - ICON_POOL[self.user_id.unsigned_abs() as usize % ICON_POOL.len()].into() - } -} - -//================================================================================================= -// Tests -//================================================================================================= - -#[cfg(test)] -mod tests { - use {super::*, crate::establish_db_connection, diesel::prelude::*}; - - #[test] - #[ignore] - fn test_guardians() -> Result<(), r2d2::Error> { - use crate::schema::guardians::dsl::*; - - dotenv::dotenv().ok(); - let pool = establish_db_connection(); - let connection = pool.get()?; - - let results = guardians - // .filter(published.eq(true)) - .limit(5) - .load::(&connection) - .expect("Error loading guardians"); - - println!("Displaying {} guardians", results.len()); - for guar in results { - println!("{}", guar); - } - - Ok(()) - } - - #[test] - #[ignore] - fn test_activities() -> Result<(), r2d2::Error> { - use crate::schema::activities::dsl::*; - - dotenv::dotenv().ok(); - let pool = establish_db_connection(); - let connection = pool.get()?; - - let results = activities - .load::(&connection) - .expect("Error loading activities"); - - println!("Displaying {} activities", results.len()); - for act in results { - println!("{}", act.format_name()); - } - - Ok(()) - } - - #[test] - #[ignore] - fn test_alerts() -> Result<(), r2d2::Error> { - use crate::schema::alerts::dsl::*; - - dotenv::dotenv().ok(); - let pool = establish_db_connection(); - let connection = pool.get()?; - - let results = alerts - .limit(5) - .load::(&connection) - .expect("Error loading alerts"); - - println!("Displaying {} alerts", results.len()); - for alrt in results { - println!("{}", alrt.title); - } - - Ok(()) - } - - #[test] - #[ignore] - fn test_planned_activities() -> Result<(), r2d2::Error> { - use crate::schema::guardians::dsl::*; - - dotenv::dotenv().ok(); - let pool = establish_db_connection(); - let connection = pool.get()?; - - let guar = guardians - .find(1) - .first::(&connection) - .expect("Guardian with id 1 not found"); - let results = PlannedActivity::belonging_to(&guar) - .load::(&connection) - .expect("Error loading activities"); - - println!("Displaying {} planned activities", results.len()); - for act in results { - println!("{}", act.to_string(&connection, Some(&guar))); - } - - Ok(()) - } -} diff --git a/bot/src/schema.patch b/bot/src/schema.patch deleted file mode 100644 index 9d87b779..00000000 --- a/bot/src/schema.patch +++ /dev/null @@ -1,20 +0,0 @@ -diff --git a/src/schema.rs b/src/schema.rs -index b23e3a3..4123d7a 100644 ---- a/src/schema.rs -+++ b/src/schema.rs -@@ -21,13 +21,13 @@ table! { - - table! { - alerts (id) { - id -> Int4, - guid -> Text, - title -> Text, - #[sql_name = "type"] -- type_ -> Text, -+ kind -> Text, - startdate -> Timestamptz, - expirydate -> Nullable, - faction -> Nullable, - flavor -> Nullable, - } - } diff --git a/bot/src/schema.rs b/bot/src/schema.rs deleted file mode 100644 index f6c8cf0d..00000000 --- a/bot/src/schema.rs +++ /dev/null @@ -1,86 +0,0 @@ -table! { - activities (id) { - id -> Int4, - name -> Text, - mode -> Nullable, - min_fireteam_size -> Int4, - max_fireteam_size -> Int4, - min_light -> Nullable, - min_level -> Nullable, - } -} - -table! { - activityshortcuts (id) { - id -> Int4, - name -> Text, - game -> Text, - link -> Int4, - } -} - -table! { - alerts (id) { - id -> Int4, - guid -> Text, - title -> Text, - #[sql_name = "type"] - kind -> Text, - startdate -> Timestamptz, - expirydate -> Nullable, - faction -> Nullable, - flavor -> Nullable, - } -} - -table! { - guardians (id) { - id -> Int4, - telegram_name -> Text, - telegram_id -> Int8, - psn_name -> Text, - email -> Nullable, - psn_clan -> Nullable, - created_at -> Timestamptz, - updated_at -> Timestamptz, - deleted_at -> Nullable, - tokens -> Nullable, - pending_activation_code -> Nullable, - is_admin -> Bool, - is_superadmin -> Bool, - } -} - -table! { - plannedactivities (id) { - id -> Int4, - author_id -> Int4, - activity_id -> Int4, - details -> Nullable, - start -> Timestamptz, - } -} - -table! { - plannedactivitymembers (id) { - id -> Int4, - planned_activity_id -> Int4, - user_id -> Int4, - added -> Timestamptz, - } -} - -joinable!(activityshortcuts -> activities (link)); -joinable!(plannedactivities -> activities (activity_id)); -joinable!(plannedactivities -> guardians (author_id)); -joinable!(plannedactivitymembers -> guardians (user_id)); -joinable!(plannedactivitymembers -> plannedactivities (planned_activity_id)); - -allow_tables_to_appear_in_same_query!( - activities, - activityshortcuts, - alerts, - guardians, - plannedactivities, - plannedactivitymembers, -); diff --git a/bot/src/schema.rs.orig b/bot/src/schema.rs.orig deleted file mode 100644 index 5bf5353b..00000000 --- a/bot/src/schema.rs.orig +++ /dev/null @@ -1,86 +0,0 @@ -table! { - activities (id) { - id -> Int4, - name -> Text, - mode -> Nullable, - min_fireteam_size -> Int4, - max_fireteam_size -> Int4, - min_light -> Nullable, - min_level -> Nullable, - } -} - -table! { - activityshortcuts (id) { - id -> Int4, - name -> Text, - game -> Text, - link -> Int4, - } -} - -table! { - alerts (id) { - id -> Int4, - guid -> Text, - title -> Text, - #[sql_name = "type"] - type_ -> Text, - startdate -> Timestamptz, - expirydate -> Nullable, - faction -> Nullable, - flavor -> Nullable, - } -} - -table! { - guardians (id) { - id -> Int4, - telegram_name -> Text, - telegram_id -> Int8, - psn_name -> Text, - email -> Nullable, - psn_clan -> Nullable, - created_at -> Timestamptz, - updated_at -> Timestamptz, - deleted_at -> Nullable, - tokens -> Nullable, - pending_activation_code -> Nullable, - is_admin -> Bool, - is_superadmin -> Bool, - } -} - -table! { - plannedactivities (id) { - id -> Int4, - author_id -> Int4, - activity_id -> Int4, - details -> Nullable, - start -> Timestamptz, - } -} - -table! { - plannedactivitymembers (id) { - id -> Int4, - planned_activity_id -> Int4, - user_id -> Int4, - added -> Timestamptz, - } -} - -joinable!(activityshortcuts -> activities (link)); -joinable!(plannedactivities -> activities (activity_id)); -joinable!(plannedactivities -> guardians (author_id)); -joinable!(plannedactivitymembers -> guardians (user_id)); -joinable!(plannedactivitymembers -> plannedactivities (planned_activity_id)); - -allow_tables_to_appear_in_same_query!( - activities, - activityshortcuts, - alerts, - guardians, - plannedactivities, - plannedactivitymembers, -); diff --git a/diesel.toml b/diesel.toml deleted file mode 100644 index afcfb3b4..00000000 --- a/diesel.toml +++ /dev/null @@ -1,3 +0,0 @@ -[print_schema] -file = "src/schema.rs" -patch_file = "src/schema.patch" diff --git a/entity/Cargo.toml b/entity/Cargo.toml new file mode 100644 index 00000000..8d4e770d --- /dev/null +++ b/entity/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "entity" +version = "0.4.0" +edition = "2021" +publish = false + +[dependencies] +anyhow.workspace = true +chrono.workspace = true +culpa.workspace = true +dotenv.workspace = true +futures.workspace = true +libbot.workspace = true +regex.workspace = true +sea-orm.workspace = true +serde.workspace = true +tokio.workspace = true diff --git a/entity/src/activities.rs b/entity/src/activities.rs new file mode 100644 index 00000000..5c790783 --- /dev/null +++ b/entity/src/activities.rs @@ -0,0 +1,89 @@ +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.1 +//------------------------------------------------------------------------------------------------- +// Activity +//------------------------------------------------------------------------------------------------- + +use sea_orm::entity::prelude::*; + +// Old diesel schema for reference: +// table! { +// activities (id) { +// id -> Int4, +// name -> Text, +// mode -> Nullable, +// min_fireteam_size -> Int4, +// max_fireteam_size -> Int4, +// min_light -> Nullable, +// min_level -> Nullable, +// } +// } + +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[sea_orm(table_name = "activities")] +pub struct Model { + #[sea_orm(primary_key)] + pub id: i32, + #[sea_orm(column_type = "Text")] + pub name: String, + #[sea_orm(column_type = "Text", nullable)] + pub mode: Option, + pub min_fireteam_size: i32, + pub max_fireteam_size: i32, + pub min_light: Option, + pub min_level: Option, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm(has_many = "super::activityshortcuts::Entity")] + ActivityShortcuts, + #[sea_orm(has_many = "super::plannedactivities::Entity")] + PlannedActivities, +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::ActivityShortcuts.def() + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::PlannedActivities.def() + } +} + +impl ActiveModelBehavior for ActiveModel {} + +impl Model { + pub fn format_name(&self) -> String { + format!("{} {}", self.name, self.mode.clone().unwrap_or_default()) + } +} + +#[cfg(test)] +mod tests { + use { + crate::establish_db_connection, + anyhow::Result, + dotenv::dotenv, + sea_orm::{EntityTrait, PaginatorTrait}, + }; + + #[tokio::test] + #[ignore] + async fn test_activities() -> Result<()> { + dotenv().ok(); + let db = establish_db_connection().await?; + + let mut results = super::Entity::find().paginate(&db, 10); + + while let Some(activities) = results.fetch_and_next().await? { + for act in activities { + println!("{}", act.format_name()); + } + } + + Ok(()) + } +} diff --git a/entity/src/activityshortcuts.rs b/entity/src/activityshortcuts.rs new file mode 100644 index 00000000..160e9d8c --- /dev/null +++ b/entity/src/activityshortcuts.rs @@ -0,0 +1,61 @@ +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.1 +//------------------------------------------------------------------------------------------------- +// ActivityShortcut +//------------------------------------------------------------------------------------------------- + +use sea_orm::entity::prelude::*; + +// Old diesel schema for reference: +// table! { +// activityshortcuts (id) { +// id -> Int4, +// name -> Text, +// game -> Text, +// link -> Int4, +// } +// } + +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[sea_orm(table_name = "activity_shortcuts")] +pub struct Model { + #[sea_orm(primary_key)] + pub id: i32, + #[sea_orm(column_type = "Text", unique)] + pub name: String, + #[sea_orm(column_type = "Text")] + pub game: String, + pub link: i32, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm( + belongs_to = "super::activities::Entity", + from = "Column::Link", + to = "super::activities::Column::Id", + on_update = "Cascade", + on_delete = "Cascade" + )] + Activities, +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Activities.def() + } +} + +impl ActiveModelBehavior for ActiveModel {} + +// impl Entity { +// pub fn find_one_by_name( +// connection: &DatabaseConnection, +// act_name: &str, +// ) -> diesel::result::QueryResult> { + +// ::table() +// .filter(name.eq(act_name)) +// .get_result::(connection) +// .optional() +// } +// } diff --git a/entity/src/alerts.rs b/entity/src/alerts.rs new file mode 100644 index 00000000..35f2c047 --- /dev/null +++ b/entity/src/alerts.rs @@ -0,0 +1,173 @@ +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.1 +//------------------------------------------------------------------------------------------------- +// Alert +//------------------------------------------------------------------------------------------------- + +use { + chrono::Duration, + sea_orm::entity::prelude::*, + std::{fmt, sync::LazyLock}, +}; + +// Old diesel schema for reference: +// table! { +// alerts (id) { +// id -> Int4, +// guid -> Text, +// title -> Text, +// #[sql_name = "type"] +// kind -> Text, +// startdate -> Timestamptz, +// expirydate -> Nullable, +// faction -> Nullable, +// flavor -> Nullable, +// } +// } + +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[sea_orm(table_name = "alerts")] +pub struct Model { + #[sea_orm(primary_key)] + pub id: i32, + #[sea_orm(column_type = "Text", unique)] + pub guid: String, + #[sea_orm(column_type = "Text")] + pub title: String, + #[sea_orm(column_type = "Text", column_name = "type")] + pub kind: String, + #[sea_orm(column_name = "startdate")] + pub start_date: ChronoDateTimeWithTimeZone, + #[sea_orm(column_name = "expirydate")] + pub expiry_date: Option, + #[sea_orm(column_type = "Text", nullable)] + pub faction: Option, + #[sea_orm(column_type = "Text", nullable)] + pub flavor: Option, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation {} + +impl ActiveModelBehavior for ActiveModel {} + +impl fmt::Display for Model { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "{} {} {}", + self.type_icon(), + self.reward_icon(), + self.title + ) + } +} + +impl Model { + pub fn is_important(&self) -> bool { + self.is_forma() + || self.is_nitain() + || self.is_orokin_reactor() + || self + .expiry_date + .map(|exp| exp - self.start_date >= Duration::minutes(90)) + .unwrap_or(false) + } + + pub fn type_icon(&self) -> String { + match self.kind.as_str() { + "Alert" => "✊".into(), + "Invasion" => "🐛".into(), + "Outbreak" => "⛓".into(), + _ => format!("⁉️ {}", self.kind), + } + } + + pub fn reward_icon(&self) -> String { + if self.is_forma() { + "⚖" + } else if self.is_nitain() { + "✨" + } else if self.is_orokin_reactor() { + "🏮" + } else if self.is_endo() { + "🔮" + } else if self.is_blueprint() { + "🗿" + } else if self.is_resource() { + "🔋" + } else if self.is_mod() { + "⚙" + } else if self.is_aura() { + "❄️" + } else if self.is_credits() { + "💰" + } else { + "" + } + .into() + } + + pub fn is_blueprint(&self) -> bool { + self.title.contains("(Blueprint)") + } + + pub fn is_resource(&self) -> bool { + self.title.contains("(Resource)") + } + + pub fn is_mod(&self) -> bool { + self.title.contains("(Mod)") + } + + pub fn is_aura(&self) -> bool { + self.title.contains("(Aura)") + } + + pub fn is_credits(&self) -> bool { + static CREDITS: LazyLock = + LazyLock::new(|| regex::Regex::new(r"^\d+cr ").unwrap()); + CREDITS.is_match(&self.title) + } + + pub fn is_forma(&self) -> bool { + self.title.contains("Forma") + } + + pub fn is_nitain(&self) -> bool { + self.title.contains("Nitain Extract") + } + + pub fn is_orokin_reactor(&self) -> bool { + self.title.contains("Orokin Reactor") + } + + pub fn is_endo(&self) -> bool { + self.title.contains("ENDO") + } +} + +#[cfg(test)] +mod tests { + use { + crate::establish_db_connection, + anyhow::Result, + dotenv::dotenv, + sea_orm::{EntityTrait, QuerySelect}, + }; + + #[tokio::test] + #[ignore] + async fn test_alerts() -> Result<()> { + dotenv().ok(); + let db = establish_db_connection().await?; + + let results = super::Entity::find().limit(5).all(&db).await?; + + println!("Displaying {} alerts", results.len()); + for alrt in results { + println!("{}", alrt.title); + } + + Ok(()) + } +} diff --git a/entity/src/guardians.rs b/entity/src/guardians.rs new file mode 100644 index 00000000..c2f20cb9 --- /dev/null +++ b/entity/src/guardians.rs @@ -0,0 +1,116 @@ +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.1 +//------------------------------------------------------------------------------------------------- +// Guardian +//------------------------------------------------------------------------------------------------- + +use {sea_orm::entity::prelude::*, std::fmt}; + +// Old diesel schema for reference: +// table! { +// guardians (id) { +// id -> Int4, +// telegram_name -> Text, +// telegram_id -> Int8, +// psn_name -> Text, +// email -> Nullable, +// psn_clan -> Nullable, +// created_at -> Timestamptz, +// updated_at -> Timestamptz, +// deleted_at -> Nullable, +// tokens -> Nullable, +// pending_activation_code -> Nullable, +// is_admin -> Bool, +// is_superadmin -> Bool, +// } +// } + +#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] +#[sea_orm(table_name = "guardians")] +pub struct Model { + #[sea_orm(primary_key)] + pub id: i32, + #[sea_orm(column_type = "Text", unique)] + pub telegram_name: String, + #[sea_orm(unique)] + pub telegram_id: i64, + #[sea_orm(column_type = "Text")] + pub psn_name: String, + #[sea_orm(column_type = "Text", nullable)] + pub email: Option, + #[sea_orm(column_type = "Text", nullable)] + pub psn_clan: Option, + pub created_at: ChronoDateTimeWithTimeZone, + pub updated_at: ChronoDateTimeWithTimeZone, + pub deleted_at: Option, + #[sea_orm(column_type = "JsonBinary", nullable)] + pub tokens: Option, + #[sea_orm(column_type = "Text", nullable)] + pub pending_activation_code: Option, + pub is_admin: bool, + pub is_superadmin: bool, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm(has_many = "super::plannedactivities::Entity")] + PlannedActivities, + #[sea_orm(has_many = "super::plannedactivitymembers::Entity")] + PlannedActivityMembers, +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::PlannedActivities.def() + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::PlannedActivityMembers.def() + } +} + +impl ActiveModelBehavior for ActiveModel {} + +impl fmt::Display for Model { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.format_name()) + } +} + +impl Model { + pub fn format_name(&self) -> String { + format!("{} (t.me/{})", self.psn_name, self.telegram_name) + } + + pub fn names(&self) -> (String, String) { + (self.telegram_name.clone(), self.psn_name.clone()) + } +} + +#[cfg(test)] +mod tests { + use { + crate::establish_db_connection, + anyhow::Result, + dotenv::dotenv, + sea_orm::{EntityTrait, PaginatorTrait}, + }; + + #[tokio::test] + #[ignore] + async fn test_guardians() -> Result<()> { + dotenv().ok(); + let db = establish_db_connection().await?; + + let mut results = super::Entity::find().paginate(&db, 10); + + while let Some(guardians) = results.fetch_and_next().await? { + for guar in guardians { + println!("{guar}"); + } + } + + Ok(()) + } +} diff --git a/entity/src/lib.rs b/entity/src/lib.rs new file mode 100644 index 00000000..4e32dc9f --- /dev/null +++ b/entity/src/lib.rs @@ -0,0 +1,41 @@ +#![feature(duration_constructors_lite)] + +use { + culpa::throws, + sea_orm::{Database, DatabaseConnection, DbErr}, +}; + +pub mod prelude; + +pub mod activities; +pub mod activityshortcuts; +pub mod alerts; +pub mod guardians; +pub mod plannedactivities; +pub mod plannedactivitymembers; + +// Old diesel schema for reference: +// +// joinable!(activityshortcuts -> activities (link)); +// joinable!(plannedactivities -> activities (activity_id)); +// joinable!(plannedactivities -> guardians (author_id)); +// joinable!(plannedactivitymembers -> guardians (user_id)); +// joinable!(plannedactivitymembers -> plannedactivities (planned_activity_id)); +// +// allow_tables_to_appear_in_same_query!( +// activities, +// activityshortcuts, +// alerts, +// guardians, +// plannedactivities, +// plannedactivitymembers, +// ); + +/// Establish a pool of connections with DB. +#[throws(DbErr)] +pub async fn establish_db_connection() -> DatabaseConnection { + dotenv::dotenv().ok(); + + let database_url = std::env::var("DATABASE_URL").expect("DATABASE_URL must be set"); + Database::connect(database_url).await? +} diff --git a/entity/src/plannedactivities.rs b/entity/src/plannedactivities.rs new file mode 100644 index 00000000..eccbc07a --- /dev/null +++ b/entity/src/plannedactivities.rs @@ -0,0 +1,314 @@ +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.1 +//------------------------------------------------------------------------------------------------- +// PlannedActivity +//------------------------------------------------------------------------------------------------- + +use { + crate::{ + guardians, + plannedactivitymembers::{self, ActivityMemberTemplate}, + }, + chrono::Duration, + culpa::throws, + libbot::datetime::{format_start_time, reference_date}, + sea_orm::{entity::prelude::*, QueryOrder}, +}; + +// Old diesel schema for reference: +// table! { +// plannedactivities (id) { +// id -> Int4, +// author_id -> Int4, +// activity_id -> Int4, +// details -> Nullable, +// start -> Timestamptz, +// } +// } + +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[sea_orm(table_name = "planned_activities")] +pub struct Model { + #[sea_orm(primary_key)] + pub id: i32, + pub author_id: i32, // refs Guardians + pub activity_id: i32, // refs Activities + #[sea_orm(column_type = "Text", nullable)] + pub details: Option, + pub start: ChronoDateTimeWithTimeZone, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm( + belongs_to = "super::activities::Entity", + from = "Column::ActivityId", + to = "super::activities::Column::Id", + on_update = "Cascade", + on_delete = "Restrict" + )] + Activities, + #[sea_orm( + belongs_to = "super::guardians::Entity", + from = "Column::AuthorId", + to = "super::guardians::Column::Id", + on_update = "NoAction", + on_delete = "NoAction" + )] + Guardians, + #[sea_orm(has_many = "super::plannedactivitymembers::Entity")] + PlannedActivityMembers, +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Activities.def() + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Guardians.def() + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::PlannedActivityMembers.def() + } +} + +impl ActiveModelBehavior for ActiveModel {} + +// Output information +#[derive(serde::Serialize)] +pub struct PlannedActivityTemplate { + pub id: i32, + pub name: String, + pub details: String, + pub members: Vec, + pub count: usize, + pub time: String, + pub fireteam_full: bool, + pub fireteam_joined: bool, + pub join_link: String, + pub leave_link: String, +} + +impl Entity { + pub async fn upcoming_activities(connection: &DatabaseConnection) -> Vec { + Self::find() + .filter(Column::Start.gt(reference_date() - Duration::minutes(60))) + .all(connection) + .await + .unwrap_or_default() + } + + pub async fn upcoming_activities_alert(connection: &DatabaseConnection) -> Option> { + // log::info!("reminder check at {}", reference_date()); + + let reference = reference_date(); + + let upcoming_events = Self::find() + .filter(Column::Start.gt(reference)) + .all(connection) + .await + .unwrap_or_default() + .into_iter() + .filter(|event| { + let event_start: chrono::DateTime = event.start.into(); + if event_start > reference { + let diff = event_start - reference; + matches!(diff.num_minutes(), 60 | 15 | 0) + } else { + false + } + }) + .collect::>(); + + if upcoming_events.is_empty() { + return None; + } + + Some(upcoming_events) + } +} + +impl Model { + #[throws(sea_orm::DbErr)] + pub async fn to_template( + &self, + connection: &DatabaseConnection, + guardian: Option<&guardians::Model>, + ) -> PlannedActivityTemplate { + let activity = self.activity(connection).await?.unwrap(); // use Relation + + let members = self.members(connection).await?; + let mut memvec = Vec::new(); + for x in members { + memvec.push(x.to_template(connection).await?); + } + let members = memvec; + + let count = activity.max_fireteam_size as usize - members.len(); + + PlannedActivityTemplate { + id: self.id, + name: activity.format_name(), + details: self.format_details(), + members, + count, + time: format_start_time(self.start.into(), reference_date()), + fireteam_full: count == 0, + fireteam_joined: self.find_member(connection, guardian).await?.is_some(), + join_link: self.join_link(), + leave_link: self.cancel_link(), + } + } + + #[throws(sea_orm::DbErr)] + pub async fn author(&self, connection: &DatabaseConnection) -> Option { + self.find_related(crate::guardians::Entity) + .one(connection) + .await? + } + + #[throws(sea_orm::DbErr)] + pub async fn activity( + &self, + connection: &DatabaseConnection, + ) -> Option { + self.find_related(crate::activities::Entity) + .one(connection) + .await? + } + + #[throws(sea_orm::DbErr)] + pub async fn members( + &self, + connection: &DatabaseConnection, + ) -> Vec { + self.find_related(crate::plannedactivitymembers::Entity) + .order_by_asc(crate::plannedactivitymembers::Column::Added) + .all(connection) + .await? + } + + #[throws(sea_orm::DbErr)] + pub async fn members_count(&self, connection: &DatabaseConnection) -> usize { + self.find_related(crate::plannedactivitymembers::Entity) + .order_by_asc(crate::plannedactivitymembers::Column::Added) + .count(connection) + .await? as usize + } + + pub fn join_link(&self) -> String { + format!("/join{}", self.id) + } + + pub fn cancel_link(&self) -> String { + format!("/cancel{}", self.id) + } + + #[throws(sea_orm::DbErr)] + pub async fn join_prompt(&self, connection: &DatabaseConnection) -> String { + if self.is_full(connection).await? { + "This activity fireteam is full.".into() + } else { + let count = self.activity(connection).await?.unwrap().max_fireteam_size as usize + - self.members_count(connection).await?; + format!( + "Enter `{joinLink}` to join this group. Up to {count} more can join.", + joinLink = self.join_link(), + count = count + ) + } + } + + #[throws(sea_orm::DbErr)] + pub async fn is_full(&self, connection: &DatabaseConnection) -> bool { + self.members_count(connection).await? + >= self.activity(connection).await?.unwrap().max_fireteam_size as usize + } + + #[throws(sea_orm::DbErr)] + pub async fn requires_more_members(&self, connection: &DatabaseConnection) -> bool { + self.members_count(connection).await? + < self.activity(connection).await?.unwrap().min_fireteam_size as usize + } + + pub fn format_details(&self) -> String { + self.details.clone().map(|s| s + "\n").unwrap_or_default() + } + + #[throws(sea_orm::DbErr)] + pub async fn members_formatted(&self, connection: &DatabaseConnection, joiner: &str) -> String { + use futures::future::try_join_all; + + let members = self.members(connection).await?; + let futures = members.into_iter().map(|guardian| { + crate::plannedactivitymembers::Model::format_member_name(connection, guardian.user_id) + }); + let names: Vec = try_join_all(futures).await?; + names.join(joiner) + } + + #[throws(sea_orm::DbErr)] + pub async fn members_formatted_list(&self, connection: &DatabaseConnection) -> String { + self.members_formatted(connection, ", ").await? + } + + #[throws(sea_orm::DbErr)] + pub async fn members_formatted_column(&self, connection: &DatabaseConnection) -> String { + self.members_formatted(connection, "\n").await? + } + + // This should be on Entity? + #[throws(sea_orm::DbErr)] + pub async fn find_member( + &self, + connection: &DatabaseConnection, + guardian: Option<&guardians::Model>, + ) -> Option { + if let Some(g) = guardian { + return plannedactivitymembers::Entity::find() + .filter(plannedactivitymembers::Column::UserId.eq(g.id)) + .filter(plannedactivitymembers::Column::PlannedActivityId.eq(self.id)) + .one(connection) + .await?; + } + None + } +} + +// #[cfg(test)] +// mod tests { +// use { +// crate::establish_db_connection, +// anyhow::Result, +// dotenv::dotenv, +// sea_orm::{EntityTrait, PaginatorTrait}, +// }; +// +// #[tokio::test] +// #[ignore] +// async fn test_planned_activities() -> Result<()> { +// dotenv().ok(); +// let db = establish_db_connection().await; +// +// // TODO: Fix this test to use SeaORM syntax +// // let guar = guardians +// // .find(1) +// // .first::(&connection) +// // .expect("Guardian with id 1 not found"); +// // let results = PlannedActivity::belonging_to(&guar) +// // .load::(&connection) +// // .expect("Error loading activities"); +// +// // println!("Displaying {} planned activities", results.len()); +// // for act in results { +// // println!("{}", act.to_string(&connection, Some(&guar))); +// // } +// +// Ok(()) +// } +// } diff --git a/entity/src/plannedactivitymembers.rs b/entity/src/plannedactivitymembers.rs new file mode 100644 index 00000000..cb71fbb4 --- /dev/null +++ b/entity/src/plannedactivitymembers.rs @@ -0,0 +1,125 @@ +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.1 +//------------------------------------------------------------------------------------------------- +// PlannedActivityMember +//------------------------------------------------------------------------------------------------- + +use { + culpa::throws, + sea_orm::entity::prelude::*, + serde::{Deserialize, Serialize}, + std::sync::LazyLock, +}; + +// Old diesel schema for reference: +// table! { +// plannedactivitymembers (id) { +// id -> Int4, +// planned_activity_id -> Int4, +// user_id -> Int4, +// added -> Timestamptz, +// } +// } + +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[sea_orm(table_name = "planned_activity_members")] +pub struct Model { + #[sea_orm(primary_key)] + pub id: i32, + pub planned_activity_id: i32, + pub user_id: i32, + pub added: ChronoDateTimeWithTimeZone, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm( + belongs_to = "super::guardians::Entity", + from = "Column::UserId", + to = "super::guardians::Column::Id", + on_update = "Cascade", + on_delete = "Cascade" + )] + Guardians, + #[sea_orm( + belongs_to = "super::plannedactivities::Entity", + from = "Column::PlannedActivityId", + to = "super::plannedactivities::Column::Id", + on_update = "Cascade", + on_delete = "Cascade" + )] + PlannedActivities, +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Guardians.def() + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::PlannedActivities.def() + } +} + +impl ActiveModelBehavior for ActiveModel {} + +#[derive(Serialize, Deserialize)] +pub struct ActivityMemberTemplate { + pub psn_name: String, + pub telegram_name: String, + pub icon: String, +} + +impl Model { + #[throws(DbErr)] + pub async fn format_name(&self, connection: &DatabaseConnection) -> String { + Self::format_member_name(connection, self.user_id).await? + } + + #[throws(DbErr)] + pub async fn format_member_name(connection: &DatabaseConnection, user_id: i32) -> String { + let guardian = super::guardians::Entity::find_by_id(user_id) + .one(connection) + .await? + .ok_or(DbErr::RecordNotFound("Guardian not found".to_string()))?; + + guardian.format_name() + } + + #[throws(DbErr)] + pub async fn to_template(&self, connection: &DatabaseConnection) -> ActivityMemberTemplate { + let guardian = super::guardians::Entity::find_by_id(self.user_id) + .one(connection) + .await? + .ok_or(DbErr::RecordNotFound("Guardian not found".to_string()))?; + + let (telegram_name, psn_name) = guardian.names(); + ActivityMemberTemplate { + psn_name, + telegram_name, + icon: self.icon(), + } + } + + pub fn icon(&self) -> String { + static ICON_POOL: LazyLock> = LazyLock::new(|| { + vec![ + "💂🏻", + "🕵🏼", + "🧑🏽‍🏭", + "🧑‍💻", + "🧑🏼‍🚒", + "🧑🏾‍🚀", + "🥷🏾", + "🥷🏻", + "🧙🏽", + "🧝🏼", + "🧌", + "🧛🏼", + "🧟", + ] + }); + ICON_POOL[self.user_id.unsigned_abs() as usize % ICON_POOL.len()].into() + } +} diff --git a/entity/src/prelude.rs b/entity/src/prelude.rs new file mode 100644 index 00000000..9a35c74f --- /dev/null +++ b/entity/src/prelude.rs @@ -0,0 +1,8 @@ +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.1 + +pub use super::{ + activities::Entity as Activities, activityshortcuts::Entity as ActivityShortcuts, + alerts::Entity as Alerts, guardians::Entity as Guardians, + plannedactivities::Entity as PlannedActivities, + plannedactivitymembers::Entity as PlannedActivityMembers, +}; From 9f4931e4ca0cf94536145a8418a36155866b946d Mon Sep 17 00:00:00 2001 From: Berkus Decker Date: Wed, 20 Aug 2025 12:14:17 +0300 Subject: [PATCH 05/26] =?UTF-8?q?=F0=9F=97=83=EF=B8=8F=20Add=20sea-orm=20m?= =?UTF-8?q?igrations?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 316 +++++++++++++++++- Cargo.toml | 3 +- bot/Cargo.toml | 1 + bot/src/bin/bot.rs | 7 +- migration/Cargo.toml | 13 + migration/README.md | 41 +++ migration/init.sql | 2 + migration/src/lib.rs | 34 ++ .../src/m20180508_101204_create_alerts.rs | 35 ++ .../src/m20180508_101216_create_guardians.rs | 47 +++ .../src/m20180508_101223_create_activities.rs | 75 +++++ ...180508_101240_create_planned_activities.rs | 101 ++++++ .../m20180508_101326_populate_activities.rs | 173 ++++++++++ .../m20180905_090102_populate_activities.rs | 115 +++++++ migration/src/m20180921_110336_add_admins.rs | 42 +++ .../src/m20180922_104727_add_superadmins.rs | 42 +++ .../src/m20180926_165439_add_foreign_keys.rs | 75 +++++ migration/src/main.rs | 6 + migration/src/tables.rs | 72 ++++ migrations/.gitkeep | 0 .../down.sql | 6 - .../up.sql | 36 -- .../2018-05-08-101204_create_alerts/down.sql | 1 - .../2018-05-08-101204_create_alerts/up.sql | 10 - .../down.sql | 1 - .../2018-05-08-101216_create_guardians/up.sql | 13 - .../down.sql | 3 - .../up.sql | 18 - .../down.sql | 2 - .../up.sql | 15 - .../down.sql | 1 - .../2018-05-08-101245_create_reminders/up.sql | 6 - .../down.sql | 2 - .../up.sql | 90 ----- .../down.sql | 2 - .../up.sql | 25 -- .../down.sql | 6 - .../up.sql | 1 - .../down.sql | 7 - .../up.sql | 7 - .../2018-09-21-110336_add_admins/down.sql | 1 - .../2018-09-21-110336_add_admins/up.sql | 3 - .../down.sql | 1 - .../2018-09-22-104727_add_superadmins/up.sql | 3 - .../down.sql | 15 - .../2018-09-26-165439_add_foreign_keys/up.sql | 22 -- 46 files changed, 1187 insertions(+), 310 deletions(-) create mode 100644 migration/Cargo.toml create mode 100644 migration/README.md create mode 100644 migration/init.sql create mode 100644 migration/src/lib.rs create mode 100644 migration/src/m20180508_101204_create_alerts.rs create mode 100644 migration/src/m20180508_101216_create_guardians.rs create mode 100644 migration/src/m20180508_101223_create_activities.rs create mode 100644 migration/src/m20180508_101240_create_planned_activities.rs create mode 100644 migration/src/m20180508_101326_populate_activities.rs create mode 100644 migration/src/m20180905_090102_populate_activities.rs create mode 100644 migration/src/m20180921_110336_add_admins.rs create mode 100644 migration/src/m20180922_104727_add_superadmins.rs create mode 100644 migration/src/m20180926_165439_add_foreign_keys.rs create mode 100644 migration/src/main.rs create mode 100644 migration/src/tables.rs delete mode 100644 migrations/.gitkeep delete mode 100644 migrations/00000000000000_diesel_initial_setup/down.sql delete mode 100644 migrations/00000000000000_diesel_initial_setup/up.sql delete mode 100644 migrations/2018-05-08-101204_create_alerts/down.sql delete mode 100644 migrations/2018-05-08-101204_create_alerts/up.sql delete mode 100644 migrations/2018-05-08-101216_create_guardians/down.sql delete mode 100644 migrations/2018-05-08-101216_create_guardians/up.sql delete mode 100644 migrations/2018-05-08-101223_create_activities/down.sql delete mode 100644 migrations/2018-05-08-101223_create_activities/up.sql delete mode 100644 migrations/2018-05-08-101240_create_planned_activities/down.sql delete mode 100644 migrations/2018-05-08-101240_create_planned_activities/up.sql delete mode 100644 migrations/2018-05-08-101245_create_reminders/down.sql delete mode 100644 migrations/2018-05-08-101245_create_reminders/up.sql delete mode 100644 migrations/2018-05-08-101326_populate_activities/down.sql delete mode 100644 migrations/2018-05-08-101326_populate_activities/up.sql delete mode 100644 migrations/2018-09-05-090102_populate_activities/down.sql delete mode 100644 migrations/2018-09-05-090102_populate_activities/up.sql delete mode 100644 migrations/2018-09-05-091147_no-reminders-table/down.sql delete mode 100644 migrations/2018-09-05-091147_no-reminders-table/up.sql delete mode 100644 migrations/2018-09-05-133606_switch_to_timestamptz/down.sql delete mode 100644 migrations/2018-09-05-133606_switch_to_timestamptz/up.sql delete mode 100644 migrations/2018-09-21-110336_add_admins/down.sql delete mode 100644 migrations/2018-09-21-110336_add_admins/up.sql delete mode 100644 migrations/2018-09-22-104727_add_superadmins/down.sql delete mode 100644 migrations/2018-09-22-104727_add_superadmins/up.sql delete mode 100644 migrations/2018-09-26-165439_add_foreign_keys/down.sql delete mode 100644 migrations/2018-09-26-165439_add_foreign_keys/up.sql diff --git a/Cargo.lock b/Cargo.lock index 15cf18da..d1a46b30 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -30,6 +30,7 @@ dependencies = [ "include_dir", "itertools 0.14.0", "log", + "migration", "paste", "procfs", "regex", @@ -98,6 +99,56 @@ dependencies = [ "libc", ] +[[package]] +name = "anstream" +version = "0.6.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ae563653d1938f79b1ab1b5e668c87c76a9930414574a6583a7b7e11a8e6192" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" + +[[package]] +name = "anstyle-parse" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2" +dependencies = [ + "windows-sys 0.60.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.60.2", +] + [[package]] name = "anyhow" version = "1.0.99" @@ -414,6 +465,52 @@ dependencies = [ "phf_codegen", ] +[[package]] +name = "clap" +version = "4.5.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fc0e74a703892159f5ae7d3aac52c8e6c392f5ae5f359c70b5881d60aaac318" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3e7f4214277f3c7aa526a59dd3fbe306a370daee1f8b7b8c987069cd8e888a8" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim 0.11.1", +] + +[[package]] +name = "clap_derive" +version = "4.5.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14cb31bb0a7d536caef2639baa7fad459e15c3144efefa6dbd1c84562c4739f6" +dependencies = [ + "heck 0.5.0", + "proc-macro2 1.0.101", + "quote 1.0.40", + "syn 2.0.106", +] + +[[package]] +name = "clap_lex" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" + +[[package]] +name = "colorchoice" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" + [[package]] name = "colored" version = "2.2.0" @@ -606,8 +703,18 @@ version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c" dependencies = [ - "darling_core", - "darling_macro", + "darling_core 0.13.4", + "darling_macro 0.13.4", +] + +[[package]] +name = "darling" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" +dependencies = [ + "darling_core 0.20.11", + "darling_macro 0.20.11", ] [[package]] @@ -620,21 +727,45 @@ dependencies = [ "ident_case", "proc-macro2 1.0.101", "quote 1.0.40", - "strsim", + "strsim 0.10.0", "syn 1.0.109", ] +[[package]] +name = "darling_core" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2 1.0.101", + "quote 1.0.40", + "syn 2.0.106", +] + [[package]] name = "darling_macro" version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835" dependencies = [ - "darling_core", + "darling_core 0.13.4", "quote 1.0.40", "syn 1.0.109", ] +[[package]] +name = "darling_macro" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" +dependencies = [ + "darling_core 0.20.11", + "quote 1.0.40", + "syn 2.0.106", +] + [[package]] name = "dashmap" version = "3.11.10" @@ -1055,8 +1186,8 @@ dependencies = [ "aho-corasick", "bstr", "log", - "regex-automata", - "regex-syntax", + "regex-automata 0.4.9", + "regex-syntax 0.8.5", ] [[package]] @@ -1396,7 +1527,7 @@ dependencies = [ "globset", "log", "memchr", - "regex-automata", + "regex-automata 0.4.9", "same-file", "walkdir", "winapi-util", @@ -1460,6 +1591,12 @@ version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + [[package]] name = "itertools" version = "0.10.5" @@ -1599,6 +1736,15 @@ version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata 0.1.10", +] + [[package]] name = "md-5" version = "0.10.6" @@ -1615,6 +1761,14 @@ version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" +[[package]] +name = "migration" +version = "0.4.0" +dependencies = [ + "sea-orm-migration", + "tokio", +] + [[package]] name = "mime" version = "0.3.17" @@ -1776,6 +1930,12 @@ version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +[[package]] +name = "once_cell_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" + [[package]] name = "openssl" version = "0.10.73" @@ -2374,8 +2534,17 @@ checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", - "regex-automata", - "regex-syntax", + "regex-automata 0.4.9", + "regex-syntax 0.8.5", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax 0.6.29", ] [[package]] @@ -2386,9 +2555,15 @@ checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", - "regex-syntax", + "regex-syntax 0.8.5", ] +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + [[package]] name = "regex-syntax" version = "0.8.5" @@ -2726,6 +2901,24 @@ dependencies = [ "uuid 1.18.0", ] +[[package]] +name = "sea-orm-cli" +version = "1.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edc17cb2b24e93fc1d56de7751a12222f2303c06e83ed4d7a1e929e39f30c7d7" +dependencies = [ + "chrono", + "clap", + "dotenvy", + "glob", + "regex", + "sea-schema", + "sqlx", + "tracing", + "tracing-subscriber", + "url", +] + [[package]] name = "sea-orm-macros" version = "1.1.14" @@ -2740,6 +2933,22 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "sea-orm-migration" +version = "1.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "695e830a1332a4e3e57b5972eee00574a36060e1938afca7041a524e0955d5ba" +dependencies = [ + "async-trait", + "clap", + "dotenvy", + "sea-orm", + "sea-orm-cli", + "sea-schema", + "tracing", + "tracing-subscriber", +] + [[package]] name = "sea-query" version = "0.32.7" @@ -2751,6 +2960,7 @@ dependencies = [ "inherent", "ordered-float", "rust_decimal", + "sea-query-derive", "serde_json", "time", "uuid 1.18.0", @@ -2772,6 +2982,45 @@ dependencies = [ "uuid 1.18.0", ] +[[package]] +name = "sea-query-derive" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bae0cbad6ab996955664982739354128c58d16e126114fe88c2a493642502aab" +dependencies = [ + "darling 0.20.11", + "heck 0.4.1", + "proc-macro2 1.0.101", + "quote 1.0.40", + "syn 2.0.106", + "thiserror 2.0.16", +] + +[[package]] +name = "sea-schema" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2239ff574c04858ca77485f112afea1a15e53135d3097d0c86509cef1def1338" +dependencies = [ + "futures", + "sea-query", + "sea-query-binder", + "sea-schema-derive", + "sqlx", +] + +[[package]] +name = "sea-schema-derive" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "debdc8729c37fdbf88472f97fd470393089f997a909e535ff67c544d18cfccf0" +dependencies = [ + "heck 0.4.1", + "proc-macro2 1.0.101", + "quote 1.0.40", + "syn 2.0.106", +] + [[package]] name = "seahash" version = "4.1.0" @@ -2905,7 +3154,7 @@ version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e182d6ec6f05393cc0e5ed1bf81ad6db3a8feedf8ee515ecdd369809bcce8082" dependencies = [ - "darling", + "darling 0.13.4", "proc-macro2 1.0.101", "quote 1.0.40", "syn 1.0.109", @@ -2933,6 +3182,15 @@ dependencies = [ "digest", ] +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + [[package]] name = "shlex" version = "1.3.0" @@ -3300,6 +3558,12 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + [[package]] name = "strum" version = "0.26.3" @@ -3544,6 +3808,15 @@ dependencies = [ "syn 2.0.106", ] +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if 1.0.3", +] + [[package]] name = "time" version = "0.3.41" @@ -3737,6 +4010,21 @@ dependencies = [ "once_cell", ] +[[package]] +name = "tracing-subscriber" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" +dependencies = [ + "matchers", + "once_cell", + "regex", + "sharded-slab", + "thread_local", + "tracing", + "tracing-core", +] + [[package]] name = "try-lock" version = "0.2.5" @@ -3881,6 +4169,12 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + [[package]] name = "uuid" version = "0.8.2" diff --git a/Cargo.toml b/Cargo.toml index 8f7443be..1f100c8a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace] -members = ["bot", "entity"] +members = ["bot", "entity", "migration"] resolver = "2" [workspace.dependencies] @@ -14,6 +14,7 @@ include_dir = { version = "0.7.4", features = ["glob", "nightly"] } itertools = "0.14" libbot = { path = "./lib" } log = "0.4" +migration = { version = "0.4", path = "./migration" } paste = "1" # plurals = "0.3" regex = "1" diff --git a/bot/Cargo.toml b/bot/Cargo.toml index ee0d480d..2036bafc 100644 --- a/bot/Cargo.toml +++ b/bot/Cargo.toml @@ -17,6 +17,7 @@ include_dir.workspace = true itertools.workspace = true libbot.workspace = true log.workspace = true +migration.workspace = true paste.workspace = true regex.workspace = true riker.workspace = true diff --git a/bot/src/bin/bot.rs b/bot/src/bin/bot.rs index a91d1c29..778ad0c8 100644 --- a/bot/src/bin/bot.rs +++ b/bot/src/bin/bot.rs @@ -9,6 +9,7 @@ use { establish_db_connection, }, dotenv::dotenv, + migration::{Migrator, MigratorTrait}, // riker::prelude::*, doesn't work here! riker::actors::{channel, ActorRefFactory, ActorSystem, ChannelRef, Publish, Tell}, std::env, @@ -75,7 +76,7 @@ fn setup_logging() -> Result<(), fern::InitError> { } #[tokio::main] -async fn main() { +async fn main() -> anyhow::Result<()> { dotenv().ok(); setup_logging().expect("failed to initialize logging"); @@ -89,6 +90,10 @@ async fn main() { .expect("BOT_LFG_CHAT_ID must be a valid telegram chat id"); let token = env::var("TELEGRAM_BOT_TOKEN").expect("TELEGRAM_BOT_TOKEN must be set"); + + let connection = establish_db_connection().await?; + Migrator::up(&connection, None).await?; + let sys = ActorSystem::new().unwrap(); let tgbot = Bot::new(token); diff --git a/migration/Cargo.toml b/migration/Cargo.toml new file mode 100644 index 00000000..d248a363 --- /dev/null +++ b/migration/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "migration" +version = "0.4.0" +edition = "2021" +publish = false + +[lib] +name = "migration" +path = "src/lib.rs" + +[dependencies] +sea-orm-migration.workspace = true +tokio.workspace = true diff --git a/migration/README.md b/migration/README.md new file mode 100644 index 00000000..3b438d89 --- /dev/null +++ b/migration/README.md @@ -0,0 +1,41 @@ +# Running Migrator CLI + +- Generate a new migration file + ```sh + cargo run -- generate MIGRATION_NAME + ``` +- Apply all pending migrations + ```sh + cargo run + ``` + ```sh + cargo run -- up + ``` +- Apply first 10 pending migrations + ```sh + cargo run -- up -n 10 + ``` +- Rollback last applied migrations + ```sh + cargo run -- down + ``` +- Rollback last 10 applied migrations + ```sh + cargo run -- down -n 10 + ``` +- Drop all tables from the database, then reapply all migrations + ```sh + cargo run -- fresh + ``` +- Rollback all applied migrations, then reapply all migrations + ```sh + cargo run -- refresh + ``` +- Rollback all applied migrations + ```sh + cargo run -- reset + ``` +- Check the status of all migrations + ```sh + cargo run -- status + ``` diff --git a/migration/init.sql b/migration/init.sql new file mode 100644 index 00000000..d25192ff --- /dev/null +++ b/migration/init.sql @@ -0,0 +1,2 @@ +CREATE ROLE aeglbot WITH LOGIN PASSWORD 'whatever'; +CREATE DATABASE aeglbot OWNER aeglbot; diff --git a/migration/src/lib.rs b/migration/src/lib.rs new file mode 100644 index 00000000..680888b3 --- /dev/null +++ b/migration/src/lib.rs @@ -0,0 +1,34 @@ +pub use sea_orm_migration::prelude::*; + +mod m20180508_101204_create_alerts; +mod m20180508_101216_create_guardians; +mod m20180508_101223_create_activities; +mod m20180508_101240_create_planned_activities; +mod m20180508_101326_populate_activities; +mod m20180905_090102_populate_activities; +mod m20180921_110336_add_admins; +mod m20180922_104727_add_superadmins; +mod tables; + +pub use tables::*; + +pub struct Migrator; + +impl MigratorTrait for Migrator { + fn migration_table_name() -> DynIden { + "__seaql_migrations".into_iden() + } + + fn migrations() -> Vec> { + vec![ + Box::new(m20180508_101204_create_alerts::Migration), + Box::new(m20180508_101216_create_guardians::Migration), + Box::new(m20180508_101223_create_activities::Migration), + Box::new(m20180508_101240_create_planned_activities::Migration), + Box::new(m20180508_101326_populate_activities::Migration), + Box::new(m20180905_090102_populate_activities::Migration), + Box::new(m20180921_110336_add_admins::Migration), + Box::new(m20180922_104727_add_superadmins::Migration), + ] + } +} diff --git a/migration/src/m20180508_101204_create_alerts.rs b/migration/src/m20180508_101204_create_alerts.rs new file mode 100644 index 00000000..50745285 --- /dev/null +++ b/migration/src/m20180508_101204_create_alerts.rs @@ -0,0 +1,35 @@ +use { + crate::Alerts, + sea_orm_migration::{prelude::*, schema::*}, +}; + +#[derive(DeriveMigrationName)] +pub struct Migration; + +#[async_trait::async_trait] +impl MigrationTrait for Migration { + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .create_table( + Table::create() + .if_not_exists() + .table(Alerts::Table) // create table if not exists alerts ( + .col(pk_auto(Alerts::Id)) // id serial primary key not null, + .col(string(Alerts::Guid)) // guid text not null unique, + .col(string(Alerts::Title)) // title text not null, + .col(string(Alerts::Type)) // type text not null, + .col(timestamp_with_time_zone(Alerts::StartDate)) // startdate timestamp with time zone not null, + .col(timestamp_with_time_zone_null(Alerts::ExpiryDate)) // expirydate timestamp with time zone, + .col(string_null(Alerts::Faction)) // faction text, + .col(string_null(Alerts::Flavor)) // flavor text + .to_owned(), // ); + ) + .await + } + + async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .drop_table(Table::drop().table(Alerts::Table).to_owned()) + .await + } +} diff --git a/migration/src/m20180508_101216_create_guardians.rs b/migration/src/m20180508_101216_create_guardians.rs new file mode 100644 index 00000000..d81b4295 --- /dev/null +++ b/migration/src/m20180508_101216_create_guardians.rs @@ -0,0 +1,47 @@ +use { + crate::Guardians, + sea_orm_migration::{prelude::*, schema::*}, +}; + +#[derive(DeriveMigrationName)] +pub struct Migration; + +#[async_trait::async_trait] +impl MigrationTrait for Migration { + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .create_table( + Table::create() + .if_not_exists() + .table(Guardians::Table) // create table guardians ( + .col(pk_auto(Guardians::Id)) // id serial primary key not null, + .col(string_uniq(Guardians::TelegramName)) // telegram_name text not null unique, + .col(big_integer_uniq(Guardians::TelegramId)) // telegram_id bigint not null unique, + .col(string(Guardians::PsnName)) // psn_name text not null, + .col(string_null(Guardians::Email)) // email text, + .col(string_null(Guardians::PsnClan)) // psn_clan text, + .col( + timestamp_with_time_zone(Guardians::CreatedAt) // created_at timestamp with time zone not null default now(), + .default(Expr::current_timestamp()), + ) + .col( + timestamp_with_time_zone(Guardians::UpdatedAt) // updated_at timestamp with time zone not null default now(), + .default(Expr::current_timestamp()), + ) + .col( + timestamp_with_time_zone_null(Guardians::DeletedAt) // deleted_at timestamp with time zone default null, + .default(Expr::value("null")), + ) + .col(json_binary_null(Guardians::Tokens)) // tokens jsonb, + .col(string_null(Guardians::PendingActivationCode)) // pending_activation_code text + .to_owned(), + ) + .await + } + + async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .drop_table(Table::drop().table(Guardians::Table).to_owned()) + .await + } +} diff --git a/migration/src/m20180508_101223_create_activities.rs b/migration/src/m20180508_101223_create_activities.rs new file mode 100644 index 00000000..310b72b3 --- /dev/null +++ b/migration/src/m20180508_101223_create_activities.rs @@ -0,0 +1,75 @@ +use { + crate::{Activities, ActivityShortcuts}, + sea_orm_migration::{prelude::*, schema::*}, +}; + +#[derive(DeriveMigrationName)] +pub struct Migration; + +#[async_trait::async_trait] +impl MigrationTrait for Migration { + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .create_table( + Table::create() + .if_not_exists() + .table(Activities::Table) // create table activities ( + .col(pk_auto(Activities::Id)) // id serial primary key not null, + .col(string(Activities::Name)) // name text not null, + .col(string_null(Activities::Mode)) // mode text, + .col(integer(Activities::MinFireteamSize)) // min_fireteam_size integer not null, + .col(integer(Activities::MaxFireteamSize)) // max_fireteam_size integer not null, + .col(integer_null(Activities::MinLight)) // min_light integer, + .col(integer_null(Activities::MinLevel)) // min_level integer + .to_owned(), + ) + .await?; + // create index activities_name_idx on activities(name); + manager + .create_index( + Index::create() + .if_not_exists() + .name("activities_name_idx") + .table(Activities::Table) + .col(Activities::Name) + .to_owned(), + ) + .await?; + + manager + .create_table( + Table::create() + .if_not_exists() + .table(ActivityShortcuts::Table) // create table activityshortcuts ( + .col(pk_auto(ActivityShortcuts::Id)) // id serial primary key not null, + .col(string_uniq(ActivityShortcuts::Name)) // name text not null unique, + .col(string(ActivityShortcuts::Game)) // game text not null, + .col(integer(ActivityShortcuts::Link)) // link integer not null references activities(id) on delete restrict + .foreign_key( + ForeignKey::create() + .name("activityshortcuts_link_fkey") + .from(ActivityShortcuts::Table, ActivityShortcuts::Link) + .to(Activities::Table, Activities::Id) + .on_delete(ForeignKeyAction::Cascade) // Drop all shortcuts if activity is dropped. + .on_update(ForeignKeyAction::Cascade), + ) + .to_owned(), + ) + .await + } + + async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { + // drop index activities_name_idx; + manager + .drop_index(Index::drop().name("activities_name_idx").to_owned()) + .await?; + // drop table activityshortcuts; + manager + .drop_table(Table::drop().table(ActivityShortcuts::Table).to_owned()) + .await?; + // drop table activities; + manager + .drop_table(Table::drop().table(Activities::Table).to_owned()) + .await + } +} diff --git a/migration/src/m20180508_101240_create_planned_activities.rs b/migration/src/m20180508_101240_create_planned_activities.rs new file mode 100644 index 00000000..6b30fee7 --- /dev/null +++ b/migration/src/m20180508_101240_create_planned_activities.rs @@ -0,0 +1,101 @@ +use { + crate::{Activities, Guardians, PlannedActivities, PlannedActivityMembers}, + sea_orm_migration::{prelude::*, schema::*}, +}; + +#[derive(DeriveMigrationName)] +pub struct Migration; + +#[async_trait::async_trait] +impl MigrationTrait for Migration { + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .create_table( + Table::create() + .if_not_exists() + .table(PlannedActivities::Table) // create table plannedactivities ( + .col(pk_auto(PlannedActivities::Id)) // id serial primary key not null, + .col(string(PlannedActivities::AuthorId)) // author_id integer not null references guardians(id), + .col(integer(PlannedActivities::ActivityId)) // activity_id integer not null references activities(id) on delete restrict on update cascade, + .col(string_null(PlannedActivities::Details)) // details text, + .col(timestamp_with_time_zone(PlannedActivities::Start)) // start timestamp with time zone not null + .foreign_key( + ForeignKey::create() + .name("plannedactivities_author_id_fkey") + .from(PlannedActivities::Table, PlannedActivities::AuthorId) + .to(Guardians::Table, Guardians::Id), + ) + // -- Disallow activity drop if plannedactivities exist. + .foreign_key( + ForeignKey::create() + .name("plannedactivities_activity_id_fkey") + .from(PlannedActivities::Table, PlannedActivities::ActivityId) + .to(Activities::Table, Activities::Id) + .on_delete(ForeignKeyAction::Restrict) + .on_update(ForeignKeyAction::Cascade), + ) + .to_owned(), + ) + .await?; + + manager + .create_table( + Table::create() + .if_not_exists() + .table(PlannedActivityMembers::Table) // create table plannedactivitymembers ( + .col(pk_auto(PlannedActivityMembers::Id)) // id serial primary key not null, + .col(integer(PlannedActivityMembers::PlannedActivityId)) // planned_activity_id integer not null references plannedactivities(id), + .col(integer(PlannedActivityMembers::UserId)) // user_id integer not null references guardians(id), + .col( + timestamp_with_time_zone(PlannedActivityMembers::Added) // added timestamp with time zone not null default now(), + .default(Expr::current_timestamp()), + ) + .foreign_key( + ForeignKey::create() + .name("plannedactivitymembers_planned_activity_id_fkey") + .from( + PlannedActivityMembers::Table, + PlannedActivityMembers::PlannedActivityId, + ) + .to(PlannedActivities::Table, PlannedActivities::Id) + .on_delete(ForeignKeyAction::Cascade) // Drop all members if planned activity is dropped. + .on_update(ForeignKeyAction::Cascade), + ) + .foreign_key( + ForeignKey::create() + .name("plannedactivitymembers_user_id_fkey") + .from( + PlannedActivityMembers::Table, + PlannedActivityMembers::UserId, + ) + .to(Guardians::Table, Guardians::Id) + .on_delete(ForeignKeyAction::Cascade) // Drop member if guardian is dropped. + .on_update(ForeignKeyAction::Cascade), + ) + .index( + Index::create() + .unique() // unique (planned_activity_id, user_id) + .name("plannedactivitymembers_planned_activity_id_user_id_key") + .col(PlannedActivityMembers::PlannedActivityId) + .col(PlannedActivityMembers::UserId), + ) + .to_owned(), + ) + .await + } + + async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { + // drop table plannedactivitymembers; + // drop table plannedactivities; + manager + .drop_table( + Table::drop() + .table(PlannedActivityMembers::Table) + .to_owned(), + ) + .await?; + manager + .drop_table(Table::drop().table(PlannedActivities::Table).to_owned()) + .await + } +} diff --git a/migration/src/m20180508_101326_populate_activities.rs b/migration/src/m20180508_101326_populate_activities.rs new file mode 100644 index 00000000..2c8071e7 --- /dev/null +++ b/migration/src/m20180508_101326_populate_activities.rs @@ -0,0 +1,173 @@ +use { + crate::{Activities, ActivityShortcuts}, + sea_orm_migration::{prelude::*, sea_orm::TransactionTrait}, +}; + +#[derive(DeriveMigrationName)] +pub struct Migration; + +#[async_trait::async_trait] +impl MigrationTrait for Migration { + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + // @fixme: this already runs in a transaction, no need for a new one... + let transaction = manager.get_connection().begin().await?; + let builder = transaction.get_database_backend(); + + // INSERT INTO activities (id, name, mode, min_fireteam_size, max_fireteam_size, min_light, min_level) + // VALUES + #[rustfmt::skip] + let activities = vec![ + (1, "Vault of Glass", "normal", 1, 6, Some(200), Some(25)), + (2, "Vault of Glass", "hard", 1, 6, Some(280), Some(30)), + (3, "Crota's End", "normal", 1, 6, Some(200), Some(30)), + (4, "Crota's End", "hard", 1, 6, Some(280), Some(33)), + (5, "King's Fall", "normal", 1, 6, Some(290), Some(35)), + (6, "King's Fall", "hard", 1, 6, Some(320), Some(40)), + (7, "Wrath of the Machine", "normal", 1, 6, Some(360), Some(40)), + (8, "Wrath of the Machine", "hard", 1, 6, Some(380), Some(40)), + (9, "Vanguard", "Patrols", 1, 3, None, None), + (10, "Vanguard", "Archon's Forge", 1, 3, None, None), + (11, "Vanguard", "Court of Oryx", 1, 3, None, None), + (12, "Vanguard", "any", 1, 3, None, None), + (13, "Crucible", "Private Matches", 1, 12, None, None), + (14, "Crucible", "Trials of Osiris", 3, 3, Some(370), Some(40)), + (15, "Crucible", "Iron Banner", 1, 6, Some(350), Some(40)), + (16, "Crucible", "6v6", 1, 6, None, None), + (17, "Crucible", "3v3", 1, 3, None, None), + (19, "Crucible", "Private Tournament", 1, 12, None, None), + (20, "Vanguard", "Challenge of Elders", 1, 3, Some(320), None), + (21, "Vanguard", "Prison of Elders", 1, 3, None, None), + (22, "Vanguard", "Nightfall", 1, 3, Some(380), None), + (18, "Crucible", "any", 1, 6, None, None), + (23, "Dolmen", "any", 1, 12, None, Some(1)), + (25, "Dungeon", "any", 3, 12, None, Some(8)), + (26, "Questing", "any", 1, 12, None, Some(1)), + (27, "Cyrodiil", "pvp", 1, 12, None, Some(10)), + (24, "Delve", "any", 1, 4, None, Some(1)), + (28, "Alienation", "coop", 1, 4, None, Some(1)), + (29, "Crucible", "4v4", 1, 4, None, None), + (30, "Titanfall 2", "coop", 1, 4, None, None), + (31, "Titanfall 2", "pvp", 1, 6, None, None), + (32, "Leviathan", "normal", 1, 6, Some(100), Some(15)), + (33, "Crucible", "Trials of the Nine", 4, 4, Some(250), Some(20)), + (34, "Warframe", "pve", 1, 4, None, None), + (35, "Warframe", "pvp", 1, 4, None, None), + (36, "Warframe", "Index", 1, 4, None, None), + (37, "Warframe", "Raid (obsolete)", 4, 8, None, None), + (38, "Leviathan", "prestige", 1, 6, Some(300), Some(20)), + (39, "Leviathan, Eater of Worlds", "normal", 1, 6, Some(300), Some(20)), + (40, "Leviathan, Eater of Worlds", "prestige", 1, 6, Some(315), Some(25)), + ]; + for act in activities { + let insert = Query::insert() + .into_table(Activities::Table) + .columns([ + Activities::Id, + Activities::Name, + Activities::Mode, + Activities::MinFireteamSize, + Activities::MaxFireteamSize, + Activities::MinLight, + Activities::MinLevel, + ]) + .values_panic([ + act.0.into(), + act.1.into(), + act.2.into(), + act.3.into(), + act.4.into(), + act.5.into(), //.unwrap_or(Expr::value("null")).into(), + act.6.into(), // unwrap_or(Expr::value("null")).into(), + ]) + .to_owned(); + + let insert = builder.build(&insert); + transaction.execute(insert).await?; + } + + // INSERT INTO activityshortcuts (id, name, game, link) + // VALUES + #[rustfmt::skip] + let shortcuts = vec![ + (1, "kf", "Destiny", 6), + (2, "kfh", "Destiny", 6), + (3, "kfn", "Destiny", 5), + (4, "cr", "Destiny", 4), + (5, "crh", "Destiny", 4), + (6, "crn", "Destiny", 3), + (7, "vog", "Destiny", 2), + (8, "vogh", "Destiny", 2), + (9, "vogn", "Destiny", 1), + (10, "wotm", "Destiny", 7), + (11, "wotmh", "Destiny", 8), + (12, "wotmn", "Destiny", 7), + (13, "pvp", "Destiny", 18), + (14, "3v3", "Destiny", 17), + (15, "6v6", "Destiny", 16), + (16, "ib", "Destiny", 15), + (17, "too", "Destiny", 14), + (18, "pvt", "Destiny", 13), + (19, "trn", "Destiny", 19), + (20, "pve", "Destiny", 12), + (21, "patrol", "Destiny", 9), + (22, "coo", "Destiny", 11), + (23, "forge", "Destiny", 10), + (24, "poe", "Destiny", 21), + (25, "coe", "Destiny", 20), + (26, "nf", "Destiny", 22), + (27, "dolmen", "TESO", 23), + (28, "delve", "TESO", 24), + (29, "dung", "TESO", 25), + (30, "quest", "TESO", 26), + (31, "cyro", "TESO", 27), + (32, "cyrod", "TESO", 27), + (33, "alien", "Alienation", 28), + (34, "pvp2", "Destiny 2", 29), + (35, "tf2coop", "Titanfall 2", 30), + (36, "tf2pvp", "Titanfall 2", 31), + (37, "levin", "Destiny 2", 32), + (38, "to9", "Destiny 2", 33), + (39, "wfpve", "Warframe", 34), + (40, "wfpvp", "Warframe", 35), + (41, "wfindex", "Warframe", 36), + (42, "wfraid", "Warframe", 37), + (43, "levip", "Destiny 2", 38), + (44, "eaten", "Destiny 2", 39), + (45, "eatep", "Destiny 2", 40), + ]; + for shr in shortcuts { + let insert = Query::insert() + .into_table(ActivityShortcuts::Table) + .columns([ + ActivityShortcuts::Id, + ActivityShortcuts::Name, + ActivityShortcuts::Game, + ActivityShortcuts::Link, + ]) + .values_panic([shr.0.into(), shr.1.into(), shr.2.into(), shr.3.into()]) + .to_owned(); + + let insert = builder.build(&insert); + transaction.execute(insert).await?; + } + + transaction.commit().await?; + + Ok(()) + } + + async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { + let transaction = manager.get_connection().begin().await?; + + transaction + .execute_unprepared("DELETE FROM activityshortcuts WHERE id BETWEEN 1 AND 45") + .await?; + transaction + .execute_unprepared("DELETE FROM activities WHERE id BETWEEN 1 AND 40") + .await?; + + transaction.commit().await?; + + Ok(()) + } +} diff --git a/migration/src/m20180905_090102_populate_activities.rs b/migration/src/m20180905_090102_populate_activities.rs new file mode 100644 index 00000000..73afedb1 --- /dev/null +++ b/migration/src/m20180905_090102_populate_activities.rs @@ -0,0 +1,115 @@ +use { + crate::{Activities, ActivityShortcuts}, + sea_orm_migration::{ + prelude::*, + sea_orm::{TransactionTrait, Value}, + }, +}; + +#[derive(DeriveMigrationName)] +pub struct Migration; + +#[async_trait::async_trait] +impl MigrationTrait for Migration { + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + // @fixme: this already runs in a transaction, no need for a new one... + let transaction = manager.get_connection().begin().await?; + let builder = transaction.get_database_backend(); + + // INSERT INTO activities (id, name, mode, min_fireteam_size, max_fireteam_size, min_light, min_level) + // VALUES + #[rustfmt::skip] + let activities = vec![ + (41, "Vanguard", "Escalation Protocol", 1, 9, Some(350), None), + (42, "Leviathan, Spire of Stars", "normal", 6, 6, Some(370), Some(30)), + (43, "Leviathan, Spire of Stars", "prestige", 6, 6, Some(385), Some(30)), + (44, "King's Fall", "weekly", 6, 6, Some(390), Some(40)), + (45, "Crota's End", "weekly", 6, 6, Some(390), Some(40)), + (46, "Vault of Glass", "weekly", 6, 6, Some(390), Some(40)), + (47, "Wrath of the Machine", "weekly", 6, 6, Some(390), Some(40)), + (48, "Last Wish", "normal", 6, 6, Some(450), Some(40)), + (49, "Last Wish", "prestige", 6, 6, Some(500), Some(40)), + (50, "Gambit", "pve/pvp", 1, 4, Some(400), Some(30)), + ]; + for act in activities { + let insert = Query::insert() + .into_table(Activities::Table) + .columns([ + Activities::Id, + Activities::Name, + Activities::Mode, + Activities::MinFireteamSize, + Activities::MaxFireteamSize, + Activities::MinLight, + Activities::MinLevel, + ]) + .values_panic([ + act.0.into(), + act.1.into(), + act.2.into(), + act.3.into(), + act.4.into(), + act.5 + .map(|v| v.into()) + .unwrap_or(Expr::value(Value::Int(None))), + act.6 + .map(|v| v.into()) + .unwrap_or(Expr::value(Value::Int(None))), + ]) + .to_owned(); + + let insert = builder.build(&insert); + transaction.execute(insert).await?; + } + + // INSERT INTO activityshortcuts (id, name, game, link) + // VALUES + #[rustfmt::skip] + let shortcuts = vec![ + (46, "escal8", "Destiny 2", 41), + (47, "spiren", "Destiny 2", 42), + (48, "spirep", "Destiny 2", 43), + (49, "kfw", "Destiny", 44), + (50, "crw", "Destiny", 45), + (51, "vogw", "Destiny", 46), + (52, "wotmw", "Destiny", 47), + (53, "lastwn", "Destiny 2", 48), + (54, "lastwp", "Destiny 2", 49), + (55, "gambit", "Destiny 2", 50), + ]; + for shr in shortcuts { + let insert = Query::insert() + .into_table(ActivityShortcuts::Table) + .columns([ + ActivityShortcuts::Id, + ActivityShortcuts::Name, + ActivityShortcuts::Game, + ActivityShortcuts::Link, + ]) + .values_panic([shr.0.into(), shr.1.into(), shr.2.into(), shr.3.into()]) + .to_owned(); + + let insert = builder.build(&insert); + transaction.execute(insert).await?; + } + + transaction.commit().await?; + + Ok(()) + } + + async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { + let transaction = manager.get_connection().begin().await?; + + transaction + .execute_unprepared("DELETE FROM activityshortcuts WHERE id BETWEEN 46 AND 55") + .await?; + transaction + .execute_unprepared("DELETE FROM activities WHERE id BETWEEN 41 AND 50") + .await?; + + transaction.commit().await?; + + Ok(()) + } +} diff --git a/migration/src/m20180921_110336_add_admins.rs b/migration/src/m20180921_110336_add_admins.rs new file mode 100644 index 00000000..4563b1ef --- /dev/null +++ b/migration/src/m20180921_110336_add_admins.rs @@ -0,0 +1,42 @@ +use { + crate::Guardians, + sea_orm_migration::{prelude::*, schema::*}, +}; + +#[derive(DeriveMigrationName)] +pub struct Migration; + +#[async_trait::async_trait] +impl MigrationTrait for Migration { + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + // alter table guardians add column is_admin boolean not null default false; + manager + .alter_table( + Table::alter() + .table(Guardians::Table) + .add_column(boolean(Guardians::IsAdmin).default(Expr::value(false))) + .to_owned(), + ) + .await?; + // update guardians set is_admin = true where telegram_name = 'berkus'; + // entity::Guardians::update_one(); + let update = Query::update() + .table(Guardians::Table) + .value(Guardians::IsAdmin, true) + .and_where(Expr::col(Guardians::TelegramName).eq("berkus")) + .to_owned(); + manager.exec_stmt(update).await + } + + async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { + // alter table guardians drop column is_admin; + manager + .alter_table( + Table::alter() + .table(Guardians::Table) + .drop_column(Guardians::IsAdmin) + .to_owned(), + ) + .await + } +} diff --git a/migration/src/m20180922_104727_add_superadmins.rs b/migration/src/m20180922_104727_add_superadmins.rs new file mode 100644 index 00000000..271cf446 --- /dev/null +++ b/migration/src/m20180922_104727_add_superadmins.rs @@ -0,0 +1,42 @@ +use { + crate::Guardians, + sea_orm_migration::{prelude::*, schema::*}, +}; + +#[derive(DeriveMigrationName)] +pub struct Migration; + +#[async_trait::async_trait] +impl MigrationTrait for Migration { + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + // alter table guardians add column is_superadmin boolean not null default false; + manager + .alter_table( + Table::alter() + .table(Guardians::Table) + .add_column(boolean(Guardians::IsSuperadmin).default(Expr::value(false))) + .to_owned(), + ) + .await?; + // update guardians set is_superadmin = true where telegram_name = 'berkus'; + let update = Query::update() + .table(Guardians::Table) + .value(Guardians::IsSuperadmin, true) + .and_where(Expr::col(Guardians::TelegramName).eq("berkus")) + .to_owned(); + + manager.exec_stmt(update).await + } + + async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { + // alter table guardians drop column is_superadmin; + manager + .alter_table( + Table::alter() + .table(Guardians::Table) + .drop_column(Guardians::IsSuperadmin) + .to_owned(), + ) + .await + } +} diff --git a/migration/src/m20180926_165439_add_foreign_keys.rs b/migration/src/m20180926_165439_add_foreign_keys.rs new file mode 100644 index 00000000..69667be6 --- /dev/null +++ b/migration/src/m20180926_165439_add_foreign_keys.rs @@ -0,0 +1,75 @@ +use { + crate::{Activities, ActivityShortcuts, Guardians, PlannedActivities, PlannedActivityMembers}, + sea_orm_migration::{prelude::*, schema::*}, +}; + +#[derive(DeriveMigrationName)] +pub struct Migration; + +#[async_trait::async_trait] +impl MigrationTrait for Migration { + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + // Replace the sample below with your own migration scripts + todo!(); + + // -- Links between activities and activityshortcuts + // -- Drop all shortcuts if activity is dropped. + // alter table activityshortcuts drop constraint if exists activityshortcuts_link_fkey; + // alter table activityshortcuts add constraint activityshortcuts_link_fkey + // foreign key (link) references activities(id) on delete cascade on update cascade; + + // -- Links between activities and plannedactivities + // -- Disallow activity drop if plannedactivities exist. + // alter table plannedactivities drop constraint if exists plannedactivities_activity_id_fkey; + // alter table plannedactivities add constraint plannedactivities_activity_id_fkey + // foreign key (activity_id) references activities(id) on delete restrict on update cascade; + + // -- Links between plannedactivitymembers and plannedactivities + // -- Drop all members if planned activity is dropped. + // alter table plannedactivitymembers drop constraint if exists plannedactivitymembers_planned_activity_id_fkey; + // alter table plannedactivitymembers add constraint plannedactivitymembers_planned_activity_id_fkey + // foreign key (planned_activity_id) references plannedactivities(id) on delete cascade on update cascade; + + // -- Drop member if guardian is dropped. + // alter table plannedactivitymembers drop constraint if exists plannedactivitymembers_user_id_fkey; + // alter table plannedactivitymembers add constraint plannedactivitymembers_user_id_fkey + // foreign key (user_id) references guardians(id) on delete cascade on update cascade; + + manager + .create_table( + Table::create() + .table(Post::Table) + .if_not_exists() + .col(pk_auto(Post::Id)) + .col(string(Post::Title)) + .col(string(Post::Text)) + .to_owned(), + ) + .await + } + + async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { + // Replace the sample below with your own migration scripts + todo!(); + + // alter table activityshortcuts drop constraint if exists activityshortcuts_link_fkey; + // alter table activityshortcuts add constraint activityshortcuts_link_fkey + // foreign key (link) references activities(id) on delete restrict; + + // alter table plannedactivities drop constraint if exists plannedactivities_activity_id_fkey; + // alter table plannedactivities add constraint plannedactivities_activity_id_fkey + // foreign key (activity_id) references activities(id); + + // alter table plannedactivitymembers drop constraint if exists plannedactivitymembers_planned_activity_id_fkey; + // alter table plannedactivitymembers add constraint plannedactivitymembers_planned_activity_id_fkey + // foreign key (planned_activity_id) references plannedactivities(id); + + // alter table plannedactivitymembers drop constraint if exists plannedactivitymembers_user_id_fkey; + // alter table plannedactivitymembers add constraint plannedactivitymembers_user_id_fkey + // foreign key (user_id) references guardians(id); + + manager + .drop_table(Table::drop().table(Post::Table).to_owned()) + .await + } +} diff --git a/migration/src/main.rs b/migration/src/main.rs new file mode 100644 index 00000000..f054deaf --- /dev/null +++ b/migration/src/main.rs @@ -0,0 +1,6 @@ +use sea_orm_migration::prelude::*; + +#[tokio::main] +async fn main() { + cli::run_cli(migration::Migrator).await; +} diff --git a/migration/src/tables.rs b/migration/src/tables.rs new file mode 100644 index 00000000..576150e8 --- /dev/null +++ b/migration/src/tables.rs @@ -0,0 +1,72 @@ +use sea_orm_migration::prelude::*; + +#[derive(DeriveIden)] +pub enum Alerts { + Table, + Id, + Guid, + Title, + Type, + StartDate, + ExpiryDate, + Faction, + Flavor, +} + +#[derive(DeriveIden)] +pub enum Activities { + Table, + Id, + Name, + Mode, + MinFireteamSize, + MaxFireteamSize, + MinLight, + MinLevel, +} + +#[derive(DeriveIden)] +pub enum ActivityShortcuts { + Table, + Id, + Name, + Game, + Link, +} + +#[derive(DeriveIden)] +pub enum PlannedActivities { + Table, + Id, + AuthorId, + ActivityId, + Details, + Start, +} + +#[derive(DeriveIden)] +pub enum PlannedActivityMembers { + Table, + Id, + PlannedActivityId, + UserId, + Added, +} + +#[derive(DeriveIden)] +pub enum Guardians { + Table, + Id, + TelegramName, + TelegramId, + PsnName, + Email, + PsnClan, + CreatedAt, + UpdatedAt, + DeletedAt, + Tokens, + PendingActivationCode, + IsAdmin, + IsSuperadmin, +} diff --git a/migrations/.gitkeep b/migrations/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/migrations/00000000000000_diesel_initial_setup/down.sql b/migrations/00000000000000_diesel_initial_setup/down.sql deleted file mode 100644 index a9f52609..00000000 --- a/migrations/00000000000000_diesel_initial_setup/down.sql +++ /dev/null @@ -1,6 +0,0 @@ --- This file was automatically created by Diesel to setup helper functions --- and other internal bookkeeping. This file is safe to edit, any future --- changes will be added to existing projects as new migrations. - -DROP FUNCTION IF EXISTS diesel_manage_updated_at(_tbl regclass); -DROP FUNCTION IF EXISTS diesel_set_updated_at(); diff --git a/migrations/00000000000000_diesel_initial_setup/up.sql b/migrations/00000000000000_diesel_initial_setup/up.sql deleted file mode 100644 index d68895b1..00000000 --- a/migrations/00000000000000_diesel_initial_setup/up.sql +++ /dev/null @@ -1,36 +0,0 @@ --- This file was automatically created by Diesel to setup helper functions --- and other internal bookkeeping. This file is safe to edit, any future --- changes will be added to existing projects as new migrations. - - - - --- Sets up a trigger for the given table to automatically set a column called --- `updated_at` whenever the row is modified (unless `updated_at` was included --- in the modified columns) --- --- # Example --- --- ```sql --- CREATE TABLE users (id SERIAL PRIMARY KEY, updated_at TIMESTAMP NOT NULL DEFAULT NOW()); --- --- SELECT diesel_manage_updated_at('users'); --- ``` -CREATE OR REPLACE FUNCTION diesel_manage_updated_at(_tbl regclass) RETURNS VOID AS $$ -BEGIN - EXECUTE format('CREATE TRIGGER set_updated_at BEFORE UPDATE ON %s - FOR EACH ROW EXECUTE PROCEDURE diesel_set_updated_at()', _tbl); -END; -$$ LANGUAGE plpgsql; - -CREATE OR REPLACE FUNCTION diesel_set_updated_at() RETURNS trigger AS $$ -BEGIN - IF ( - NEW IS DISTINCT FROM OLD AND - NEW.updated_at IS NOT DISTINCT FROM OLD.updated_at - ) THEN - NEW.updated_at := current_timestamp; - END IF; - RETURN NEW; -END; -$$ LANGUAGE plpgsql; diff --git a/migrations/2018-05-08-101204_create_alerts/down.sql b/migrations/2018-05-08-101204_create_alerts/down.sql deleted file mode 100644 index 84810116..00000000 --- a/migrations/2018-05-08-101204_create_alerts/down.sql +++ /dev/null @@ -1 +0,0 @@ -drop table alerts; diff --git a/migrations/2018-05-08-101204_create_alerts/up.sql b/migrations/2018-05-08-101204_create_alerts/up.sql deleted file mode 100644 index b5b57088..00000000 --- a/migrations/2018-05-08-101204_create_alerts/up.sql +++ /dev/null @@ -1,10 +0,0 @@ -create table alerts ( - id serial primary key not null, - guid text not null unique, - title text not null, - type text not null, - startdate timestamp without time zone not null, - expirydate timestamp without time zone, - faction text, - flavor text -); diff --git a/migrations/2018-05-08-101216_create_guardians/down.sql b/migrations/2018-05-08-101216_create_guardians/down.sql deleted file mode 100644 index cf137a7b..00000000 --- a/migrations/2018-05-08-101216_create_guardians/down.sql +++ /dev/null @@ -1 +0,0 @@ -drop table guardians; diff --git a/migrations/2018-05-08-101216_create_guardians/up.sql b/migrations/2018-05-08-101216_create_guardians/up.sql deleted file mode 100644 index bdb7126f..00000000 --- a/migrations/2018-05-08-101216_create_guardians/up.sql +++ /dev/null @@ -1,13 +0,0 @@ -create table guardians ( - id serial primary key not null, - telegram_name text not null unique, - telegram_id bigint not null unique, - psn_name text not null, - email text, - psn_clan text, - created_at timestamp without time zone not null default now(), - updated_at timestamp without time zone not null default now(), - deleted_at timestamp without time zone default null, - tokens jsonb, - pending_activation_code text -); diff --git a/migrations/2018-05-08-101223_create_activities/down.sql b/migrations/2018-05-08-101223_create_activities/down.sql deleted file mode 100644 index 23885e15..00000000 --- a/migrations/2018-05-08-101223_create_activities/down.sql +++ /dev/null @@ -1,3 +0,0 @@ -drop index activities_name_idx; -drop table activityshortcuts; -drop table activities; diff --git a/migrations/2018-05-08-101223_create_activities/up.sql b/migrations/2018-05-08-101223_create_activities/up.sql deleted file mode 100644 index 8efdf8f6..00000000 --- a/migrations/2018-05-08-101223_create_activities/up.sql +++ /dev/null @@ -1,18 +0,0 @@ -create table activities ( - id serial primary key not null, - name text not null, - mode text, - min_fireteam_size integer not null, - max_fireteam_size integer not null, - min_light integer, - min_level integer -); - -create index activities_name_idx on activities(name); - -create table activityshortcuts ( - id serial primary key not null, - name text not null unique, - game text not null, - link integer not null references activities(id) on delete restrict -); diff --git a/migrations/2018-05-08-101240_create_planned_activities/down.sql b/migrations/2018-05-08-101240_create_planned_activities/down.sql deleted file mode 100644 index 22e5a8de..00000000 --- a/migrations/2018-05-08-101240_create_planned_activities/down.sql +++ /dev/null @@ -1,2 +0,0 @@ -drop table plannedactivitymembers; -drop table plannedactivities; diff --git a/migrations/2018-05-08-101240_create_planned_activities/up.sql b/migrations/2018-05-08-101240_create_planned_activities/up.sql deleted file mode 100644 index 44110798..00000000 --- a/migrations/2018-05-08-101240_create_planned_activities/up.sql +++ /dev/null @@ -1,15 +0,0 @@ -create table plannedactivities ( - id serial primary key not null, - author_id integer not null references guardians(id), - activity_id integer not null references activities(id), - details text, - start timestamp without time zone not null -); - -create table plannedactivitymembers ( - id serial primary key not null, - planned_activity_id integer not null references plannedactivities(id), - user_id integer not null references guardians(id), - added timestamp without time zone not null default now(), - unique (planned_activity_id, user_id) -); diff --git a/migrations/2018-05-08-101245_create_reminders/down.sql b/migrations/2018-05-08-101245_create_reminders/down.sql deleted file mode 100644 index 4d68dbf1..00000000 --- a/migrations/2018-05-08-101245_create_reminders/down.sql +++ /dev/null @@ -1 +0,0 @@ -drop table plannedactivityreminders; diff --git a/migrations/2018-05-08-101245_create_reminders/up.sql b/migrations/2018-05-08-101245_create_reminders/up.sql deleted file mode 100644 index 0c550d08..00000000 --- a/migrations/2018-05-08-101245_create_reminders/up.sql +++ /dev/null @@ -1,6 +0,0 @@ -create table plannedactivityreminders ( - id serial primary key not null, - planned_activity_id integer not null references plannedactivities(id), - user_id integer not null references guardians(id), - remind timestamp without time zone not null -); diff --git a/migrations/2018-05-08-101326_populate_activities/down.sql b/migrations/2018-05-08-101326_populate_activities/down.sql deleted file mode 100644 index f5c34e59..00000000 --- a/migrations/2018-05-08-101326_populate_activities/down.sql +++ /dev/null @@ -1,2 +0,0 @@ -delete from activityshortcuts where id between 1 and 45; -delete from activities where id between 1 and 40; diff --git a/migrations/2018-05-08-101326_populate_activities/up.sql b/migrations/2018-05-08-101326_populate_activities/up.sql deleted file mode 100644 index 1f5d6b20..00000000 --- a/migrations/2018-05-08-101326_populate_activities/up.sql +++ /dev/null @@ -1,90 +0,0 @@ -INSERT INTO activities (id, name, mode, min_fireteam_size, max_fireteam_size, min_light, min_level) -VALUES -(1 , 'Vault of Glass', 'normal', 1, 6, 200, 25), -(2 , 'Vault of Glass', 'hard', 1, 6, 280, 30), -(3 , 'Crota''s End', 'normal', 1, 6, 200, 30), -(4 , 'Crota''s End', 'hard', 1, 6, 280, 33), -(5 , 'King''s Fall', 'normal', 1, 6, 290, 35), -(6 , 'King''s Fall', 'hard', 1, 6, 320, 40), -(7 , 'Wrath of the Machine', 'normal', 1, 6, 360, 40), -(8 , 'Wrath of the Machine', 'hard', 1, 6, 380, 40), -(9 , 'Vanguard', 'Patrols', 1, 3, null, null), -(10, 'Vanguard', 'Archon''s Forge', 1, 3, null, null), -(11, 'Vanguard', 'Court of Oryx', 1, 3, null, null), -(12, 'Vanguard', 'any', 1, 3, null, null), -(13, 'Crucible', 'Private Matches', 1, 12, null, null), -(14, 'Crucible', 'Trials of Osiris', 3, 3, 370, 40), -(15, 'Crucible', 'Iron Banner', 1, 6, 350, 40), -(16, 'Crucible', '6v6', 1, 6, null, null), -(17, 'Crucible', '3v3', 1, 3, null, null), -(19, 'Crucible', 'Private Tournament', 1, 12, null, null), -(20, 'Vanguard', 'Challenge of Elders', 1, 3, 320, null), -(21, 'Vanguard', 'Prison of Elders', 1, 3, null, null), -(22, 'Vanguard', 'Nightfall', 1, 3, 380, null), -(18, 'Crucible', 'any', 1, 6, null, null), -(23, 'Dolmen' , 'any', 1, 12, null, 1), -(25, 'Dungeon', 'any', 3, 12, null, 8), -(26, 'Questing', 'any', 1, 12, null, 1), -(27, 'Cyrodiil', 'pvp', 1, 12, null, 10), -(24, 'Delve', 'any', 1, 4, null, 1), -(28, 'Alienation', 'coop', 1, 4, null, 1), -(29, 'Crucible', '4v4', 1, 4, null, null), -(30, 'Titanfall 2', 'coop', 1, 4, null, null), -(31, 'Titanfall 2', 'pvp', 1, 6, null, null), -(32, 'Leviathan', 'normal', 1, 6, 100, 15), -(33, 'Crucible', 'Trials of the Nine', 4, 4, 250, 20), -(34, 'Warframe', 'pve', 1, 4, null, null), -(35, 'Warframe', 'pvp', 1, 4, null, null), -(36, 'Warframe', 'Index', 1, 4, null, null), -(37, 'Warframe', 'Raid (obsolete)', 4, 8, null, null), -(38, 'Leviathan', 'prestige', 1, 6, 300, 20), -(39, 'Leviathan, Eater of Worlds', 'normal', 1, 6, 300, 20), -(40, 'Leviathan, Eater of Worlds', 'prestige', 1, 6, 315, 25); - -INSERT INTO activityshortcuts (id, name, game, link) -VALUES -(1, 'kf', 'Destiny', 6), -(2, 'kfh', 'Destiny', 6), -(3, 'kfn', 'Destiny', 5), -(4, 'cr', 'Destiny', 4), -(5, 'crh', 'Destiny', 4), -(6, 'crn', 'Destiny', 3), -(7, 'vog', 'Destiny', 2), -(8, 'vogh', 'Destiny', 2), -(9, 'vogn', 'Destiny', 1), -(10, 'wotm', 'Destiny', 7), -(11, 'wotmh', 'Destiny', 8), -(12, 'wotmn', 'Destiny', 7), -(13, 'pvp', 'Destiny', 18), -(14, '3v3', 'Destiny', 17), -(15, '6v6', 'Destiny', 16), -(16, 'ib', 'Destiny', 15), -(17, 'too', 'Destiny', 14), -(18, 'pvt', 'Destiny', 13), -(19, 'trn', 'Destiny', 19), -(20, 'pve', 'Destiny', 12), -(21, 'patrol', 'Destiny', 9), -(22, 'coo', 'Destiny', 11), -(23, 'forge', 'Destiny', 10), -(24, 'poe', 'Destiny', 21), -(25, 'coe', 'Destiny', 20), -(26, 'nf', 'Destiny', 22), -(27, 'dolmen', 'TESO', 23), -(28, 'delve', 'TESO', 24), -(29, 'dung', 'TESO', 25), -(30, 'quest', 'TESO', 26), -(31, 'cyro', 'TESO', 27), -(32, 'cyrod', 'TESO', 27), -(33, 'alien', 'Alienation', 28), -(34, 'pvp2', 'Destiny 2', 29), -(35, 'tf2coop', 'Titanfall 2', 30), -(36, 'tf2pvp', 'Titanfall 2', 31), -(37, 'levin', 'Destiny 2', 32), -(38, 'to9', 'Destiny 2', 33), -(39, 'wfpve', 'Warframe', 34), -(40, 'wfpvp', 'Warframe', 35), -(41, 'wfindex', 'Warframe', 36), -(42, 'wfraid', 'Warframe', 37), -(43, 'levip', 'Destiny 2', 38), -(44, 'eaten', 'Destiny 2', 39), -(45, 'eatep', 'Destiny 2', 40); diff --git a/migrations/2018-09-05-090102_populate_activities/down.sql b/migrations/2018-09-05-090102_populate_activities/down.sql deleted file mode 100644 index 749f452c..00000000 --- a/migrations/2018-09-05-090102_populate_activities/down.sql +++ /dev/null @@ -1,2 +0,0 @@ -delete from activityshortcuts where id between 46 and 55; -delete from activities where id between 41 and 50; diff --git a/migrations/2018-09-05-090102_populate_activities/up.sql b/migrations/2018-09-05-090102_populate_activities/up.sql deleted file mode 100644 index 3d402efc..00000000 --- a/migrations/2018-09-05-090102_populate_activities/up.sql +++ /dev/null @@ -1,25 +0,0 @@ -INSERT INTO activities (id, name, mode, min_fireteam_size, max_fireteam_size, min_light, min_level) -VALUES -(41, 'Vanguard', 'Escalation Protocol', 1, 9, 350, null), -(42, 'Leviathan, Spire of Stars', 'normal', 6, 6, 370, 30), -(43, 'Leviathan, Spire of Stars', 'prestige', 6, 6, 385, 30), -(44, 'King''s Fall', 'weekly', 6, 6, 390, 40), -(45, 'Crota''s End', 'weekly', 6, 6, 390, 40), -(46, 'Vault of Glass', 'weekly', 6, 6, 390, 40), -(47, 'Wrath of the Machine', 'weekly', 6, 6, 390, 40), -(48, 'Last Wish', 'normal', 6, 6, 450, 40), -(49, 'Last Wish', 'prestige', 6, 6, 500, 40), -(50, 'Gambit', 'pve/pvp', 1, 4, 400, 30); - -INSERT INTO activityshortcuts (id, name, game, link) -VALUES -(46, 'escal8', 'Destiny 2', 41), -(47, 'spiren', 'Destiny 2', 42), -(48, 'spirep', 'Destiny 2', 43), -(49, 'kfw' , 'Destiny' , 44), -(50, 'crw' , 'Destiny' , 45), -(51, 'vogw' , 'Destiny' , 46), -(52, 'wotmw' , 'Destiny' , 47), -(53, 'lastwn', 'Destiny 2', 48), -(54, 'lastwp', 'Destiny 2', 49), -(55, 'gambit', 'Destiny 2', 50); diff --git a/migrations/2018-09-05-091147_no-reminders-table/down.sql b/migrations/2018-09-05-091147_no-reminders-table/down.sql deleted file mode 100644 index 0c550d08..00000000 --- a/migrations/2018-09-05-091147_no-reminders-table/down.sql +++ /dev/null @@ -1,6 +0,0 @@ -create table plannedactivityreminders ( - id serial primary key not null, - planned_activity_id integer not null references plannedactivities(id), - user_id integer not null references guardians(id), - remind timestamp without time zone not null -); diff --git a/migrations/2018-09-05-091147_no-reminders-table/up.sql b/migrations/2018-09-05-091147_no-reminders-table/up.sql deleted file mode 100644 index 4d68dbf1..00000000 --- a/migrations/2018-09-05-091147_no-reminders-table/up.sql +++ /dev/null @@ -1 +0,0 @@ -drop table plannedactivityreminders; diff --git a/migrations/2018-09-05-133606_switch_to_timestamptz/down.sql b/migrations/2018-09-05-133606_switch_to_timestamptz/down.sql deleted file mode 100644 index a4889d11..00000000 --- a/migrations/2018-09-05-133606_switch_to_timestamptz/down.sql +++ /dev/null @@ -1,7 +0,0 @@ -ALTER TABLE alerts ALTER COLUMN startdate TYPE timestamp USING startdate AT TIME ZONE 'Europe/Moscow'; -ALTER TABLE alerts ALTER COLUMN expirydate TYPE timestamp USING expirydate AT TIME ZONE 'Europe/Moscow'; -ALTER TABLE guardians ALTER COLUMN created_at TYPE timestamp USING created_at AT TIME ZONE 'Europe/Moscow'; -ALTER TABLE guardians ALTER COLUMN updated_at TYPE timestamp USING updated_at AT TIME ZONE 'Europe/Moscow'; -ALTER TABLE guardians ALTER COLUMN deleted_at TYPE timestamp USING deleted_at AT TIME ZONE 'Europe/Moscow'; -ALTER TABLE plannedactivities ALTER COLUMN start TYPE timestamp USING start AT TIME ZONE 'Europe/Moscow'; -ALTER TABLE plannedactivitymembers ALTER COLUMN added TYPE timestamp USING added AT TIME ZONE 'Europe/Moscow'; diff --git a/migrations/2018-09-05-133606_switch_to_timestamptz/up.sql b/migrations/2018-09-05-133606_switch_to_timestamptz/up.sql deleted file mode 100644 index 9baa2b58..00000000 --- a/migrations/2018-09-05-133606_switch_to_timestamptz/up.sql +++ /dev/null @@ -1,7 +0,0 @@ -ALTER TABLE alerts ALTER COLUMN startdate TYPE timestamp with time zone USING startdate AT TIME ZONE 'Europe/Moscow'; -ALTER TABLE alerts ALTER COLUMN expirydate TYPE timestamp with time zone USING expirydate AT TIME ZONE 'Europe/Moscow'; -ALTER TABLE guardians ALTER COLUMN created_at TYPE timestamp with time zone USING created_at AT TIME ZONE 'Europe/Moscow'; -ALTER TABLE guardians ALTER COLUMN updated_at TYPE timestamp with time zone USING updated_at AT TIME ZONE 'Europe/Moscow'; -ALTER TABLE guardians ALTER COLUMN deleted_at TYPE timestamp with time zone USING deleted_at AT TIME ZONE 'Europe/Moscow'; -ALTER TABLE plannedactivities ALTER COLUMN start TYPE timestamp with time zone USING start AT TIME ZONE 'Europe/Moscow'; -ALTER TABLE plannedactivitymembers ALTER COLUMN added TYPE timestamp with time zone USING added AT TIME ZONE 'Europe/Moscow'; diff --git a/migrations/2018-09-21-110336_add_admins/down.sql b/migrations/2018-09-21-110336_add_admins/down.sql deleted file mode 100644 index 946dba4a..00000000 --- a/migrations/2018-09-21-110336_add_admins/down.sql +++ /dev/null @@ -1 +0,0 @@ -alter table guardians drop column is_admin; diff --git a/migrations/2018-09-21-110336_add_admins/up.sql b/migrations/2018-09-21-110336_add_admins/up.sql deleted file mode 100644 index 50874cea..00000000 --- a/migrations/2018-09-21-110336_add_admins/up.sql +++ /dev/null @@ -1,3 +0,0 @@ -alter table guardians add column is_admin boolean not null default false; - -update guardians set is_admin = true where telegram_name = 'berkus'; diff --git a/migrations/2018-09-22-104727_add_superadmins/down.sql b/migrations/2018-09-22-104727_add_superadmins/down.sql deleted file mode 100644 index a2552e29..00000000 --- a/migrations/2018-09-22-104727_add_superadmins/down.sql +++ /dev/null @@ -1 +0,0 @@ -alter table guardians drop column is_superadmin; diff --git a/migrations/2018-09-22-104727_add_superadmins/up.sql b/migrations/2018-09-22-104727_add_superadmins/up.sql deleted file mode 100644 index eb3f467b..00000000 --- a/migrations/2018-09-22-104727_add_superadmins/up.sql +++ /dev/null @@ -1,3 +0,0 @@ -alter table guardians add column is_superadmin boolean not null default false; - -update guardians set is_superadmin = true where telegram_name = 'berkus'; diff --git a/migrations/2018-09-26-165439_add_foreign_keys/down.sql b/migrations/2018-09-26-165439_add_foreign_keys/down.sql deleted file mode 100644 index c4303e64..00000000 --- a/migrations/2018-09-26-165439_add_foreign_keys/down.sql +++ /dev/null @@ -1,15 +0,0 @@ -alter table activityshortcuts drop constraint if exists activityshortcuts_link_fkey; -alter table activityshortcuts add constraint activityshortcuts_link_fkey -foreign key (link) references activities(id) on delete restrict; - -alter table plannedactivities drop constraint if exists plannedactivities_activity_id_fkey; -alter table plannedactivities add constraint plannedactivities_activity_id_fkey -foreign key (activity_id) references activities(id); - -alter table plannedactivitymembers drop constraint if exists plannedactivitymembers_planned_activity_id_fkey; -alter table plannedactivitymembers add constraint plannedactivitymembers_planned_activity_id_fkey -foreign key (planned_activity_id) references plannedactivities(id); - -alter table plannedactivitymembers drop constraint if exists plannedactivitymembers_user_id_fkey; -alter table plannedactivitymembers add constraint plannedactivitymembers_user_id_fkey -foreign key (user_id) references guardians(id); diff --git a/migrations/2018-09-26-165439_add_foreign_keys/up.sql b/migrations/2018-09-26-165439_add_foreign_keys/up.sql deleted file mode 100644 index f90ecd37..00000000 --- a/migrations/2018-09-26-165439_add_foreign_keys/up.sql +++ /dev/null @@ -1,22 +0,0 @@ --- Links between activities and activityshortcuts --- Drop all shortcuts if activity is dropped. -alter table activityshortcuts drop constraint if exists activityshortcuts_link_fkey; -alter table activityshortcuts add constraint activityshortcuts_link_fkey -foreign key (link) references activities(id) on delete cascade on update cascade; - --- Links between activities and plannedactivities --- Disallow activity drop if plannedactivities exist. -alter table plannedactivities drop constraint if exists plannedactivities_activity_id_fkey; -alter table plannedactivities add constraint plannedactivities_activity_id_fkey -foreign key (activity_id) references activities(id) on delete restrict on update cascade; - --- Links between plannedactivitymembers and plannedactivities --- Drop all members if planned activity is dropped. -alter table plannedactivitymembers drop constraint if exists plannedactivitymembers_planned_activity_id_fkey; -alter table plannedactivitymembers add constraint plannedactivitymembers_planned_activity_id_fkey -foreign key (planned_activity_id) references plannedactivities(id) on delete cascade on update cascade; - --- Drop member if guardian is dropped. -alter table plannedactivitymembers drop constraint if exists plannedactivitymembers_user_id_fkey; -alter table plannedactivitymembers add constraint plannedactivitymembers_user_id_fkey -foreign key (user_id) references guardians(id) on delete cascade on update cascade; From ea84efa9ad0538fe21022dc0198bbe4dc9303827 Mon Sep 17 00:00:00 2001 From: Berkus Decker Date: Wed, 20 Aug 2025 14:56:21 +0300 Subject: [PATCH 06/26] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Switch=20from=20dies?= =?UTF-8?q?el=20to=20sea-orm?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 1 + TODO.md | 11 +---- bot/Cargo.toml | 2 + bot/src/commands/mod.rs | 96 +++++++++++++++++++++++------------------ bot/src/lib.rs | 28 +++--------- 5 files changed, 65 insertions(+), 73 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d1a46b30..3ea019eb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -24,6 +24,7 @@ dependencies = [ "anyhow", "chrono", "chrono-tz 0.10.4", + "culpa", "dotenv", "entity", "fern", diff --git a/TODO.md b/TODO.md index 398936d0..8020360e 100644 --- a/TODO.md +++ b/TODO.md @@ -39,9 +39,6 @@ impl ActiveModel { - [ ] Add waiting-list for activities. - [ ] Mark past WF events with "(ended) " prefix. -- [ ] Handle diesel in async using blocking() handoff - - https://docs.rs/tokio-threadpool/0.1/tokio_threadpool/fn.blocking.html - - tokio_diesel - [ ] HANDLE ERRORS - [x] match guardian- and telegram-names case-insensitive (use filter(`.ilike`(match))) @done (19-05-21 22:09) - [ ] GUARDIAN_ID could be int, telegram name or psn name @@ -54,19 +51,13 @@ impl ActiveModel { - [ ] Interactive calendar + clock picker to plan raids - [ ] this may require full actor framework already to track states properly - [ ] actor per user creating raid? -- [ ] Rewrite with actors, self-healing and other nice-to-have things. +- [x] Rewrite with actors, self-healing and other nice-to-have things. - actix / riker could be used for structuring the bot as independent entities 1. supervisor to restart failing telegram connection - see telegram-event-bot for structure idea - telecord uses a simplistic approach - 2. actix-diesel thingie to run blocking diesel in a separate actor - - https://github.com/actix/examples/blob/f8e3570bd16bcf816070af20f210ce1b4f2fca69/diesel/src/main.rs#L64-L70 3. futures-cpupool as a very primitive wrapper - https://github.com/diesel-rs/diesel/issues/399 - 4. tokio threadpool blocking tasks - - https://github.com/tokio-rs/tokio/pull/317 - 5. bb8 as diesel pool - - could this help? 6. Marat promised actix-telegram crate on 2018-09-12 in @rustlang_ru chat: https://github.com/jeizsm/actix-telegram/blob/autogenerated_types/examples/polling.rs ✔ it's now available @done (18-09-22 19:41) diff --git a/bot/Cargo.toml b/bot/Cargo.toml index 2036bafc..a7d44d4a 100644 --- a/bot/Cargo.toml +++ b/bot/Cargo.toml @@ -9,6 +9,7 @@ publish = false anyhow.workspace = true chrono-tz.workspace = true chrono.workspace = true +culpa.workspace = true dotenv.workspace = true entity.workspace = true fern.workspace = true @@ -21,6 +22,7 @@ migration.workspace = true paste.workspace = true regex.workspace = true riker.workspace = true +sea-orm.workspace = true serde.workspace = true teloxide.workspace = true tera.workspace = true diff --git a/bot/src/commands/mod.rs b/bot/src/commands/mod.rs index 9d3e3bf8..a575c247 100644 --- a/bot/src/commands/mod.rs +++ b/bot/src/commands/mod.rs @@ -1,29 +1,26 @@ use { - crate::{ - actors::bot_actor::{ActorUpdateMessage, Format, Notify, SendMessageReply}, - models::Guardian, - schema::guardians::dsl::*, - DbConnection, - }, - diesel::prelude::*, + crate::actors::bot_actor::{ActorUpdateMessage, Format, Notify, SendMessageReply}, + entity::guardians, riker::actors::{ActorRef, Tell}, + sea_orm::{ColumnTrait, EntityTrait, QueryFilter}, }; #[macro_export] macro_rules! command_actor { ($name:ident, [ $($msgs:ident),* ]) => { - use $crate::{bot_actor::BotActorMsg, NamedActor, DbConnPool, BotConnection}; + use $crate::{bot_actor::BotActorMsg, NamedActor}; + use paste::paste; use riker::actors::{ actor, Actor, ActorFactoryArgs, ActorRef, BasicActorRef, Context, Sender, Receive, }; - use paste::paste; + use sea_orm::DatabaseConnection; #[derive(Clone)] #[actor($($msgs)*)] pub struct $name { bot_ref: ActorRef<$crate::actors::bot_actor::BotActor>, bot_name: String, - connection_pool: DbConnPool, + connection_pool: DatabaseConnection, } impl NamedActor for $name { @@ -31,8 +28,20 @@ macro_rules! command_actor { } impl $name { - pub fn connection(&self) -> BotConnection { - self.connection_pool.get().unwrap() + pub fn new( + bot_ref: ActorRef<$crate::actors::bot_actor::BotActor>, + bot_name: String, + connection_pool: DatabaseConnection, + ) -> Self { + Self { + bot_ref, + bot_name, + connection_pool, + } + } + + pub fn connection(&self) -> &DatabaseConnection { + &self.connection_pool } pub fn new( @@ -146,13 +155,14 @@ pub fn decapitalize(s: &str) -> String { pub async fn validate_username( bot: &ActorRef, message: &ActorUpdateMessage, - connection: &DbConnection, -) -> Option { - let username = match message.update.from().as_ref().unwrap().username { + connection: &DatabaseConnection, +) -> Option { + let username = match message.update.from.as_ref().unwrap().username { None => { - bot.tell( + let _ = bot.tell( SendMessageReply( - "You have no telegram username, register your telegram account first.".into(), + "❌ You have no telegram username, register your telegram account first." + .into(), message.clone(), Format::Plain, Notify::Off, @@ -164,17 +174,17 @@ pub async fn validate_username( Some(ref name) => name.clone(), }; - let db_user = guardians - .filter(telegram_name.eq(&username)) // @todo Fix with tg-id - .first::(connection) - .optional(); + let db_user = guardians::Entity::find() + .filter(guardians::Column::TelegramName.eq(&username)) // @todo Fix with tg-id + .one(connection) + .await; match db_user { Ok(Some(user)) => Some(user), Ok(None) => { - bot.tell( + let _ = bot.tell( SendMessageReply( - "You need to link your PSN account first: use /psn command".into(), + "❌ You need to link your PSN account first: use /psn command".into(), message.clone(), Format::Plain, Notify::Off, @@ -184,9 +194,9 @@ pub async fn validate_username( None } Err(_) => { - bot.tell( + let _ = bot.tell( SendMessageReply( - "Error querying guardian info.".into(), + "❌ Error querying guardian info.".into(), message.clone(), Format::Plain, Notify::Off, @@ -202,31 +212,33 @@ pub async fn validate_username( pub async fn admin_check( bot: &ActorRef, message: &ActorUpdateMessage, - connection: &DbConnection, -) -> Option { - validate_username(bot, message, connection).filter(|g| g.is_admin) + connection: &DatabaseConnection, +) -> Option { + validate_username(bot, message, connection) + .await + .filter(|u| u.is_admin) } -pub fn guardian_lookup( +pub async fn guardian_lookup( name: &str, - connection: &DbConnection, -) -> Result, diesel::result::Error> { + connection: &DatabaseConnection, +) -> Result, sea_orm::DbErr> { if let Some(name) = name.strip_prefix('@') { - guardians - .filter(telegram_name.eq(name)) - .first::(connection) - .optional() + guardians::Entity::find() + .filter(guardians::Column::TelegramName.eq(name)) + .one(connection) + .await } else { - guardians - .filter(psn_name.ilike(&name)) - .first::(connection) - .optional() + guardians::Entity::find() + .filter(guardians::Column::PsnName.contains(name)) + .one(connection) + .await } - // @todo: lookup by integer id, positive + // @todo: lookup by integer id, positive (tg user id) } /// Match command in both variations (with bot name and without bot name). -/// @param msg Input message received from Telegram. +/// @param text Input message received from Telegram. /// @param command Command name with leading slash, if it's a root command. FIXME: Is it correct? /// @param bot_name Registered bot name. /// @returns A pair of matched command and remainder of the message text. @@ -239,7 +251,7 @@ fn match_command( ) -> (Option, Option) { // Take first token in the text - that must be the command, if any. // Split it by @ to see if we have a bot name attached - // If we do - it must match out bot name completely. + // If we do - it must match our bot name completely. // Strip trailing numeric digits from the left side - this might be part of the command argument, remember it. // The rest of the left side must match EXACTLY, not as a prefix. text.and_then(|input| { diff --git a/bot/src/lib.rs b/bot/src/lib.rs index c4c62d0b..4d9389e6 100644 --- a/bot/src/lib.rs +++ b/bot/src/lib.rs @@ -1,20 +1,20 @@ // #![allow(proc_macro_derive_resolution_fallback)] // see https://github.com/rust-lang/rust/issues/50504 #![warn(unused_imports)] // during development #![feature(type_ascription)] -#![expect(non_local_definitions)] // Old diesel macros use { - diesel::pg::PgConnection, diesel_logger::LoggingConnection, r2d2::Pool, - sea_orm::DatabaseConnection, + culpa::throws, + sea_orm::{DatabaseConnection, DbErr}, }; pub mod actors; pub mod commands; -// TODO: only BotConnection should be public -pub type DbConnection = LoggingConnection; -pub type DbConnPool = Pool>; -pub type BotConnection = r2d2::PooledConnection>; +/// Establish a database connection using the entity crate +#[throws(DbErr)] +pub async fn establish_db_connection() -> DatabaseConnection { + entity::establish_db_connection().await? +} pub trait NamedActor { fn actor_name() -> String; @@ -30,20 +30,6 @@ pub trait BotCommand { fn description() -> &'static str; } -/// Establish a pool of connections with DB. -pub fn establish_db_connection() -> DbConnPool { - dotenv::dotenv().ok(); - - let database_url = std::env::var("DATABASE_URL").expect("DATABASE_URL must be set"); - let manager = diesel::r2d2::ConnectionManager::new(database_url.clone()); - - r2d2::Pool::builder() - .min_idle(Some(1)) - .max_size(15) - .build(manager) - .unwrap_or_else(|_| panic!("Error connecting to {}", database_url)) -} - // https://chaoslibrary.blot.im/rust-cloning-a-trait-object/ // // trait BotCommandClone { From 9afb6b7d616ffa7353b5185b898c202eaceefd7b Mon Sep 17 00:00:00 2001 From: Berkus Decker Date: Tue, 19 Aug 2025 11:54:48 +0300 Subject: [PATCH 07/26] =?UTF-8?q?=E2=AC=86=EF=B8=8F=20Upgrade=20teloxide?= =?UTF-8?q?=20version=20to=200.17?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 573 +++++++++++++++-------------- Cargo.toml | 2 +- bot/src/actors/bot_actor.rs | 235 ++++++------ bot/src/bin/bot.rs | 9 +- bot/src/commands/chatid_command.rs | 2 +- 5 files changed, 418 insertions(+), 403 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3ea019eb..1bbf1a4e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -158,13 +158,13 @@ checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100" [[package]] name = "aquamarine" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21cc1548309245035eb18aa7f0967da6bc65587005170c56e6ef2788a4cf3f4e" +checksum = "0f50776554130342de4836ba542aa85a4ddb361690d7e8df13774d7284c3d5c2" dependencies = [ "include_dir", "itertools 0.10.5", - "proc-macro-error", + "proc-macro-error2", "proc-macro2 1.0.101", "quote 1.0.40", "syn 2.0.106", @@ -230,6 +230,12 @@ dependencies = [ "num-traits 0.2.19", ] +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + [[package]] name = "autocfg" version = "1.5.0" @@ -251,12 +257,6 @@ dependencies = [ "windows-targets 0.52.6", ] -[[package]] -name = "base64" -version = "0.21.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" - [[package]] name = "base64" version = "0.22.1" @@ -380,6 +380,12 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "bytemuck" +version = "1.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3995eaeebcdf32f91f980d360f78732ddc061097ab4e39991ae7a6ace9194677" + [[package]] name = "byteorder" version = "1.5.0" @@ -485,7 +491,7 @@ dependencies = [ "anstream", "anstyle", "clap_lex", - "strsim 0.11.1", + "strsim", ] [[package]] @@ -522,6 +528,15 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "colored" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fde0e0ec90c9dfb3b4b1a0891a7dcd0e2bffde2f7efed5fe7c9bb00e5bfb915e" +dependencies = [ + "windows-sys 0.59.0", +] + [[package]] name = "concurrent-queue" version = "2.5.0" @@ -573,12 +588,6 @@ dependencies = [ "tiny-keccak", ] -[[package]] -name = "convert_case" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" - [[package]] name = "core-foundation" version = "0.9.4" @@ -698,38 +707,14 @@ dependencies = [ "syn 2.0.106", ] -[[package]] -name = "darling" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c" -dependencies = [ - "darling_core 0.13.4", - "darling_macro 0.13.4", -] - [[package]] name = "darling" version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" dependencies = [ - "darling_core 0.20.11", - "darling_macro 0.20.11", -] - -[[package]] -name = "darling_core" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610" -dependencies = [ - "fnv", - "ident_case", - "proc-macro2 1.0.101", - "quote 1.0.40", - "strsim 0.10.0", - "syn 1.0.109", + "darling_core", + "darling_macro", ] [[package]] @@ -742,27 +727,17 @@ dependencies = [ "ident_case", "proc-macro2 1.0.101", "quote 1.0.40", + "strsim", "syn 2.0.106", ] -[[package]] -name = "darling_macro" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835" -dependencies = [ - "darling_core 0.13.4", - "quote 1.0.40", - "syn 1.0.109", -] - [[package]] name = "darling_macro" version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ - "darling_core 0.20.11", + "darling_core", "quote 1.0.40", "syn 2.0.106", ] @@ -801,15 +776,23 @@ dependencies = [ [[package]] name = "derive_more" -version = "0.99.20" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6edb4b64a43d977b8e99788fe3a04d483834fba1215a7e02caa415b626497f7f" +checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" dependencies = [ - "convert_case", "proc-macro2 1.0.101", "quote 1.0.40", - "rustc_version", "syn 2.0.106", + "unicode-xid 0.2.6", ] [[package]] @@ -855,13 +838,20 @@ checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" [[package]] name = "dptree" -version = "0.3.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d81175dab5ec79c30e0576df2ed2c244e1721720c302000bb321b107e82e265c" +checksum = "db96968fcf52fe063a98c75df1d1f2b1fba304e7ae29b72fdc81c1165b7e2fd0" dependencies = [ + "colored 3.0.0", "futures", ] +[[package]] +name = "dyn-clone" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" + [[package]] name = "either" version = "1.15.0" @@ -871,15 +861,6 @@ dependencies = [ "serde 1.0.219", ] -[[package]] -name = "encoding_rs" -version = "0.8.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" -dependencies = [ - "cfg-if 1.0.3", -] - [[package]] name = "entity" version = "0.4.0" @@ -954,7 +935,7 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4316185f709b23713e41e3195f90edef7fb00c3ed4adc79769cf09cc762a3b29" dependencies = [ - "colored", + "colored 2.2.0", "log", ] @@ -1202,25 +1183,6 @@ dependencies = [ "walkdir", ] -[[package]] -name = "h2" -version = "0.3.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0beca50380b1fc32983fc1cb4587bfa4bb9e78fc259aad4a0032d2080309222d" -dependencies = [ - "bytes", - "fnv", - "futures-core", - "futures-sink", - "futures-util", - "http", - "indexmap", - "slab", - "tokio", - "tokio-util", - "tracing", -] - [[package]] name = "hashbrown" version = "0.12.3" @@ -1303,9 +1265,9 @@ dependencies = [ [[package]] name = "http" -version = "0.2.12" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" dependencies = [ "bytes", "fnv", @@ -1314,12 +1276,24 @@ dependencies = [ [[package]] name = "http-body" -version = "0.4.6" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" dependencies = [ "bytes", + "futures-core", "http", + "http-body", "pin-project-lite", ] @@ -1329,12 +1303,6 @@ version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" -[[package]] -name = "httpdate" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" - [[package]] name = "humansize" version = "2.1.3" @@ -1346,39 +1314,63 @@ dependencies = [ [[package]] name = "hyper" -version = "0.14.32" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7" +checksum = "eb3aa54a13a0dfe7fbe3a59e0c76093041720fdc77b110cc0fc260fafb4dc51e" dependencies = [ + "atomic-waker", "bytes", "futures-channel", "futures-core", - "futures-util", - "h2", "http", "http-body", "httparse", - "httpdate", "itoa", "pin-project-lite", - "socket2 0.5.10", + "pin-utils", + "smallvec", "tokio", - "tower-service", - "tracing", "want", ] [[package]] name = "hyper-tls" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" dependencies = [ "bytes", + "http-body-util", "hyper", + "hyper-util", "native-tls", "tokio", "tokio-native-tls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d9b05277c7e8da2c93a568989bb6207bef0112e8d17df7a6eda4a3cf143bc5e" +dependencies = [ + "base64", + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "http", + "http-body", + "hyper", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", ] [[package]] @@ -1554,6 +1546,17 @@ dependencies = [ "quote 1.0.40", ] +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde 1.0.219", +] + [[package]] name = "indexmap" version = "2.10.0" @@ -1562,6 +1565,7 @@ checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" dependencies = [ "equivalent", "hashbrown 0.15.5", + "serde 1.0.219", ] [[package]] @@ -1592,6 +1596,16 @@ version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" +[[package]] +name = "iri-string" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbc5ebe9c3a1a7a5127f920a418f7585e9e758e911d0466ed004f393b0e380b2" +dependencies = [ + "memchr", + "serde 1.0.219", +] + [[package]] name = "is_terminal_polyfill" version = "1.70.1" @@ -2080,7 +2094,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1db05f56d34358a8b1066f67cbb203ee3e7ed2ba674a6263a1d5ec6db2204323" dependencies = [ "memchr", - "thiserror 2.0.16", + "thiserror", "ucd-trie", ] @@ -2286,30 +2300,6 @@ dependencies = [ "toml_edit", ] -[[package]] -name = "proc-macro-error" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" -dependencies = [ - "proc-macro-error-attr", - "proc-macro2 1.0.101", - "quote 1.0.40", - "syn 1.0.109", - "version_check", -] - -[[package]] -name = "proc-macro-error-attr" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" -dependencies = [ - "proc-macro2 1.0.101", - "quote 1.0.40", - "version_check", -] - [[package]] name = "proc-macro-error-attr2" version = "2.0.0" @@ -2338,7 +2328,7 @@ version = "0.4.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759" dependencies = [ - "unicode-xid", + "unicode-xid 0.1.0", ] [[package]] @@ -2388,6 +2378,15 @@ dependencies = [ "hex", ] +[[package]] +name = "psm" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e944464ec8536cd1beb0bbfd96987eb5e3b72f2ecdafdc5c769a37f1fa2ae1f" +dependencies = [ + "cc", +] + [[package]] name = "ptr_meta" version = "0.1.4" @@ -2527,6 +2526,26 @@ dependencies = [ "bitflags 2.9.2", ] +[[package]] +name = "ref-cast" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a0ae411dbe946a674d89546582cea4ba2bb8defac896622d6496f14c23ba5cf" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1165225c21bff1f3bbce98f5a1f889949bc902d3575308cc7b0de30b4f6d27c7" +dependencies = [ + "proc-macro2 1.0.101", + "quote 1.0.40", + "syn 2.0.106", +] + [[package]] name = "regex" version = "1.11.1" @@ -2582,45 +2601,51 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.11.27" +version = "0.12.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" +checksum = "d429f34c8092b2d42c7c93cec323bb4adeb7c67698f70839adec842ec10c7ceb" dependencies = [ - "base64 0.21.7", + "base64", "bytes", - "encoding_rs", "futures-core", "futures-util", - "h2", "http", "http-body", + "http-body-util", "hyper", "hyper-tls", - "ipnet", + "hyper-util", "js-sys", "log", - "mime", "mime_guess", "native-tls", - "once_cell", "percent-encoding", "pin-project-lite", - "rustls-pemfile", + "rustls-pki-types", "serde 1.0.219", "serde_json", "serde_urlencoded", "sync_wrapper", - "system-configuration", "tokio", "tokio-native-tls", "tokio-util", + "tower", + "tower-http", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", "wasm-streams", "web-sys", - "winreg", +] + +[[package]] +name = "rgb" +version = "0.8.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c6a884d2998352bb4daf0183589aec883f16a6da1f4dde84d8e2e9a5409a1ce" +dependencies = [ + "bytemuck", ] [[package]] @@ -2746,15 +2771,6 @@ version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" -[[package]] -name = "rustc_version" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" -dependencies = [ - "semver", -] - [[package]] name = "rustix" version = "0.38.44" @@ -2795,15 +2811,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "rustls-pemfile" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" -dependencies = [ - "base64 0.21.7", -] - [[package]] name = "rustls-pki-types" version = "1.12.0" @@ -2854,6 +2861,30 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "schemars" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde 1.0.219", + "serde_json", +] + +[[package]] +name = "schemars" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82d20c4491bc164fa2f6c5d44565947a52ad80b9505d8e36f8d54c27c739fcd0" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde 1.0.219", + "serde_json", +] + [[package]] name = "scopeguard" version = "1.2.0" @@ -2895,7 +2926,7 @@ dependencies = [ "serde_json", "sqlx", "strum", - "thiserror 2.0.16", + "thiserror", "time", "tracing", "url", @@ -2989,12 +3020,12 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bae0cbad6ab996955664982739354128c58d16e126114fe88c2a493642502aab" dependencies = [ - "darling 0.20.11", + "darling", "heck 0.4.1", "proc-macro2 1.0.101", "quote 1.0.40", "syn 2.0.106", - "thiserror 2.0.16", + "thiserror", ] [[package]] @@ -3051,12 +3082,6 @@ dependencies = [ "libc", ] -[[package]] -name = "semver" -version = "1.0.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" - [[package]] name = "serde" version = "0.8.23" @@ -3141,24 +3166,34 @@ dependencies = [ [[package]] name = "serde_with" -version = "1.14.0" +version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "678b5a069e50bf00ecd22d0cd8ddf7c236f68581b03db652061ed5eb13a312ff" +checksum = "f2c45cd61fefa9db6f254525d46e392b852e0e61d9a1fd36e5bd183450a556d5" dependencies = [ + "base64", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.10.0", + "schemars 0.9.0", + "schemars 1.0.4", "serde 1.0.219", + "serde_derive", + "serde_json", "serde_with_macros", + "time", ] [[package]] name = "serde_with_macros" -version = "1.5.2" +version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e182d6ec6f05393cc0e5ed1bf81ad6db3a8feedf8ee515ecdd369809bcce8082" +checksum = "de90945e6565ce0d9a25098082ed4ee4002e047cb59892c318d66821e14bb30f" dependencies = [ - "darling 0.13.4", + "darling", "proc-macro2 1.0.101", "quote 1.0.40", - "syn 1.0.109", + "syn 2.0.106", ] [[package]] @@ -3282,16 +3317,6 @@ dependencies = [ "serde 1.0.219", ] -[[package]] -name = "socket2" -version = "0.5.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" -dependencies = [ - "libc", - "windows-sys 0.52.0", -] - [[package]] name = "socket2" version = "0.6.0" @@ -3340,7 +3365,7 @@ version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee6798b1838b6a0f69c007c133b8df5866302197e404e8b6ee8ed3e3a5e68dc6" dependencies = [ - "base64 0.22.1", + "base64", "bigdecimal", "bytes", "chrono", @@ -3354,7 +3379,7 @@ dependencies = [ "futures-util", "hashbrown 0.15.5", "hashlink", - "indexmap", + "indexmap 2.10.0", "log", "memchr", "once_cell", @@ -3365,7 +3390,7 @@ dependencies = [ "serde_json", "sha2", "smallvec", - "thiserror 2.0.16", + "thiserror", "time", "tokio", "tokio-stream", @@ -3420,7 +3445,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526" dependencies = [ "atoi", - "base64 0.22.1", + "base64", "bigdecimal", "bitflags 2.9.2", "byteorder", @@ -3453,7 +3478,7 @@ dependencies = [ "smallvec", "sqlx-core", "stringprep", - "thiserror 2.0.16", + "thiserror", "time", "tracing", "uuid 1.18.0", @@ -3467,7 +3492,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46" dependencies = [ "atoi", - "base64 0.22.1", + "base64", "bigdecimal", "bitflags 2.9.2", "byteorder", @@ -3496,7 +3521,7 @@ dependencies = [ "smallvec", "sqlx-core", "stringprep", - "thiserror 2.0.16", + "thiserror", "time", "tracing", "uuid 1.18.0", @@ -3523,7 +3548,7 @@ dependencies = [ "serde 1.0.219", "serde_urlencoded", "sqlx-core", - "thiserror 2.0.16", + "thiserror", "time", "tracing", "url", @@ -3536,6 +3561,19 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +[[package]] +name = "stacker" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cddb07e32ddb770749da91081d8d0ac3a16f1a569a18b20348cd371f5dead06b" +dependencies = [ + "cc", + "cfg-if 1.0.3", + "libc", + "psm", + "windows-sys 0.59.0", +] + [[package]] name = "static_assertions" version = "1.1.0" @@ -3553,12 +3591,6 @@ dependencies = [ "unicode-properties", ] -[[package]] -name = "strsim" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" - [[package]] name = "strsim" version = "0.11.1" @@ -3585,7 +3617,7 @@ checksum = "9ca4b3b69a77cbe1ffc9e198781b7acb0c7365a883670e8f1c1bc66fba79a5c5" dependencies = [ "proc-macro2 0.4.30", "quote 0.6.13", - "unicode-xid", + "unicode-xid 0.1.0", ] [[package]] @@ -3612,9 +3644,12 @@ dependencies = [ [[package]] name = "sync_wrapper" -version = "0.1.2" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] [[package]] name = "synstructure" @@ -3627,27 +3662,6 @@ dependencies = [ "syn 2.0.106", ] -[[package]] -name = "system-configuration" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" -dependencies = [ - "bitflags 1.3.2", - "core-foundation", - "system-configuration-sys", -] - -[[package]] -name = "system-configuration-sys" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" -dependencies = [ - "core-foundation-sys", - "libc", -] - [[package]] name = "take_mut" version = "0.2.2" @@ -3668,9 +3682,9 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "teloxide" -version = "0.13.0" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f79dd283eb21b90451c03fa7c7f83b9985130efb876b33bad89a2c208ccbc16" +checksum = "84992abeed3ae42e8401b25d266d12bcba1def0abe59d22f6b9781167545f71e" dependencies = [ "aquamarine", "bytes", @@ -3685,7 +3699,7 @@ dependencies = [ "serde_json", "teloxide-core", "teloxide-macros", - "thiserror 1.0.69", + "thiserror", "tokio", "tokio-stream", "tokio-util", @@ -3694,11 +3708,11 @@ dependencies = [ [[package]] name = "teloxide-core" -version = "0.10.1" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1642a7ef10e7af63b8298c8d13c0f986d4fc646d42649ff060359607f62f69" +checksum = "7f7a34ca8e971fa892e633858c07547fe138ef4a02e4a4eaa1d35e517d6e0bc4" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.9.2", "bytes", "chrono", "derive_more", @@ -3710,12 +3724,14 @@ dependencies = [ "pin-project", "rc-box", "reqwest", + "rgb", "serde 1.0.219", "serde_json", "serde_with", + "stacker", "take_mut", "takecell", - "thiserror 1.0.69", + "thiserror", "tokio", "tokio-util", "url", @@ -3724,14 +3740,14 @@ dependencies = [ [[package]] name = "teloxide-macros" -version = "0.8.0" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e2d33d809c3e7161a9ab18bedddf98821245014f0a78fa4d2c9430b2ec018c1" +checksum = "300fadcaf0c182f19b5ca10bf23a45dc9a48925f00c704405fd90ee2c03942f9" dependencies = [ - "heck 0.4.1", + "heck 0.5.0", "proc-macro2 1.0.101", "quote 1.0.40", - "syn 1.0.109", + "syn 2.0.106", ] [[package]] @@ -3769,33 +3785,13 @@ dependencies = [ "unic-segment", ] -[[package]] -name = "thiserror" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" -dependencies = [ - "thiserror-impl 1.0.69", -] - [[package]] name = "thiserror" version = "2.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0" dependencies = [ - "thiserror-impl 2.0.16", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" -dependencies = [ - "proc-macro2 1.0.101", - "quote 1.0.40", - "syn 2.0.106", + "thiserror-impl", ] [[package]] @@ -3897,7 +3893,7 @@ dependencies = [ "pin-project-lite", "signal-hook-registry", "slab", - "socket2 0.6.0", + "socket2", "tokio-macros", "windows-sys 0.59.0", ] @@ -3968,11 +3964,50 @@ version = "0.22.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ - "indexmap", + "indexmap 2.10.0", "toml_datetime", "winnow", ] +[[package]] +name = "tower" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-http" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" +dependencies = [ + "bitflags 2.9.2", + "bytes", + "futures-util", + "http", + "http-body", + "iri-string", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + [[package]] name = "tower-service" version = "0.3.3" @@ -4146,6 +4181,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + [[package]] name = "untrusted" version = "0.9.0" @@ -4676,16 +4717,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "winreg" -version = "0.50.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" -dependencies = [ - "cfg-if 1.0.3", - "windows-sys 0.48.0", -] - [[package]] name = "wit-bindgen-rt" version = "0.39.0" diff --git a/Cargo.toml b/Cargo.toml index 1f100c8a..6fa4b2fd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,7 +31,7 @@ sea-orm-migration = { version = "1.1", features = [ "with-chrono", ] } serde = { version = "1.0", features = ["derive"] } -teloxide = { version = "0.13", features = ["macros"] } +teloxide = { version = "0.17", features = ["macros"] } tera = "1" tokio = { version = "1", features = ["macros", "rt-multi-thread"] } two_timer = "2.2" # doesn't parse "in 3 hours" diff --git a/bot/src/actors/bot_actor.rs b/bot/src/actors/bot_actor.rs index a082b9b4..eb64d89b 100644 --- a/bot/src/actors/bot_actor.rs +++ b/bot/src/actors/bot_actor.rs @@ -1,31 +1,31 @@ use { crate::{ - actors::reminder_actor::{ + commands::*, + establish_db_connection, + services::reminder_actor::{ ReminderActor, ScheduleNextDay, ScheduleNextMinute, ScheduleNextWeek, }, - commands::*, - BotCommand, BotConnection, + BotCommand, DbConnPool, NamedActor, }, - kameo::{ - actor::ActorRef, - error::Infallible, - message::{Context, Message}, - Actor, + riker::actors::{ + actor, Actor, ActorFactoryArgs, ActorRefFactory, BasicActorRef, ChannelRef, Context, + Receive, Sender, Subscribe, Tell, }, std::fmt::Formatter, teloxide::{ prelude::*, types::{ChatId, ParseMode}, }, - tokio::sync::broadcast, }; +#[derive(Clone)] +#[actor(SendMessage, SendMessageReply, ListCommands)] pub struct BotActor { pub bot: Bot, bot_name: String, lfg_chat_id: i64, - update_sender: broadcast::Sender, - connection_pool: BotConnection, + update_channel: ChannelRef, + connection_pool: DbConnPool, commands_list: Vec<(String, String)>, } @@ -37,34 +37,33 @@ impl std::fmt::Debug for BotActor { } } -#[derive(Debug, Clone)] pub struct ActorUpdateMessage { - pub requester: Bot, - pub update: teloxide::types::Message, +pub requester: Bot, +pub update: Message, } impl ActorUpdateMessage { - pub fn new(requester: Bot, update: teloxide::types::Message) -> Self { - Self { requester, update } +pub fn new(requester: Bot, update: Message) -> Self { + Self { requester, update } } } impl BotActor { // Public API - pub async fn new( +pub async fn new( name: &str, bot: Bot, - update_sender: broadcast::Sender, + chan: ChannelRef, lfg_chat_id: i64, ) -> Self { - let connection_pool = crate::establish_db_connection().await.unwrap(); + let connection_pool = crate::establish_db_connection().await.unwrap(); BotActor { bot, bot_name: name.to_string(), lfg_chat_id, - update_sender, - connection_pool, + update_channel: chan, + connection_pool, commands_list: vec![], } } @@ -92,82 +91,66 @@ impl BotActor { // } } -use crate::commands::match_command; - impl Actor for BotActor { - type Args = Self; - type Error = Infallible; + type Msg = BotActorMsg; - async fn on_start(args: Self::Args, actor_ref: ActorRef) -> Result { + /// Register all bot commands and subscribe them to the system notification channel. + fn pre_start(&mut self, ctx: &Context) { macro_rules! new_command { - ($T:ident, $args:expr) => { - let cmd = $T::spawn($T::new( - actor_ref.clone(), - $args.bot_name.clone(), - $args.connection_pool.clone(), - )); - $args - .commands_list - .push(($T::prefix().into(), $T::description().into())); - - // Subscribe to updates - let mut update_receiver = $args.update_sender.subscribe(); - let cmd_clone = cmd.clone(); - let bot_name = $args.bot_name.clone(); - tokio::spawn(async move { - while let Ok(msg) = update_receiver.recv().await { - if let (Some(_), _) = - match_command(msg.update.text(), $T::prefix(), &bot_name) - { - let _ = cmd_clone.tell(msg).await; - } - } - }); - }; + ($T:ident) => { + let cmd = ctx + .actor_of_args::<$T, _>(&$T::actor_name(), (ctx.myself().clone(), self.bot_name.clone(), self.connection_pool.clone())) + .unwrap(); // FIXME: panics in pre_start do not cause actor restart, so this is faulty! + self.commands_list.push(($T::prefix().into(), $T::description().into())); + self.update_channel.tell( + Subscribe { + actor: Box::new(cmd), + topic: "raw-commands".into(), + }, + None, + ); + } } - let mut bot_actor = args; - - new_command!(ActivitiesCommand, bot_actor); - new_command!(CancelCommand, bot_actor); - new_command!(ChatidCommand, bot_actor); - new_command!(D1weekCommand, bot_actor); - new_command!(D2weekCommand, bot_actor); - new_command!(EditCommand, bot_actor); - new_command!(EditGuardianCommand, bot_actor); - new_command!(HelpCommand, bot_actor); - new_command!(JoinCommand, bot_actor); - new_command!(LfgCommand, bot_actor); - new_command!(ListCommand, bot_actor); - new_command!(ManageCommand, bot_actor); - new_command!(PsnCommand, bot_actor); - new_command!(UptimeCommand, bot_actor); - new_command!(WhoisCommand, bot_actor); + new_command!(ActivitiesCommand); + new_command!(CancelCommand); + new_command!(ChatidCommand); + new_command!(D1weekCommand); + new_command!(D2weekCommand); + new_command!(EditCommand); + new_command!(EditGuardianCommand); + new_command!(HelpCommand); + new_command!(JoinCommand); + new_command!(LfgCommand); + new_command!(ListCommand); + new_command!(ManageCommand); + new_command!(PsnCommand); + new_command!(UptimeCommand); + new_command!(WhoisCommand); // Create reminder tasks actor - let reminders = ReminderActor::spawn(ReminderActor::new( - actor_ref.clone(), - bot_actor.lfg_chat_id, - bot_actor.connection_pool.clone(), - )); - + let reminders = ctx + .actor_of_args::( + "reminders", + (ctx.myself(), self.lfg_chat_id, self.connection_pool.clone()), + ) + .unwrap(); // Schedule first run, the actor handler will reschedule. - let _ = reminders.tell(ScheduleNextMinute).await; - let _ = reminders.tell(ScheduleNextDay).await; - let _ = reminders.tell(ScheduleNextWeek).await; + reminders.tell(ScheduleNextMinute, None); + reminders.tell(ScheduleNextDay, None); + reminders.tell(ScheduleNextWeek, None); + } - Ok(bot_actor) + fn recv(&mut self, ctx: &Context, msg: Self::Msg, sender: Sender) { + self.receive(ctx, msg, sender); } } -impl BotActor { - pub async fn create( - bot_name: String, - bot: Bot, - update_sender: broadcast::Sender, - lfg_chat: i64, +impl ActorFactoryArgs<(String, Bot, ChannelRef, i64)> for BotActor { + fn create_args( + (bot_name, bot, chan, lfg_chat): (String, Bot, ChannelRef, i64), ) -> Self { - Self::new(&bot_name, bot, update_sender, lfg_chat).await + Self::new(&bot_name, bot, chan, lfg_chat) } } @@ -193,14 +176,10 @@ pub struct SendMessageReply(pub String, pub ActorUpdateMessage, pub Format, pub #[derive(Clone, Debug)] pub struct ListCommands(pub ActorUpdateMessage); -impl Message for BotActor { - type Reply = (); +impl Receive for BotActor { + type Msg = BotActorMsg; - async fn handle( - &mut self, - msg: SendMessage, - _ctx: &mut Context, - ) -> Self::Reply { + fn receive(&mut self, _ctx: &Context, msg: SendMessage, _sender: Sender) { log::debug!("SendMessage: {}", &msg.0); let resp = self .bot @@ -209,13 +188,13 @@ impl Message for BotActor { Notify::On => false, Notify::Off => true, }) - .link_preview_options(teloxide::types::LinkPreviewOptions { - is_disabled: true, - url: None, - prefer_small_media: false, - prefer_large_media: false, - show_above_text: false, - }); + .link_preview_options(teloxide::types::LinkPreviewOptions { + is_disabled: true, + url: None, + prefer_small_media: false, + prefer_large_media: false, + show_above_text: false, + }); let resp = match msg.2 { Format::Html => resp.parse_mode(ParseMode::Html), @@ -223,36 +202,37 @@ impl Message for BotActor { Format::Plain => resp, }; - let _ = resp.send().await; + let rt = tokio::runtime::Builder::new_current_thread() + .enable_all() + .build() + .unwrap(); + + rt.block_on(resp.send()).unwrap(); } } -impl Message for BotActor { - type Reply = (); +impl Receive for BotActor { + type Msg = BotActorMsg; - async fn handle( - &mut self, - msg: SendMessageReply, - _ctx: &mut Context, - ) -> Self::Reply { + fn receive(&mut self, _ctx: &Context, msg: SendMessageReply, _sender: Sender) { log::debug!("SendMessageReply: {}", &msg.0); let message = msg.1; let fut = self .bot - .send_message(message.update.chat.id, msg.0) - .reply_parameters(teloxide::types::ReplyParameters::new(message.update.id)) + .send_message(message.update.chat.id, msg.0) + .reply_parameters(teloxide::types::ReplyParameters::new(message.update.id)) .disable_notification(match msg.3 { Notify::On => false, Notify::Off => true, }) - .link_preview_options(teloxide::types::LinkPreviewOptions { - is_disabled: true, - url: None, - prefer_small_media: false, - prefer_large_media: false, - show_above_text: false, - }); + .link_preview_options(teloxide::types::LinkPreviewOptions { + is_disabled: true, + url: None, + prefer_small_media: false, + prefer_large_media: false, + show_above_text: false, + }); let fut = match msg.2 { Format::Html => fut.parse_mode(ParseMode::Html), @@ -260,18 +240,19 @@ impl Message for BotActor { Format::Plain => fut, }; - let _ = fut.send().await; + let rt = tokio::runtime::Builder::new_current_thread() + .enable_all() + .build() + .unwrap(); + + rt.block_on(fut.send()).unwrap(); } } -impl Message for BotActor { - type Reply = (); +impl Receive for BotActor { + type Msg = BotActorMsg; - async fn handle( - &mut self, - msg: ListCommands, - ctx: &mut Context, - ) -> Self::Reply { + fn receive(&mut self, ctx: &Context, msg: ListCommands, _sender: Sender) { log::debug!("ListCommands"); let message = msg.0; @@ -282,9 +263,9 @@ impl Message for BotActor { |acc, pair| format!("{}{} — {}\n\n", acc, pair.0, pair.1), ); - let _ = ctx - .actor_ref() - .tell(SendMessageReply(reply, message, Format::Html, Notify::Off)) - .try_send(); // @todo use unbounded mailbox for bot_actor? prolly not + ctx.myself.tell( + SendMessageReply(reply, message, Format::Html, Notify::Off), + None, + ); } } diff --git a/bot/src/bin/bot.rs b/bot/src/bin/bot.rs index 778ad0c8..94834840 100644 --- a/bot/src/bin/bot.rs +++ b/bot/src/bin/bot.rs @@ -104,13 +104,16 @@ async fn main() -> anyhow::Result<()> { .actor_of_args::("bot", (bot_name, tgbot.clone(), chan.clone(), lfg_chat)) .expect("Couldn't start the bot"); - teloxide::repl(tgbot.clone(), move |message: UpdateMessage| { + teloxide::repl(tgbot.clone(), move |bot: Bot, message: Message| { let chan = chan.clone(); async move { - log::debug!("Processing message {}", message.update.id); + log::debug!("Processing message {}", message.id); chan.tell( Publish { - msg: message.into(), + msg: ActorUpdateMessage { + requester: bot, + update: message, + }, topic: "raw-commands".into(), }, None, diff --git a/bot/src/commands/chatid_command.rs b/bot/src/commands/chatid_command.rs index 130a12a8..96cd519b 100644 --- a/bot/src/commands/chatid_command.rs +++ b/bot/src/commands/chatid_command.rs @@ -26,7 +26,7 @@ impl Receive for ChatidCommand { if let (Some(_), _) = match_command(msg.update.text(), Self::prefix(), &self.bot_name) { self.bot_ref.tell( SendMessageReply( - format!("ChatId: {}", msg.update.chat_id()), + format!("ChatId: {}", msg.update.chat.id), msg, Format::Plain, Notify::Off, From 9af4bc3fec5f71ac762d19ddacaceff3e3ba06b8 Mon Sep 17 00:00:00 2001 From: Berkus Decker Date: Tue, 19 Aug 2025 19:16:24 +0300 Subject: [PATCH 08/26] =?UTF-8?q?=E2=9C=A8=20Re-add=20templating=20support?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bot/src/lib.rs | 77 ++++++++++++++++++++++++++++++++++++++ bot/templates/en/README.md | 1 + bot/templates/ru/README.md | 1 + 3 files changed, 79 insertions(+) create mode 100644 bot/templates/en/README.md create mode 100644 bot/templates/ru/README.md diff --git a/bot/src/lib.rs b/bot/src/lib.rs index 4d9389e6..96c19f3a 100644 --- a/bot/src/lib.rs +++ b/bot/src/lib.rs @@ -5,11 +5,88 @@ use { culpa::throws, sea_orm::{DatabaseConnection, DbErr}, + std::{error::Error, fmt::Write}, }; pub mod actors; pub mod commands; +static TEMPLATE_FILES: std::sync::LazyLock> = + std::sync::LazyLock::new(|| include_dir::include_dir!("$CARGO_MANIFEST_DIR/templates")); + +pub(crate) static TEMPLATES: std::sync::LazyLock = std::sync::LazyLock::new(|| { + let mut tera = tera::Tera::default(); + for file in TEMPLATE_FILES.find("**/*.tera").unwrap() { + if let Some(template) = file.as_file() { + tera.add_raw_template( + template.path().with_extension("").to_str().unwrap(), // drop .tera extension + template.contents_utf8().unwrap(), + ) + .unwrap(); + } + } + tera +}); + +pub fn error_chain_to_string(err: &dyn Error) -> String { + let mut result = format!("Error: {}", err); + let mut current = err.source(); + while let Some(source) = current { + write!(&mut result, "\n- caused by: {}", source).unwrap(); + current = source.source(); + } + result +} + +#[macro_export] +macro_rules! render_template { + ($template:expr) => { + { + $crate::TEMPLATES.render($template, &tera::Context::new()) + .map_err(|e| format!("{}", $crate::error_chain_to_string(&e))) + } + }; + ($template:expr, $(($key:expr => $value:expr)),+) => { + { + let mut context = tera::Context::new(); + $( + context.insert($key, $value); + )* + $crate::TEMPLATES.render($template, &context) + .map_err(|e| format!("{}", $crate::error_chain_to_string(&e))) + } + }; +} + +/// Like render_template, but also render the error text if any error happens during template rendering. +#[macro_export] +macro_rules! render_template_or_err { + ($template:expr) => { + { + let res = $crate::TEMPLATES.render($template, &tera::Context::new()); + if let Ok(item) = res { + item + } else { + format!("🐛 {}", $crate::error_chain_to_string(&res.unwrap_err())) + } + } + }; + ($template:expr, $(($key:expr => $value:expr)),+) => { + { + let mut context = tera::Context::new(); + $( + context.insert($key, $value); + )* + let res = $crate::TEMPLATES.render($template, &context); + if let Ok(item) = res { + item + } else { + format!("🐛 {}", $crate::error_chain_to_string(&res.unwrap_err())) + } + } + }; +} + /// Establish a database connection using the entity crate #[throws(DbErr)] pub async fn establish_db_connection() -> DatabaseConnection { diff --git a/bot/templates/en/README.md b/bot/templates/en/README.md new file mode 100644 index 00000000..4f710026 --- /dev/null +++ b/bot/templates/en/README.md @@ -0,0 +1 @@ +Template files for english version. diff --git a/bot/templates/ru/README.md b/bot/templates/ru/README.md new file mode 100644 index 00000000..d8f20b1d --- /dev/null +++ b/bot/templates/ru/README.md @@ -0,0 +1 @@ +Шаблонные файлы для русской версии. From aa5bbef8444946f8a32316cc51be8e551689c857 Mon Sep 17 00:00:00 2001 From: Berkus Decker Date: Tue, 19 Aug 2025 12:32:06 +0300 Subject: [PATCH 09/26] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Port=20from=20riker?= =?UTF-8?q?=20to=20kameo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 799 +++++++++++------------------------- Cargo.toml | 2 +- TODO.md | 6 + bot/Cargo.toml | 2 +- bot/src/actors/bot_actor.rs | 236 ++++++----- bot/src/bin/bot.rs | 36 +- bot/src/commands/mod.rs | 83 ++-- bot/src/lib.rs | 4 - 8 files changed, 409 insertions(+), 759 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1bbf1a4e..52d27b17 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -30,29 +30,20 @@ dependencies = [ "fern", "include_dir", "itertools 0.14.0", + "kameo", "log", "migration", "paste", "procfs", "regex", - "riker", "sea-orm", - "serde 1.0.219", + "serde", "teloxide", "tera", "tokio", "two_timer", ] -[[package]] -name = "ahash" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8fd72866655d1904d6b0997d0b07ba561047d070fbe29de039031c641b61217" -dependencies = [ - "const-random", -] - [[package]] name = "ahash" version = "0.7.8" @@ -165,23 +156,11 @@ dependencies = [ "include_dir", "itertools 0.10.5", "proc-macro-error2", - "proc-macro2 1.0.101", - "quote 1.0.40", + "proc-macro2", + "quote", "syn 2.0.106", ] -[[package]] -name = "arc-swap" -version = "1.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" - -[[package]] -name = "arrayvec" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" - [[package]] name = "arrayvec" version = "0.7.6" @@ -205,8 +184,8 @@ version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ - "proc-macro2 1.0.101", - "quote 1.0.40", + "proc-macro2", + "quote", "syn 2.0.106", ] @@ -216,8 +195,8 @@ version = "0.1.89" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ - "proc-macro2 1.0.101", - "quote 1.0.40", + "proc-macro2", + "quote", "syn 2.0.106", ] @@ -227,7 +206,7 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528" dependencies = [ - "num-traits 0.2.19", + "num-traits", ] [[package]] @@ -249,7 +228,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" dependencies = [ "addr2line", - "cfg-if 1.0.3", + "cfg-if", "libc", "miniz_oxide", "object", @@ -279,23 +258,17 @@ dependencies = [ "libm", "num-bigint", "num-integer", - "num-traits 0.2.19", - "serde 1.0.219", + "num-traits", + "serde", ] -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - [[package]] name = "bitflags" version = "2.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a65b545ab31d687cff52899d4890855fec459eb6afe0da6417b8a18da87aa29" dependencies = [ - "serde 1.0.219", + "serde", ] [[package]] @@ -337,8 +310,8 @@ checksum = "fdd1d3c0c2f5833f22386f252fe8ed005c7f59fdcddeef025c01b4c3b9fd9ac3" dependencies = [ "once_cell", "proc-macro-crate", - "proc-macro2 1.0.101", - "quote 1.0.40", + "proc-macro2", + "quote", "syn 2.0.106", ] @@ -349,7 +322,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "234113d19d0d7d613b40e86fb654acf958910802bcceab913a4f9e7cda03b1a4" dependencies = [ "memchr", - "serde 1.0.219", + "serde", ] [[package]] @@ -375,8 +348,8 @@ version = "0.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3db406d29fbcd95542e92559bed4d8ad92636d1ca8b3b72ede10b4bcc010e659" dependencies = [ - "proc-macro2 1.0.101", - "quote 1.0.40", + "proc-macro2", + "quote", "syn 1.0.109", ] @@ -407,12 +380,6 @@ dependencies = [ "shlex", ] -[[package]] -name = "cfg-if" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" - [[package]] name = "cfg-if" version = "1.0.3" @@ -434,8 +401,8 @@ dependencies = [ "android-tzdata", "iana-time-zone", "js-sys", - "num-traits 0.2.19", - "serde 1.0.219", + "num-traits", + "serde", "wasm-bindgen", "windows-link", ] @@ -501,8 +468,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14cb31bb0a7d536caef2639baa7fad459e15c3144efefa6dbd1c84562c4739f6" dependencies = [ "heck 0.5.0", - "proc-macro2 1.0.101", - "quote 1.0.40", + "proc-macro2", + "quote", "syn 2.0.106", ] @@ -546,48 +513,12 @@ dependencies = [ "crossbeam-utils", ] -[[package]] -name = "config" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19b076e143e1d9538dde65da30f8481c2a6c44040edb8e02b9bf1351edb92ce3" -dependencies = [ - "lazy_static", - "nom", - "rust-ini", - "serde 1.0.219", - "serde-hjson", - "serde_json", - "toml", - "yaml-rust", -] - [[package]] name = "const-oid" version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" -[[package]] -name = "const-random" -version = "0.1.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359" -dependencies = [ - "const-random-macro", -] - -[[package]] -name = "const-random-macro" -version = "0.1.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" -dependencies = [ - "getrandom 0.2.16", - "once_cell", - "tiny-keccak", -] - [[package]] name = "core-foundation" version = "0.9.4" @@ -634,7 +565,7 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" dependencies = [ - "cfg-if 1.0.3", + "cfg-if", ] [[package]] @@ -671,12 +602,6 @@ version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" -[[package]] -name = "crunchy" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" - [[package]] name = "crypto-common" version = "0.1.6" @@ -702,8 +627,8 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1234e1717066d3c71dcf89b75e7b586299e41204d361db56ec51e6ded5014279" dependencies = [ - "proc-macro2 1.0.101", - "quote 1.0.40", + "proc-macro2", + "quote", "syn 2.0.106", ] @@ -725,8 +650,8 @@ checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" dependencies = [ "fnv", "ident_case", - "proc-macro2 1.0.101", - "quote 1.0.40", + "proc-macro2", + "quote", "strsim", "syn 2.0.106", ] @@ -738,21 +663,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core", - "quote 1.0.40", + "quote", "syn 2.0.106", ] -[[package]] -name = "dashmap" -version = "3.11.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f260e2fc850179ef410018660006951c1b55b79e8087e87111a2c388994b9b5" -dependencies = [ - "ahash 0.3.8", - "cfg-if 0.1.10", - "num_cpus", -] - [[package]] name = "der" version = "0.7.10" @@ -771,7 +685,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" dependencies = [ "powerfmt", - "serde 1.0.219", + "serde", ] [[package]] @@ -789,10 +703,10 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" dependencies = [ - "proc-macro2 1.0.101", - "quote 1.0.40", + "proc-macro2", + "quote", "syn 2.0.106", - "unicode-xid 0.2.6", + "unicode-xid", ] [[package]] @@ -819,8 +733,8 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ - "proc-macro2 1.0.101", - "quote 1.0.40", + "proc-macro2", + "quote", "syn 2.0.106", ] @@ -836,6 +750,12 @@ version = "0.15.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" +[[package]] +name = "downcast-rs" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea8a8b81cacc08888170eef4d13b775126db426d0b348bee9d18c2c1eaf123cf" + [[package]] name = "dptree" version = "0.5.1" @@ -858,7 +778,7 @@ version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" dependencies = [ - "serde 1.0.219", + "serde", ] [[package]] @@ -871,7 +791,7 @@ dependencies = [ "dotenv", "regex", "sea-orm", - "serde 1.0.219", + "serde", "tokio", ] @@ -907,7 +827,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" dependencies = [ - "cfg-if 1.0.3", + "cfg-if", "home", "windows-sys 0.48.0", ] @@ -1042,7 +962,6 @@ dependencies = [ "futures-core", "futures-task", "futures-util", - "num_cpus", ] [[package]] @@ -1068,8 +987,8 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ - "proc-macro2 1.0.101", - "quote 1.0.40", + "proc-macro2", + "quote", "syn 2.0.106", ] @@ -1113,24 +1032,13 @@ dependencies = [ "version_check", ] -[[package]] -name = "getrandom" -version = "0.1.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" -dependencies = [ - "cfg-if 1.0.3", - "libc", - "wasi 0.9.0+wasi-snapshot-preview1", -] - [[package]] name = "getrandom" version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ - "cfg-if 1.0.3", + "cfg-if", "libc", "wasi 0.11.1+wasi-snapshot-preview1", ] @@ -1141,7 +1049,7 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" dependencies = [ - "cfg-if 1.0.3", + "cfg-if", "libc", "r-efi", "wasi 0.14.2+wasi-0.2.4", @@ -1178,7 +1086,7 @@ version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bf760ebf69878d9fd8f110c89703d90ce35095324d1f1edcb595c63945ee757" dependencies = [ - "bitflags 2.9.2", + "bitflags", "ignore", "walkdir", ] @@ -1189,7 +1097,7 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" dependencies = [ - "ahash 0.7.8", + "ahash", ] [[package]] @@ -1224,12 +1132,6 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" -[[package]] -name = "hermit-abi" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" - [[package]] name = "hex" version = "0.4.3" @@ -1542,8 +1444,8 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7cab85a7ed0bd5f0e76d93846e0147172bed2e2d3f859bcc33a8d9699cad1a75" dependencies = [ - "proc-macro2 1.0.101", - "quote 1.0.40", + "proc-macro2", + "quote", ] [[package]] @@ -1554,7 +1456,7 @@ checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", "hashbrown 0.12.3", - "serde 1.0.219", + "serde", ] [[package]] @@ -1565,7 +1467,7 @@ checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" dependencies = [ "equivalent", "hashbrown 0.15.5", - "serde 1.0.219", + "serde", ] [[package]] @@ -1574,8 +1476,8 @@ version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c38228f24186d9cc68c729accb4d413be9eaed6ad07ff79e0270d9e56f3de13" dependencies = [ - "proc-macro2 1.0.101", - "quote 1.0.40", + "proc-macro2", + "quote", "syn 2.0.106", ] @@ -1585,8 +1487,8 @@ version = "0.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d93587f37623a1a17d94ef2bc9ada592f5465fe7732084ab7beefabe5c77c0c4" dependencies = [ - "bitflags 2.9.2", - "cfg-if 1.0.3", + "bitflags", + "cfg-if", "libc", ] @@ -1603,7 +1505,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dbc5ebe9c3a1a7a5127f920a418f7585e9e758e911d0466ed004f393b0e380b2" dependencies = [ "memchr", - "serde 1.0.219", + "serde", ] [[package]] @@ -1647,25 +1549,41 @@ dependencies = [ ] [[package]] -name = "lazy_static" -version = "1.5.0" +name = "kameo" +version = "0.17.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +checksum = "41a73be96f616ca2784f597b5b6635582f5a7b3ba73b1dbe7afa5d9667955d39" dependencies = [ - "spin", + "downcast-rs", + "dyn-clone", + "futures", + "kameo_macros", + "once_cell", + "serde", + "tokio", + "tracing", ] [[package]] -name = "lexical-core" -version = "0.7.6" +name = "kameo_macros" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6607c62aa161d23d17a9072cc5da0be67cdfc89d3afb1e8d9c842bebc2525ffe" +checksum = "b3f384b32bf6426ae93a8b37da62c85073b676a31a82a86d608ad86453878de0" dependencies = [ - "arrayvec 0.5.2", - "bitflags 1.3.2", - "cfg-if 1.0.3", - "ryu", - "static_assertions", + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.106", + "uuid", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +dependencies = [ + "spin", ] [[package]] @@ -1686,7 +1604,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "391290121bad3d37fbddad76d8f5d1c1c314cfc646d143d7e07a3086ddff0ce3" dependencies = [ - "bitflags 2.9.2", + "bitflags", "libc", "redox_syscall", ] @@ -1701,22 +1619,6 @@ dependencies = [ "vcpkg", ] -[[package]] -name = "linked-hash-map" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d262045c5b87c0861b3f004610afd0e2c851e2908d08b6c870cbb9d5f494ecd" -dependencies = [ - "serde 0.8.23", - "serde_test", -] - -[[package]] -name = "linked-hash-map" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" - [[package]] name = "linux-raw-sys" version = "0.4.15" @@ -1766,7 +1668,7 @@ version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" dependencies = [ - "cfg-if 1.0.3", + "cfg-if", "digest", ] @@ -1837,17 +1739,6 @@ dependencies = [ "tempfile", ] -[[package]] -name = "nom" -version = "5.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08959a387a676302eebf4ddbcbc611da04285579f76f88ee0506c63b1a61dd4b" -dependencies = [ - "lexical-core", - "memchr", - "version_check", -] - [[package]] name = "num-bigint" version = "0.4.6" @@ -1855,7 +1746,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" dependencies = [ "num-integer", - "num-traits 0.2.19", + "num-traits", ] [[package]] @@ -1869,8 +1760,8 @@ dependencies = [ "libm", "num-integer", "num-iter", - "num-traits 0.2.19", - "rand 0.8.5", + "num-traits", + "rand", "smallvec", "zeroize", ] @@ -1887,7 +1778,7 @@ version = "0.1.46" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" dependencies = [ - "num-traits 0.2.19", + "num-traits", ] [[package]] @@ -1898,16 +1789,7 @@ checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" dependencies = [ "autocfg", "num-integer", - "num-traits 0.2.19", -] - -[[package]] -name = "num-traits" -version = "0.1.43" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92e5113e9fd4cc14ded8e499429f396a20f98c772a47cc8622a736e1ec843c31" -dependencies = [ - "num-traits 0.2.19", + "num-traits", ] [[package]] @@ -1920,16 +1802,6 @@ dependencies = [ "libm", ] -[[package]] -name = "num_cpus" -version = "1.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" -dependencies = [ - "hermit-abi", - "libc", -] - [[package]] name = "object" version = "0.36.7" @@ -1957,8 +1829,8 @@ version = "0.10.73" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8" dependencies = [ - "bitflags 2.9.2", - "cfg-if 1.0.3", + "bitflags", + "cfg-if", "foreign-types", "libc", "once_cell", @@ -1972,8 +1844,8 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ - "proc-macro2 1.0.101", - "quote 1.0.40", + "proc-macro2", + "quote", "syn 2.0.106", ] @@ -2001,7 +1873,7 @@ version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7bb71e1b3fa6ca1c61f383464aaf2bb0e2f8e772a1f01d486832464de363b951" dependencies = [ - "num-traits 0.2.19", + "num-traits", ] [[package]] @@ -2022,9 +1894,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c7028bdd3d43083f6d8d4d5187680d0d3560d54df4cc9d752005268b41e64d0" dependencies = [ "heck 0.4.1", - "proc-macro2 1.0.101", + "proc-macro2", "proc-macro2-diagnostics", - "quote 1.0.40", + "quote", "syn 2.0.106", ] @@ -2050,7 +1922,7 @@ version = "0.9.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" dependencies = [ - "cfg-if 1.0.3", + "cfg-if", "libc", "redox_syscall", "smallvec", @@ -2116,8 +1988,8 @@ checksum = "87e404e638f781eb3202dc82db6760c8ae8a1eeef7fb3fa8264b2ef280504966" dependencies = [ "pest", "pest_meta", - "proc-macro2 1.0.101", - "quote 1.0.40", + "proc-macro2", + "quote", "syn 2.0.106", ] @@ -2137,7 +2009,7 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc58e2d255979a31caa7cabfa7aac654af0354220719ab7a68520ae7a91e8c0b" dependencies = [ - "serde 1.0.219", + "serde", ] [[package]] @@ -2175,7 +2047,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" dependencies = [ "phf_shared 0.11.3", - "rand 0.8.5", + "rand", ] [[package]] @@ -2204,7 +2076,7 @@ checksum = "28a2a37d2d894d50891a12fba6dd7b84292caf2f14fd0086a2af2404be2d8ebb" dependencies = [ "lazy_static", "regex", - "serde 1.0.219", + "serde", "serde_regex", ] @@ -2223,8 +2095,8 @@ version = "1.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" dependencies = [ - "proc-macro2 1.0.101", - "quote 1.0.40", + "proc-macro2", + "quote", "syn 2.0.106", ] @@ -2306,8 +2178,8 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" dependencies = [ - "proc-macro2 1.0.101", - "quote 1.0.40", + "proc-macro2", + "quote", ] [[package]] @@ -2317,20 +2189,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" dependencies = [ "proc-macro-error-attr2", - "proc-macro2 1.0.101", - "quote 1.0.40", + "proc-macro2", + "quote", "syn 2.0.106", ] -[[package]] -name = "proc-macro2" -version = "0.4.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759" -dependencies = [ - "unicode-xid 0.1.0", -] - [[package]] name = "proc-macro2" version = "1.0.101" @@ -2346,8 +2209,8 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" dependencies = [ - "proc-macro2 1.0.101", - "quote 1.0.40", + "proc-macro2", + "quote", "syn 2.0.106", "version_check", "yansi", @@ -2359,7 +2222,7 @@ version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc5b72d8145275d844d4b5f6d4e1eef00c8cd889edb6035c21675d1bb1f45c9f" dependencies = [ - "bitflags 2.9.2", + "bitflags", "chrono", "flate2", "hex", @@ -2373,7 +2236,7 @@ version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "239df02d8349b06fc07398a3a1697b06418223b1c7725085e801e7c0fc6a12ec" dependencies = [ - "bitflags 2.9.2", + "bitflags", "chrono", "hex", ] @@ -2402,27 +2265,18 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac" dependencies = [ - "proc-macro2 1.0.101", - "quote 1.0.40", + "proc-macro2", + "quote", "syn 1.0.109", ] -[[package]] -name = "quote" -version = "0.6.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1" -dependencies = [ - "proc-macro2 0.4.30", -] - [[package]] name = "quote" version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ - "proc-macro2 1.0.101", + "proc-macro2", ] [[package]] @@ -2437,19 +2291,6 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" -[[package]] -name = "rand" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" -dependencies = [ - "getrandom 0.1.16", - "libc", - "rand_chacha 0.2.2", - "rand_core 0.5.1", - "rand_hc", -] - [[package]] name = "rand" version = "0.8.5" @@ -2457,18 +2298,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", - "rand_chacha 0.3.1", - "rand_core 0.6.4", -] - -[[package]] -name = "rand_chacha" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" -dependencies = [ - "ppv-lite86", - "rand_core 0.5.1", + "rand_chacha", + "rand_core", ] [[package]] @@ -2478,16 +2309,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core 0.6.4", -] - -[[package]] -name = "rand_core" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" -dependencies = [ - "getrandom 0.1.16", + "rand_core", ] [[package]] @@ -2499,15 +2321,6 @@ dependencies = [ "getrandom 0.2.16", ] -[[package]] -name = "rand_hc" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" -dependencies = [ - "rand_core 0.5.1", -] - [[package]] name = "rc-box" version = "1.3.0" @@ -2523,7 +2336,7 @@ version = "0.5.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" dependencies = [ - "bitflags 2.9.2", + "bitflags", ] [[package]] @@ -2541,8 +2354,8 @@ version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1165225c21bff1f3bbce98f5a1f889949bc902d3575308cc7b0de30b4f6d27c7" dependencies = [ - "proc-macro2 1.0.101", - "quote 1.0.40", + "proc-macro2", + "quote", "syn 2.0.106", ] @@ -2622,7 +2435,7 @@ dependencies = [ "percent-encoding", "pin-project-lite", "rustls-pki-types", - "serde 1.0.219", + "serde", "serde_json", "serde_urlencoded", "sync_wrapper", @@ -2648,38 +2461,6 @@ dependencies = [ "bytemuck", ] -[[package]] -name = "riker" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abff93ece5a5d3d7f2c54dfba7550657a644c9dc0a871c7ddf8c31381971c41b" -dependencies = [ - "chrono", - "config", - "dashmap", - "futures", - "num_cpus", - "pin-utils", - "rand 0.7.3", - "regex", - "riker-macros", - "slog", - "slog-scope", - "slog-stdlog", - "uuid 0.8.2", -] - -[[package]] -name = "riker-macros" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2a8e8f71c9e7980a596c39c7e3537ea8563054526e15712a610ac97a02dba15" -dependencies = [ - "proc-macro2 0.4.30", - "quote 0.6.13", - "syn 0.15.44", -] - [[package]] name = "ring" version = "0.17.14" @@ -2687,7 +2468,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", - "cfg-if 1.0.3", + "cfg-if", "getrandom 0.2.16", "libc", "untrusted", @@ -2709,7 +2490,7 @@ dependencies = [ "rkyv_derive", "seahash", "tinyvec", - "uuid 1.18.0", + "uuid", ] [[package]] @@ -2718,8 +2499,8 @@ version = "0.7.45" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "503d1d27590a2b0a3a4ca4c94755aa2875657196ecbf401a42eff41d7de532c0" dependencies = [ - "proc-macro2 1.0.101", - "quote 1.0.40", + "proc-macro2", + "quote", "syn 1.0.109", ] @@ -2733,35 +2514,29 @@ dependencies = [ "digest", "num-bigint-dig", "num-integer", - "num-traits 0.2.19", + "num-traits", "pkcs1", "pkcs8", - "rand_core 0.6.4", + "rand_core", "signature", "spki", "subtle", "zeroize", ] -[[package]] -name = "rust-ini" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e52c148ef37f8c375d49d5a73aa70713125b7f19095948a923f80afdeb22ec2" - [[package]] name = "rust_decimal" version = "1.37.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b203a6425500a03e0919c42d3c47caca51e79f1132046626d2c8871c5092035d" dependencies = [ - "arrayvec 0.7.6", + "arrayvec", "borsh", "bytes", - "num-traits 0.2.19", - "rand 0.8.5", + "num-traits", + "rand", "rkyv", - "serde 1.0.219", + "serde", "serde_json", ] @@ -2777,7 +2552,7 @@ version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ - "bitflags 2.9.2", + "bitflags", "errno", "libc", "linux-raw-sys 0.4.15", @@ -2790,7 +2565,7 @@ version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" dependencies = [ - "bitflags 2.9.2", + "bitflags", "errno", "libc", "linux-raw-sys 0.9.4", @@ -2869,7 +2644,7 @@ checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" dependencies = [ "dyn-clone", "ref-cast", - "serde 1.0.219", + "serde", "serde_json", ] @@ -2881,7 +2656,7 @@ checksum = "82d20c4491bc164fa2f6c5d44565947a52ad80b9505d8e36f8d54c27c739fcd0" dependencies = [ "dyn-clone", "ref-cast", - "serde 1.0.219", + "serde", "serde_json", ] @@ -2899,8 +2674,8 @@ checksum = "f694a6ab48f14bc063cfadff30ab551d3c7e46d8f81836c51989d548f44a2a25" dependencies = [ "heck 0.4.1", "proc-macro-error2", - "proc-macro2 1.0.101", - "quote 1.0.40", + "proc-macro2", + "quote", "syn 2.0.106", ] @@ -2922,7 +2697,7 @@ dependencies = [ "sea-orm-macros", "sea-query", "sea-query-binder", - "serde 1.0.219", + "serde", "serde_json", "sqlx", "strum", @@ -2930,7 +2705,7 @@ dependencies = [ "time", "tracing", "url", - "uuid 1.18.0", + "uuid", ] [[package]] @@ -2958,8 +2733,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a489127c872766445b4e28f846825f89a076ac3af2591d1365503a68f93e974c" dependencies = [ "heck 0.5.0", - "proc-macro2 1.0.101", - "quote 1.0.40", + "proc-macro2", + "quote", "sea-bae", "syn 2.0.106", "unicode-ident", @@ -2995,7 +2770,7 @@ dependencies = [ "sea-query-derive", "serde_json", "time", - "uuid 1.18.0", + "uuid", ] [[package]] @@ -3011,7 +2786,7 @@ dependencies = [ "serde_json", "sqlx", "time", - "uuid 1.18.0", + "uuid", ] [[package]] @@ -3022,8 +2797,8 @@ checksum = "bae0cbad6ab996955664982739354128c58d16e126114fe88c2a493642502aab" dependencies = [ "darling", "heck 0.4.1", - "proc-macro2 1.0.101", - "quote 1.0.40", + "proc-macro2", + "quote", "syn 2.0.106", "thiserror", ] @@ -3048,8 +2823,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "debdc8729c37fdbf88472f97fd470393089f997a909e535ff67c544d18cfccf0" dependencies = [ "heck 0.4.1", - "proc-macro2 1.0.101", - "quote 1.0.40", + "proc-macro2", + "quote", "syn 2.0.106", ] @@ -3065,7 +2840,7 @@ version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ - "bitflags 2.9.2", + "bitflags", "core-foundation", "core-foundation-sys", "libc", @@ -3082,12 +2857,6 @@ dependencies = [ "libc", ] -[[package]] -name = "serde" -version = "0.8.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dad3f759919b92c3068c696c15c3d17238234498bbdcc80f2c469606f948ac8" - [[package]] name = "serde" version = "1.0.219" @@ -3097,27 +2866,14 @@ dependencies = [ "serde_derive", ] -[[package]] -name = "serde-hjson" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a3a4e0ea8a88553209f6cc6cfe8724ecad22e1acf372793c27d995290fe74f8" -dependencies = [ - "lazy_static", - "linked-hash-map 0.3.0", - "num-traits 0.1.43", - "regex", - "serde 0.8.23", -] - [[package]] name = "serde_derive" version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ - "proc-macro2 1.0.101", - "quote 1.0.40", + "proc-macro2", + "quote", "syn 2.0.106", ] @@ -3130,7 +2886,7 @@ dependencies = [ "itoa", "memchr", "ryu", - "serde 1.0.219", + "serde", ] [[package]] @@ -3140,16 +2896,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8136f1a4ea815d7eac4101cfd0b16dc0cb5e1fe1b8609dfd728058656b7badf" dependencies = [ "regex", - "serde 1.0.219", -] - -[[package]] -name = "serde_test" -version = "0.8.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "110b3dbdf8607ec493c22d5d947753282f3bae73c0f56d322af1e8c78e4c23d5" -dependencies = [ - "serde 0.8.23", + "serde", ] [[package]] @@ -3161,7 +2908,7 @@ dependencies = [ "form_urlencoded", "itoa", "ryu", - "serde 1.0.219", + "serde", ] [[package]] @@ -3177,7 +2924,7 @@ dependencies = [ "indexmap 2.10.0", "schemars 0.9.0", "schemars 1.0.4", - "serde 1.0.219", + "serde", "serde_derive", "serde_json", "serde_with_macros", @@ -3191,8 +2938,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de90945e6565ce0d9a25098082ed4ee4002e047cb59892c318d66821e14bb30f" dependencies = [ "darling", - "proc-macro2 1.0.101", - "quote 1.0.40", + "proc-macro2", + "quote", "syn 2.0.106", ] @@ -3202,7 +2949,7 @@ version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ - "cfg-if 1.0.3", + "cfg-if", "cpufeatures", "digest", ] @@ -3213,7 +2960,7 @@ version = "0.10.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ - "cfg-if 1.0.3", + "cfg-if", "cpufeatures", "digest", ] @@ -3249,7 +2996,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ "digest", - "rand_core 0.6.4", + "rand_core", ] [[package]] @@ -3270,34 +3017,6 @@ version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" -[[package]] -name = "slog" -version = "2.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8347046d4ebd943127157b94d63abb990fcf729dc4e9978927fdf4ac3c998d06" - -[[package]] -name = "slog-scope" -version = "4.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f95a4b4c3274cd2869549da82b57ccc930859bdbf5bcea0424bc5f140b3c786" -dependencies = [ - "arc-swap", - "lazy_static", - "slog", -] - -[[package]] -name = "slog-stdlog" -version = "4.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6706b2ace5bbae7291d3f8d2473e2bfab073ccd7d03670946197aec98471fa3e" -dependencies = [ - "log", - "slog", - "slog-scope", -] - [[package]] name = "slug" version = "0.1.6" @@ -3314,7 +3033,7 @@ version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" dependencies = [ - "serde 1.0.219", + "serde", ] [[package]] @@ -3386,7 +3105,7 @@ dependencies = [ "percent-encoding", "rust_decimal", "rustls", - "serde 1.0.219", + "serde", "serde_json", "sha2", "smallvec", @@ -3396,7 +3115,7 @@ dependencies = [ "tokio-stream", "tracing", "url", - "uuid 1.18.0", + "uuid", "webpki-roots 0.26.11", ] @@ -3406,8 +3125,8 @@ version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2d452988ccaacfbf5e0bdbc348fb91d7c8af5bee192173ac3636b5fb6e6715d" dependencies = [ - "proc-macro2 1.0.101", - "quote 1.0.40", + "proc-macro2", + "quote", "sqlx-core", "sqlx-macros-core", "syn 2.0.106", @@ -3424,9 +3143,9 @@ dependencies = [ "heck 0.5.0", "hex", "once_cell", - "proc-macro2 1.0.101", - "quote 1.0.40", - "serde 1.0.219", + "proc-macro2", + "quote", + "serde", "serde_json", "sha2", "sqlx-core", @@ -3447,7 +3166,7 @@ dependencies = [ "atoi", "base64", "bigdecimal", - "bitflags 2.9.2", + "bitflags", "byteorder", "bytes", "chrono", @@ -3469,10 +3188,10 @@ dependencies = [ "memchr", "once_cell", "percent-encoding", - "rand 0.8.5", + "rand", "rsa", "rust_decimal", - "serde 1.0.219", + "serde", "sha1", "sha2", "smallvec", @@ -3481,7 +3200,7 @@ dependencies = [ "thiserror", "time", "tracing", - "uuid 1.18.0", + "uuid", "whoami", ] @@ -3494,7 +3213,7 @@ dependencies = [ "atoi", "base64", "bigdecimal", - "bitflags 2.9.2", + "bitflags", "byteorder", "chrono", "crc", @@ -3513,9 +3232,9 @@ dependencies = [ "memchr", "num-bigint", "once_cell", - "rand 0.8.5", + "rand", "rust_decimal", - "serde 1.0.219", + "serde", "serde_json", "sha2", "smallvec", @@ -3524,7 +3243,7 @@ dependencies = [ "thiserror", "time", "tracing", - "uuid 1.18.0", + "uuid", "whoami", ] @@ -3545,14 +3264,14 @@ dependencies = [ "libsqlite3-sys", "log", "percent-encoding", - "serde 1.0.219", + "serde", "serde_urlencoded", "sqlx-core", "thiserror", "time", "tracing", "url", - "uuid 1.18.0", + "uuid", ] [[package]] @@ -3568,7 +3287,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cddb07e32ddb770749da91081d8d0ac3a16f1a569a18b20348cd371f5dead06b" dependencies = [ "cc", - "cfg-if 1.0.3", + "cfg-if", "libc", "psm", "windows-sys 0.59.0", @@ -3609,25 +3328,14 @@ version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" -[[package]] -name = "syn" -version = "0.15.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ca4b3b69a77cbe1ffc9e198781b7acb0c7365a883670e8f1c1bc66fba79a5c5" -dependencies = [ - "proc-macro2 0.4.30", - "quote 0.6.13", - "unicode-xid 0.1.0", -] - [[package]] name = "syn" version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ - "proc-macro2 1.0.101", - "quote 1.0.40", + "proc-macro2", + "quote", "unicode-ident", ] @@ -3637,8 +3345,8 @@ version = "2.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" dependencies = [ - "proc-macro2 1.0.101", - "quote 1.0.40", + "proc-macro2", + "quote", "unicode-ident", ] @@ -3657,8 +3365,8 @@ version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ - "proc-macro2 1.0.101", - "quote 1.0.40", + "proc-macro2", + "quote", "syn 2.0.106", ] @@ -3695,7 +3403,7 @@ dependencies = [ "log", "mime", "pin-project", - "serde 1.0.219", + "serde", "serde_json", "teloxide-core", "teloxide-macros", @@ -3712,7 +3420,7 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f7a34ca8e971fa892e633858c07547fe138ef4a02e4a4eaa1d35e517d6e0bc4" dependencies = [ - "bitflags 2.9.2", + "bitflags", "bytes", "chrono", "derive_more", @@ -3725,7 +3433,7 @@ dependencies = [ "rc-box", "reqwest", "rgb", - "serde 1.0.219", + "serde", "serde_json", "serde_with", "stacker", @@ -3735,7 +3443,7 @@ dependencies = [ "tokio", "tokio-util", "url", - "uuid 1.18.0", + "uuid", ] [[package]] @@ -3745,8 +3453,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "300fadcaf0c182f19b5ca10bf23a45dc9a48925f00c704405fd90ee2c03942f9" dependencies = [ "heck 0.5.0", - "proc-macro2 1.0.101", - "quote 1.0.40", + "proc-macro2", + "quote", "syn 2.0.106", ] @@ -3777,9 +3485,9 @@ dependencies = [ "percent-encoding", "pest", "pest_derive", - "rand 0.8.5", + "rand", "regex", - "serde 1.0.219", + "serde", "serde_json", "slug", "unic-segment", @@ -3800,8 +3508,8 @@ version = "2.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960" dependencies = [ - "proc-macro2 1.0.101", - "quote 1.0.40", + "proc-macro2", + "quote", "syn 2.0.106", ] @@ -3811,7 +3519,7 @@ version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" dependencies = [ - "cfg-if 1.0.3", + "cfg-if", ] [[package]] @@ -3824,7 +3532,7 @@ dependencies = [ "itoa", "num-conv", "powerfmt", - "serde 1.0.219", + "serde", "time-core", "time-macros", ] @@ -3845,15 +3553,6 @@ dependencies = [ "time-core", ] -[[package]] -name = "tiny-keccak" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" -dependencies = [ - "crunchy", -] - [[package]] name = "tinystr" version = "0.8.1" @@ -3895,6 +3594,7 @@ dependencies = [ "slab", "socket2", "tokio-macros", + "tracing", "windows-sys 0.59.0", ] @@ -3904,8 +3604,8 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ - "proc-macro2 1.0.101", - "quote 1.0.40", + "proc-macro2", + "quote", "syn 2.0.106", ] @@ -3943,15 +3643,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "toml" -version = "0.5.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" -dependencies = [ - "serde 1.0.219", -] - [[package]] name = "toml_datetime" version = "0.6.11" @@ -3990,7 +3681,7 @@ version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" dependencies = [ - "bitflags 2.9.2", + "bitflags", "bytes", "futures-util", "http", @@ -4032,8 +3723,8 @@ version = "0.1.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" dependencies = [ - "proc-macro2 1.0.101", - "quote 1.0.40", + "proc-macro2", + "quote", "syn 2.0.106", ] @@ -4175,12 +3866,6 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0" -[[package]] -name = "unicode-xid" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" - [[package]] name = "unicode-xid" version = "0.2.6" @@ -4202,7 +3887,7 @@ dependencies = [ "form_urlencoded", "idna", "percent-encoding", - "serde 1.0.219", + "serde", ] [[package]] @@ -4217,15 +3902,6 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" -[[package]] -name = "uuid" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" -dependencies = [ - "getrandom 0.2.16", -] - [[package]] name = "uuid" version = "1.18.0" @@ -4234,7 +3910,7 @@ checksum = "f33196643e165781c20a5ead5582283a7dacbb87855d867fbc2df3f81eddc1be" dependencies = [ "getrandom 0.3.3", "js-sys", - "serde 1.0.219", + "serde", "wasm-bindgen", ] @@ -4269,12 +3945,6 @@ dependencies = [ "try-lock", ] -[[package]] -name = "wasi" -version = "0.9.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" - [[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" @@ -4302,7 +3972,7 @@ version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" dependencies = [ - "cfg-if 1.0.3", + "cfg-if", "once_cell", "rustversion", "wasm-bindgen-macro", @@ -4316,8 +3986,8 @@ checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" dependencies = [ "bumpalo", "log", - "proc-macro2 1.0.101", - "quote 1.0.40", + "proc-macro2", + "quote", "syn 2.0.106", "wasm-bindgen-shared", ] @@ -4328,7 +3998,7 @@ version = "0.4.50" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" dependencies = [ - "cfg-if 1.0.3", + "cfg-if", "js-sys", "once_cell", "wasm-bindgen", @@ -4341,7 +4011,7 @@ version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" dependencies = [ - "quote 1.0.40", + "quote", "wasm-bindgen-macro-support", ] @@ -4351,8 +4021,8 @@ version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ - "proc-macro2 1.0.101", - "quote 1.0.40", + "proc-macro2", + "quote", "syn 2.0.106", "wasm-bindgen-backend", "wasm-bindgen-shared", @@ -4446,8 +4116,8 @@ version = "0.60.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" dependencies = [ - "proc-macro2 1.0.101", - "quote 1.0.40", + "proc-macro2", + "quote", "syn 2.0.106", ] @@ -4457,8 +4127,8 @@ version = "0.59.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" dependencies = [ - "proc-macro2 1.0.101", - "quote 1.0.40", + "proc-macro2", + "quote", "syn 2.0.106", ] @@ -4723,7 +4393,7 @@ version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" dependencies = [ - "bitflags 2.9.2", + "bitflags", ] [[package]] @@ -4741,15 +4411,6 @@ dependencies = [ "tap", ] -[[package]] -name = "yaml-rust" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" -dependencies = [ - "linked-hash-map 0.5.6", -] - [[package]] name = "yansi" version = "1.0.1" @@ -4762,7 +4423,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" dependencies = [ - "serde 1.0.219", + "serde", "stable_deref_trait", "yoke-derive", "zerofrom", @@ -4774,8 +4435,8 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" dependencies = [ - "proc-macro2 1.0.101", - "quote 1.0.40", + "proc-macro2", + "quote", "syn 2.0.106", "synstructure", ] @@ -4795,8 +4456,8 @@ version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" dependencies = [ - "proc-macro2 1.0.101", - "quote 1.0.40", + "proc-macro2", + "quote", "syn 2.0.106", ] @@ -4815,8 +4476,8 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ - "proc-macro2 1.0.101", - "quote 1.0.40", + "proc-macro2", + "quote", "syn 2.0.106", "synstructure", ] @@ -4855,7 +4516,7 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" dependencies = [ - "proc-macro2 1.0.101", - "quote 1.0.40", + "proc-macro2", + "quote", "syn 2.0.106", ] diff --git a/Cargo.toml b/Cargo.toml index 6fa4b2fd..e3c82d10 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,13 +12,13 @@ entity = { version = "0.4", path = "./entity" } fern = { version = "0.7", features = ["colored"] } include_dir = { version = "0.7.4", features = ["glob", "nightly"] } itertools = "0.14" +kameo = "0.17" libbot = { path = "./lib" } log = "0.4" migration = { version = "0.4", path = "./migration" } paste = "1" # plurals = "0.3" regex = "1" -riker = "0.4" sea-orm = { version = "1.1", features = [ "macros", "runtime-tokio-rustls", diff --git a/TODO.md b/TODO.md index 8020360e..90828ea8 100644 --- a/TODO.md +++ b/TODO.md @@ -67,3 +67,9 @@ Remember to use `async fn`!! Send function: - https://github.com/bytesnake/telebot/blob/master/telebot-derive/src/lib.rs#L239 + +Update riker to kameo: +- [x] https://tqwewe.com/blog/comparing-rust-actor-libraries/ + +A version of two-timer that can use "in x hours" and supports timezones (default doesn't work with TZs properly): +- https://github.com/JellyWX/two-timer/tree/master diff --git a/bot/Cargo.toml b/bot/Cargo.toml index a7d44d4a..d0b0060c 100644 --- a/bot/Cargo.toml +++ b/bot/Cargo.toml @@ -16,12 +16,12 @@ fern.workspace = true futures.workspace = true include_dir.workspace = true itertools.workspace = true +kameo.workspace = true libbot.workspace = true log.workspace = true migration.workspace = true paste.workspace = true regex.workspace = true -riker.workspace = true sea-orm.workspace = true serde.workspace = true teloxide.workspace = true diff --git a/bot/src/actors/bot_actor.rs b/bot/src/actors/bot_actor.rs index eb64d89b..dc3ae894 100644 --- a/bot/src/actors/bot_actor.rs +++ b/bot/src/actors/bot_actor.rs @@ -1,31 +1,32 @@ use { crate::{ - commands::*, - establish_db_connection, - services::reminder_actor::{ + actors::reminder_actor::{ ReminderActor, ScheduleNextDay, ScheduleNextMinute, ScheduleNextWeek, }, - BotCommand, DbConnPool, NamedActor, + commands::*, + BotCommand, }, - riker::actors::{ - actor, Actor, ActorFactoryArgs, ActorRefFactory, BasicActorRef, ChannelRef, Context, - Receive, Sender, Subscribe, Tell, + kameo::{ + actor::ActorRef, + error::Infallible, + message::{Context, Message}, + Actor, }, + sea_orm::DatabaseConnection, std::fmt::Formatter, teloxide::{ prelude::*, types::{ChatId, ParseMode}, }, + tokio::sync::broadcast, }; -#[derive(Clone)] -#[actor(SendMessage, SendMessageReply, ListCommands)] pub struct BotActor { pub bot: Bot, bot_name: String, lfg_chat_id: i64, - update_channel: ChannelRef, - connection_pool: DbConnPool, + update_sender: broadcast::Sender, + connection_pool: DatabaseConnection, commands_list: Vec<(String, String)>, } @@ -37,33 +38,34 @@ impl std::fmt::Debug for BotActor { } } +#[derive(Debug, Clone)] pub struct ActorUpdateMessage { -pub requester: Bot, -pub update: Message, + pub requester: Bot, + pub update: teloxide::types::Message, } impl ActorUpdateMessage { -pub fn new(requester: Bot, update: Message) -> Self { - Self { requester, update } + pub fn new(requester: Bot, update: teloxide::types::Message) -> Self { + Self { requester, update } } } impl BotActor { // Public API -pub async fn new( + pub async fn new( name: &str, bot: Bot, - chan: ChannelRef, + update_sender: broadcast::Sender, lfg_chat_id: i64, ) -> Self { - let connection_pool = crate::establish_db_connection().await.unwrap(); + let connection_pool = crate::establish_db_connection().await.unwrap(); BotActor { bot, bot_name: name.to_string(), lfg_chat_id, - update_channel: chan, - connection_pool, + update_sender, + connection_pool, commands_list: vec![], } } @@ -91,66 +93,82 @@ pub async fn new( // } } +use crate::commands::match_command; + impl Actor for BotActor { - type Msg = BotActorMsg; + type Args = Self; + type Error = Infallible; - /// Register all bot commands and subscribe them to the system notification channel. - fn pre_start(&mut self, ctx: &Context) { + async fn on_start(args: Self::Args, actor_ref: ActorRef) -> Result { macro_rules! new_command { - ($T:ident) => { - let cmd = ctx - .actor_of_args::<$T, _>(&$T::actor_name(), (ctx.myself().clone(), self.bot_name.clone(), self.connection_pool.clone())) - .unwrap(); // FIXME: panics in pre_start do not cause actor restart, so this is faulty! - self.commands_list.push(($T::prefix().into(), $T::description().into())); - self.update_channel.tell( - Subscribe { - actor: Box::new(cmd), - topic: "raw-commands".into(), - }, - None, - ); - } + ($T:ident, $args:expr) => { + let cmd = $T::spawn($T::new( + actor_ref.clone(), + $args.bot_name.clone(), + $args.connection_pool.clone(), + )); + $args + .commands_list + .push(($T::prefix().into(), $T::description().into())); + + // Subscribe to updates + let mut update_receiver = $args.update_sender.subscribe(); + let cmd_clone = cmd.clone(); + let bot_name = $args.bot_name.clone(); + tokio::spawn(async move { + while let Ok(msg) = update_receiver.recv().await { + if let (Some(_), _) = + match_command(msg.update.text(), $T::prefix(), &bot_name) + { + let _ = cmd_clone.tell(msg).await; + } + } + }); + }; } - new_command!(ActivitiesCommand); - new_command!(CancelCommand); - new_command!(ChatidCommand); - new_command!(D1weekCommand); - new_command!(D2weekCommand); - new_command!(EditCommand); - new_command!(EditGuardianCommand); - new_command!(HelpCommand); - new_command!(JoinCommand); - new_command!(LfgCommand); - new_command!(ListCommand); - new_command!(ManageCommand); - new_command!(PsnCommand); - new_command!(UptimeCommand); - new_command!(WhoisCommand); + let mut bot_actor = args; + + new_command!(ActivitiesCommand, bot_actor); + new_command!(CancelCommand, bot_actor); + new_command!(ChatidCommand, bot_actor); + new_command!(D1weekCommand, bot_actor); + new_command!(D2weekCommand, bot_actor); + new_command!(EditCommand, bot_actor); + new_command!(EditGuardianCommand, bot_actor); + new_command!(HelpCommand, bot_actor); + new_command!(JoinCommand, bot_actor); + new_command!(LfgCommand, bot_actor); + new_command!(ListCommand, bot_actor); + new_command!(ManageCommand, bot_actor); + new_command!(PsnCommand, bot_actor); + new_command!(UptimeCommand, bot_actor); + new_command!(WhoisCommand, bot_actor); // Create reminder tasks actor - let reminders = ctx - .actor_of_args::( - "reminders", - (ctx.myself(), self.lfg_chat_id, self.connection_pool.clone()), - ) - .unwrap(); + let reminders = ReminderActor::spawn(ReminderActor::new( + actor_ref.clone(), + bot_actor.lfg_chat_id, + bot_actor.connection_pool.clone(), + )); + // Schedule first run, the actor handler will reschedule. - reminders.tell(ScheduleNextMinute, None); - reminders.tell(ScheduleNextDay, None); - reminders.tell(ScheduleNextWeek, None); - } + let _ = reminders.tell(ScheduleNextMinute).await; + let _ = reminders.tell(ScheduleNextDay).await; + let _ = reminders.tell(ScheduleNextWeek).await; - fn recv(&mut self, ctx: &Context, msg: Self::Msg, sender: Sender) { - self.receive(ctx, msg, sender); + Ok(bot_actor) } } -impl ActorFactoryArgs<(String, Bot, ChannelRef, i64)> for BotActor { - fn create_args( - (bot_name, bot, chan, lfg_chat): (String, Bot, ChannelRef, i64), +impl BotActor { + pub async fn create( + bot_name: String, + bot: Bot, + update_sender: broadcast::Sender, + lfg_chat: i64, ) -> Self { - Self::new(&bot_name, bot, chan, lfg_chat) + Self::new(&bot_name, bot, update_sender, lfg_chat).await } } @@ -176,10 +194,14 @@ pub struct SendMessageReply(pub String, pub ActorUpdateMessage, pub Format, pub #[derive(Clone, Debug)] pub struct ListCommands(pub ActorUpdateMessage); -impl Receive for BotActor { - type Msg = BotActorMsg; +impl Message for BotActor { + type Reply = (); - fn receive(&mut self, _ctx: &Context, msg: SendMessage, _sender: Sender) { + async fn handle( + &mut self, + msg: SendMessage, + _ctx: &mut Context, + ) -> Self::Reply { log::debug!("SendMessage: {}", &msg.0); let resp = self .bot @@ -188,13 +210,13 @@ impl Receive for BotActor { Notify::On => false, Notify::Off => true, }) - .link_preview_options(teloxide::types::LinkPreviewOptions { - is_disabled: true, - url: None, - prefer_small_media: false, - prefer_large_media: false, - show_above_text: false, - }); + .link_preview_options(teloxide::types::LinkPreviewOptions { + is_disabled: true, + url: None, + prefer_small_media: false, + prefer_large_media: false, + show_above_text: false, + }); let resp = match msg.2 { Format::Html => resp.parse_mode(ParseMode::Html), @@ -202,37 +224,36 @@ impl Receive for BotActor { Format::Plain => resp, }; - let rt = tokio::runtime::Builder::new_current_thread() - .enable_all() - .build() - .unwrap(); - - rt.block_on(resp.send()).unwrap(); + let _ = resp.send().await; } } -impl Receive for BotActor { - type Msg = BotActorMsg; +impl Message for BotActor { + type Reply = (); - fn receive(&mut self, _ctx: &Context, msg: SendMessageReply, _sender: Sender) { + async fn handle( + &mut self, + msg: SendMessageReply, + _ctx: &mut Context, + ) -> Self::Reply { log::debug!("SendMessageReply: {}", &msg.0); let message = msg.1; let fut = self .bot - .send_message(message.update.chat.id, msg.0) - .reply_parameters(teloxide::types::ReplyParameters::new(message.update.id)) + .send_message(message.update.chat.id, msg.0) + .reply_parameters(teloxide::types::ReplyParameters::new(message.update.id)) .disable_notification(match msg.3 { Notify::On => false, Notify::Off => true, }) - .link_preview_options(teloxide::types::LinkPreviewOptions { - is_disabled: true, - url: None, - prefer_small_media: false, - prefer_large_media: false, - show_above_text: false, - }); + .link_preview_options(teloxide::types::LinkPreviewOptions { + is_disabled: true, + url: None, + prefer_small_media: false, + prefer_large_media: false, + show_above_text: false, + }); let fut = match msg.2 { Format::Html => fut.parse_mode(ParseMode::Html), @@ -240,19 +261,18 @@ impl Receive for BotActor { Format::Plain => fut, }; - let rt = tokio::runtime::Builder::new_current_thread() - .enable_all() - .build() - .unwrap(); - - rt.block_on(fut.send()).unwrap(); + let _ = fut.send().await; } } -impl Receive for BotActor { - type Msg = BotActorMsg; +impl Message for BotActor { + type Reply = (); - fn receive(&mut self, ctx: &Context, msg: ListCommands, _sender: Sender) { + async fn handle( + &mut self, + msg: ListCommands, + ctx: &mut Context, + ) -> Self::Reply { log::debug!("ListCommands"); let message = msg.0; @@ -263,9 +283,9 @@ impl Receive for BotActor { |acc, pair| format!("{}{} — {}\n\n", acc, pair.0, pair.1), ); - ctx.myself.tell( - SendMessageReply(reply, message, Format::Html, Notify::Off), - None, - ); + let _ = ctx + .actor_ref() + .tell(SendMessageReply(reply, message, Format::Html, Notify::Off)) + .try_send(); // @todo use unbounded mailbox for bot_actor? prolly not } } diff --git a/bot/src/bin/bot.rs b/bot/src/bin/bot.rs index 94834840..57655267 100644 --- a/bot/src/bin/bot.rs +++ b/bot/src/bin/bot.rs @@ -9,11 +9,11 @@ use { establish_db_connection, }, dotenv::dotenv, + kameo::Actor, migration::{Migrator, MigratorTrait}, - // riker::prelude::*, doesn't work here! - riker::actors::{channel, ActorRefFactory, ActorSystem, ChannelRef, Publish, Tell}, std::env, - teloxide::{prelude::*, requests::ResponseResult}, + teloxide::{prelude::*, requests::ResponseResult, types::Message as TelegramMessage}, + tokio::sync::broadcast, }; fn setup_logging() -> Result<(), fern::InitError> { @@ -94,32 +94,26 @@ async fn main() -> anyhow::Result<()> { let connection = establish_db_connection().await?; Migrator::up(&connection, None).await?; - let sys = ActorSystem::new().unwrap(); - let tgbot = Bot::new(token); - let chan: ChannelRef = channel("commands", &sys).unwrap(); + let (update_sender, _) = broadcast::channel::(1000); - let _bot_ref = sys - .actor_of_args::("bot", (bot_name, tgbot.clone(), chan.clone(), lfg_chat)) - .expect("Couldn't start the bot"); + let bot_actor = + BotActor::create(bot_name, tgbot.clone(), update_sender.clone(), lfg_chat).await; + let _bot_ref = BotActor::spawn(bot_actor); - teloxide::repl(tgbot.clone(), move |bot: Bot, message: Message| { - let chan = chan.clone(); + teloxide::repl(tgbot.clone(), move |bot: Bot, message: TelegramMessage| { + let update_sender = update_sender.clone(); async move { log::debug!("Processing message {}", message.id); - chan.tell( - Publish { - msg: ActorUpdateMessage { - requester: bot, - update: message, - }, - topic: "raw-commands".into(), - }, - None, - ); + let _ = update_sender.send(ActorUpdateMessage { + requester: bot, + update: message, + }); ResponseResult::<()>::Ok(()) } }) .await; + + Ok(()) } diff --git a/bot/src/commands/mod.rs b/bot/src/commands/mod.rs index a575c247..601f626c 100644 --- a/bot/src/commands/mod.rs +++ b/bot/src/commands/mod.rs @@ -1,53 +1,31 @@ use { crate::actors::bot_actor::{ActorUpdateMessage, Format, Notify, SendMessageReply}, entity::guardians, - riker::actors::{ActorRef, Tell}, - sea_orm::{ColumnTrait, EntityTrait, QueryFilter}, + kameo::actor::ActorRef, + sea_orm::{ColumnTrait, DatabaseConnection, EntityTrait, QueryFilter}, }; #[macro_export] macro_rules! command_actor { ($name:ident, [ $($msgs:ident),* ]) => { - use $crate::{bot_actor::BotActorMsg, NamedActor}; - use paste::paste; - use riker::actors::{ - actor, Actor, ActorFactoryArgs, ActorRef, BasicActorRef, Context, Sender, Receive, + use { + kameo::{actor::ActorRef, error::Infallible, message::*, Actor}, + sea_orm::DatabaseConnection, + $crate::BotConnection, }; - use sea_orm::DatabaseConnection; #[derive(Clone)] - #[actor($($msgs)*)] pub struct $name { bot_ref: ActorRef<$crate::actors::bot_actor::BotActor>, bot_name: String, connection_pool: DatabaseConnection, } - impl NamedActor for $name { - fn actor_name() -> String { std::stringify!($name).into() } - } - impl $name { - pub fn new( - bot_ref: ActorRef<$crate::actors::bot_actor::BotActor>, - bot_name: String, - connection_pool: DatabaseConnection, - ) -> Self { - Self { - bot_ref, - bot_name, - connection_pool, - } - } - - pub fn connection(&self) -> &DatabaseConnection { - &self.connection_pool - } - pub fn new( bot_ref: ActorRef<$crate::actors::bot_actor::BotActor>, bot_name: String, - connection_pool: BotConnection, + connection_pool: DatabaseConnection, ) -> Self { Self { bot_ref, @@ -56,7 +34,7 @@ macro_rules! command_actor { } } - pub fn connection(&self) -> &BotConnection { + pub fn connection(&self) -> &DatabaseConnection { &self.connection_pool } @@ -98,16 +76,14 @@ macro_rules! command_actor { } impl Actor for $name { - type Msg = paste! { [<$name Msg>] }; + type Args = Self; + type Error = Infallible; - fn recv(&mut self, ctx: &Context, msg: Self::Msg, sender: Sender) { - self.receive(ctx, msg, sender); - } - } - - impl ActorFactoryArgs<(ActorRef, String, DbConnPool)> for $name { - fn create_args((bot_ref, bot_name, connection_pool): (ActorRef, String, DbConnPool)) -> Self { - Self { bot_ref, bot_name, connection_pool } + async fn on_start( + args: Self::Args, + _actor_ref: ActorRef, + ) -> Result { + Ok(args) } } }; @@ -159,16 +135,15 @@ pub async fn validate_username( ) -> Option { let username = match message.update.from.as_ref().unwrap().username { None => { - let _ = bot.tell( - SendMessageReply( + let _ = bot + .tell(SendMessageReply( "❌ You have no telegram username, register your telegram account first." .into(), message.clone(), Format::Plain, Notify::Off, - ), - None, - ); + )) + .await; return None; } Some(ref name) => name.clone(), @@ -182,27 +157,25 @@ pub async fn validate_username( match db_user { Ok(Some(user)) => Some(user), Ok(None) => { - let _ = bot.tell( - SendMessageReply( + let _ = bot + .tell(SendMessageReply( "❌ You need to link your PSN account first: use /psn command".into(), message.clone(), Format::Plain, Notify::Off, - ), - None, - ); + )) + .await; None } Err(_) => { - let _ = bot.tell( - SendMessageReply( + let _ = bot + .tell(SendMessageReply( "❌ Error querying guardian info.".into(), message.clone(), Format::Plain, Notify::Off, - ), - None, - ); + )) + .await; None } } @@ -244,7 +217,7 @@ pub async fn guardian_lookup( /// @returns A pair of matched command and remainder of the message text. /// (None, None) if command did not match, /// (command, and Some remaining text after command otherwise). -fn match_command( +pub fn match_command( text: Option<&str>, command: &str, bot_name: &str, diff --git a/bot/src/lib.rs b/bot/src/lib.rs index 96c19f3a..c0ea1852 100644 --- a/bot/src/lib.rs +++ b/bot/src/lib.rs @@ -93,10 +93,6 @@ pub async fn establish_db_connection() -> DatabaseConnection { entity::establish_db_connection().await? } -pub trait NamedActor { - fn actor_name() -> String; -} - pub trait BotCommand { /// Print command usage instructions. // fn usage(&self, bot: &BotMenu, message: &UpdateWithCx, Message>); From 295d7f2e79660ae31a4221ea8aeaa665c573b030 Mon Sep 17 00:00:00 2001 From: Berkus Decker Date: Mon, 18 Aug 2025 23:26:28 +0300 Subject: [PATCH 10/26] =?UTF-8?q?=E2=9C=A8=20Automate=20BotCommand=20impl?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add automatic usage template rendering for commands --- bot/src/commands/mod.rs | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/bot/src/commands/mod.rs b/bot/src/commands/mod.rs index 601f626c..6f12c5a9 100644 --- a/bot/src/commands/mod.rs +++ b/bot/src/commands/mod.rs @@ -7,11 +7,11 @@ use { #[macro_export] macro_rules! command_actor { - ($name:ident, [ $($msgs:ident),* ]) => { + ($name:ident, $prefix:literal, $help:literal) => { use { kameo::{actor::ActorRef, error::Infallible, message::*, Actor}, sea_orm::DatabaseConnection, - $crate::BotConnection, + $crate::BotCommand, }; #[derive(Clone)] @@ -21,6 +21,16 @@ macro_rules! command_actor { connection_pool: DatabaseConnection, } + impl BotCommand for $name { + fn prefix() -> &'static str { + concat!("/", $prefix) + } + + fn description() -> &'static str { + $help + } + } + impl $name { pub fn new( bot_ref: ActorRef<$crate::actors::bot_actor::BotActor>, @@ -38,6 +48,14 @@ macro_rules! command_actor { &self.connection_pool } + pub async fn usage(&self, message: &$crate::actors::bot_actor::ActorUpdateMessage) { + self.send_reply( + message, + $crate::render_template_or_err!(concat!($prefix, "/usage")), + ) + .await; + } + #[allow(dead_code, reason = "help_command doesn't use those")] async fn send_reply_with_format( &self, From f9c8b443baaf8e664c9f5758e317df7a362b2b2d Mon Sep 17 00:00:00 2001 From: Berkus Decker Date: Wed, 20 Aug 2025 15:03:26 +0300 Subject: [PATCH 11/26] =?UTF-8?q?=F0=9F=91=BD=20Update=20Activities=20comm?= =?UTF-8?q?and?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bot/src/commands/activities_command.rs | 688 +++++++++++++------------ bot/templates/activities/list.tera | 11 + bot/templates/activities/usage.tera | 29 ++ 3 files changed, 411 insertions(+), 317 deletions(-) create mode 100644 bot/templates/activities/list.tera create mode 100644 bot/templates/activities/usage.tera diff --git a/bot/src/commands/activities_command.rs b/bot/src/commands/activities_command.rs index 008a20ec..8f366fb8 100644 --- a/bot/src/commands/activities_command.rs +++ b/bot/src/commands/activities_command.rs @@ -1,416 +1,467 @@ use { crate::{ - actors::bot_actor::{ActorUpdateMessage, Format, Notify, SendMessageReply}, + actors::bot_actor::{ActorUpdateMessage, Format}, commands::{admin_check, match_command}, - models::{Activity, ActivityShortcut, NewActivity, NewActivityShortcut}, - BotCommand, + render_template_or_err, }, - diesel::{self, prelude::*}, - diesel_derives_traits::{Model, NewModel}, + entity::{activities, activityshortcuts}, itertools::Itertools, - riker::actors::Tell, + kameo::message::Context, + sea_orm::{ActiveModelTrait, EntityTrait, QueryOrder, Set}, std::collections::HashMap, }; -command_actor!(ActivitiesCommand, [ActorUpdateMessage]); +command_actor!( + ActivitiesCommand, + "activities", + "List available activity shortcuts" +); impl ActivitiesCommand { - fn send_reply(&self, message: &ActorUpdateMessage, reply: S) - where - S: Into, - { - self.bot_ref.tell( - SendMessageReply(reply.into(), message.clone(), Format::Plain, Notify::Off), - None, - ); - } + async fn all_activities_list( + &self, + connection: &DatabaseConnection, + message: &ActorUpdateMessage, + ) { + let games = activityshortcuts::Entity::find() + .find_also_related(activities::Entity) + .order_by_asc(activityshortcuts::Column::Game) + .order_by_asc(activityshortcuts::Column::Name) + .all(connection) + .await + .expect("❌ Failed to load activity shortcuts"); + + #[derive(serde::Serialize)] + struct Game { + game: String, + shortcut: String, + activity: String, + } + + let games: Vec = games + .into_iter() + .map(|game| { + let link_activity = game.1.expect("❌ Activity not found"); + + Game { + game: game.0.game, + shortcut: game.0.name, + activity: link_activity.format_name(), + } + }) + .collect(); - fn usage(&self, message: &ActorUpdateMessage) { - self.send_reply( + self.send_reply_with_format( message, - "Activities command help: - -/activities - Lists all available activities shortcuts. - -Admin-only mode: - -/activities ids - Lists IDs of all activities. -/activities add KV - Create new activity from KV pairs (see below). -/activities edit ID KV - Modify activity with given ID by updating all given KVs. -/activities addsc ID shortcut - Add activity shortcut for activity ID. -/activities delete ID - Remove activity if it doesn't have any activities planned. - -KV pairs are space-separated pairs of key=value elements -String arguments may be in quotes, but this is optional. - -Supported KV pairs for add/edit commands: - -name=activity name (e.g. Crucible) -mode=activity mode (e.g. Iron Banner) -min_fireteam_size=n -max_fireteam_size=n -min_light=n -min_level=n ", - ); + render_template_or_err!("activities/list", ("games" => &games)), + Format::Html, + ) + .await; } -} -impl BotCommand for ActivitiesCommand { - fn prefix() -> &'static str { - "/activities" + async fn activities_ids_list( + &self, + connection: &DatabaseConnection, + message: &ActorUpdateMessage, + ) { + let games = activities::Entity::find() + .all(connection) + .await + .expect("❌ Failed to load activities"); + + let mut text = "Activities:\n\n".to_string(); + for activity in games { + text += &format!( + "{}. {} {}\n", + activity.id, + activity.name, + activity.mode.unwrap_or("".into()) + ); + } + self.send_reply(message, text).await; + } + + async fn activity_add( + &self, + connection: &DatabaseConnection, + message: &ActorUpdateMessage, + mut argmap: HashMap<&str, &str>, + ) { + let name = argmap.remove("name"); + if name.is_none() { + return self + .send_reply(message, "❌ Must specify activity name, see help.") + .await; + } + + let min_fireteam_size = argmap.remove("min_fireteam_size"); + if min_fireteam_size.is_none() { + return self + .send_reply(message, "❌ Must specify min_fireteam_size, see help.") + .await; + } + let min_fireteam_size = min_fireteam_size.unwrap().parse::(); + if min_fireteam_size.is_err() { + return self + .send_reply(message, "❌ min_fireteam_size must be a number") + .await; + } + let min_fireteam_size = min_fireteam_size.unwrap(); + + let max_fireteam_size = argmap.remove("max_fireteam_size"); + if max_fireteam_size.is_none() { + return self + .send_reply(message, "❌ Must specify max_fireteam_size, see help.") + .await; + } + let max_fireteam_size = max_fireteam_size.unwrap().parse::(); + if max_fireteam_size.is_err() { + return self + .send_reply(message, "❌ max_fireteam_size must be a number") + .await; + } + let max_fireteam_size = max_fireteam_size.unwrap(); + + // TODO: check for no duplicates -- ? + + let mut act = activities::ActiveModel { + name: Set(name.unwrap().to_string()), + mode: Set(None), + min_fireteam_size: Set(min_fireteam_size), + max_fireteam_size: Set(max_fireteam_size), + min_level: Set(None), + min_light: Set(None), + ..Default::default() + }; + + for (key, val) in argmap { + match key { + "min_light" => { + let val = val.parse::(); + if val.is_err() { + return self + .send_reply(message, "❌ min_light must be a number") + .await; + } + act.min_light = Set(Some(val.unwrap())); + } + "min_level" => { + let val = val.parse::(); + if val.is_err() { + return self + .send_reply(message, "❌ min_level must be a number") + .await; + } + act.min_level = Set(Some(val.unwrap())); + } + "mode" => act.mode = Set(Some(val.to_string())), + _ => { + return self + .send_reply(message, format!("❌ Unknown field name {}", key)) + .await; + } + } + } + + match act.insert(connection).await { + Ok(act) => { + self.send_reply(message, format!("✅ Activity {} added.", act.format_name())) + .await + } + Err(e) => { + self.send_reply(message, format!("❌ Error creating activity. {:?}", e)) + .await + } + } + } + + async fn activity_add_shortcut( + &self, + connection: &DatabaseConnection, + message: &ActorUpdateMessage, + link: i32, + name: String, + game: String, + ) { + let act = activities::Entity::find_by_id(link) + .one(connection) + .await + .expect("Failed to run SQL"); + + if act.is_none() { + return self + .send_reply(message, format!("❌ Activity {} was not found.", link)) + .await; + } + + let shortcut = activityshortcuts::ActiveModel { + name: Set(name), + game: Set(game), + link: Set(link), + ..Default::default() + }; + + if shortcut.insert(connection).await.is_err() { + return self.send_reply(message, "❌ Error creating shortcut").await; + } + + self.send_reply(message, "✅ Shortcut added").await; + } + + async fn activity_edit( + &self, + connection: &DatabaseConnection, + message: &ActorUpdateMessage, + id: i32, + argmap: HashMap<&str, &str>, + ) { + let act = activities::Entity::find_by_id(id) + .one(connection) + .await + .expect("❌ Failed to run SQL"); + + if act.is_none() { + return self + .send_reply(message, format!("❌ Activity {} was not found.", id)) + .await; + } + let act = act.unwrap(); + let mut act: activities::ActiveModel = act.into(); + + for (key, val) in argmap { + match key { + "name" => act.name = Set(val.to_string()), + "min_fireteam_size" => { + let val = val.parse::(); + if val.is_err() { + return self + .send_reply(message, "❌ min_fireteam_size must be a number") + .await; + } + act.min_fireteam_size = Set(val.unwrap()) + } + "max_fireteam_size" => { + let val = val.parse::(); + if val.is_err() { + return self + .send_reply(message, "❌ max_fireteam_size must be a number") + .await; + } + act.max_fireteam_size = Set(val.unwrap()) + } + "min_light" => { + let val = val.parse::(); + if val.is_err() { + return self + .send_reply(message, "❌ min_light must be a number") + .await; + } + act.min_light = Set(Some(val.unwrap())) + } + "min_level" => { + let val = val.parse::(); + if val.is_err() { + return self + .send_reply(message, "❌ min_level must be a number") + .await; + } + act.min_level = Set(Some(val.unwrap())) + } + "mode" => act.mode = Set(Some(val.to_string())), + _ => { + return self + .send_reply(message, format!("❌ Unknown field name {}", key)) + .await; + } + } + } + + match act.update(connection).await { + Ok(act) => { + self.send_reply( + message, + format!("✅ Activity {} updated.", act.format_name()), + ) + .await + } + Err(e) => { + self.send_reply(message, format!("❌ Error updating activity. {:?}", e)) + .await + } + } } - fn description() -> &'static str { - "List available activity shortcuts" + async fn activity_delete( + &self, + connection: &DatabaseConnection, + message: &ActorUpdateMessage, + id: i32, + ) { + let act = activities::Entity::find_by_id(id) + .one(connection) + .await + .expect("❌ Failed to run SQL"); + + if act.is_none() { + return self + .send_reply(message, format!("❌ Activity {} was not found.", id)) + .await; + } + let act = act.unwrap(); + + let name = act.format_name(); + + match activities::Entity::delete_by_id(id).exec(connection).await { + Ok(_) => { + self.send_reply(message, format!("✅ Activity {} deleted.", name)) + .await + } + Err(e) => { + // TODO: error chain? + self.send_reply(message, format!("❌ Error deleting activity. {:?}", e)) + .await + } + } } } -// Need to find a way to partially implement the Actor trait here, esp to set up sub-command actors -// impl Actor for ActivitiesCommand { -// // Create subcommand actors somewhere here... -// -// fn pre_start(&mut self, ctx: &Context) { -// todo!() -// } -// -// fn post_start(&mut self, ctx: &Context) { -// todo!() -// } -// } - -impl Receive for ActivitiesCommand { - type Msg = ActivitiesCommandMsg; - - fn receive(&mut self, _ctx: &Context, message: ActorUpdateMessage, _sender: Sender) { +impl Message for ActivitiesCommand { + type Reply = (); + + async fn handle( + &mut self, + message: ActorUpdateMessage, + _ctx: &mut Context, + ) -> Self::Reply { if let (Some(_), args) = match_command(message.update.text(), Self::prefix(), &self.bot_name) { let connection = self.connection(); if args.is_none() { - use crate::schema::{ - activities::dsl::{activities, id}, - activityshortcuts::dsl::{activityshortcuts, game, name}, - }; - // Just /activities - let games = activityshortcuts - .select(game) - .distinct() - .order(game.asc()) - .load::(&connection) - .expect("Failed to load activity shortcuts"); - - let mut text = "Activities: use a short name:\n".to_owned(); - - for game_name in games { - text += &format!("*** {0}:\n", game_name); - let shortcuts = activityshortcuts - .filter(game.eq(game_name)) - .order(name.asc()) - .load::(&connection) - .expect("TEMP loading @FIXME"); - for shortcut in shortcuts { - let link_name = activities - .filter(id.eq(shortcut.link)) - .first::(&connection) - .expect("Failed to load activity"); - - text += &format!( - "{name}\t{link}\n", - name = shortcut.name, - link = link_name.format_name(), - ); - } - text += "\n"; - } - - return self.bot_ref.tell( - SendMessageReply(text, message, Format::Html, Notify::Off), - None, - ); + return self.all_activities_list(connection, &message).await; } // some args - pass to a subcommand - let args = args.unwrap(); let args: Vec<&str> = args.splitn(2, ' ').collect(); if args.is_empty() { - return self.usage(&message); + return self.usage(&message).await; } - let admin = admin_check(&self.bot_ref, &message, &connection); + let admin = admin_check(&self.bot_ref, &message, connection).await; if admin.is_none() { - return self.send_reply(&message, "You are not admin"); + return self.send_reply(&message, "❌ You are not admin").await; } // split into subcommands: match args[0] { - "ids" => { - use crate::schema::activities::dsl::{activities, id, mode, name}; - - let games = activities - .select((id, name, mode)) - .order(id.asc()) - .load::<(i32, String, Option)>(&connection) - .expect("Failed to load activities"); - - let mut text = "Activities:\n\n".to_string(); - for (id_, name_, mode_) in games { - text += &format!("{}. {} {}\n", id_, name_, mode_.unwrap_or("".into())); - } - self.send_reply(&message, text); - } + "ids" => self.activities_ids_list(connection, &message).await, "add" => { if args.len() < 2 { - self.send_reply(&message, "Syntax: /activities add KV"); - return self.usage(&message); + self.send_reply(&message, "❓ Syntax: /activities add KV") + .await; + return self.usage(&message).await; } let argmap = parse_kv_args(args[1]); if argmap.is_none() { return self - .send_reply(&message, "Invalid activity specification, see help."); - } - let mut argmap = argmap.unwrap(); - let name = argmap.remove("name"); - if name.is_none() { - return self.send_reply(&message, "Must specify activity name, see help."); - } - - let min_fireteam_size = argmap.remove("min_fireteam_size"); - if min_fireteam_size.is_none() { - return self - .send_reply(&message, "Must specify min_fireteam_size, see help."); - } - let min_fireteam_size = min_fireteam_size.unwrap().parse::(); - if min_fireteam_size.is_err() { - return self.send_reply(&message, "min_fireteam_size must be a number"); - } - let min_fireteam_size = min_fireteam_size.unwrap(); - - let max_fireteam_size = argmap.remove("max_fireteam_size"); - if max_fireteam_size.is_none() { - return self - .send_reply(&message, "Must specify max_fireteam_size, see help."); - } - let max_fireteam_size = max_fireteam_size.unwrap().parse::(); - if max_fireteam_size.is_err() { - return self.send_reply(&message, "max_fireteam_size must be a number"); - } - let max_fireteam_size = max_fireteam_size.unwrap(); - - // check no duplicates -- ? - let mut act = NewActivity { - name: name.unwrap().into(), - mode: None, - min_fireteam_size, - max_fireteam_size, - min_level: None, - min_light: None, - }; - - for (key, val) in argmap { - match key { - "min_light" => { - let val = val.parse::(); - if val.is_err() { - return self.send_reply(&message, "min_light must be a number"); - } - act.min_light = Some(val.unwrap()) - } - "min_level" => { - let val = val.parse::(); - if val.is_err() { - return self.send_reply(&message, "min_level must be a number"); - } - act.min_level = Some(val.unwrap()) - } - "mode" => act.mode = Some(val.into()), - _ => { - return self - .send_reply(&message, format!("Unknown field name {}", key)); - } - } - } - - match act.save(&connection) { - Ok(act) => self - .send_reply(&message, format!("Activity {} added.", act.format_name())), - Err(e) => { - self.send_reply(&message, format!("Error creating activity. {:?}", e)) - } + .send_reply(&message, "❌ Invalid activity specification, see help.") + .await; } + let argmap = argmap.unwrap(); + self.activity_add(connection, &message, argmap).await; } "addsc" => { if args.len() < 2 { - return self.send_reply( - &message, - "Syntax: /activities addsc ActivityID ShortcutName Game name", - ); + return self + .send_reply( + &message, + "❓ Syntax: /activities addsc ActivityID ShortcutName Game name", + ) + .await; } let args: Vec<&str> = args[1].splitn(3, ' ').collect(); if args.len() != 3 { return self.send_reply( &message, - "To add a shortcut specify activity ID, shortcut name and then the game name", - ); + "❌ To add a shortcut specify 1) activity ID, 2) shortcut name and then 3) the game name", + ).await; } let link = args[0].parse::(); if link.is_err() { - return self.send_reply(&message, "ActivityID must be a number"); + return self + .send_reply(&message, "❌ ActivityID must be a number") + .await; } let link = link.unwrap(); let name = args[1].to_string(); let game = args[2].to_string(); - let act = Activity::find_one(&connection, &link).expect("Failed to run SQL"); - - if act.is_none() { - return self - .send_reply(&message, format!("Activity {} was not found.", link)); - } - - let shortcut = NewActivityShortcut { name, game, link }; - - if shortcut.save(&connection).is_err() { - return self.send_reply(&message, "Error creating shortcut"); - } - - self.send_reply(&message, "Shortcut added"); + self.activity_add_shortcut(connection, &message, link, name, game) + .await; } "edit" => { if args.len() < 2 { - self.send_reply(&message, "Syntax: /activities edit ID KV"); - return self.usage(&message); + self.send_reply(&message, "❓ Syntax: /activities edit ID KV") + .await; + return self.usage(&message).await; } let args: Vec<&str> = args[1].splitn(2, ' ').collect(); if args.len() != 2 { - return self.send_reply( - &message, - "To edit first specify Activity ID and then key=value pairs", - ); + return self + .send_reply( + &message, + "❌ To edit first specify Activity ID and then key=value pairs", + ) + .await; } let id = args[0].parse::(); if id.is_err() { - return self.send_reply(&message, "ActivityID must be a number"); - } - let id = id.unwrap(); - - let act = Activity::find_one(&connection, &id).expect("Failed to run SQL"); - - if act.is_none() { return self - .send_reply(&message, format!("Activity {} was not found.", id)); + .send_reply(&message, "❌ ActivityID must be a number") + .await; } - let mut act = act.unwrap(); + let id = id.unwrap(); let argmap = parse_kv_args(args[1]); if argmap.is_none() { return self - .send_reply(&message, "Invalid activity specification, see help."); + .send_reply(&message, "❌ Invalid activity specification, see help.") + .await; } let argmap = argmap.unwrap(); - for (key, val) in argmap { - match key { - "name" => act.name = val.into(), - "min_fireteam_size" => { - let val = val.parse::(); - if val.is_err() { - return self.send_reply( - &message, - "min_fireteam_size must be a number", - ); - } - act.min_fireteam_size = val.unwrap() - } - "max_fireteam_size" => { - let val = val.parse::(); - if val.is_err() { - return self.send_reply( - &message, - "max_fireteam_size must be a number", - ); - } - act.max_fireteam_size = val.unwrap() - } - "min_light" => { - let val = val.parse::(); - if val.is_err() { - return self.send_reply(&message, "min_light must be a number"); - } - act.min_light = Some(val.unwrap()) - } - "min_level" => { - let val = val.parse::(); - if val.is_err() { - return self.send_reply(&message, "min_level must be a number"); - } - act.min_level = Some(val.unwrap()) - } - "mode" => act.mode = Some(val.into()), - _ => { - return self - .send_reply(&message, format!("Unknown field name {}", key)); - } - } - } - - match act.save(&connection) { - Ok(act) => self.send_reply( - &message, - format!("Activity {} updated.", act.format_name()), - ), - Err(e) => { - self.send_reply(&message, format!("Error updating activity. {:?}", e)) - } - } + self.activity_edit(connection, &message, id, argmap).await; } "delete" => { if args.len() < 2 { - self.send_reply(&message, "Syntax: /activities delete ID"); - return self.usage(&message); + self.send_reply(&message, "❓ Syntax: /activities delete ID") + .await; + return self.usage(&message).await; } let id = args[1].parse::(); if id.is_err() { - return self.send_reply(&message, "ActivityID must be a number"); - } - let id = id.unwrap(); - - let act = Activity::find_one(&connection, &id).expect("Failed to run SQL"); - - if act.is_none() { return self - .send_reply(&message, format!("Activity {} was not found.", id)); + .send_reply(&message, "❌ Activity ID must be a number") + .await; } + let id = id.unwrap(); - let act = act.unwrap(); - - let name = act.format_name(); - - match act.destroy(&connection) { - Ok(_) => self.send_reply(&message, format!("Activity {} deleted.", name)), - Err(e) => { - self.send_reply(&message, format!("Error deleting activity. {:?}", e)) - } - } + self.activity_delete(connection, &message, id).await; } _ => { - self.send_reply(&message, "Unknown activities operation"); - self.usage(&message); + self.send_reply(&message, "❌ Unknown activities operation") + .await; + self.usage(&message).await; } } } @@ -431,8 +482,11 @@ fn parse_kv_args(args: &str) -> Option> { match fragments.len() { x if x < 2 => None, - 2 => // only single parameter - Some(final_collect(fragments)), + 2 => + // only single parameter + { + Some(final_collect(fragments)) + } _ => { // ['max_fireteam_size', '1', 'name', '6', 'mode', '"Last Wish, Enhance"'] let subfrags = fragments[1..fragments.len() - 1] diff --git a/bot/templates/activities/list.tera b/bot/templates/activities/list.tera new file mode 100644 index 00000000..9824efd8 --- /dev/null +++ b/bot/templates/activities/list.tera @@ -0,0 +1,11 @@ +Activities: use a short name: + +{%- set_global prevgame = "" -%} +{% for game in games -%} +{% if prevgame != game.game -%} +{% set_global prevgame = game.game %} + +*** {{ game.game }}: +{% endif %} +{{game.shortcut}} {{game.activity}} +{%- endfor %} diff --git a/bot/templates/activities/usage.tera b/bot/templates/activities/usage.tera new file mode 100644 index 00000000..1e83f782 --- /dev/null +++ b/bot/templates/activities/usage.tera @@ -0,0 +1,29 @@ +Activities command help: + +/activities + Lists all available activities shortcuts. + +🔑 Admin-only mode: + +/activities ids + Lists IDs of all activities. +/activities add KV + Create new activity from KV pairs (see below). +/activities edit ID KV + Modify activity with given ID by updating all given KVs. +/activities addsc ID shortcut + Add activity shortcut for activity ID. +/activities delete ID + Remove activity if it doesn't have any activities planned. + +KV pairs are space-separated pairs of key=value elements +String arguments may be in quotes, but this is optional. + +Supported KV pairs for add/edit commands: + +name=activity name (e.g. Crucible) +mode=activity mode (e.g. Iron Banner) +min_fireteam_size=n +max_fireteam_size=n +min_light=n +min_level=n From bd28cf943b0ba223e53b7124a3c478d73a79dc60 Mon Sep 17 00:00:00 2001 From: Berkus Decker Date: Wed, 20 Aug 2025 14:40:46 +0300 Subject: [PATCH 12/26] =?UTF-8?q?=F0=9F=91=BD=20Update=20Cancel=20command?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bot/src/commands/cancel_command.rs | 146 +++++++++++++++------------- bot/templates/cancel/disbanded.tera | 1 + bot/templates/cancel/left.tera | 2 + bot/templates/cancel/usage.tera | 4 + 4 files changed, 84 insertions(+), 69 deletions(-) create mode 100644 bot/templates/cancel/disbanded.tera create mode 100644 bot/templates/cancel/left.tera create mode 100644 bot/templates/cancel/usage.tera diff --git a/bot/src/commands/cancel_command.rs b/bot/src/commands/cancel_command.rs index 55d917d4..06f28136 100644 --- a/bot/src/commands/cancel_command.rs +++ b/bot/src/commands/cancel_command.rs @@ -2,121 +2,129 @@ use { crate::{ actors::bot_actor::ActorUpdateMessage, commands::{decapitalize, match_command, validate_username}, - models::PlannedActivity, - BotCommand, + render_template_or_err, }, chrono::Duration, - diesel_derives_traits::Model, + culpa::throws, + entity::{plannedactivities, plannedactivitymembers}, + kameo::message::Context, libbot::datetime::{format_start_time, reference_date}, - riker::actors::Tell, + sea_orm::{ColumnTrait, EntityTrait, QueryFilter}, }; -command_actor!(CancelCommand, [ActorUpdateMessage]); - -impl CancelCommand { - fn send_reply(&self, message: &ActorUpdateMessage, reply: S) - where - S: Into, - { - self.bot_ref.tell( - SendMessageReply(reply.into(), message.clone(), Format::Plain, Notify::Off), - None, - ); - } - - fn usage(&self, message: &ActorUpdateMessage) { - self.send_reply( - message, - "To leave a fireteam provide fireteam id -Fireteam IDs are available from output of /list command.", - ); - } -} +command_actor!(CancelCommand, "cancel", "Leave joined activity"); -impl BotCommand for CancelCommand { - fn prefix() -> &'static str { - "/cancel" - } - - fn description() -> &'static str { - "Leave joined activity" - } -} +impl Message for CancelCommand { + type Reply = anyhow::Result<()>; -impl Receive for CancelCommand { - type Msg = CancelCommandMsg; - - fn receive(&mut self, _ctx: &Context, message: ActorUpdateMessage, _sender: Sender) { + #[throws(anyhow::Error)] + async fn handle(&mut self, message: ActorUpdateMessage, _ctx: &mut Context) { if let (Some(_), activity_id) = match_command(message.update.text(), Self::prefix(), &self.bot_name) { if activity_id.is_none() { - return self.usage(&message); + return self.usage(&message).await; } let activity_id = activity_id.unwrap().parse::(); if activity_id.is_err() { - return self.usage(&message); + return self.usage(&message).await; } let activity_id = activity_id.unwrap(); + let connection = self.connection(); - if let Some(guardian) = validate_username(&self.bot_ref, &message, &connection) { - let planned = PlannedActivity::find_one(&connection, &activity_id) - .expect("Failed to run SQL"); + if let Some(guardian) = validate_username(&self.bot_ref, &message, connection).await { + let planned = plannedactivities::Entity::find_by_id(activity_id) + .one(connection) + .await + .expect("❌ Failed to run SQL"); if planned.is_none() { return self - .send_reply(&message, format!("Activity {} was not found.", activity_id)); + .send_reply( + &message, + format!("❌ Activity {} was not found.", activity_id), + ) + .await; } let planned = planned.unwrap(); - let member = planned.find_member(&connection, Some(&guardian)); + let member = plannedactivitymembers::Entity::find() + .filter(plannedactivitymembers::Column::PlannedActivityId.eq(activity_id)) + .filter(plannedactivitymembers::Column::UserId.eq(guardian.id)) + .one(connection) + .await + .expect("❌ Failed to find member"); if member.is_none() { - return self.send_reply(&message, "You are not part of this group."); + return self + .send_reply(&message, "❌ You are not a part of this group.") + .await; } - if planned.start < reference_date() - Duration::hours(1) { - return self.send_reply(&message, "You can not leave past activities."); + if chrono::DateTime::::from(planned.start) + < reference_date() - Duration::hours(1) + { + return self + .send_reply(&message, "❌ You can not leave activities from the past.") + .await; } let member = member.unwrap(); - if member.destroy(&connection).is_err() { - return self.send_reply(&message, "Failed to remove group member"); + // Delete the member + if plannedactivitymembers::Entity::delete_by_id(member.id) + .exec(connection) + .await + .is_err() + { + return self + .send_reply(&message, "❌ Failed to remove group member".to_string()) + .await; } - let act_name = planned.activity(&connection).format_name(); - let act_time = decapitalize(&format_start_time(planned.start, reference_date())); - - let suffix = if planned.members(&connection).is_empty() { - if planned.destroy(&connection).is_err() { - return self.send_reply(&message, "Failed to remove planned activity"); + let act_name = planned.activity(connection).await?.unwrap().format_name(); + let act_time = decapitalize(&format_start_time( + chrono::DateTime::::from(planned.start), + reference_date(), + )); + + let suffix = if planned.members_count(connection).await? == 0 { + if plannedactivities::Entity::delete_by_id(activity_id) + .exec(connection) + .await + .is_err() + { + return self + .send_reply( + &message, + "❌ Failed to remove planned activity".to_string(), + ) + .await; } - "This fireteam is disbanded and can no longer be joined.".into() + render_template_or_err!("cancel/disbanded") } else { format!( - "{} are going -{}", - planned.members_formatted_list(&connection), - planned.join_prompt(&connection) + "{} are going\n{}", + planned.members_formatted_list(connection).await?, + planned.join_prompt(connection).await? ) }; self.send_reply( &message, - format!( - "{guarName} has left {actName} group {actTime} -{suffix}", - guarName = guardian.format_name(), - actName = act_name, - actTime = act_time, - suffix = suffix + render_template_or_err!( + "cancel/left", + ("guardian_name" => &guardian.telegram_name), + ("activity_name" => &act_name), + ("activity_time" => &act_time), + ("suffix" => &suffix) ), - ); + ) + .await; } } } diff --git a/bot/templates/cancel/disbanded.tera b/bot/templates/cancel/disbanded.tera new file mode 100644 index 00000000..194e6611 --- /dev/null +++ b/bot/templates/cancel/disbanded.tera @@ -0,0 +1 @@ +This fireteam is disbanded and can no longer be joined. diff --git a/bot/templates/cancel/left.tera b/bot/templates/cancel/left.tera new file mode 100644 index 00000000..93520e96 --- /dev/null +++ b/bot/templates/cancel/left.tera @@ -0,0 +1,2 @@ +{{guardian_name}} has left {{activity_name}} group {{activity_time}} +{{suffix}} diff --git a/bot/templates/cancel/usage.tera b/bot/templates/cancel/usage.tera new file mode 100644 index 00000000..5f23c2fc --- /dev/null +++ b/bot/templates/cancel/usage.tera @@ -0,0 +1,4 @@ +Cancel command help: + +/cancel ActivityID + Leave planned activity by its number. From be26fcb391fd1a094b7fab0d1efbec000beddcf3 Mon Sep 17 00:00:00 2001 From: Berkus Decker Date: Sat, 23 Aug 2025 02:05:17 +0300 Subject: [PATCH 13/26] =?UTF-8?q?=F0=9F=91=BD=20Update=20ChatId=20command?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bot/src/actors/bot_actor.rs | 2 +- bot/src/commands/chatid_command.rs | 42 ++++++++---------------------- 2 files changed, 12 insertions(+), 32 deletions(-) diff --git a/bot/src/actors/bot_actor.rs b/bot/src/actors/bot_actor.rs index dc3ae894..5c94f303 100644 --- a/bot/src/actors/bot_actor.rs +++ b/bot/src/actors/bot_actor.rs @@ -131,7 +131,7 @@ impl Actor for BotActor { new_command!(ActivitiesCommand, bot_actor); new_command!(CancelCommand, bot_actor); - new_command!(ChatidCommand, bot_actor); + new_command!(ChatIdCommand, bot_actor); new_command!(D1weekCommand, bot_actor); new_command!(D2weekCommand, bot_actor); new_command!(EditCommand, bot_actor); diff --git a/bot/src/commands/chatid_command.rs b/bot/src/commands/chatid_command.rs index 96cd519b..d1552157 100644 --- a/bot/src/commands/chatid_command.rs +++ b/bot/src/commands/chatid_command.rs @@ -1,38 +1,18 @@ -use { - crate::{ - actors::bot_actor::{ActorUpdateMessage, Format, Notify, SendMessageReply}, - commands::match_command, - BotCommand, - }, - riker::actors::Tell, -}; +use crate::{actors::bot_actor::ActorUpdateMessage, commands::match_command}; -command_actor!(ChatidCommand, [ActorUpdateMessage]); +command_actor!(ChatIdCommand, "chatid", "Figure out the numeric chat ID"); -impl BotCommand for ChatidCommand { - fn prefix() -> &'static str { - "/chatid" - } - - fn description() -> &'static str { - "Figure out the numeric chat ID" - } -} - -impl Receive for ChatidCommand { - type Msg = ChatidCommandMsg; +impl Message for ChatIdCommand { + type Reply = (); - fn receive(&mut self, _ctx: &Context, msg: ActorUpdateMessage, _sender: Sender) { + async fn handle( + &mut self, + msg: ActorUpdateMessage, + _ctx: &mut Context, + ) -> Self::Reply { if let (Some(_), _) = match_command(msg.update.text(), Self::prefix(), &self.bot_name) { - self.bot_ref.tell( - SendMessageReply( - format!("ChatId: {}", msg.update.chat.id), - msg, - Format::Plain, - Notify::Off, - ), - None, - ); + self.send_reply(&msg, format!("✅ Current chat id: {}", msg.update.chat.id)) + .await; } } } From ecd6a1d427aa3455c2d82751b844abbc7c28bfbd Mon Sep 17 00:00:00 2001 From: Berkus Decker Date: Wed, 20 Aug 2025 15:21:51 +0300 Subject: [PATCH 14/26] =?UTF-8?q?=F0=9F=91=BD=20Update=20D2Week=20command?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bot/src/commands/d2week_command.rs | 35 +++++++++++------------------- 1 file changed, 13 insertions(+), 22 deletions(-) diff --git a/bot/src/commands/d2week_command.rs b/bot/src/commands/d2week_command.rs index 8d6d1f0c..7f05771a 100644 --- a/bot/src/commands/d2week_command.rs +++ b/bot/src/commands/d2week_command.rs @@ -1,34 +1,25 @@ use { crate::{ - actors::bot_actor::{ActorUpdateMessage, Format, Notify, SendMessageReply}, + actors::bot_actor::{ActorUpdateMessage, Format}, commands::match_command, - BotCommand, }, + kameo::message::Context, libbot::services::destiny_schedule::this_week_in_d2, - riker::actors::Tell, }; -command_actor!(D2weekCommand, [ActorUpdateMessage]); +command_actor!(D2weekCommand, "d2week", "Show current Destiny 2 week"); -impl BotCommand for D2weekCommand { - fn prefix() -> &'static str { - "/d2week" - } - - fn description() -> &'static str { - "Show current Destiny 2 week" - } -} - -impl Receive for D2weekCommand { - type Msg = D2weekCommandMsg; +impl Message for D2weekCommand { + type Reply = (); - fn receive(&mut self, _ctx: &Context, msg: ActorUpdateMessage, _sender: Sender) { - if let (Some(_), _) = match_command(msg.update.text(), Self::prefix(), &self.bot_name) { - self.bot_ref.tell( - SendMessageReply(this_week_in_d2(), msg, Format::Markdown, Notify::Off), - None, - ); + async fn handle( + &mut self, + message: ActorUpdateMessage, + _ctx: &mut Context, + ) -> Self::Reply { + if let (Some(_), _) = match_command(message.update.text(), Self::prefix(), &self.bot_name) { + self.send_reply_with_format(&message, this_week_in_d2(), Format::Markdown) + .await; } } } From 4a43723977a7cf7b4a922f1f9c1580d05145b399 Mon Sep 17 00:00:00 2001 From: Berkus Decker Date: Wed, 20 Aug 2025 15:21:57 +0300 Subject: [PATCH 15/26] =?UTF-8?q?=F0=9F=91=BD=20Update=20DWeek=20command?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bot/src/commands/dweek_command.rs | 35 ++++++++++++------------------- 1 file changed, 13 insertions(+), 22 deletions(-) diff --git a/bot/src/commands/dweek_command.rs b/bot/src/commands/dweek_command.rs index 8d0b7737..e45097d8 100644 --- a/bot/src/commands/dweek_command.rs +++ b/bot/src/commands/dweek_command.rs @@ -1,34 +1,25 @@ use { crate::{ - actors::bot_actor::{ActorUpdateMessage, Format, Notify, SendMessageReply}, + actors::bot_actor::{ActorUpdateMessage, Format}, commands::match_command, - BotCommand, }, + kameo::message::Context, libbot::services::destiny_schedule::this_week_in_d1, - riker::actors::Tell, }; -command_actor!(D1weekCommand, [ActorUpdateMessage]); +command_actor!(D1weekCommand, "dweek", "Show current Destiny 1 week"); -impl BotCommand for D1weekCommand { - fn prefix() -> &'static str { - "/dweek" - } - - fn description() -> &'static str { - "Show current Destiny 1 week" - } -} - -impl Receive for D1weekCommand { - type Msg = D1weekCommandMsg; +impl Message for D1weekCommand { + type Reply = (); - fn receive(&mut self, _ctx: &Context, msg: ActorUpdateMessage, _sender: Sender) { - if let (Some(_), _) = match_command(msg.update.text(), Self::prefix(), &self.bot_name) { - self.bot_ref.tell( - SendMessageReply(this_week_in_d1(), msg, Format::Markdown, Notify::Off), - None, - ); + async fn handle( + &mut self, + message: ActorUpdateMessage, + _ctx: &mut Context, + ) -> Self::Reply { + if let (Some(_), _) = match_command(message.update.text(), Self::prefix(), &self.bot_name) { + self.send_reply_with_format(&message, this_week_in_d1(), Format::Markdown) + .await; } } } From b1d084bb9c6b3d467035ccc36ed8f925b59a1cc3 Mon Sep 17 00:00:00 2001 From: Berkus Decker Date: Wed, 20 Aug 2025 15:03:43 +0300 Subject: [PATCH 16/26] =?UTF-8?q?=F0=9F=91=BD=20Update=20Edit=20command?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bot/src/commands/edit_command.rs | 204 ++++++++++++++----------------- bot/templates/edit/usage.tera | 13 ++ 2 files changed, 105 insertions(+), 112 deletions(-) create mode 100644 bot/templates/edit/usage.tera diff --git a/bot/src/commands/edit_command.rs b/bot/src/commands/edit_command.rs index 50274bf4..1072d948 100644 --- a/bot/src/commands/edit_command.rs +++ b/bot/src/commands/edit_command.rs @@ -1,180 +1,160 @@ use { crate::{ - bot_actor::{ActorUpdateMessage, Format, Notify, SendMessageReply}, + actors::bot_actor::ActorUpdateMessage, commands::{match_command, validate_username}, - models::{ActivityShortcut, PlannedActivity}, - BotCommand, }, chrono::{prelude::*, Duration}, - chrono_english::{parse_date_string, Dialect}, - chrono_tz::Europe::Moscow, - diesel_derives_traits::Model, - riker::actors::Tell, + entity::{activityshortcuts, plannedactivities}, + kameo::message::Context, libbot::datetime::reference_date, + sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, Set}, }; -command_actor!(EditCommand, [ActorUpdateMessage]); - -impl EditCommand { - fn send_reply(&self, message: &ActorUpdateMessage, reply: S) - where - S: Into, - { - self.bot_ref.tell( - SendMessageReply(reply.into(), message.clone(), Format::Plain, Notify::Off), - None, - ); - } - - fn usage(&self, message: &ActorUpdateMessage) { - self.send_reply( - message, - "Usage: - -ActivityIDs are available from output of /list command. - -/edit ActivityID time - Change activity time to a new one. Accepted time spec - is the same as in /lfg command. - -/edit ActivityID details - Replaces old /details command. - To update activity details enter text, - to delete details use `delete` instead of text. - -/edit ActivityID activity - Change type of activity, list of shortcuts - is available from output of /activities command", - ); - } -} - -impl BotCommand for EditCommand { - fn prefix() -> &'static str { - "/edit" - } +command_actor!(EditCommand, "edit", "Edit existing activity"); - fn description() -> &'static str { - "Edit existing activity" - } -} +impl Message for EditCommand { + type Reply = (); -impl Receive for EditCommand { - type Msg = EditCommandMsg; - - fn receive(&mut self, _ctx: &Context, message: ActorUpdateMessage, _sender: Sender) { + async fn handle( + &mut self, + message: ActorUpdateMessage, + _ctx: &mut Context, + ) -> Self::Reply { if let (Some(_), args) = match_command(message.update.text(), Self::prefix(), &self.bot_name) { - let connection = self.connection(); - if args.is_none() { - return self.usage(&message); + return self.usage(&message).await; } let args = args.unwrap(); let args: Vec<_> = args.splitn(3, ' ').collect(); if args.len() != 3 { - return self.usage(&message); + return self.usage(&message).await; } - if validate_username(&self.bot_ref, &message, &connection).is_some() { + let connection = self.connection(); + + if validate_username(&self.bot_ref, &message, connection) + .await + .is_some() + { let id = args[0].parse::(); if id.is_err() { - return self.send_reply(&message, "ActivityID must be a number"); + return self + .send_reply(&message, "❌ Activity ID must be a number") + .await; } let id = id.unwrap(); - let planned = - PlannedActivity::find_one(&connection, &id).expect("Failed to run SQL"); + let planned = plannedactivities::Entity::find_by_id(id) + .one(connection) + .await + .expect("❌ Failed to run SQL"); if planned.is_none() { - return self.send_reply(&message, format!("Activity {} was not found.", id)); + return self + .send_reply(&message, format!("❌ Activity {} was not found.", id)) + .await; } - let mut planned = planned.unwrap(); + let planned = planned.unwrap(); if planned.start < reference_date() - Duration::hours(1) { - return self.send_reply(&message, "You can not edit past activities."); + return self + .send_reply(&message, "❌ You can not edit past activities.") + .await; } match args[1] { "time" => { let timespec = args[2]; - let start_time = parse_date_string( - timespec, - Local::now().with_timezone(&Moscow), - Dialect::Uk, - ); - // @todo Honor TELEGRAM_BOT_TIMEZONE envvar - - if start_time.is_err() { - return self.send_reply( - &message, - format!("Failed to parse time {}", timespec), - ); - } - - // ...then convert back to UTC. - let start_time = start_time.unwrap().with_timezone(&Utc); + let start_time = match libbot::datetime::parse_time_spec(timespec) { + Ok(start) => start, //.and_utc(), + Err(_) => { + return self + .send_reply( + &message, + format!("❌ Failed to parse time {}", timespec), + ) + .await; + } + }; log::info!("...parsed `{:?}`", start_time); - if planned.start < reference_date() - Duration::hours(1) { - return self.send_reply( - &message, - "You can not set activity time in the past.", - ); + if start_time < reference_date() - Duration::hours(1) { + return self + .send_reply( + &message, + "❌ You can not set activity time in the past.", + ) + .await; } - planned.start = start_time; + let mut planned: plannedactivities::ActiveModel = planned.into(); + let offset = start_time.offset().fix(); + planned.start = Set(start_time.with_timezone(&offset)); - if planned.save(&connection).is_err() { - return self.send_reply(&message, "Failed to update start time."); + if planned.update(connection).await.is_err() { + return self + .send_reply(&message, "❌ Failed to update start time.") + .await; } - self.send_reply(&message, "Start time updated."); + self.send_reply(&message, "✅ Start time updated.").await; } "details" => { let description = args[2]; - planned.details = if description == "delete" { + let mut planned: plannedactivities::ActiveModel = planned.into(); + planned.details = Set(if description == "delete" { Some(String::new()) } else { Some(description.to_string()) - }; - if planned.save(&connection).is_err() { - return self.send_reply(&message, "Failed to update details."); + }); + + if planned.update(connection).await.is_err() { + return self + .send_reply(&message, "❌ Failed to update details.") + .await; } - self.send_reply(&message, "Details updated."); + self.send_reply(&message, "✅ Details updated.").await; } "activity" => { let activity = args[2]; - let act = ActivityShortcut::find_one_by_name(&connection, activity) - .expect("Failed to load Activity shortcut"); + let act = activityshortcuts::Entity::find() + .filter(activityshortcuts::Column::Name.eq(activity)) + .one(connection) + .await + .expect("❌ Failed to load Activity shortcut"); if act.is_none() { - self.send_reply( - &message, - format!( - "Activity {} was not found. Use /activities for a list.", - activity - ), - ); + return self + .send_reply( + &message, + format!( + "❌ Activity {} was not found. Use /activities for a list.", + activity + ), + ) + .await; } let act = act.unwrap(); + let mut planned: plannedactivities::ActiveModel = planned.into(); + planned.activity_id = Set(act.link); - planned.activity_id = act.link; - - if planned.save(&connection).is_err() { - return self.send_reply(&message, "Failed to update activity."); + if planned.update(connection).await.is_err() { + return self + .send_reply(&message, "❌ Failed to update activity type.") + .await; } - self.send_reply(&message, "Activity updated."); + self.send_reply(&message, "✅ Activity type updated.").await; } - x => { - self.send_reply(&message, format!("Unknown attribute {}", x)); + _ => { + self.usage(&message).await; } } } diff --git a/bot/templates/edit/usage.tera b/bot/templates/edit/usage.tera new file mode 100644 index 00000000..63ee3fae --- /dev/null +++ b/bot/templates/edit/usage.tera @@ -0,0 +1,13 @@ +Edit command help: + +/edit time + Change scheduled time for activity. Time format examples: + \"tomorrow at 21:00\" or \"Friday at 9 pm\" or \"21:00\" + +/edit details + Change details/description for activity. + Use 'delete' as description text to remove details. + +/edit activity + Change type of activity, list of shortcuts + is available from output of `/activities` command. From 37d078f217b917bc561ad89f9c737b3fa2599f8d Mon Sep 17 00:00:00 2001 From: Berkus Decker Date: Wed, 20 Aug 2025 17:34:40 +0300 Subject: [PATCH 17/26] =?UTF-8?q?=F0=9F=91=BD=20Update=20EditGuar=20comman?= =?UTF-8?q?d?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bot/src/commands/editguar_command.rs | 121 ++++++++---------- .../templates}/editguar/usage.tera | 0 2 files changed, 55 insertions(+), 66 deletions(-) rename {templates => bot/templates}/editguar/usage.tera (100%) diff --git a/bot/src/commands/editguar_command.rs b/bot/src/commands/editguar_command.rs index 40d93667..0d8e8648 100644 --- a/bot/src/commands/editguar_command.rs +++ b/bot/src/commands/editguar_command.rs @@ -2,53 +2,31 @@ use { crate::{ actors::bot_actor::ActorUpdateMessage, commands::{admin_check, guardian_lookup, match_command, validate_username}, - render_template, BotCommand, }, - riker::actors::Tell, + entity::guardians, + kameo::message::Context, + sea_orm::{ActiveModelTrait, Set}, }; -command_actor!(EditGuardianCommand, [ActorUpdateMessage]); +command_actor!( + EditGuardianCommand, + "editguar", + "Edit information about registered guardians" +); -impl EditGuardianCommand { - fn send_reply(&self, message: &ActorUpdateMessage, reply: S) - where - S: Into, - { - self.bot_ref.tell( - SendMessageReply(reply.into(), message.clone(), Format::Plain, Notify::Off), - None, - ); - } - - fn usage(&self, message: &ActorUpdateMessage) { - self.send_reply( - message, - render_template!("editguar/usage").expect("Failed to render editguar usage template"), - ); - } -} - -impl BotCommand for EditGuardianCommand { - fn prefix() -> &'static str { - "/editguar" - } - - fn description() -> &'static str { - "Edit information about registered guardians" - } -} - -impl Receive for EditGuardianCommand { - type Msg = EditGuardianCommandMsg; +impl Message for EditGuardianCommand { + type Reply = (); - fn receive(&mut self, _ctx: &Context, message: ActorUpdateMessage, _sender: Sender) { + async fn handle( + &mut self, + message: ActorUpdateMessage, + _ctx: &mut Context, + ) -> Self::Reply { if let (Some(_), args) = match_command(message.update.text(), Self::prefix(), &self.bot_name) { - let connection = self.connection(); - if args.is_none() { - return self.usage(&message); + return self.usage(&message).await; } // Split args in two or three: @@ -59,33 +37,37 @@ impl Receive for EditGuardianCommand { let args: Vec<&str> = args.splitn(3, ' ').collect(); if args.is_empty() || args.len() == 2 { - return self.usage(&message); + return self.usage(&message).await; } let name = args[0]; + let connection = self.connection(); + let guardian = if name == "my" { - let guardian = validate_username(&self.bot_ref, &message, &connection); + let guardian = validate_username(&self.bot_ref, &message, connection).await; if guardian.is_none() { - return; + return; // TODO: You are not registered } guardian.unwrap() } else { - let admin = admin_check(&self.bot_ref, &message, &connection); + let admin = admin_check(&self.bot_ref, &message, connection).await; if admin.is_none() { - return self.send_reply(&message, "You are not admin"); + return self.send_reply(&message, "❌ You are not admin").await; } - let guardian = guardian_lookup(name, &connection); + let guardian = guardian_lookup(name, connection).await; let guardian = match guardian { Ok(Some(guardian)) => Some(guardian), Ok(None) => { - self.send_reply(&message, format!("Guardian {} was not found.", &name)); + self.send_reply(&message, format!("❌ Guardian {} was not found.", &name)) + .await; None } Err(_) => { - self.send_reply(&message, "Error querying guardian by name."); + self.send_reply(&message, "❌ Error querying guardian by name.") + .await; None } }; @@ -103,7 +85,7 @@ impl Receive for EditGuardianCommand { .clone() .map(|s| format!("[{}] ", s)) .unwrap_or_default(), - name = guardian.format_name(), + name = guardian.telegram_name, email = guardian.email.clone().unwrap_or("".into()), admin = if guardian.is_superadmin { "" @@ -113,44 +95,51 @@ impl Receive for EditGuardianCommand { "" }, ); - return self.send_reply(&message, info); + return self.send_reply(&message, info).await; } let command = args[1]; let value = args[2]; - let mut guardian = guardian; - - use diesel_derives_traits::Model; - match command { "psn" => { - guardian.psn_name = value.into(); - guardian.save(&connection).expect("Failed to update PSN"); - self.send_reply(&message, "Updated guardian PSN"); + let mut guardian: guardians::ActiveModel = guardian.into(); + guardian.psn_name = Set(value.to_string()); + if guardian.update(connection).await.is_err() { + return self.send_reply(&message, "❌ Failed to update PSN").await; + } + self.send_reply(&message, "✅ PSN updated successfully") + .await; } "clan" => { - let value = if value == "delete" { + let clan_value = if value == "delete" { None } else { - Some(value.into()) + Some(value.to_string()) }; - guardian.psn_clan = value; - guardian.save(&connection).expect("Failed to update clan"); - self.send_reply(&message, "Updated guardian clan"); + let mut guardian: guardians::ActiveModel = guardian.into(); + guardian.psn_clan = Set(clan_value); + if guardian.update(connection).await.is_err() { + return self.send_reply(&message, "❌ Failed to update clan").await; + } + self.send_reply(&message, "✅ Updated guardian clan").await; } "email" => { - let value = if value == "delete" { + let email_value = if value == "delete" { None } else { - Some(value.into()) + Some(value.to_string()) }; - guardian.email = value; - guardian.save(&connection).expect("Failed to update email"); - self.send_reply(&message, "Updated guardian email"); + let mut guardian: guardians::ActiveModel = guardian.into(); + guardian.email = Set(email_value); + if guardian.update(connection).await.is_err() { + return self.send_reply(&message, "❌ Failed to update email").await; + } + self.send_reply(&message, "✅ Updated guardian email").await; } _ => { - self.send_reply(&message, "Unknown information field"); + self.send_reply(&message, "⁉️ Unknown information field") + .await; } } } diff --git a/templates/editguar/usage.tera b/bot/templates/editguar/usage.tera similarity index 100% rename from templates/editguar/usage.tera rename to bot/templates/editguar/usage.tera From dd8f71c04e6a9daf1f104eb92c62f43eeee1fc6c Mon Sep 17 00:00:00 2001 From: Berkus Decker Date: Wed, 20 Aug 2025 15:22:29 +0300 Subject: [PATCH 18/26] =?UTF-8?q?=F0=9F=91=BD=20Update=20Help=20command?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bot/src/commands/help_command.rs | 25 ++++++++----------------- 1 file changed, 8 insertions(+), 17 deletions(-) diff --git a/bot/src/commands/help_command.rs b/bot/src/commands/help_command.rs index a0a5f21e..ab71d84b 100644 --- a/bot/src/commands/help_command.rs +++ b/bot/src/commands/help_command.rs @@ -2,29 +2,20 @@ use { crate::{ actors::bot_actor::{ActorUpdateMessage, ListCommands}, commands::match_command, - BotCommand, }, - riker::actors::Tell, + culpa::throws, + kameo::message::Context, }; -command_actor!(HelpCommand, [ActorUpdateMessage]); +command_actor!(HelpCommand, "help", "List available commands"); -impl BotCommand for HelpCommand { - fn prefix() -> &'static str { - "/help" - } - - fn description() -> &'static str { - "List available commands" - } -} - -impl Receive for HelpCommand { - type Msg = HelpCommandMsg; +impl Message for HelpCommand { + type Reply = anyhow::Result<()>; - fn receive(&mut self, _ctx: &Context, message: ActorUpdateMessage, _sender: Sender) { + #[throws(anyhow::Error)] + async fn handle(&mut self, message: ActorUpdateMessage, _ctx: &mut Context) { if let (Some(_), _) = match_command(message.update.text(), Self::prefix(), &self.bot_name) { - self.bot_ref.tell(ListCommands(message), None); + self.bot_ref.tell(ListCommands(message)).await?; } } } From b28f27a293bb32add3c88212784d6590c6636767 Mon Sep 17 00:00:00 2001 From: Berkus Decker Date: Wed, 20 Aug 2025 17:34:19 +0300 Subject: [PATCH 19/26] =?UTF-8?q?=F0=9F=91=BD=20Update=20Join=20command?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bot/src/commands/join_command.rs | 142 +++++++++++++++---------------- bot/templates/join/joined.tera | 3 + bot/templates/join/usage.tera | 6 ++ templates/join/joined.tera | 3 - templates/join/usage.tera | 2 - 5 files changed, 77 insertions(+), 79 deletions(-) create mode 100644 bot/templates/join/joined.tera create mode 100644 bot/templates/join/usage.tera delete mode 100644 templates/join/joined.tera delete mode 100644 templates/join/usage.tera diff --git a/bot/src/commands/join_command.rs b/bot/src/commands/join_command.rs index a1381ef5..7a9ebdf9 100644 --- a/bot/src/commands/join_command.rs +++ b/bot/src/commands/join_command.rs @@ -1,119 +1,113 @@ use { crate::{ - bot_actor::{ActorUpdateMessage, Format, Notify, SendMessageReply}, + actors::bot_actor::ActorUpdateMessage, commands::{decapitalize, match_command, validate_username}, - datetime::{format_start_time, reference_date}, - models::{NewPlannedActivityMember, PlannedActivity}, - render_template, BotCommand, + render_template_or_err, }, chrono::Duration, - diesel_derives_traits::{Model, NewModel}, - riker::actors::Tell, + culpa::throws, + entity::{plannedactivitymembers, prelude::*}, + kameo::message::Context, + libbot::datetime::{format_start_time, reference_date}, + sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, Set}, }; -command_actor!(JoinCommand, [ActorUpdateMessage]); - -impl JoinCommand { - fn send_reply(&self, message: &ActorUpdateMessage, reply: S) - where - S: Into, - { - self.bot_ref.tell( - SendMessageReply(reply.into(), message.clone(), Format::Plain, Notify::Off), - None, - ); - } - - fn usage(&self, message: &ActorUpdateMessage) { - self.send_reply( - message, - render_template!("join/usage").expect("Failed to render join usage template"), - ); - } -} +command_actor!(JoinCommand, "join", "Join existing activity from the list"); -impl BotCommand for JoinCommand { - fn prefix() -> &'static str { - "/join" - } - - fn description() -> &'static str { - "Join existing activity from the list" - } -} +impl Message for JoinCommand { + type Reply = anyhow::Result<()>; -impl Receive for JoinCommand { - type Msg = JoinCommandMsg; - - fn receive(&mut self, _ctx: &Context, message: ActorUpdateMessage, _sender: Sender) { + #[throws(anyhow::Error)] + async fn handle(&mut self, message: ActorUpdateMessage, _ctx: &mut Context) { if let (Some(_), activity_id) = match_command(message.update.text(), Self::prefix(), &self.bot_name) { if activity_id.is_none() { - return self.usage(&message); + return self.usage(&message).await; } let activity_id = activity_id.unwrap().parse::(); if activity_id.is_err() { - return self.usage(&message); + return self.usage(&message).await; } let activity_id = activity_id.unwrap(); + let connection = self.connection(); - if let Some(guardian) = validate_username(&self.bot_ref, &message, &connection) { - let planned = PlannedActivity::find_one(&connection, &activity_id) - .expect("Failed to run SQL"); + if let Some(guardian) = validate_username(&self.bot_ref, &message, connection).await { + let found = PlannedActivities::find_by_id(activity_id) + .find_also_related(Activities) + .one(connection) + .await?; - if planned.is_none() { + if found.is_none() { return self - .send_reply(&message, format!("Activity {} was not found.", activity_id)); + .send_reply( + &message, + format!("❌ Activity {} was not found.", activity_id), + ) + .await; } - let planned = planned.unwrap(); + let (planned, activity) = found.unwrap(); - let member = planned.find_member(&connection, Some(&guardian)); + let member = plannedactivitymembers::Entity::find() + .filter(plannedactivitymembers::Column::PlannedActivityId.eq(activity_id)) + .filter(plannedactivitymembers::Column::UserId.eq(guardian.id)) + .one(connection) + .await + .expect("❌ Failed to find member"); if member.is_some() { - return self.send_reply(&message, "You are already part of this group."); + return self + .send_reply(&message, "✅ You are already part of this group.") + .await; } - if planned.is_full(&connection) { - return self.send_reply(&message, "This activity group is full."); + if planned.is_full(connection).await? { + return self + .send_reply(&message, "❌ This activity group is full.") + .await; } if planned.start < reference_date() - Duration::hours(1) { - return self.send_reply(&message, "You can not join past activities."); + return self + .send_reply(&message, "❌ You can not join past activities.") + .await; } - let planned_activity_member = NewPlannedActivityMember { - user_id: guardian.id, - planned_activity_id: planned.id, - added: reference_date(), + let planned_activity_member = plannedactivitymembers::ActiveModel { + user_id: Set(guardian.id), + planned_activity_id: Set(planned.id), + added: Set(reference_date().into()), + ..Default::default() }; - planned_activity_member - .save(&connection) - .expect("Unexpected error saving group joiner"); + if planned_activity_member.insert(connection).await.is_err() { + return self + .send_reply(&message, "🐛 Unexpected error saving group joiner") + .await; + } - // join/joined template + // join/joined template - TODO: format new member correctly (with icon etc) let guar_name = guardian.to_string(); - let act_name = planned.activity(&connection).format_name(); - let act_time = decapitalize(&format_start_time(planned.start, reference_date())); - let other_guars = planned.members_formatted_list(&connection); - let join_prompt = planned.join_prompt(&connection); + let act_name = activity.expect("❌ REASONS").format_name(); + let act_time = + decapitalize(&format_start_time(planned.start.into(), reference_date())); + let other_guars = planned.members_formatted_list(connection).await?; + let join_prompt = planned.join_prompt(connection).await?; - let text = render_template!( + let text = render_template_or_err!( "join/joined", - ("guarName", &guar_name), - ("actName", &act_name), - ("actTime", &act_time), - ("otherGuars", &other_guars), - ("joinPrompt", &join_prompt) - ) - .expect("Failed to render join joined template"); - - self.send_reply(&message, text); + ("guardian" => &guar_name), + ("activity_name" => &act_name), + ("activity_time" => &act_time), + ("other_guardians" => &other_guars), + ("join_prompt" => &join_prompt) + ); + + self.send_reply(&message, text).await; } } } diff --git a/bot/templates/join/joined.tera b/bot/templates/join/joined.tera new file mode 100644 index 00000000..d6fb5c14 --- /dev/null +++ b/bot/templates/join/joined.tera @@ -0,0 +1,3 @@ +{{guardian}} has joined {{activity_name}} group {{activity_time}} +{{other_guardians}} are going +{{join_prompt}}{# join link and a few ifs? #} diff --git a/bot/templates/join/usage.tera b/bot/templates/join/usage.tera new file mode 100644 index 00000000..dc0b6fe8 --- /dev/null +++ b/bot/templates/join/usage.tera @@ -0,0 +1,6 @@ +Join planned activity by its number + +Activity IDs are available from the output of `/list` command. + +`/join ActivityID` + Join planned activity by its number. diff --git a/templates/join/joined.tera b/templates/join/joined.tera deleted file mode 100644 index 71e629e7..00000000 --- a/templates/join/joined.tera +++ /dev/null @@ -1,3 +0,0 @@ -{{guarName}} has joined {{actName}} group {{actTime}} -{{otherGuars}} are going -{{joinPrompt}}{# join link and a few ifs? #} diff --git a/templates/join/usage.tera b/templates/join/usage.tera deleted file mode 100644 index e60af32a..00000000 --- a/templates/join/usage.tera +++ /dev/null @@ -1,2 +0,0 @@ -To join a fireteam provide fireteam id -Fireteam IDs are available from the output of `/list` command. From 102b9fac4f5bd1456ad3620acd7e7cdc99c694e8 Mon Sep 17 00:00:00 2001 From: Berkus Decker Date: Wed, 20 Aug 2025 15:04:15 +0300 Subject: [PATCH 20/26] =?UTF-8?q?=F0=9F=91=BD=20Update=20LFG=20command?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bot/src/commands/lfg_command.rs | 191 ++++++++++++++------------------ bot/templates/lfg/created.tera | 2 + bot/templates/lfg/usage.tera | 8 ++ 3 files changed, 91 insertions(+), 110 deletions(-) create mode 100644 bot/templates/lfg/created.tera create mode 100644 bot/templates/lfg/usage.tera diff --git a/bot/src/commands/lfg_command.rs b/bot/src/commands/lfg_command.rs index 360f6b92..711ff781 100644 --- a/bot/src/commands/lfg_command.rs +++ b/bot/src/commands/lfg_command.rs @@ -1,65 +1,32 @@ use { crate::{ - actors::bot_actor::{ActorUpdateMessage, Format, Notify, SendMessageReply}, + actors::bot_actor::ActorUpdateMessage, commands::{match_command, validate_username}, - models::{Activity, ActivityShortcut, NewPlannedActivity, NewPlannedActivityMember}, - BotCommand, + render_template_or_err, }, - chrono::prelude::*, - chrono_english::{parse_date_string, Dialect}, - chrono_tz::Europe::Moscow, - diesel::{self, prelude::*}, - diesel_derives_traits::{Model, NewModel}, + entity::{activities, activityshortcuts, plannedactivities, plannedactivitymembers}, + kameo::message::Context, libbot::datetime::{format_start_time, reference_date}, - riker::actors::Tell, + sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, Set}, }; -command_actor!(LfgCommand, [ActorUpdateMessage]); - -impl LfgCommand { - fn send_reply(&self, message: &ActorUpdateMessage, reply: S, format: Format) - where - S: Into, - { - self.bot_ref.tell( - SendMessageReply(reply.into(), message.clone(), format, Notify::Off), - None, - ); - } - - fn usage(&self, message: &ActorUpdateMessage) { - self.send_reply( - message, - "LFG usage: /lfg activity YYYY-MM-DD HH:MM -For a list of activity codes: /activities -Example: /lfg kf 2018-09-10 23:00 -Times are in Moscow (MSK) timezone.", - Format::Html, - ); - } -} - -impl BotCommand for LfgCommand { - fn prefix() -> &'static str { - "/lfg" - } +command_actor!(LfgCommand, "lfg", "Create a new Looking For Group event"); - fn description() -> &'static str { - "Create a new Looking For Group event" - } -} - -impl Receive for LfgCommand { - type Msg = LfgCommandMsg; +impl Message for LfgCommand { + type Reply = (); - fn receive(&mut self, _ctx: &Context, message: ActorUpdateMessage, _sender: Sender) { + async fn handle( + &mut self, + message: ActorUpdateMessage, + _ctx: &mut Context, + ) -> Self::Reply { if let (Some(_), args) = match_command(message.update.text(), Self::prefix(), &self.bot_name) { log::info!("args are {:?}", args); if args.is_none() { - return self.usage(&message); + return self.usage(&message).await; } // Split args in two: @@ -68,96 +35,100 @@ impl Receive for LfgCommand { let args = args.unwrap(); let args: Vec<&str> = args.splitn(2, ' ').collect(); - if args.len() < 2 { - return self.usage(&message); + if args.len() != 2 { + return self.usage(&message).await; } let activity = args[0]; let timespec = args[1]; - let connection = self.connection(); log::info!("Adding activity `{}` at `{}`", &activity, ×pec); - if let Some(guardian) = validate_username(&self.bot_ref, &message, &connection) { - let act = ActivityShortcut::find_one_by_name(&connection, activity) - .expect("Failed to load Activity shortcut"); + let connection = self.connection(); + + if let Some(guardian) = validate_username(&self.bot_ref, &message, connection).await { + let act = activityshortcuts::Entity::find() + .filter(activityshortcuts::Column::Name.eq(activity)) + .one(connection) + .await + .expect("❌ Failed to load Activity shortcut"); if act.is_none() { - return self.send_reply( - &message, - format!( - "Activity {} was not found. Use /activities to see the list.", - activity - ), - Format::Plain, - ); + return self + .send_reply( + &message, + format!( + "❌ Activity {} was not found. Use /activities to see the list.", + activity + ), + ) + .await; } // Parse input in MSK timezone... - let start_time = - parse_date_string(timespec, Local::now().with_timezone(&Moscow), Dialect::Uk); + let start_time = libbot::datetime::parse_time_spec(timespec); // @todo Honor TELEGRAM_BOT_TIMEZONE envvar if start_time.is_err() { - return self.send_reply( - &message, - format!("Failed to parse time {}", timespec), - Format::Plain, - ); + return self + .send_reply(&message, format!("❌ Failed to parse time {}", timespec)) + .await; } // ...then convert back to UTC. - let start_time = start_time.unwrap().with_timezone(&Utc); + let start_time = start_time.unwrap(); //.and_utc(); let act = act.unwrap(); log::info!("...parsed `{:?}`", start_time); - let planned_activity = NewPlannedActivity { - author_id: guardian.id, - activity_id: act.link, - start: start_time, - }; - - use diesel::result::Error; - - connection - .transaction::<_, Error, _>(|| { - let planned_activity = planned_activity - .save(&connection) - .expect("Unexpected error saving LFG group"); + use chrono::Offset; + let offset = start_time.offset().fix(); - let planned_activity_member = NewPlannedActivityMember { - user_id: guardian.id, - planned_activity_id: planned_activity.id, - added: reference_date(), - }; + let planned_activity = plannedactivities::ActiveModel { + author_id: Set(guardian.id), + activity_id: Set(act.link), + start: Set(start_time.with_timezone(&offset)), + ..Default::default() + }; - planned_activity_member - .save(&connection) - .expect("Unexpected error saving LFG group creator"); + // Note: Simplified without transaction for now - let activity = Activity::find_one(&connection, &act.link) - .expect("Couldn't find linked activity") - .unwrap(); + let planned_activity = planned_activity + .insert(connection) + .await + .expect("❌ Unexpected error saving LFG group"); - self.send_reply( - &message, - format!( - "{guarName} is looking for {groupName} group {onTime} -{joinPrompt} -Enter `/edit{actId} details ` to specify more details about the event.", - guarName = guardian, - groupName = activity.format_name(), - onTime = format_start_time(start_time, reference_date()), - joinPrompt = planned_activity.join_prompt(&connection), - actId = planned_activity.id - ), - Format::Plain, - ); + let planned_activity_member = plannedactivitymembers::ActiveModel { + user_id: Set(guardian.id), + planned_activity_id: Set(planned_activity.id), + added: Set(reference_date().into()), + ..Default::default() + }; - Ok(()) - }) - .expect("never happens, but please implement error handling"); + planned_activity_member + .insert(connection) + .await + .expect("❌ Unexpected error saving LFG group creator"); + + let activity = activities::Entity::find_by_id(act.link) + .one(connection) + .await + .expect("❌ Couldn't find linked activity") + .unwrap(); + + let start_time = format_start_time(start_time.to_utc(), reference_date()); + + self.send_reply( + &message, + render_template_or_err!( + "lfg/created", + ("guarName" => &guardian.to_string()), + ("groupName" => &activity.format_name()), + ("onTime" => &start_time), + ("actId" => &planned_activity.id) + ), + ) + .await; } } } diff --git a/bot/templates/lfg/created.tera b/bot/templates/lfg/created.tera new file mode 100644 index 00000000..3663185e --- /dev/null +++ b/bot/templates/lfg/created.tera @@ -0,0 +1,2 @@ +{{guarName}} is looking for {{groupName}} group {{onTime}} +Enter `/edit{{actId}} details ` to specify more details about the event. diff --git a/bot/templates/lfg/usage.tera b/bot/templates/lfg/usage.tera new file mode 100644 index 00000000..a9685aed --- /dev/null +++ b/bot/templates/lfg/usage.tera @@ -0,0 +1,8 @@ +LFG usage: /lfg + +For a list of activity codes see `/activities` +Start date can be specified as absolute date YYYY-MM-DD HH:MM or +as an open text "in 3 hours" or "next sunday". + +Example: /lfg kf 2018-09-10 23:00 +Times are in Moscow (MSK) timezone. From 26ed75433106f72131563ab5efd97c23ee60761e Mon Sep 17 00:00:00 2001 From: Berkus Decker Date: Wed, 20 Aug 2025 17:34:26 +0300 Subject: [PATCH 21/26] =?UTF-8?q?=F0=9F=91=BD=20Update=20List=20command?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.toml | 1 + bot/src/commands/list_command.rs | 62 +++++++------------ {templates => bot/templates}/list/event.tera | 4 +- bot/templates/list/join.tera | 5 ++ bot/templates/list/leave.tera | 3 + bot/templates/list/member.tera | 1 + .../templates}/list/planned.tera | 5 +- 7 files changed, 38 insertions(+), 43 deletions(-) rename {templates => bot/templates}/list/event.tera (85%) create mode 100644 bot/templates/list/join.tera create mode 100644 bot/templates/list/leave.tera create mode 100644 bot/templates/list/member.tera rename {templates => bot/templates}/list/planned.tera (55%) diff --git a/Cargo.toml b/Cargo.toml index e3c82d10..41ad190b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,7 @@ culpa = "1.0" dotenv = "0.15" entity = { version = "0.4", path = "./entity" } fern = { version = "0.7", features = ["colored"] } +futures = "0.3" include_dir = { version = "0.7.4", features = ["glob", "nightly"] } itertools = "0.14" kameo = "0.17" diff --git a/bot/src/commands/list_command.rs b/bot/src/commands/list_command.rs index 27428bcd..2a9eceb5 100644 --- a/bot/src/commands/list_command.rs +++ b/bot/src/commands/list_command.rs @@ -1,58 +1,40 @@ use { crate::{ - bot_actor::{ActorUpdateMessage, Format, Notify, SendMessageReply}, + actors::bot_actor::{ActorUpdateMessage, Format}, commands::{match_command, validate_username}, - datetime::nowtz, - models::PlannedActivity, - render_template, BotCommand, + render_template_or_err, }, - riker::actors::Tell, + entity::prelude::PlannedActivities, + futures::future::try_join_all, + kameo::message::Context, }; -command_actor!(ListCommand, [ActorUpdateMessage]); +command_actor!(ListCommand, "list", "List current events"); -impl ListCommand { - fn send_reply(&self, message: &ActorUpdateMessage, reply: S, format: Format) - where - S: Into, - { - self.bot_ref.tell( - SendMessageReply(reply.into(), message.clone(), format, Notify::Off), - None, - ); - } -} - -impl BotCommand for ListCommand { - fn prefix() -> &'static str { - "/list" - } - - fn description() -> &'static str { - "List current events" - } -} - -impl Receive for ListCommand { - type Msg = ListCommandMsg; +impl Message for ListCommand { + type Reply = anyhow::Result<()>; - fn receive(&mut self, _ctx: &Context, message: ActorUpdateMessage, _sender: Sender) { + async fn handle( + &mut self, + message: ActorUpdateMessage, + _ctx: &mut Context, + ) -> Self::Reply { if let (Some(_), _) = match_command(message.update.text(), Self::prefix(), &self.bot_name) { let connection = self.connection(); - if let Some(guardian) = validate_username(&self.bot_ref, &message, &connection) { - // let count = self.activity(connection).max_fireteam_size as usize - // - self.members_count(connection); - let upcoming_events: Vec<_> = PlannedActivity::upcoming_activities(&connection) + if let Some(guardian) = validate_username(&self.bot_ref, &message, connection).await { + let events_data = PlannedActivities::upcoming_activities(connection).await; + let futures = events_data .iter() - .map(|s| s.to_template(Some(&guardian), &connection)) - .collect(); + .map(|event| event.to_template(connection, Some(&guardian))); + let events_data = try_join_all(futures).await?; - let output = render_template!("list/planned", ("events", &upcoming_events)) - .expect("Rendering failed"); + let output = render_template_or_err!("list/planned", ("events" => &events_data)); - self.send_reply(&message, output, Format::Html); + self.send_reply_with_format(&message, output, Format::Html) + .await; } } + Ok(()) } } diff --git a/templates/list/event.tera b/bot/templates/list/event.tera similarity index 85% rename from templates/list/event.tera rename to bot/templates/list/event.tera index 08013d6f..835d8572 100644 --- a/templates/list/event.tera +++ b/bot/templates/list/event.tera @@ -3,7 +3,7 @@ {{ event.details }} {% endif %} {% for member in event.members -%} -{{ member.psn_name }} (t.me/{{ member.telegram_name }}) +{% include "list/member" %} {% endfor %} ⏰ {{ event.time }} {#- join_prompt -#} @@ -12,7 +12,7 @@ This activity fireteam is full. {% else %} Enter `{{ event.join_link }}` to join this group. Up to {{ event.count }} more can join. {% endif -%} -{#- end join_prompt -#} +{#- leave prompt -#} {% if event.fireteam_joined -%} Enter `{{ event.leave_link }}` to leave this group. {% endif -%} diff --git a/bot/templates/list/join.tera b/bot/templates/list/join.tera new file mode 100644 index 00000000..2ea23184 --- /dev/null +++ b/bot/templates/list/join.tera @@ -0,0 +1,5 @@ +{%- if event.fireteam_full %} +This activity fireteam is full. +{% else %} +Enter `{{ event.join_link }}` to join this group. Up to {{ event.count }} more can join. +{% endif -%} diff --git a/bot/templates/list/leave.tera b/bot/templates/list/leave.tera new file mode 100644 index 00000000..7a16ee2e --- /dev/null +++ b/bot/templates/list/leave.tera @@ -0,0 +1,3 @@ +{% if event.fireteam_joined -%} +Enter `{{ event.leave_link }}` to leave this group. +{% endif -%} diff --git a/bot/templates/list/member.tera b/bot/templates/list/member.tera new file mode 100644 index 00000000..2bb43112 --- /dev/null +++ b/bot/templates/list/member.tera @@ -0,0 +1 @@ +{{ member.icon }} {{ member.psn_name }} (t.me/{{ member.telegram_name }}) diff --git a/templates/list/planned.tera b/bot/templates/list/planned.tera similarity index 55% rename from templates/list/planned.tera rename to bot/templates/list/planned.tera index a9a4081f..a7eb2e17 100644 --- a/templates/list/planned.tera +++ b/bot/templates/list/planned.tera @@ -2,6 +2,9 @@ {% for event in events -%} {% include "list/event" %} +{%- if not loop.last %} +𑗌 {#- Siddham Section Mark With Rays And Dotted Crescents #} +{% endif %} {% else -%} No activities planned, add something with `/lfg` -{%- endfor -%} +{% endfor %} From 22c2f4ea6d018d4f6e3629cb01385a6020f533c6 Mon Sep 17 00:00:00 2001 From: Berkus Decker Date: Wed, 20 Aug 2025 15:04:32 +0300 Subject: [PATCH 22/26] =?UTF-8?q?=F0=9F=91=BD=20Update=20Manage=20command?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- TODO.md | 11 ++ bot/src/commands/manage_command.rs | 269 ++++++++++++----------------- bot/templates/manage/admins.tera | 5 + bot/templates/manage/usage.tera | 10 ++ 4 files changed, 133 insertions(+), 162 deletions(-) create mode 100644 bot/templates/manage/admins.tera create mode 100644 bot/templates/manage/usage.tera diff --git a/TODO.md b/TODO.md index 90828ea8..3eaa2c17 100644 --- a/TODO.md +++ b/TODO.md @@ -73,3 +73,14 @@ Update riker to kameo: A version of two-timer that can use "in x hours" and supports timezones (default doesn't work with TZs properly): - https://github.com/JellyWX/two-timer/tree/master + +// in manage_command: +// Need to invent some sort of match string format for matching subcommands +// Some are `/command subcommand [args]`, some are `/command arg subcommand args` etc. +// Can encode this string in prefix() for subcommands and make them match, maybe even directly? +// i.e. add subcommands together with master command to the general list of commands (need to sort properly too) +// if match_subcommand(message, ListAdminsSubcommand) { +// return ListAdminsSubcommand::execute(); +// } else if match_subcommand(message, AddAdminSubcommand) { +// return AddAdminSubcommand::execute(); +// } diff --git a/bot/src/commands/manage_command.rs b/bot/src/commands/manage_command.rs index 1b6c7ddf..033f4664 100644 --- a/bot/src/commands/manage_command.rs +++ b/bot/src/commands/manage_command.rs @@ -1,76 +1,39 @@ use { crate::{ - actors::bot_actor::{ActorUpdateMessage, Format, Notify, SendMessageReply}, + actors::bot_actor::ActorUpdateMessage, commands::{admin_check, guardian_lookup, match_command}, - BotCommand, + render_template_or_err, }, - riker::actors::Tell, + entity::guardians, + kameo::message::Context, + sea_orm::{ActiveModelTrait, Set}, }; -// #[derive(Clone)] -// struct ListAdminsSubcommand; -// -// #[derive(Clone)] -// struct AddAdminSubcommand; -// -// #[derive(Clone)] -// struct RemoveAdminSubcommand; +command_actor!(ManageCommand, "manage", "Manage bot users (admin-only)"); -command_actor!(ManageCommand, [ActorUpdateMessage]); +impl Message for ManageCommand { + type Reply = (); -impl ManageCommand { - fn send_reply(&self, message: &ActorUpdateMessage, reply: S) - where - S: Into, - { - self.bot_ref.tell( - SendMessageReply(reply.into(), message.clone(), Format::Plain, Notify::Off), - None, - ); - } - - fn usage(&self, message: &ActorUpdateMessage) { - self.send_reply( - message, - "Manage admins: -/manage list-admins - List existing admins -/manage add-admin - Add existing guardian as an admin -/manage remove-admin - Remove admin rights from guardian", - ); - } -} - -impl BotCommand for ManageCommand { - fn prefix() -> &'static str { - "/manage" - } - - fn description() -> &'static str { - "Manage bot users (admin-only)" - } -} - -impl Receive for ManageCommand { - type Msg = ManageCommandMsg; - - fn receive(&mut self, _ctx: &Context, message: ActorUpdateMessage, _sender: Sender) { + async fn handle( + &mut self, + message: ActorUpdateMessage, + _ctx: &mut Context, + ) -> Self::Reply { if let (Some(_), args) = match_command(message.update.text(), Self::prefix(), &self.bot_name) { let connection = self.connection(); - let admin = admin_check(&self.bot_ref, &message, &connection); + + let admin = admin_check(&self.bot_ref, &message, connection).await; if admin.is_none() { - return self.send_reply(&message, "You are not admin"); + return self.send_reply(&message, "❌ You are not admin").await; } // let _admin = admin.unwrap(); if args.is_none() { - return self.usage(&message); + return self.usage(&message).await; } // Split args in two: @@ -80,7 +43,7 @@ impl Receive for ManageCommand { let args: Vec<&str> = args.splitn(2, ' ').collect(); if args.is_empty() { - return self.usage(&message); + return self.usage(&message).await; } let subcommand = args[0]; @@ -93,190 +56,172 @@ impl Receive for ManageCommand { log::info!("{:?}", args); match subcommand { - "list-admins" => self.list_admins_subcommand(&message), - "add-admin" => self.add_admin_subcommand(&message, args), - "remove-admin" => self.remove_admin_subcommand(&message, args), + "list-admins" => self.list_admins_subcommand(&message, connection).await, + "add-admin" => self.add_admin_subcommand(&message, args, connection).await, + "remove-admin" => { + self.remove_admin_subcommand(&message, args, connection) + .await + } &_ => { - self.send_reply(&message, "Unknown management command"); + self.send_reply(&message, "❌ Unknown management command") + .await; } } - - // Need to invent some sort of match string format for matching subcommands - // Some are `/command subcommand [args]`, some are `/command arg subcommand args` etc. - // Can encode this string in prefix() for subcommands and make them match, maybe even directly? - // i.e. add subcommands together with master command to the general list of commands (need to sort properly too) - // if match_subcommand(message, ListAdminsSubcommand) { - // return ListAdminsSubcommand::execute(); - // } else if match_subcommand(message, AddAdminSubcommand) { - // return AddAdminSubcommand::execute(); - // } } } } -// command_ctor!(ListAdminsSubcommand); -// -// impl BotCommand for ListAdminsSubcommand { -// fn prefix(&self) -> &'static str { -// "list-admins" -// } -// -// fn description(&self) -> &'static str { -// "List bot admins (admin-only)" -// } impl ManageCommand { - fn list_admins_subcommand(&self, message: &ActorUpdateMessage) { - use { - crate::{models::Guardian, schema::guardians::dsl::*}, - diesel::prelude::*, - }; - - let connection = self.connection(); - - let admins = guardians - .filter(is_admin.eq(true)) - .order(telegram_name.asc()) - .load::(&connection) - .expect("Cannot execute SQL query"); + async fn list_admins_subcommand( + &self, + message: &ActorUpdateMessage, + connection: &DatabaseConnection, + ) { + use sea_orm::{ColumnTrait, EntityTrait, QueryFilter, QueryOrder}; + + let admins = guardians::Entity::find() + .filter(guardians::Column::IsAdmin.eq(true)) + .order_by_asc(guardians::Column::TelegramName) + .all(connection) + .await + .expect("❌ Cannot execute SQL query"); if admins.is_empty() { - return self.send_reply(message, "No admins found"); + return self.send_reply(message, "❌ No admins found").await; } - let text = admins - .iter() - .fold("Existing admins:\n\n".to_owned(), |acc, admin| { - acc + &format!("{}\n", admin) - }); + let admins: Vec = admins.into_iter().map(|adm| adm.format_name()).collect(); - self.send_reply(message, text); + self.send_reply( + message, + render_template_or_err!("manage/admins", ("admins" => &admins)), + ) + .await; } -} -// command_ctor!(AddAdminSubcommand); -// -// impl BotCommand for AddAdminSubcommand { -// fn prefix(&self) -> &'static str { -// "add-admin" -// } -// -// fn description(&self) -> &'static str { -// "Add bot admin (admin-only)" -// } -impl ManageCommand { - fn add_admin_subcommand(&self, message: &ActorUpdateMessage, args: Option) { - let connection = self.connection(); - let admin = admin_check(&self.bot_ref, message, &connection); + async fn add_admin_subcommand( + &self, + message: &ActorUpdateMessage, + args: Option, + connection: &DatabaseConnection, + ) { + let admin = admin_check(&self.bot_ref, message, connection).await; if admin.is_none() { - return self.send_reply(message, "You are not admin"); + return self.send_reply(message, "❌ You are not admin").await; } let admin = admin.unwrap(); if !admin.is_superadmin { - return self.send_reply(message, "You are not superadmin"); + return self.send_reply(message, "❌ You are not superadmin").await; } if args.is_none() { - return self.send_reply(message, "Specify a guardian to promote to admin"); + return self + .send_reply(message, "❌ Specify a guardian to promote to admin") + .await; } let name = args.unwrap(); - let guardian = guardian_lookup(&name, &connection); + let guardian = guardian_lookup(&name, connection).await; match guardian { - Ok(Some(mut guardian)) => { + Ok(Some(guardian)) => { let tg_name = guardian.telegram_name.clone(); if guardian.is_admin { - return self.send_reply(message, format!("@{} is already an admin", &tg_name)); + return self + .send_reply(message, format!("✅ @{tg_name} is already an admin")) + .await; } - use diesel_derives_traits::Model; + let mut guardian: guardians::ActiveModel = guardian.into(); + guardian.is_admin = Set(true); - guardian.is_admin = true; - guardian - .save(&connection) // @todo handle DbError - .expect("Cannot execute SQL query"); + if guardian.update(connection).await.is_err() { + return self.send_reply(message, "❌ Error updating guardian").await; + } - self.send_reply(message, format!("@{} is now an admin!", &tg_name)); + self.send_reply(message, format!("✅ @{tg_name} is now an admin!")) + .await; } Ok(None) => { - self.send_reply(message, format!("Guardian {} was not found.", &name)); + self.send_reply(message, format!("❌ Guardian {name} was not found.")) + .await; } Err(_) => { - self.send_reply(message, "Error querying guardian name."); + self.send_reply(message, "❌ Error querying guardian name.") + .await; } } } -} -// command_ctor!(RemoveAdminSubcommand); -// -// impl BotCommand for RemoveAdminSubcommand { -// fn prefix(&self) -> &'static str { -// "remove-admin" -// } -// -// fn description(&self) -> &'static str { -// "Remove bot admin (admin-only)" -// } - -impl ManageCommand { - fn remove_admin_subcommand(&self, message: &ActorUpdateMessage, args: Option) { - let connection = self.connection(); - let admin = admin_check(&self.bot_ref, message, &connection); + async fn remove_admin_subcommand( + &self, + message: &ActorUpdateMessage, + args: Option, + connection: &DatabaseConnection, + ) { + let admin = admin_check(&self.bot_ref, message, connection).await; if admin.is_none() { - return self.send_reply(message, "You are not admin"); + return self.send_reply(message, "❌ You are not admin").await; } let admin = admin.unwrap(); if !admin.is_superadmin { - return self.send_reply(message, "You are not superadmin"); + return self.send_reply(message, "❌ You are not superadmin").await; } if args.is_none() { - return self.send_reply(message, "Specify a guardian to demote from admins"); + return self + .send_reply(message, "❌ Specify a guardian to demote from admins") + .await; } let name = args.unwrap(); - let guardian = guardian_lookup(&name, &connection); + let guardian = guardian_lookup(&name, connection).await; match guardian { - Ok(Some(mut guardian)) => { + Ok(Some(guardian)) => { let tg_name = guardian.telegram_name.clone(); if !guardian.is_admin { return self - .send_reply(message, format!("@{} is already not an admin", &tg_name)); + .send_reply(message, format!("✅ @{tg_name} is already not an admin")) + .await; } if guardian.is_superadmin { - return self.send_reply( - message, - format!("@{} is a superadmin, you can not demote.", &tg_name), - ); + return self + .send_reply( + message, + format!("❌ @{tg_name} is a superadmin, you can not demote."), + ) + .await; } - use diesel_derives_traits::Model; + let mut guardian: guardians::ActiveModel = guardian.into(); + guardian.is_admin = Set(false); - guardian.is_admin = false; - guardian - .save(&connection) // @todo handle DbError - .expect("Cannot execute SQL query"); + if guardian.update(connection).await.is_err() { + return self.send_reply(message, "❌ Error updating guardian").await; + } - self.send_reply(message, format!("@{} is not an admin anymore!", &tg_name)); + self.send_reply(message, format!("✅ @{tg_name} is not an admin anymore!")) + .await; } Ok(None) => { - self.send_reply(message, format!("Guardian {} was not found.", &name)); + self.send_reply(message, format!("❌ Guardian {name} was not found.")) + .await; } Err(_) => { - self.send_reply(message, "Error querying guardian name."); + self.send_reply(message, "❌ Error querying guardian name.") + .await; } } } diff --git a/bot/templates/manage/admins.tera b/bot/templates/manage/admins.tera new file mode 100644 index 00000000..15f5a106 --- /dev/null +++ b/bot/templates/manage/admins.tera @@ -0,0 +1,5 @@ +Existing admins: + +{% for admin in admins %} +{{admin}} +{% endfor -%} diff --git a/bot/templates/manage/usage.tera b/bot/templates/manage/usage.tera new file mode 100644 index 00000000..c08a13a9 --- /dev/null +++ b/bot/templates/manage/usage.tera @@ -0,0 +1,10 @@ +Manage command help: + +/manage list-admins + List currently registered admins. + +/manage add-admin + Grant admin rights to guardian. + +/manage remove-admin + Remove admin rights from guardian. From 5ae3d29eed6e6771cd27454ae3341ee6800a68f9 Mon Sep 17 00:00:00 2001 From: Berkus Decker Date: Wed, 20 Aug 2025 15:04:48 +0300 Subject: [PATCH 23/26] =?UTF-8?q?=F0=9F=91=BD=20Update=20PSN=20command?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bot/src/commands/psn_command.rs | 167 ++++++++++++-------------------- bot/templates/psn/usage.tera | 4 + 2 files changed, 66 insertions(+), 105 deletions(-) create mode 100644 bot/templates/psn/usage.tera diff --git a/bot/src/commands/psn_command.rs b/bot/src/commands/psn_command.rs index e232aa76..a0c0f036 100644 --- a/bot/src/commands/psn_command.rs +++ b/bot/src/commands/psn_command.rs @@ -1,162 +1,119 @@ use { - crate::{ - bot_actor::{ActorUpdateMessage, Format, Notify, SendMessageReply}, - commands::match_command, - models::{Guardian, NewGuardian}, - schema::guardians::dsl::*, - BotCommand, - }, - diesel::{self, prelude::*}, - riker::actors::Tell, + crate::{actors::bot_actor::ActorUpdateMessage, commands::match_command}, + entity::guardians, + kameo::message::Context, + sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, Set}, }; -command_actor!(PsnCommand, [ActorUpdateMessage]); +command_actor!(PsnCommand, "psn", "Link your telegram user to PSN"); -impl PsnCommand { - fn send_reply(&self, message: &ActorUpdateMessage, reply: S, format: Format) - where - S: Into, - { - self.bot_ref.tell( - SendMessageReply(reply.into(), message.clone(), format, Notify::Off), - None, - ); - } -} +impl Message for PsnCommand { + type Reply = (); -impl BotCommand for PsnCommand { - fn prefix() -> &'static str { - "/psn" - } - - fn description() -> &'static str { - "Link your telegram user to PSN" - } -} - -impl Receive for PsnCommand { - type Msg = PsnCommandMsg; - - fn receive(&mut self, _ctx: &Context, message: ActorUpdateMessage, _sender: Sender) { + async fn handle( + &mut self, + message: ActorUpdateMessage, + _ctx: &mut Context, + ) -> Self::Reply { if let (Some(_), name) = match_command(message.update.text(), Self::prefix(), &self.bot_name) { log::info!("PSN command"); if name.is_none() { - return self.send_reply( - &message, - "Usage: /psn psnid\nFor example: /psn KPOTA_B_ATEOHE", - Format::Html, - ); + return self.usage(&message).await; } let name = name.unwrap(); - let from = match message.update.from() { + let from = match &message.update.from { None => { - return self.send_reply(&message, "Message has no sender info.", Format::Plain); + return self + .send_reply(&message, "Message has no sender info.") + .await; } Some(from) => from, }; - let username = match from.username { + let username = match &from.username { None => { - return self.send_reply( - &message, - "You have no telegram username, register your telegram account first.", - Format::Plain, - ); + return self.usage(&message).await; } - Some(ref name) => name, + Some(name) => name, }; - let connection = self.connection(); let user_id = from.id; - let db_user = guardians - .filter(telegram_id.eq(&user_id)) - .first::(&connection) - .optional(); + let connection = self.connection(); + + let db_user = guardians::Entity::find() + .filter(guardians::Column::TelegramId.eq(user_id.0 as i64)) + .one(connection) + .await; match db_user { Ok(Some(user)) => { - let another_user = guardians - .filter(psn_name.ilike(&name)) - .filter(telegram_id.ne(&user_id)) - .first::(&connection) - .optional(); + let another_user = guardians::Entity::find() + .filter(guardians::Column::PsnName.contains(&name)) + .filter(guardians::Column::TelegramId.ne(user_id.0 as i64)) + .one(connection) + .await; match another_user { Ok(Some(_)) => { self.send_reply( &message, format!( - "The psn {psn} is already used by somebody else.", + "❌ The psn {psn} is already used by somebody else.", psn = name ), - Format::Plain, - ); + ) + .await; } Ok(None) => { - use diesel_derives_traits::Model; + let mut user: guardians::ActiveModel = user.into(); + user.telegram_name = Set(username.to_string()); + user.psn_name = Set(name.to_string()); - let mut user = user; - user.telegram_name = username.to_string(); - user.psn_name = name.to_string(); - if user.save(&connection).is_err() { - self.send_reply( - &message, - "Failed to update telegram and PSN names.", - Format::Plain, - ); + if user.update(connection).await.is_err() { + self.send_reply(&message, "❌ Failed to update PSN name.") + .await; } else { self.send_reply( &message, - format!( - "Your telegram @{username} is linked with PSN {psn}", - username = username, - psn = name - ), - Format::Plain, - ); + format!("✅ Your PSN name updated to {}.", name), + ) + .await; } } Err(_) => { - self.send_reply( - &message, - "Error querying guardian PSN.", - Format::Plain, - ); + self.send_reply(&message, "❌ Error querying guardian PSN.") + .await; } } } Ok(None) => { - use crate::schema::guardians; - - let guardian = NewGuardian { - telegram_name: username, - telegram_id: user_id, - psn_name: &name, + let guardian = guardians::ActiveModel { + telegram_name: Set(username.to_string()), + telegram_id: Set(user_id.0 as i64), + psn_name: Set(name.to_string()), + ..Default::default() }; - diesel::insert_into(guardians::table) - .values(&guardian) - .execute(&connection) - .expect("Unexpected error saving guardian"); - - self.send_reply( - &message, - format!( - "Linking telegram @{username} with PSN {psn}", - username = username, - psn = name - ), - Format::Plain, - ); + if guardian.insert(connection).await.is_err() { + self.send_reply(&message, "❌ Error saving guardian info.") + .await; + } else { + self.send_reply( + &message, + format!("✅ Your PSN name is now set to {name}."), + ) + .await; + } } Err(_) => { - self.send_reply(&message, "Error querying guardian name.", Format::Plain); + self.send_reply(&message, "❌ Error querying guardian name.") + .await; } }; } diff --git a/bot/templates/psn/usage.tera b/bot/templates/psn/usage.tera new file mode 100644 index 00000000..e31ceec5 --- /dev/null +++ b/bot/templates/psn/usage.tera @@ -0,0 +1,4 @@ +PSN command help: + +/psn + Link your Telegram account to your PSN account. From 2c64b5f11a16e73e21ab20f0d5068733d9b47546 Mon Sep 17 00:00:00 2001 From: Berkus Decker Date: Wed, 20 Aug 2025 15:23:56 +0300 Subject: [PATCH 24/26] =?UTF-8?q?=F0=9F=91=BD=20Update=20Uptime=20command?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 20 ++++++++++++ bot/Cargo.toml | 1 + bot/src/commands/uptime_command.rs | 50 +++++++++++------------------- bot/templates/uptime/procinfo.tera | 3 ++ bot/templates/uptime/usage.tera | 1 + 5 files changed, 43 insertions(+), 32 deletions(-) create mode 100644 bot/templates/uptime/procinfo.tera create mode 100644 bot/templates/uptime/usage.tera diff --git a/Cargo.lock b/Cargo.lock index 52d27b17..42dca434 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -28,9 +28,11 @@ dependencies = [ "dotenv", "entity", "fern", + "futures", "include_dir", "itertools 0.14.0", "kameo", + "libbot", "log", "migration", "paste", @@ -40,6 +42,7 @@ dependencies = [ "serde", "teloxide", "tera", + "thousands", "tokio", "two_timer", ] @@ -789,6 +792,8 @@ dependencies = [ "chrono", "culpa", "dotenv", + "futures", + "libbot", "regex", "sea-orm", "serde", @@ -1586,6 +1591,15 @@ dependencies = [ "spin", ] +[[package]] +name = "libbot" +version = "0.4.0" +dependencies = [ + "chrono", + "chrono-tz 0.10.4", + "culpa", +] + [[package]] name = "libc" version = "0.2.175" @@ -3513,6 +3527,12 @@ dependencies = [ "syn 2.0.106", ] +[[package]] +name = "thousands" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bf63baf9f5039dadc247375c29eb13706706cfde997d0330d05aa63a77d8820" + [[package]] name = "thread_local" version = "1.1.9" diff --git a/bot/Cargo.toml b/bot/Cargo.toml index d0b0060c..969cfe7a 100644 --- a/bot/Cargo.toml +++ b/bot/Cargo.toml @@ -31,3 +31,4 @@ two_timer.workspace = true [target.'cfg(target_os="linux")'.dependencies] procfs = "0.17" +thousands = "0.2" diff --git a/bot/src/commands/uptime_command.rs b/bot/src/commands/uptime_command.rs index 6cc3c1d6..1be3c4be 100644 --- a/bot/src/commands/uptime_command.rs +++ b/bot/src/commands/uptime_command.rs @@ -1,29 +1,24 @@ #[cfg(target_os = "linux")] use procfs::process::Process; use { - crate::{ - bot_actor::{ActorUpdateMessage, Format, Notify, SendMessageReply}, - commands::match_command, - BotCommand, - }, - riker::actors::Tell, + crate::{actors::bot_actor::ActorUpdateMessage, commands::match_command}, + kameo::message::Context, }; -command_actor!(UptimeCommand, [ActorUpdateMessage]); +command_actor!(UptimeCommand, "uptime", "Show bot uptime and statistics"); #[cfg(target_os = "linux")] fn get_process_info() -> String { if let Ok(process) = Process::myself() { - use thousands::Separable; + use {crate::render_template_or_err, thousands::Separable}; let stat = process.stat().unwrap(); let page_size = procfs::page_size(); - format!( - "- 🧵 {thn} threads\n- 📃 {vmb} bytes ({vmp} pages) virtual memory\n- 📃 {rmb} bytes ({rmp} pages) resident memory", - thn = stat.num_threads, - vmb = stat.vsize.separate_with_commas(), - vmp = (stat.vsize / page_size).separate_with_commas(), - rmp = stat.rss.separate_with_commas(), - rmb = (stat.rss * page_size).separate_with_commas(), + render_template_or_err!("uptime/procinfo", + ("thn" => &stat.num_threads), + ("vmb" => &stat.vsize.separate_with_commas()), + ("vmp" => &(stat.vsize / page_size).separate_with_commas()), + ("rmp" => &stat.rss.separate_with_commas()), + ("rmb" => &(stat.rss * page_size).separate_with_commas()) ) } else { "- Couldn't access process information".to_string() @@ -35,27 +30,18 @@ fn get_process_info() -> String { "- Process info only available on Linux hosts.".to_string() } -impl BotCommand for UptimeCommand { - fn prefix() -> &'static str { - "/uptime" - } - - fn description() -> &'static str { - "Show bot uptime and statistics" - } -} - -impl Receive for UptimeCommand { - type Msg = UptimeCommandMsg; +impl Message for UptimeCommand { + type Reply = (); - fn receive(&mut self, _ctx: &Context, msg: ActorUpdateMessage, _sender: Sender) { + async fn handle( + &mut self, + msg: ActorUpdateMessage, + _ctx: &mut Context, + ) -> Self::Reply { if let (Some(_), _) = match_command(msg.update.text(), Self::prefix(), &self.bot_name) { let uptime = libbot::datetime::format_uptime(); let message = format!("- ⏰ Started {uptime}\n{}", get_process_info()); - self.bot_ref.tell( - SendMessageReply(message, msg, Format::Plain, Notify::Off), - None, - ); + self.send_reply(&msg, message).await; } } } diff --git a/bot/templates/uptime/procinfo.tera b/bot/templates/uptime/procinfo.tera new file mode 100644 index 00000000..0b2af7ca --- /dev/null +++ b/bot/templates/uptime/procinfo.tera @@ -0,0 +1,3 @@ +- 🧵 {{thn}} threads +- 📃 {{vmb}} bytes ({{vmp}} pages) virtual memory +- 📃 {{rmb}} bytes ({{rmp}} pages) resident memory diff --git a/bot/templates/uptime/usage.tera b/bot/templates/uptime/usage.tera new file mode 100644 index 00000000..e060d527 --- /dev/null +++ b/bot/templates/uptime/usage.tera @@ -0,0 +1 @@ +Show bot uptime and statistics From 8ad18a3877b76a0c9d5b6f4023a6eb1beef5da3b Mon Sep 17 00:00:00 2001 From: Berkus Decker Date: Wed, 20 Aug 2025 15:04:58 +0300 Subject: [PATCH 25/26] =?UTF-8?q?=F0=9F=91=BD=20Update=20WhoIs=20command?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bot/src/commands/whois_command.rs | 61 ++++++++++++------------------- bot/templates/whois/usage.tera | 1 + 2 files changed, 24 insertions(+), 38 deletions(-) create mode 100644 bot/templates/whois/usage.tera diff --git a/bot/src/commands/whois_command.rs b/bot/src/commands/whois_command.rs index 49d687a2..395ce817 100644 --- a/bot/src/commands/whois_command.rs +++ b/bot/src/commands/whois_command.rs @@ -2,74 +2,59 @@ use { crate::{ actors::bot_actor::ActorUpdateMessage, commands::{guardian_lookup, match_command, validate_username}, - BotCommand, }, - riker::actors::Tell, + kameo::message::Context, }; -command_actor!(WhoisCommand, [ActorUpdateMessage]); +command_actor!(WhoisCommand, "whois", "Query telegram or PSN id"); -impl WhoisCommand { - fn send_reply(&self, message: &ActorUpdateMessage, reply: S) - where - S: Into, - { - self.bot_ref.tell( - SendMessageReply(reply.into(), message.clone(), Format::Plain, Notify::Off), - None, - ); - } -} +impl Message for WhoisCommand { + type Reply = (); -impl BotCommand for WhoisCommand { - fn prefix() -> &'static str { - "/whois" - } - - fn description() -> &'static str { - "Query telegram or PSN id" - } -} - -impl Receive for WhoisCommand { - type Msg = WhoisCommandMsg; - - fn receive(&mut self, _ctx: &Context, message: ActorUpdateMessage, _sender: Sender) { + async fn handle( + &mut self, + message: ActorUpdateMessage, + _ctx: &mut Context, + ) -> Self::Reply { if let (Some(_), name) = match_command(message.update.text(), Self::prefix(), &self.bot_name) { if name.is_none() { - return self.send_reply( - &message, - "To query user provide his @TelegramId (starting with @) or PsnId", - ); + return self.usage(&message).await; } let name = name.unwrap(); + let connection = self.connection(); - if validate_username(&self.bot_ref, &message, &connection).is_none() { + if validate_username(&self.bot_ref, &message, connection) + .await + .is_none() + { return; // TODO: say something? } - let guardian = guardian_lookup(&name, &connection); + let guardian = guardian_lookup(&name, connection).await; match guardian { Ok(Some(guardian)) => { self.send_reply( &message, format!( - "Guardian @{telegram_name} PSN {psn_name}", + "✅ Guardian @{telegram_name} PSN {psn_name}", telegram_name = guardian.telegram_name, psn_name = guardian.psn_name ), - ); + ) + .await; } Ok(None) => { - self.send_reply(&message, format!("Guardian {} was not found.", name)); + self.send_reply(&message, format!("❌ Guardian {name} was not found.")) + .await; } Err(_) => { - self.send_reply(&message, "Error querying guardian name."); + self.send_reply(&message, "❌ Error querying guardian name.") + .await; } } } diff --git a/bot/templates/whois/usage.tera b/bot/templates/whois/usage.tera new file mode 100644 index 00000000..3148d410 --- /dev/null +++ b/bot/templates/whois/usage.tera @@ -0,0 +1 @@ +To query user provide his @TelegramId (starting with @) or PsnId (without the @) From 238eab2a04f35a9b004d7b672c930487adfb4cb0 Mon Sep 17 00:00:00 2001 From: Berkus Decker Date: Sun, 10 Aug 2025 17:18:06 +0300 Subject: [PATCH 26/26] =?UTF-8?q?=E2=9C=A8=20Parse=20time=20using=20two=5F?= =?UTF-8?q?timer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use fixed two_timer (custom git) - this one can parse "in 3 hours". --- Cargo.lock | 9 ++++----- Cargo.toml | 2 +- bot/Cargo.toml | 1 - bot/src/bin/bot.rs | 2 +- lib/Cargo.toml | 2 ++ lib/src/datetime.rs | 26 ++++++++++++++++++++++++++ 6 files changed, 34 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 42dca434..0099ceb7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -44,7 +44,6 @@ dependencies = [ "tera", "thousands", "tokio", - "two_timer", ] [[package]] @@ -1595,9 +1594,11 @@ dependencies = [ name = "libbot" version = "0.4.0" dependencies = [ + "anyhow", "chrono", "chrono-tz 0.10.4", "culpa", + "two_timer", ] [[package]] @@ -3780,12 +3781,10 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "two_timer" -version = "2.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75e22270f53d0cc86e580617796a681e89d5d5c22fa3774d70ba1592915404ee" +version = "2.2.1" +source = "git+https://github.com/berkus/two-timer.git?branch=updated#2becda6a3a9c7c99bb0fc538079649ef43d67eb2" dependencies = [ "chrono", - "lazy_static", "pidgin", "regex", "serde_json", diff --git a/Cargo.toml b/Cargo.toml index 41ad190b..37c44526 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,4 +35,4 @@ serde = { version = "1.0", features = ["derive"] } teloxide = { version = "0.17", features = ["macros"] } tera = "1" tokio = { version = "1", features = ["macros", "rt-multi-thread"] } -two_timer = "2.2" # doesn't parse "in 3 hours" +two_timer = { version = "2.2", git = "https://github.com/berkus/two-timer.git", branch = "updated" } diff --git a/bot/Cargo.toml b/bot/Cargo.toml index 969cfe7a..dfc2cac7 100644 --- a/bot/Cargo.toml +++ b/bot/Cargo.toml @@ -27,7 +27,6 @@ serde.workspace = true teloxide.workspace = true tera.workspace = true tokio.workspace = true -two_timer.workspace = true [target.'cfg(target_os="linux")'.dependencies] procfs = "0.17" diff --git a/bot/src/bin/bot.rs b/bot/src/bin/bot.rs index 57655267..b0d74f72 100644 --- a/bot/src/bin/bot.rs +++ b/bot/src/bin/bot.rs @@ -1,7 +1,7 @@ // Async Rust implementation of the bot // // To make it usable it misses natty parsing lib implementation in rust -// (There are now several rust impls including https://lib.rs/crates/two_timer and https://lib.rs/crates/intervalle) +// Use https://lib.rs/crates/two_timer use { aegl_bot::{ diff --git a/lib/Cargo.toml b/lib/Cargo.toml index 8f0c9032..6a754d18 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -9,7 +9,9 @@ publish = false name = "libbot" [dependencies] +anyhow.workspace = true chrono.workspace = true chrono-tz.workspace = true culpa.workspace = true # entity.workspace = true +two_timer.workspace = true diff --git a/lib/src/datetime.rs b/lib/src/datetime.rs index 1d65f108..27cc07c1 100644 --- a/lib/src/datetime.rs +++ b/lib/src/datetime.rs @@ -1,6 +1,8 @@ use { + anyhow::bail, chrono::{prelude::*, DateTime, Duration, TimeZone, Utc}, chrono_tz::{Europe::Moscow, Tz}, + culpa::throws, std::fmt::Write, }; @@ -10,6 +12,30 @@ use { pub type BotDateTime = chrono::DateTime; pub type BotTime = chrono::NaiveTime; // UTC +#[throws(anyhow::Error)] +pub fn parse_time_spec(timespec: impl AsRef) -> DateTime { + let now = Local::now().with_timezone(&Moscow); + // Parse input in MSK timezone... + // @todo Honor TELEGRAM_BOT_TIMEZONE envvar + let start_time = match two_timer::parse( + timespec.as_ref(), + two_timer::Config::new(now).default_to_past(false), + ) { + Ok((start, _end, _found)) => start, //.and_utc(), + Err(_) => { + bail!("❌ Failed to parse time {}", timespec.as_ref()) + } + }; + + // ...then convert back to UTC. + // let start_time = start_time.unwrap().0; //.and_utc(); + + // use chrono::Offset; + // let offset = start_time.offset().fix(); + + start_time +} + fn time_diff_string(duration: Duration) -> String { let times = vec![ (Duration::days(365), "year"),