From d09abcf047bc7981dc9abb9ae560f694b665da6d Mon Sep 17 00:00:00 2001 From: jamesbt365 Date: Tue, 28 Nov 2023 12:25:39 +0000 Subject: [PATCH 1/5] Rewrite TabletBot to poise - Uses serenity 0.12 (poise doesn't have a release targetting it yet) - Fixes the Nix build action. --- .editorconfig | 5 +- .github/workflows/nix.yml | 5 +- .gitignore | 3 +- Cargo.lock | 1425 +++++++++++++++++++++++-------------- Cargo.toml | 11 +- default.nix | 32 +- flake.lock | 86 ++- flake.nix | 49 +- shell.nix | 10 +- src/commands/mod.rs | 259 +------ src/commands/snippets.rs | 430 ++++++----- src/commands/utils.rs | 144 ++-- src/events/code.rs | 228 +++--- src/events/issue.rs | 317 ++++----- src/events/mod.rs | 22 +- src/formatting.rs | 40 +- src/main.rs | 187 ++--- src/structures.rs | 114 ++- 18 files changed, 1813 insertions(+), 1554 deletions(-) diff --git a/.editorconfig b/.editorconfig index 01bdc7a..b23e985 100644 --- a/.editorconfig +++ b/.editorconfig @@ -7,5 +7,8 @@ trim_trailing_whitespace = true insert_final_newline = true indent_style = space -[**.{nix,sh,rs,toml}] +[**.{nix,sh,toml}] indent_size = 2 + +[**.rs] +indent_size = 4 diff --git a/.github/workflows/nix.yml b/.github/workflows/nix.yml index d7512b7..90227f7 100644 --- a/.github/workflows/nix.yml +++ b/.github/workflows/nix.yml @@ -8,12 +8,13 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: cachix/install-nix-action@v18 + - uses: actions/checkout@v4 + - uses: cachix/install-nix-action@v24 with: extra_nix_config: | access-tokens = github.com=${{ secrets.GITHUB_TOKEN }} - uses: cachix/cachix-action@v12 + if: github.event_name == 'push' with: name: opentabletdriver authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}' diff --git a/.gitignore b/.gitignore index 509ce0c..f009a9f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .build/ .history result - +target/ +state.json diff --git a/Cargo.lock b/Cargo.lock index bab83da..4d5a58e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,9 @@ version = 3 [[package]] name = "addr2line" -version = "0.17.0" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" dependencies = [ "gimli", ] @@ -19,13 +19,19 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "aho-corasick" -version = "0.7.19" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" dependencies = [ "memchr", ] +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + [[package]] name = "android_system_properties" version = "0.1.5" @@ -37,35 +43,28 @@ dependencies = [ [[package]] name = "arc-swap" -version = "1.5.1" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "983cd8b9d4b02a6dc6ffa557262eb5858a27a0038ffffe21a0f133eaa819a164" +checksum = "bddcadddf5e9015d310179a59bb28c4d4b9920ad0f11e8e14dbadf654890c9a6" [[package]] -name = "async-trait" -version = "0.1.58" +name = "arrayvec" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e805d94e6b5001b651426cf4cd446b1ab5f319d27bab5c644f61de0a804360c" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" dependencies = [ - "proc-macro2", - "quote", - "syn", + "serde", ] [[package]] -name = "async-tungstenite" -version = "0.17.2" +name = "async-trait" +version = "0.1.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1b71b31561643aa8e7df3effe284fa83ab1a840e52294c5f4bd7bfd8b2becbb" +checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9" dependencies = [ - "futures-io", - "futures-util", - "log", - "pin-project-lite", - "tokio", - "tokio-rustls", - "tungstenite", - "webpki-roots", + "proc-macro2", + "quote", + "syn 2.0.39", ] [[package]] @@ -76,9 +75,9 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "backtrace" -version = "0.3.66" +version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cab84319d616cfb654d03394f38ab7e6f0919e181b1b57e1fd15e7fb4077d9a7" +checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" dependencies = [ "addr2line", "cc", @@ -95,44 +94,96 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" +[[package]] +name = "base64" +version = "0.21.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9" + [[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitflags" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" + [[package]] name = "block-buffer" -version = "0.10.3" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" dependencies = [ "generic-array", ] [[package]] name = "bumpalo" -version = "3.11.1" +version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" +checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" + +[[package]] +name = "bytecount" +version = "0.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1e5f035d16fc623ae5f74981db80a439803888314e3a555fd6f04acd51a3205" [[package]] name = "byteorder" -version = "1.4.3" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.1.0" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" + +[[package]] +name = "camino" +version = "1.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c59e92b5a388f549b863a7bea62612c09f24c8393560709a54558a9abdfb3b9c" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo-platform" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" +checksum = "e34637b3140142bdf929fb439e8aa4ebad7651ebf7b1080b3930aa16ac1459ff" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo_metadata" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4acbb09d9ee8e23699b9634375c72795d095bf268439da88562cf9b501f181fa" +dependencies = [ + "camino", + "cargo-platform", + "semver", + "serde", + "serde_json", +] [[package]] name = "cc" -version = "1.0.74" +version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "581f5dba903aac52ea3feb5ec4810848460ee833876f1f9b0fdeab1f19091574" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "libc", +] [[package]] name = "cfg-if" @@ -142,39 +193,26 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.22" +version = "0.4.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfd4d1b31faaa3a89d7934dbded3111da0d2ef28e3ebccdb4f0179f5929d1ef1" +checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" dependencies = [ + "android-tzdata", "iana-time-zone", - "js-sys", - "num-integer", "num-traits", "serde", - "time 0.1.44", - "wasm-bindgen", - "winapi", -] - -[[package]] -name = "codespan-reporting" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" -dependencies = [ - "termcolor", - "unicode-width", + "windows-targets 0.48.5", ] [[package]] name = "command_attr" -version = "0.4.1" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d999d4e7731150ee14aee8f619c7a9aa9a4385bca0606c4fa95aa2f36a05d9a" +checksum = "32f08c85a02e066b7b4f7dcb60eee6ae0793ef7d6452a3547d1f19665df070a9" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -189,15 +227,15 @@ dependencies = [ [[package]] name = "core-foundation-sys" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" +checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" [[package]] name = "cpufeatures" -version = "0.2.5" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" +checksum = "ce420fe07aecd3e67c5f910618fe65e94158f6dcc0adf44e00d69ce2bdfe0fd0" dependencies = [ "libc", ] @@ -211,6 +249,25 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crossbeam-channel" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" +dependencies = [ + "cfg-if", +] + [[package]] name = "crypto-common" version = "0.1.6" @@ -222,54 +279,45 @@ dependencies = [ ] [[package]] -name = "cxx" -version = "1.0.80" +name = "darling" +version = "0.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b7d4e43b25d3c994662706a1d4fcfc32aaa6afd287502c111b237093bb23f3a" +checksum = "0209d94da627ab5605dcccf08bb18afa5009cfbef48d8a8b7d7bdbc79be25c5e" dependencies = [ - "cc", - "cxxbridge-flags", - "cxxbridge-macro", - "link-cplusplus", + "darling_core", + "darling_macro", ] [[package]] -name = "cxx-build" -version = "1.0.80" +name = "darling_core" +version = "0.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84f8829ddc213e2c1368e51a2564c552b65a8cb6a28f31e576270ac81d5e5827" +checksum = "177e3443818124b357d8e76f53be906d60937f0d3a90773a664fa63fa253e621" dependencies = [ - "cc", - "codespan-reporting", - "once_cell", + "fnv", + "ident_case", "proc-macro2", "quote", - "scratch", - "syn", + "strsim", + "syn 2.0.39", ] [[package]] -name = "cxxbridge-flags" -version = "1.0.80" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e72537424b474af1460806647c41d4b6d35d09ef7fe031c5c2fa5766047cc56a" - -[[package]] -name = "cxxbridge-macro" -version = "1.0.80" +name = "darling_macro" +version = "0.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "309e4fb93eed90e1e14bea0da16b209f81813ba9fc7830c20ed151dd7bc0a4d7" +checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5" dependencies = [ - "proc-macro2", + "darling_core", "quote", - "syn", + "syn 2.0.39", ] [[package]] name = "dashmap" -version = "5.4.0" +version = "5.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "907076dfda823b0b36d2a1bb5f90c96660a5bbcd7729e10727f07858f22c4edc" +checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" dependencies = [ "cfg-if", "hashbrown", @@ -279,11 +327,38 @@ dependencies = [ "serde", ] +[[package]] +name = "data-encoding" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5" + +[[package]] +name = "deranged" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f32d04922c60427da6f9fef14d042d9edddef64cb9d4ce0d64d0685fbeb1fd3" +dependencies = [ + "powerfmt", + "serde", +] + +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "digest" -version = "0.10.5" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adfbc57365a37acbd2ebf2b64d7e69bb766e2fea813521ed536f5d0520dcf86c" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", "crypto-common", @@ -295,29 +370,57 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" +[[package]] +name = "either" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" + [[package]] name = "encoding_rs" -version = "0.8.31" +version = "0.8.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9852635589dc9f9ea1b6fe9f05b50ef208c85c834a562f0c6abb1c475736ec2b" +checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" dependencies = [ "cfg-if", ] [[package]] -name = "fastrand" -version = "1.8.0" +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "errno" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499" +checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" dependencies = [ - "instant", + "libc", + "windows-sys 0.52.0", ] +[[package]] +name = "error-chain" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d2f06b9cac1506ece98fe3231e3cc9c4410ec3d5b1f24ae1c8946f0742cdefc" +dependencies = [ + "version_check", +] + +[[package]] +name = "fastrand" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" + [[package]] name = "flate2" -version = "1.0.24" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f82b0f4c27ad9f8bfd1f3208d882da2b09c301bc1c828fd3a00d0216d2fbbff6" +checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" dependencies = [ "crc32fast", "miniz_oxide", @@ -346,19 +449,18 @@ checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" [[package]] name = "form_urlencoded" -version = "1.0.1" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" dependencies = [ - "matches", "percent-encoding", ] [[package]] name = "futures" -version = "0.3.25" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38390104763dc37a5145a53c29c63c1290b5d316d6086ec32c293f6736051bb0" +checksum = "da0290714b38af9b4a7b094b8a37086d1b4e61f2df9122c3cad2577669145335" dependencies = [ "futures-channel", "futures-core", @@ -370,9 +472,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.25" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ba265a92256105f45b719605a571ffe2d1f0fea3807304b522c1d778f79eed" +checksum = "ff4dd66668b557604244583e3e1e1eada8c5c2e96a6d0d6653ede395b78bbacb" dependencies = [ "futures-core", "futures-sink", @@ -380,37 +482,49 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.25" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac" +checksum = "eb1d22c66e66d9d72e1758f0bd7d4fd0bee04cad842ee34587d68c07e45d088c" [[package]] name = "futures-io" -version = "0.3.25" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bf34a163b5c4c52d0478a4d757da8fb65cabef42ba90515efee0f6f9fa45aaa" + +[[package]] +name = "futures-macro" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00f5fb52a06bdcadeb54e8d3671f8888a39697dcb0b81b23b55174030427f4eb" +checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.39", +] [[package]] name = "futures-sink" -version = "0.3.25" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39c15cf1a4aa79df40f1bb462fb39676d0ad9e366c2a33b590d7c66f4f81fcf9" +checksum = "e36d3378ee38c2a36ad710c5d30c2911d752cb941c00c72dbabfb786a7970817" [[package]] name = "futures-task" -version = "0.3.25" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea" +checksum = "efd193069b0ddadc69c46389b740bbccdd97203899b48d09c5f7969591d6bae2" [[package]] name = "futures-util" -version = "0.3.25" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6" +checksum = "a19526d624e703a3179b3d322efec918b6246ea0fa51d41124525f00f1cc8104" dependencies = [ "futures-channel", "futures-core", "futures-io", + "futures-macro", "futures-sink", "futures-task", "memchr", @@ -419,11 +533,20 @@ dependencies = [ "slab", ] +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + [[package]] name = "generic-array" -version = "0.14.6" +version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", @@ -431,26 +554,32 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.8" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" +checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f" dependencies = [ "cfg-if", "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi", ] [[package]] name = "gimli" -version = "0.26.2" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" + +[[package]] +name = "glob" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22030e2c5a68ec659fde1e949a745124b48e6fa8b045b7ed5bd1fe4ccc5c4e5d" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" [[package]] name = "h2" -version = "0.3.15" +version = "0.3.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f9f29bc9dda355256b2916cf526ab02ce0aeaaaf2bad60d65ef3f12f11dd0f4" +checksum = "4d6250322ef6e60f93f9a2162799302cd6f68f79f6e5d85c8c16f14d1d958178" dependencies = [ "bytes", "fnv", @@ -467,24 +596,21 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.12.3" +version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" [[package]] name = "heck" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "hermit-abi" -version = "0.1.19" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" -dependencies = [ - "libc", -] +checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" [[package]] name = "hex" @@ -494,9 +620,9 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "http" -version = "0.2.8" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" +checksum = "8947b1a6fad4393052c7ba1f4cd97bed3e953a95c79c92ad9b051a04611d9fbb" dependencies = [ "bytes", "fnv", @@ -522,15 +648,15 @@ checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" [[package]] name = "httpdate" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hyper" -version = "0.14.22" +version = "0.14.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abfba89e19b959ca163c7752ba59d737c1ceea53a5d31a149c805446fc958064" +checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468" dependencies = [ "bytes", "futures-channel", @@ -543,7 +669,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "socket2", + "socket2 0.4.10", "tokio", "tower-service", "tracing", @@ -552,10 +678,11 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.23.0" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d87c48c02e0dc5e3b849a2041db3029fd066650f8f717c07bf8ed78ccb895cac" +checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" dependencies = [ + "futures-util", "http", "hyper", "rustls", @@ -576,117 +703,90 @@ dependencies = [ "tokio-native-tls", ] -[[package]] -name = "hyperx" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5617e92fc2f2501c3e2bc6ce547cad841adba2bae5b921c7e52510beca6d084c" -dependencies = [ - "base64", - "bytes", - "http", - "httpdate", - "language-tags", - "mime", - "percent-encoding", - "unicase", -] - [[package]] name = "iana-time-zone" -version = "0.1.53" +version = "0.1.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64c122667b287044802d6ce17ee2ddf13207ed924c712de9a66a5814d5b64765" +checksum = "8326b86b6cff230b97d0d312a6c40a60726df3332e721f72a1b035f451663b20" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", "wasm-bindgen", - "winapi", + "windows-core", ] [[package]] name = "iana-time-zone-haiku" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" dependencies = [ - "cxx", - "cxx-build", + "cc", ] +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "idna" -version = "0.2.3" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" dependencies = [ - "matches", "unicode-bidi", "unicode-normalization", ] [[package]] name = "indexmap" -version = "1.9.1" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" +checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" dependencies = [ - "autocfg", + "equivalent", "hashbrown", ] -[[package]] -name = "instant" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" -dependencies = [ - "cfg-if", -] - [[package]] name = "ipnet" -version = "2.5.0" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "879d54834c8c76457ef4293a689b2a8c59b076067ad77b15efafbb05f92a592b" +checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" [[package]] name = "itoa" -version = "1.0.4" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc" +checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" [[package]] name = "js-sys" -version = "0.3.60" +version = "0.3.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47" +checksum = "cee9c64da59eae3b50095c18d3e74f8b73c0b86d2792824ff01bbce68ba229ca" dependencies = [ "wasm-bindgen", ] [[package]] name = "jsonwebtoken" -version = "8.1.1" +version = "8.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1aa4b4af834c6cfd35d8763d359661b90f2e45d8f750a0849156c7f4671af09c" +checksum = "6971da4d9c3aa03c3d8f3ff0f4155b534aad021292003895a469716b2a230378" dependencies = [ - "base64", + "base64 0.21.5", "pem", - "ring", + "ring 0.16.20", "serde", "serde_json", "simple_asn1", ] -[[package]] -name = "language-tags" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388" - [[package]] name = "lazy_static" version = "1.4.0" @@ -701,24 +801,21 @@ checksum = "db13adb97ab515a3691f56e4dbab09283d0b86cb45abd991d8634a9d6f501760" [[package]] name = "libc" -version = "0.2.137" +version = "0.2.150" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89" +checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" [[package]] -name = "link-cplusplus" -version = "1.0.7" +name = "linux-raw-sys" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9272ab7b96c9046fbc5bc56c06c117cb639fe2d509df0c421cad82d2915cf369" -dependencies = [ - "cc", -] +checksum = "969488b55f8ac402214f3f5fd243ebb7206cf82de60d3172994707a4bcc2b829" [[package]] name = "lock_api" -version = "0.4.9" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" +checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" dependencies = [ "autocfg", "scopeguard", @@ -726,30 +823,21 @@ dependencies = [ [[package]] name = "log" -version = "0.4.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "matches" -version = "0.1.9" +version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" [[package]] name = "memchr" -version = "2.5.0" +version = "2.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" [[package]] name = "mime" -version = "0.3.16" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] name = "mime_guess" @@ -761,32 +849,46 @@ dependencies = [ "unicase", ] +[[package]] +name = "mini-moka" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23e0b72e7c9042467008b10279fc732326bd605459ae03bda88825909dd19b56" +dependencies = [ + "crossbeam-channel", + "crossbeam-utils", + "dashmap", + "skeptic", + "smallvec", + "tagptr", + "triomphe", +] + [[package]] name = "miniz_oxide" -version = "0.5.4" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96590ba8f175222643a85693f33d26e9c8a015f599c216509b1a6894af675d34" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" dependencies = [ "adler", ] [[package]] name = "mio" -version = "0.8.5" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de" +checksum = "3dce281c5e46beae905d4de1870d8b1509a9142b62eedf18b443b011ca8343d0" dependencies = [ "libc", - "log", - "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.42.0", + "wasi", + "windows-sys 0.48.0", ] [[package]] name = "native-tls" -version = "0.2.10" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd7e2f3618557f980e0b17e8856252eee3c97fa12c54dff0ca290fb6266ca4a9" +checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" dependencies = [ "lazy_static", "libc", @@ -802,9 +904,9 @@ dependencies = [ [[package]] name = "num-bigint" -version = "0.4.3" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f" +checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" dependencies = [ "autocfg", "num-integer", @@ -823,54 +925,45 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.15" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" dependencies = [ "autocfg", ] [[package]] name = "num_cpus" -version = "1.13.1" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" dependencies = [ "hermit-abi", "libc", ] -[[package]] -name = "num_threads" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" -dependencies = [ - "libc", -] - [[package]] name = "object" -version = "0.29.0" +version = "0.32.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21158b2c33aa6d4561f1c0a6ea283ca92bc54802a93b263e910746d679a7eb53" +checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" dependencies = [ "memchr", ] [[package]] name = "octocrab" -version = "0.17.0" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db14aefad92da160884fae912983ba22a05afd0437c69d793ac86f639ec7a0fe" +checksum = "496442a5ec5ad38376a0c49bc0f31ba55dbda5276cf12757498c378c3bc2ea1c" dependencies = [ "arc-swap", "async-trait", - "base64", + "base64 0.21.5", "bytes", "cfg-if", "chrono", - "hyperx", + "either", "jsonwebtoken", "once_cell", "reqwest", @@ -879,22 +972,23 @@ dependencies = [ "serde_json", "serde_path_to_error", "snafu", + "tracing", "url", ] [[package]] name = "once_cell" -version = "1.16.0" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" [[package]] name = "openssl" -version = "0.10.42" +version = "0.10.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12fc0523e3bd51a692c8850d075d74dc062ccf251c0110668cbd921917118a13" +checksum = "79a4c6c3a2b158f7f8f2a2fc5a969fa3a068df6fc9dbb4a43845436e3af7c800" dependencies = [ - "bitflags", + "bitflags 2.4.1", "cfg-if", "foreign-types", "libc", @@ -905,13 +999,13 @@ dependencies = [ [[package]] name = "openssl-macros" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.39", ] [[package]] @@ -922,26 +1016,16 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.77" +version = "0.9.96" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b03b84c3b2d099b81f0953422b4d4ad58761589d0229b5506356afca05a3670a" +checksum = "3812c071ba60da8b5677cc12bcb1d42989a65553772897a7e0355545a819838f" dependencies = [ - "autocfg", "cc", "libc", "pkg-config", "vcpkg", ] -[[package]] -name = "ordered-float" -version = "2.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7940cf2ca942593318d07fcf2596cdca60a85c9e7fab408a5e21a4f9dcd40d87" -dependencies = [ - "num-traits", -] - [[package]] name = "parking_lot" version = "0.12.1" @@ -954,37 +1038,37 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.4" +version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dc9e0dc2adc1c69d09143aff38d3d30c5c3f0df0dad82e6d25547af174ebec0" +checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", - "windows-sys 0.42.0", + "windows-targets 0.48.5", ] [[package]] name = "pem" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03c64931a1a212348ec4f3b4362585eca7159d0d09cbdf4a7f74f02173596fd4" +checksum = "a8835c273a76a90455d7344889b0964598e3316e2a79ede8e36f16bdcf2228b8" dependencies = [ - "base64", + "base64 0.13.1", ] [[package]] name = "percent-encoding" -version = "2.1.0" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pin-project-lite" -version = "0.2.9" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" [[package]] name = "pin-utils" @@ -994,30 +1078,74 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkg-config" -version = "0.3.26" +version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" +checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" + +[[package]] +name = "poise" +version = "0.5.7" +source = "git+https://github.com/serenity-rs/poise.git?branch=current#835a016f0e511c9b5d74f5c32b9ae151001347cd" +dependencies = [ + "async-trait", + "derivative", + "futures-util", + "parking_lot", + "poise_macros", + "regex", + "serenity", + "tokio", + "tracing", +] + +[[package]] +name = "poise_macros" +version = "0.5.7" +source = "git+https://github.com/serenity-rs/poise.git?branch=current#835a016f0e511c9b5d74f5c32b9ae151001347cd" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.39", +] + +[[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.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "proc-macro2" -version = "1.0.47" +version = "1.0.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725" +checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b" dependencies = [ "unicode-ident", ] +[[package]] +name = "pulldown-cmark" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a1a2f1f0a7ecff9c31abbe177637be0e97a0aef46cf8738ece09327985d998" +dependencies = [ + "bitflags 1.3.2", + "memchr", + "unicase", +] + [[package]] name = "quote" -version = "1.0.21" +version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" dependencies = [ "proc-macro2", ] @@ -1054,46 +1182,49 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.2.16" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" dependencies = [ - "bitflags", + "bitflags 1.3.2", ] [[package]] name = "regex" -version = "1.6.0" +version = "1.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" +checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" dependencies = [ "aho-corasick", "memchr", + "regex-automata", "regex-syntax", ] [[package]] -name = "regex-syntax" -version = "0.6.27" +name = "regex-automata" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" +checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] [[package]] -name = "remove_dir_all" -version = "0.5.3" +name = "regex-syntax" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" -dependencies = [ - "winapi", -] +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" [[package]] name = "reqwest" -version = "0.11.12" +version = "0.11.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "431949c384f4e2ae07605ccaa56d1d9d2ecdb5cadd4f9577ccfab29f2e5149fc" +checksum = "046cd98826c46c2ac8ddecae268eb5c2e58628688a5fc7a2643704a73faba95b" dependencies = [ - "base64", + "base64 0.21.5", "bytes", "encoding_rs", "futures-core", @@ -1118,6 +1249,7 @@ dependencies = [ "serde", "serde_json", "serde_urlencoded", + "system-configuration", "tokio", "tokio-native-tls", "tokio-rustls", @@ -1126,6 +1258,7 @@ dependencies = [ "url", "wasm-bindgen", "wasm-bindgen-futures", + "wasm-streams", "web-sys", "webpki-roots", "winreg", @@ -1140,75 +1273,114 @@ dependencies = [ "cc", "libc", "once_cell", - "spin", - "untrusted", + "spin 0.5.2", + "untrusted 0.7.1", "web-sys", "winapi", ] +[[package]] +name = "ring" +version = "0.17.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb0205304757e5d899b9c2e448b867ffd03ae7f988002e47cd24954391394d0b" +dependencies = [ + "cc", + "getrandom", + "libc", + "spin 0.9.8", + "untrusted 0.9.0", + "windows-sys 0.48.0", +] + [[package]] name = "rustc-demangle" -version = "0.1.21" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + +[[package]] +name = "rustix" +version = "0.38.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" +checksum = "dc99bc2d4f1fed22595588a013687477aedf3cdcfb26558c559edb67b4d9b22e" +dependencies = [ + "bitflags 2.4.1", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.48.0", +] [[package]] name = "rustls" -version = "0.20.7" +version = "0.21.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "539a2bfe908f471bfa933876bd1eb6a19cf2176d375f82ef7f99530a40e48c2c" +checksum = "629648aced5775d558af50b2b4c7b02983a04b312126d45eeead26e7caa498b9" dependencies = [ "log", - "ring", + "ring 0.17.5", + "rustls-webpki", "sct", - "webpki", ] [[package]] name = "rustls-pemfile" -version = "1.0.1" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +dependencies = [ + "base64 0.21.5", +] + +[[package]] +name = "rustls-webpki" +version = "0.101.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0864aeff53f8c05aa08d86e5ef839d3dfcf07aeba2db32f12db0ef716e87bd55" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" dependencies = [ - "base64", + "ring 0.17.5", + "untrusted 0.9.0", ] [[package]] name = "ryu" -version = "1.0.11" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" +checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" [[package]] -name = "schannel" -version = "0.1.20" +name = "same-file" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88d6731146462ea25d9244b2ed5fd1d716d25c52e4d54aa4fb0f3c4e9854dbe2" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" dependencies = [ - "lazy_static", - "windows-sys 0.36.1", + "winapi-util", ] [[package]] -name = "scopeguard" -version = "1.1.0" +name = "schannel" +version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88" +dependencies = [ + "windows-sys 0.48.0", +] [[package]] -name = "scratch" -version = "1.0.2" +name = "scopeguard" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8132065adcfd6e02db789d9285a0deb2f3fcb04002865ab67d5fb103533898" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "sct" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" dependencies = [ - "ring", - "untrusted", + "ring 0.17.5", + "untrusted 0.9.0", ] [[package]] @@ -1217,16 +1389,17 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9bd1c54ea06cfd2f6b63219704de0b9b4f72dcc2b8fdef820be6cd799780e91e" dependencies = [ + "serde", "zeroize", ] [[package]] name = "security-framework" -version = "2.7.0" +version = "2.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bc1bb97804af6631813c55739f771071e0f2ed33ee20b68c86ec505d906356c" +checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" dependencies = [ - "bitflags", + "bitflags 1.3.2", "core-foundation", "core-foundation-sys", "libc", @@ -1235,49 +1408,48 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.6.1" +version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0160a13a177a45bfb43ce71c01580998474f556ad854dcbca936dd2841a5c556" +checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" dependencies = [ "core-foundation-sys", "libc", ] [[package]] -name = "serde" -version = "1.0.147" +name = "semver" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d193d69bae983fc11a79df82342761dfbf28a99fc8d203dca4c3c1b590948965" +checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090" dependencies = [ - "serde_derive", + "serde", ] [[package]] -name = "serde-value" -version = "0.7.0" +name = "serde" +version = "1.0.193" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c" +checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" dependencies = [ - "ordered-float", - "serde", + "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.147" +version = "1.0.193" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f1d362ca8fc9c3e3a7484440752472d68a6caa98f1ab81d99b5dfe517cec852" +checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.39", ] [[package]] name = "serde_json" -version = "1.0.87" +version = "1.0.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce777b7b150d76b9cf60d28b55f5847135a003f7d7350c6be7a773508ce7d45" +checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" dependencies = [ "itoa", "ryu", @@ -1286,10 +1458,11 @@ dependencies = [ [[package]] name = "serde_path_to_error" -version = "0.1.8" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "184c643044780f7ceb59104cef98a5a6f12cb2288a7bc701ab93a362b49fd47d" +checksum = "4beec8bce849d58d06238cb50db2e1c417cfeafa4c63f692b15c82b7c80f8335" dependencies = [ + "itoa", "serde", ] @@ -1307,43 +1480,45 @@ dependencies = [ [[package]] name = "serenity" -version = "0.11.5" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82fd5e7b5858ad96e99d440138f34f5b98e1b959ebcd3a1036203b30e78eb788" +checksum = "385647faa24a889929028973650a4f158fb1b4272b2fcf94feb9fcc3c009e813" dependencies = [ + "arrayvec", "async-trait", - "async-tungstenite", - "base64", - "bitflags", + "base64 0.21.5", + "bitflags 2.4.1", "bytes", - "cfg-if", + "chrono", "command_attr", "dashmap", "flate2", "futures", + "fxhash", "levenshtein", - "mime", "mime_guess", "parking_lot", "percent-encoding", "reqwest", + "secrecy", "serde", - "serde-value", "serde_json", "static_assertions", - "time 0.3.16", + "time", "tokio", + "tokio-tungstenite", "tracing", "typemap_rev", + "typesize", "url", "uwl", ] [[package]] -name = "sha-1" -version = "0.10.0" +name = "sha1" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "028f48d513f9678cda28f6e4064755b3fbb2af6acd672f2c209b62323f7aea0f" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ "cfg-if", "cpufeatures", @@ -1352,9 +1527,9 @@ dependencies = [ [[package]] name = "signal-hook-registry" -version = "1.4.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" dependencies = [ "libc", ] @@ -1368,29 +1543,44 @@ dependencies = [ "num-bigint", "num-traits", "thiserror", - "time 0.3.16", + "time", +] + +[[package]] +name = "skeptic" +version = "0.13.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16d23b015676c90a0f01c197bfdc786c20342c73a0afdda9025adb0bc42940a8" +dependencies = [ + "bytecount", + "cargo_metadata", + "error-chain", + "glob", + "pulldown-cmark", + "tempfile", + "walkdir", ] [[package]] name = "slab" -version = "0.4.7" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" dependencies = [ "autocfg", ] [[package]] name = "smallvec" -version = "1.10.0" +version = "1.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" +checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" [[package]] name = "snafu" -version = "0.7.3" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a152ba99b054b22972ee794cf04e5ef572da1229e33b65f3c57abbff0525a454" +checksum = "e4de37ad025c587a29e8f3f5605c00f70b98715ef90b9061a815b9e59e9042d6" dependencies = [ "backtrace", "doc-comment", @@ -1399,55 +1589,110 @@ dependencies = [ [[package]] name = "snafu-derive" -version = "0.7.3" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5e79cdebbabaebb06a9bdbaedc7f159b410461f63611d4d0e3fb0fab8fed850" +checksum = "990079665f075b699031e9c08fd3ab99be5029b96f3b78dc0709e8f77e4efebf" dependencies = [ "heck", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] name = "socket2" -version = "0.4.7" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd" +checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" dependencies = [ "libc", "winapi", ] +[[package]] +name = "socket2" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" +dependencies = [ + "libc", + "windows-sys 0.48.0", +] + [[package]] name = "spin" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + [[package]] name = "static_assertions" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "syn" -version = "1.0.103" +version = "2.0.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a864042229133ada95abf3b54fdc62ef5ccabe9515b64717bcb9a1919e59445d" +checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] +[[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 = "tabletbot" version = "1.0.0" dependencies = [ "hex", "octocrab", + "poise", "regex", "reqwest", "serde", @@ -1456,69 +1701,54 @@ dependencies = [ "tokio", ] +[[package]] +name = "tagptr" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" + [[package]] name = "tempfile" -version = "3.3.0" +version = "3.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" +checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5" dependencies = [ "cfg-if", "fastrand", - "libc", "redox_syscall", - "remove_dir_all", - "winapi", -] - -[[package]] -name = "termcolor" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" -dependencies = [ - "winapi-util", + "rustix", + "windows-sys 0.48.0", ] [[package]] name = "thiserror" -version = "1.0.37" +version = "1.0.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e" +checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.37" +version = "1.0.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb" +checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" dependencies = [ "proc-macro2", "quote", - "syn", -] - -[[package]] -name = "time" -version = "0.1.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" -dependencies = [ - "libc", - "wasi 0.10.0+wasi-snapshot-preview1", - "winapi", + "syn 2.0.39", ] [[package]] name = "time" -version = "0.3.16" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fab5c8b9980850e06d92ddbe3ab839c062c801f3927c0fb8abd6fc8e918fbca" +checksum = "c4a34ab300f2dee6e562c10a046fc05e358b29f9bf92277f30c3c8d82275f6f5" dependencies = [ + "deranged", "itoa", - "libc", - "num_threads", + "powerfmt", "serde", "time-core", "time-macros", @@ -1526,15 +1756,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.0" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.5" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65bb801831d812c562ae7d2bfb531f26e66e4e1f6b17307ba4149c5064710e5b" +checksum = "4ad70d68dba9e1f8aceda7aa6711965dfec1cac869f311a51bd08b3a2ccbce20" dependencies = [ "time-core", ] @@ -1550,45 +1780,44 @@ dependencies = [ [[package]] name = "tinyvec_macros" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.21.2" +version = "1.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9e03c497dc955702ba729190dc4aac6f2a0ce97f913e5b1b5912fc5039d9099" +checksum = "d0c014766411e834f7af5b8f4cf46257aab4036ca95e9d2c144a10f59ad6f5b9" dependencies = [ - "autocfg", + "backtrace", "bytes", "libc", - "memchr", "mio", "num_cpus", "pin-project-lite", "signal-hook-registry", - "socket2", + "socket2 0.5.5", "tokio-macros", - "winapi", + "windows-sys 0.48.0", ] [[package]] name = "tokio-macros" -version = "1.8.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484" +checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.39", ] [[package]] name = "tokio-native-tls" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" dependencies = [ "native-tls", "tokio", @@ -1596,20 +1825,34 @@ dependencies = [ [[package]] name = "tokio-rustls" -version = "0.23.4" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" +checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" dependencies = [ "rustls", "tokio", - "webpki", +] + +[[package]] +name = "tokio-tungstenite" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "212d5dcb2a1ce06d81107c3d0ffa3121fe974b73f068c8282cb1c32328113b6c" +dependencies = [ + "futures-util", + "log", + "rustls", + "tokio", + "tokio-rustls", + "tungstenite", + "webpki-roots", ] [[package]] name = "tokio-util" -version = "0.7.4" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bb2e075f03b3d66d8d8785356224ba688d2906a371015e225beeb65ca92c740" +checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" dependencies = [ "bytes", "futures-core", @@ -1627,11 +1870,10 @@ checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" [[package]] name = "tracing" -version = "0.1.37" +version = "0.1.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ - "cfg-if", "log", "pin-project-lite", "tracing-attributes", @@ -1640,83 +1882,117 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.23" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.39", ] [[package]] name = "tracing-core" -version = "0.1.30" +version = "0.1.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" dependencies = [ "once_cell", ] +[[package]] +name = "triomphe" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0c5a71827ac326072b6405552093e2ad2accd25a32fd78d4edc82d98c7f2409" + [[package]] name = "try-lock" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" +checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" [[package]] name = "tungstenite" -version = "0.17.3" +version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e27992fd6a8c29ee7eef28fc78349aa244134e10ad447ce3b9f0ac0ed0fa4ce0" +checksum = "9e3dac10fd62eaf6617d3a904ae222845979aec67c615d1c842b4002c7666fb9" dependencies = [ - "base64", "byteorder", "bytes", + "data-encoding", "http", "httparse", "log", "rand", "rustls", - "sha-1", + "sha1", "thiserror", "url", "utf-8", - "webpki", ] [[package]] name = "typemap_rev" -version = "0.1.5" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed5b74f0a24b5454580a79abb6994393b09adf0ab8070f15827cb666255de155" +checksum = "74b08b0c1257381af16a5c3605254d529d3e7e109f3c62befc5d168968192998" [[package]] name = "typenum" -version = "1.15.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "typesize" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e43a952445d2d9df648a822545093c01699df209ceda4df5ac78fef8969c61c" +dependencies = [ + "chrono", + "dashmap", + "hashbrown", + "mini-moka", + "parking_lot", + "secrecy", + "serde_json", + "time", + "typesize-derive", + "url", +] + +[[package]] +name = "typesize-derive" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5274e4d582fd16b83bf7949cc44d6610d3b0290e441d9e5c337fdda9a003849f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.39", +] [[package]] name = "unicase" -version = "2.6.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" +checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" dependencies = [ "version_check", ] [[package]] name = "unicode-bidi" -version = "0.3.8" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" +checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" [[package]] name = "unicode-ident" -version = "1.0.5" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "unicode-normalization" @@ -1728,22 +2004,22 @@ dependencies = [ ] [[package]] -name = "unicode-width" -version = "0.1.10" +name = "untrusted" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" [[package]] name = "untrusted" -version = "0.7.1" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.3.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22fe195a4f217c25b25cb5058ced57059824a678474874038dc88d211bf508d3" +checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" dependencies = [ "form_urlencoded", "idna", @@ -1776,20 +2052,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] -name = "want" -version = "0.3.0" +name = "walkdir" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" dependencies = [ - "log", - "try-lock", + "same-file", + "winapi-util", ] [[package]] -name = "wasi" -version = "0.10.0+wasi-snapshot-preview1" +name = "want" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] [[package]] name = "wasi" @@ -1799,9 +2078,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.83" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" +checksum = "0ed0d4f68a3015cc185aff4db9506a015f4b96f95303897bfa23f846db54064e" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -1809,24 +2088,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.83" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142" +checksum = "1b56f625e64f3a1084ded111c4d5f477df9f8c92df113852fa5a374dbda78826" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn", + "syn 2.0.39", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.33" +version = "0.4.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23639446165ca5a5de86ae1d8896b737ae80319560fbaa4c2887b7da6e7ebd7d" +checksum = "ac36a15a220124ac510204aec1c3e5db8a22ab06fd6706d881dc6149f8ed9a12" dependencies = [ "cfg-if", "js-sys", @@ -1836,9 +2115,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.83" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810" +checksum = "0162dbf37223cd2afce98f3d0785506dcb8d266223983e4b5b525859e6e182b2" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1846,51 +2125,51 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.83" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" +checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.39", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.83" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" +checksum = "7ab9b36309365056cd639da3134bf87fa8f3d86008abf99e612384a6eecd459f" [[package]] -name = "web-sys" -version = "0.3.60" +name = "wasm-streams" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcda906d8be16e728fd5adc5b729afad4e444e106ab28cd1c7256e54fa61510f" +checksum = "b4609d447824375f43e1ffbc051b50ad8f4b3ae8219680c94452ea05eb240ac7" dependencies = [ + "futures-util", "js-sys", "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", ] [[package]] -name = "webpki" -version = "0.22.0" +name = "web-sys" +version = "0.3.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" +checksum = "50c24a44ec86bb68fbecd1b3efed7e85ea5621b39b35ef2766b66cd984f8010f" dependencies = [ - "ring", - "untrusted", + "js-sys", + "wasm-bindgen", ] [[package]] name = "webpki-roots" -version = "0.22.5" +version = "0.25.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368bfe657969fb01238bb756d351dcade285e0f6fcbd36dcb23359a5169975be" -dependencies = [ - "webpki", -] +checksum = "1778a42e8b3b90bff8d0f5032bf22250792889a5cdc752aa0020c84abe3aaf10" [[package]] name = "winapi" @@ -1910,9 +2189,9 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" dependencies = [ "winapi", ] @@ -1923,117 +2202,159 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-core" +version = "0.51.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64" +dependencies = [ + "windows-targets 0.48.5", +] + [[package]] name = "windows-sys" -version = "0.36.1" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "windows_aarch64_msvc 0.36.1", - "windows_i686_gnu 0.36.1", - "windows_i686_msvc 0.36.1", - "windows_x86_64_gnu 0.36.1", - "windows_x86_64_msvc 0.36.1", + "windows-targets 0.48.5", ] [[package]] name = "windows-sys" -version = "0.42.0" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.0", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc 0.42.0", - "windows_i686_gnu 0.42.0", - "windows_i686_msvc 0.42.0", - "windows_x86_64_gnu 0.42.0", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc 0.42.0", + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +dependencies = [ + "windows_aarch64_gnullvm 0.52.0", + "windows_aarch64_msvc 0.52.0", + "windows_i686_gnu 0.52.0", + "windows_i686_msvc 0.52.0", + "windows_x86_64_gnu 0.52.0", + "windows_x86_64_gnullvm 0.52.0", + "windows_x86_64_msvc 0.52.0", ] [[package]] name = "windows_aarch64_gnullvm" -version = "0.42.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" [[package]] name = "windows_aarch64_msvc" -version = "0.36.1" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.42.0" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4" +checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" [[package]] name = "windows_i686_gnu" -version = "0.36.1" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.42.0" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7" +checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" [[package]] name = "windows_i686_msvc" -version = "0.36.1" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.42.0" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246" +checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" [[package]] name = "windows_x86_64_gnu" -version = "0.36.1" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.42.0" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed" +checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" [[package]] name = "windows_x86_64_gnullvm" -version = "0.42.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" [[package]] name = "windows_x86_64_msvc" -version = "0.36.1" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.42.0" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" +checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" [[package]] name = "winreg" -version = "0.10.1" +version = "0.50.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" dependencies = [ - "winapi", + "cfg-if", + "windows-sys 0.48.0", ] [[package]] name = "zeroize" -version = "1.5.7" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c394b5bd0c6f669e7275d9c20aa90ae064cb22e75a1cad54e1b34088034b149f" +checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" diff --git a/Cargo.toml b/Cargo.toml index f61db21..23eb65e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,11 +12,12 @@ serde = { version = "1.0", features = ["derive"] } serde_json = { version = "1.0" } # Discord API -serenity = { version = "0.11", default-features = false, features = ["cache", "client", "gateway", "rustls_backend", "model", "framework", "standard_framework"] } -tokio = { version = "1.21.2", features = ["macros", "signal", "rt-multi-thread"] } +poise = { git = "https://github.com/serenity-rs/poise.git", branch = "current" } +serenity = {version = "0.12", default-features = false, features = ["cache", "client", "gateway", "rustls_backend", "model", "framework", "standard_framework"] } +tokio = { version = "1.29.1", features = ["macros", "signal", "rt-multi-thread"] } # Misc -regex = "1.6.0" -octocrab = "0.17.0" -reqwest = "0.11.12" +regex = "1.10.2" +octocrab = "0.19.0" +reqwest = "0.11.22" hex = "0.4.3" diff --git a/default.nix b/default.nix index cd81f10..6018182 100644 --- a/default.nix +++ b/default.nix @@ -1,24 +1,8 @@ -{ lib -, rustPlatform -, pkg-config -, openssl -}: - -rustPlatform.buildRustPackage rec { - pname = "tabletbot"; - name = pname; - - nativeBuildInputs = [ - pkg-config - ]; - - buildInputs = [ - openssl - ]; - - src = ./.; - - cargoLock = { - lockFile = ./Cargo.lock; - }; -} +{ lib, rustPlatform, pkg-config, openssl }: + + (pkgs.makeRustPlatform { + cargo = toolchain; + rustc = toolchain; + }).buildRustPackage { + pname = "example"; + version = "0.1.0"; diff --git a/flake.lock b/flake.lock index f61fa2e..47a914a 100644 --- a/flake.lock +++ b/flake.lock @@ -1,25 +1,97 @@ { "nodes": { - "nixpkgs": { + "fenix": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ], + "rust-analyzer-src": "rust-analyzer-src" + }, "locked": { - "lastModified": 1666688649, - "narHash": "sha256-i1Tq2VgXbEZKgjM2p2OqZdxcnK4FZjRZ9Oy4Ewx8gjA=", - "owner": "NixOS", - "repo": "nixpkgs", - "rev": "03a00f66fc4e893dccba1579df6d0c83852e1c2c", + "lastModified": 1701238978, + "narHash": "sha256-CL3RjhwV47ZI9oWRpezS2eP0mhiyqpOGb3GBMOcF2w4=", + "owner": "nix-community", + "repo": "fenix", + "rev": "22e61fab6d93cfce2b9659bb7734762ad6a7cf11", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "fenix", + "type": "github" + } + }, + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1694529238, + "narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "ff7b65b44d01cf9ba6a71320833626af21126384", "type": "github" }, "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1701068326, + "narHash": "sha256-vmMceA+q6hG1yrjb+MP8T0YFDQIrW3bl45e7z24IEts=", "owner": "NixOS", - "ref": "nixpkgs-unstable", "repo": "nixpkgs", + "rev": "8cfef6986adfb599ba379ae53c9f5631ecd2fd9c", "type": "github" + }, + "original": { + "id": "nixpkgs", + "ref": "nixos-unstable", + "type": "indirect" } }, "root": { "inputs": { + "fenix": "fenix", + "flake-utils": "flake-utils", "nixpkgs": "nixpkgs" } + }, + "rust-analyzer-src": { + "flake": false, + "locked": { + "lastModified": 1701186284, + "narHash": "sha256-euPBY3EmEy7+Jjm2ToRPlSp/qrj0UL9+PRobxVz6+aQ=", + "owner": "rust-lang", + "repo": "rust-analyzer", + "rev": "c7c582afb57bb802715262d7f1ba73b8a86c1c5a", + "type": "github" + }, + "original": { + "owner": "rust-lang", + "ref": "nightly", + "repo": "rust-analyzer", + "type": "github" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } } }, "root": "root", diff --git a/flake.nix b/flake.nix index 2b3d1d6..74283e4 100644 --- a/flake.nix +++ b/flake.nix @@ -1,26 +1,41 @@ { - description = "TabletBot"; - inputs = { - nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; + fenix = { + url = "github:nix-community/fenix"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + flake-utils.url = "github:numtide/flake-utils"; + nixpkgs.url = "nixpkgs/nixos-unstable"; }; - outputs = attrs @ { self, nixpkgs, ... }: let - - system = "x86_64-linux"; + outputs = { self, fenix, flake-utils, nixpkgs }: + flake-utils.lib.eachDefaultSystem (system: { + packages.default = let + toolchain = fenix.packages.${system}.minimal.toolchain; + pkgs = import nixpkgs { + inherit system; + config.allowUnfree = true; + }; - pkgs = import nixpkgs { - inherit system; - config.allowUnfree = true; - }; + in (pkgs.makeRustPlatform { + cargo = toolchain; + rustc = toolchain; + }).buildRustPackage { + pname = "tabletbot"; + version = "0.1.0"; - in { + src = ./.; + nativeBuildInputs = with pkgs; [ pkg-config ]; - packages.${system} = rec { - tabletbot = pkgs.callPackage ./. {}; - default = tabletbot; - }; + buildInputs = with pkgs; [ openssl ]; - devShells.${system}.default = import ./shell.nix { inherit pkgs; }; - }; + cargoLock = { + lockFile = ./Cargo.lock; + outputHashes = { + "poise-0.5.7" = + "1jp2a1hzwv8946kpffii614wizza8kw27iya2pv2nhrb3a0hb3mw"; + }; + }; + }; + }); } diff --git a/shell.nix b/shell.nix index e007f75..e0e08ec 100644 --- a/shell.nix +++ b/shell.nix @@ -1,9 +1,11 @@ # https://nixos.wiki/wiki/Rust#Installation_via_rustup -{ pkgs ? import {} }: +{ pkgs ? import { } }: let - readFileIfExists = path: with pkgs.lib; if pathExists path then readFile path else null; + readFileIfExists = path: + with pkgs.lib; + if pathExists path then readFile path else null; - default = pkgs.callPackage ./. {}; + default = pkgs.callPackage ./. { }; rustDeps = with pkgs; [ llvmPackages_latest.llvm @@ -41,7 +43,7 @@ let in pkgs.mkShell rec { RUSTC_VERSION = readFileIfExists ./rust-toolchain; CARGO_HOME = toString ./.build/cargo; - CARGO_TARGET_DIR= toString ./.build/target; + CARGO_TARGET_DIR = toString ./.build/target; RUSTUP_HOME = toString ./.build/rustup; TABLETBOT_DATA = toString ./.build/data; diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 1234cac..05669e3 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -1,249 +1,48 @@ -use serenity::builder::CreateApplicationCommand; -use serenity::builder::CreateApplicationCommandOption; -use serenity::builder::CreateEmbed; -use serenity::http::Http; -use serenity::model::prelude::command::Command; -use serenity::model::prelude::command::CommandOptionType; -use serenity::model::prelude::interaction::application_command::ApplicationCommandInteraction; -use serenity::model::prelude::interaction::application_command::CommandDataOptionValue; -use serenity::prelude::Context; -use serenity::prelude::TypeMapKey; -use serenity::utils::Colour; -use std::collections::HashMap; -use crate::structures::State; - -mod snippets; -mod utils; - -pub async fn register(ctx: &Context) -> ApplicationCommandMap { - println!("Registering slash commands..."); - - let mut data = ctx.data.write().await; - let state = data.get_mut::() - .expect("Failed to get state"); - - let commands = ApplicationCommandMap::new(state); - - match commands.register(ctx).await { - Ok(c) => println!("Registered {} slash commands", c.len()), - Err(e) => println!("Failed to register slash commands: {}", e) - } - - commands -} - -pub async fn interact(ctx: &Context, interaction: &ApplicationCommandInteraction) { - let name = &interaction.data.name; - - interaction.defer(ctx).await.expect("Failed to defer interaction"); - - match name.as_str() { - "snippet" => snippets::snippet(ctx, interaction).await, - "create-snippet" => snippets::create_snippet(ctx, interaction).await, - "edit-snippet" => snippets::edit_snippet(ctx, interaction).await, - "remove-snippet" => snippets::remove_snippet(ctx, interaction).await, - "export-snippet" => snippets::export_snippet(ctx, interaction).await, - "embed" => utils::embed(ctx, interaction).await, - _ => { - println!("WARNING: Received invalid application command interaction!: {}", name); - - let title = "Invalid application command"; - let content = &format!("An invalid application command was recieved: {}", name); - respond_err(ctx, interaction, title, content).await; - } - } -} +pub mod snippets; +pub mod utils; pub(crate) const ACCENT_COLOUR: Colour = Colour(0x8957e5); pub(crate) const OK_COLOUR: Colour = Colour(0x2ecc71); pub(crate) const ERROR_COLOUR: Colour = Colour(0xe74c3c); -type CommandHashMap = HashMap<&'static str, CreateApplicationCommand>; - -#[derive(Clone)] -pub struct ApplicationCommandMap(pub CommandHashMap); - -impl TypeMapKey for ApplicationCommandMap { - type Value = ApplicationCommandMap; -} - -impl ApplicationCommandMap { - pub fn new(state: &State) -> ApplicationCommandMap { - let mut id_opt = CreateApplicationCommandOption::default(); - id_opt.name("id") - .description("The snippet's id") - .kind(CommandOptionType::String) - .required(true); - - let mut title_opt = CreateApplicationCommandOption::default(); - title_opt.name("title") - .description("The snippet's title") - .kind(CommandOptionType::String); - - let mut content_opt = CreateApplicationCommandOption::default(); - content_opt.name("content") - .description("The snippet's content") - .kind(CommandOptionType::String); - - let snippet = CreateApplicationCommand::default() - .description("Shows a snippet") - .clone(); +use serenity::model::Colour; - let create_snippet = CreateApplicationCommand::default() - .description("Creates a snippet") - .add_option(id_opt) - .add_option(title_opt.required(true).clone()) - .add_option(content_opt.required(true).clone()) - .clone(); +use crate::{Context, Error}; - let edit_snippet = CreateApplicationCommand::default() - .description("Edits a snippet") - .add_option(title_opt.required(false).clone()) - .add_option(content_opt.required(false).clone()) - .clone(); +use poise::serenity_prelude::{self as serenity, CreateEmbed}; - let remove_snippet = CreateApplicationCommand::default() - .description("Removes a snippet") - .clone(); +#[poise::command(prefix_command, hide_in_help)] +pub async fn register(ctx: Context<'_>) -> Result<(), Error> { + poise::builtins::register_application_commands_buttons(ctx).await?; - let export_snippet = CreateApplicationCommand::default() - .description("Exports a snippet for user editing") - .clone(); - - let embed = CreateApplicationCommand::default() - .description("Creates an embed in the current channel") - .create_option(|o| o - .name("title") - .description("The embed title") - .kind(CommandOptionType::String) - ) - .create_option(|o| o - .name("description") - .description("The embed description") - .kind(CommandOptionType::String) - ) - .create_option(|o| o - .name("color") - .description("The color of the embed in hexadecimal form. (ex: #ff00ff)") - .kind(CommandOptionType::String) - ) - .create_option(|o| o - .name("url") - .description("The embed url") - .kind(CommandOptionType::String) - ) - .create_option(|o| o - .name("footer") - .description("The embed footer text") - .kind(CommandOptionType::String) - ) - .create_option(|o| o - .name("image") - .description("The image url for the embed") - .kind(CommandOptionType::String) - ) - .clone(); - - let mut commands = ApplicationCommandMap(CommandHashMap::new()); - - commands.insert("snippet", snippet); - commands.insert("create-snippet", create_snippet); - commands.insert("edit-snippet", edit_snippet); - commands.insert("remove-snippet", remove_snippet); - commands.insert("export-snippet", export_snippet); - commands.insert("embed", embed); - - for (name, command) in commands.0.iter_mut() { - match *name { - "snippet" => snippets::sync_snippets(state, command), - "remove-snippet" => snippets::sync_snippets(state, command), - "export-snippet" => snippets::sync_snippets(state, command), - "edit-snippet" => snippets::sync_snippets(state, command), - _ => () - } - } - - commands - } - - fn insert(&mut self, k: &'static str, v: CreateApplicationCommand) -> Option { - self.0.insert(k, v) - } - - fn builders(&self) -> Vec { - self.0.iter() - .map(|p| { - let mut builder = p.1.clone(); - builder.name(p.0); - builder - }) - .collect::>() - } - - pub async fn register(&self, http: impl AsRef) -> Result, serenity::Error> { - Command::set_global_application_commands(http, |commands| { - commands.set_application_commands(self.builders()) - }).await - } + Ok(()) } -pub fn arg(interaction: &ApplicationCommandInteraction, name: &'static str) -> CommandDataOptionValue { - arg_opt(interaction, name).expect(&format!("No '{name}' argument provided")).clone() -} +pub async fn respond_embed(ctx: &Context<'_>, embed: CreateEmbed, ephemeral: bool) { + let builder = poise::CreateReply::default() + .embed(embed) + .ephemeral(ephemeral); + let result = ctx.send(builder).await; -pub fn arg_opt(interaction: &ApplicationCommandInteraction, name: &'static str) -> Option { - let opt = interaction.data.options.iter() - .find(|o| o.name == name); - - if let Some(opt) = opt { - opt.resolved.as_ref().cloned() - } else { - None - } -} - -pub async fn respond_embed(ctx: &Context, interaction: &ApplicationCommandInteraction, embed: &CreateEmbed, ephemeral: bool) { - let result = interaction.create_followup_message(ctx, |r| r - .add_embed(embed.clone()) - .ephemeral(ephemeral) - ).await; - - if let Err(e) = result { - println!("Failed to respond to interaction '{}': {:#?}", interaction.data.name, e) - } -} - -pub async fn respond_ok(ctx: &Context, interaction: &ApplicationCommandInteraction, title: &str, content: &str) { - let mut embed = CreateEmbed::default(); - let embed = embed - .title(title) - .description(content) - .colour(OK_COLOUR); - - respond_embed(ctx, interaction, embed, false).await; + if let Err(e) = result { + println!("Failed to respond: {}", e) + } } -pub async fn respond_err(ctx: &Context, interaction: &ApplicationCommandInteraction, title: &str, content: &str) { - let mut embed = CreateEmbed::default(); - let embed = embed - .title(title) - .description(content) - .colour(ERROR_COLOUR); +pub async fn respond_ok(ctx: &Context<'_>, title: &str, content: &str) { + let embed = CreateEmbed::default() + .title(title) + .description(content) + .colour(OK_COLOUR); - respond_embed(ctx, interaction, embed, false).await; + respond_embed(ctx, embed, false).await; } -pub async fn update_commands(ctx: &Context) { - let mut data = ctx.data.write().await; - let state = data.get_mut::() - .expect("Failed to get state"); - - println!("Updating commands..."); - let command_map = ApplicationCommandMap::new(state); +pub async fn respond_err(ctx: &Context<'_>, title: &str, content: &str) { + let embed = CreateEmbed::default() + .title(title) + .description(content) + .colour(ERROR_COLOUR); - println!("Registering newly updated commands"); - match command_map.register(ctx).await { - Ok(commands) => println!("Successfully updated {} commands", commands.len()), - Err(e) => println!("Failed to update commands: {e}") - } + respond_embed(ctx, embed, false).await; } diff --git a/src/commands/snippets.rs b/src/commands/snippets.rs index 7dd3bd1..c6a4a5f 100644 --- a/src/commands/snippets.rs +++ b/src/commands/snippets.rs @@ -1,239 +1,275 @@ -use core::panic; -use serenity::builder::{CreateEmbed, CreateApplicationCommandOption, CreateApplicationCommand}; -use serenity::json::Value; -use serenity::model::prelude::command::CommandOptionType; -use serenity::model::prelude::interaction::application_command::{ApplicationCommandInteraction, CommandDataOptionValue}; -use serenity::prelude::Context; -use crate::structures::{State, Snippet, Embeddable}; -use crate::commands::{arg, respond_ok}; - -use super::{respond_err, respond_embed, arg_opt}; - -pub(super) fn sync_snippets(state: &State, command: &mut CreateApplicationCommand) { - let mut id_option = CreateApplicationCommandOption::default(); - id_option.name("id") - .description("The snippet's id") - .kind(CommandOptionType::String) - .required(true); - - for snippet in state.snippets.iter().take(25) { - let name = format!("{}: {}", snippet.id, snippet.title); - id_option.add_string_choice(name, snippet.id.clone()); - } - - insert_option(command, 0, id_option); +use crate::{ + commands::{respond_embed, respond_err, respond_ok}, + structures::{Embeddable, Snippet}, + Context, Error, +}; +use ::serenity::futures::{Stream, StreamExt}; +use poise::serenity_prelude::{futures, Colour, CreateAttachment, CreateEmbed}; + +async fn autocomplete_snippet<'a>( + ctx: Context<'a>, + partial: &'a str, +) -> impl Stream + 'a { + let snippet_list: Vec = { + ctx.data() + .snip + .lock() + .unwrap() + .snippets + .iter() + .take(25) + .map(|s| format!("{}: {}", s.id, s.title)) + .collect() + }; + + futures::stream::iter(snippet_list) + .filter(move |name| futures::future::ready(name.starts_with(partial))) + .map(|name| name.to_string()) } -pub(super) async fn snippet(ctx: &Context, interaction: &ApplicationCommandInteraction) { - match arg(interaction, "id") { - CommandDataOptionValue::String(id) => { - if let Some(snippet) = get_snippet(ctx, &id).await { +/// Show a snippet +/// +/// Allows usage of both just the id and the formatted name (id: title) +#[poise::command(slash_command, prefix_command, guild_only, track_edits)] +pub async fn snippet( + ctx: Context<'_>, + #[rest] + #[description = "The snippet's id"] + #[autocomplete = "autocomplete_snippet"] + id: String, +) -> Result<(), Error> { + // Lazily get snippet because this is a prefix command too. + if let Some(snippet) = get_snippet_lazy(&ctx, &id).await { let embed = snippet.embed(); - respond_embed(ctx, interaction, &embed, false).await; - } else { - respond_err(ctx, interaction, "Failed to find snippet", &format!("Failed to find the snippet '{id}'")).await; - } - }, - _ => panic!("Invalid arguments provided to command: {}", &interaction.data.name) - } -} - -pub(super) async fn edit_snippet(ctx: &Context, interaction: &ApplicationCommandInteraction) { - let id = arg(interaction, "id"); - let title = arg_opt(interaction, "title"); - let content = arg_opt(interaction, "content"); - - if let CommandDataOptionValue::String(id) = id { - { - let mut data = ctx.data.write().await; - let state = data.get_mut::().expect("Failed to get state"); - - let snippet = state.snippets.iter_mut().find(|s| s.id.eq(&id)); - - if let Some(snippet) = snippet { - if let Some(CommandDataOptionValue::String(title)) = title { - snippet.title = title; - } - - if let Some(CommandDataOptionValue::String(content)) = content { - snippet.content = content; - } - - println!("Snippet edited '{}: {}'", &snippet.title, &snippet.content); - - state.write() - } else { - match (title, content) { - ( - Some(CommandDataOptionValue::String(title)), - Some(CommandDataOptionValue::String(content)) - ) => { - let snippet = Snippet { - id: id.clone(), - title: title.clone(), - content: content.replace(r#"\n"#, "\n") - }; - - println!("New snippet created '{}: {}'", id, title); - - state.snippets.push(snippet); - state.write() - }, - _ => { - let title = "Failed to edit snippet"; - let content = &format!("The snippet '{}' does not exist", &id); - return respond_err(ctx, interaction, title, content).await - } - } - } + respond_embed(&ctx, embed, false).await; + } else { + respond_err( + &ctx, + "Failed to find snippet", + &format!("Failed to find the snippet '{id}'"), + ) + .await; } - super::update_commands(ctx).await; - - let mut embed = get_snippet(ctx, &id).await - .expect("Failed to get snippet for recently modified snippet") - .embed(); - - embed.colour(super::OK_COLOUR); - - respond_embed(ctx, interaction, &embed, false).await; - } + Ok(()) } -pub(super) async fn create_snippet(ctx: &Context, interaction: &ApplicationCommandInteraction) { - let id = arg(interaction, "id"); - let title = arg(interaction, "title"); - let content = arg(interaction, "content"); - - match (id, title, content) { - ( - CommandDataOptionValue::String(id), - CommandDataOptionValue::String(title), - CommandDataOptionValue::String(content) - ) => { - let embed = { - let mut data = ctx.data.write().await; - let state = data.get_mut::().expect("Failed to get state"); - - if let Some(snippet) = state.snippets.iter().position(|s| s.id.eq(&id)) { - state.snippets.remove(snippet); +/// Creates a snippet +#[poise::command(rename = "create-snippet", slash_command, guild_only)] +pub async fn create_snippet( + ctx: Context<'_>, + #[description = "The snippet's id"] id: String, + #[description = "The snippet's title"] title: String, + #[description = "The snippet's content"] content: String, +) -> Result<(), Error> { + // I really don't like the code I wrote here. + let embed = { + let mut mutex_guard = ctx.data().snip.lock().unwrap(); + + if let Some(position) = mutex_guard.snippets.iter().position(|s| s.id.eq(&id)) { + mutex_guard.snippets.remove(position); } let snippet = Snippet { - id: id.clone(), - title: title.clone(), - content: content.replace(r#"\n"#, "\n") + id: id.clone(), + title: title.clone(), + content: content.replace(r"\n", "\n"), }; + mutex_guard.snippets.push(snippet.clone()); + + mutex_guard.snippets = mutex_guard.snippets.clone(); println!("New snippet created '{}: {}'", id, title); + mutex_guard.write(); let mut embed = snippet.embed(); - embed.colour(super::OK_COLOUR); - - state.snippets.push(snippet); - state.write(); - - if state.snippets.len() > 25 { - embed.field("Warning", "There are more than 25 snippets, some may not appear in the snippet list.", false); + embed = embed.colour(super::OK_COLOUR); + + if mutex_guard.snippets.len() > 25 { + embed = embed.field( + "Warning", + "There are more than 25 snippets, some may not appear in the snippet list.", + false, + ); } embed - }; + }; + + respond_embed(&ctx, embed, false).await; - super::update_commands(ctx).await; - respond_embed(ctx, interaction, &embed, false).await; - }, - _ => panic!("Invalid arguments provided to command: {}", &interaction.data.name) - } + Ok(()) } -pub(super) async fn remove_snippet(ctx: &Context, interaction: &ApplicationCommandInteraction) { - let id = arg(interaction, "id"); +/// Edits a snippet +#[poise::command(rename = "edit-snippet", slash_command, guild_only)] +pub async fn edit_snippet( + ctx: Context<'_>, + #[autocomplete = "autocomplete_snippet"] + #[description = "The snippet's id"] + id: String, + #[description = "The snippet's title"] title: Option, + #[description = "The snippet's content"] content: Option, +) -> Result<(), Error> { + match get_snippet(&ctx, &id).await { + Some(mut snippet) => { + if let Some(title) = title { + snippet.title = title; + } + + if let Some(content) = content { + snippet.content = content.replace(r"\n", "\n"); + } + + { + let mut mutex_guard = ctx.data().snip.lock().unwrap(); + mutex_guard.snippets.push(snippet.clone()); + println!("Snippet edited '{}: {}'", snippet.title, snippet.content); + mutex_guard.write(); + } + + let embed = snippet.embed().colour(super::OK_COLOUR); + respond_embed(&ctx, embed, false).await; + } + None => { + let title = &"Failed to edit snippet"; + let content = &&format!("The snippet '{id}' does not exist"); + respond_err(&ctx, title, content).await + } + }; - match id { - CommandDataOptionValue::String(id) => { - println!("Removing snippet '{id}'"); + Ok(()) +} - match get_snippet(ctx, &id).await { +/// Delete snippet +/// +/// Must use the full formatted snippet name (id: title) +#[poise::command(rename = "delete-snippet", slash_command, guild_only)] +pub async fn delete_snippet( + ctx: Context<'_>, + #[autocomplete = "autocomplete_snippet"] + #[description = "The snippet's id"] + id: String, +) -> Result<(), Error> { + match get_snippet(&ctx, &id).await { Some(snippet) => { - rm_snippet(ctx, &snippet).await; - super::update_commands(ctx).await; - - let title = &"Snippet successfully removed"; - let content = &&format!("Removed snippet '{}: {}'", snippet.id, snippet.title); - respond_ok(ctx, interaction, title, content).await; - }, + rm_snippet(&ctx, &snippet).await; + let title = &"Snippet successfully removed"; + let content = &&format!("Removed snippet '{}: {}'", snippet.id, snippet.title); + respond_ok(&ctx, title, content).await; + } None => { - let title = &"Failed to remove snippet"; - let content = &&format!("The snippet '{id}' does not exist"); - respond_err(ctx, interaction, title, content).await + let title = &"Failed to remove snippet"; + let content = &&format!("The snippet '{id}' does not exist"); + respond_err(&ctx, title, content).await } - } - }, - _ => panic!("Invalid arguments provided to command: {}", interaction.data.name) - } + } + + Ok(()) } -pub(super) async fn export_snippet(ctx: &Context, interaction: &ApplicationCommandInteraction) { - let id = arg(interaction, "id"); - - match id { - CommandDataOptionValue::String(id) => { - let snippet = get_snippet(ctx, &id).await - .expect("Failed to get snippet"); - - let result = interaction.create_followup_message(ctx, |r| r - .content(format!("```{}```", &snippet.content.replace("\n", r#"\n"#))) - .add_embed(snippet.embed()) - ).await; - - if let Err(e) = result { - println!("Failed to respond to interaction '{}': {:#?}", interaction.data.name, e) - } - }, - _ => panic!("Invalid arguments provided to command: {}", interaction.data.name) - } +/// Lists all snippets +#[poise::command( + rename = "list-snippets", + slash_command, + prefix_command, + guild_only, + track_edits +)] +pub async fn list_snippets(ctx: Context<'_>) -> Result<(), Error> { + let snippets = { ctx.data().snip.lock().unwrap().snippets.clone() }; + + let mut embed = CreateEmbed::default().title("Snippets").color(Colour::TEAL); + + // fields are limited to 25 max, we can't display more than 25 snippets in the snippets command + // due to a discord limitation. + for snippet in snippets.iter().take(25) { + embed = embed.field(format!("`{}`", snippet.id), &snippet.title, false); + } + + ctx.send(poise::CreateReply::default().embed(embed)).await?; + + Ok(()) +} + +/// Exports a snippet for user editing. +/// +/// Allows usage of both just the id and the formatted name (id: title) +#[poise::command(rename = "export-snippet", slash_command, prefix_command, guild_only)] +pub async fn export_snippet( + ctx: Context<'_>, + #[rest] + #[autocomplete = "autocomplete_snippet"] + #[description = "The snippet's id"] + id: String, +) -> Result<(), Error> { + match get_snippet_lazy(&ctx, &id).await { + Some(snippet) => { + let attachment = CreateAttachment::bytes( + format!("{}", &snippet.content.replace('\n', r"\n")), + "snippet.txt", + ); + let message = poise::CreateReply::default() + .attachment(attachment) + .embed(snippet.embed()); + ctx.send(message).await?; + } + None => { + let title = &"Failed to export snippet"; + let content = &&format!("The snippet '{id}' does not exist"); + respond_err(&ctx, title, content).await + } + } + + Ok(()) } impl Embeddable for Snippet { - fn embed(&self) -> CreateEmbed { - CreateEmbed::default() - .title(&self.title) - .description(&self.content) - .colour(super::ACCENT_COLOUR) - .clone() - } + fn embed(&self) -> CreateEmbed { + CreateEmbed::default() + .title(&self.title) + .description(&self.content) + .colour(super::ACCENT_COLOUR) + .clone() + } } -async fn get_snippet(ctx: &Context, id: &str) -> Option { - let data = ctx.data.read().await; - let state = data.get::().expect("Failed to get state"); +// Exact matches the snippet id and name. +async fn get_snippet(ctx: &Context<'_>, id: &str) -> Option { + let data = ctx.data(); + let mutex_guard = data.snip.lock().unwrap(); - state.snippets.iter() - .find(|s| s.id.eq(id)) - .cloned() + mutex_guard + .snippets + .iter() + .find(|s| s.format_output().eq(id)) + .cloned() } -async fn rm_snippet(ctx: &Context, snippet: &Snippet) { - let mut data = ctx.data.write().await; - let state = data.get_mut::() - .expect("Failed to get state"); +// Matches the snippet by checking if its starts with the id and name. +async fn get_snippet_lazy(ctx: &Context<'_>, id: &str) -> Option { + let data = ctx.data(); + let mutex_guard = data.snip.lock().unwrap(); - let index = state.snippets.iter() - .position(|s| s.id == snippet.id) - .expect("Snippet was not found in vec"); - - println!("Removing snippet '{}: {}'", snippet.id, snippet.title); - state.snippets.remove(index); - state.write(); + mutex_guard + .snippets + .iter() + .find(|s| s.format_output().starts_with(id)) + .cloned() } -fn insert_option(command: &mut CreateApplicationCommand, index: usize, option: CreateApplicationCommandOption) -> &mut CreateApplicationCommand { - let new_option = serenity::json::hashmap_to_json_map(option.0); - let options = command.0.entry("options").or_insert_with(|| Value::from(Vec::::new())); - let opt_arr = options.as_array_mut().expect("Must be an array"); - opt_arr.insert(index, Value::from(new_option)); +async fn rm_snippet(ctx: &Context<'_>, snippet: &Snippet) { + let data = ctx.data(); + let mut mutex_guard = data.snip.lock().unwrap(); + + let index = mutex_guard + .snippets + .iter() + .position(|s| s.id == snippet.id) + .expect("Snippet was not found in vec"); - command + println!("Removing snippet '{}: {}'", snippet.id, snippet.title); + mutex_guard.snippets.remove(index); + mutex_guard.write(); } diff --git a/src/commands/utils.rs b/src/commands/utils.rs index 623f44f..6dafc40 100644 --- a/src/commands/utils.rs +++ b/src/commands/utils.rs @@ -1,81 +1,93 @@ -use serenity::builder::CreateEmbed; -use serenity::model::prelude::interaction::application_command::{ApplicationCommandInteraction, CommandDataOptionValue}; -use serenity::prelude::Context; -use serenity::utils::Colour; +use crate::{ + commands::{respond_embed, respond_err}, + Context, Error, +}; -use super::{arg_opt, respond_err, respond_embed}; +use poise::serenity_prelude::{Colour, CreateEmbed, CreateEmbedFooter}; -pub(super) async fn embed(ctx: &Context, interaction: &ApplicationCommandInteraction) { - let title = arg_opt(interaction, "title"); - let description = arg_opt(interaction, "description"); - let color = arg_opt(interaction, "color"); - let url = arg_opt(interaction, "url"); - let footer_text = arg_opt(interaction, "footer"); - let image = arg_opt(interaction, "image"); +/// Create an embed in the current channel. +#[allow(clippy::too_many_arguments)] +#[poise::command(slash_command, guild_only)] +pub async fn embed( + ctx: Context<'_>, + #[description = "The embed title"] title: Option, + #[description = "The embed description"] description: Option, + #[description = "The color of the embed in hexidecimal form. (ex: ff00ff)"] color: Option< + String, + >, + #[description = "The embed url"] url: Option, + #[description = "The embed image"] image: Option, + #[description = "The embed footer text"] footer: Option, + #[description = "The embed thumbnail"] thumbnail: Option, +) -> Result<(), Error> { + let at_least_one_property_set = title.is_some() + || description.is_some() + || image.is_some() + || thumbnail.is_some() + || footer.is_some(); - let mut embed = CreateEmbed::default(); + let url_invalid = url.is_some() && title.is_none(); - if let Some(CommandDataOptionValue::String(title)) = title { - embed.title(title); - } + if !at_least_one_property_set { + respond_err( + &ctx, + "Failed to respond with embed", + "Please provide at least one title, description, image, footer or thumbnail", + ) + .await; + ctx.say("You must provide at least one title, description, image or thumbnail.") + .await?; + return Ok(()); + } + + if url_invalid { + respond_err( + &ctx, + "Failed to respond with embed", + "To set a url, you must set a title", + ) + .await; + return Ok(()); + } - if let Some(CommandDataOptionValue::String(description)) = description { - embed.description(description); - } + let mut embed = CreateEmbed::default(); - if let Some(CommandDataOptionValue::String(color)) = &color { - match hex::decode(color.to_ascii_lowercase().replace("#", "")) { - Ok(hex_arr) => { - embed.color(Colour::from_rgb(hex_arr[0], hex_arr[1], hex_arr[2])); - }, - Err(e) => { - let title = "Invalid color provided"; - let content = &format!("The color '{}' is not a valid hexadecimal color: {}", &color, e); - return respond_err(ctx, interaction, title, content).await - } + if let Some(title) = title { + embed = embed.title(title); } - } - if let Some(CommandDataOptionValue::String(url)) = url { - match url.parse::() { - Ok(_) => { - if embed.0.contains_key("title") { - embed.url(url); - } else { - let title = "Invalid parameters"; - let content = "A title is required for a url to function"; - return respond_err(ctx, interaction, title, content).await - } - }, - Err(e) => { - let title = "Invalid url provided"; - let content = &format!("The url '{}' is not a valid url: {}", url, e); - return respond_err(ctx, interaction, title, content).await - } + if let Some(description) = description { + embed = embed.description(description.replace(r"\n", "\n")); + } + if let Some(image) = image { + embed = embed.image(image); } - } - if let Some(CommandDataOptionValue::String(footer_text)) = footer_text { - embed.footer(|f| f.text(footer_text)); - } + if let Some(footer) = footer { + embed = embed.footer(CreateEmbedFooter::new(footer)); + } - if let Some(CommandDataOptionValue::String(image)) = image { - match image.parse::() { - Ok(_) => { - embed.image(image); - }, - Err(e) => { - let title = "Invalid image url provided"; - let content = &format!("The image url '{}' is not a valid image url: {}", image, e); + if let Some(thumbnail) = thumbnail { + embed = embed.thumbnail(thumbnail); + } - return respond_err(ctx, interaction, title, content).await - } + if let Some(color) = color { + match hex::decode(color.to_ascii_lowercase().replace('#', "")) { + Ok(hex_arr) => { + embed = embed.color(Colour::from_rgb(hex_arr[0], hex_arr[1], hex_arr[2])); + } + Err(e) => { + let title = "Invalid color provided"; + let content = &format!( + "The color '{}' is not a valid hexadecimal color: {}", + &color, e + ); + respond_err(&ctx, title, content).await; + } + } } - } - if embed.0.contains_key("title") || embed.0.contains_key("description") || embed.0.contains_key("footer") { - respond_embed(ctx, interaction, &embed, false).await - } else { - respond_err(ctx, interaction, "Failed to respond with embed", "Embed does not have any content").await - } + respond_embed(&ctx, embed, false).await; + + Ok(()) } diff --git a/src/events/code.rs b/src/events/code.rs index 80c13e4..c1d81d5 100644 --- a/src/events/code.rs +++ b/src/events/code.rs @@ -1,151 +1,157 @@ +use regex::{Match, Regex}; use std::path::Path; use std::str::FromStr; -use regex::{Regex, Match}; -use serenity::builder::CreateEmbed; -use serenity::model::prelude::Message; -use serenity::prelude::Context; -use serenity::utils::Colour; -use crate::formatting::*; -const ACCENT_COLOUR: Colour = Colour(0x8957e5); +use poise::serenity_prelude::{self as serenity, Colour, Context, CreateEmbed, Message}; + +use crate::formatting::trim_indent; + +const ACCENT_COLOUR: Colour = Colour::new(0x8957e5); pub async fn message(ctx: &Context, message: &Message) { - if let Some(embeds) = get_embeds(ctx, message).await { - message.channel_id.send_message(&ctx.http, |f| f - .add_embeds(embeds) - ).await.expect("Failed to reply to code message"); - } + if let Some(embeds) = get_embeds(ctx, message).await { + let typing = message.channel_id.start_typing(&ctx.http); + + let content = serenity::CreateMessage::default() + .embeds(embeds) + .reference_message(message); + let _ = message.channel_id.send_message(ctx, content).await; + + typing.stop(); + } } async fn get_embeds(ctx: &Context, message: &Message) -> Option> { - let typing = message.channel_id.start_typing(&ctx.http).expect("Failed to start typing"); - let mut embeds: Vec = vec![]; - - if let Some(refs) = FileReference::try_from_str(&message.content) { - for file_ref in refs { - if let Some(embed) = file_ref.create_embed().await { - embeds.push(embed); - } + let typing = message.channel_id.start_typing(&ctx.http); + let mut embeds: Vec = vec![]; + + if let Some(refs) = FileReference::try_from_str(&message.content) { + for file_ref in refs { + if let Some(embed) = file_ref.create_embed().await { + embeds.push(embed); + } + } } - } - typing.stop().expect("Failed to stop typing"); + typing.stop(); - if !embeds.is_empty() { - Some(embeds) - } else { - None - } + if !embeds.is_empty() { + Some(embeds) + } else { + None + } } async fn http_get_body_text(url: &String) -> Option { - match reqwest::get(url).await { - Ok(res) => { - match res.text().await { - Ok(content) => Some(content), + match reqwest::get(url).await { + Ok(res) => match res.text().await { + Ok(content) => Some(content), + Err(e) => { + println!("Failed to get text: {}", e); + None + } + }, Err(e) => { - println!("Failed to get text: {}", e); - return None + println!("Failed to get response: {}", e); + None } - } - }, - Err(e) => { - println!("Failed to get response: {}", e); - return None } - } } fn try_parse(m: Option) -> Option { - if let Some(m) = m { - if let Ok(f) = m.as_str().parse::() { - return Some(f.to_owned()) + if let Some(m) = m { + if let Ok(f) = m.as_str().parse::() { + return Some(f.to_owned()); + } } - } - None + None } struct FileReference<'a> { - owner: &'a str, - repo: &'a str, - git_ref: &'a str, - path: &'a str, - start: usize, - end: Option + owner: &'a str, + repo: &'a str, + git_ref: &'a str, + path: &'a str, + start: usize, + end: Option, } impl FileReference<'_> { - pub fn try_from_str(text: &str) -> Option> { - let r = Regex::new(r"https://github.com/(.+?)/(.+?)/blob/(.+?)/(.+?)#L([0-9]+)(?:-L([0-9]+))?") - .expect("Expected url regex"); - - let files: Vec = r.captures_iter(text) - .map(|capture| { - FileReference { - owner: capture.get(1).expect("Expected owner").as_str(), - repo: capture.get(2).expect("Expected repo").as_str(), - git_ref: capture.get(3).expect("Expected git ref").as_str(), - path: capture.get(4).expect("Expected file path").as_str(), - start: try_parse::(capture.get(5)).expect("Expected start line"), - end: try_parse::(capture.get(6)) + pub fn try_from_str(text: &str) -> Option> { + let r = + Regex::new(r"https://github.com/(.+?)/(.+?)/blob/(.+?)/(.+?)#L([0-9]+)(?:-L([0-9]+))?") + .expect("Expected url regex"); + + let files: Vec = r + .captures_iter(text) + .map(|capture| FileReference { + owner: capture.get(1).expect("Expected owner").as_str(), + repo: capture.get(2).expect("Expected repo").as_str(), + git_ref: capture.get(3).expect("Expected git ref").as_str(), + path: capture.get(4).expect("Expected file path").as_str(), + start: try_parse::(capture.get(5)).expect("Expected start line"), + end: try_parse::(capture.get(6)), + }) + .collect(); + + if !files.is_empty() { + Some(files) + } else { + None } - }) - .collect(); - - if !files.is_empty() { - Some(files) - } else { - None } - } - pub async fn create_embed(&self) -> Option { - let extension = self.get_extension(); + pub async fn create_embed(&self) -> Option { + let extension = self.get_extension(); - if let Some(mut content) = self.display().await { - content.shrink_to(4096 - 8 - extension.len()); + if let Some(mut content) = self.display().await { + content.shrink_to(4096 - 8 - extension.len()); - let description = format!("```{}\n{}\n```", extension, content); + let description = format!("```{}\n{}\n```", extension, content); - let mut default = CreateEmbed::default(); - default.title(self.path) - .description(description) - .colour(ACCENT_COLOUR); + let mut default = CreateEmbed::default(); + default = default + .title(self.path) + .description(description) + .colour(ACCENT_COLOUR); - Some(default) - } else { - None + Some(default) + } else { + None + } + } + + fn get_extension(&self) -> String { + Path::new(self.path) + .extension() + .unwrap_or_default() + .to_string_lossy() + .to_string() } - } - - fn get_extension(&self) -> String { - Path::new(self.path) - .extension() - .unwrap_or_default() - .to_string_lossy() - .to_string() - } - - pub async fn display(&self) -> Option { - let url = format!("https://raw.githubusercontent.com/{}/{}/{}/{}", self.owner, self.repo, self.git_ref, self.path); - println!("Downloading content: {}", url); - - if let Some(content) = http_get_body_text(&url).await { - let lines: Vec<&str> = content.split("\n").collect(); - let start = self.start - 1; - - if let Some(end) = self.end { - if end <= start { - None + + pub async fn display(&self) -> Option { + let url = format!( + "https://raw.githubusercontent.com/{}/{}/{}/{}", + self.owner, self.repo, self.git_ref, self.path + ); + println!("Downloading content: {}", url); + + if let Some(content) = http_get_body_text(&url).await { + let lines: Vec<&str> = content.split('\n').collect(); + let start = self.start - 1; + + if let Some(end) = self.end { + if end <= start { + None + } else { + Some(trim_indent(&lines[start..end])) + } + } else { + Some(lines[start].trim_start().to_string()) + } } else { - Some(trim_indent(&lines[start..end])) + None } - } else { - Some(lines[start].trim_start().to_string()) - } - } else { - None } - } } diff --git a/src/events/issue.rs b/src/events/issue.rs index 10300fa..9b4374d 100644 --- a/src/events/issue.rs +++ b/src/events/issue.rs @@ -1,215 +1,214 @@ +use ::serenity::builder::CreateEmbedAuthor; use octocrab::models::issues::Issue; use octocrab::models::pulls::PullRequest; +use poise::serenity_prelude::{self as serenity, Colour, Context, CreateEmbed, Message}; use regex::Regex; -use serenity::builder::CreateEmbed; -use serenity::model::prelude::Message; -use serenity::prelude::Context; -use serenity::utils::Colour; + use crate::structures::Embeddable; const REPO_OWNER: &str = "OpenTabletDriver"; const REPO_NAME: &str = "OpenTabletDriver"; -const OPEN_COLOUR: Colour = Colour(0x238636); -const RESOLVED_COLOUR: Colour = Colour(0x8957e5); -const CLOSED_COLOUR: Colour = Colour(0xda3633); +const OPEN_COLOUR: Colour = Colour::new(0x238636); +const RESOLVED_COLOUR: Colour = Colour::new(0x8957e5); +const CLOSED_COLOUR: Colour = Colour::new(0xda3633); pub async fn message(ctx: &Context, message: &Message) { - if let Some(embeds) = issue_embeds(message).await { - let typing = message.channel_id.start_typing(&ctx.http) - .expect("Failed to start typing"); + if let Some(embeds) = issue_embeds(message).await { + let typing = message.channel_id.start_typing(&ctx.http); - message.channel_id.send_message(&ctx.http, |f| f - .reference_message(message) - .set_embeds(embeds) - ).await.expect("Failed to reply with github embed"); + let content = serenity::CreateMessage::default() + .embeds(embeds) + .reference_message(message); + let _ = message.channel_id.send_message(ctx, content).await; - typing.stop().expect("Failed to stop typing"); - } + typing.stop(); + } } async fn issue_embeds(message: &Message) -> Option> { - let mut embeds: Vec = vec![]; - let client = octocrab::instance(); - let ratelimit = client.ratelimit(); - let issues = client.issues(REPO_OWNER, REPO_NAME); - let prs = client.pulls(REPO_OWNER, REPO_NAME); - - let regex = Regex::new(r#" ?#([0-9]+[0-9]) ?"#) - .expect("Expected numbers regex"); - - for capture in regex.captures_iter(&message.content) { - if let Some(m) = capture.get(1) { - let issue_num = m.as_str().parse::() - .expect("Match is not a number"); - - let ratelimit = ratelimit.get().await - .expect("Failed to get github rate limit"); - - if ratelimit.rate.remaining > 2 { - if let Ok(pr) = prs.get(issue_num).await { - embeds.push(pr.embed()); - } else if let Ok(issue) = issues.get(issue_num).await { - embeds.push(issue.embed()); + let mut embeds: Vec = vec![]; + let client = octocrab::instance(); + let ratelimit = client.ratelimit(); + + let issues = client.issues(REPO_OWNER, REPO_NAME); + let prs = client.pulls(REPO_OWNER, REPO_NAME); + + let regex = Regex::new(r#" ?#([0-9]+[0-9]) ?"#).expect("Expected numbers regex"); + + for capture in regex.captures_iter(&message.content) { + if let Some(m) = capture.get(1) { + let issue_num = m.as_str().parse::().expect("Match is not a number"); + + let ratelimit = ratelimit + .get() + .await + .expect("Failed to get github rate limit"); + + if ratelimit.rate.remaining > 2 { + if let Ok(pr) = prs.get(issue_num).await { + embeds.push(pr.embed()); + } else if let Ok(issue) = issues.get(issue_num).await { + embeds.push(issue.embed()); + } + } } - } } - } - if embeds.is_empty() { - None - } else { - Some(embeds) - } + if embeds.is_empty() { + None + } else { + Some(embeds) + } } trait Document { - fn get_title(&self) -> String; - fn get_content(&self) -> String; - fn get_colour(&self) -> Colour; - fn get_labels(&self) -> Option; + fn get_title(&self) -> String; + fn get_content(&self) -> String; + fn get_colour(&self) -> Colour; + fn get_labels(&self) -> Option; } impl Embeddable for Issue { - fn embed(&self) -> CreateEmbed { - let mut default = CreateEmbed::default(); - let embed = default - .title(self.get_title()) - .description(self.get_content()) - .url(self.html_url.as_str()) - .colour(self.get_colour()) - .author(|a| a - .name(&self.user.login) - .url(&self.user.url) - .icon_url(&self.user.avatar_url) - ); - - if let Some(milestone) = &self.milestone { - embed.field("Milestone", &milestone.title, true); - } + fn embed(&self) -> CreateEmbed { + let default = CreateEmbed::default(); + let author = CreateEmbedAuthor::new(&self.user.login) + .url(self.user.url.clone()) + .icon_url(self.user.avatar_url.clone()); + let mut embed = default + .title(self.get_title()) + .description(self.get_content()) + .url(self.html_url.as_str()) + .colour(self.get_colour()) + .author(author); + + if let Some(milestone) = &self.milestone { + embed = embed.field("Milestone", &milestone.title, true); + } - if let Some(labels) = self.get_labels() { - embed.field("Labels", labels, true); - } + if let Some(labels) = self.get_labels() { + embed = embed.field("Labels", labels, true); + } - embed.to_owned() - } + embed + } } impl Document for Issue { - fn get_title(&self) -> String { - format!("#{}: {}", self.number, self.title) - } - - fn get_content(&self) -> String { - let body = self.body.as_deref().unwrap_or_default(); - - let mut description = String::default(); - for line in body.split("\n").take(15) { - description.push_str(&format!("{}\n", line)); + fn get_title(&self) -> String { + format!("#{}: {}", self.number, self.title) } - description.shrink_to(4096); - description - } + fn get_content(&self) -> String { + let body = self.body.as_deref().unwrap_or_default(); + + let mut description = String::default(); + for line in body.split('\n').take(15) { + description.push_str(&format!("{}\n", line)); + } - fn get_colour(&self) -> Colour { - match self.closed_at { - Some(_) => CLOSED_COLOUR, - None => OPEN_COLOUR + description.shrink_to(4096); + description } - } - fn get_labels(&self) -> Option { - if !self.labels.is_empty() { - let labels = &self.labels.iter() - .map(|l| l.name.clone()) - .collect::>(); + fn get_colour(&self) -> Colour { + match self.closed_at { + Some(_) => CLOSED_COLOUR, + None => OPEN_COLOUR, + } + } - Some(format!("`{}`", labels.join("`, `"))) - } else { - None + fn get_labels(&self) -> Option { + if !self.labels.is_empty() { + let labels = &self + .labels + .iter() + .map(|l| l.name.clone()) + .collect::>(); + + Some(format!("`{}`", labels.join("`, `"))) + } else { + None + } } - } } impl Embeddable for PullRequest { - fn embed(&self) -> CreateEmbed { - let mut description = self.body.clone().unwrap_or_default(); - description.shrink_to(4096); - - let mut default = CreateEmbed::default(); - let embed = default - .title(self.get_title()) - .description(self.get_content()) - .colour(self.get_colour()); - - if let Some(user) = &self.user { - embed.author(|a| a - .name(&user.login) - .url(&user.url) - .icon_url(&user.avatar_url) - ); - } + fn embed(&self) -> CreateEmbed { + let mut description = self.body.clone().unwrap_or_default(); + description.shrink_to(4096); + + let default = CreateEmbed::default(); + let mut embed = default + .title(self.get_title()) + .description(self.get_content()) + .colour(self.get_colour()); + + if let Some(user) = &self.user { + let author = CreateEmbedAuthor::new(user.login.clone()) + .url(user.url.clone()) + .icon_url(user.avatar_url.clone()); + embed = embed.author(author); + } - if let Some(url) = &self.html_url { - embed.url(url.as_str()); - } + if let Some(url) = &self.html_url { + embed = embed.url(url.as_str()); + } - if let Some(milestone) = &self.milestone { - embed.field("Milestone", &milestone.title, true); - } + if let Some(milestone) = &self.milestone { + embed = embed.field("Milestone", &milestone.title, true); + } - if let Some(labels) = self.get_labels() { - embed.field("Labels", labels, true); - } + if let Some(labels) = self.get_labels() { + embed = embed.field("Labels", labels, true); + } - embed.to_owned() - } + embed.to_owned() + } } - impl Document for PullRequest { - fn get_title(&self) -> String { - match &self.title { - Some(title) => format!("#{}: {}", self.number, title), - None => format!("#{}", self.number) + fn get_title(&self) -> String { + match &self.title { + Some(title) => format!("#{}: {}", self.number, title), + None => format!("#{}", self.number), + } } - } - fn get_content(&self) -> String { - let body = self.body.as_deref().unwrap_or_default(); + fn get_content(&self) -> String { + let body = self.body.as_deref().unwrap_or_default(); + + let mut content = String::default(); + for line in body.split('\n').take(15) { + content.push_str(&format!("{}\n", line)); + } - let mut content = String::default(); - for line in body.split("\n").take(15) { - content.push_str(&format!("{}\n", line)); + content.shrink_to(4096); + content } - content.shrink_to(4096); - content - } - - fn get_colour(&self) -> Colour { - match self.closed_at { - Some(_) => match self.merged_at { - Some(_) => RESOLVED_COLOUR, - None => CLOSED_COLOUR - }, - None => OPEN_COLOUR + fn get_colour(&self) -> Colour { + match self.closed_at { + Some(_) => match self.merged_at { + Some(_) => RESOLVED_COLOUR, + None => CLOSED_COLOUR, + }, + None => OPEN_COLOUR, + } } - } - fn get_labels(&self) -> Option { - if let Some(labels) = &self.labels { - if !labels.is_empty() { - let labels = labels.iter() - .map(|l| l.name.clone()) - .collect::>(); + fn get_labels(&self) -> Option { + if let Some(labels) = &self.labels { + if !labels.is_empty() { + let labels = labels + .iter() + .map(|l| l.name.clone()) + .collect::>(); - return Some(format!("`{}`", labels.join("`, `"))) - } - } + return Some(format!("`{}`", labels.join("`, `"))); + } + } - None - } + None + } } diff --git a/src/events/mod.rs b/src/events/mod.rs index 71a3071..4984a56 100644 --- a/src/events/mod.rs +++ b/src/events/mod.rs @@ -1,12 +1,20 @@ use serenity::model::prelude::Message; -use serenity::prelude::Context; -pub mod issue; +use poise::serenity_prelude as serenity; + +use crate::{Data, Error}; + pub mod code; +pub mod issue; -pub async fn message(ctx: &Context, msg: &Message) { - if !msg.author.bot { - issue::message(&ctx, &msg).await; - code::message(&ctx, &msg).await; - } +pub async fn message( + ctx: &serenity::Context, + new_message: Message, + _data: &Data, +) -> Result<(), Error> { + if !new_message.author.bot { + issue::message(ctx, &new_message).await; + code::message(ctx, &new_message).await; + } + Ok(()) } diff --git a/src/formatting.rs b/src/formatting.rs index 22a1162..188a178 100644 --- a/src/formatting.rs +++ b/src/formatting.rs @@ -1,31 +1,33 @@ pub fn trim_indent(lines: &[&str]) -> String { - let base_indent = get_base_indent(lines); - let prefix = String::from_iter(std::iter::repeat(' ').take(base_indent)); + let base_indent = get_base_indent(lines); + let prefix = String::from_iter(std::iter::repeat(' ').take(base_indent)); - let trimmed_lines: Vec<&str> = lines.iter() - .map(move |line| line.strip_prefix(&prefix).unwrap_or(line)) - .collect(); + let trimmed_lines: Vec<&str> = lines + .iter() + .map(move |line| line.strip_prefix(&prefix).unwrap_or(line)) + .collect(); - trimmed_lines.join("\n") + trimmed_lines.join("\n") } fn get_base_indent(lines: &[&str]) -> usize { - lines.iter() - .filter(|p| !p.is_empty()) - .map(|l| get_indent(l)) - .min() - .expect("Failed to get base indent") + lines + .iter() + .filter(|p| !p.is_empty()) + .map(|l| get_indent(l)) + .min() + .expect("Failed to get base indent") } fn get_indent(line: &str) -> usize { - let mut i = 0; - for c in line.chars() { - if c.is_whitespace() { - i += 1; - } else { - return i + let mut i = 0; + for c in line.chars() { + if c.is_whitespace() { + i += 1; + } else { + return i; + } } - } - 0 + 0 } diff --git a/src/main.rs b/src/main.rs index 465ca2d..92c0ed5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,107 +1,114 @@ -pub(crate) mod structures; pub(crate) mod commands; pub(crate) mod events; pub(crate) mod formatting; +pub(crate) mod structures; + +use std::sync::Mutex; +use std::time::Duration; +use std::{env, sync::Arc}; use octocrab::Octocrab; -use serenity::async_trait; -use serenity::framework::StandardFramework; -use serenity::http::Http; -use serenity::model::application::interaction::*; -use serenity::model::prelude::{Message, Ready, UserId}; -use serenity::prelude::*; -use std::collections::HashSet; -use std::env; -use crate::structures::*; +use poise::serenity_prelude::{self as serenity, GatewayIntents}; +use structures::SnippetState; + +pub struct Data { + pub octocrab: Arc, + pub snip: Mutex, +} +type Error = Box; +type Context<'a> = poise::Context<'a, Data, Error>; + +async fn on_error(error: poise::FrameworkError<'_, Data, Error>) { + match error { + poise::FrameworkError::Setup { error, .. } => panic!("Failed to start bot: {:?}", error), + poise::FrameworkError::Command { error, ctx, .. } => { + println!("Error in command `{}`: {:?}", ctx.command().name, error,); + } + error => { + if let Err(e) = poise::builtins::on_error(error).await { + println!("Error while handling error: {}", e) + } + } + } +} #[tokio::main] async fn main() { - let discord_token = env::var("DISCORD_TOKEN").expect("Expected discord api token"); - let github_token = env::var("GITHUB_TOKEN").expect("Expected github api token"); - - let http = Http::new(&discord_token); - - let (owners, bot_id) = match http.get_current_application_info().await { - Ok(info) => { - let mut owners = HashSet::new(); - owners.insert(info.owner.id); - (owners, info.id) - }, - Err(why) => panic!("Could not access application info: {:?}", why), - }; - - let framework = StandardFramework::new() - .configure(|configuration| { - configuration - .on_mention(Some(UserId(*bot_id.as_u64()))) - .owners(owners) - .prefix("!") + let discord_token = env::var("DISCORD_TOKEN").expect("Expected discord api token"); + let github_token = env::var("GITHUB_TOKEN").expect("Expected github api token"); + + let octo_builder = Octocrab::builder().personal_token(github_token); + + let octocrab = octocrab::initialise(octo_builder).expect("Failed to build github client"); + + let snip = Mutex::new(structures::SnippetState::read()); + + let options = poise::FrameworkOptions { + commands: vec![ + commands::register(), + commands::snippets::snippet(), + commands::snippets::create_snippet(), + commands::snippets::delete_snippet(), + commands::snippets::export_snippet(), + commands::snippets::list_snippets(), + commands::snippets::edit_snippet(), + commands::utils::embed(), + ], + prefix_options: poise::PrefixFrameworkOptions { + prefix: Some("!".into()), + edit_tracker: Some(Arc::new(poise::EditTracker::for_timespan( + Duration::from_secs(600), + ))), + ..Default::default() + }, + on_error: |error| Box::pin(on_error(error)), + + pre_command: |ctx| { + Box::pin(async move { + println!("Executing command {}...", ctx.command().qualified_name); + }) + }, + + skip_checks_for_owners: false, + event_handler: |ctx, event: &serenity::FullEvent, framework, data| { + Box::pin(event_handler(ctx, event.clone(), framework, data)) + }, + ..Default::default() + }; + + let framework = poise::Framework::new(options, move |ctx, ready, framework| { + Box::pin(async move { + println!("Logged in as {}", ready.user.name); + poise::builtins::register_globally(ctx, &framework.options().commands).await?; + Ok(Data { octocrab, snip }) + }) }); - let intents = GatewayIntents::GUILD_MESSAGES - | GatewayIntents::DIRECT_MESSAGES - | GatewayIntents::MESSAGE_CONTENT; - - let mut client = Client::builder(&discord_token, intents) - .framework(framework) - .event_handler(Handler) - .await - .expect("Error creating client"); - - let octo_builder = Octocrab::builder() - .personal_token(github_token); - - octocrab::initialise(octo_builder) - .expect("Failed to build github client"); + let intents = GatewayIntents::GUILD_MESSAGES + | GatewayIntents::DIRECT_MESSAGES + | GatewayIntents::MESSAGE_CONTENT; - { - let mut data = client.data.write().await; - data.insert::(State::read()); - data.insert::(client.shard_manager.clone()); - } + let mut client = serenity::Client::builder(discord_token, intents) + .framework(framework) + .await + .unwrap(); - let shard_manager = client.shard_manager.clone(); - tokio::spawn(async move { - tokio::signal::ctrl_c().await.expect("Could not register ctrl+c handler"); - - println!("Disconnecting"); - shard_manager.lock().await.shutdown_all().await; - }); - - if let Err(why) = client.start().await { - println!("Client error: {:?}", why); - } + client.start().await.unwrap(); } -struct Handler; - -#[async_trait] -impl EventHandler for Handler { - async fn ready(&self, ctx: Context, ready: Ready) { - println!("Connected to Discord API as bot user '{}#{:04}'", ready.user.name, ready.user.discriminator); - - commands::register(&ctx).await; - } - - async fn message(&self, ctx: Context, msg: Message) { - let mut channel_name = "N/A".to_string(); - - if let Ok(channel) = msg.channel(&ctx).await { - if let Some(guild_channel) = channel.guild() { - channel_name = guild_channel.name.clone(); - } +pub async fn event_handler( + ctx: &serenity::Context, + event: serenity::FullEvent, + _framework: poise::FrameworkContext<'_, Data, Error>, + data: &Data, +) -> Result<(), Error> { + #[allow(clippy::single_match)] + match event { + serenity::FullEvent::Message { new_message } => { + events::message(ctx, new_message, data).await?; + } + _ => (), } - let user_name = format!("{}#{}", msg.author.name, msg.author.discriminator); - println!("[#{}/{}]: {}", channel_name, user_name, msg.content); - - events::message(&ctx, &msg).await; - } - - async fn interaction_create(&self, ctx: Context, interaction: Interaction) { - if let Interaction::ApplicationCommand(command) = interaction { - println!("Received command interaction '{}'", command.data.name); - commands::interact(&ctx, &command).await; - } - } + Ok(()) } diff --git a/src/structures.rs b/src/structures.rs index 8a6bc98..92ff947 100644 --- a/src/structures.rs +++ b/src/structures.rs @@ -1,90 +1,80 @@ -use serde_json::{from_reader, to_writer_pretty}; use serde::{Deserialize, Serialize}; +use serde_json::{from_reader, to_writer_pretty}; use serenity::builder::CreateEmbed; -use serenity::client::bridge::gateway::ShardManager; -use serenity::prelude::{TypeMapKey, Mutex}; +use serenity::prelude::TypeMapKey; use std::env; -use std::fs::{self, File, OpenOptions }; +use std::fs::{self, File, OpenOptions}; use std::path::Path; -use std::sync::Arc; - -pub struct ShardManagerContainer; - -impl TypeMapKey for ShardManagerContainer { - type Value = Arc>; -} pub trait Embeddable { - fn embed(&self) -> CreateEmbed; + fn embed(&self) -> CreateEmbed; } #[derive(Deserialize, Serialize, Clone)] pub struct Snippet { - pub id: String, - pub title: String, - pub content: String + pub id: String, + pub title: String, + pub content: String, } -#[derive(Deserialize, Serialize)] -pub struct State { - pub snippets: Vec +impl Snippet { + pub fn format_output(&self) -> String { + format!("{}: {}", self.id, self.title) + } } -impl Default for State { - fn default() -> State { - Self { - snippets: Vec::new() - } - } +#[derive(Deserialize, Serialize, Default)] +pub struct SnippetState { + pub snippets: Vec, } -impl TypeMapKey for State { - type Value = State; +impl TypeMapKey for SnippetState { + type Value = SnippetState; } -impl State { - pub fn get_path() -> String { - let pwd = env::current_dir().unwrap().to_string_lossy().to_string(); - let data_root = env::var("TABLETBOT_DATA").unwrap_or(pwd); +impl SnippetState { + pub fn get_path() -> String { + let pwd = env::current_dir().unwrap().to_string_lossy().to_string(); + let data_root = env::var("TABLETBOT_DATA").unwrap_or(pwd); - match env::var("TABLETBOT_STATE") { - Ok(path) => path, - Err(_) => format!("{data_root}/state.json") + match env::var("TABLETBOT_STATE") { + Ok(path) => path, + Err(_) => format!("{data_root}/state.json"), + } } - } - pub fn read() -> State { - let path_str = Self::get_path(); - let path = Path::new(&path_str); + pub fn read() -> SnippetState { + let path_str = Self::get_path(); + let path = Path::new(&path_str); - if path.exists() { - let file = File::open(path).unwrap(); - from_reader(file).unwrap() - } else { - State::default() + if path.exists() { + let file = File::open(path).unwrap(); + from_reader(file).unwrap() + } else { + SnippetState::default() + } } - } - pub fn write(&self) { - let path_str = Self::get_path(); - let path = Path::new(&path_str); + pub fn write(&self) { + let path_str = Self::get_path(); + let path = Path::new(&path_str); - if path.exists() { - fs::remove_file(path).expect("Failed to delete old file."); - } + if path.exists() { + fs::remove_file(path).expect("Failed to delete old file."); + } - let writer = OpenOptions::new() - .read(true) - .write(true) - .create(true) - .open(&path); + let writer = OpenOptions::new() + .read(true) + .write(true) + .create(true) + .open(path); - match writer { - Ok(writer) => match to_writer_pretty(writer, self) { - Ok(_) => println!("Successfully saved state to '{path_str}'", ), - Err(e) => println!("Failed to save state to '{path_str}': {e}") - }, - Err(e) => println!("Unable to write state to '{path_str}': {e}") - }; - } + match writer { + Ok(writer) => match to_writer_pretty(writer, self) { + Ok(_) => println!("Successfully saved state to '{path_str}'",), + Err(e) => println!("Failed to save state to '{path_str}': {e}"), + }, + Err(e) => println!("Unable to write state to '{path_str}': {e}"), + }; + } } From d65c15da89a642544eecaf11e7a6b9375db08bbe Mon Sep 17 00:00:00 2001 From: InfinityGhost Date: Mon, 4 Dec 2023 13:51:52 -0500 Subject: [PATCH 2/5] Fix flake --- .envrc | 14 ++++- 268075-nixpkgs.patch | 131 +++++++++++++++++++++++++++++++++++++++++++ default.nix | 37 +++++++++--- flake.lock | 84 ++------------------------- flake.nix | 65 ++++++++++----------- shell.nix | 58 ++++--------------- 6 files changed, 218 insertions(+), 171 deletions(-) create mode 100644 268075-nixpkgs.patch diff --git a/.envrc b/.envrc index 1d953f4..771add0 100644 --- a/.envrc +++ b/.envrc @@ -1 +1,13 @@ -use nix +files=( + ./default.nix + ./flake.lock + ./flake.nix + ./shell.nix +) + +for file in ${files[@]}; do + watch_file $file +done + +# outputs.devShells.$system.default +eval "$(nix print-dev-env --no-write-lock-file)" diff --git a/268075-nixpkgs.patch b/268075-nixpkgs.patch new file mode 100644 index 0000000..38b9b47 --- /dev/null +++ b/268075-nixpkgs.patch @@ -0,0 +1,131 @@ +From e9ca6038eb5f4398fc57feb4084b7df4afaa6d7d Mon Sep 17 00:00:00 2001 +From: Alyssa Ross +Date: Thu, 16 Nov 2023 19:07:23 +0100 +Subject: [PATCH] rustc,cargo: 1.73.0 -> 1.74.0 + +The BOOTSTRAP_ARGS patch is no longer necessary since +863d2fddd79 ("Pass `-jN` from Make to `BOOTSTRAP_ARGS`"). + +We now have to set sysconfdir, because since +350ead87205 ("add sanity checks for user write access on `x install`"), +the build system will check it can write there, even though nothing +actually gets installed there. (Previously it defaulted to /etc.) +--- + .../compilers/rust/{1_73.nix => 1_74.nix} | 30 +++++++++---------- + pkgs/development/compilers/rust/rustc.nix | 9 +----- + pkgs/top-level/all-packages.nix | 8 ++--- + 3 files changed, 20 insertions(+), 27 deletions(-) + rename pkgs/development/compilers/rust/{1_73.nix => 1_74.nix} (57%) + +diff --git a/pkgs/development/compilers/rust/1_73.nix b/pkgs/development/compilers/rust/1_74.nix +similarity index 57% +rename from pkgs/development/compilers/rust/1_73.nix +rename to pkgs/development/compilers/rust/1_74.nix +index 37e75e0a7a4f1e..72169a7b4d3f28 100644 +--- a/pkgs/development/compilers/rust/1_73.nix ++++ b/pkgs/development/compilers/rust/1_74.nix +@@ -20,8 +20,8 @@ + } @ args: + + import ./default.nix { +- rustcVersion = "1.73.0"; +- rustcSha256 = "sha256-ltYubR8tId96yKyzuYgkEfnnxwNhc/fy7enh8faxuzo="; ++ rustcVersion = "1.74.0"; ++ rustcSha256 = "sha256-iCtYS8Mhxdz+d82qafJ3kGuTYlXveAj81cdJKSXPEEk="; + + llvmSharedForBuild = pkgsBuildBuild.llvmPackages_16.libllvm.override { enableSharedLibraries = true; }; + llvmSharedForHost = pkgsBuildHost.llvmPackages_16.libllvm.override { enableSharedLibraries = true; }; +@@ -35,24 +35,24 @@ import ./default.nix { + + # Note: the version MUST be one version prior to the version we're + # building +- bootstrapVersion = "1.72.1"; ++ bootstrapVersion = "1.73.0"; + + # fetch hashes by running `print-hashes.sh ${bootstrapVersion}` + bootstrapHashes = { +- i686-unknown-linux-gnu = "a2a849a701dfd6643aaaa27e1ed5ac56aea00f7dee26c00d81c520808efd8911"; +- x86_64-unknown-linux-gnu = "4fbd8df2000cf73c632d67a219a7fc153537ceffa2e6474491e3db71fdd5a410"; +- x86_64-unknown-linux-musl = "94eddc044868a944a887d0b0375e393cb3acc6ebc034e3eac2ef2890ec7c0eac"; +- arm-unknown-linux-gnueabihf = "a4d90538882181722d3e7cb8d7f021770e29e6b6d28375452e31a98049600110"; +- armv7-unknown-linux-gnueabihf = "4c8e6b3c705a84d17894d3a1cfe744fb6083dd57c61868e67aac8b8512640ecb"; +- aarch64-unknown-linux-gnu = "190d0473cbe619f163d33a6c4e2ef982abdd4178f73abc3194631cd2d5c8ed8b"; +- aarch64-unknown-linux-musl = "c83778d1a95f6604bc3610a9070e8a8435c60a8bca5117aad71ffab36dea020f"; +- x86_64-apple-darwin = "d01e7e9a7482f88a51b4fd888f06234274b49f51b5476c2d14fd46fd6e99ba9e"; +- aarch64-apple-darwin = "42b0aaf269b6d9c60db13a64a920336d6064ab11d0c7043c9deeb9d4f67b3983"; +- powerpc64le-unknown-linux-gnu = "9310df247efc072f2ca27354a875c4989cf3c29c9e545255a7472895d830163c"; +- riscv64gc-unknown-linux-gnu = "1e08cd3ecd29d5bf247e3f7f4bc97318b439f0443dd9c99c36edcfa717d55101"; ++ i686-unknown-linux-gnu = "6a088acbbda734d27e8b431499f1d746de7781673b88fead3aeae072be1d1a5a"; ++ x86_64-unknown-linux-gnu = "aa4cf0b7e66a9f5b7c623d4b340bb1ac2864a5f2c2b981f39f796245dc84f2cb"; ++ x86_64-unknown-linux-musl = "c888457d106ccd40288ca8db1cb966b23d719c9a128daca701ecc574c53773d4"; ++ arm-unknown-linux-gnueabihf = "9c29bb42786aedbb16ea71564eb06068a8b01cca6c6b8857f0c37f91dfba7134"; ++ armv7-unknown-linux-gnueabihf = "092b32b82c602c18279d76d9a96763e85030aa62cda64c1bc73fc1f6355bb99c"; ++ aarch64-unknown-linux-gnu = "e54d7d886ba413ae573151f668e76ea537f9a44406d3d29598269a4a536d12f6"; ++ aarch64-unknown-linux-musl = "f4e9ff895aa55558777585ad4debe2ccf3c0298cb5d65db67814f62428de4a5b"; ++ x86_64-apple-darwin = "ece9646bb153d4bc0f7f1443989de0cbcd8989a7d0bf3b7fb9956e1223954f0c"; ++ aarch64-apple-darwin = "9c96e4c57328fb438ee2d87aa75970ce89b4426b49780ccb3c16af0d7c617cc6"; ++ powerpc64le-unknown-linux-gnu = "8fa215ee3e274fb64364e7084613bc570369488fa22cf5bc8e0fe6dc810fe2b9"; ++ riscv64gc-unknown-linux-gnu = "381379a2381835428b2e7a396b3046581517356b7cc851e39e385aebd5700623"; + }; + +- selectRustPackage = pkgs: pkgs.rust_1_73; ++ selectRustPackage = pkgs: pkgs.rust_1_74; + + rustcPatches = [ ]; + } +diff --git a/pkgs/development/compilers/rust/rustc.nix b/pkgs/development/compilers/rust/rustc.nix +index 1758abb6bea518..3a32dfc0c1e8d5 100644 +--- a/pkgs/development/compilers/rust/rustc.nix ++++ b/pkgs/development/compilers/rust/rustc.nix +@@ -81,6 +81,7 @@ in stdenv.mkDerivation (finalAttrs: { + ccForTarget = ccPrefixForStdenv pkgsBuildTarget.targetPackages.stdenv; + cxxForTarget = cxxPrefixForStdenv pkgsBuildTarget.targetPackages.stdenv; + in [ ++ "--sysconfdir=${placeholder "out"}/etc" + "--release-channel=stable" + "--set=build.rustc=${rustc}/bin/rustc" + "--set=build.cargo=${cargo}/bin/cargo" +@@ -178,14 +179,6 @@ in stdenv.mkDerivation (finalAttrs: { + runHook postInstall + '' else null; + +- # The bootstrap.py will generated a Makefile that then executes the build. +- # The BOOTSTRAP_ARGS used by this Makefile must include all flags to pass +- # to the bootstrap builder. +- postConfigure = '' +- substituteInPlace Makefile \ +- --replace 'BOOTSTRAP_ARGS :=' 'BOOTSTRAP_ARGS := --jobs $(NIX_BUILD_CORES)' +- ''; +- + # the rust build system complains that nix alters the checksums + dontFixLibtool = true; + +diff --git a/pkgs/top-level/all-packages.nix b/pkgs/top-level/all-packages.nix +index 06a2c24625e0c6..d9b622f528b123 100644 +--- a/pkgs/top-level/all-packages.nix ++++ b/pkgs/top-level/all-packages.nix +@@ -16939,11 +16939,11 @@ with pkgs; + inherit (darwin) apple_sdk; + }; + +- rust_1_73 = callPackage ../development/compilers/rust/1_73.nix { ++ rust_1_74 = callPackage ../development/compilers/rust/1_74.nix { + inherit (darwin.apple_sdk.frameworks) CoreFoundation Security SystemConfiguration; + llvm_16 = llvmPackages_16.libllvm; + }; +- rust = rust_1_73; ++ rust = rust_1_74; + + mrustc = callPackage ../development/compilers/mrustc { }; + mrustc-minicargo = callPackage ../development/compilers/mrustc/minicargo.nix { }; +@@ -16951,8 +16951,8 @@ with pkgs; + openssl = openssl_1_1; + }; + +- rustPackages_1_73 = rust_1_73.packages.stable; +- rustPackages = rustPackages_1_73; ++ rustPackages_1_74 = rust_1_74.packages.stable; ++ rustPackages = rustPackages_1_74; + + inherit (rustPackages) cargo cargo-auditable cargo-auditable-cargo-wrapper clippy rustc rustPlatform; + diff --git a/default.nix b/default.nix index 6018182..d1421f6 100644 --- a/default.nix +++ b/default.nix @@ -1,8 +1,29 @@ -{ lib, rustPlatform, pkg-config, openssl }: - - (pkgs.makeRustPlatform { - cargo = toolchain; - rustc = toolchain; - }).buildRustPackage { - pname = "example"; - version = "0.1.0"; +{ lib +, rustPlatform +, pkg-config +, openssl +, fetchurl +, ... +}: + +rustPlatform.buildRustPackage rec { + pname = "tabletbot"; + name = pname; + + nativeBuildInputs = [ + pkg-config + ]; + + buildInputs = [ + openssl + ]; + + src = ./.; + + cargoLock = { + lockFile = ./Cargo.lock; + outputHashes = { + "poise-0.5.7" = "sha256-vI4FgRorQyv2FcrHI/hE6v/ISTAxOnenIQlt/mFQ4so="; + }; + }; +} diff --git a/flake.lock b/flake.lock index 47a914a..90c62d9 100644 --- a/flake.lock +++ b/flake.lock @@ -1,97 +1,25 @@ { "nodes": { - "fenix": { - "inputs": { - "nixpkgs": [ - "nixpkgs" - ], - "rust-analyzer-src": "rust-analyzer-src" - }, - "locked": { - "lastModified": 1701238978, - "narHash": "sha256-CL3RjhwV47ZI9oWRpezS2eP0mhiyqpOGb3GBMOcF2w4=", - "owner": "nix-community", - "repo": "fenix", - "rev": "22e61fab6d93cfce2b9659bb7734762ad6a7cf11", - "type": "github" - }, - "original": { - "owner": "nix-community", - "repo": "fenix", - "type": "github" - } - }, - "flake-utils": { - "inputs": { - "systems": "systems" - }, - "locked": { - "lastModified": 1694529238, - "narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=", - "owner": "numtide", - "repo": "flake-utils", - "rev": "ff7b65b44d01cf9ba6a71320833626af21126384", - "type": "github" - }, - "original": { - "owner": "numtide", - "repo": "flake-utils", - "type": "github" - } - }, "nixpkgs": { "locked": { - "lastModified": 1701068326, - "narHash": "sha256-vmMceA+q6hG1yrjb+MP8T0YFDQIrW3bl45e7z24IEts=", + "lastModified": 1701436327, + "narHash": "sha256-tRHbnoNI8SIM5O5xuxOmtSLnswEByzmnQcGGyNRjxsE=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "8cfef6986adfb599ba379ae53c9f5631ecd2fd9c", + "rev": "91050ea1e57e50388fa87a3302ba12d188ef723a", "type": "github" }, "original": { - "id": "nixpkgs", + "owner": "NixOS", "ref": "nixos-unstable", - "type": "indirect" + "repo": "nixpkgs", + "type": "github" } }, "root": { "inputs": { - "fenix": "fenix", - "flake-utils": "flake-utils", "nixpkgs": "nixpkgs" } - }, - "rust-analyzer-src": { - "flake": false, - "locked": { - "lastModified": 1701186284, - "narHash": "sha256-euPBY3EmEy7+Jjm2ToRPlSp/qrj0UL9+PRobxVz6+aQ=", - "owner": "rust-lang", - "repo": "rust-analyzer", - "rev": "c7c582afb57bb802715262d7f1ba73b8a86c1c5a", - "type": "github" - }, - "original": { - "owner": "rust-lang", - "ref": "nightly", - "repo": "rust-analyzer", - "type": "github" - } - }, - "systems": { - "locked": { - "lastModified": 1681028828, - "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", - "owner": "nix-systems", - "repo": "default", - "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", - "type": "github" - }, - "original": { - "owner": "nix-systems", - "repo": "default", - "type": "github" - } } }, "root": "root", diff --git a/flake.nix b/flake.nix index 74283e4..4e89a31 100644 --- a/flake.nix +++ b/flake.nix @@ -1,41 +1,34 @@ { + description = "TabletBot"; + inputs = { - fenix = { - url = "github:nix-community/fenix"; - inputs.nixpkgs.follows = "nixpkgs"; - }; - flake-utils.url = "github:numtide/flake-utils"; - nixpkgs.url = "nixpkgs/nixos-unstable"; + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; }; - outputs = { self, fenix, flake-utils, nixpkgs }: - flake-utils.lib.eachDefaultSystem (system: { - packages.default = let - toolchain = fenix.packages.${system}.minimal.toolchain; - pkgs = import nixpkgs { - inherit system; - config.allowUnfree = true; - }; - - in (pkgs.makeRustPlatform { - cargo = toolchain; - rustc = toolchain; - }).buildRustPackage { - pname = "tabletbot"; - version = "0.1.0"; - - src = ./.; - nativeBuildInputs = with pkgs; [ pkg-config ]; - - buildInputs = with pkgs; [ openssl ]; - - cargoLock = { - lockFile = ./Cargo.lock; - outputHashes = { - "poise-0.5.7" = - "1jp2a1hzwv8946kpffii614wizza8kw27iya2pv2nhrb3a0hb3mw"; - }; - }; - }; - }); + outputs = { self, nixpkgs, ... }: let + + system = "x86_64-linux"; + + importPkgs = pkgs: import pkgs { inherit system; }; + + patchedPkgs = (importPkgs nixpkgs).applyPatches { + name = "nixpkgs-patched"; + src = nixpkgs; + patches = [ ./268075-nixpkgs.patch ]; + }; + + pkgs = importPkgs patchedPkgs; + + in rec { + + packages.${system} = rec { + tabletbot = pkgs.callPackage ./default.nix { flake = self; }; + default = tabletbot; + }; + + devShells.${system}.default = import ./shell.nix { + inherit pkgs; + packages = packages.${system}; + }; + }; } diff --git a/shell.nix b/shell.nix index e0e08ec..2176943 100644 --- a/shell.nix +++ b/shell.nix @@ -1,60 +1,22 @@ -# https://nixos.wiki/wiki/Rust#Installation_via_rustup -{ pkgs ? import { } }: -let - readFileIfExists = path: - with pkgs.lib; - if pathExists path then readFile path else null; +{ pkgs ? nixpkgs, nixpkgs ? import {}, packages, ... }: - default = pkgs.callPackage ./. { }; +pkgs.mkShell rec { + inputsFrom = [ packages.tabletbot ]; - rustDeps = with pkgs; [ - llvmPackages_latest.llvm - llvmPackages_latest.bintools - zlib.out - rustup - xorriso - grub2 - qemu - llvmPackages_latest.lld - python3 - ]; - - deps = default.nativeBuildInputs; - - buildPath = toString ./.build/out; - - build = pkgs.writers.writeBashBin "build" '' - cd ${toString ./src} - cargo build $@ - ''; - - run = pkgs.writers.writeBashBin "run" '' - cd ${toString ./src} - cargo run $@ - ''; - - utils = with pkgs; [ - build # cargo build - run # cargo run + buildInputs = with pkgs; [ + cargo cachix jq ]; -in pkgs.mkShell rec { - RUSTC_VERSION = readFileIfExists ./rust-toolchain; - CARGO_HOME = toString ./.build/cargo; - CARGO_TARGET_DIR = toString ./.build/target; - RUSTUP_HOME = toString ./.build/rustup; - TABLETBOT_DATA = toString ./.build/data; + shellHook = with pkgs.lib; '' + PROJECT_ROOT="$(git rev-parse --show-toplevel)" - buildInputs = with pkgs; rustDeps ++ deps ++ utils; + export CARGO_HOME="$PROJECT_ROOT/.build/cargo" + export CARGO_TARGET_DIR="$PROJECT_ROOT/.build/target" + export TABLETBOT_DATA="$PROJECT_ROOT/.build/data" - shellHook = with pkgs.lib; '' export LD_LIBRARY_PATH=${escapeShellArg (makeLibraryPath buildInputs)} - export PATH=$PATH:''${CARGO_HOME:-~/.cargo}/bin - export PATH=$PATH:''${RUSTUP_HOME:-~/.rustup}/toolchains/$RUSTC_VERSION-x86_64-unknown-linux-gnu/bin/ export PATH=$PATH:${escapeShellArg (makeBinPath buildInputs)} - - [ -r ${TABLETBOT_DATA}/tokens ] && source ${TABLETBOT_DATA}/tokens ''; } From afa6d48dadf4c1cd4a21c757f33eb17e2c6ad0a6 Mon Sep 17 00:00:00 2001 From: jamesbt365 Date: Thu, 30 Nov 2023 22:23:42 +0000 Subject: [PATCH 3/5] Improve features & add various utility commands - Adds buttons to manipulate issue embeds - Add issue tokens to allow for linking other repositories - Add edit-embed support. --- src/commands/snippets.rs | 42 +++--- src/commands/utils.rs | 295 ++++++++++++++++++++++++++++++++++++++- src/events/issue.rs | 119 +++++++++++++--- src/events/mod.rs | 22 +-- src/main.rs | 33 ++--- src/structures.rs | 26 +++- 6 files changed, 461 insertions(+), 76 deletions(-) diff --git a/src/commands/snippets.rs b/src/commands/snippets.rs index c6a4a5f..2a2134b 100644 --- a/src/commands/snippets.rs +++ b/src/commands/snippets.rs @@ -12,8 +12,8 @@ async fn autocomplete_snippet<'a>( ) -> impl Stream + 'a { let snippet_list: Vec = { ctx.data() - .snip - .lock() + .state + .read() .unwrap() .snippets .iter() @@ -65,10 +65,10 @@ pub async fn create_snippet( ) -> Result<(), Error> { // I really don't like the code I wrote here. let embed = { - let mut mutex_guard = ctx.data().snip.lock().unwrap(); + let mut rwlock_guard = ctx.data().state.write().unwrap(); - if let Some(position) = mutex_guard.snippets.iter().position(|s| s.id.eq(&id)) { - mutex_guard.snippets.remove(position); + if let Some(position) = rwlock_guard.snippets.iter().position(|s| s.id.eq(&id)) { + rwlock_guard.snippets.remove(position); } let snippet = Snippet { @@ -77,16 +77,16 @@ pub async fn create_snippet( content: content.replace(r"\n", "\n"), }; - mutex_guard.snippets.push(snippet.clone()); + rwlock_guard.snippets.push(snippet.clone()); - mutex_guard.snippets = mutex_guard.snippets.clone(); + rwlock_guard.snippets = rwlock_guard.snippets.clone(); println!("New snippet created '{}: {}'", id, title); - mutex_guard.write(); + rwlock_guard.write(); let mut embed = snippet.embed(); embed = embed.colour(super::OK_COLOUR); - if mutex_guard.snippets.len() > 25 { + if rwlock_guard.snippets.len() > 25 { embed = embed.field( "Warning", "There are more than 25 snippets, some may not appear in the snippet list.", @@ -123,10 +123,10 @@ pub async fn edit_snippet( } { - let mut mutex_guard = ctx.data().snip.lock().unwrap(); - mutex_guard.snippets.push(snippet.clone()); + let mut rwlock_guard = ctx.data().state.write().unwrap(); + rwlock_guard.snippets.push(snippet.clone()); println!("Snippet edited '{}: {}'", snippet.title, snippet.content); - mutex_guard.write(); + rwlock_guard.write(); } let embed = snippet.embed().colour(super::OK_COLOUR); @@ -178,7 +178,7 @@ pub async fn delete_snippet( track_edits )] pub async fn list_snippets(ctx: Context<'_>) -> Result<(), Error> { - let snippets = { ctx.data().snip.lock().unwrap().snippets.clone() }; + let snippets = { ctx.data().state.read().unwrap().snippets.clone() }; let mut embed = CreateEmbed::default().title("Snippets").color(Colour::TEAL); @@ -238,9 +238,9 @@ impl Embeddable for Snippet { // Exact matches the snippet id and name. async fn get_snippet(ctx: &Context<'_>, id: &str) -> Option { let data = ctx.data(); - let mutex_guard = data.snip.lock().unwrap(); + let rwlock_guard = data.state.read().unwrap(); - mutex_guard + rwlock_guard .snippets .iter() .find(|s| s.format_output().eq(id)) @@ -250,9 +250,9 @@ async fn get_snippet(ctx: &Context<'_>, id: &str) -> Option { // Matches the snippet by checking if its starts with the id and name. async fn get_snippet_lazy(ctx: &Context<'_>, id: &str) -> Option { let data = ctx.data(); - let mutex_guard = data.snip.lock().unwrap(); + let rwlock_guard = data.state.read().unwrap(); - mutex_guard + rwlock_guard .snippets .iter() .find(|s| s.format_output().starts_with(id)) @@ -261,15 +261,15 @@ async fn get_snippet_lazy(ctx: &Context<'_>, id: &str) -> Option { async fn rm_snippet(ctx: &Context<'_>, snippet: &Snippet) { let data = ctx.data(); - let mut mutex_guard = data.snip.lock().unwrap(); + let mut rwlock_guard = data.state.write().unwrap(); - let index = mutex_guard + let index = rwlock_guard .snippets .iter() .position(|s| s.id == snippet.id) .expect("Snippet was not found in vec"); println!("Removing snippet '{}: {}'", snippet.id, snippet.title); - mutex_guard.snippets.remove(index); - mutex_guard.write(); + rwlock_guard.snippets.remove(index); + rwlock_guard.write(); } diff --git a/src/commands/utils.rs b/src/commands/utils.rs index 6dafc40..0430c9b 100644 --- a/src/commands/utils.rs +++ b/src/commands/utils.rs @@ -1,9 +1,33 @@ use crate::{ - commands::{respond_embed, respond_err}, + commands::{respond_embed, respond_err, respond_ok}, + structures::RepositoryDetails, Context, Error, }; -use poise::serenity_prelude::{Colour, CreateEmbed, CreateEmbedFooter}; +use poise::serenity_prelude::{Colour, CreateEmbed, CreateEmbedFooter, EditMessage, Message}; +use regex::Regex; +use serenity::futures::{self, Stream, StreamExt}; + +async fn autocomplete_key<'a>( + ctx: Context<'a>, + partial: &'a str, +) -> impl Stream + 'a { + let snippet_list: Vec = { + ctx.data() + .state + .read() + .unwrap() + .issue_prefixes + .iter() + .take(25) + .map(|s| s.0.clone()) + .collect() + }; + + futures::stream::iter(snippet_list) + .filter(move |name| futures::future::ready(name.starts_with(partial))) + .map(|name| name.to_string()) +} /// Create an embed in the current channel. #[allow(clippy::too_many_arguments)] @@ -91,3 +115,270 @@ pub async fn embed( Ok(()) } + +/// Create an embed in the current channel. +/// +/// +#[allow(clippy::too_many_arguments)] +#[poise::command(rename = "edit-embed", slash_command, guild_only)] +pub async fn edit_embed( + ctx: Context<'_>, + #[description = "The message to be edited."] message: Message, + #[description = "The embed title"] title: Option, + #[description = "The embed description"] description: Option, + #[description = "The color of the embed in hexidecimal form. (ex: ff00ff)"] color: Option< + String, + >, + #[description = "The embed url"] url: Option, + #[description = "The embed image"] image: Option, + #[description = "The embed footer text"] footer: Option, + #[description = "The embed thumbnail"] thumbnail: Option, +) -> Result<(), Error> { + let mut msg_clone = message.clone(); + if message.author.id != ctx.cache().current_user().id { + respond_err( + &ctx, + "Cannot edit message!", + "I am not the author of the specified message!", + ) + .await; + } + + if let Some(interaction) = message.interaction { + if interaction.name == "embed" { + // Embed for checking reasons. + let embed = &message.embeds[0]; + let mut embedb = CreateEmbed::default(); + + if let Some(title) = title { + if title != "_" { + embedb = embedb.title(title); + } + } else if let Some(t) = &embed.title { + embedb = embedb.title(t); + } + + if let Some(description) = description { + if description != "_" { + embedb = embedb.description(description); + } + } else if let Some(d) = &embed.description { + embedb = embedb.description(d); + } + + if let Some(color) = color { + if color != "_" { + match hex::decode(color.to_ascii_lowercase().replace('#', "")) { + Ok(hex_arr) => { + embedb = + embedb.color(Colour::from_rgb(hex_arr[0], hex_arr[1], hex_arr[2])); + } + Err(e) => { + let title = "Invalid color provided"; + let content = &format!( + "The color '{}' is not a valid hexadecimal color: {}", + &color, e + ); + respond_err(&ctx, title, content).await; + } + } + } + } else if let Some(c) = &embed.colour { + embedb = embedb.color(c.0); + } + + if let Some(url) = url { + if url != "_" { + embedb = embedb.url(url); + } + } else if let Some(u) = &embed.url { + embedb = embedb.url(u); + } + + if let Some(image) = image { + if image != "_" { + embedb = embedb.image(image); + } + } else if let Some(i) = &embed.image { + embedb = embedb.image(i.url.clone()); + } + + if let Some(footer) = footer { + if footer != "_" { + embedb = embedb.footer(CreateEmbedFooter::new(footer)); + } + } else if let Some(f) = &embed.footer { + embedb = embedb.footer(CreateEmbedFooter::new(f.text.clone())); + } + + if let Some(thumbnail) = thumbnail { + if thumbnail != "_" { + embedb = embedb.thumbnail(thumbnail); + } + } else if let Some(t) = &embed.thumbnail { + embedb = embedb.thumbnail(t.url.clone()); + } + + let builder = EditMessage::default().embed(embedb); + + match msg_clone.edit(ctx, builder).await { + Ok(_) => { + respond_ok( + &ctx, + "Successfully edited embed", + "The message has been edited successfully!", + ) + .await; + } + Err(error) => { + // Better error handling later. + respond_err(&ctx, "Error while handling message!", &format!("{}", error)).await + } + } + } else { + respond_err( + &ctx, + "Failure to edit embed", + "This message was an interaction, but not an embed interaction!", + ) + .await; + } + } else { + respond_err( + &ctx, + "Failure to edit embed", + "This message is not an interaction!", + ) + .await; + }; + + Ok(()) +} + +/// Adds an issue token +#[poise::command(rename = "add-issue-token", slash_command, guild_only)] +pub async fn add_issue_token( + ctx: Context<'_>, + #[description = "The key to the issue token in a lowercase alphabetic string"] key: String, + #[description = "The owner of the repository."] owner: String, + #[description = "The respository name."] repository: String, +) -> Result<(), Error> { + let key_regex = Regex::new(r"[a-z+]+$").unwrap(); + let repo_details_regex = Regex::new(r"^[a-zA-Z0-9](?:[a-zA-Z0-9.-]*[a-zA-Z0-9])?$").unwrap(); + if !key_regex.is_match(&key) { + respond_err( + &ctx, + "Issue token parsing error", + "The key is limited to lowercase letters only.", + ) + .await; + return Ok(()); + } + if !repo_details_regex.is_match(&key) || !repo_details_regex.is_match(&repository) { + respond_err( + &ctx, + "Issue token parsing error", + "Your inputs for owner and repository name must be valid.", + ) + .await; + return Ok(()); + } + + { + let mut rwlock_guard = { ctx.data().state.write().unwrap() }; + let details = RepositoryDetails { + owner: owner.clone(), + name: repository.clone(), + }; + + rwlock_guard.issue_prefixes.insert(key.clone(), details); + println!( + "Successfully added issue token {} for **{}/{}**", + key, owner, repository + ); + rwlock_guard.write(); + }; + + respond_ok( + &ctx, + "Successfully added issue token", + &format!("{}: {}/{}", key, owner, repository), + ) + .await; + + Ok(()) +} + +/// Removes an issue token. +#[poise::command(rename = "remove-issue-token", slash_command, guild_only)] +pub async fn remove_issue_token( + ctx: Context<'_>, + #[autocomplete = "autocomplete_key"] + #[description = "The issue token key."] + key: String, +) -> Result<(), Error> { + // I know we could just do rm_repo, but that doesn't return a result. + // I may change this in the future, but before I do that I'll probably + // impl a solution directly into the types? + + // not sure why I have to do this, it won't settle otherwise. + let key_str = format!("The issue token with the key '{}' has been removed", key); + match get_repo_details(&ctx, &key).await { + Some(_) => { + rm_repo(&ctx, &key).await; + + respond_ok(&ctx, "Successfully removed token!", &key_str).await; + } + None => { + let title = "Failure to find issue token"; + let content = format!("The key '{}' does not exist.", key); + respond_err(&ctx, title, &content).await; + } + }; + + Ok(()) +} + +/// Lists all snippets +#[poise::command( + rename = "list-tokens", + slash_command, + prefix_command, + guild_only, + track_edits +)] +pub async fn list_tokens(ctx: Context<'_>) -> Result<(), Error> { + let tokens = { ctx.data().state.read().unwrap().issue_prefixes.clone() }; + + let mut embed = CreateEmbed::default() + .title("Issue tokens") + .color(Colour::TEAL); + + // fields are limited to 25 max, we can't display more than 25 snippets in the snippets command + // due to a discord limitation. + for token in tokens.iter().take(25) { + embed = embed.field( + format!("**{}**", token.0), + format!("{}/{}", token.1.owner, token.1.name), + false, + ); + } + + ctx.send(poise::CreateReply::default().embed(embed)).await?; + + Ok(()) +} + +async fn get_repo_details(ctx: &Context<'_>, key: &str) -> Option { + let data = ctx.data(); + let rwlock_guard = data.state.read().unwrap(); + + rwlock_guard.issue_prefixes.get(key).cloned() +} + +async fn rm_repo(ctx: &Context<'_>, key: &str) { + let data = ctx.data(); + let mut rwlock_guard = data.state.write().unwrap(); + + rwlock_guard.issue_prefixes.remove(key); +} diff --git a/src/events/issue.rs b/src/events/issue.rs index 9b4374d..761a769 100644 --- a/src/events/issue.rs +++ b/src/events/issue.rs @@ -1,45 +1,132 @@ +use std::time::Duration; + +use crate::{structures::Embeddable, Data}; use ::serenity::builder::CreateEmbedAuthor; use octocrab::models::issues::Issue; use octocrab::models::pulls::PullRequest; -use poise::serenity_prelude::{self as serenity, Colour, Context, CreateEmbed, Message}; +use poise::serenity_prelude::{ + self as serenity, Colour, Context, CreateEmbed, Message, Permissions, +}; use regex::Regex; -use crate::structures::Embeddable; - -const REPO_OWNER: &str = "OpenTabletDriver"; -const REPO_NAME: &str = "OpenTabletDriver"; +const DEFAULT_REPO_OWNER: &str = "OpenTabletDriver"; +const DEFAULT_REPO_NAME: &str = "OpenTabletDriver"; const OPEN_COLOUR: Colour = Colour::new(0x238636); const RESOLVED_COLOUR: Colour = Colour::new(0x8957e5); const CLOSED_COLOUR: Colour = Colour::new(0xda3633); -pub async fn message(ctx: &Context, message: &Message) { - if let Some(embeds) = issue_embeds(message).await { +pub async fn message(data: &Data, ctx: &Context, message: &Message) { + if let Some(embeds) = issue_embeds(data, message).await { let typing = message.channel_id.start_typing(&ctx.http); - let content = serenity::CreateMessage::default() + let ctx_id = message.id.get(); // poise context isn't available here. + let remove_id = format!("{}remove", ctx_id); + let hide_body_id = format!("{}hide_body", ctx_id); + let components = serenity::CreateActionRow::Buttons(vec![ + serenity::CreateButton::new(&remove_id) + .label("delete") + .style(serenity::ButtonStyle::Danger), + serenity::CreateButton::new(&hide_body_id).label("hide body"), + ]); + + let content: serenity::CreateMessage = serenity::CreateMessage::default() .embeds(embeds) - .reference_message(message); - let _ = message.channel_id.send_message(ctx, content).await; - + .reference_message(message) + .components(vec![components]); + let msg_result = message.channel_id.send_message(ctx, content).await; typing.stop(); + + let mut msg_deleted = false; + let mut body_hid = false; + while let Some(press) = serenity::ComponentInteractionCollector::new(ctx) + .filter(move |press| press.data.custom_id.starts_with(&ctx_id.to_string())) + .timeout(Duration::from_secs(60)) + .await + { + // Safe to unwap member because this only runs in guilds. + let has_perms = press.member.as_ref().map_or(false, |member| { + member.permissions.map_or(false, |member_perms| { + member_perms.contains(Permissions::MANAGE_MESSAGES) + }) + }); + + if press.data.custom_id == remove_id + && (press.user.id == message.author.id || has_perms) + { + let _ = press + .create_response(ctx, serenity::CreateInteractionResponse::Acknowledge) + .await; + if let Ok(ref msg) = msg_result { + let _ = msg.delete(ctx).await; + } + msg_deleted = true; + } + + if press.data.custom_id == hide_body_id + && (press.user.id == message.author.id || has_perms) + { + if !body_hid { + let mut hid_body_embeds: Vec = Vec::new(); + if let Ok(ref msg) = msg_result { + for mut embed in msg.embeds.clone() { + embed.description = None; + let embed: CreateEmbed = embed.clone().into(); + hid_body_embeds.push(embed); + } + } + + let _ = press + .create_response( + ctx, + serenity::CreateInteractionResponse::UpdateMessage( + serenity::CreateInteractionResponseMessage::new() + .embeds(hid_body_embeds), + ), + ) + .await; + } + body_hid = true; + } + } + // Triggers on timeout. + if !msg_deleted { + if let Ok(mut msg) = msg_result { + let _ = msg + .edit(ctx, serenity::EditMessage::default().components(vec![])) + .await; + } + } + // } } -async fn issue_embeds(message: &Message) -> Option> { +async fn issue_embeds(data: &Data, message: &Message) -> Option> { let mut embeds: Vec = vec![]; let client = octocrab::instance(); let ratelimit = client.ratelimit(); - let issues = client.issues(REPO_OWNER, REPO_NAME); - let prs = client.pulls(REPO_OWNER, REPO_NAME); + let regex = Regex::new(r#" ?([a-z]+)?#([0-9]+[0-9]) ?"#).expect("Expected numbers regex"); + + let custom_repos = { data.state.read().unwrap().issue_prefixes.clone() }; - let regex = Regex::new(r#" ?#([0-9]+[0-9]) ?"#).expect("Expected numbers regex"); + let mut issues = client.issues(DEFAULT_REPO_OWNER, DEFAULT_REPO_NAME); + let mut prs = client.pulls(DEFAULT_REPO_OWNER, DEFAULT_REPO_NAME); for capture in regex.captures_iter(&message.content) { - if let Some(m) = capture.get(1) { + if let Some(m) = capture.get(2) { let issue_num = m.as_str().parse::().expect("Match is not a number"); + if let Some(repo) = capture.get(1) { + let repository = custom_repos.get(repo.as_str()); + if let Some(repository) = repository { + let (owner, repo) = repository.get(); + + issues = client.issues(owner, repo); + prs = client.pulls(owner, repo); + } + } + let ratelimit = ratelimit .get() .await diff --git a/src/events/mod.rs b/src/events/mod.rs index 4984a56..5b71a81 100644 --- a/src/events/mod.rs +++ b/src/events/mod.rs @@ -1,5 +1,3 @@ -use serenity::model::prelude::Message; - use poise::serenity_prelude as serenity; use crate::{Data, Error}; @@ -7,14 +5,22 @@ use crate::{Data, Error}; pub mod code; pub mod issue; -pub async fn message( +pub async fn event_handler( ctx: &serenity::Context, - new_message: Message, - _data: &Data, + event: &serenity::FullEvent, + _framework: poise::FrameworkContext<'_, Data, Error>, + data: &Data, ) -> Result<(), Error> { - if !new_message.author.bot { - issue::message(ctx, &new_message).await; - code::message(ctx, &new_message).await; + #[allow(clippy::single_match)] + match event { + serenity::FullEvent::Message { new_message } => { + if !new_message.author.bot && new_message.guild_id.is_some() { + issue::message(data, ctx, new_message).await; + code::message(ctx, new_message).await; + } + } + _ => (), } + Ok(()) } diff --git a/src/main.rs b/src/main.rs index 92c0ed5..4c7936b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,17 +3,17 @@ pub(crate) mod events; pub(crate) mod formatting; pub(crate) mod structures; -use std::sync::Mutex; +use std::sync::RwLock; use std::time::Duration; use std::{env, sync::Arc}; use octocrab::Octocrab; use poise::serenity_prelude::{self as serenity, GatewayIntents}; -use structures::SnippetState; +use structures::BotState; pub struct Data { pub octocrab: Arc, - pub snip: Mutex, + pub state: RwLock, } type Error = Box; type Context<'a> = poise::Context<'a, Data, Error>; @@ -41,7 +41,7 @@ async fn main() { let octocrab = octocrab::initialise(octo_builder).expect("Failed to build github client"); - let snip = Mutex::new(structures::SnippetState::read()); + let state = RwLock::new(BotState::read()); let options = poise::FrameworkOptions { commands: vec![ @@ -53,6 +53,10 @@ async fn main() { commands::snippets::list_snippets(), commands::snippets::edit_snippet(), commands::utils::embed(), + commands::utils::edit_embed(), + commands::utils::add_issue_token(), + commands::utils::remove_issue_token(), + commands::utils::list_tokens(), ], prefix_options: poise::PrefixFrameworkOptions { prefix: Some("!".into()), @@ -71,7 +75,7 @@ async fn main() { skip_checks_for_owners: false, event_handler: |ctx, event: &serenity::FullEvent, framework, data| { - Box::pin(event_handler(ctx, event.clone(), framework, data)) + Box::pin(events::event_handler(ctx, event, framework, data)) }, ..Default::default() }; @@ -80,7 +84,7 @@ async fn main() { Box::pin(async move { println!("Logged in as {}", ready.user.name); poise::builtins::register_globally(ctx, &framework.options().commands).await?; - Ok(Data { octocrab, snip }) + Ok(Data { octocrab, state }) }) }); @@ -95,20 +99,3 @@ async fn main() { client.start().await.unwrap(); } - -pub async fn event_handler( - ctx: &serenity::Context, - event: serenity::FullEvent, - _framework: poise::FrameworkContext<'_, Data, Error>, - data: &Data, -) -> Result<(), Error> { - #[allow(clippy::single_match)] - match event { - serenity::FullEvent::Message { new_message } => { - events::message(ctx, new_message, data).await?; - } - _ => (), - } - - Ok(()) -} diff --git a/src/structures.rs b/src/structures.rs index 92ff947..1501f9b 100644 --- a/src/structures.rs +++ b/src/structures.rs @@ -2,6 +2,7 @@ use serde::{Deserialize, Serialize}; use serde_json::{from_reader, to_writer_pretty}; use serenity::builder::CreateEmbed; use serenity::prelude::TypeMapKey; +use std::collections::HashMap; use std::env; use std::fs::{self, File, OpenOptions}; use std::path::Path; @@ -24,15 +25,28 @@ impl Snippet { } #[derive(Deserialize, Serialize, Default)] -pub struct SnippetState { +pub struct BotState { pub snippets: Vec, + pub issue_prefixes: HashMap, } -impl TypeMapKey for SnippetState { - type Value = SnippetState; +impl TypeMapKey for BotState { + type Value = BotState; } -impl SnippetState { +#[derive(Deserialize, Clone, Serialize, Default)] +pub struct RepositoryDetails { + pub owner: String, + pub name: String, +} + +impl RepositoryDetails { + pub fn get(&self) -> (&String, &String) { + (&self.owner, &self.name) + } +} + +impl BotState { pub fn get_path() -> String { let pwd = env::current_dir().unwrap().to_string_lossy().to_string(); let data_root = env::var("TABLETBOT_DATA").unwrap_or(pwd); @@ -43,7 +57,7 @@ impl SnippetState { } } - pub fn read() -> SnippetState { + pub fn read() -> BotState { let path_str = Self::get_path(); let path = Path::new(&path_str); @@ -51,7 +65,7 @@ impl SnippetState { let file = File::open(path).unwrap(); from_reader(file).unwrap() } else { - SnippetState::default() + BotState::default() } } From eca81b36eab39e634962da0e72a7b909ad4b2000 Mon Sep 17 00:00:00 2001 From: jamesbt365 Date: Mon, 4 Dec 2023 21:49:31 +0000 Subject: [PATCH 4/5] pagination, regex tweaks & other minor changes --- src/commands/mod.rs | 110 +++++++++++++++++++++++++++++++++++++-- src/commands/snippets.rs | 33 ++++++++---- src/commands/utils.rs | 79 +++++++++++++++++++--------- src/events/issue.rs | 6 ++- src/main.rs | 8 +-- 5 files changed, 192 insertions(+), 44 deletions(-) diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 05669e3..c03cba3 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -5,11 +5,13 @@ pub(crate) const ACCENT_COLOUR: Colour = Colour(0x8957e5); pub(crate) const OK_COLOUR: Colour = Colour(0x2ecc71); pub(crate) const ERROR_COLOUR: Colour = Colour(0xe74c3c); -use serenity::model::Colour; - use crate::{Context, Error}; -use poise::serenity_prelude::{self as serenity, CreateEmbed}; +use poise::serenity_prelude::{ + self as serenity, Colour, ComponentInteractionCollector, CreateActionRow, CreateButton, + CreateEmbed, CreateEmbedFooter, CreateInteractionResponse, CreateInteractionResponseMessage, +}; +use poise::CreateReply; #[poise::command(prefix_command, hide_in_help)] pub async fn register(ctx: Context<'_>) -> Result<(), Error> { @@ -46,3 +48,105 @@ pub async fn respond_err(ctx: &Context<'_>, title: &str, content: &str) { respond_embed(ctx, embed, false).await; } + +pub async fn paginate_lists( + ctx: poise::Context<'_, U, E>, + pages: &[Vec<(String, String, bool)>], + embed_title: &str, +) -> Result<(), Error> { + let ctx_id = ctx.id(); + let prev_button_id = format!("{}prev", ctx_id); + let next_button_id = format!("{}next", ctx_id); + + let colour = Colour::TEAL; + + let components = CreateActionRow::Buttons(vec![ + CreateButton::new(&prev_button_id).emoji('◀'), + CreateButton::new(&next_button_id).emoji('▶'), + ]); + let mut current_page = 0; + + // Don't paginate if its one page. + let reply = if pages.len() > 1 { + CreateReply::default() + .embed( + CreateEmbed::default() + .title(embed_title) + .fields(pages[current_page].clone()) + .colour(colour) + .footer(CreateEmbedFooter::new(format!( + "Page: {}/{}", + current_page + 1, + pages.len() + ))), + ) + .components(vec![components]) + } else { + CreateReply::default().embed( + CreateEmbed::default() + .title(embed_title) + .colour(colour) + .fields(pages[current_page].clone()), + ) + }; + + let msg = ctx.send(reply).await?; + + if pages.len() > 1 { + while let Some(press) = ComponentInteractionCollector::new(ctx) + .filter(move |press| press.data.custom_id.starts_with(&ctx_id.to_string())) + .timeout(std::time::Duration::from_secs(180)) + .await + { + if press.data.custom_id == next_button_id { + current_page += 1; + if current_page >= pages.len() { + current_page = 0; + } + } else if press.data.custom_id == prev_button_id { + current_page = current_page.checked_sub(1).unwrap_or(pages.len() - 1); + } else { + continue; + } + + press + .create_response( + ctx.serenity_context(), + CreateInteractionResponse::UpdateMessage( + CreateInteractionResponseMessage::new().embed( + serenity::CreateEmbed::new() + .title(embed_title) + .colour(colour) + .fields(pages[current_page].clone()) + .footer(CreateEmbedFooter::new(format!( + "Page: {}/{}", + current_page + 1, + pages.len() + ))), + ), + ), + ) + .await?; + } + // Remove components after timeout. + msg.edit( + ctx, + poise::CreateReply::default() + .embed( + serenity::CreateEmbed::default() + .title(embed_title) + .colour(colour) + .fields(pages[current_page].clone()) + .footer(CreateEmbedFooter::new(format!( + "Page: {}/{}", + current_page + 1, + pages.len() + ))), + ) + .components(vec![]), + ) + .await?; + } + + Ok(()) +} diff --git a/src/commands/snippets.rs b/src/commands/snippets.rs index 2a2134b..05a85fc 100644 --- a/src/commands/snippets.rs +++ b/src/commands/snippets.rs @@ -4,7 +4,7 @@ use crate::{ Context, Error, }; use ::serenity::futures::{Stream, StreamExt}; -use poise::serenity_prelude::{futures, Colour, CreateAttachment, CreateEmbed}; +use poise::serenity_prelude::{futures, CreateAttachment, CreateEmbed}; async fn autocomplete_snippet<'a>( ctx: Context<'a>, @@ -142,11 +142,11 @@ pub async fn edit_snippet( Ok(()) } -/// Delete snippet +/// Removes a snippet /// /// Must use the full formatted snippet name (id: title) -#[poise::command(rename = "delete-snippet", slash_command, guild_only)] -pub async fn delete_snippet( +#[poise::command(rename = "remove-snippet", slash_command, guild_only)] +pub async fn remove_snippet( ctx: Context<'_>, #[autocomplete = "autocomplete_snippet"] #[description = "The snippet's id"] @@ -172,6 +172,7 @@ pub async fn delete_snippet( /// Lists all snippets #[poise::command( rename = "list-snippets", + aliases("list-snippet", "snippets"), slash_command, prefix_command, guild_only, @@ -180,15 +181,25 @@ pub async fn delete_snippet( pub async fn list_snippets(ctx: Context<'_>) -> Result<(), Error> { let snippets = { ctx.data().state.read().unwrap().snippets.clone() }; - let mut embed = CreateEmbed::default().title("Snippets").color(Colour::TEAL); - - // fields are limited to 25 max, we can't display more than 25 snippets in the snippets command - // due to a discord limitation. - for snippet in snippets.iter().take(25) { - embed = embed.field(format!("`{}`", snippet.id), &snippet.title, false); + if snippets.is_empty() { + respond_err( + &ctx, + "Cannot send list of snippets", + "There are no snippets to list!", + ) + .await; + return Ok(()); } - ctx.send(poise::CreateReply::default().embed(embed)).await?; + let pages: Vec> = snippets + .iter() + .map(|snippet| (snippet.id.clone(), snippet.title.clone(), true)) + .collect::>() + .chunks(25) + .map(|chunk| chunk.to_vec()) + .collect(); + + super::paginate_lists(ctx, &pages, "Snippets").await?; Ok(()) } diff --git a/src/commands/utils.rs b/src/commands/utils.rs index 0430c9b..bf6adfa 100644 --- a/src/commands/utils.rs +++ b/src/commands/utils.rs @@ -255,30 +255,30 @@ pub async fn edit_embed( Ok(()) } -/// Adds an issue token -#[poise::command(rename = "add-issue-token", slash_command, guild_only)] -pub async fn add_issue_token( +/// Adds a repository token +#[poise::command(rename = "add-repository", slash_command, guild_only)] +pub async fn add_repo( ctx: Context<'_>, - #[description = "The key to the issue token in a lowercase alphabetic string"] key: String, + #[description = "The key to the repository in a lowercase alphabetic string"] key: String, #[description = "The owner of the repository."] owner: String, #[description = "The respository name."] repository: String, ) -> Result<(), Error> { let key_regex = Regex::new(r"[a-z+]+$").unwrap(); - let repo_details_regex = Regex::new(r"^[a-zA-Z0-9](?:[a-zA-Z0-9.-]*[a-zA-Z0-9])?$").unwrap(); + let repo_details_regex = Regex::new(r"^([a-zA-Z0-9-_]+)*$").unwrap(); if !key_regex.is_match(&key) { respond_err( &ctx, - "Issue token parsing error", - "The key is limited to lowercase letters only.", + "Key parsing error", + "The key can only lowercase ASCII letters, digits, and the characters ., -, and _.", ) .await; return Ok(()); } - if !repo_details_regex.is_match(&key) || !repo_details_regex.is_match(&repository) { + if !repo_details_regex.is_match(&owner) || !repo_details_regex.is_match(&repository) { respond_err( &ctx, - "Issue token parsing error", - "Your inputs for owner and repository name must be valid.", + "Repository details parsing error", + "Your inputs for owner and repository name must be valid repository names.", ) .await; return Ok(()); @@ -291,10 +291,14 @@ pub async fn add_issue_token( name: repository.clone(), }; - rwlock_guard.issue_prefixes.insert(key.clone(), details); + rwlock_guard + .issue_prefixes + .insert(key.clone().to_lowercase(), details); println!( - "Successfully added issue token {} for **{}/{}**", - key, owner, repository + "Successfully added repository {} for **{}/{}**", + key.to_lowercase(), + owner, + repository ); rwlock_guard.write(); }; @@ -309,12 +313,12 @@ pub async fn add_issue_token( Ok(()) } -/// Removes an issue token. -#[poise::command(rename = "remove-issue-token", slash_command, guild_only)] -pub async fn remove_issue_token( +/// Removes a repository +#[poise::command(rename = "remove-repository", slash_command, guild_only)] +pub async fn remove_repo( ctx: Context<'_>, #[autocomplete = "autocomplete_key"] - #[description = "The issue token key."] + #[description = "The repository key."] key: String, ) -> Result<(), Error> { // I know we could just do rm_repo, but that doesn't return a result. @@ -322,15 +326,15 @@ pub async fn remove_issue_token( // impl a solution directly into the types? // not sure why I have to do this, it won't settle otherwise. - let key_str = format!("The issue token with the key '{}' has been removed", key); + let key_str = format!("The repository with the key '{}' has been removed", key); match get_repo_details(&ctx, &key).await { Some(_) => { rm_repo(&ctx, &key).await; - respond_ok(&ctx, "Successfully removed token!", &key_str).await; + respond_ok(&ctx, "Successfully removed repository!", &key_str).await; } None => { - let title = "Failure to find issue token"; + let title = "Failure to find repository"; let content = format!("The key '{}' does not exist.", key); respond_err(&ctx, title, &content).await; } @@ -339,24 +343,51 @@ pub async fn remove_issue_token( Ok(()) } -/// Lists all snippets +/// Lists all repositories #[poise::command( - rename = "list-tokens", + rename = "list-repositories", + aliases("repos-list", "list-repos", "repos"), slash_command, prefix_command, guild_only, track_edits )] -pub async fn list_tokens(ctx: Context<'_>) -> Result<(), Error> { +pub async fn list_repos(ctx: Context<'_>) -> Result<(), Error> { let tokens = { ctx.data().state.read().unwrap().issue_prefixes.clone() }; + if tokens.is_empty() { + respond_err( + &ctx, + "Cannot send list of repositories", + "There are no repositories to list!", + ) + .await; + return Ok(()); + } + + let pages: Vec> = tokens + .iter() + .map(|token| { + ( + token.0.clone(), + format!("{}/{}", token.1.name, token.1.owner), + true, + ) + }) + .collect::>() + .chunks(25) + .map(|chunk| chunk.to_vec()) + .collect(); + + super::paginate_lists(ctx, &pages, "Repositories").await?; + let mut embed = CreateEmbed::default() .title("Issue tokens") .color(Colour::TEAL); // fields are limited to 25 max, we can't display more than 25 snippets in the snippets command // due to a discord limitation. - for token in tokens.iter().take(25) { + for token in tokens { embed = embed.field( format!("**{}**", token.0), format!("{}/{}", token.1.owner, token.1.name), diff --git a/src/events/issue.rs b/src/events/issue.rs index 761a769..d7fc1e9 100644 --- a/src/events/issue.rs +++ b/src/events/issue.rs @@ -45,6 +45,7 @@ pub async fn message(data: &Data, ctx: &Context, message: &Message) { .await { // Safe to unwap member because this only runs in guilds. + // The only way this could go wrong if cache isn't ready? (fresh bot restart) let has_perms = press.member.as_ref().map_or(false, |member| { member.permissions.map_or(false, |member_perms| { member_perms.contains(Permissions::MANAGE_MESSAGES) @@ -106,7 +107,8 @@ async fn issue_embeds(data: &Data, message: &Message) -> Option let client = octocrab::instance(); let ratelimit = client.ratelimit(); - let regex = Regex::new(r#" ?([a-z]+)?#([0-9]+[0-9]) ?"#).expect("Expected numbers regex"); + let regex = + Regex::new(r#" ?([a-zA-Z0-9-_.]+)?#([0-9]+[0-9]) ?"#).expect("Expected numbers regex"); let custom_repos = { data.state.read().unwrap().issue_prefixes.clone() }; @@ -118,7 +120,7 @@ async fn issue_embeds(data: &Data, message: &Message) -> Option let issue_num = m.as_str().parse::().expect("Match is not a number"); if let Some(repo) = capture.get(1) { - let repository = custom_repos.get(repo.as_str()); + let repository = custom_repos.get(&repo.as_str().to_lowercase()); if let Some(repository) = repository { let (owner, repo) = repository.get(); diff --git a/src/main.rs b/src/main.rs index 4c7936b..ac388a4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -48,15 +48,15 @@ async fn main() { commands::register(), commands::snippets::snippet(), commands::snippets::create_snippet(), - commands::snippets::delete_snippet(), + commands::snippets::remove_snippet(), commands::snippets::export_snippet(), commands::snippets::list_snippets(), commands::snippets::edit_snippet(), commands::utils::embed(), commands::utils::edit_embed(), - commands::utils::add_issue_token(), - commands::utils::remove_issue_token(), - commands::utils::list_tokens(), + commands::utils::add_repo(), + commands::utils::remove_repo(), + commands::utils::list_repos(), ], prefix_options: poise::PrefixFrameworkOptions { prefix: Some("!".into()), From 1c5ba1a984b191bf98f0856a59fbb9fb378ca839 Mon Sep 17 00:00:00 2001 From: jamesbt365 Date: Tue, 5 Dec 2023 20:42:06 +0000 Subject: [PATCH 5/5] various code fixes - Fixes missing . in regex for repos - fix list-repos sending 2 messages - fix missing url (and url parsing) in embed command - fix embed command sending 2 responses in certain errors. - lazily match everywhere due to autocomplete limitations - remove redundant 25+ snippet warning --- src/commands/snippets.rs | 8 +++--- src/commands/utils.rs | 55 ++++++++++++++++++---------------------- 2 files changed, 27 insertions(+), 36 deletions(-) diff --git a/src/commands/snippets.rs b/src/commands/snippets.rs index 05a85fc..dd0c296 100644 --- a/src/commands/snippets.rs +++ b/src/commands/snippets.rs @@ -112,7 +112,7 @@ pub async fn edit_snippet( #[description = "The snippet's title"] title: Option, #[description = "The snippet's content"] content: Option, ) -> Result<(), Error> { - match get_snippet(&ctx, &id).await { + match get_snippet_lazy(&ctx, &id).await { Some(mut snippet) => { if let Some(title) = title { snippet.title = title; @@ -143,8 +143,6 @@ pub async fn edit_snippet( } /// Removes a snippet -/// -/// Must use the full formatted snippet name (id: title) #[poise::command(rename = "remove-snippet", slash_command, guild_only)] pub async fn remove_snippet( ctx: Context<'_>, @@ -152,7 +150,7 @@ pub async fn remove_snippet( #[description = "The snippet's id"] id: String, ) -> Result<(), Error> { - match get_snippet(&ctx, &id).await { + match get_snippet_lazy(&ctx, &id).await { Some(snippet) => { rm_snippet(&ctx, &snippet).await; let title = &"Snippet successfully removed"; @@ -247,7 +245,7 @@ impl Embeddable for Snippet { } // Exact matches the snippet id and name. -async fn get_snippet(ctx: &Context<'_>, id: &str) -> Option { +async fn _get_snippet(ctx: &Context<'_>, id: &str) -> Option { let data = ctx.data(); let rwlock_guard = data.state.read().unwrap(); diff --git a/src/commands/utils.rs b/src/commands/utils.rs index bf6adfa..d63acdb 100644 --- a/src/commands/utils.rs +++ b/src/commands/utils.rs @@ -50,8 +50,6 @@ pub async fn embed( || thumbnail.is_some() || footer.is_some(); - let url_invalid = url.is_some() && title.is_none(); - if !at_least_one_property_set { respond_err( &ctx, @@ -59,23 +57,34 @@ pub async fn embed( "Please provide at least one title, description, image, footer or thumbnail", ) .await; - ctx.say("You must provide at least one title, description, image or thumbnail.") - .await?; return Ok(()); } + let mut embed = CreateEmbed::default(); - if url_invalid { - respond_err( - &ctx, - "Failed to respond with embed", - "To set a url, you must set a title", - ) - .await; - return Ok(()); + if let Some(url) = url { + match url.parse::() { + Ok(_) => { + if title.is_some() { + embed = embed.url(url) + } else { + respond_err( + &ctx, + "Failed to respond with embed", + "To set a url, you must set a title.", + ) + .await; + return Ok(()); + } + } + Err(e) => { + let title = "Invalid url provided"; + let content = &format!("The url '{}' is not a valid url: {}", url, e); + respond_err(&ctx, title, content).await; + return Ok(()); + } + } } - let mut embed = CreateEmbed::default(); - if let Some(title) = title { embed = embed.title(title); } @@ -264,7 +273,7 @@ pub async fn add_repo( #[description = "The respository name."] repository: String, ) -> Result<(), Error> { let key_regex = Regex::new(r"[a-z+]+$").unwrap(); - let repo_details_regex = Regex::new(r"^([a-zA-Z0-9-_]+)*$").unwrap(); + let repo_details_regex = Regex::new(r"^([a-zA-Z0-9-_.]+)*$").unwrap(); if !key_regex.is_match(&key) { respond_err( &ctx, @@ -381,22 +390,6 @@ pub async fn list_repos(ctx: Context<'_>) -> Result<(), Error> { super::paginate_lists(ctx, &pages, "Repositories").await?; - let mut embed = CreateEmbed::default() - .title("Issue tokens") - .color(Colour::TEAL); - - // fields are limited to 25 max, we can't display more than 25 snippets in the snippets command - // due to a discord limitation. - for token in tokens { - embed = embed.field( - format!("**{}**", token.0), - format!("{}/{}", token.1.owner, token.1.name), - false, - ); - } - - ctx.send(poise::CreateReply::default().embed(embed)).await?; - Ok(()) }