From 45403e9d9bca09bff75e353e7fabe6f308eee58e Mon Sep 17 00:00:00 2001 From: seth Date: Sun, 3 Dec 2023 04:11:57 -0500 Subject: [PATCH 01/90] initial rewrite in rust & moderation commands Signed-off-by: seth --- .envrc | 10 +- .eslintignore | 2 - .eslintrc.cjs | 11 - .gitignore | 22 +- Cargo.lock | 2418 ++++++++++++++++++++++++++++ Cargo.toml | 33 + package.json | 30 - pnpm-lock.yaml | 1562 ------------------ renovate.json | 2 +- src/_reupload.ts | 79 - src/api/dadjoke.rs | 21 + src/api/mod.rs | 17 + src/api/rory.rs | 42 + src/commands/general/joke.rs | 12 + src/commands/general/members.rs | 25 + src/commands/general/mod.rs | 13 + src/commands/general/modrinth.rs | 8 + src/commands/general/rory.rs | 21 + src/commands/general/say.rs | 43 + src/commands/general/stars.rs | 30 + src/commands/joke.ts | 11 - src/commands/members.ts | 24 - src/commands/mod.rs | 20 + src/commands/moderation/actions.rs | 80 + src/commands/moderation/mod.rs | 3 + src/commands/modrinth.ts | 124 -- src/commands/rory.ts | 51 - src/commands/say.ts | 37 - src/commands/stars.ts | 23 - src/commands/tags.ts | 38 - src/config/discord.rs | 87 + src/config/github.rs | 65 + src/config/mod.rs | 39 + src/constants.ts | 27 - src/consts.rs | 13 + src/handlers/error.rs | 42 + src/handlers/event/mod.rs | 24 + src/handlers/mod.rs | 5 + src/index.ts | 223 --- src/logproviders/0x0.ts | 19 - src/logproviders/haste.ts | 21 - src/logproviders/mclogs.ts | 22 - src/logproviders/pastegg.ts | 30 - src/logs.ts | 251 --- src/main.rs | 91 ++ src/storage.ts | 22 - src/tags.ts | 37 - src/utils/macros.rs | 6 + src/utils/mod.rs | 110 ++ src/utils/pluralKit.ts | 22 - src/utils/remoteVersions.ts | 30 - src/utils/resolveMessage.ts | 106 -- tsconfig.json | 13 - 53 files changed, 3297 insertions(+), 2820 deletions(-) delete mode 100644 .eslintignore delete mode 100644 .eslintrc.cjs create mode 100644 Cargo.lock create mode 100644 Cargo.toml delete mode 100644 package.json delete mode 100644 pnpm-lock.yaml delete mode 100644 src/_reupload.ts create mode 100644 src/api/dadjoke.rs create mode 100644 src/api/mod.rs create mode 100644 src/api/rory.rs create mode 100644 src/commands/general/joke.rs create mode 100644 src/commands/general/members.rs create mode 100644 src/commands/general/mod.rs create mode 100644 src/commands/general/modrinth.rs create mode 100644 src/commands/general/rory.rs create mode 100644 src/commands/general/say.rs create mode 100644 src/commands/general/stars.rs delete mode 100644 src/commands/joke.ts delete mode 100644 src/commands/members.ts create mode 100644 src/commands/mod.rs create mode 100644 src/commands/moderation/actions.rs create mode 100644 src/commands/moderation/mod.rs delete mode 100644 src/commands/modrinth.ts delete mode 100644 src/commands/rory.ts delete mode 100644 src/commands/say.ts delete mode 100644 src/commands/stars.ts delete mode 100644 src/commands/tags.ts create mode 100644 src/config/discord.rs create mode 100644 src/config/github.rs create mode 100644 src/config/mod.rs delete mode 100644 src/constants.ts create mode 100644 src/consts.rs create mode 100644 src/handlers/error.rs create mode 100644 src/handlers/event/mod.rs create mode 100644 src/handlers/mod.rs delete mode 100644 src/index.ts delete mode 100644 src/logproviders/0x0.ts delete mode 100644 src/logproviders/haste.ts delete mode 100644 src/logproviders/mclogs.ts delete mode 100644 src/logproviders/pastegg.ts delete mode 100644 src/logs.ts create mode 100644 src/main.rs delete mode 100644 src/storage.ts delete mode 100644 src/tags.ts create mode 100644 src/utils/macros.rs create mode 100644 src/utils/mod.rs delete mode 100644 src/utils/pluralKit.ts delete mode 100644 src/utils/remoteVersions.ts delete mode 100644 src/utils/resolveMessage.ts delete mode 100644 tsconfig.json diff --git a/.envrc b/.envrc index a96880d..0475d40 100644 --- a/.envrc +++ b/.envrc @@ -1,2 +1,10 @@ -use flake +# only use flake when `nix` is present +if command -v nix &> /dev/null; then + if ! has nix_direnv_version || ! nix_direnv_version 2.2.1; then + source_url "https://raw.githubusercontent.com/nix-community/nix-direnv/2.2.1/direnvrc" "sha256-zelF0vLbEl5uaqrfIzbgNzJWGmLzCmYAkInj/LNxvKs=" + fi + + use flake +fi + dotenv_if_exists diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index b947077..0000000 --- a/.eslintignore +++ /dev/null @@ -1,2 +0,0 @@ -node_modules/ -dist/ diff --git a/.eslintrc.cjs b/.eslintrc.cjs deleted file mode 100644 index 11db6be..0000000 --- a/.eslintrc.cjs +++ /dev/null @@ -1,11 +0,0 @@ -/* eslint-env node */ - -module.exports = { - root: true, - parser: '@typescript-eslint/parser', - plugins: ['@typescript-eslint'], - extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended'], - rules: { - '@typescript-eslint/no-non-null-assertion': 0, - }, -}; diff --git a/.gitignore b/.gitignore index ae15518..bdd812e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,13 +1,29 @@ -node_modules/ +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb + + +# direnv secrets .env .env.* !.env.example +# Nix .direnv/ .pre-commit-config.yaml - -eslint_report.json +result* +repl-result-out* .DS_Store *.rdb diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..5e28afd --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,2418 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "addr2line" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "aho-corasick" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "arc-swap" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bddcadddf5e9015d310179a59bb28c4d4b9920ad0f11e8e14dbadf654890c9a6" + +[[package]] +name = "async-trait" +version = "0.1.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.39", +] + +[[package]] +name = "async-tungstenite" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1b71b31561643aa8e7df3effe284fa83ab1a840e52294c5f4bd7bfd8b2becbb" +dependencies = [ + "futures-io", + "futures-util", + "log", + "pin-project-lite", + "tokio", + "tokio-rustls 0.23.4", + "tungstenite", + "webpki-roots 0.22.6", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "backtrace" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "base64" +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.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" + +[[package]] +name = "cc" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "libc", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "num-traits", + "serde", + "windows-targets 0.48.5", +] + +[[package]] +name = "color-eyre" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a667583cca8c4f8436db8de46ea8233c42a7d9ae424a82d338f2e4675229204" +dependencies = [ + "backtrace", + "color-spantrace", + "eyre", + "indenter", + "once_cell", + "owo-colors", + "tracing-error", +] + +[[package]] +name = "color-spantrace" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd6be1b2a7e382e2b98b43b2adcca6bb0e465af0bdd38123873ae61eb17a72c2" +dependencies = [ + "once_cell", + "owo-colors", + "tracing-core", + "tracing-error", +] + +[[package]] +name = "combine" +version = "4.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35ed6e9d84f0b51a7f52daf1c7d71dd136fd7a3f41a8462b8cdb8c78d920fad4" +dependencies = [ + "bytes", + "futures-core", + "memchr", + "pin-project-lite", + "tokio", + "tokio-util", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" + +[[package]] +name = "cpufeatures" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce420fe07aecd3e67c5f910618fe65e94158f6dcc0adf44e00d69ce2bdfe0fd0" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "darling" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b750cb3417fd1b327431a470f388520309479ab0bf5e323505daf0290cd3850" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "109c1ca6e6b7f82cc233a97004ea8ed7ca123a9af07a8230878fcfda9b158bf0" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 1.0.109", +] + +[[package]] +name = "darling_macro" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4aab4dbc9f7611d8b55048a3a16d2d010c2c8334e46304b40ac1cc14bf3b48e" +dependencies = [ + "darling_core", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "dashmap" +version = "5.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" +dependencies = [ + "cfg-if", + "hashbrown", + "lock_api", + "once_cell", + "parking_lot_core", + "serde", +] + +[[package]] +name = "deranged" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eb30d70a07a3b04884d2677f06bec33509dc67ca60d92949e5535352d3191dc" +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.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "doc-comment" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" + +[[package]] +name = "dotenvy" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" + +[[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.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "env_logger" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95b3f3e67048839cb0d0781f445682a35113da7121f7c949db0e2be96a4fbece" +dependencies = [ + "humantime", + "is-terminal", + "log", + "regex", + "termcolor", +] + +[[package]] +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 = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "eyre" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80f656be11ddf91bd709454d15d5bd896fbaf4cc3314e69349e4d1569f5b46cd" +dependencies = [ + "indenter", + "once_cell", +] + +[[package]] +name = "flate2" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0290714b38af9b4a7b094b8a37086d1b4e61f2df9122c3cad2577669145335" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff4dd66668b557604244583e3e1e1eada8c5c2e96a6d0d6653ede395b78bbacb" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb1d22c66e66d9d72e1758f0bd7d4fd0bee04cad842ee34587d68c07e45d088c" + +[[package]] +name = "futures-executor" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f4fb8693db0cf099eadcca0efe2a5a22e4550f98ed16aba6c48700da29597bc" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +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 = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.39", +] + +[[package]] +name = "futures-sink" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e36d3378ee38c2a36ad710c5d30c2911d752cb941c00c72dbabfb786a7970817" + +[[package]] +name = "futures-task" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efd193069b0ddadc69c46389b740bbccdd97203899b48d09c5f7969591d6bae2" + +[[package]] +name = "futures-util" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a19526d624e703a3179b3d322efec918b6246ea0fa51d41124525f00f1cc8104" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "gimli" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" + +[[package]] +name = "h2" +version = "0.3.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d6250322ef6e60f93f9a2162799302cd6f68f79f6e5d85c8c16f14d1d958178" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "hermit-abi" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" + +[[package]] +name = "http" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8947b1a6fad4393052c7ba1f4cd97bed3e953a95c79c92ad9b051a04611d9fbb" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[package]] +name = "http-range-header" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "add0ab9360ddbd88cfeb3bd9574a1d85cfdfa14db10b3e21d3700dbc4328758f" + +[[package]] +name = "httparse" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "hyper" +version = "0.14.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2 0.4.10", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" +dependencies = [ + "futures-util", + "http", + "hyper", + "log", + "rustls 0.21.9", + "rustls-native-certs", + "tokio", + "tokio-rustls 0.24.1", +] + +[[package]] +name = "hyper-timeout" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" +dependencies = [ + "hyper", + "pin-project-lite", + "tokio", + "tokio-io-timeout", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8326b86b6cff230b97d0d312a6c40a60726df3332e721f72a1b035f451663b20" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "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.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "indenter" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" + +[[package]] +name = "indexmap" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "ipnet" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" + +[[package]] +name = "iri-string" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21859b667d66a4c1dacd9df0863b3efb65785474255face87f5bca39dd8407c0" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "is-terminal" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" +dependencies = [ + "hermit-abi", + "rustix", + "windows-sys 0.48.0", +] + +[[package]] +name = "itoa" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" + +[[package]] +name = "js-sys" +version = "0.3.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cee9c64da59eae3b50095c18d3e74f8b73c0b86d2792824ff01bbce68ba229ca" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "jsonwebtoken" +version = "9.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c7ea04a7c5c055c175f189b6dc6ba036fd62306b58c66c9f6389036c503a3f4" +dependencies = [ + "base64 0.21.5", + "js-sys", + "pem", + "ring 0.17.6", + "serde", + "serde_json", + "simple_asn1", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.150" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" + +[[package]] +name = "linux-raw-sys" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" + +[[package]] +name = "lock_api" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" + +[[package]] +name = "memchr" +version = "2.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mime_guess" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef" +dependencies = [ + "mime", + "unicase", +] + +[[package]] +name = "miniz_oxide" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +dependencies = [ + "adler", +] + +[[package]] +name = "mio" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dce281c5e46beae905d4de1870d8b1509a9142b62eedf18b443b011ca8343d0" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.48.0", +] + +[[package]] +name = "num-bigint" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "object" +version = "0.32.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" +dependencies = [ + "memchr", +] + +[[package]] +name = "octocrab" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abfeeafb5fa0da7046229ec3c7b3bd2981aae05c549871192c408d59fc0fffd5" +dependencies = [ + "arc-swap", + "async-trait", + "base64 0.21.5", + "bytes", + "cfg-if", + "chrono", + "either", + "futures", + "futures-util", + "http", + "http-body", + "hyper", + "hyper-rustls", + "hyper-timeout", + "jsonwebtoken", + "once_cell", + "percent-encoding", + "pin-project", + "secrecy", + "serde", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "snafu", + "tokio", + "tower", + "tower-http", + "tracing", + "url", +] + +[[package]] +name = "once_cell" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "ordered-float" +version = "2.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68f19d67e5a2795c94e73e0bb1cc1a7edeb2e28efd39e2e1c9b7a40c1108b11c" +dependencies = [ + "num-traits", +] + +[[package]] +name = "owo-colors" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.48.5", +] + +[[package]] +name = "pem" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3163d2912b7c3b52d651a055f2c7eec9ba5cd22d26ef75b8dd3a59980b185923" +dependencies = [ + "base64 0.21.5", + "serde", +] + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pin-project" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.39", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "poise" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d104e4b5847283b2fbd6a7ec19fb6a8af328e2145623d056b66d750a30073fdf" +dependencies = [ + "async-trait", + "derivative", + "futures-core", + "futures-util", + "log", + "once_cell", + "parking_lot", + "poise_macros", + "regex", + "serenity", + "tokio", +] + +[[package]] +name = "poise_macros" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb516a8cf4e4ae4bd7ef5819d08c6ca408976461a9bea3ee3eec5138ac070c1" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[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.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "proc-macro2" +version = "1.0.70" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "redis" +version = "0.23.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f49cdc0bb3f412bf8e7d1bd90fe1d9eb10bc5c399ba90973c14662a27b3f8ba" +dependencies = [ + "async-trait", + "bytes", + "combine", + "futures-util", + "itoa", + "percent-encoding", + "pin-project-lite", + "rustls 0.21.9", + "rustls-native-certs", + "ryu", + "sha1_smol", + "socket2 0.4.10", + "tokio", + "tokio-rustls 0.24.1", + "tokio-util", + "url", +] + +[[package]] +name = "redis-macros" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60eb39e2b44d4c0f9c84e7c5fc4fc3adc8dd26ec48f1ac3a160033f7c03b18fd" +dependencies = [ + "redis", + "redis-macros-derive", + "serde", + "serde_json", +] + +[[package]] +name = "redis-macros-derive" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39550b9e94ce430a349c5490ca4efcae90ab8189603320f88c1d69f0326f169e" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "refraction" +version = "2.0.0" +dependencies = [ + "color-eyre", + "dotenvy", + "env_logger", + "log", + "octocrab", + "once_cell", + "poise", + "rand", + "redis", + "redis-macros", + "reqwest", + "serde", + "serde_json", + "tokio", + "url", +] + +[[package]] +name = "regex" +version = "1.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" + +[[package]] +name = "reqwest" +version = "0.11.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "046cd98826c46c2ac8ddecae268eb5c2e58628688a5fc7a2643704a73faba95b" +dependencies = [ + "base64 0.21.5", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper", + "hyper-rustls", + "ipnet", + "js-sys", + "log", + "mime", + "mime_guess", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls 0.21.9", + "rustls-pemfile", + "serde", + "serde_json", + "serde_urlencoded", + "system-configuration", + "tokio", + "tokio-rustls 0.24.1", + "tokio-util", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", + "webpki-roots 0.25.3", + "winreg", +] + +[[package]] +name = "ring" +version = "0.16.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +dependencies = [ + "cc", + "libc", + "once_cell", + "spin 0.5.2", + "untrusted 0.7.1", + "web-sys", + "winapi", +] + +[[package]] +name = "ring" +version = "0.17.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "684d5e6e18f669ccebf64a92236bb7db9a34f07be010e3627368182027180866" +dependencies = [ + "cc", + "getrandom", + "libc", + "spin 0.9.8", + "untrusted 0.9.0", + "windows-sys 0.48.0", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + +[[package]] +name = "rustix" +version = "0.38.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9470c4bf8246c8daf25f9598dca807fb6510347b1e1cfa55749113850c79d88a" +dependencies = [ + "bitflags 2.4.1", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustls" +version = "0.20.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b80e3dec595989ea8510028f30c408a4630db12c9cbb8de34203b89d6577e99" +dependencies = [ + "log", + "ring 0.16.20", + "sct", + "webpki", +] + +[[package]] +name = "rustls" +version = "0.21.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "629648aced5775d558af50b2b4c7b02983a04b312126d45eeead26e7caa498b9" +dependencies = [ + "log", + "ring 0.17.6", + "rustls-webpki", + "sct", +] + +[[package]] +name = "rustls-native-certs" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00" +dependencies = [ + "openssl-probe", + "rustls-pemfile", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pemfile" +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 = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" +dependencies = [ + "ring 0.17.6", + "untrusted 0.9.0", +] + +[[package]] +name = "rustversion" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" + +[[package]] +name = "ryu" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" + +[[package]] +name = "schannel" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88" +dependencies = [ + "windows-sys 0.48.0", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "sct" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" +dependencies = [ + "ring 0.17.6", + "untrusted 0.9.0", +] + +[[package]] +name = "secrecy" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bd1c54ea06cfd2f6b63219704de0b9b4f72dcc2b8fdef820be6cd799780e91e" +dependencies = [ + "zeroize", +] + +[[package]] +name = "security-framework" +version = "2.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "serde" +version = "1.0.193" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde-value" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c" +dependencies = [ + "ordered-float", + "serde", +] + +[[package]] +name = "serde_derive" +version = "1.0.193" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.39", +] + +[[package]] +name = "serde_json" +version = "1.0.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_path_to_error" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4beec8bce849d58d06238cb50db2e1c417cfeafa4c63f692b15c82b7c80f8335" +dependencies = [ + "itoa", + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serenity" +version = "0.11.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a7a89cef23483fc9d4caf2df41e6d3928e18aada84c56abd237439d929622c6" +dependencies = [ + "async-trait", + "async-tungstenite", + "base64 0.21.5", + "bitflags 1.3.2", + "bytes", + "cfg-if", + "chrono", + "dashmap", + "flate2", + "futures", + "mime", + "mime_guess", + "parking_lot", + "percent-encoding", + "reqwest", + "rustversion", + "serde", + "serde-value", + "serde_json", + "time", + "tokio", + "tracing", + "typemap_rev", + "url", +] + +[[package]] +name = "sha-1" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5058ada175748e33390e40e872bd0fe59a19f265d0158daa551c5a88a76009c" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha1_smol" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012" + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +dependencies = [ + "libc", +] + +[[package]] +name = "simple_asn1" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adc4e5204eb1910f40f9cfa375f6f05b68c3abac4b6fd879c8ff5e7ae8a0a085" +dependencies = [ + "num-bigint", + "num-traits", + "thiserror", + "time", +] + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" + +[[package]] +name = "snafu" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4de37ad025c587a29e8f3f5605c00f70b98715ef90b9061a815b9e59e9042d6" +dependencies = [ + "backtrace", + "doc-comment", + "snafu-derive", +] + +[[package]] +name = "snafu-derive" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990079665f075b699031e9c08fd3ab99be5029b96f3b78dc0709e8f77e4efebf" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "socket2" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +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 = "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 = "2.0.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +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 = "termcolor" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff1bc3d3f05aff0403e8ac0d92ced918ec05b666a43f83297ccef5bea8a3d449" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "thiserror" +version = "1.0.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.39", +] + +[[package]] +name = "thread_local" +version = "1.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" +dependencies = [ + "cfg-if", + "once_cell", +] + +[[package]] +name = "time" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4a34ab300f2dee6e562c10a046fc05e358b29f9bf92277f30c3c8d82275f6f5" +dependencies = [ + "deranged", + "itoa", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + +[[package]] +name = "time-macros" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ad70d68dba9e1f8aceda7aa6711965dfec1cac869f311a51bd08b3a2ccbce20" +dependencies = [ + "time-core", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0c014766411e834f7af5b8f4cf46257aab4036ca95e9d2c144a10f59ad6f5b9" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "num_cpus", + "pin-project-lite", + "signal-hook-registry", + "socket2 0.5.5", + "tokio-macros", + "windows-sys 0.48.0", +] + +[[package]] +name = "tokio-io-timeout" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30b74022ada614a1b4834de765f9bb43877f910cc8ce4be40e89042c9223a8bf" +dependencies = [ + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-macros" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.39", +] + +[[package]] +name = "tokio-rustls" +version = "0.23.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" +dependencies = [ + "rustls 0.20.9", + "tokio", + "webpki", +] + +[[package]] +name = "tokio-rustls" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" +dependencies = [ + "rustls 0.21.9", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", + "tracing", +] + +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "pin-project", + "pin-project-lite", + "tokio", + "tokio-util", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-http" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c5bb1d698276a2443e5ecfabc1008bf15a36c12e6a7176e7bf089ea9131140" +dependencies = [ + "bitflags 2.4.1", + "bytes", + "futures-core", + "futures-util", + "http", + "http-body", + "http-range-header", + "iri-string", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-layer" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" + +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + +[[package]] +name = "tracing" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.39", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-error" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d686ec1c0f384b1277f097b2f279a2ecc11afe8c133c1aabf036a27cb4cd206e" +dependencies = [ + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +dependencies = [ + "sharded-slab", + "thread_local", + "tracing-core", +] + +[[package]] +name = "try-lock" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" + +[[package]] +name = "tungstenite" +version = "0.17.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e27992fd6a8c29ee7eef28fc78349aa244134e10ad447ce3b9f0ac0ed0fa4ce0" +dependencies = [ + "base64 0.13.1", + "byteorder", + "bytes", + "http", + "httparse", + "log", + "rand", + "rustls 0.20.9", + "sha-1", + "thiserror", + "url", + "utf-8", + "webpki", +] + +[[package]] +name = "typemap_rev" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5b74f0a24b5454580a79abb6994393b09adf0ab8070f15827cb666255de155" + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "unicase" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" +dependencies = [ + "version_check", +] + +[[package]] +name = "unicode-bidi" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unicode-normalization" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ed0d4f68a3015cc185aff4db9506a015f4b96f95303897bfa23f846db54064e" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b56f625e64f3a1084ded111c4d5f477df9f8c92df113852fa5a374dbda78826" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.39", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac36a15a220124ac510204aec1c3e5db8a22ab06fd6706d881dc6149f8ed9a12" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0162dbf37223cd2afce98f3d0785506dcb8d266223983e4b5b525859e6e182b2" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.39", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ab9b36309365056cd639da3134bf87fa8f3d86008abf99e612384a6eecd459f" + +[[package]] +name = "wasm-streams" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4609d447824375f43e1ffbc051b50ad8f4b3ae8219680c94452ea05eb240ac7" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "web-sys" +version = "0.3.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50c24a44ec86bb68fbecd1b3efed7e85ea5621b39b35ef2766b66cd984f8010f" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed63aea5ce73d0ff405984102c42de94fc55a6b75765d621c65262469b3c9b53" +dependencies = [ + "ring 0.17.6", + "untrusted 0.9.0", +] + +[[package]] +name = "webpki-roots" +version = "0.22.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c71e40d7d2c34a5106301fb632274ca37242cd0c9d3e64dbece371a40a2d87" +dependencies = [ + "webpki", +] + +[[package]] +name = "webpki-roots" +version = "0.25.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1778a42e8b3b90bff8d0f5032bf22250792889a5cdc752aa0020c84abe3aaf10" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +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.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +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 = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "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.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +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.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +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.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" + +[[package]] +name = "winreg" +version = "0.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + +[[package]] +name = "zeroize" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..1854b90 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,33 @@ +[package] +name = "refraction" +version = "2.0.0" +edition = "2021" +repository = "https://github.com/PrismLauncher/refraction" +license = "GPL-3.0-or-later" +readme = "README.md" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +color-eyre = "0.6.2" +dotenvy = "0.15.7" +env_logger = "0.10.0" +log = "0.4.20" +poise = "0.5.7" +octocrab = "0.32.0" +once_cell = "1.18.0" +rand = "0.8.5" +redis = { version = "0.23.3", features = ["tokio-comp", "tokio-rustls-comp"] } +redis-macros = "0.2.1" +reqwest = { version = "0.11.22", default-features = false, features = [ + "rustls-tls", + "json", +] } +serde = "1.0.193" +serde_json = "1.0.108" +tokio = { version = "1.33.0", features = [ + "macros", + "rt-multi-thread", + "signal", +] } +url = { version = "2.5.0", features = ["serde"] } diff --git a/package.json b/package.json deleted file mode 100644 index 5ec4f53..0000000 --- a/package.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "name": "refraction", - "version": "1.0.0", - "license": "GPL-3.0", - "scripts": { - "dev": "NODE_ENV=development tsx watch src/index.ts", - "start": "tsx src/index.ts", - "reupload": "tsx src/_reupload.ts", - "lint": "tsc && eslint ." - }, - "dependencies": { - "@discordjs/rest": "2.1.0", - "discord.js": "14.14.1", - "just-random": "3.2.0", - "kleur": "4.1.5", - "redis": "4.6.10", - "tsx": "4.1.1" - }, - "devDependencies": { - "@types/node": "20.9.0", - "@typescript-eslint/eslint-plugin": "6.10.0", - "@typescript-eslint/parser": "6.10.0", - "dotenv": "16.3.1", - "eslint": "8.53.0", - "gray-matter": "4.0.3", - "prettier": "3.0.3", - "typescript": "5.2.2" - }, - "packageManager": "pnpm@8.10.3" -} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml deleted file mode 100644 index dd97a44..0000000 --- a/pnpm-lock.yaml +++ /dev/null @@ -1,1562 +0,0 @@ -lockfileVersion: '6.0' - -settings: - autoInstallPeers: true - excludeLinksFromLockfile: false - -dependencies: - '@discordjs/rest': - specifier: 2.1.0 - version: 2.1.0 - discord.js: - specifier: 14.14.1 - version: 14.14.1 - just-random: - specifier: 3.2.0 - version: 3.2.0 - kleur: - specifier: 4.1.5 - version: 4.1.5 - redis: - specifier: 4.6.10 - version: 4.6.10 - tsx: - specifier: 4.1.1 - version: 4.1.1 - -devDependencies: - '@types/node': - specifier: 20.9.0 - version: 20.9.0 - '@typescript-eslint/eslint-plugin': - specifier: 6.10.0 - version: 6.10.0(@typescript-eslint/parser@6.10.0)(eslint@8.53.0)(typescript@5.2.2) - '@typescript-eslint/parser': - specifier: 6.10.0 - version: 6.10.0(eslint@8.53.0)(typescript@5.2.2) - dotenv: - specifier: 16.3.1 - version: 16.3.1 - eslint: - specifier: 8.53.0 - version: 8.53.0 - gray-matter: - specifier: 4.0.3 - version: 4.0.3 - prettier: - specifier: 3.0.3 - version: 3.0.3 - typescript: - specifier: 5.2.2 - version: 5.2.2 - -packages: - - /@aashutoshrathi/word-wrap@1.2.6: - resolution: {integrity: sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==} - engines: {node: '>=0.10.0'} - dev: true - - /@discordjs/builders@1.7.0: - resolution: {integrity: sha512-GDtbKMkg433cOZur8Dv6c25EHxduNIBsxeHrsRoIM8+AwmEZ8r0tEpckx/sHwTLwQPOF3e2JWloZh9ofCaMfAw==} - engines: {node: '>=16.11.0'} - dependencies: - '@discordjs/formatters': 0.3.3 - '@discordjs/util': 1.0.2 - '@sapphire/shapeshift': 3.9.3 - discord-api-types: 0.37.61 - fast-deep-equal: 3.1.3 - ts-mixer: 6.0.3 - tslib: 2.6.2 - dev: false - - /@discordjs/collection@1.5.3: - resolution: {integrity: sha512-SVb428OMd3WO1paV3rm6tSjM4wC+Kecaa1EUGX7vc6/fddvw/6lg90z4QtCqm21zvVe92vMMDt9+DkIvjXImQQ==} - engines: {node: '>=16.11.0'} - dev: false - - /@discordjs/collection@2.0.0: - resolution: {integrity: sha512-YTWIXLrf5FsrLMycpMM9Q6vnZoR/lN2AWX23/Cuo8uOOtS8eHB2dyQaaGnaF8aZPYnttf2bkLMcXn/j6JUOi3w==} - engines: {node: '>=18'} - dev: false - - /@discordjs/formatters@0.3.3: - resolution: {integrity: sha512-wTcI1Q5cps1eSGhl6+6AzzZkBBlVrBdc9IUhJbijRgVjCNIIIZPgqnUj3ntFODsHrdbGU8BEG9XmDQmgEEYn3w==} - engines: {node: '>=16.11.0'} - dependencies: - discord-api-types: 0.37.61 - dev: false - - /@discordjs/rest@2.1.0: - resolution: {integrity: sha512-5gFWFkZX2JCFSRzs8ltx8bWmyVi0wPMk6pBa9KGIQSDPMmrP+uOrZ9j9HOwvmVWGe+LmZ5Bov0jMnQd6/jVReg==} - engines: {node: '>=16.11.0'} - dependencies: - '@discordjs/collection': 2.0.0 - '@discordjs/util': 1.0.2 - '@sapphire/async-queue': 1.5.0 - '@sapphire/snowflake': 3.5.1 - '@vladfrangu/async_event_emitter': 2.2.2 - discord-api-types: 0.37.61 - magic-bytes.js: 1.5.0 - tslib: 2.6.2 - undici: 5.27.2 - dev: false - - /@discordjs/util@1.0.2: - resolution: {integrity: sha512-IRNbimrmfb75GMNEjyznqM1tkI7HrZOf14njX7tCAAUetyZM1Pr8hX/EK2lxBCOgWDRmigbp24fD1hdMfQK5lw==} - engines: {node: '>=16.11.0'} - dev: false - - /@discordjs/ws@1.0.2: - resolution: {integrity: sha512-+XI82Rm2hKnFwAySXEep4A7Kfoowt6weO6381jgW+wVdTpMS/56qCvoXyFRY0slcv7c/U8My2PwIB2/wEaAh7Q==} - engines: {node: '>=16.11.0'} - dependencies: - '@discordjs/collection': 2.0.0 - '@discordjs/rest': 2.1.0 - '@discordjs/util': 1.0.2 - '@sapphire/async-queue': 1.5.0 - '@types/ws': 8.5.9 - '@vladfrangu/async_event_emitter': 2.2.2 - discord-api-types: 0.37.61 - tslib: 2.6.2 - ws: 8.14.2 - transitivePeerDependencies: - - bufferutil - - utf-8-validate - dev: false - - /@esbuild/android-arm64@0.18.20: - resolution: {integrity: sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==} - engines: {node: '>=12'} - cpu: [arm64] - os: [android] - requiresBuild: true - dev: false - optional: true - - /@esbuild/android-arm@0.18.20: - resolution: {integrity: sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==} - engines: {node: '>=12'} - cpu: [arm] - os: [android] - requiresBuild: true - dev: false - optional: true - - /@esbuild/android-x64@0.18.20: - resolution: {integrity: sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==} - engines: {node: '>=12'} - cpu: [x64] - os: [android] - requiresBuild: true - dev: false - optional: true - - /@esbuild/darwin-arm64@0.18.20: - resolution: {integrity: sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==} - engines: {node: '>=12'} - cpu: [arm64] - os: [darwin] - requiresBuild: true - dev: false - optional: true - - /@esbuild/darwin-x64@0.18.20: - resolution: {integrity: sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==} - engines: {node: '>=12'} - cpu: [x64] - os: [darwin] - requiresBuild: true - dev: false - optional: true - - /@esbuild/freebsd-arm64@0.18.20: - resolution: {integrity: sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==} - engines: {node: '>=12'} - cpu: [arm64] - os: [freebsd] - requiresBuild: true - dev: false - optional: true - - /@esbuild/freebsd-x64@0.18.20: - resolution: {integrity: sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==} - engines: {node: '>=12'} - cpu: [x64] - os: [freebsd] - requiresBuild: true - dev: false - optional: true - - /@esbuild/linux-arm64@0.18.20: - resolution: {integrity: sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==} - engines: {node: '>=12'} - cpu: [arm64] - os: [linux] - requiresBuild: true - dev: false - optional: true - - /@esbuild/linux-arm@0.18.20: - resolution: {integrity: sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==} - engines: {node: '>=12'} - cpu: [arm] - os: [linux] - requiresBuild: true - dev: false - optional: true - - /@esbuild/linux-ia32@0.18.20: - resolution: {integrity: sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==} - engines: {node: '>=12'} - cpu: [ia32] - os: [linux] - requiresBuild: true - dev: false - optional: true - - /@esbuild/linux-loong64@0.18.20: - resolution: {integrity: sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==} - engines: {node: '>=12'} - cpu: [loong64] - os: [linux] - requiresBuild: true - dev: false - optional: true - - /@esbuild/linux-mips64el@0.18.20: - resolution: {integrity: sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==} - engines: {node: '>=12'} - cpu: [mips64el] - os: [linux] - requiresBuild: true - dev: false - optional: true - - /@esbuild/linux-ppc64@0.18.20: - resolution: {integrity: sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==} - engines: {node: '>=12'} - cpu: [ppc64] - os: [linux] - requiresBuild: true - dev: false - optional: true - - /@esbuild/linux-riscv64@0.18.20: - resolution: {integrity: sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==} - engines: {node: '>=12'} - cpu: [riscv64] - os: [linux] - requiresBuild: true - dev: false - optional: true - - /@esbuild/linux-s390x@0.18.20: - resolution: {integrity: sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==} - engines: {node: '>=12'} - cpu: [s390x] - os: [linux] - requiresBuild: true - dev: false - optional: true - - /@esbuild/linux-x64@0.18.20: - resolution: {integrity: sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==} - engines: {node: '>=12'} - cpu: [x64] - os: [linux] - requiresBuild: true - dev: false - optional: true - - /@esbuild/netbsd-x64@0.18.20: - resolution: {integrity: sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==} - engines: {node: '>=12'} - cpu: [x64] - os: [netbsd] - requiresBuild: true - dev: false - optional: true - - /@esbuild/openbsd-x64@0.18.20: - resolution: {integrity: sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==} - engines: {node: '>=12'} - cpu: [x64] - os: [openbsd] - requiresBuild: true - dev: false - optional: true - - /@esbuild/sunos-x64@0.18.20: - resolution: {integrity: sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==} - engines: {node: '>=12'} - cpu: [x64] - os: [sunos] - requiresBuild: true - dev: false - optional: true - - /@esbuild/win32-arm64@0.18.20: - resolution: {integrity: sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==} - engines: {node: '>=12'} - cpu: [arm64] - os: [win32] - requiresBuild: true - dev: false - optional: true - - /@esbuild/win32-ia32@0.18.20: - resolution: {integrity: sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==} - engines: {node: '>=12'} - cpu: [ia32] - os: [win32] - requiresBuild: true - dev: false - optional: true - - /@esbuild/win32-x64@0.18.20: - resolution: {integrity: sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==} - engines: {node: '>=12'} - cpu: [x64] - os: [win32] - requiresBuild: true - dev: false - optional: true - - /@eslint-community/eslint-utils@4.4.0(eslint@8.53.0): - resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - peerDependencies: - eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 - dependencies: - eslint: 8.53.0 - eslint-visitor-keys: 3.4.3 - dev: true - - /@eslint-community/regexpp@4.6.2: - resolution: {integrity: sha512-pPTNuaAG3QMH+buKyBIGJs3g/S5y0caxw0ygM3YyE6yJFySwiGGSzA+mM3KJ8QQvzeLh3blwgSonkFjgQdxzMw==} - engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} - dev: true - - /@eslint/eslintrc@2.1.3: - resolution: {integrity: sha512-yZzuIG+jnVu6hNSzFEN07e8BxF3uAzYtQb6uDkaYZLo6oYZDCq454c5kB8zxnzfCYyP4MIuyBn10L0DqwujTmA==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - dependencies: - ajv: 6.12.6 - debug: 4.3.4 - espree: 9.6.1 - globals: 13.20.0 - ignore: 5.2.4 - import-fresh: 3.3.0 - js-yaml: 4.1.0 - minimatch: 3.1.2 - strip-json-comments: 3.1.1 - transitivePeerDependencies: - - supports-color - dev: true - - /@eslint/js@8.53.0: - resolution: {integrity: sha512-Kn7K8dx/5U6+cT1yEhpX1w4PCSg0M+XyRILPgvwcEBjerFWCwQj5sbr3/VmxqV0JGHCBCzyd6LxypEuehypY1w==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - dev: true - - /@fastify/busboy@2.1.0: - resolution: {integrity: sha512-+KpH+QxZU7O4675t3mnkQKcZZg56u+K/Ct2K+N2AZYNVK8kyeo/bI18tI8aPm3tvNNRyTWfj6s5tnGNlcbQRsA==} - engines: {node: '>=14'} - dev: false - - /@humanwhocodes/config-array@0.11.13: - resolution: {integrity: sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==} - engines: {node: '>=10.10.0'} - dependencies: - '@humanwhocodes/object-schema': 2.0.1 - debug: 4.3.4 - minimatch: 3.1.2 - transitivePeerDependencies: - - supports-color - dev: true - - /@humanwhocodes/module-importer@1.0.1: - resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} - engines: {node: '>=12.22'} - dev: true - - /@humanwhocodes/object-schema@2.0.1: - resolution: {integrity: sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==} - dev: true - - /@nodelib/fs.scandir@2.1.5: - resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} - engines: {node: '>= 8'} - dependencies: - '@nodelib/fs.stat': 2.0.5 - run-parallel: 1.2.0 - dev: true - - /@nodelib/fs.stat@2.0.5: - resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} - engines: {node: '>= 8'} - dev: true - - /@nodelib/fs.walk@1.2.8: - resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} - engines: {node: '>= 8'} - dependencies: - '@nodelib/fs.scandir': 2.1.5 - fastq: 1.15.0 - dev: true - - /@redis/bloom@1.2.0(@redis/client@1.5.11): - resolution: {integrity: sha512-HG2DFjYKbpNmVXsa0keLHp/3leGJz1mjh09f2RLGGLQZzSHpkmZWuwJbAvo3QcRY8p80m5+ZdXZdYOSBLlp7Cg==} - peerDependencies: - '@redis/client': ^1.0.0 - dependencies: - '@redis/client': 1.5.11 - dev: false - - /@redis/client@1.5.11: - resolution: {integrity: sha512-cV7yHcOAtNQ5x/yQl7Yw1xf53kO0FNDTdDU6bFIMbW6ljB7U7ns0YRM+QIkpoqTAt6zK5k9Fq0QWlUbLcq9AvA==} - engines: {node: '>=14'} - dependencies: - cluster-key-slot: 1.1.2 - generic-pool: 3.9.0 - yallist: 4.0.0 - dev: false - - /@redis/graph@1.1.0(@redis/client@1.5.11): - resolution: {integrity: sha512-16yZWngxyXPd+MJxeSr0dqh2AIOi8j9yXKcKCwVaKDbH3HTuETpDVPcLujhFYVPtYrngSco31BUcSa9TH31Gqg==} - peerDependencies: - '@redis/client': ^1.0.0 - dependencies: - '@redis/client': 1.5.11 - dev: false - - /@redis/json@1.0.6(@redis/client@1.5.11): - resolution: {integrity: sha512-rcZO3bfQbm2zPRpqo82XbW8zg4G/w4W3tI7X8Mqleq9goQjAGLL7q/1n1ZX4dXEAmORVZ4s1+uKLaUOg7LrUhw==} - peerDependencies: - '@redis/client': ^1.0.0 - dependencies: - '@redis/client': 1.5.11 - dev: false - - /@redis/search@1.1.5(@redis/client@1.5.11): - resolution: {integrity: sha512-hPP8w7GfGsbtYEJdn4n7nXa6xt6hVZnnDktKW4ArMaFQ/m/aR7eFvsLQmG/mn1Upq99btPJk+F27IQ2dYpCoUg==} - peerDependencies: - '@redis/client': ^1.0.0 - dependencies: - '@redis/client': 1.5.11 - dev: false - - /@redis/time-series@1.0.5(@redis/client@1.5.11): - resolution: {integrity: sha512-IFjIgTusQym2B5IZJG3XKr5llka7ey84fw/NOYqESP5WUfQs9zz1ww/9+qoz4ka/S6KcGBodzlCeZ5UImKbscg==} - peerDependencies: - '@redis/client': ^1.0.0 - dependencies: - '@redis/client': 1.5.11 - dev: false - - /@sapphire/async-queue@1.5.0: - resolution: {integrity: sha512-JkLdIsP8fPAdh9ZZjrbHWR/+mZj0wvKS5ICibcLrRI1j84UmLMshx5n9QmL8b95d4onJ2xxiyugTgSAX7AalmA==} - engines: {node: '>=v14.0.0', npm: '>=7.0.0'} - dev: false - - /@sapphire/shapeshift@3.9.3: - resolution: {integrity: sha512-WzKJSwDYloSkHoBbE8rkRW8UNKJiSRJ/P8NqJ5iVq7U2Yr/kriIBx2hW+wj2Z5e5EnXL1hgYomgaFsdK6b+zqQ==} - engines: {node: '>=v14.0.0', npm: '>=7.0.0'} - dependencies: - fast-deep-equal: 3.1.3 - lodash: 4.17.21 - dev: false - - /@sapphire/snowflake@3.5.1: - resolution: {integrity: sha512-BxcYGzgEsdlG0dKAyOm0ehLGm2CafIrfQTZGWgkfKYbj+pNNsorZ7EotuZukc2MT70E0UbppVbtpBrqpzVzjNA==} - engines: {node: '>=v14.0.0', npm: '>=7.0.0'} - dev: false - - /@types/json-schema@7.0.12: - resolution: {integrity: sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==} - dev: true - - /@types/node@20.9.0: - resolution: {integrity: sha512-nekiGu2NDb1BcVofVcEKMIwzlx4NjHlcjhoxxKBNLtz15Y1z7MYf549DFvkHSId02Ax6kGwWntIBPC3l/JZcmw==} - dependencies: - undici-types: 5.26.5 - - /@types/semver@7.5.0: - resolution: {integrity: sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw==} - dev: true - - /@types/ws@8.5.9: - resolution: {integrity: sha512-jbdrY0a8lxfdTp/+r7Z4CkycbOFN8WX+IOchLJr3juT/xzbJ8URyTVSJ/hvNdadTgM1mnedb47n+Y31GsFnQlg==} - dependencies: - '@types/node': 20.9.0 - dev: false - - /@typescript-eslint/eslint-plugin@6.10.0(@typescript-eslint/parser@6.10.0)(eslint@8.53.0)(typescript@5.2.2): - resolution: {integrity: sha512-uoLj4g2OTL8rfUQVx2AFO1hp/zja1wABJq77P6IclQs6I/m9GLrm7jCdgzZkvWdDCQf1uEvoa8s8CupsgWQgVg==} - engines: {node: ^16.0.0 || >=18.0.0} - peerDependencies: - '@typescript-eslint/parser': ^6.0.0 || ^6.0.0-alpha - eslint: ^7.0.0 || ^8.0.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - dependencies: - '@eslint-community/regexpp': 4.6.2 - '@typescript-eslint/parser': 6.10.0(eslint@8.53.0)(typescript@5.2.2) - '@typescript-eslint/scope-manager': 6.10.0 - '@typescript-eslint/type-utils': 6.10.0(eslint@8.53.0)(typescript@5.2.2) - '@typescript-eslint/utils': 6.10.0(eslint@8.53.0)(typescript@5.2.2) - '@typescript-eslint/visitor-keys': 6.10.0 - debug: 4.3.4 - eslint: 8.53.0 - graphemer: 1.4.0 - ignore: 5.2.4 - natural-compare: 1.4.0 - semver: 7.5.4 - ts-api-utils: 1.0.1(typescript@5.2.2) - typescript: 5.2.2 - transitivePeerDependencies: - - supports-color - dev: true - - /@typescript-eslint/parser@6.10.0(eslint@8.53.0)(typescript@5.2.2): - resolution: {integrity: sha512-+sZwIj+s+io9ozSxIWbNB5873OSdfeBEH/FR0re14WLI6BaKuSOnnwCJ2foUiu8uXf4dRp1UqHP0vrZ1zXGrog==} - engines: {node: ^16.0.0 || >=18.0.0} - peerDependencies: - eslint: ^7.0.0 || ^8.0.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - dependencies: - '@typescript-eslint/scope-manager': 6.10.0 - '@typescript-eslint/types': 6.10.0 - '@typescript-eslint/typescript-estree': 6.10.0(typescript@5.2.2) - '@typescript-eslint/visitor-keys': 6.10.0 - debug: 4.3.4 - eslint: 8.53.0 - typescript: 5.2.2 - transitivePeerDependencies: - - supports-color - dev: true - - /@typescript-eslint/scope-manager@6.10.0: - resolution: {integrity: sha512-TN/plV7dzqqC2iPNf1KrxozDgZs53Gfgg5ZHyw8erd6jd5Ta/JIEcdCheXFt9b1NYb93a1wmIIVW/2gLkombDg==} - engines: {node: ^16.0.0 || >=18.0.0} - dependencies: - '@typescript-eslint/types': 6.10.0 - '@typescript-eslint/visitor-keys': 6.10.0 - dev: true - - /@typescript-eslint/type-utils@6.10.0(eslint@8.53.0)(typescript@5.2.2): - resolution: {integrity: sha512-wYpPs3hgTFblMYwbYWPT3eZtaDOjbLyIYuqpwuLBBqhLiuvJ+9sEp2gNRJEtR5N/c9G1uTtQQL5AhV0fEPJYcg==} - engines: {node: ^16.0.0 || >=18.0.0} - peerDependencies: - eslint: ^7.0.0 || ^8.0.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - dependencies: - '@typescript-eslint/typescript-estree': 6.10.0(typescript@5.2.2) - '@typescript-eslint/utils': 6.10.0(eslint@8.53.0)(typescript@5.2.2) - debug: 4.3.4 - eslint: 8.53.0 - ts-api-utils: 1.0.1(typescript@5.2.2) - typescript: 5.2.2 - transitivePeerDependencies: - - supports-color - dev: true - - /@typescript-eslint/types@6.10.0: - resolution: {integrity: sha512-36Fq1PWh9dusgo3vH7qmQAj5/AZqARky1Wi6WpINxB6SkQdY5vQoT2/7rW7uBIsPDcvvGCLi4r10p0OJ7ITAeg==} - engines: {node: ^16.0.0 || >=18.0.0} - dev: true - - /@typescript-eslint/typescript-estree@6.10.0(typescript@5.2.2): - resolution: {integrity: sha512-ek0Eyuy6P15LJVeghbWhSrBCj/vJpPXXR+EpaRZqou7achUWL8IdYnMSC5WHAeTWswYQuP2hAZgij/bC9fanBg==} - engines: {node: ^16.0.0 || >=18.0.0} - peerDependencies: - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - dependencies: - '@typescript-eslint/types': 6.10.0 - '@typescript-eslint/visitor-keys': 6.10.0 - debug: 4.3.4 - globby: 11.1.0 - is-glob: 4.0.3 - semver: 7.5.4 - ts-api-utils: 1.0.1(typescript@5.2.2) - typescript: 5.2.2 - transitivePeerDependencies: - - supports-color - dev: true - - /@typescript-eslint/utils@6.10.0(eslint@8.53.0)(typescript@5.2.2): - resolution: {integrity: sha512-v+pJ1/RcVyRc0o4wAGux9x42RHmAjIGzPRo538Z8M1tVx6HOnoQBCX/NoadHQlZeC+QO2yr4nNSFWOoraZCAyg==} - engines: {node: ^16.0.0 || >=18.0.0} - peerDependencies: - eslint: ^7.0.0 || ^8.0.0 - dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@8.53.0) - '@types/json-schema': 7.0.12 - '@types/semver': 7.5.0 - '@typescript-eslint/scope-manager': 6.10.0 - '@typescript-eslint/types': 6.10.0 - '@typescript-eslint/typescript-estree': 6.10.0(typescript@5.2.2) - eslint: 8.53.0 - semver: 7.5.4 - transitivePeerDependencies: - - supports-color - - typescript - dev: true - - /@typescript-eslint/visitor-keys@6.10.0: - resolution: {integrity: sha512-xMGluxQIEtOM7bqFCo+rCMh5fqI+ZxV5RUUOa29iVPz1OgCZrtc7rFnz5cLUazlkPKYqX+75iuDq7m0HQ48nCg==} - engines: {node: ^16.0.0 || >=18.0.0} - dependencies: - '@typescript-eslint/types': 6.10.0 - eslint-visitor-keys: 3.4.3 - dev: true - - /@ungap/structured-clone@1.2.0: - resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} - dev: true - - /@vladfrangu/async_event_emitter@2.2.2: - resolution: {integrity: sha512-HIzRG7sy88UZjBJamssEczH5q7t5+axva19UbZLO6u0ySbYPrwzWiXBcC0WuHyhKKoeCyneH+FvYzKQq/zTtkQ==} - engines: {node: '>=v14.0.0', npm: '>=7.0.0'} - dev: false - - /acorn-jsx@5.3.2(acorn@8.9.0): - resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} - peerDependencies: - acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 - dependencies: - acorn: 8.9.0 - dev: true - - /acorn@8.9.0: - resolution: {integrity: sha512-jaVNAFBHNLXspO543WnNNPZFRtavh3skAkITqD0/2aeMkKZTN+254PyhwxFYrk3vQ1xfY+2wbesJMs/JC8/PwQ==} - engines: {node: '>=0.4.0'} - hasBin: true - dev: true - - /ajv@6.12.6: - resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} - dependencies: - fast-deep-equal: 3.1.3 - fast-json-stable-stringify: 2.1.0 - json-schema-traverse: 0.4.1 - uri-js: 4.4.1 - dev: true - - /ansi-regex@5.0.1: - resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} - engines: {node: '>=8'} - dev: true - - /ansi-styles@4.3.0: - resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} - engines: {node: '>=8'} - dependencies: - color-convert: 2.0.1 - dev: true - - /argparse@1.0.10: - resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} - dependencies: - sprintf-js: 1.0.3 - dev: true - - /argparse@2.0.1: - resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} - dev: true - - /array-union@2.1.0: - resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} - engines: {node: '>=8'} - dev: true - - /balanced-match@1.0.2: - resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - dev: true - - /brace-expansion@1.1.11: - resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} - dependencies: - balanced-match: 1.0.2 - concat-map: 0.0.1 - dev: true - - /braces@3.0.2: - resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} - engines: {node: '>=8'} - dependencies: - fill-range: 7.0.1 - dev: true - - /buffer-from@1.1.2: - resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} - dev: false - - /callsites@3.1.0: - resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} - engines: {node: '>=6'} - dev: true - - /chalk@4.1.2: - resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} - engines: {node: '>=10'} - dependencies: - ansi-styles: 4.3.0 - supports-color: 7.2.0 - dev: true - - /cluster-key-slot@1.1.2: - resolution: {integrity: sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==} - engines: {node: '>=0.10.0'} - dev: false - - /color-convert@2.0.1: - resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} - engines: {node: '>=7.0.0'} - dependencies: - color-name: 1.1.4 - dev: true - - /color-name@1.1.4: - resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - dev: true - - /concat-map@0.0.1: - resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} - dev: true - - /cross-spawn@7.0.3: - resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} - engines: {node: '>= 8'} - dependencies: - path-key: 3.1.1 - shebang-command: 2.0.0 - which: 2.0.2 - dev: true - - /debug@4.3.4: - resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} - engines: {node: '>=6.0'} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - dependencies: - ms: 2.1.2 - dev: true - - /deep-is@0.1.4: - resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} - dev: true - - /dir-glob@3.0.1: - resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} - engines: {node: '>=8'} - dependencies: - path-type: 4.0.0 - dev: true - - /discord-api-types@0.37.61: - resolution: {integrity: sha512-o/dXNFfhBpYHpQFdT6FWzeO7pKc838QeeZ9d91CfVAtpr5XLK4B/zYxQbYgPdoMiTDvJfzcsLW5naXgmHGDNXw==} - dev: false - - /discord.js@14.14.1: - resolution: {integrity: sha512-/hUVzkIerxKHyRKopJy5xejp4MYKDPTszAnpYxzVVv4qJYf+Tkt+jnT2N29PIPschicaEEpXwF2ARrTYHYwQ5w==} - engines: {node: '>=16.11.0'} - dependencies: - '@discordjs/builders': 1.7.0 - '@discordjs/collection': 1.5.3 - '@discordjs/formatters': 0.3.3 - '@discordjs/rest': 2.1.0 - '@discordjs/util': 1.0.2 - '@discordjs/ws': 1.0.2 - '@sapphire/snowflake': 3.5.1 - '@types/ws': 8.5.9 - discord-api-types: 0.37.61 - fast-deep-equal: 3.1.3 - lodash.snakecase: 4.1.1 - tslib: 2.6.2 - undici: 5.27.2 - ws: 8.14.2 - transitivePeerDependencies: - - bufferutil - - utf-8-validate - dev: false - - /doctrine@3.0.0: - resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} - engines: {node: '>=6.0.0'} - dependencies: - esutils: 2.0.3 - dev: true - - /dotenv@16.3.1: - resolution: {integrity: sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==} - engines: {node: '>=12'} - dev: true - - /esbuild@0.18.20: - resolution: {integrity: sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==} - engines: {node: '>=12'} - hasBin: true - requiresBuild: true - optionalDependencies: - '@esbuild/android-arm': 0.18.20 - '@esbuild/android-arm64': 0.18.20 - '@esbuild/android-x64': 0.18.20 - '@esbuild/darwin-arm64': 0.18.20 - '@esbuild/darwin-x64': 0.18.20 - '@esbuild/freebsd-arm64': 0.18.20 - '@esbuild/freebsd-x64': 0.18.20 - '@esbuild/linux-arm': 0.18.20 - '@esbuild/linux-arm64': 0.18.20 - '@esbuild/linux-ia32': 0.18.20 - '@esbuild/linux-loong64': 0.18.20 - '@esbuild/linux-mips64el': 0.18.20 - '@esbuild/linux-ppc64': 0.18.20 - '@esbuild/linux-riscv64': 0.18.20 - '@esbuild/linux-s390x': 0.18.20 - '@esbuild/linux-x64': 0.18.20 - '@esbuild/netbsd-x64': 0.18.20 - '@esbuild/openbsd-x64': 0.18.20 - '@esbuild/sunos-x64': 0.18.20 - '@esbuild/win32-arm64': 0.18.20 - '@esbuild/win32-ia32': 0.18.20 - '@esbuild/win32-x64': 0.18.20 - dev: false - - /escape-string-regexp@4.0.0: - resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} - engines: {node: '>=10'} - dev: true - - /eslint-scope@7.2.2: - resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - dependencies: - esrecurse: 4.3.0 - estraverse: 5.3.0 - dev: true - - /eslint-visitor-keys@3.4.3: - resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - dev: true - - /eslint@8.53.0: - resolution: {integrity: sha512-N4VuiPjXDUa4xVeV/GC/RV3hQW9Nw+Y463lkWaKKXKYMvmRiRDAtfpuPFLN+E1/6ZhyR8J2ig+eVREnYgUsiag==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - hasBin: true - dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@8.53.0) - '@eslint-community/regexpp': 4.6.2 - '@eslint/eslintrc': 2.1.3 - '@eslint/js': 8.53.0 - '@humanwhocodes/config-array': 0.11.13 - '@humanwhocodes/module-importer': 1.0.1 - '@nodelib/fs.walk': 1.2.8 - '@ungap/structured-clone': 1.2.0 - ajv: 6.12.6 - chalk: 4.1.2 - cross-spawn: 7.0.3 - debug: 4.3.4 - doctrine: 3.0.0 - escape-string-regexp: 4.0.0 - eslint-scope: 7.2.2 - eslint-visitor-keys: 3.4.3 - espree: 9.6.1 - esquery: 1.5.0 - esutils: 2.0.3 - fast-deep-equal: 3.1.3 - file-entry-cache: 6.0.1 - find-up: 5.0.0 - glob-parent: 6.0.2 - globals: 13.20.0 - graphemer: 1.4.0 - ignore: 5.2.4 - imurmurhash: 0.1.4 - is-glob: 4.0.3 - is-path-inside: 3.0.3 - js-yaml: 4.1.0 - json-stable-stringify-without-jsonify: 1.0.1 - levn: 0.4.1 - lodash.merge: 4.6.2 - minimatch: 3.1.2 - natural-compare: 1.4.0 - optionator: 0.9.3 - strip-ansi: 6.0.1 - text-table: 0.2.0 - transitivePeerDependencies: - - supports-color - dev: true - - /espree@9.6.1: - resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - dependencies: - acorn: 8.9.0 - acorn-jsx: 5.3.2(acorn@8.9.0) - eslint-visitor-keys: 3.4.3 - dev: true - - /esprima@4.0.1: - resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} - engines: {node: '>=4'} - hasBin: true - dev: true - - /esquery@1.5.0: - resolution: {integrity: sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==} - engines: {node: '>=0.10'} - dependencies: - estraverse: 5.3.0 - dev: true - - /esrecurse@4.3.0: - resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} - engines: {node: '>=4.0'} - dependencies: - estraverse: 5.3.0 - dev: true - - /estraverse@5.3.0: - resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} - engines: {node: '>=4.0'} - dev: true - - /esutils@2.0.3: - resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} - engines: {node: '>=0.10.0'} - dev: true - - /extend-shallow@2.0.1: - resolution: {integrity: sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==} - engines: {node: '>=0.10.0'} - dependencies: - is-extendable: 0.1.1 - dev: true - - /fast-deep-equal@3.1.3: - resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} - - /fast-glob@3.2.12: - resolution: {integrity: sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==} - engines: {node: '>=8.6.0'} - dependencies: - '@nodelib/fs.stat': 2.0.5 - '@nodelib/fs.walk': 1.2.8 - glob-parent: 5.1.2 - merge2: 1.4.1 - micromatch: 4.0.5 - dev: true - - /fast-json-stable-stringify@2.1.0: - resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} - dev: true - - /fast-levenshtein@2.0.6: - resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} - dev: true - - /fastq@1.15.0: - resolution: {integrity: sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==} - dependencies: - reusify: 1.0.4 - dev: true - - /file-entry-cache@6.0.1: - resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} - engines: {node: ^10.12.0 || >=12.0.0} - dependencies: - flat-cache: 3.0.4 - dev: true - - /fill-range@7.0.1: - resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} - engines: {node: '>=8'} - dependencies: - to-regex-range: 5.0.1 - dev: true - - /find-up@5.0.0: - resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} - engines: {node: '>=10'} - dependencies: - locate-path: 6.0.0 - path-exists: 4.0.0 - dev: true - - /flat-cache@3.0.4: - resolution: {integrity: sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==} - engines: {node: ^10.12.0 || >=12.0.0} - dependencies: - flatted: 3.2.7 - rimraf: 3.0.2 - dev: true - - /flatted@3.2.7: - resolution: {integrity: sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==} - dev: true - - /fs.realpath@1.0.0: - resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} - dev: true - - /fsevents@2.3.3: - resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} - engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} - os: [darwin] - requiresBuild: true - dev: false - optional: true - - /generic-pool@3.9.0: - resolution: {integrity: sha512-hymDOu5B53XvN4QT9dBmZxPX4CWhBPPLguTZ9MMFeFa/Kg0xWVfylOVNlJji/E7yTZWFd/q9GO5TxDLq156D7g==} - engines: {node: '>= 4'} - dev: false - - /get-tsconfig@4.7.2: - resolution: {integrity: sha512-wuMsz4leaj5hbGgg4IvDU0bqJagpftG5l5cXIAvo8uZrqn0NJqwtfupTN00VnkQJPcIRrxYrm1Ue24btpCha2A==} - dependencies: - resolve-pkg-maps: 1.0.0 - dev: false - - /glob-parent@5.1.2: - resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} - engines: {node: '>= 6'} - dependencies: - is-glob: 4.0.3 - dev: true - - /glob-parent@6.0.2: - resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} - engines: {node: '>=10.13.0'} - dependencies: - is-glob: 4.0.3 - dev: true - - /glob@7.2.3: - resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} - dependencies: - fs.realpath: 1.0.0 - inflight: 1.0.6 - inherits: 2.0.4 - minimatch: 3.1.2 - once: 1.4.0 - path-is-absolute: 1.0.1 - dev: true - - /globals@13.20.0: - resolution: {integrity: sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==} - engines: {node: '>=8'} - dependencies: - type-fest: 0.20.2 - dev: true - - /globby@11.1.0: - resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} - engines: {node: '>=10'} - dependencies: - array-union: 2.1.0 - dir-glob: 3.0.1 - fast-glob: 3.2.12 - ignore: 5.2.4 - merge2: 1.4.1 - slash: 3.0.0 - dev: true - - /graphemer@1.4.0: - resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} - dev: true - - /gray-matter@4.0.3: - resolution: {integrity: sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==} - engines: {node: '>=6.0'} - dependencies: - js-yaml: 3.14.1 - kind-of: 6.0.3 - section-matter: 1.0.0 - strip-bom-string: 1.0.0 - dev: true - - /has-flag@4.0.0: - resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} - engines: {node: '>=8'} - dev: true - - /ignore@5.2.4: - resolution: {integrity: sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==} - engines: {node: '>= 4'} - dev: true - - /import-fresh@3.3.0: - resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} - engines: {node: '>=6'} - dependencies: - parent-module: 1.0.1 - resolve-from: 4.0.0 - dev: true - - /imurmurhash@0.1.4: - resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} - engines: {node: '>=0.8.19'} - dev: true - - /inflight@1.0.6: - resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} - dependencies: - once: 1.4.0 - wrappy: 1.0.2 - dev: true - - /inherits@2.0.4: - resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} - dev: true - - /is-extendable@0.1.1: - resolution: {integrity: sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==} - engines: {node: '>=0.10.0'} - dev: true - - /is-extglob@2.1.1: - resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} - engines: {node: '>=0.10.0'} - dev: true - - /is-glob@4.0.3: - resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} - engines: {node: '>=0.10.0'} - dependencies: - is-extglob: 2.1.1 - dev: true - - /is-number@7.0.0: - resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} - engines: {node: '>=0.12.0'} - dev: true - - /is-path-inside@3.0.3: - resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} - engines: {node: '>=8'} - dev: true - - /isexe@2.0.0: - resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - dev: true - - /js-yaml@3.14.1: - resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==} - hasBin: true - dependencies: - argparse: 1.0.10 - esprima: 4.0.1 - dev: true - - /js-yaml@4.1.0: - resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} - hasBin: true - dependencies: - argparse: 2.0.1 - dev: true - - /json-schema-traverse@0.4.1: - resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} - dev: true - - /json-stable-stringify-without-jsonify@1.0.1: - resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} - dev: true - - /just-random@3.2.0: - resolution: {integrity: sha512-RMf8vbtCfLIbAEHvIPu2FwMkpB/JudGyk/VPfqPItcRgt7k8QnV+Aa7s7kRFPo+bavQkUi8Yg1x/ooW6Ttyb9A==} - dev: false - - /kind-of@6.0.3: - resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} - engines: {node: '>=0.10.0'} - dev: true - - /kleur@4.1.5: - resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} - engines: {node: '>=6'} - dev: false - - /levn@0.4.1: - resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} - engines: {node: '>= 0.8.0'} - dependencies: - prelude-ls: 1.2.1 - type-check: 0.4.0 - dev: true - - /locate-path@6.0.0: - resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} - engines: {node: '>=10'} - dependencies: - p-locate: 5.0.0 - dev: true - - /lodash.merge@4.6.2: - resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} - dev: true - - /lodash.snakecase@4.1.1: - resolution: {integrity: sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==} - dev: false - - /lodash@4.17.21: - resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} - dev: false - - /lru-cache@6.0.0: - resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} - engines: {node: '>=10'} - dependencies: - yallist: 4.0.0 - dev: true - - /magic-bytes.js@1.5.0: - resolution: {integrity: sha512-wJkXvutRbNWcc37tt5j1HyOK1nosspdh3dj6LUYYAvF6JYNqs53IfRvK9oEpcwiDA1NdoIi64yAMfdivPeVAyw==} - dev: false - - /merge2@1.4.1: - resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} - engines: {node: '>= 8'} - dev: true - - /micromatch@4.0.5: - resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==} - engines: {node: '>=8.6'} - dependencies: - braces: 3.0.2 - picomatch: 2.3.1 - dev: true - - /minimatch@3.1.2: - resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} - dependencies: - brace-expansion: 1.1.11 - dev: true - - /ms@2.1.2: - resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} - dev: true - - /natural-compare@1.4.0: - resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} - dev: true - - /once@1.4.0: - resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} - dependencies: - wrappy: 1.0.2 - dev: true - - /optionator@0.9.3: - resolution: {integrity: sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==} - engines: {node: '>= 0.8.0'} - dependencies: - '@aashutoshrathi/word-wrap': 1.2.6 - deep-is: 0.1.4 - fast-levenshtein: 2.0.6 - levn: 0.4.1 - prelude-ls: 1.2.1 - type-check: 0.4.0 - dev: true - - /p-limit@3.1.0: - resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} - engines: {node: '>=10'} - dependencies: - yocto-queue: 0.1.0 - dev: true - - /p-locate@5.0.0: - resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} - engines: {node: '>=10'} - dependencies: - p-limit: 3.1.0 - dev: true - - /parent-module@1.0.1: - resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} - engines: {node: '>=6'} - dependencies: - callsites: 3.1.0 - dev: true - - /path-exists@4.0.0: - resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} - engines: {node: '>=8'} - dev: true - - /path-is-absolute@1.0.1: - resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} - engines: {node: '>=0.10.0'} - dev: true - - /path-key@3.1.1: - resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} - engines: {node: '>=8'} - dev: true - - /path-type@4.0.0: - resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} - engines: {node: '>=8'} - dev: true - - /picomatch@2.3.1: - resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} - engines: {node: '>=8.6'} - dev: true - - /prelude-ls@1.2.1: - resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} - engines: {node: '>= 0.8.0'} - dev: true - - /prettier@3.0.3: - resolution: {integrity: sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg==} - engines: {node: '>=14'} - hasBin: true - dev: true - - /punycode@2.3.0: - resolution: {integrity: sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==} - engines: {node: '>=6'} - dev: true - - /queue-microtask@1.2.3: - resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} - dev: true - - /redis@4.6.10: - resolution: {integrity: sha512-mmbyhuKgDiJ5TWUhiKhBssz+mjsuSI/lSZNPI9QvZOYzWvYGejtb+W3RlDDf8LD6Bdl5/mZeG8O1feUGhXTxEg==} - dependencies: - '@redis/bloom': 1.2.0(@redis/client@1.5.11) - '@redis/client': 1.5.11 - '@redis/graph': 1.1.0(@redis/client@1.5.11) - '@redis/json': 1.0.6(@redis/client@1.5.11) - '@redis/search': 1.1.5(@redis/client@1.5.11) - '@redis/time-series': 1.0.5(@redis/client@1.5.11) - dev: false - - /resolve-from@4.0.0: - resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} - engines: {node: '>=4'} - dev: true - - /resolve-pkg-maps@1.0.0: - resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} - dev: false - - /reusify@1.0.4: - resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} - engines: {iojs: '>=1.0.0', node: '>=0.10.0'} - dev: true - - /rimraf@3.0.2: - resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} - hasBin: true - dependencies: - glob: 7.2.3 - dev: true - - /run-parallel@1.2.0: - resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} - dependencies: - queue-microtask: 1.2.3 - dev: true - - /section-matter@1.0.0: - resolution: {integrity: sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==} - engines: {node: '>=4'} - dependencies: - extend-shallow: 2.0.1 - kind-of: 6.0.3 - dev: true - - /semver@7.5.4: - resolution: {integrity: sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==} - engines: {node: '>=10'} - hasBin: true - dependencies: - lru-cache: 6.0.0 - dev: true - - /shebang-command@2.0.0: - resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} - engines: {node: '>=8'} - dependencies: - shebang-regex: 3.0.0 - dev: true - - /shebang-regex@3.0.0: - resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} - engines: {node: '>=8'} - dev: true - - /slash@3.0.0: - resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} - engines: {node: '>=8'} - dev: true - - /source-map-support@0.5.21: - resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} - dependencies: - buffer-from: 1.1.2 - source-map: 0.6.1 - dev: false - - /source-map@0.6.1: - resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} - engines: {node: '>=0.10.0'} - dev: false - - /sprintf-js@1.0.3: - resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} - dev: true - - /strip-ansi@6.0.1: - resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} - engines: {node: '>=8'} - dependencies: - ansi-regex: 5.0.1 - dev: true - - /strip-bom-string@1.0.0: - resolution: {integrity: sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==} - engines: {node: '>=0.10.0'} - dev: true - - /strip-json-comments@3.1.1: - resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} - engines: {node: '>=8'} - dev: true - - /supports-color@7.2.0: - resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} - engines: {node: '>=8'} - dependencies: - has-flag: 4.0.0 - dev: true - - /text-table@0.2.0: - resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} - dev: true - - /to-regex-range@5.0.1: - resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} - engines: {node: '>=8.0'} - dependencies: - is-number: 7.0.0 - dev: true - - /ts-api-utils@1.0.1(typescript@5.2.2): - resolution: {integrity: sha512-lC/RGlPmwdrIBFTX59wwNzqh7aR2otPNPR/5brHZm/XKFYKsfqxihXUe9pU3JI+3vGkl+vyCoNNnPhJn3aLK1A==} - engines: {node: '>=16.13.0'} - peerDependencies: - typescript: '>=4.2.0' - dependencies: - typescript: 5.2.2 - dev: true - - /ts-mixer@6.0.3: - resolution: {integrity: sha512-k43M7uCG1AkTyxgnmI5MPwKoUvS/bRvLvUb7+Pgpdlmok8AoqmUaZxUUw8zKM5B1lqZrt41GjYgnvAi0fppqgQ==} - dev: false - - /tslib@2.6.2: - resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} - dev: false - - /tsx@4.1.1: - resolution: {integrity: sha512-zyPn5BFMB0TB5kMLbYPNx4x/oL/oSlaecdKCv6WeJ0TeSEfx8RTJWjuB5TZ2dSewktgfBsBO/HNA9mrMWqLXMA==} - engines: {node: '>=18.0.0'} - hasBin: true - dependencies: - esbuild: 0.18.20 - get-tsconfig: 4.7.2 - source-map-support: 0.5.21 - optionalDependencies: - fsevents: 2.3.3 - dev: false - - /type-check@0.4.0: - resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} - engines: {node: '>= 0.8.0'} - dependencies: - prelude-ls: 1.2.1 - dev: true - - /type-fest@0.20.2: - resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} - engines: {node: '>=10'} - dev: true - - /typescript@5.2.2: - resolution: {integrity: sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==} - engines: {node: '>=14.17'} - hasBin: true - dev: true - - /undici-types@5.26.5: - resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} - - /undici@5.27.2: - resolution: {integrity: sha512-iS857PdOEy/y3wlM3yRp+6SNQQ6xU0mmZcwRSriqk+et/cwWAtwmIGf6WkoDN2EK/AMdCO/dfXzIwi+rFMrjjQ==} - engines: {node: '>=14.0'} - dependencies: - '@fastify/busboy': 2.1.0 - dev: false - - /uri-js@4.4.1: - resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} - dependencies: - punycode: 2.3.0 - dev: true - - /which@2.0.2: - resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} - engines: {node: '>= 8'} - hasBin: true - dependencies: - isexe: 2.0.0 - dev: true - - /wrappy@1.0.2: - resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} - dev: true - - /ws@8.14.2: - resolution: {integrity: sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g==} - engines: {node: '>=10.0.0'} - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: '>=5.0.2' - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - dev: false - - /yallist@4.0.0: - resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} - - /yocto-queue@0.1.0: - resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} - engines: {node: '>=10'} - dev: true diff --git a/renovate.json b/renovate.json index e9cca0b..d7a91d3 100644 --- a/renovate.json +++ b/renovate.json @@ -1,4 +1,4 @@ { "$schema": "https://docs.renovatebot.com/renovate-schema.json", - "extends": ["config:base", "config:js-app"] + "extends": ["config:base", "config:recommended"] } diff --git a/src/_reupload.ts b/src/_reupload.ts deleted file mode 100644 index 43c9e73..0000000 --- a/src/_reupload.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { - SlashCommandBuilder, - Routes, - PermissionFlagsBits, - type RESTGetAPIOAuth2CurrentApplicationResult, -} from 'discord.js'; -import { REST } from '@discordjs/rest'; -import { getTags } from './tags'; - -export const reuploadCommands = async () => { - const tags = await getTags(); - - const commands = [ - new SlashCommandBuilder() - .setName('ping') - .setDescription('Replies with pong!'), - new SlashCommandBuilder() - .setName('stars') - .setDescription('Returns GitHub stargazer count'), - new SlashCommandBuilder() - .setName('members') - .setDescription('Returns the number of members in the server'), - new SlashCommandBuilder() - .setName('tag') - .setDescription('Send a tag') - .addStringOption((option) => - option - .setName('name') - .setDescription('The tag name') - .setRequired(true) - .addChoices(...tags.map((b) => ({ name: b.name, value: b.name }))) - ) - .addUserOption((option) => - option - .setName('user') - .setDescription('The user to mention') - .setRequired(false) - ), - new SlashCommandBuilder() - .setName('modrinth') - .setDescription('Get info on a Modrinth project') - .addStringOption((option) => - option.setName('id').setDescription('The ID or slug').setRequired(true) - ), - new SlashCommandBuilder() - .setName('say') - .setDescription('Say something through the bot') - .addStringOption((option) => - option - .setName('content') - .setDescription('Just content?') - .setRequired(true) - ) - .setDefaultMemberPermissions(PermissionFlagsBits.ModerateMembers) - .setDMPermission(false), - new SlashCommandBuilder().setName('joke').setDescription("it's a joke"), - new SlashCommandBuilder() - .setName('rory') - .setDescription('Gets a Rory photo!') - .addStringOption((option) => - option - .setName('id') - .setDescription('specify a Rory ID') - .setRequired(false) - ), - ].map((command) => command.toJSON()); - - const rest = new REST({ version: '10' }).setToken(process.env.DISCORD_TOKEN!); - - const { id: appId } = (await rest.get( - Routes.oauth2CurrentApplication() - )) as RESTGetAPIOAuth2CurrentApplicationResult; - - await rest.put(Routes.applicationCommands(appId), { - body: commands, - }); - - console.log('Successfully registered application commands.'); -}; diff --git a/src/api/dadjoke.rs b/src/api/dadjoke.rs new file mode 100644 index 0000000..8a9f641 --- /dev/null +++ b/src/api/dadjoke.rs @@ -0,0 +1,21 @@ +use crate::api::REQWEST_CLIENT; + +use color_eyre::eyre::{eyre, Result}; +use log::*; +use reqwest::StatusCode; + +const DADJOKE: &str = "https://icanhazdadjoke.com"; + +pub async fn get_joke() -> Result { + let req = REQWEST_CLIENT.get(DADJOKE).build()?; + + info!("making request to {}", req.url()); + let resp = REQWEST_CLIENT.execute(req).await?; + let status = resp.status(); + + if let StatusCode::OK = status { + Ok(resp.text().await?) + } else { + Err(eyre!("Failed to fetch joke from {DADJOKE} with {status}")) + } +} diff --git a/src/api/mod.rs b/src/api/mod.rs new file mode 100644 index 0000000..44df020 --- /dev/null +++ b/src/api/mod.rs @@ -0,0 +1,17 @@ +use once_cell::sync::Lazy; + +pub mod dadjoke; +pub mod rory; + +pub static USER_AGENT: Lazy = Lazy::new(|| { + let version = option_env!("CARGO_PKG_VERSION").unwrap_or("development"); + + format!("refraction/{version}") +}); + +pub static REQWEST_CLIENT: Lazy = Lazy::new(|| { + reqwest::Client::builder() + .user_agent(USER_AGENT.to_string()) + .build() + .unwrap_or_default() +}); diff --git a/src/api/rory.rs b/src/api/rory.rs new file mode 100644 index 0000000..6fd4bf9 --- /dev/null +++ b/src/api/rory.rs @@ -0,0 +1,42 @@ +use crate::api::REQWEST_CLIENT; + +use color_eyre::eyre::{eyre, Result}; +use log::*; +use reqwest::StatusCode; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize)] +pub struct RoryResponse { + pub id: u64, + pub url: String, +} + +const RORY: &str = "https://rory.cat"; +const ENDPOINT: &str = "/purr"; + +pub async fn get_rory(id: Option) -> Result { + let target = { + if let Some(id) = id { + id.to_string() + } else { + "".to_string() + } + }; + + let req = REQWEST_CLIENT + .get(format!("{RORY}{ENDPOINT}/{target}")) + .build()?; + + info!("making request to {}", req.url()); + let resp = REQWEST_CLIENT.execute(req).await?; + let status = resp.status(); + + if let StatusCode::OK = status { + let data = resp.json::().await?; + Ok(data) + } else { + Err(eyre!( + "Failed to get rory from {RORY}{ENDPOINT}/{target} with {status}", + )) + } +} diff --git a/src/commands/general/joke.rs b/src/commands/general/joke.rs new file mode 100644 index 0000000..f99ab1b --- /dev/null +++ b/src/commands/general/joke.rs @@ -0,0 +1,12 @@ +use crate::api::dadjoke; +use crate::Context; + +use color_eyre::eyre::Result; + +#[poise::command(slash_command, prefix_command)] +pub async fn joke(ctx: Context<'_>) -> Result<()> { + let joke = dadjoke::get_joke().await?; + + ctx.reply(joke).await?; + Ok(()) +} diff --git a/src/commands/general/members.rs b/src/commands/general/members.rs new file mode 100644 index 0000000..021ffa5 --- /dev/null +++ b/src/commands/general/members.rs @@ -0,0 +1,25 @@ +use crate::{consts, Context}; + +use color_eyre::eyre::{eyre, Result}; + +#[poise::command(slash_command, prefix_command)] +pub async fn members(ctx: Context<'_>) -> Result<()> { + let guild = ctx.guild().ok_or_else(|| eyre!("Couldn't fetch guild!"))?; + + let count = guild.member_count; + let online = if let Some(count) = guild.approximate_presence_count { + count.to_string() + } else { + "Undefined".to_string() + }; + + ctx.send(|m| { + m.embed(|e| { + e.title(format!("{count} total members!")) + .description(format!("{online} online members")) + .color(consts::COLORS["blue"]) + }) + }) + .await?; + Ok(()) +} diff --git a/src/commands/general/mod.rs b/src/commands/general/mod.rs new file mode 100644 index 0000000..5fa1d95 --- /dev/null +++ b/src/commands/general/mod.rs @@ -0,0 +1,13 @@ +mod joke; +mod members; +mod modrinth; +mod rory; +mod say; +mod stars; + +pub use joke::joke; +pub use members::members; +pub use modrinth::modrinth; +pub use rory::rory; +pub use say::say; +pub use stars::stars; diff --git a/src/commands/general/modrinth.rs b/src/commands/general/modrinth.rs new file mode 100644 index 0000000..40879e8 --- /dev/null +++ b/src/commands/general/modrinth.rs @@ -0,0 +1,8 @@ +use crate::Context; + +use color_eyre::eyre::Result; + +#[poise::command(slash_command, prefix_command)] +pub async fn modrinth(ctx: Context<'_>) -> Result<()> { + todo!() +} diff --git a/src/commands/general/rory.rs b/src/commands/general/rory.rs new file mode 100644 index 0000000..5350e32 --- /dev/null +++ b/src/commands/general/rory.rs @@ -0,0 +1,21 @@ +use crate::api::rory::get_rory; +use crate::Context; + +use color_eyre::eyre::Result; + +#[poise::command(slash_command, prefix_command)] +pub async fn rory(ctx: Context<'_>, id: Option) -> Result<()> { + let resp = get_rory(id).await?; + + ctx.send(|m| { + m.embed(|e| { + e.title("Rory :3") + .url(&resp.url) + .image(resp.url) + .footer(|f| f.text(format!("ID {}", resp.id))) + }) + }) + .await?; + + Ok(()) +} diff --git a/src/commands/general/say.rs b/src/commands/general/say.rs new file mode 100644 index 0000000..a3f87d5 --- /dev/null +++ b/src/commands/general/say.rs @@ -0,0 +1,43 @@ +use crate::Context; + +use color_eyre::eyre::{eyre, Result}; + +#[poise::command(slash_command, prefix_command, ephemeral)] +pub async fn say(ctx: Context<'_>, content: String) -> Result<()> { + let guild = ctx.guild().ok_or_else(|| eyre!("Couldn't get guild!"))?; + let channel = ctx + .guild_channel() + .await + .ok_or_else(|| eyre!("Couldn't get channel!"))?; + + channel.say(ctx, &content).await?; + ctx.say("I said what you said!").await?; + + if let Some(channel_id) = ctx.data().config.discord.channels.say_log_channel_id { + let log_channel = guild + .channels + .iter() + .find(|c| c.0 == &channel_id) + .ok_or_else(|| eyre!("Couldn't get log channel from guild!"))?; + + log_channel + .1 + .clone() + .guild() + .ok_or_else(|| eyre!("Couldn't cast channel we found from guild as GuildChannel?????"))? + .send_message(ctx, |m| { + m.embed(|e| { + e.title("Say command used!") + .description(content) + .author(|a| { + a.name(ctx.author().tag()).icon_url( + ctx.author().avatar_url().unwrap_or("undefined".to_string()), + ) + }) + }) + }) + .await?; + } + + Ok(()) +} diff --git a/src/commands/general/stars.rs b/src/commands/general/stars.rs new file mode 100644 index 0000000..14e4217 --- /dev/null +++ b/src/commands/general/stars.rs @@ -0,0 +1,30 @@ +use crate::{consts::COLORS, Context}; + +use color_eyre::eyre::{Context as _, Result}; + +#[poise::command(slash_command, prefix_command)] +pub async fn stars(ctx: Context<'_>) -> Result<()> { + let prismlauncher = ctx + .data() + .octocrab + .repos("PrismLauncher", "PrismLauncher") + .get() + .await + .wrap_err_with(|| "Couldn't get PrismLauncher/PrismLauncher from GitHub!")?; + + let count = if let Some(count) = prismlauncher.stargazers_count { + count.to_string() + } else { + "undefined".to_string() + }; + + ctx.send(|m| { + m.embed(|e| { + e.title(format!("⭐ {count} total stars!")) + .color(COLORS["yellow"]) + }) + }) + .await?; + + Ok(()) +} diff --git a/src/commands/joke.ts b/src/commands/joke.ts deleted file mode 100644 index b0f9a34..0000000 --- a/src/commands/joke.ts +++ /dev/null @@ -1,11 +0,0 @@ -import type { CacheType, ChatInputCommandInteraction } from 'discord.js'; - -export const jokeCommand = async ( - i: ChatInputCommandInteraction -) => { - await i.deferReply(); - const joke = await fetch('https://icanhazdadjoke.com', { - headers: { Accept: 'text/plain' }, - }).then((r) => r.text()); - await i.editReply(joke); -}; diff --git a/src/commands/members.ts b/src/commands/members.ts deleted file mode 100644 index 1e7bde9..0000000 --- a/src/commands/members.ts +++ /dev/null @@ -1,24 +0,0 @@ -import type { CacheType, ChatInputCommandInteraction } from 'discord.js'; - -import { COLORS } from '../constants'; - -export const membersCommand = async ( - i: ChatInputCommandInteraction -) => { - await i.deferReply(); - - const memes = await i.guild?.members.fetch().then((r) => r.toJSON()); - if (!memes) return; - - await i.editReply({ - embeds: [ - { - title: `${memes.length} total members!`, - description: `${ - memes.filter((m) => m.presence?.status !== 'offline').length - } online members`, - color: COLORS.blue, - }, - ], - }); -}; diff --git a/src/commands/mod.rs b/src/commands/mod.rs new file mode 100644 index 0000000..bef9edd --- /dev/null +++ b/src/commands/mod.rs @@ -0,0 +1,20 @@ +use crate::Data; + +use color_eyre::eyre::Report; +use poise::Command; + +mod general; +mod moderation; + +pub fn to_global_commands() -> Vec> { + vec![ + general::joke(), + general::members(), + general::modrinth(), + general::rory(), + general::say(), + general::stars(), + moderation::ban_user(), + moderation::kick_user(), + ] +} diff --git a/src/commands/moderation/actions.rs b/src/commands/moderation/actions.rs new file mode 100644 index 0000000..38307d1 --- /dev/null +++ b/src/commands/moderation/actions.rs @@ -0,0 +1,80 @@ +use crate::{consts::COLORS, Context}; + +use color_eyre::eyre::{eyre, Result}; +use poise::serenity_prelude::{CreateEmbed, User}; + +fn create_moderation_embed( + title: String, + user: &User, + delete_messages_days: Option, + reason: String, +) -> impl FnOnce(&mut CreateEmbed) -> &mut CreateEmbed { + let fields = [ + ("User", format!("{} ({})", user.name, user.id), false), + ("Reason", reason, false), + ( + "Deleted messages", + format!("Last {} days", delete_messages_days.unwrap_or(0)), + false, + ), + ]; + + |e: &mut CreateEmbed| e.title(title).fields(fields).color(COLORS["red"]) +} + +// ban a user +#[poise::command( + slash_command, + prefix_command, + default_member_permissions = "BAN_MEMBERS" +)] +pub async fn ban_user( + ctx: Context<'_>, + user: User, + delete_messages_days: Option, + reason: Option, +) -> Result<()> { + let days = delete_messages_days.unwrap_or(1); + let guild = ctx + .guild() + .ok_or_else(|| eyre!("Couldn't get guild from message; Unable to ban!"))?; + + let reason = reason.unwrap_or("n/a".to_string()); + + if reason != "n/a" { + guild.ban_with_reason(ctx, &user, days, &reason).await?; + } else { + guild.ban(ctx, &user, days).await?; + } + + let embed = create_moderation_embed("User banned!".to_string(), &user, Some(days), reason); + + ctx.send(|m| m.embed(embed)).await?; + + Ok(()) +} + +// kick a user +#[poise::command( + slash_command, + prefix_command, + default_member_permissions = "KICK_MEMBERS" +)] +pub async fn kick_user(ctx: Context<'_>, user: User, reason: Option) -> Result<()> { + let guild = ctx + .guild() + .ok_or_else(|| eyre!("Couldn't get guild from message; Unable to ban!"))?; + + let reason = reason.unwrap_or("n/a".to_string()); + + if reason != "n/a" { + guild.kick_with_reason(ctx, &user, &reason).await?; + } else { + guild.kick(ctx, &user).await?; + } + + let embed = create_moderation_embed("User kicked!".to_string(), &user, None, reason); + ctx.send(|m| m.embed(embed)).await?; + + Ok(()) +} diff --git a/src/commands/moderation/mod.rs b/src/commands/moderation/mod.rs new file mode 100644 index 0000000..13f51de --- /dev/null +++ b/src/commands/moderation/mod.rs @@ -0,0 +1,3 @@ +mod actions; + +pub use actions::*; diff --git a/src/commands/modrinth.ts b/src/commands/modrinth.ts deleted file mode 100644 index ba1291d..0000000 --- a/src/commands/modrinth.ts +++ /dev/null @@ -1,124 +0,0 @@ -type Side = 'required' | 'optional' | 'unsupported'; - -export interface ModrinthProject { - slug: string; - title: string; - description: string; - categories: string[]; - client_side: Side; - server_side: Side; - project_type: 'mod' | 'modpack'; - downloads: number; - icon_url: string | null; - id: string; - team: string; -} - -import { - EmbedBuilder, - type CacheType, - type ChatInputCommandInteraction, -} from 'discord.js'; - -import { COLORS } from '../constants'; - -export const modrinthCommand = async ( - i: ChatInputCommandInteraction -) => { - await i.deferReply(); - - const { value: id } = i.options.get('id') ?? { value: null }; - - if (!id || typeof id !== 'string') { - await i.editReply({ - embeds: [ - new EmbedBuilder() - .setTitle('Error!') - .setDescription('You need to provide a valid mod ID!') - .setColor(COLORS.red), - ], - }); - - return; - } - - const res = await fetch('https://api.modrinth.com/v2/project/' + id); - - if (!res.ok) { - await i.editReply({ - embeds: [ - new EmbedBuilder() - .setTitle('Error!') - .setDescription('Not found!') - .setColor(COLORS.red), - ], - }); - - setTimeout(() => { - i.deleteReply(); - }, 3000); - - return; - } - - const data = (await res.json()) as - | ModrinthProject - | { error: string; description: string }; - - if ('error' in data) { - console.error(data); - - await i.editReply({ - embeds: [ - new EmbedBuilder() - .setTitle('Error!') - .setDescription(`\`${data.error}\` ${data.description}`) - .setColor(COLORS.red), - ], - }); - - setTimeout(() => { - i.deleteReply(); - }, 3000); - - return; - } - - await i.editReply({ - embeds: [ - new EmbedBuilder() - .setTitle(data.title) - .setDescription(data.description) - .setThumbnail(data.icon_url) - .setURL(`https://modrinth.com/project/${data.slug}`) - .setFields([ - { - name: 'Categories', - value: data.categories.join(', '), - inline: true, - }, - { - name: 'Project type', - value: data.project_type, - inline: true, - }, - { - name: 'Downloads', - value: data.downloads.toString(), - inline: true, - }, - { - name: 'Client', - value: data.client_side, - inline: true, - }, - { - name: 'Server', - value: data.server_side, - inline: true, - }, - ]) - .setColor(COLORS.green), - ], - }); -}; diff --git a/src/commands/rory.ts b/src/commands/rory.ts deleted file mode 100644 index 0a1026e..0000000 --- a/src/commands/rory.ts +++ /dev/null @@ -1,51 +0,0 @@ -import type { CacheType, ChatInputCommandInteraction } from 'discord.js'; -import { EmbedBuilder } from 'discord.js'; - -export interface RoryResponse { - /** - * The ID of this Rory - */ - id: number; - /** - * The URL to the image of this Rory - */ - url: string; - /** - * When error :( - */ - error: string | undefined; -} - -export const roryCommand = async ( - i: ChatInputCommandInteraction -) => { - await i.deferReply(); - - const { value: id } = i.options.get('id') ?? { value: '' }; - - const rory: RoryResponse = await fetch(`https://rory.cat/purr/${id}`, { - headers: { Accept: 'application/json' }, - }).then((r) => r.json()); - - if (rory.error) { - await i.editReply({ - embeds: [ - new EmbedBuilder().setTitle('Error!').setDescription(rory.error), - ], - }); - - return; - } - - await i.editReply({ - embeds: [ - new EmbedBuilder() - .setTitle('Rory :3') - .setURL(`https://rory.cat/id/${rory.id}`) - .setImage(rory.url) - .setFooter({ - text: `ID ${rory.id}`, - }), - ], - }); -}; diff --git a/src/commands/say.ts b/src/commands/say.ts deleted file mode 100644 index fd3af32..0000000 --- a/src/commands/say.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { - CacheType, - ChatInputCommandInteraction, - EmbedBuilder, -} from 'discord.js'; - -export const sayCommand = async ( - interaction: ChatInputCommandInteraction -) => { - if (!interaction.guild || !interaction.channel) return; - - const content = interaction.options.getString('content', true); - await interaction.deferReply({ ephemeral: true }); - const message = await interaction.channel.send(content); - await interaction.editReply('I said what you said!'); - - if (process.env.SAY_LOGS_CHANNEL) { - const logsChannel = await interaction.guild.channels.fetch( - process.env.SAY_LOGS_CHANNEL - ); - - if (!logsChannel?.isTextBased()) return; - - await logsChannel.send({ - embeds: [ - new EmbedBuilder() - .setTitle('Say command used') - .setDescription(content) - .setAuthor({ - name: interaction.user.tag, - iconURL: interaction.user.avatarURL() ?? undefined, - }) - .setURL(message.url), - ], - }); - } -}; diff --git a/src/commands/stars.ts b/src/commands/stars.ts deleted file mode 100644 index 9eea2ab..0000000 --- a/src/commands/stars.ts +++ /dev/null @@ -1,23 +0,0 @@ -import type { CacheType, ChatInputCommandInteraction } from 'discord.js'; -import { COLORS } from '../constants'; - -export const starsCommand = async ( - i: ChatInputCommandInteraction -) => { - await i.deferReply(); - - const count = await fetch( - 'https://api.github.com/repos/PrismLauncher/PrismLauncher' - ) - .then((r) => r.json() as Promise<{ stargazers_count: number }>) - .then((j) => j.stargazers_count); - - await i.editReply({ - embeds: [ - { - title: `⭐ ${count} total stars!`, - color: COLORS.yellow, - }, - ], - }); -}; diff --git a/src/commands/tags.ts b/src/commands/tags.ts deleted file mode 100644 index 870e452..0000000 --- a/src/commands/tags.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { - type ChatInputCommandInteraction, - type CacheType, - EmbedBuilder, -} from 'discord.js'; -import { getTags } from '../tags'; - -export const tagsCommand = async ( - i: ChatInputCommandInteraction -) => { - const tags = await getTags(); - const tagName = i.options.getString('name', true); - const mention = i.options.getUser('user', false); - - const tag = tags.find( - (tag) => tag.name === tagName || tag.aliases?.includes(tagName) - ); - - if (!tag) { - await i.reply({ - content: `Tag \`${tagName}\` does not exist.`, - ephemeral: true, - }); - return; - } - - const embed = new EmbedBuilder(); - embed.setTitle(tag.title ?? tag.name); - embed.setDescription(tag.content); - if (tag.color) embed.setColor(tag.color); - if (tag.image) embed.setImage(tag.image); - if (tag.fields) embed.setFields(tag.fields); - - await i.reply({ - content: mention ? `<@${mention.id}> ` : undefined, - embeds: [embed], - }); -}; diff --git a/src/config/discord.rs b/src/config/discord.rs new file mode 100644 index 0000000..77cc392 --- /dev/null +++ b/src/config/discord.rs @@ -0,0 +1,87 @@ +use crate::required_var; + +use color_eyre::eyre::{Context as _, Result}; +use log::*; +use poise::serenity_prelude::{ApplicationId, ChannelId}; +use url::Url; + +#[derive(Debug, Clone)] +pub struct RefractionOAuth2 { + pub redirect_uri: Url, + pub scope: String, +} + +#[derive(Debug, Clone, Default)] +pub struct RefractionChannels { + pub say_log_channel_id: Option, +} + +#[derive(Debug, Clone, Default)] +pub struct DiscordConfig { + pub client_id: ApplicationId, + pub client_secret: String, + pub bot_token: String, + pub oauth2: RefractionOAuth2, + pub channels: RefractionChannels, +} + +impl Default for RefractionOAuth2 { + fn default() -> Self { + Self { + scope: "identify connections role_connections.write".to_string(), + redirect_uri: Url::parse("https://google.com").unwrap(), + } + } +} + +impl RefractionOAuth2 { + pub fn new_from_env() -> Result { + let unparsed = format!("{}/oauth2/callback", required_var!("PUBLIC_URI")); + let redirect_uri = Url::parse(&unparsed)?; + + debug!("OAuth2 Redirect URI is {redirect_uri}"); + Ok(Self { + redirect_uri, + ..Default::default() + }) + } +} + +impl RefractionChannels { + pub fn new_from_env() -> Result { + let unparsed = std::env::var("DISCORD_SAY_LOG_CHANNELID"); + if let Ok(unparsed) = unparsed { + let id = unparsed.parse::()?; + let channel_id = ChannelId::from(id); + + debug!("Log channel is {id}"); + Ok(Self { + say_log_channel_id: Some(channel_id), + }) + } else { + warn!("DISCORD_SAY_LOG_CHANNELID is empty; this will disable logging in your server."); + Ok(Self { + say_log_channel_id: None, + }) + } + } +} + +impl DiscordConfig { + pub fn new_from_env() -> Result { + let unparsed_client = required_var!("DISCORD_CLIENT_ID").parse::()?; + let client_id = ApplicationId::from(unparsed_client); + let client_secret = required_var!("DISCORD_CLIENT_SECRET"); + let bot_token = required_var!("DISCORD_BOT_TOKEN"); + let oauth2 = RefractionOAuth2::new_from_env()?; + let channels = RefractionChannels::new_from_env()?; + + Ok(Self { + client_id, + client_secret, + bot_token, + oauth2, + channels, + }) + } +} diff --git a/src/config/github.rs b/src/config/github.rs new file mode 100644 index 0000000..d9ca12e --- /dev/null +++ b/src/config/github.rs @@ -0,0 +1,65 @@ +use color_eyre::eyre::{Context as _, Result}; + +use crate::required_var; + +#[derive(Debug, Clone)] +pub struct RefractionRepo { + pub owner: String, + pub repo: String, + pub key: String, + pub name: String, +} + +#[derive(Debug, Clone)] +pub struct GithubConfig { + pub token: String, + pub repos: Vec, + pub cache_sec: u16, + pub update_cron_job: String, +} + +impl Default for GithubConfig { + fn default() -> Self { + let owner = "PrismLauncher".to_string(); + let repos = Vec::::from([ + RefractionRepo { + owner: owner.clone(), + repo: "PrismLauncher".to_string(), + key: "launcher".to_string(), + name: "Launcher contributor".to_string(), + }, + RefractionRepo { + owner: owner.clone(), + repo: "prismlauncher.org".to_string(), + + key: "website".to_string(), + name: "Web developer".to_string(), + }, + RefractionRepo { + owner: owner.clone(), + repo: "Translations".to_string(), + + key: "translations".to_string(), + name: "Translator".to_string(), + }, + ]); + + Self { + repos, + cache_sec: 3600, + update_cron_job: "0 */10 * * * *".to_string(), // every 10 minutes + token: String::default(), + } + } +} + +impl GithubConfig { + pub fn new_from_env() -> Result { + let token = required_var!("GITHUB_TOKEN"); + + Ok(Self { + token, + ..Default::default() + }) + } +} diff --git a/src/config/mod.rs b/src/config/mod.rs new file mode 100644 index 0000000..a2bec63 --- /dev/null +++ b/src/config/mod.rs @@ -0,0 +1,39 @@ +use color_eyre::eyre::Result; + +mod discord; +mod github; + +pub use discord::*; +pub use github::*; + +#[derive(Debug, Clone)] +pub struct Config { + pub discord: DiscordConfig, + pub github: GithubConfig, + pub http_port: u16, + pub redis_url: String, +} + +impl Default for Config { + fn default() -> Self { + Self { + discord: DiscordConfig::default(), + github: GithubConfig::default(), + http_port: 3000, + redis_url: "redis://localhost:6379".to_string(), + } + } +} + +impl Config { + pub fn new_from_env() -> Result { + let discord = DiscordConfig::new_from_env()?; + let github = GithubConfig::new_from_env()?; + + Ok(Self { + discord, + github, + ..Default::default() + }) + } +} diff --git a/src/constants.ts b/src/constants.ts deleted file mode 100644 index fb22a8f..0000000 --- a/src/constants.ts +++ /dev/null @@ -1,27 +0,0 @@ -export const ETA_REGEX = /\beta\b/i; -export const ETA_MESSAGES = [ - 'Sometime', - 'Some day', - 'Not far', - 'The future', - 'Never', - 'Perhaps tomorrow?', - 'There are no ETAs', - 'No', - 'Nah', - 'Yes', - 'Yas', - 'Next month', - 'Next year', - 'Next week', - 'In Prism Launcher 2.0.0', - 'At the appropriate juncture, in due course, in the fullness of time', -]; - -export const COLORS = { - red: 0xef4444, - green: 0x22c55e, - blue: 0x60a5fa, - yellow: 0xfde047, - orange: 0xfb923c, -} as { [key: string]: number }; diff --git a/src/consts.rs b/src/consts.rs new file mode 100644 index 0000000..10cbf68 --- /dev/null +++ b/src/consts.rs @@ -0,0 +1,13 @@ +use std::collections::HashMap; + +use once_cell::sync::Lazy; + +pub static COLORS: Lazy> = Lazy::new(|| { + HashMap::from([ + ("red", (239, 68, 68)), + ("green", (34, 197, 94)), + ("blue", (96, 165, 250)), + ("yellow", (253, 224, 71)), + ("orange", (251, 146, 60)), + ]) +}); diff --git a/src/handlers/error.rs b/src/handlers/error.rs new file mode 100644 index 0000000..1ed4dc0 --- /dev/null +++ b/src/handlers/error.rs @@ -0,0 +1,42 @@ +use crate::consts::COLORS; +use crate::Data; + +use color_eyre::eyre::Report; +use log::*; +use poise::serenity_prelude::Timestamp; +use poise::FrameworkError; + +pub async fn handle(error: poise::FrameworkError<'_, Data, Report>) { + match error { + FrameworkError::Setup { error, .. } => error!("Error setting up client!\n{error:#?}"), + + FrameworkError::Command { error, ctx } => { + error!("Error in command {}:\n{error:?}", ctx.command().name); + ctx.send(|c| { + c.embed(|e| { + e.title("Something went wrong!") + .description("oopsie") + .timestamp(Timestamp::now()) + .color(COLORS["orange"]) + }) + }) + .await + .ok(); + } + + FrameworkError::EventHandler { + error, + ctx: _, + event, + framework: _, + } => { + error!("Error while handling event {}:\n{error:?}", event.name()); + } + + error => { + if let Err(e) = poise::builtins::on_error(error).await { + error!("Unhandled error occured:\n{e:#?}"); + } + } + } +} diff --git a/src/handlers/event/mod.rs b/src/handlers/event/mod.rs new file mode 100644 index 0000000..530d145 --- /dev/null +++ b/src/handlers/event/mod.rs @@ -0,0 +1,24 @@ +use crate::Data; + +use color_eyre::eyre::{Report, Result}; +use poise::serenity_prelude as serenity; +use poise::{Event, FrameworkContext}; + +pub async fn handle( + ctx: &serenity::Context, + event: &Event<'_>, + framework: FrameworkContext<'_, Data, Report>, + data: &Data, +) -> Result<()> { + match event { + Event::Ready { data_about_bot } => { + log::info!("Logged in as {}!", data_about_bot.user.name) + } + + Event::Message { new_message } => {} + + _ => {} + } + + Ok(()) +} diff --git a/src/handlers/mod.rs b/src/handlers/mod.rs new file mode 100644 index 0000000..2ae0539 --- /dev/null +++ b/src/handlers/mod.rs @@ -0,0 +1,5 @@ +mod error; +mod event; + +pub use error::handle as handle_error; +pub use event::handle as handle_event; diff --git a/src/index.ts b/src/index.ts deleted file mode 100644 index f3f344b..0000000 --- a/src/index.ts +++ /dev/null @@ -1,223 +0,0 @@ -import { - Client, - GatewayIntentBits, - Partials, - OAuth2Scopes, - InteractionType, - PermissionFlagsBits, - ChannelType, - Events, -} from 'discord.js'; -import { reuploadCommands } from './_reupload'; -import { - connect as connectStorage, - isUserPlural, - storeUserPlurality, -} from './storage'; - -import * as BuildConfig from './constants'; -import { parseLog } from './logs'; -import { getLatestMinecraftVersion } from './utils/remoteVersions'; -import { expandDiscordLink } from './utils/resolveMessage'; - -import { membersCommand } from './commands/members'; -import { starsCommand } from './commands/stars'; -import { modrinthCommand } from './commands/modrinth'; -import { tagsCommand } from './commands/tags'; -import { jokeCommand } from './commands/joke'; -import { roryCommand } from './commands/rory'; -import { sayCommand } from './commands/say'; - -import random from 'just-random'; -import { green, bold, yellow, cyan } from 'kleur/colors'; -import 'dotenv/config'; -import { - fetchPluralKitMessage, - isMessageProxied, - pkDelay, -} from './utils/pluralKit'; - -const client = new Client({ - intents: [ - GatewayIntentBits.Guilds, - GatewayIntentBits.GuildMessages, - GatewayIntentBits.MessageContent, - GatewayIntentBits.DirectMessages, - GatewayIntentBits.GuildMembers, - GatewayIntentBits.GuildPresences, - GatewayIntentBits.GuildMessageReactions, - GatewayIntentBits.GuildModeration, - ], - partials: [Partials.Channel], -}); - -client.once('ready', async () => { - console.log(green('Discord bot ready!')); - - console.log( - cyan( - client.generateInvite({ - scopes: [OAuth2Scopes.Bot], - permissions: [ - PermissionFlagsBits.AddReactions, - PermissionFlagsBits.ViewChannel, - PermissionFlagsBits.BanMembers, - PermissionFlagsBits.KickMembers, - PermissionFlagsBits.CreatePublicThreads, - PermissionFlagsBits.CreatePrivateThreads, - PermissionFlagsBits.EmbedLinks, - PermissionFlagsBits.ManageChannels, - PermissionFlagsBits.ManageRoles, - PermissionFlagsBits.ModerateMembers, - PermissionFlagsBits.MentionEveryone, - PermissionFlagsBits.MuteMembers, - PermissionFlagsBits.SendMessages, - PermissionFlagsBits.SendMessagesInThreads, - PermissionFlagsBits.ReadMessageHistory, - ], - }) - ) - ); - - if (process.env.NODE_ENV !== 'development') - console.warn(yellow(bold('Running in production mode!'))); - - const mcVersion = await getLatestMinecraftVersion(); - client.user?.presence.set({ - activities: [{ name: `Minecraft ${mcVersion}` }], - status: 'online', - }); - - client.on(Events.MessageCreate, async (e) => { - try { - if (e.channel.partial) await e.channel.fetch(); - if (e.author.partial) await e.author.fetch(); - - if (!e.content) return; - if (!e.channel.isTextBased()) return; - - if (e.author === client.user) return; - - if (e.webhookId !== null) { - // defer PK detection - setTimeout(async () => { - const pkMessage = await fetchPluralKitMessage(e); - if (pkMessage !== null) storeUserPlurality(pkMessage.sender); - }, pkDelay); - } - - if ((await isUserPlural(e.author.id)) && (await isMessageProxied(e))) - return; - - if (e.cleanContent.match(BuildConfig.ETA_REGEX)) { - await e.reply( - `${random(BuildConfig.ETA_MESSAGES)} <:pofat:1031701005559144458>` - ); - } - const log = await parseLog(e.content); - if (log != null) { - e.reply({ embeds: [log] }); - return; - } - await expandDiscordLink(e); - } catch (error) { - console.error('Unhandled exception on MessageCreate', error); - } - }); -}); - -client.on(Events.InteractionCreate, async (interaction) => { - try { - if (!interaction.isChatInputCommand()) return; - - const { commandName } = interaction; - - if (commandName === 'ping') { - await interaction.reply({ - content: `Pong! \`${client.ws.ping}ms\``, - ephemeral: true, - }); - } else if (commandName === 'members') { - await membersCommand(interaction); - } else if (commandName === 'stars') { - await starsCommand(interaction); - } else if (commandName === 'modrinth') { - await modrinthCommand(interaction); - } else if (commandName === 'say') { - await sayCommand(interaction); - } else if (commandName === 'tag') { - await tagsCommand(interaction); - } else if (commandName === 'joke') { - await jokeCommand(interaction); - } else if (commandName === 'rory') { - await roryCommand(interaction); - } - } catch (error) { - console.error('Unhandled exception on InteractionCreate', error); - } -}); - -client.on(Events.MessageReactionAdd, async (reaction, user) => { - try { - if (reaction.partial) { - try { - await reaction.fetch(); - } catch (error) { - console.error('Something went wrong when fetching the message:', error); - return; - } - } - - if ( - reaction.message.interaction && - reaction.message.interaction?.type === - InteractionType.ApplicationCommand && - reaction.message.interaction?.user === user && - reaction.emoji.name === '❌' - ) { - await reaction.message.delete(); - } - } catch (error) { - console.error('Unhandled exception on MessageReactionAdd', error); - } -}); - -client.on(Events.ThreadCreate, async (channel) => { - try { - if ( - channel.type === ChannelType.PublicThread && - channel.parent && - channel.parent.name === 'support' && - channel.guild - ) { - const pingRole = channel.guild.roles.cache.find( - (r) => r.name === 'Moderators' - ); - - if (!pingRole) return; - - await channel.send({ - content: ` - <@${channel.ownerId}> We've received your support ticket! Please upload your logs and post the link here if possible (run \`/tag log\` to find out how). Please don't ping people for support questions, unless you have their permission. - `.trim(), - allowedMentions: { - repliedUser: true, - roles: [], - users: channel.ownerId ? [channel.ownerId] : [], - }, - }); - } - } catch (error) { - console.error('Error handling ThreadCreate', error); - } -}); - -reuploadCommands() - .then(() => { - connectStorage(); - client.login(process.env.DISCORD_TOKEN); - }) - .catch((e) => { - console.error(e); - process.exit(1); - }); diff --git a/src/logproviders/0x0.ts b/src/logproviders/0x0.ts deleted file mode 100644 index 65774e6..0000000 --- a/src/logproviders/0x0.ts +++ /dev/null @@ -1,19 +0,0 @@ -const reg = /https:\/\/0x0.st\/\w*.\w*/; - -export async function read0x0(s: string): Promise { - const r = s.match(reg); - if (r == null || !r[0]) return null; - const link = r[0]; - let log: string; - try { - const f = await fetch(link); - if (f.status != 200) { - throw 'nope'; - } - log = await f.text(); - } catch (err) { - console.log('Log analyze fail', err); - return null; - } - return log; -} diff --git a/src/logproviders/haste.ts b/src/logproviders/haste.ts deleted file mode 100644 index c295f41..0000000 --- a/src/logproviders/haste.ts +++ /dev/null @@ -1,21 +0,0 @@ -const reg = /https:\/\/hst.sh\/[\w]*/; - -export async function readHastebin(s: string): Promise { - const r = s.match(reg); - if (r == null || !r[0]) return null; - const link = r[0]; - const id = link.replace('https://hst.sh/', ''); - if (!id) return null; - let log: string; - try { - const f = await fetch(`https://hst.sh/raw/${id}`); - if (f.status != 200) { - throw 'nope'; - } - log = await f.text(); - } catch (err) { - console.log('Log analyze fail', err); - return null; - } - return log; -} diff --git a/src/logproviders/mclogs.ts b/src/logproviders/mclogs.ts deleted file mode 100644 index eecda09..0000000 --- a/src/logproviders/mclogs.ts +++ /dev/null @@ -1,22 +0,0 @@ -const reg = /https:\/\/mclo.gs\/\w*/; - -export async function readMcLogs(s: string): Promise { - const r = s.match(reg); - if (r == null || !r[0]) return null; - const link = r[0]; - const id = link.replace('https://mclo.gs/', ''); - if (!id) return null; - const apiUrl = 'https://api.mclo.gs/1/raw/' + id; - let log: string; - try { - const f = await fetch(apiUrl); - if (f.status != 200) { - throw 'nope'; - } - log = await f.text(); - } catch (err) { - console.log('Log analyze fail', err); - return null; - } - return log; -} diff --git a/src/logproviders/pastegg.ts b/src/logproviders/pastegg.ts deleted file mode 100644 index 11181e7..0000000 --- a/src/logproviders/pastegg.ts +++ /dev/null @@ -1,30 +0,0 @@ -const reg = /https:\/\/paste.gg\/p\/[\w]*\/[\w]*/; - -export async function readPasteGG(s: string): Promise { - const r = s.match(reg); - if (r == null || !r[0]) return null; - const link = r[0]; - const id = link.replace(/https:\/\/paste.gg\/p\/[\w]*\//, ''); - if (!id) return null; - let log: string; - try { - const pasteJson = await ( - await fetch('https://api.paste.gg/v1/pastes/' + id) - ).json(); - if (pasteJson.status != 'success') throw 'up'; - const pasteData = await ( - await fetch( - 'https://api.paste.gg/v1/pastes/' + - id + - '/files/' + - pasteJson.result.files[0].id - ) - ).json(); - if (pasteData.status != 'success') throw 'up'; - return pasteData.result.content.value; - } catch (err) { - console.log('Log analyze fail', err); - return null; - } - return log; -} diff --git a/src/logs.ts b/src/logs.ts deleted file mode 100644 index 08c5c39..0000000 --- a/src/logs.ts +++ /dev/null @@ -1,251 +0,0 @@ -import { getLatestPrismLauncherVersion } from './utils/remoteVersions'; -import { EmbedBuilder } from 'discord.js'; - -// log providers -import { readMcLogs } from './logproviders/mclogs'; -import { read0x0 } from './logproviders/0x0'; -import { readPasteGG } from './logproviders/pastegg'; -import { readHastebin } from './logproviders/haste'; -import { COLORS } from './constants'; - -type Analyzer = (text: string) => Promise | null>; -type LogProvider = (text: string) => Promise; - -const javaAnalyzer: Analyzer = async (text) => { - if (text.includes('This instance is not compatible with Java version')) { - const xp = - /Please switch to one of the following Java versions for this instance:[\r\n]+(Java version (?:\d|\.)+)/g; - - let ver: string; - const m = text.match(xp); - if (!m || !m[0]) { - ver = ''; - } else { - ver = m[0].split('\n')[1]; - } - - return [ - 'Wrong Java Version', - `Please switch to the following: \`${ver}\`\nFor more information, type \`/tag java\``, - ]; - } else if ( - text.includes('Java major version is incompatible. Things might break.') - ) { - return [ - 'Java compatibility check skipped', - 'The Java major version may not work with your Minecraft instance. Please switch to a compatible version', - ]; - } - return null; -}; - -const versionAnalyzer: Analyzer = async (text) => { - const vers = text.match(/Prism Launcher version: [0-9].[0-9].[0-9]/g); - if (vers && vers[0]) { - const latest = await getLatestPrismLauncherVersion(); - const current = vers[0].replace('Prism Launcher version: ', ''); - if (current < latest) { - return [ - 'Outdated Prism Launcher', - `Your installed version is ${current}, while the newest version is ${latest}.\nPlease update, for more info see https://prismlauncher.org/download/`, - ]; - } - } - return null; -}; - -const flatpakNvidiaAnalyzer: Analyzer = async (text) => { - if ( - text.includes('org.lwjgl.LWJGLException: Could not choose GLX13 config') || - text.includes( - 'GLFW error 65545: GLX: Failed to find a suitable GLXFBConfig' - ) - ) { - return [ - 'Outdated Nvidia Flatpak Driver', - `The Nvidia driver for flatpak is outdated.\nPlease run \`flatpak update\` to fix this issue. If that does not solve it, please wait until the driver is added to Flathub and run it again.`, - ]; - } - return null; -}; - -const forgeJavaAnalyzer: Analyzer = async (text) => { - if ( - text.includes( - 'java.lang.NoSuchMethodError: sun.security.util.ManifestEntryVerifier.(Ljava/util/jar/Manifest;)V' - ) - ) { - return [ - 'Forge Java Bug', - 'Old versions of Forge crash with Java 8u321+.\nTo fix this, update forge to the latest version via the Versions tab \n(right click on Forge, click Change Version, and choose the latest one)\nAlternatively, you can download 8u312 or lower. See [archive](https://github.com/adoptium/temurin8-binaries/releases/tag/jdk8u312-b07)', - ]; - } - return null; -}; - -const intelHDAnalyzer: Analyzer = async (text) => { - if (text.includes('org.lwjgl.LWJGLException: Pixel format not accelerated')) { - return [ - 'Intel HD Windows 10', - "Your drivers don't support windows 10 officially\nSee https://prismlauncher.org/wiki/getting-started/installing-java/#a-note-about-intel-hd-20003000-on-windows-10 for more info", - ]; - } - return null; -}; - -const macOSNSWindowAnalyzer: Analyzer = async (text) => { - if ( - text.includes( - "Terminating app due to uncaught exception 'NSInternalInconsistencyException'" - ) - ) { - return [ - 'MacOS NSInternalInconsistencyException', - 'You need to downgrade your Java 8 version. See https://prismlauncher.org/wiki/getting-started/installing-java/#older-minecraft-on-macos', - ]; - } - return null; -}; - -const quiltFabricInternalsAnalyzer: Analyzer = async (text) => { - const base = 'Caused by: java.lang.ClassNotFoundException: '; - if ( - text.includes(base + 'net.fabricmc.fabric.impl') || - text.includes(base + 'net.fabricmc.fabric.mixin') || - text.includes(base + 'net.fabricmc.loader.impl') || - text.includes(base + 'net.fabricmc.loader.mixin') || - text.includes( - 'org.quiltmc.loader.impl.FormattedException: java.lang.NoSuchMethodError:' - ) - ) { - return [ - 'Fabric Internal Access', - `The mod you are using is using fabric internals that are not meant to be used by anything but the loader itself. - Those mods break both on Quilt and with fabric updates. - If you're using fabric, downgrade your fabric loader could work, on Quilt you can try updating to the latest beta version, but there's nothing much to do unless the mod author stops using them.`, - ]; - } - return null; -}; - -const oomAnalyzer: Analyzer = async (text) => { - if (text.includes('java.lang.OutOfMemoryError')) { - return [ - 'Out of Memory', - 'Allocating more RAM to your instance could help prevent this crash.', - ]; - } - return null; -}; - -const javaOptionsAnalyzer: Analyzer = async (text) => { - const r1 = /Unrecognized VM option '(\w*)'/; - const r2 = /Unrecognized option: \w*/; - const m1 = text.match(r1); - const m2 = text.match(r2); - if (m1) { - return [ - 'Wrong Java Arguments', - `Remove \`-XX:${m1[1]}\` from your Java arguments.`, - ]; - } - if (m2) { - return [ - 'Wrong Java Arguments', - `Remove \`${m2[1]}\` from your Java arguments.`, - ]; - } - return null; -}; - -const shenadoahGCAnalyzer: Analyzer = async (text) => { - if (text.includes("Unrecognized VM option 'UseShenandoahGC'")) { - return [ - "Java 8 doesn't support ShenandoahGC", - 'Remove `UseShenandoahGC` from your Java Arguments', - ]; - } - return null; -}; - -const optifineAnalyzer: Analyzer = async (text) => { - const matchesOpti = text.match(/\[✔️\] OptiFine_[\w,.]*/); - const matchesOptiFabric = text.match(/\[✔️\] optifabric-[\w,.]*/); - if (matchesOpti || matchesOptiFabric) { - return [ - 'Possible Optifine Problems', - 'OptiFine is known to cause problems when paired with other mods. Try to disable OptiFine and see if the issue persists.\nCheck `/tag optifine` for more info & alternatives you can use.', - ]; - } - return null; -}; - -const analyzers: Analyzer[] = [ - javaAnalyzer, - versionAnalyzer, - flatpakNvidiaAnalyzer, - forgeJavaAnalyzer, - intelHDAnalyzer, - macOSNSWindowAnalyzer, - quiltFabricInternalsAnalyzer, - oomAnalyzer, - shenadoahGCAnalyzer, - optifineAnalyzer, - javaOptionsAnalyzer, -]; - -const providers: LogProvider[] = [ - readMcLogs, - read0x0, - readPasteGG, - readHastebin, -]; - -export async function parseLog(s: string): Promise { - if (/(https?:\/\/)?pastebin\.com\/(raw\/)?[^/\s]{8}/g.test(s)) { - const embed = new EmbedBuilder() - .setTitle('pastebin.com detected') - .setDescription( - 'Please use https://mclo.gs or another paste provider and send logs using the Log Upload feature in Prism Launcher. (See `/tag log`)' - ) - .setColor(COLORS.red); - return embed; - } - - let log = ''; - for (const i in providers) { - const provider = providers[i]; - const res = await provider(s); - if (res) { - log = res; - break; - } else { - continue; - } - } - if (!log) return null; - const embed = new EmbedBuilder().setTitle('Log analysis'); - - let thereWasAnIssue = false; - for (const i in analyzers) { - const Analyzer = analyzers[i]; - const out = await Analyzer(log); - if (out) { - embed.addFields({ name: out[0], value: out[1] }); - thereWasAnIssue = true; - } - } - - if (thereWasAnIssue) { - embed.setColor(COLORS.red); - return embed; - } else { - embed.setColor(COLORS.green); - embed.addFields({ - name: 'Analyze failed', - value: 'No issues found automatically', - }); - - return embed; - } -} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..6d5b31b --- /dev/null +++ b/src/main.rs @@ -0,0 +1,91 @@ +use std::{sync::Arc, time::Duration}; + +use color_eyre::eyre::{eyre, Context as _, Report, Result}; +use config::Config; +use log::*; +use poise::{ + serenity_prelude as serenity, EditTracker, Framework, FrameworkOptions, PrefixFrameworkOptions, +}; + +mod api; +mod commands; +mod config; +mod consts; +mod handlers; +mod utils; + +type Context<'a> = poise::Context<'a, Data, Report>; + +#[derive(Clone)] +pub struct Data { + config: config::Config, + redis: redis::Client, + octocrab: Arc, +} + +impl Data { + pub fn new() -> Result { + let config = Config::new_from_env()?; + let redis = redis::Client::open(config.redis_url.clone())?; + let octocrab = octocrab::instance(); + + Ok(Self { + config, + redis, + octocrab, + }) + } +} + +#[tokio::main] +async fn main() -> Result<()> { + dotenvy::dotenv().ok(); + color_eyre::install()?; + env_logger::init(); + + let token = + std::env::var("TOKEN").wrap_err_with(|| eyre!("Couldn't find token in environment!"))?; + + let intents = + serenity::GatewayIntents::non_privileged() | serenity::GatewayIntents::MESSAGE_CONTENT; + + let options = FrameworkOptions { + commands: commands::to_global_commands(), + on_error: |error| Box::pin(handlers::handle_error(error)), + command_check: Some(|ctx| { + Box::pin(async move { Ok(ctx.author().id != ctx.framework().bot_id) }) + }), + event_handler: |ctx, event, framework, data| { + Box::pin(handlers::handle_event(ctx, event, framework, data)) + }, + prefix_options: PrefixFrameworkOptions { + prefix: Some("!".into()), + edit_tracker: Some(EditTracker::for_timespan(Duration::from_secs(3600))), + ..Default::default() + }, + ..Default::default() + }; + + let framework = Framework::builder() + .token(token) + .intents(intents) + .options(options) + .setup(|ctx, _ready, framework| { + Box::pin(async move { + poise::builtins::register_globally(ctx, &framework.options().commands).await?; + info!("Registered global commands!"); + + let data = Data::new()?; + + Ok(data) + }) + }); + + tokio::select! { + result = framework.run() => { result.map_err(Report::from) }, + _ = tokio::signal::ctrl_c() => { + info!("Interrupted! Exiting..."); + std::process::exit(130); + } + } +} diff --git a/src/storage.ts b/src/storage.ts deleted file mode 100644 index c3b40c9..0000000 --- a/src/storage.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { createClient } from 'redis'; - -export const client = createClient({ - url: process.env.REDIS_URL || 'redis://localhost:6379', -}); - -export const storeUserPlurality = async (userId: string) => { - // Just store some value. We only care about the presence of this key - await client - .multi() - .set(`user:${userId}:pk`, '0') - .expire(`user:${userId}:pk`, 7 * 24 * 60 * 60) - .exec(); -}; - -export const isUserPlural = async (userId: string) => { - return (await client.exists(`user:${userId}:pk`)) > 0; -}; - -export const connect = () => { - client.connect(); -}; diff --git a/src/tags.ts b/src/tags.ts deleted file mode 100644 index 3df1e2e..0000000 --- a/src/tags.ts +++ /dev/null @@ -1,37 +0,0 @@ -import matter from 'gray-matter'; -import { readdir, readFile } from 'fs/promises'; -import { join } from 'path'; -import { COLORS } from './constants'; - -import { type EmbedField } from 'discord.js'; - -interface Tag { - name: string; - aliases?: string[]; - title?: string; - color?: number; - content: string; - image?: string; - fields?: EmbedField[]; -} - -const TAG_DIR = join(process.cwd(), 'tags'); - -export const getTags = async (): Promise => { - const filenames = await readdir(TAG_DIR); - const tags: Tag[] = []; - - for (const _file of filenames) { - const file = join(TAG_DIR, _file); - const { data, content } = matter(await readFile(file)); - - tags.push({ - ...data, - name: _file.replace('.md', ''), - content: content.trim(), - color: data.color ? COLORS[data.color] : undefined, - }); - } - - return tags; -}; diff --git a/src/utils/macros.rs b/src/utils/macros.rs new file mode 100644 index 0000000..a473959 --- /dev/null +++ b/src/utils/macros.rs @@ -0,0 +1,6 @@ +#[macro_export] +macro_rules! required_var { + ($name: expr) => { + std::env::var($name).wrap_err_with(|| format!("Couldn't find {} in environment!", $name))? + }; +} diff --git a/src/utils/mod.rs b/src/utils/mod.rs new file mode 100644 index 0000000..800865d --- /dev/null +++ b/src/utils/mod.rs @@ -0,0 +1,110 @@ +use crate::Context; + +use color_eyre::eyre::{eyre, Result}; +use poise::serenity_prelude as serenity; +use rand::seq::SliceRandom; +use serenity::{CreateEmbed, Message}; +use url::Url; + +#[macro_use] +mod macros; + +/* + * chooses a random element from an array + */ +pub fn random_choice(arr: [&str; N]) -> Result { + let mut rng = rand::thread_rng(); + let resp = arr + .choose(&mut rng) + .ok_or_else(|| eyre!("Couldn't choose random object from array:\n{arr:#?}!"))?; + + Ok((*resp).to_string()) +} + +// waiting for `round_char_boundary` to stabilize +pub fn floor_char_boundary(s: &str, index: usize) -> usize { + if index >= s.len() { + s.len() + } else { + let lower_bound = index.saturating_sub(3); + let new_index = s.as_bytes()[lower_bound..=index] + .iter() + .rposition(|&b| (b as i8) >= -0x40); // b.is_utf8_char_boundary + + // Can be made unsafe but whatever + lower_bound + new_index.unwrap() + } +} + +pub async fn send_url_as_embed(ctx: Context<'_>, url: String) -> Result<()> { + let parsed = Url::parse(&url)?; + + let title = parsed + .path_segments() + .unwrap() + .last() + .unwrap_or("image") + .replace("%20", " "); + + ctx.send(|c| c.embed(|e| e.title(title).image(&url).url(url))) + .await?; + + Ok(()) +} + +pub async fn resolve_message_to_embed(ctx: &serenity::Context, msg: &Message) -> CreateEmbed { + let truncation_point = floor_char_boundary(&msg.content, 700); + let truncated_content = if msg.content.len() <= truncation_point { + msg.content.to_string() + } else { + format!("{}...", &msg.content[..truncation_point]) + }; + + let color = msg + .member(ctx) + .await + .ok() + .and_then(|m| m.highest_role_info(&ctx.cache)) + .and_then(|(role, _)| role.to_role_cached(&ctx.cache)) + .map(|role| role.colour); + + let attached_image = msg + .attachments + .iter() + .filter(|a| { + a.content_type + .as_ref() + .filter(|ct| ct.contains("image/")) + .is_some() + }) + .map(|a| &a.url) + .next(); + + let attachments_len = msg.attachments.len(); + + let mut embed = msg + .embeds + .first() + .map(|embed| CreateEmbed::from(embed.clone())) + .unwrap_or_default(); + + embed.author(|author| author.name(&msg.author.name).icon_url(&msg.author.face())); + + if let Some(color) = color { + embed.color(color); + } + + if let Some(attachment) = attached_image { + embed.image(attachment); + } + + if attachments_len > 1 { + embed.footer(|footer| { + // yes it will say '1 attachments' no i do not care + footer.text(format!("{} attachments", attachments_len)) + }); + } + + embed.description(truncated_content); + embed +} diff --git a/src/utils/pluralKit.ts b/src/utils/pluralKit.ts deleted file mode 100644 index 3f5aae3..0000000 --- a/src/utils/pluralKit.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { Message } from 'discord.js'; - -interface PkMessage { - sender: string; -} - -export const pkDelay = 1000; - -export async function fetchPluralKitMessage(message: Message) { - const response = await fetch( - `https://api.pluralkit.me/v2/messages/${message.id}` - ); - - if (!response.ok) return null; - - return (await response.json()) as PkMessage; -} - -export async function isMessageProxied(message: Message) { - await new Promise((resolve) => setTimeout(resolve, pkDelay)); - return (await fetchPluralKitMessage(message)) !== null; -} diff --git a/src/utils/remoteVersions.ts b/src/utils/remoteVersions.ts deleted file mode 100644 index d862787..0000000 --- a/src/utils/remoteVersions.ts +++ /dev/null @@ -1,30 +0,0 @@ -interface MetaPackage { - formatVersion: number; - name: string; - recommended: string[]; - uid: string; -} - -interface SimplifiedGHReleases { - tag_name: string; -} - -// TODO: caching -export async function getLatestMinecraftVersion(): Promise { - const f = await fetch( - 'https://meta.prismlauncher.org/v1/net.minecraft/package.json' - ); - - const minecraft = (await f.json()) as MetaPackage; - return minecraft.recommended[0]; -} - -// TODO: caching -export async function getLatestPrismLauncherVersion(): Promise { - const f = await fetch( - 'https://api.github.com/repos/PrismLauncher/PrismLauncher/releases' - ); - const versions = (await f.json()) as SimplifiedGHReleases[]; - - return versions[0].tag_name; -} diff --git a/src/utils/resolveMessage.ts b/src/utils/resolveMessage.ts deleted file mode 100644 index 8b7c0d2..0000000 --- a/src/utils/resolveMessage.ts +++ /dev/null @@ -1,106 +0,0 @@ -import { - Colors, - EmbedBuilder, - type Message, - ThreadChannel, - ReactionCollector, -} from 'discord.js'; - -function findFirstImage(message: Message): string | undefined { - const result = message.attachments.find((attach) => { - return attach.contentType?.startsWith('image/'); - }); - - if (result === undefined) { - return undefined; - } else { - return result.url; - } -} - -export async function expandDiscordLink(message: Message): Promise { - if (message.author.bot && !message.webhookId) return; - - const re = - /(https?:\/\/)?(?:canary\.|ptb\.)?discord(?:app)?\.com\/channels\/(?\d+)\/(?\d+)\/(?\d+)/g; - - const results = message.content.matchAll(re); - const resultEmbeds: EmbedBuilder[] = []; - - for (const r of results) { - if (resultEmbeds.length >= 3) break; // only process three previews - - if (r.groups == undefined || r.groups.serverId != message.guildId) continue; // do not let the bot leak messages from one server to another - - try { - const channel = await message.guild?.channels.fetch(r.groups.channelId); - - if (!channel || !channel.isTextBased()) continue; - - if (channel instanceof ThreadChannel) { - if ( - !channel.parent?.members?.some((user) => user.id == message.author.id) - ) - continue; // do not reveal a message to a user who can't see it - } else { - if (!channel.members?.some((user) => user.id == message.author.id)) - continue; // do not reveal a message to a user who can't see it - } - - const originalMessage = await channel.messages.fetch(r.groups.messageId); - - const embed = new EmbedBuilder() - .setAuthor({ - name: originalMessage.author.tag, - iconURL: originalMessage.author.displayAvatarURL(), - }) - .setColor(Colors.Aqua) - .setTimestamp(originalMessage.createdTimestamp) - .setFooter({ text: `#${originalMessage.channel.name}` }); - - embed.setDescription( - (originalMessage.content ? originalMessage.content + '\n\n' : '') + - `[Jump to original message](${originalMessage.url})` - ); - - if (originalMessage.attachments.size > 0) { - embed.addFields({ - name: 'Attachments', - value: originalMessage.attachments - .map((att) => `[${att.name}](${att.url})`) - .join('\n'), - }); - - const firstImage = findFirstImage(originalMessage); - if (firstImage) { - embed.setImage(firstImage); - } - } - - resultEmbeds.push(embed); - } catch (ignored) { - /* */ - } - } - - if (resultEmbeds.length > 0) { - const reply = await message.reply({ - embeds: resultEmbeds, - allowedMentions: { repliedUser: false }, - }); - - const collector = new ReactionCollector(reply, { - filter: (reaction) => { - return reaction.emoji.name === '❌'; - }, - time: 5 * 60 * 1000, - }); - - collector.on('collect', async (_, user) => { - if (user === message.author) { - await reply.delete(); - collector.stop(); - } - }); - } -} diff --git a/tsconfig.json b/tsconfig.json deleted file mode 100644 index def0a5e..0000000 --- a/tsconfig.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "compilerOptions": { - "strict": true, - "downlevelIteration": true, - "module": "esnext", - "target": "esnext", - "moduleResolution": "node", - "esModuleInterop": true, - "allowSyntheticDefaultImports": true, - "skipLibCheck": true, - "noEmit": true - } -} From e928eb67df4cef9814797d158351094180d1afa0 Mon Sep 17 00:00:00 2001 From: seth Date: Sun, 3 Dec 2023 04:14:20 -0500 Subject: [PATCH 02/90] feat: add nix package, module, and container Signed-off-by: seth --- flake.lock | 150 ++++++++++++++++++++++++++++++++++----------- flake.nix | 57 ++++++++--------- nix/deployment.nix | 84 +++++++++++++++++++++++++ nix/derivation.nix | 51 +++++++++++++++ nix/dev.nix | 50 +++++++++++++++ nix/module.nix | 143 ++++++++++++++++++++++++++++++++++++++++++ nix/packages.nix | 29 +++++++++ 7 files changed, 497 insertions(+), 67 deletions(-) create mode 100644 nix/deployment.nix create mode 100644 nix/derivation.nix create mode 100644 nix/dev.nix create mode 100644 nix/module.nix create mode 100644 nix/packages.nix diff --git a/flake.lock b/flake.lock index 06957f9..ba6aa30 100644 --- a/flake.lock +++ b/flake.lock @@ -1,5 +1,26 @@ { "nodes": { + "fenix": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ], + "rust-analyzer-src": "rust-analyzer-src" + }, + "locked": { + "lastModified": 1701498074, + "narHash": "sha256-UNYTZtBYa/4G5+dRzNNXNcEi1RVm6yOUQNHYkcRag2Q=", + "owner": "nix-community", + "repo": "fenix", + "rev": "ce8747b0d8d6605264651f9ab5c84db6d7a56728", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "fenix", + "type": "github" + } + }, "flake-compat": { "flake": false, "locked": { @@ -16,21 +37,18 @@ "type": "github" } }, - "flake-parts": { - "inputs": { - "nixpkgs-lib": "nixpkgs-lib" - }, + "flake-root": { "locked": { - "lastModified": 1696343447, - "narHash": "sha256-B2xAZKLkkeRFG5XcHHSXXcP7To9Xzr59KXeZiRf4vdQ=", - "owner": "hercules-ci", - "repo": "flake-parts", - "rev": "c9afaba3dfa4085dbd2ccb38dfade5141e33d9d4", + "lastModified": 1692742795, + "narHash": "sha256-f+Y0YhVCIJ06LemO+3Xx00lIcqQxSKJHXT/yk1RTKxw=", + "owner": "srid", + "repo": "flake-root", + "rev": "d9a70d9c7a5fd7f3258ccf48da9335e9b47c3937", "type": "github" }, "original": { - "owner": "hercules-ci", - "repo": "flake-parts", + "owner": "srid", + "repo": "flake-root", "type": "github" } }, @@ -73,13 +91,33 @@ "type": "github" } }, + "naersk": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1698420672, + "narHash": "sha256-/TdeHMPRjjdJub7p7+w55vyABrsJlt5QkznPYy55vKA=", + "owner": "nix-community", + "repo": "naersk", + "rev": "aeb58d5e8faead8980a807c840232697982d47b9", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "naersk", + "type": "github" + } + }, "nixpkgs": { "locked": { - "lastModified": 1696757521, - "narHash": "sha256-cfgtLNCBLFx2qOzRLI6DHfqTdfWI+UbvsKYa3b3fvaA=", + "lastModified": 1701336116, + "narHash": "sha256-kEmpezCR/FpITc6yMbAh4WrOCiT2zg5pSjnKrq51h5Y=", "owner": "nixos", "repo": "nixpkgs", - "rev": "2646b294a146df2781b1ca49092450e8a32814e1", + "rev": "f5c27c6136db4d76c30e533c20517df6864c46ee", "type": "github" }, "original": { @@ -89,24 +127,6 @@ "type": "github" } }, - "nixpkgs-lib": { - "locked": { - "dir": "lib", - "lastModified": 1696019113, - "narHash": "sha256-X3+DKYWJm93DRSdC5M6K5hLqzSya9BjibtBsuARoPco=", - "owner": "NixOS", - "repo": "nixpkgs", - "rev": "f5892ddac112a1e9b3612c39af1b72987ee5783a", - "type": "github" - }, - "original": { - "dir": "lib", - "owner": "NixOS", - "ref": "nixos-unstable", - "repo": "nixpkgs", - "type": "github" - } - }, "nixpkgs-stable": { "locked": { "lastModified": 1685801374, @@ -123,6 +143,26 @@ "type": "github" } }, + "parts": { + "inputs": { + "nixpkgs-lib": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1701473968, + "narHash": "sha256-YcVE5emp1qQ8ieHUnxt1wCZCC3ZfAS+SRRWZ2TMda7E=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "34fed993f1674c8d06d58b37ce1e0fe5eebcb9f5", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "flake-parts", + "type": "github" + } + }, "pre-commit-hooks": { "inputs": { "flake-compat": "flake-compat", @@ -134,11 +174,11 @@ "nixpkgs-stable": "nixpkgs-stable" }, "locked": { - "lastModified": 1696846637, - "narHash": "sha256-0hv4kbXxci2+pxhuXlVgftj/Jq79VSmtAyvfabCCtYk=", + "lastModified": 1700922917, + "narHash": "sha256-ej2fch/T584b5K9sk1UhmZF7W6wEfDHuoUYpFN8dtvM=", "owner": "cachix", "repo": "pre-commit-hooks.nix", - "rev": "42e1b6095ef80a51f79595d9951eb38e91c4e6ca", + "rev": "e5ee5c5f3844550c01d2131096c7271cec5e9b78", "type": "github" }, "original": { @@ -147,11 +187,47 @@ "type": "github" } }, + "proc-flake": { + "locked": { + "lastModified": 1692742849, + "narHash": "sha256-Nv8SOX+O6twFfPnA9BfubbPLZpqc+UeK6JvIWnWkdb0=", + "owner": "srid", + "repo": "proc-flake", + "rev": "25291b6e3074ad5dd573c1cb7d96110a9591e10f", + "type": "github" + }, + "original": { + "owner": "srid", + "repo": "proc-flake", + "type": "github" + } + }, "root": { "inputs": { - "flake-parts": "flake-parts", + "fenix": "fenix", + "flake-root": "flake-root", + "naersk": "naersk", "nixpkgs": "nixpkgs", - "pre-commit-hooks": "pre-commit-hooks" + "parts": "parts", + "pre-commit-hooks": "pre-commit-hooks", + "proc-flake": "proc-flake" + } + }, + "rust-analyzer-src": { + "flake": false, + "locked": { + "lastModified": 1701447636, + "narHash": "sha256-WaCcxLNAqo/FAK0QtYqweKCUVTGcbKpFIHClc+k2YlI=", + "owner": "rust-lang", + "repo": "rust-analyzer", + "rev": "e402c494b7c7d94a37c6d789a216187aaf9ccd3e", + "type": "github" + }, + "original": { + "owner": "rust-lang", + "ref": "nightly", + "repo": "rust-analyzer", + "type": "github" } }, "systems": { diff --git a/flake.nix b/flake.nix index 9b8d655..c311d1f 100644 --- a/flake.nix +++ b/flake.nix @@ -3,44 +3,41 @@ inputs = { nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable"; - flake-parts.url = "github:hercules-ci/flake-parts"; + parts = { + url = "github:hercules-ci/flake-parts"; + inputs.nixpkgs-lib.follows = "nixpkgs"; + }; + + naersk = { + url = "github:nix-community/naersk"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + + fenix = { + url = "github:nix-community/fenix"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + pre-commit-hooks = { url = "github:cachix/pre-commit-hooks.nix"; inputs.nixpkgs.follows = "nixpkgs"; }; + + flake-root.url = "github:srid/flake-root"; + proc-flake.url = "github:srid/proc-flake"; }; - outputs = { - flake-parts, - pre-commit-hooks, - ... - } @ inputs: - flake-parts.lib.mkFlake {inherit inputs;} { + outputs = {parts, ...} @ inputs: + parts.lib.mkFlake {inherit inputs;} { imports = [ - pre-commit-hooks.flakeModule - ]; + ./nix/dev.nix + ./nix/packages.nix + ./nix/deployment.nix - perSystem = { - config, - lib, - pkgs, - ... - }: { - pre-commit.settings.hooks = { - alejandra.enable = true; - prettier = { - enable = true; - excludes = ["flake.lock" "pnpm-lock.yaml"]; - }; - }; - devShells.default = pkgs.mkShell { - shellHook = '' - ${config.pre-commit.installationScript} - ''; - packages = with pkgs; [nodePackages.pnpm redis]; - }; - formatter = pkgs.alejandra; - }; + inputs.pre-commit-hooks.flakeModule + inputs.flake-root.flakeModule + inputs.proc-flake.flakeModule + ]; systems = [ "x86_64-linux" diff --git a/nix/deployment.nix b/nix/deployment.nix new file mode 100644 index 0000000..84044fc --- /dev/null +++ b/nix/deployment.nix @@ -0,0 +1,84 @@ +{ + inputs, + self, + ... +}: { + flake.nixosModules.default = import ./module.nix self; + + perSystem = { + lib, + pkgs, + system, + config, + inputs', + ... + }: let + crossPkgsFor = lib.fix (finalAttrs: { + "x86_64-linux" = { + "x86_64" = pkgs.pkgsStatic; + "aarch64" = pkgs.pkgsCross.aarch64-multiplatform.pkgsStatic; + }; + + "aarch64-linux" = { + "x86_64" = pkgs.pkgsCross.musl64; + "aarch64" = pkgs.pkgsStatic; + }; + + "x86_64-darwin" = { + "x86_64" = pkgs.pkgsCross.musl64; + "aarch64" = pkgs.pkgsCross.aarch64-multiplatform.pkgsStatic; + }; + + "aarch64-darwin" = finalAttrs."x86_64-darwin"; + }); + + exeFor = arch: let + target = "${arch}-unknown-linux-musl"; + target' = builtins.replaceStrings ["-"] ["_"] target; + targetUpper = lib.toUpper target'; + + toolchain = with inputs'.fenix.packages; + combine [ + minimal.cargo + minimal.rustc + targets.${target}.latest.rust-std + ]; + + naersk' = inputs.naersk.lib.${system}.override { + cargo = toolchain; + rustc = toolchain; + }; + + refraction = config.packages.refraction.override { + naersk = naersk'; + optimizeSize = true; + }; + + inherit (crossPkgsFor.${system}.${arch}.stdenv) cc; + in + lib.getExe ( + refraction.overrideAttrs (_: + lib.fix (finalAttrs: { + CARGO_BUILD_TARGET = target; + "CC_${target'}" = "${cc}/bin/${cc.targetPrefix}cc"; + "CARGO_TARGET_${targetUpper}_RUSTFLAGS" = "-C target-feature=+crt-static"; + "CARGO_TARGET_${targetUpper}_LINKER" = finalAttrs."CC_${target'}"; + })) + ); + + containerFor = arch: + pkgs.dockerTools.buildLayeredImage { + name = "refraction"; + tag = "latest-${arch}"; + contents = [pkgs.dockerTools.caCertificates]; + config.Cmd = [(exeFor arch)]; + + architecture = crossPkgsFor.${system}.${arch}.go.GOARCH; + }; + in { + legacyPackages = { + container-x86_64 = containerFor "x86_64"; + container-aarch64 = containerFor "aarch64"; + }; + }; +} diff --git a/nix/derivation.nix b/nix/derivation.nix new file mode 100644 index 0000000..f52b5a8 --- /dev/null +++ b/nix/derivation.nix @@ -0,0 +1,51 @@ +{ + lib, + stdenv, + naersk, + CoreFoundation, + Security, + SystemConfiguration, + version, + optimizeSize ? false, +}: let + filter = path: type: let + path' = toString path; + base = baseNameOf path'; + + dirBlocklist = ["nix"]; + + matches = lib.any (suffix: lib.hasSuffix suffix base) [".rs"]; + isCargo = base == "Cargo.lock" || base == "Cargo.toml"; + isAllowedDir = !(builtins.elem base dirBlocklist); + in + (type == "directory" && isAllowedDir) || matches || isCargo; + + filterSource = src: + lib.cleanSourceWith { + src = lib.cleanSource src; + inherit filter; + }; +in + naersk.buildPackage { + pname = "refraction"; + inherit version; + + src = filterSource ../.; + + buildInputs = lib.optionals stdenv.hostPlatform.isDarwin [ + CoreFoundation + Security + SystemConfiguration + ]; + + RUSTFLAGS = lib.optionalString optimizeSize "-C codegen-units=1 -C strip=symbols -C opt-level=z"; + + meta = with lib; { + mainProgram = "refraction"; + description = "Discord bot for Prism Launcher"; + homepage = "https://github.com/PrismLauncher/refraction"; + license = licenses.gpl3Plus; + platforms = with platforms; linux ++ darwin; + maintainers = with maintainers; [getchoo Scrumplex]; + }; + } diff --git a/nix/dev.nix b/nix/dev.nix new file mode 100644 index 0000000..9697dac --- /dev/null +++ b/nix/dev.nix @@ -0,0 +1,50 @@ +{ + perSystem = { + lib, + pkgs, + config, + ... + }: { + pre-commit.settings.hooks = { + actionlint.enable = true; + alejandra.enable = true; + rustfmt.enable = true; + nil.enable = true; + prettier = { + enable = true; + excludes = ["flake.lock"]; + }; + }; + + proc.groups.daemons.processes = { + redis.command = "${lib.getExe' pkgs.redis "redis-server"}"; + }; + + devShells.default = pkgs.mkShell { + shellHook = '' + ${config.pre-commit.installationScript} + ''; + + packages = with pkgs; [ + # general + actionlint + config.proc.groups.daemons.package + + # rust + cargo + rustc + clippy + rustfmt + rust-analyzer + + # nix + config.formatter + nil + ]; + + RUST_SRC_PATH = "${pkgs.rust.packages.stable.rustPlatform.rustLibSrc}"; + }; + + formatter = pkgs.alejandra; + }; +} diff --git a/nix/module.nix b/nix/module.nix new file mode 100644 index 0000000..baee5fe --- /dev/null +++ b/nix/module.nix @@ -0,0 +1,143 @@ +self: { + config, + lib, + pkgs, + ... +}: let + cfg = config.services.refraction; + defaultUser = "refraction"; + + inherit + (lib) + getExe + literalExpression + mdDoc + mkEnableOption + mkIf + mkOption + mkPackageOption + optionals + types + ; +in { + options.services.refraction = { + enable = mkEnableOption "refraction"; + package = mkPackageOption self.packages.${pkgs.stdenv.hostPlatform.system} "refraction" {}; + + user = mkOption { + description = mdDoc '' + User under which the service should run. If this is the default value, + the user will be created, with the specified group as the primary + group. + ''; + type = types.str; + default = defaultUser; + example = literalExpression '' + "bob" + ''; + }; + + group = mkOption { + description = mdDoc '' + Group under which the service should run. If this is the default value, + the group will be created. + ''; + type = types.str; + default = defaultUser; + example = literalExpression '' + "discordbots" + ''; + }; + + redisUrl = mkOption { + description = mdDoc '' + Connection to a redis server. If this needs to include credentials + that shouldn't be world-readable in the Nix store, set environmentFile + and override the `REDIS_URL` entry. + Pass the string `local` to setup a local Redis database. + ''; + type = types.str; + default = "local"; + example = literalExpression '' + "redis://localhost/" + ''; + }; + + environmentFile = mkOption { + description = mdDoc '' + Environment file as defined in {manpage}`systemd.exec(5)` + ''; + type = types.nullOr types.path; + default = null; + example = literalExpression '' + "/run/agenix.d/1/refraction" + ''; + }; + }; + + config = mkIf cfg.enable { + services.redis.servers.refraction = mkIf (cfg.redisUrl == "local") { + enable = true; + inherit (cfg) user; + port = 0; # disable tcp listener + }; + + systemd.services."refraction" = { + enable = true; + wantedBy = ["multi-user.target"]; + after = + ["network.target"] + ++ optionals (cfg.redisUrl == "local") ["redis-refraction.service"]; + + script = '' + ${getExe cfg.package} + ''; + + environment = { + REDIS_URL = + if cfg.redisUrl == "local" + then "unix:${config.services.redis.servers.refraction.unixSocket}" + else cfg.redisUrl; + }; + + serviceConfig = { + Type = "simple"; + Restart = "always"; + + EnvironmentFile = mkIf (cfg.environmentFile != null) cfg.environmentFile; + + User = cfg.user; + Group = cfg.group; + + # hardening + NoNewPrivileges = true; + PrivateDevices = true; + PrivateTmp = true; + PrivateUsers = true; + ProtectClock = true; + ProtectControlGroups = true; + ProtectHome = true; + ProtectHostname = true; + ProtectKernelLogs = true; + ProtectKernelModules = true; + ProtectKernelTunables = true; + ProtectSystem = "strict"; + RestrictNamespaces = "uts ipc pid user cgroup"; + RestrictSUIDSGID = true; + }; + }; + + users = { + users = mkIf (cfg.user == defaultUser) { + ${defaultUser} = { + isSystemUser = true; + inherit (cfg) group; + }; + }; + + groups = mkIf (cfg.group == defaultUser) { + ${defaultUser} = {}; + }; + }; + }; +} diff --git a/nix/packages.nix b/nix/packages.nix new file mode 100644 index 0000000..e973cf9 --- /dev/null +++ b/nix/packages.nix @@ -0,0 +1,29 @@ +{ + self, + inputs, + ... +}: { + perSystem = { + pkgs, + system, + config, + ... + }: { + packages = { + refraction = pkgs.callPackage ./derivation.nix { + version = builtins.substring 0 8 self.lastModifiedDate or "dirty"; + + inherit + (pkgs.darwin.apple_sdk.frameworks) + CoreFoundation + Security + SystemConfiguration + ; + + naersk = inputs.naersk.lib.${system}; + }; + + default = config.packages.refraction; + }; + }; +} From 65fc5d5ed7e272c3b7a639ffb4efc9c753437187 Mon Sep 17 00:00:00 2001 From: seth Date: Sun, 3 Dec 2023 04:16:57 -0500 Subject: [PATCH 03/90] ci: add clippy/rustfmt checks Signed-off-by: seth --- .github/workflows/check.yml | 68 +++++++++++++++++++++++++++++++++++++ .github/workflows/lint.yml | 25 -------------- 2 files changed, 68 insertions(+), 25 deletions(-) create mode 100644 .github/workflows/check.yml delete mode 100644 .github/workflows/lint.yml diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml new file mode 100644 index 0000000..6b27d70 --- /dev/null +++ b/.github/workflows/check.yml @@ -0,0 +1,68 @@ +name: Check + +on: + push: + branches: ['main'] + pull_request: + +jobs: + rustfmt: + name: Run rustfmt + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + with: + toolchain: stable + components: rustfmt + + - name: Setup Rust cache + uses: Swatinem/rust-cache@v2 + + - name: Run rustfmt + run: cargo fmt --all -- --check + + clippy: + name: Run Clippy scan + runs-on: ubuntu-latest + + permissions: + security-events: write + + steps: + - uses: actions/checkout@v4 + + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + with: + toolchain: stable + components: clippy + + - name: Setup Rust cache + uses: Swatinem/rust-cache@v2 + + - name: Install SARIF tools + run: cargo install clippy-sarif sarif-fmt + + - name: Fetch Cargo deps + run: cargo fetch --locked + + - name: Run Clippy + continue-on-error: true + run: | + set -euxo pipefail + + cargo clippy \ + --all-features \ + --all-targets \ + --message-format=json \ + | clippy-sarif | tee /tmp/clippy.sarif | sarif-fmt + + - name: Upload results + uses: github/codeql-action/upload-sarif@v2 + with: + sarif_file: /tmp/clippy.sarif + wait-for-processing: true diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml deleted file mode 100644 index a7a80ce..0000000 --- a/.github/workflows/lint.yml +++ /dev/null @@ -1,25 +0,0 @@ -name: Lint - -on: - push: - branches: ['main'] - pull_request: - branches: ['main'] - -jobs: - lint: - runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4 - - name: Install Node.js - uses: actions/setup-node@v4 - with: - node-version: 20 - - name: Install pnpm - uses: pnpm/action-setup@v2 - - - name: Install dependencies - run: pnpm install - - name: Lint - run: pnpm run lint From a26a2fd4849bfd497ddc367c7134946da6b95f98 Mon Sep 17 00:00:00 2001 From: seth Date: Sun, 3 Dec 2023 04:17:43 -0500 Subject: [PATCH 04/90] ci: add nix workflows Signed-off-by: seth --- .github/workflows/nix.yml | 44 ++++++++++++++++++++++++++++++ .github/workflows/update-flake.yml | 28 +++++++++++++++++++ 2 files changed, 72 insertions(+) create mode 100644 .github/workflows/nix.yml create mode 100644 .github/workflows/update-flake.yml diff --git a/.github/workflows/nix.yml b/.github/workflows/nix.yml new file mode 100644 index 0000000..57ae224 --- /dev/null +++ b/.github/workflows/nix.yml @@ -0,0 +1,44 @@ +name: Nix + +on: + push: + branches: ['main'] + pull_request: + workflow_dispatch: + +jobs: + build: + strategy: + matrix: + os: [ubuntu-latest, macos-latest] + + runs-on: ${{ matrix.os }} + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Install Nix + uses: DeterminateSystems/nix-installer-action@v8 + + - name: Setup Nix cache + uses: DeterminateSystems/magic-nix-cache-action@v2 + + - name: Build refraction + run: nix build -L --fallback + + check: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Install Nix + uses: DeterminateSystems/nix-installer-action@v8 + + - name: Setup Nix cache + uses: DeterminateSystems/magic-nix-cache-action@v2 + + - name: Run checks + run: nix flake check -L --show-trace diff --git a/.github/workflows/update-flake.yml b/.github/workflows/update-flake.yml new file mode 100644 index 0000000..014a603 --- /dev/null +++ b/.github/workflows/update-flake.yml @@ -0,0 +1,28 @@ +name: Update flake.lock + +on: + schedule: + # run every saturday + - cron: '0 0 * * 6' + workflow_dispatch: + +jobs: + update: + runs-on: ubuntu-latest + + permissions: + contents: write + pull-requests: write + + steps: + - uses: actions/checkout@v4 + + - name: Install Nix + uses: nixbuild/nix-quick-install-action@v26 + + - name: Update and create PR + uses: DeterminateSystems/update-flake-lock@v20 + with: + commit-msg: 'flake: update inputs' + pr-title: 'flake: update inputs' + token: ${{ github.token }} From 4b80ec7345c089387066e5b5252665c0d634a49b Mon Sep 17 00:00:00 2001 From: seth Date: Sun, 3 Dec 2023 04:18:12 -0500 Subject: [PATCH 05/90] ci: build docker images with nix Signed-off-by: seth --- .dockerignore | 6 -- .github/workflows/docker.yml | 130 ++++++++++++++--------------------- Dockerfile | 11 --- 3 files changed, 53 insertions(+), 94 deletions(-) delete mode 100644 .dockerignore delete mode 100644 Dockerfile diff --git a/.dockerignore b/.dockerignore deleted file mode 100644 index cf1edea..0000000 --- a/.dockerignore +++ /dev/null @@ -1,6 +0,0 @@ -node_modules/ -.git/ - -.env -.env.* -!.env.example diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index eab8a0b..009aa4e 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -3,112 +3,88 @@ name: Docker on: push: branches: ['main'] - -env: - REGISTRY: ghcr.io - IMAGE_NAME: prismlauncher/refraction - -permissions: - contents: read - packages: write + pull_request: + workflow_dispatch: jobs: build: + name: Build image + runs-on: ubuntu-latest strategy: matrix: - platform: - - linux/arm64 - - linux/amd64 + arch: [x86_64, aarch64] steps: - - name: Checkout repository - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4 + - uses: actions/checkout@v4 - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 - if: ${{ matrix.platform != 'linux/amd64' }} + - name: Install Nix + uses: DeterminateSystems/nix-installer-action@v8 - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 + - name: Setup Nix cache + uses: DeterminateSystems/magic-nix-cache-action@v2 - - name: Log in to the Container registry - uses: docker/login-action@v3 - with: - registry: ${{ env.REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Extract metadata (tags, labels) for Docker - id: meta - uses: docker/metadata-action@v5 - with: - images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} - tags: | - type=ref,event=branch - type=raw,value=latest - - - name: Build and push by digest - uses: docker/build-push-action@v5 + - name: Build Docker image id: build - with: - context: . - provenance: false - labels: ${{ steps.meta.outputs.labels }} - platforms: ${{ matrix.platform }} - outputs: type=image,name=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }},push-by-digest=true,name-canonical=true,push=true - - - name: Export digests run: | - mkdir -p /tmp/digests - digest="${{ steps.build.outputs.digest }}" - touch "/tmp/digests/${digest#sha256:}" + nix build -L --accept-flake-config .#container-${{ matrix.arch }} + [ ! -L result ] && exit 1 + echo "path=$(realpath result)" >> "$GITHUB_OUTPUT" - - name: Upload digests + - name: Upload image uses: actions/upload-artifact@v3 with: - name: digests - path: /tmp/digests/* + name: container-${{ matrix.arch }} + path: ${{ steps.build.outputs.path }} if-no-files-found: error retention-days: 1 push: + name: Push image + + needs: build runs-on: ubuntu-latest - needs: - - build + + permissions: + packages: write + + env: + REGISTRY: ghcr.io + USERNAME: ${{ github.actor }} + IMAGE_NAME: ${{ github.repository }} + + if: github.event_name == 'push' steps: - - name: Download digests + - uses: actions/checkout@v4 + + - name: Download images uses: actions/download-artifact@v3 with: - name: digests - path: /tmp/digests + path: images - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Log in to the Container registry + - name: Login to registry uses: docker/login-action@v3 with: registry: ${{ env.REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} + username: ${{ env.USERNAME }} + password: ${{ github.token }} - - name: Extract metadata (tags, labels) for Docker - id: meta - uses: docker/metadata-action@v5 - with: - images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} - tags: | - type=ref,event=branch - type=raw,value=latest - - - name: Create manifest list and push - working-directory: /tmp/digests + - name: Push to registry + env: + TAG: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest run: | - docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ - $(printf '${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@sha256:%s ' *) + set -eux - - name: Inspect image - run: | - docker buildx imagetools inspect ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.meta.outputs.version }} + architectures=("x86_64" "aarch64") + for arch in "${architectures[@]}"; do + docker load < images/container-"$arch"/*.tar.gz + docker tag refraction:latest-"$arch" ${{ env.TAG }}-"$arch" + docker push ${{ env.TAG }}-"$arch" + done + + docker manifest create ${{ env.TAG }} \ + --amend ${{ env.TAG }}-x86_64 \ + --amend ${{ env.TAG }}-aarch64 + + docker manifest push ${{ env.TAG }} diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 53f341b..0000000 --- a/Dockerfile +++ /dev/null @@ -1,11 +0,0 @@ -FROM docker.io/library/node:21-alpine -RUN corepack enable -RUN corepack prepare pnpm@latest --activate - -WORKDIR /app - -COPY package.json pnpm-lock.yaml . -RUN pnpm install --frozen-lockfile - -COPY . . -CMD [ "pnpm", "run", "start" ] From 358df91509e8f8000301dfd8a5f691d13d180054 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Sun, 3 Dec 2023 13:42:36 +0000 Subject: [PATCH 06/90] feat: reintroduce eta Signed-off-by: seth --- src/consts.rs | 19 +++++++++++++++++++ src/handlers/error.rs | 2 +- src/handlers/event/eta.rs | 18 ++++++++++++++++++ src/handlers/event/mod.rs | 8 +++++--- 4 files changed, 43 insertions(+), 4 deletions(-) create mode 100644 src/handlers/event/eta.rs diff --git a/src/consts.rs b/src/consts.rs index 10cbf68..63d5e12 100644 --- a/src/consts.rs +++ b/src/consts.rs @@ -11,3 +11,22 @@ pub static COLORS: Lazy> = Lazy::new(|| { ("orange", (251, 146, 60)), ]) }); + +pub const ETA_MESSAGES: [&str; 16] = [ + "Sometime", + "Some day", + "Not far", + "The future", + "Never", + "Perhaps tomorrow?", + "There are no ETAs", + "No", + "Nah", + "Yes", + "Yas", + "Next month", + "Next year", + "Next week", + "In Prism Launcher 2.0.0", + "At the appropriate juncture, in due course, in the fullness of time", +]; diff --git a/src/handlers/error.rs b/src/handlers/error.rs index 1ed4dc0..5842b49 100644 --- a/src/handlers/error.rs +++ b/src/handlers/error.rs @@ -6,7 +6,7 @@ use log::*; use poise::serenity_prelude::Timestamp; use poise::FrameworkError; -pub async fn handle(error: poise::FrameworkError<'_, Data, Report>) { +pub async fn handle(error: FrameworkError<'_, Data, Report>) { match error { FrameworkError::Setup { error, .. } => error!("Error setting up client!\n{error:#?}"), diff --git a/src/handlers/event/eta.rs b/src/handlers/event/eta.rs new file mode 100644 index 0000000..3e44674 --- /dev/null +++ b/src/handlers/event/eta.rs @@ -0,0 +1,18 @@ +use crate::{consts, utils}; + +use color_eyre::eyre::Result; +use poise::serenity_prelude::{Context, Message}; + +pub async fn handle_eta(ctx: &Context, message: &Message) -> Result<()> { + if !message.content.contains(" eta ") { + return Ok(()); + } + + let response = format!( + "{} <:pofat:1031701005559144458>", + utils::random_choice(consts::ETA_MESSAGES)? + ); + + message.reply(ctx, response).await?; + Ok(()) +} diff --git a/src/handlers/event/mod.rs b/src/handlers/event/mod.rs index 530d145..bf60a5f 100644 --- a/src/handlers/event/mod.rs +++ b/src/handlers/event/mod.rs @@ -1,11 +1,13 @@ use crate::Data; use color_eyre::eyre::{Report, Result}; -use poise::serenity_prelude as serenity; +use poise::serenity_prelude::Context; use poise::{Event, FrameworkContext}; +mod eta; + pub async fn handle( - ctx: &serenity::Context, + ctx: &Context, event: &Event<'_>, framework: FrameworkContext<'_, Data, Report>, data: &Data, @@ -15,7 +17,7 @@ pub async fn handle( log::info!("Logged in as {}!", data_about_bot.user.name) } - Event::Message { new_message } => {} + Event::Message { new_message } => eta::handle_eta(ctx, new_message).await?, _ => {} } From a8eb4a212a5639e05c272196fcd9365c939a2f39 Mon Sep 17 00:00:00 2001 From: seth Date: Sun, 3 Dec 2023 18:51:59 -0500 Subject: [PATCH 07/90] chore: deprecate modrinth command Signed-off-by: seth --- src/commands/general/mod.rs | 2 -- src/commands/general/modrinth.rs | 8 -------- src/commands/mod.rs | 1 - 3 files changed, 11 deletions(-) delete mode 100644 src/commands/general/modrinth.rs diff --git a/src/commands/general/mod.rs b/src/commands/general/mod.rs index 5fa1d95..c322c73 100644 --- a/src/commands/general/mod.rs +++ b/src/commands/general/mod.rs @@ -1,13 +1,11 @@ mod joke; mod members; -mod modrinth; mod rory; mod say; mod stars; pub use joke::joke; pub use members::members; -pub use modrinth::modrinth; pub use rory::rory; pub use say::say; pub use stars::stars; diff --git a/src/commands/general/modrinth.rs b/src/commands/general/modrinth.rs deleted file mode 100644 index 40879e8..0000000 --- a/src/commands/general/modrinth.rs +++ /dev/null @@ -1,8 +0,0 @@ -use crate::Context; - -use color_eyre::eyre::Result; - -#[poise::command(slash_command, prefix_command)] -pub async fn modrinth(ctx: Context<'_>) -> Result<()> { - todo!() -} diff --git a/src/commands/mod.rs b/src/commands/mod.rs index bef9edd..67e2b1e 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -10,7 +10,6 @@ pub fn to_global_commands() -> Vec> { vec![ general::joke(), general::members(), - general::modrinth(), general::rory(), general::say(), general::stars(), From 30cc4a6220c02e5bbb423a9723dd68528fb5a54c Mon Sep 17 00:00:00 2001 From: seth Date: Mon, 4 Dec 2023 05:11:54 -0500 Subject: [PATCH 08/90] feat: reintroduce tag command Signed-off-by: seth --- Cargo.lock | 37 +++++++++++ Cargo.toml | 6 ++ build.rs | 121 ++++++++++++++++++++++++++++++++++++ src/commands/general/mod.rs | 2 + src/commands/general/tag.rs | 57 +++++++++++++++++ src/commands/mod.rs | 1 + src/consts.rs | 13 ++-- src/handlers/event/mod.rs | 4 +- src/main.rs | 1 + src/tags.rs | 21 +++++++ tags/FTB.md | 1 - tags/log.md | 2 +- 12 files changed, 256 insertions(+), 10 deletions(-) create mode 100644 build.rs create mode 100644 src/commands/general/tag.rs create mode 100644 src/tags.rs diff --git a/Cargo.lock b/Cargo.lock index 5e28afd..3003383 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -548,6 +548,18 @@ version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" +[[package]] +name = "gray_matter" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cf2fb99fac0b821a4e61c61abff076324bb0e5c3b4a83815bbc3518a38971ad" +dependencies = [ + "serde", + "serde_json", + "toml", + "yaml-rust", +] + [[package]] name = "h2" version = "0.3.22" @@ -807,6 +819,12 @@ version = "0.2.150" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" +[[package]] +name = "linked-hash-map" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" + [[package]] name = "linux-raw-sys" version = "0.4.12" @@ -1209,6 +1227,7 @@ dependencies = [ "color-eyre", "dotenvy", "env_logger", + "gray_matter", "log", "octocrab", "once_cell", @@ -1885,6 +1904,15 @@ dependencies = [ "tracing", ] +[[package]] +name = "toml" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +dependencies = [ + "serde", +] + [[package]] name = "tower" version = "0.4.13" @@ -2411,6 +2439,15 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "yaml-rust" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" +dependencies = [ + "linked-hash-map", +] + [[package]] name = "zeroize" version = "1.7.0" diff --git a/Cargo.toml b/Cargo.toml index 1854b90..a7d48aa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,8 +5,14 @@ edition = "2021" repository = "https://github.com/PrismLauncher/refraction" license = "GPL-3.0-or-later" readme = "README.md" +build = "build.rs" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[build-dependencies] +gray_matter = "0.2.6" +poise = "0.5.7" +serde = "1.0.193" +serde_json = "1.0.108" [dependencies] color-eyre = "0.6.2" diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..647d720 --- /dev/null +++ b/build.rs @@ -0,0 +1,121 @@ +use std::collections::HashMap; +use std::path::Path; +use std::{env, fs}; + +use gray_matter::{engine, Matter}; + +include!("src/tags.rs"); + +/// generate the ChoiceParameter enum and tag data we will use in the `tag` command +#[allow(dead_code)] +fn main() { + let out_dir = env::var_os("OUT_DIR").unwrap(); + let generated = Path::new(&out_dir).join("generated.rs"); + + let tag_files: Vec = fs::read_dir(TAG_DIR) + .unwrap() + .map(|f| f.unwrap().file_name().to_string_lossy().to_string()) + .collect(); + + let tags: Vec = tag_files + .clone() + .into_iter() + .map(|name| { + let file_name = format!("{TAG_DIR}/{name}"); + let content = fs::read_to_string(&file_name).unwrap(); + + let matter = Matter::::new(); + let frontmatter: TagFrontmatter = matter + .parse(&content) + .data + .unwrap() + .deserialize() + .unwrap_or_else(|e| { + // actually handling the error since this is the most likely thing to fail -getchoo + panic!( + "Failed to parse file {file_name}! Here's what it looked like:\n{content}\n\nReported Error:\n{e}\n", + ) + }); + + Tag { + content, + file_name: name, + frontmatter, + } + }) + .collect(); + + let aliases: HashMap> = tags + .iter() + .filter_map(|t| { + t.frontmatter + .aliases + .clone() + .map(|aliases| (t.file_name.clone(), aliases)) + }) + .collect(); + + let formatted_names: Vec = tags + .iter() + .flat_map(|t| { + let mut res = Vec::from([t.file_name.replace(".md", "").replace('-', "_")]); + if let Some(tag_aliases) = aliases.get(&t.file_name) { + res.append(&mut tag_aliases.clone()) + } + + res + }) + .collect(); + + let tag_choice = format!( + r#" + #[allow(non_camel_case_types, clippy::upper_case_acronyms)] + #[derive(Clone, Debug, poise::ChoiceParameter)] + pub enum TagChoice {{ + {} + }}"#, + formatted_names.join(",\n") + ); + + let to_str = format!( + r#" + impl TagChoice {{ + fn as_str(&self) -> &str {{ + match &self {{ + {} + }} + }} + }} + "#, + formatted_names + .iter() + .map(|n| { + let file_name = n.replace('_', "-") + ".md"; + + // assume this is an alias if we can't match the file name + let name = if tag_files.contains(&file_name) { + file_name + } else { + aliases + .iter() + .find(|a| a.1.contains(n)) + .unwrap() + .0 + .to_string() + }; + + format!("Self::{n} => \"{name}\",") + }) + .collect::>() + .join("\n") + ); + + let contents = Vec::from([tag_choice, to_str]).join("\n\n"); + + fs::write(generated, contents).unwrap(); + println!( + "cargo:rustc-env=TAGS={}", + // make sure we can deserialize with env! at runtime + serde_json::to_string(&tags).unwrap() + ); +} diff --git a/src/commands/general/mod.rs b/src/commands/general/mod.rs index c322c73..f9385d4 100644 --- a/src/commands/general/mod.rs +++ b/src/commands/general/mod.rs @@ -3,9 +3,11 @@ mod members; mod rory; mod say; mod stars; +mod tag; pub use joke::joke; pub use members::members; pub use rory::rory; pub use say::say; pub use stars::stars; +pub use tag::tag; diff --git a/src/commands/general/tag.rs b/src/commands/general/tag.rs new file mode 100644 index 0000000..d312bb5 --- /dev/null +++ b/src/commands/general/tag.rs @@ -0,0 +1,57 @@ +#![allow(non_camel_case_types, clippy::upper_case_acronyms)] +use crate::tags::Tag; +use crate::{consts, Context}; +use std::env; + +use color_eyre::eyre::{eyre, Result}; +use once_cell::sync::Lazy; +use poise::serenity_prelude::{Color, User}; + +include!(concat!(env!("OUT_DIR"), "/generated.rs")); +static TAGS: Lazy> = Lazy::new(|| serde_json::from_str(env!("TAGS")).unwrap()); + +/// Send a tag +#[poise::command(slash_command)] +pub async fn tag( + ctx: Context<'_>, + #[description = "the copypasta you want to send"] name: TagChoice, + user: Option, +) -> Result<()> { + let tag_file = name.as_str(); + let tag = TAGS + .iter() + .find(|t| t.file_name == tag_file) + .ok_or_else(|| eyre!("Tried to get non-existent tag: {tag_file}"))?; + + let frontmatter = &tag.frontmatter; + + ctx.send(|m| { + if let Some(user) = user { + m.content(format!("<@{}>", user.id)); + } + + m.embed(|e| { + if let Some(color) = &frontmatter.color { + let color = *consts::COLORS + .get(color.as_str()) + .unwrap_or(&Color::default()); + e.color(color); + } + + if let Some(image) = &frontmatter.image { + e.image(image); + } + + if let Some(fields) = &frontmatter.fields { + for field in fields { + e.field(&field.name, &field.value, field.inline); + } + } + + e + }) + }) + .await?; + + Ok(()) +} diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 67e2b1e..cbb13c1 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -13,6 +13,7 @@ pub fn to_global_commands() -> Vec> { general::rory(), general::say(), general::stars(), + general::tag(), moderation::ban_user(), moderation::kick_user(), ] diff --git a/src/consts.rs b/src/consts.rs index 63d5e12..1c305fe 100644 --- a/src/consts.rs +++ b/src/consts.rs @@ -1,14 +1,15 @@ use std::collections::HashMap; use once_cell::sync::Lazy; +use poise::serenity_prelude::Color; -pub static COLORS: Lazy> = Lazy::new(|| { +pub static COLORS: Lazy> = Lazy::new(|| { HashMap::from([ - ("red", (239, 68, 68)), - ("green", (34, 197, 94)), - ("blue", (96, 165, 250)), - ("yellow", (253, 224, 71)), - ("orange", (251, 146, 60)), + ("red", Color::from((239, 68, 68))), + ("green", Color::from((34, 197, 94))), + ("blue", Color::from((96, 165, 250))), + ("yellow", Color::from((253, 224, 71))), + ("orange", Color::from((251, 146, 60))), ]) }); diff --git a/src/handlers/event/mod.rs b/src/handlers/event/mod.rs index bf60a5f..7d80556 100644 --- a/src/handlers/event/mod.rs +++ b/src/handlers/event/mod.rs @@ -9,8 +9,8 @@ mod eta; pub async fn handle( ctx: &Context, event: &Event<'_>, - framework: FrameworkContext<'_, Data, Report>, - data: &Data, + _framework: FrameworkContext<'_, Data, Report>, + _data: &Data, ) -> Result<()> { match event { Event::Ready { data_about_bot } => { diff --git a/src/main.rs b/src/main.rs index 6d5b31b..b26863d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,6 +12,7 @@ mod commands; mod config; mod consts; mod handlers; +mod tags; mod utils; type Context<'a> = poise::Context<'a, Data, Report>; diff --git a/src/tags.rs b/src/tags.rs new file mode 100644 index 0000000..a174fab --- /dev/null +++ b/src/tags.rs @@ -0,0 +1,21 @@ +use poise::serenity_prelude::EmbedField; +use serde::{Deserialize, Serialize}; + +#[allow(dead_code)] +pub const TAG_DIR: &str = "tags"; + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct TagFrontmatter { + pub title: String, + pub aliases: Option>, + pub color: Option, + pub image: Option, + pub fields: Option>, +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct Tag { + pub content: String, + pub file_name: String, + pub frontmatter: TagFrontmatter, +} diff --git a/tags/FTB.md b/tags/FTB.md index 4e49dbc..f78a016 100644 --- a/tags/FTB.md +++ b/tags/FTB.md @@ -1,7 +1,6 @@ --- title: Can I install/download FTB packs directly from Prism? color: orange -aliases: ['FTB'] --- You cannot download FTB packs directly from Prism Launcher. diff --git a/tags/log.md b/tags/log.md index c0a9756..8299879 100644 --- a/tags/log.md +++ b/tags/log.md @@ -1,7 +1,7 @@ --- title: Upload Logs color: orange -aliases: ['sendlog', 'logs', '🪵'] +aliases: ['sendlog', 'logs'] image: https://cdn.discordapp.com/attachments/1031694870756204566/1156971972232740874/image.png --- From 368b5e0cb02fed1a34ddf1af6736abe8245c74df Mon Sep 17 00:00:00 2001 From: seth Date: Mon, 4 Dec 2023 05:40:51 -0500 Subject: [PATCH 09/90] =?UTF-8?q?feat:=20delete=20messages=20on=20?= =?UTF-8?q?=E2=9D=8C=20again?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: seth --- src/handlers/event/delete.rs | 25 +++++++++++++++++++++++++ src/handlers/event/eta.rs | 2 +- src/handlers/event/mod.rs | 13 ++++++++++++- 3 files changed, 38 insertions(+), 2 deletions(-) create mode 100644 src/handlers/event/delete.rs diff --git a/src/handlers/event/delete.rs b/src/handlers/event/delete.rs new file mode 100644 index 0000000..dddcc9a --- /dev/null +++ b/src/handlers/event/delete.rs @@ -0,0 +1,25 @@ +use color_eyre::eyre::{Context as _, Result}; +use poise::serenity_prelude::{Context, InteractionType, Reaction}; + +pub async fn handle(ctx: &Context, reaction: &Reaction) -> Result<()> { + let user = reaction + .user(ctx) + .await + .wrap_err_with(|| "Couldn't fetch user from reaction!")?; + + let message = reaction + .message(ctx) + .await + .wrap_err_with(|| "Couldn't fetch message from reaction!")?; + + if let Some(interaction) = &message.interaction { + if interaction.kind == InteractionType::ApplicationCommand + && interaction.user == user + && reaction.emoji.unicode_eq("❌") + { + message.delete(ctx).await?; + } + } + + Ok(()) +} diff --git a/src/handlers/event/eta.rs b/src/handlers/event/eta.rs index 3e44674..a5ba3b4 100644 --- a/src/handlers/event/eta.rs +++ b/src/handlers/event/eta.rs @@ -3,7 +3,7 @@ use crate::{consts, utils}; use color_eyre::eyre::Result; use poise::serenity_prelude::{Context, Message}; -pub async fn handle_eta(ctx: &Context, message: &Message) -> Result<()> { +pub async fn handle(ctx: &Context, message: &Message) -> Result<()> { if !message.content.contains(" eta ") { return Ok(()); } diff --git a/src/handlers/event/mod.rs b/src/handlers/event/mod.rs index 7d80556..e5379ae 100644 --- a/src/handlers/event/mod.rs +++ b/src/handlers/event/mod.rs @@ -4,6 +4,7 @@ use color_eyre::eyre::{Report, Result}; use poise::serenity_prelude::Context; use poise::{Event, FrameworkContext}; +mod delete; mod eta; pub async fn handle( @@ -17,7 +18,17 @@ pub async fn handle( log::info!("Logged in as {}!", data_about_bot.user.name) } - Event::Message { new_message } => eta::handle_eta(ctx, new_message).await?, + Event::Message { new_message } => { + // ignore new messages from bots + // NOTE: the webhook_id check allows us to still respond to PK users + if new_message.author.bot && new_message.webhook_id.is_none() { + return Ok(()); + } + + eta::handle(ctx, new_message).await? + } + + Event::ReactionAdd { add_reaction } => delete::handle(ctx, add_reaction).await?, _ => {} } From 604a81fb449e1f64d20a58c3f14f1e2b6798bb2f Mon Sep 17 00:00:00 2001 From: seth Date: Mon, 4 Dec 2023 06:04:38 -0500 Subject: [PATCH 10/90] feat: reintroduce support onboarding Signed-off-by: seth --- src/handlers/event/mod.rs | 3 ++ src/handlers/event/support_onboard.rs | 43 +++++++++++++++++++++++++++ 2 files changed, 46 insertions(+) create mode 100644 src/handlers/event/support_onboard.rs diff --git a/src/handlers/event/mod.rs b/src/handlers/event/mod.rs index e5379ae..ad63d66 100644 --- a/src/handlers/event/mod.rs +++ b/src/handlers/event/mod.rs @@ -6,6 +6,7 @@ use poise::{Event, FrameworkContext}; mod delete; mod eta; +mod support_onboard; pub async fn handle( ctx: &Context, @@ -30,6 +31,8 @@ pub async fn handle( Event::ReactionAdd { add_reaction } => delete::handle(ctx, add_reaction).await?, + Event::ThreadCreate { thread } => support_onboard::handle(ctx, thread).await?, + _ => {} } diff --git a/src/handlers/event/support_onboard.rs b/src/handlers/event/support_onboard.rs new file mode 100644 index 0000000..9b6bc16 --- /dev/null +++ b/src/handlers/event/support_onboard.rs @@ -0,0 +1,43 @@ +use color_eyre::eyre::{eyre, Result}; +use log::*; +use poise::serenity_prelude::{ChannelType, Context, GuildChannel}; + +pub async fn handle(ctx: &Context, thread: &GuildChannel) -> Result<()> { + if thread.kind != ChannelType::PublicThread { + return Ok(()); + } + + let parent_id = thread + .parent_id + .ok_or_else(|| eyre!("Couldn't get parent ID from thread {}!", thread.name))?; + + let parent_channel = ctx + .cache + .guild_channel(parent_id) + .ok_or_else(|| eyre!("Couldn't get GuildChannel {}!", parent_id))?; + + if parent_channel.name != "support" { + debug!("Not posting onboarding message to threads outside of support"); + return Ok(()); + } + + let owner = thread + .owner_id + .ok_or_else(|| eyre!("Couldn't get owner of thread!"))?; + + let msg = format!( + "<@{}> We've received your support ticket! {} {}", + owner, + "Please upload your logs and post the link here if possible (run `tag log` to find out how).", + "Please don't ping people for support questions, unless you have their permission." + ); + + thread + .send_message(ctx, |m| { + m.content(msg) + .allowed_mentions(|am| am.replied_user(true).users(Vec::from([owner]))) + }) + .await?; + + Ok(()) +} From 640409f2e2c6ca90ac594cb563b6616b6de3b9a8 Mon Sep 17 00:00:00 2001 From: seth Date: Mon, 4 Dec 2023 08:22:38 -0500 Subject: [PATCH 11/90] feat: reintroduce message link embeds Signed-off-by: seth --- Cargo.lock | 1 + Cargo.toml | 1 + src/handlers/event/expand_link.rs | 28 +++++++ src/handlers/event/mod.rs | 4 +- src/utils/mod.rs | 96 +---------------------- src/utils/resolve_message.rs | 124 ++++++++++++++++++++++++++++++ 6 files changed, 160 insertions(+), 94 deletions(-) create mode 100644 src/handlers/event/expand_link.rs create mode 100644 src/utils/resolve_message.rs diff --git a/Cargo.lock b/Cargo.lock index 3003383..3d7601a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1235,6 +1235,7 @@ dependencies = [ "rand", "redis", "redis-macros", + "regex", "reqwest", "serde", "serde_json", diff --git a/Cargo.toml b/Cargo.toml index a7d48aa..d38897d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,6 +25,7 @@ once_cell = "1.18.0" rand = "0.8.5" redis = { version = "0.23.3", features = ["tokio-comp", "tokio-rustls-comp"] } redis-macros = "0.2.1" +regex = "1.10.2" reqwest = { version = "0.11.22", default-features = false, features = [ "rustls-tls", "json", diff --git a/src/handlers/event/expand_link.rs b/src/handlers/event/expand_link.rs new file mode 100644 index 0000000..95065e1 --- /dev/null +++ b/src/handlers/event/expand_link.rs @@ -0,0 +1,28 @@ +use color_eyre::eyre::{eyre, Context as _, Result}; +use poise::serenity_prelude::{Context, Message}; + +use crate::utils; + +pub async fn handle(ctx: &Context, msg: &Message) -> Result<()> { + let embeds = utils::resolve_message(ctx, msg).await?; + + // TOOD getchoo: actually reply to user + // ...not sure why Message doesn't give me a builder in reply() or equivalents + let our_channel = msg + .channel(ctx) + .await + .wrap_err_with(|| "Couldn't get channel from message!")? + .guild() + .ok_or_else(|| eyre!("Couldn't convert to GuildChannel!"))?; + + if !embeds.is_empty() { + our_channel + .send_message(ctx, |m| { + m.set_embeds(embeds) + .allowed_mentions(|am| am.replied_user(false)) + }) + .await?; + } + + Ok(()) +} diff --git a/src/handlers/event/mod.rs b/src/handlers/event/mod.rs index ad63d66..b953eae 100644 --- a/src/handlers/event/mod.rs +++ b/src/handlers/event/mod.rs @@ -6,6 +6,7 @@ use poise::{Event, FrameworkContext}; mod delete; mod eta; +mod expand_link; mod support_onboard; pub async fn handle( @@ -26,7 +27,8 @@ pub async fn handle( return Ok(()); } - eta::handle(ctx, new_message).await? + eta::handle(ctx, new_message).await?; + expand_link::handle(ctx, new_message).await?; } Event::ReactionAdd { add_reaction } => delete::handle(ctx, add_reaction).await?, diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 800865d..700ce3d 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -1,13 +1,11 @@ -use crate::Context; - use color_eyre::eyre::{eyre, Result}; -use poise::serenity_prelude as serenity; use rand::seq::SliceRandom; -use serenity::{CreateEmbed, Message}; -use url::Url; #[macro_use] mod macros; +mod resolve_message; + +pub use resolve_message::resolve as resolve_message; /* * chooses a random element from an array @@ -20,91 +18,3 @@ pub fn random_choice(arr: [&str; N]) -> Result { Ok((*resp).to_string()) } - -// waiting for `round_char_boundary` to stabilize -pub fn floor_char_boundary(s: &str, index: usize) -> usize { - if index >= s.len() { - s.len() - } else { - let lower_bound = index.saturating_sub(3); - let new_index = s.as_bytes()[lower_bound..=index] - .iter() - .rposition(|&b| (b as i8) >= -0x40); // b.is_utf8_char_boundary - - // Can be made unsafe but whatever - lower_bound + new_index.unwrap() - } -} - -pub async fn send_url_as_embed(ctx: Context<'_>, url: String) -> Result<()> { - let parsed = Url::parse(&url)?; - - let title = parsed - .path_segments() - .unwrap() - .last() - .unwrap_or("image") - .replace("%20", " "); - - ctx.send(|c| c.embed(|e| e.title(title).image(&url).url(url))) - .await?; - - Ok(()) -} - -pub async fn resolve_message_to_embed(ctx: &serenity::Context, msg: &Message) -> CreateEmbed { - let truncation_point = floor_char_boundary(&msg.content, 700); - let truncated_content = if msg.content.len() <= truncation_point { - msg.content.to_string() - } else { - format!("{}...", &msg.content[..truncation_point]) - }; - - let color = msg - .member(ctx) - .await - .ok() - .and_then(|m| m.highest_role_info(&ctx.cache)) - .and_then(|(role, _)| role.to_role_cached(&ctx.cache)) - .map(|role| role.colour); - - let attached_image = msg - .attachments - .iter() - .filter(|a| { - a.content_type - .as_ref() - .filter(|ct| ct.contains("image/")) - .is_some() - }) - .map(|a| &a.url) - .next(); - - let attachments_len = msg.attachments.len(); - - let mut embed = msg - .embeds - .first() - .map(|embed| CreateEmbed::from(embed.clone())) - .unwrap_or_default(); - - embed.author(|author| author.name(&msg.author.name).icon_url(&msg.author.face())); - - if let Some(color) = color { - embed.color(color); - } - - if let Some(attachment) = attached_image { - embed.image(attachment); - } - - if attachments_len > 1 { - embed.footer(|footer| { - // yes it will say '1 attachments' no i do not care - footer.text(format!("{} attachments", attachments_len)) - }); - } - - embed.description(truncated_content); - embed -} diff --git a/src/utils/resolve_message.rs b/src/utils/resolve_message.rs new file mode 100644 index 0000000..cbf7a82 --- /dev/null +++ b/src/utils/resolve_message.rs @@ -0,0 +1,124 @@ +use color_eyre::eyre::{eyre, Context as _, Result}; +use log::*; +use once_cell::sync::Lazy; +use poise::serenity_prelude::{ChannelType, Colour, Context, CreateEmbed, Message}; +use regex::Regex; + +static MESSAGE_PATTERN: Lazy = Lazy::new(|| { + Regex::new(r"/(https?:\/\/)?(?:canary\.|ptb\.)?discord(?:app)?\.com\/channels\/(?\d+)\/(?\d+)\/(?\d+)/g;").unwrap() +}); + +pub fn find_first_image(msg: &Message) -> Option { + msg.attachments + .iter() + .find(|a| { + a.content_type + .as_ref() + .unwrap_or(&"".to_string()) + .starts_with("image/") + }) + .map(|res| res.url.clone()) +} + +pub async fn resolve(ctx: &Context, msg: &Message) -> Result> { + let matches = MESSAGE_PATTERN.captures_iter(&msg.content); + let mut embeds: Vec = vec![]; + + for captured in matches.take(3) { + // don't leak messages from other servers + if let Some(server_id) = captured.get(0) { + let other_server: u64 = server_id.as_str().parse().unwrap_or_default(); + let current_id = msg.guild_id.unwrap_or_default(); + + if &other_server != current_id.as_u64() { + debug!("Not resolving message of other guild."); + continue; + } + } else { + warn!("Couldn't find server_id from Discord link! Not resolving message to be safe"); + continue; + } + + if let Some(channel_id) = captured.get(1) { + let parsed: u64 = channel_id.as_str().parse().unwrap_or_default(); + let req_channel = ctx + .cache + .channel(parsed) + .ok_or_else(|| eyre!("Couldn't get channel_id from Discord regex!"))? + .guild() + .ok_or_else(|| { + eyre!("Couldn't convert to GuildChannel from channel_id {parsed}!") + })?; + + if !req_channel.is_text_based() { + debug!("Not resolving message is non-text-based channel."); + continue; + } + + if req_channel.kind == ChannelType::PrivateThread { + if let Some(id) = req_channel.parent_id { + let parent = ctx.cache.guild_channel(id).ok_or_else(|| { + eyre!("Couldn't get parent channel {id} for thread {req_channel}!") + })?; + let parent_members = parent.members(ctx).await.unwrap_or_default(); + + if !parent_members.iter().any(|m| m.user.id == msg.author.id) { + debug!("Not resolving message for user not a part of a private thread."); + continue; + } + } + } else if req_channel + .members(ctx) + .await? + .iter() + .any(|m| m.user.id == msg.author.id) + { + debug!("Not resolving for message for user not a part of a channel"); + continue; + } + + let message_id: u64 = captured + .get(2) + .ok_or_else(|| eyre!("Couldn't get message_id from Discord regex!"))? + .as_str() + .parse() + .wrap_err_with(|| { + eyre!("Couldn't parse message_id from Discord regex as a MessageId!") + })?; + + let original_message = req_channel.message(ctx, message_id).await?; + let mut embed = CreateEmbed::default(); + embed + .author(|a| { + a.name(original_message.author.tag()) + .icon_url(original_message.author.default_avatar_url()) + }) + .color(Colour::BLITZ_BLUE) + .timestamp(original_message.timestamp) + .footer(|f| f.text(format!("#{}", req_channel.name))) + .description(format!( + "{}\n\n[Jump to original message]({})", + original_message.content, + original_message.link() + )); + + if !original_message.attachments.is_empty() { + embed.fields(original_message.attachments.iter().map(|a| { + ( + "Attachments".to_string(), + format!("[{}]({})", a.filename, a.url), + false, + ) + })); + + if let Some(image) = find_first_image(msg) { + embed.image(image); + } + } + + embeds.push(embed); + } + } + + Ok(embeds) +} From 4bf3136364fe093c63bb9d2a5bf8cfa9c4136b37 Mon Sep 17 00:00:00 2001 From: seth Date: Mon, 4 Dec 2023 08:29:45 -0500 Subject: [PATCH 12/90] fix: don't filter out tags in nix build Signed-off-by: seth --- nix/derivation.nix | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/nix/derivation.nix b/nix/derivation.nix index f52b5a8..5f0ebee 100644 --- a/nix/derivation.nix +++ b/nix/derivation.nix @@ -11,14 +11,16 @@ filter = path: type: let path' = toString path; base = baseNameOf path'; + parent = baseNameOf (dirOf path'); dirBlocklist = ["nix"]; matches = lib.any (suffix: lib.hasSuffix suffix base) [".rs"]; isCargo = base == "Cargo.lock" || base == "Cargo.toml"; + isTag = parent == "tags"; isAllowedDir = !(builtins.elem base dirBlocklist); in - (type == "directory" && isAllowedDir) || matches || isCargo; + (type == "directory" && isAllowedDir) || matches || isCargo || isTag; filterSource = src: lib.cleanSourceWith { From 1c168bd8ba3c628f83d6d12f843cae734745d218 Mon Sep 17 00:00:00 2001 From: seth Date: Mon, 4 Dec 2023 08:48:20 -0500 Subject: [PATCH 13/90] fix: use regex for eta again Signed-off-by: seth --- src/handlers/event/eta.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/handlers/event/eta.rs b/src/handlers/event/eta.rs index a5ba3b4..2eb8ae2 100644 --- a/src/handlers/event/eta.rs +++ b/src/handlers/event/eta.rs @@ -1,10 +1,14 @@ use crate::{consts, utils}; use color_eyre::eyre::Result; +use once_cell::sync::Lazy; use poise::serenity_prelude::{Context, Message}; +use regex::Regex; -pub async fn handle(ctx: &Context, message: &Message) -> Result<()> { - if !message.content.contains(" eta ") { +static ETA_REGEX: Lazy = Lazy::new(|| Regex::new(r"\beta\b").unwrap()); + +pub async fn handle(ctx: &Context, msg: &Message) -> Result<()> { + if !ETA_REGEX.is_match(&msg.content) { return Ok(()); } @@ -13,6 +17,6 @@ pub async fn handle(ctx: &Context, message: &Message) -> Result<()> { utils::random_choice(consts::ETA_MESSAGES)? ); - message.reply(ctx, response).await?; + msg.reply(ctx, response).await?; Ok(()) } From 5b16c14b4534cd47a853a56e8e9b85853956dc81 Mon Sep 17 00:00:00 2001 From: seth Date: Mon, 4 Dec 2023 09:09:49 -0500 Subject: [PATCH 14/90] feat: set presence info on ready again Signed-off-by: seth --- src/api/dadjoke.rs | 2 +- src/api/mod.rs | 1 + src/api/prism_meta.rs | 41 +++++++++++++++++++++++++++++++++++++++ src/api/rory.rs | 2 +- src/handlers/event/mod.rs | 14 ++++++++++--- 5 files changed, 55 insertions(+), 5 deletions(-) create mode 100644 src/api/prism_meta.rs diff --git a/src/api/dadjoke.rs b/src/api/dadjoke.rs index 8a9f641..d3ac9c6 100644 --- a/src/api/dadjoke.rs +++ b/src/api/dadjoke.rs @@ -9,7 +9,7 @@ const DADJOKE: &str = "https://icanhazdadjoke.com"; pub async fn get_joke() -> Result { let req = REQWEST_CLIENT.get(DADJOKE).build()?; - info!("making request to {}", req.url()); + info!("Making request to {}", req.url()); let resp = REQWEST_CLIENT.execute(req).await?; let status = resp.status(); diff --git a/src/api/mod.rs b/src/api/mod.rs index 44df020..149b974 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -1,6 +1,7 @@ use once_cell::sync::Lazy; pub mod dadjoke; +pub mod prism_meta; pub mod rory; pub static USER_AGENT: Lazy = Lazy::new(|| { diff --git a/src/api/prism_meta.rs b/src/api/prism_meta.rs new file mode 100644 index 0000000..95dacaa --- /dev/null +++ b/src/api/prism_meta.rs @@ -0,0 +1,41 @@ +use crate::api::REQWEST_CLIENT; + +use color_eyre::eyre::{eyre, Result}; +use log::*; +use reqwest::StatusCode; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize)] +pub struct MinecraftPackageJson { + pub format_version: u8, + pub name: String, + pub recommended: Vec, + pub uid: String, +} + +const PRISM_META: &str = "https://meta.prismlauncher.org/v1"; +const MINECRAFT_PACKAGEJSON_ENDPOINT: &str = "/net.minecraft/package.json"; + +pub async fn get_latest_minecraft_version() -> Result { + let req = REQWEST_CLIENT + .get(format!("{PRISM_META}{MINECRAFT_PACKAGEJSON_ENDPOINT}")) + .build()?; + + info!("Making request to {}", req.url()); + let resp = REQWEST_CLIENT.execute(req).await?; + let status = resp.status(); + + if let StatusCode::OK = status { + let data = resp.json::().await?; + let version = data + .recommended + .first() + .ok_or_else(|| eyre!("Couldn't find first recommended version!"))?; + + Ok(version.clone()) + } else { + Err(eyre!( + "Failed to get latest Minecraft version from {PRISM_META}{MINECRAFT_PACKAGEJSON_ENDPOINT} with {status}", + )) + } +} diff --git a/src/api/rory.rs b/src/api/rory.rs index 6fd4bf9..9e56e8d 100644 --- a/src/api/rory.rs +++ b/src/api/rory.rs @@ -27,7 +27,7 @@ pub async fn get_rory(id: Option) -> Result { .get(format!("{RORY}{ENDPOINT}/{target}")) .build()?; - info!("making request to {}", req.url()); + info!("Making request to {}", req.url()); let resp = REQWEST_CLIENT.execute(req).await?; let status = resp.status(); diff --git a/src/handlers/event/mod.rs b/src/handlers/event/mod.rs index b953eae..8db6df5 100644 --- a/src/handlers/event/mod.rs +++ b/src/handlers/event/mod.rs @@ -1,7 +1,8 @@ -use crate::Data; +use crate::{api, Data}; use color_eyre::eyre::{Report, Result}; -use poise::serenity_prelude::Context; +use log::*; +use poise::serenity_prelude::{Activity, Context, OnlineStatus}; use poise::{Event, FrameworkContext}; mod delete; @@ -17,13 +18,20 @@ pub async fn handle( ) -> Result<()> { match event { Event::Ready { data_about_bot } => { - log::info!("Logged in as {}!", data_about_bot.user.name) + info!("Logged in as {}!", data_about_bot.user.name); + + let latest_minecraft_version = api::prism_meta::get_latest_minecraft_version().await?; + let activity = Activity::playing(format!("Minecraft {}", latest_minecraft_version)); + + info!("Setting presence to activity {activity:#?}"); + ctx.set_presence(Some(activity), OnlineStatus::Online).await; } Event::Message { new_message } => { // ignore new messages from bots // NOTE: the webhook_id check allows us to still respond to PK users if new_message.author.bot && new_message.webhook_id.is_none() { + debug!("Ignoring message {} from bot", new_message.id); return Ok(()); } From 542ecf6bc884c5ba3391f5e130505f2abcc41685 Mon Sep 17 00:00:00 2001 From: seth Date: Mon, 4 Dec 2023 09:10:48 -0500 Subject: [PATCH 15/90] chore: add RUST_LOG to .env.example Signed-off-by: seth --- .env.example | 1 + 1 file changed, 1 insertion(+) diff --git a/.env.example b/.env.example index bfffe34..9b080a1 100644 --- a/.env.example +++ b/.env.example @@ -1,2 +1,3 @@ DISCORD_TOKEN= SAY_LOGS_CHANNEL= +RUST_LOG=refraction=info,warn,error From 6e33299af7e674186b95b7699c9056be6d182de8 Mon Sep 17 00:00:00 2001 From: seth Date: Mon, 4 Dec 2023 09:25:45 -0500 Subject: [PATCH 16/90] feat: reintroduce ping command Signed-off-by: seth --- src/commands/general/mod.rs | 2 ++ src/commands/general/ping.rs | 9 +++++++++ src/commands/mod.rs | 1 + 3 files changed, 12 insertions(+) create mode 100644 src/commands/general/ping.rs diff --git a/src/commands/general/mod.rs b/src/commands/general/mod.rs index f9385d4..e514e39 100644 --- a/src/commands/general/mod.rs +++ b/src/commands/general/mod.rs @@ -1,5 +1,6 @@ mod joke; mod members; +mod ping; mod rory; mod say; mod stars; @@ -7,6 +8,7 @@ mod tag; pub use joke::joke; pub use members::members; +pub use ping::ping; pub use rory::rory; pub use say::say; pub use stars::stars; diff --git a/src/commands/general/ping.rs b/src/commands/general/ping.rs new file mode 100644 index 0000000..fc9d697 --- /dev/null +++ b/src/commands/general/ping.rs @@ -0,0 +1,9 @@ +use crate::Context; + +use color_eyre::eyre::Result; + +#[poise::command(slash_command, prefix_command, ephemeral)] +pub async fn ping(ctx: Context<'_>) -> Result<()> { + ctx.reply("Pong!").await?; + Ok(()) +} diff --git a/src/commands/mod.rs b/src/commands/mod.rs index cbb13c1..da96285 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -10,6 +10,7 @@ pub fn to_global_commands() -> Vec> { vec![ general::joke(), general::members(), + general::ping(), general::rory(), general::say(), general::stars(), From 8bad9d963685e8f0fbda7df7acae350ca6159bfc Mon Sep 17 00:00:00 2001 From: seth Date: Mon, 4 Dec 2023 09:31:14 -0500 Subject: [PATCH 17/90] feat: add descriptions to commands & arguments Signed-off-by: seth --- src/commands/general/joke.rs | 1 + src/commands/general/members.rs | 1 + src/commands/general/ping.rs | 1 + src/commands/general/rory.rs | 6 +++++- src/commands/general/say.rs | 10 ++++++++-- src/commands/general/stars.rs | 1 + src/commands/moderation/actions.rs | 4 ++-- 7 files changed, 19 insertions(+), 5 deletions(-) diff --git a/src/commands/general/joke.rs b/src/commands/general/joke.rs index f99ab1b..638db3e 100644 --- a/src/commands/general/joke.rs +++ b/src/commands/general/joke.rs @@ -3,6 +3,7 @@ use crate::Context; use color_eyre::eyre::Result; +/// It's a joke #[poise::command(slash_command, prefix_command)] pub async fn joke(ctx: Context<'_>) -> Result<()> { let joke = dadjoke::get_joke().await?; diff --git a/src/commands/general/members.rs b/src/commands/general/members.rs index 021ffa5..5498dd0 100644 --- a/src/commands/general/members.rs +++ b/src/commands/general/members.rs @@ -2,6 +2,7 @@ use crate::{consts, Context}; use color_eyre::eyre::{eyre, Result}; +/// Returns the number of members in the server #[poise::command(slash_command, prefix_command)] pub async fn members(ctx: Context<'_>) -> Result<()> { let guild = ctx.guild().ok_or_else(|| eyre!("Couldn't fetch guild!"))?; diff --git a/src/commands/general/ping.rs b/src/commands/general/ping.rs index fc9d697..4563af3 100644 --- a/src/commands/general/ping.rs +++ b/src/commands/general/ping.rs @@ -2,6 +2,7 @@ use crate::Context; use color_eyre::eyre::Result; +/// Replies with pong! #[poise::command(slash_command, prefix_command, ephemeral)] pub async fn ping(ctx: Context<'_>) -> Result<()> { ctx.reply("Pong!").await?; diff --git a/src/commands/general/rory.rs b/src/commands/general/rory.rs index 5350e32..ea62d1d 100644 --- a/src/commands/general/rory.rs +++ b/src/commands/general/rory.rs @@ -3,8 +3,12 @@ use crate::Context; use color_eyre::eyre::Result; +/// Gets a Rory photo! #[poise::command(slash_command, prefix_command)] -pub async fn rory(ctx: Context<'_>, id: Option) -> Result<()> { +pub async fn rory( + ctx: Context<'_>, + #[description = "specify a Rory ID"] id: Option, +) -> Result<()> { let resp = get_rory(id).await?; ctx.send(|m| { diff --git a/src/commands/general/say.rs b/src/commands/general/say.rs index a3f87d5..3f61fe4 100644 --- a/src/commands/general/say.rs +++ b/src/commands/general/say.rs @@ -2,8 +2,14 @@ use crate::Context; use color_eyre::eyre::{eyre, Result}; -#[poise::command(slash_command, prefix_command, ephemeral)] -pub async fn say(ctx: Context<'_>, content: String) -> Result<()> { +/// Say something through the bot +#[poise::command( + slash_command, + prefix_command, + ephemeral, + default_member_permissions = "MODERATE_MEMBERS" +)] +pub async fn say(ctx: Context<'_>, #[description = "Just content?"] content: String) -> Result<()> { let guild = ctx.guild().ok_or_else(|| eyre!("Couldn't get guild!"))?; let channel = ctx .guild_channel() diff --git a/src/commands/general/stars.rs b/src/commands/general/stars.rs index 14e4217..fef4fe8 100644 --- a/src/commands/general/stars.rs +++ b/src/commands/general/stars.rs @@ -2,6 +2,7 @@ use crate::{consts::COLORS, Context}; use color_eyre::eyre::{Context as _, Result}; +/// Returns GitHub stargazer count #[poise::command(slash_command, prefix_command)] pub async fn stars(ctx: Context<'_>) -> Result<()> { let prismlauncher = ctx diff --git a/src/commands/moderation/actions.rs b/src/commands/moderation/actions.rs index 38307d1..6f3c541 100644 --- a/src/commands/moderation/actions.rs +++ b/src/commands/moderation/actions.rs @@ -22,7 +22,7 @@ fn create_moderation_embed( |e: &mut CreateEmbed| e.title(title).fields(fields).color(COLORS["red"]) } -// ban a user +/// Ban a user #[poise::command( slash_command, prefix_command, @@ -54,7 +54,7 @@ pub async fn ban_user( Ok(()) } -// kick a user +/// Kick a user #[poise::command( slash_command, prefix_command, From 640690d9bf28955b6d44a7a2c673451be9ef7871 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Mon, 4 Dec 2023 15:54:16 +0000 Subject: [PATCH 18/90] refactor: use unwrap_or_default() for kick/ban reason Signed-off-by: seth --- src/commands/moderation/actions.rs | 25 +++++++++---------------- 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/src/commands/moderation/actions.rs b/src/commands/moderation/actions.rs index 6f3c541..e776b0e 100644 --- a/src/commands/moderation/actions.rs +++ b/src/commands/moderation/actions.rs @@ -7,11 +7,11 @@ fn create_moderation_embed( title: String, user: &User, delete_messages_days: Option, - reason: String, + reason: Option, ) -> impl FnOnce(&mut CreateEmbed) -> &mut CreateEmbed { let fields = [ ("User", format!("{} ({})", user.name, user.id), false), - ("Reason", reason, false), + ("Reason", reason.unwrap_or("None".to_string()), false), ( "Deleted messages", format!("Last {} days", delete_messages_days.unwrap_or(0)), @@ -39,13 +39,9 @@ pub async fn ban_user( .guild() .ok_or_else(|| eyre!("Couldn't get guild from message; Unable to ban!"))?; - let reason = reason.unwrap_or("n/a".to_string()); - - if reason != "n/a" { - guild.ban_with_reason(ctx, &user, days, &reason).await?; - } else { - guild.ban(ctx, &user, days).await?; - } + guild + .ban_with_reason(ctx, &user, days, reason.clone().unwrap_or_default()) + .await?; let embed = create_moderation_embed("User banned!".to_string(), &user, Some(days), reason); @@ -65,15 +61,12 @@ pub async fn kick_user(ctx: Context<'_>, user: User, reason: Option) -> .guild() .ok_or_else(|| eyre!("Couldn't get guild from message; Unable to ban!"))?; - let reason = reason.unwrap_or("n/a".to_string()); - - if reason != "n/a" { - guild.kick_with_reason(ctx, &user, &reason).await?; - } else { - guild.kick(ctx, &user).await?; - } + guild + .kick_with_reason(ctx, &user, &reason.clone().unwrap_or_default()) + .await?; let embed = create_moderation_embed("User kicked!".to_string(), &user, None, reason); + ctx.send(|m| m.embed(embed)).await?; Ok(()) From 95fe62051b8baa362906dda3e0e209fc2dbcd957 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Mon, 4 Dec 2023 19:26:36 +0000 Subject: [PATCH 19/90] chore: remove tag aliases with tag names *and* aliases both being options, we reach the limit for the discord API Signed-off-by: seth --- build.rs | 44 ++++++------------------------------- src/commands/general/tag.rs | 5 ++++- src/tags.rs | 1 - tags/always-offline.md | 1 - tags/binary-search.md | 1 - tags/curseforge.md | 1 - tags/fractureiser.md | 1 - tags/javaforgebugfix.md | 1 - tags/log.md | 1 - tags/macosarmjava.md | 1 - tags/migrate.md | 1 - tags/nightly.md | 1 - tags/optifine.md | 1 - tags/paths.md | 1 - tags/pluralkit.md | 1 - tags/update.md | 1 - tags/vcredist.md | 1 - tags/why.md | 6 ----- tags/whyjava8.md | 1 - 19 files changed, 11 insertions(+), 60 deletions(-) diff --git a/build.rs b/build.rs index 647d720..1426363 100644 --- a/build.rs +++ b/build.rs @@ -1,4 +1,3 @@ -use std::collections::HashMap; use std::path::Path; use std::{env, fs}; @@ -22,11 +21,12 @@ fn main() { .into_iter() .map(|name| { let file_name = format!("{TAG_DIR}/{name}"); - let content = fs::read_to_string(&file_name).unwrap(); + let file_content = fs::read_to_string(&file_name).unwrap(); let matter = Matter::::new(); - let frontmatter: TagFrontmatter = matter - .parse(&content) + let parsed = matter.parse(&file_content); + let content = parsed.content; + let data = parsed .data .unwrap() .deserialize() @@ -40,31 +40,14 @@ fn main() { Tag { content, file_name: name, - frontmatter, + frontmatter: data, } }) .collect(); - let aliases: HashMap> = tags - .iter() - .filter_map(|t| { - t.frontmatter - .aliases - .clone() - .map(|aliases| (t.file_name.clone(), aliases)) - }) - .collect(); - let formatted_names: Vec = tags .iter() - .flat_map(|t| { - let mut res = Vec::from([t.file_name.replace(".md", "").replace('-', "_")]); - if let Some(tag_aliases) = aliases.get(&t.file_name) { - res.append(&mut tag_aliases.clone()) - } - - res - }) + .map(|t| t.file_name.replace(".md", "").replace('-', "_")) .collect(); let tag_choice = format!( @@ -91,20 +74,7 @@ fn main() { .iter() .map(|n| { let file_name = n.replace('_', "-") + ".md"; - - // assume this is an alias if we can't match the file name - let name = if tag_files.contains(&file_name) { - file_name - } else { - aliases - .iter() - .find(|a| a.1.contains(n)) - .unwrap() - .0 - .to_string() - }; - - format!("Self::{n} => \"{name}\",") + format!("Self::{n} => \"{file_name}\",") }) .collect::>() .join("\n") diff --git a/src/commands/general/tag.rs b/src/commands/general/tag.rs index d312bb5..ff1138c 100644 --- a/src/commands/general/tag.rs +++ b/src/commands/general/tag.rs @@ -11,7 +11,7 @@ include!(concat!(env!("OUT_DIR"), "/generated.rs")); static TAGS: Lazy> = Lazy::new(|| serde_json::from_str(env!("TAGS")).unwrap()); /// Send a tag -#[poise::command(slash_command)] +#[poise::command(slash_command, prefix_command)] pub async fn tag( ctx: Context<'_>, #[description = "the copypasta you want to send"] name: TagChoice, @@ -31,6 +31,9 @@ pub async fn tag( } m.embed(|e| { + e.title(&frontmatter.title); + e.description(&tag.content); + if let Some(color) = &frontmatter.color { let color = *consts::COLORS .get(color.as_str()) diff --git a/src/tags.rs b/src/tags.rs index a174fab..dacdd01 100644 --- a/src/tags.rs +++ b/src/tags.rs @@ -7,7 +7,6 @@ pub const TAG_DIR: &str = "tags"; #[derive(Clone, Debug, Deserialize, Serialize)] pub struct TagFrontmatter { pub title: String, - pub aliases: Option>, pub color: Option, pub image: Option, pub fields: Option>, diff --git a/tags/always-offline.md b/tags/always-offline.md index a1a12d3..56b64ea 100644 --- a/tags/always-offline.md +++ b/tags/always-offline.md @@ -1,7 +1,6 @@ --- title: Prism Launcher is always offline? color: orange -aliases: ['reinstall'] --- If you recently updated Prism Launcher on Windows and you are now unable to do anything in Prism Launcher that would require an internet connection (i.e. logging in, downloading mods/modpacks and more), try uninstalling the launcher and then reinstalling it again. diff --git a/tags/binary-search.md b/tags/binary-search.md index 5472343..487a902 100644 --- a/tags/binary-search.md +++ b/tags/binary-search.md @@ -1,7 +1,6 @@ --- title: Binary Search - A method of finding problems with mods color: blue -aliases: ['thanosmethod'] --- The binary search is a way of finding a faulty thing amongst a lot of other things, without having to remove the things one-by-one. This is useful for finding a broken mod among hundreds of mods, without having to spend time testing the mods one-by-one. diff --git a/tags/curseforge.md b/tags/curseforge.md index b2b8e78..cd67b98 100644 --- a/tags/curseforge.md +++ b/tags/curseforge.md @@ -1,7 +1,6 @@ --- title: What's wrong with CurseForge? color: orange -aliases: ['cf', 'curse', 'cursed', 'cursedfrog'] --- CurseForge added a new option to block third party clients like Prism Launcher from accessing mod files, and they started to enforce this option lately. We can't allow you to download those mods directly from CurseForge because of this. However, Prism Launcher offers a workaround to enable the downloading of these mods, by allowing you to download these mods from your browser and automatically importing them into the instance. We highly encourage asking authors that opted out of client downloads to stop doing so. diff --git a/tags/fractureiser.md b/tags/fractureiser.md index 394f667..2a124d7 100644 --- a/tags/fractureiser.md +++ b/tags/fractureiser.md @@ -1,7 +1,6 @@ --- title: Information about Fractureiser color: orange -aliases: ['malware'] --- Starting June 6th, 2023 UTC, there have been reports of malware being distributed through websites such as Curseforge, Bukkit, and possibly others in the form of mods, plugins, and modpacks. According to both [Modrinth](https://twitter.com/modrinth/status/1666853947804463115) and [Curseforge](https://twitter.com/CurseForge/status/1666741580022128641), all infected files have been removed. These services should be safe to use now, however users should still take caution in downloading files, especially from less trustworthy services. diff --git a/tags/javaforgebugfix.md b/tags/javaforgebugfix.md index 9433d79..5b85315 100644 --- a/tags/javaforgebugfix.md +++ b/tags/javaforgebugfix.md @@ -1,7 +1,6 @@ --- title: Forge Bugfix color: yellow -aliases: ['javaforgebug', 'forgebug', 'forgejavabugfix', 'forgejavabug'] image: https://media.discordapp.net/attachments/1040383700845740072/1057840239751729172/Fix.png --- diff --git a/tags/log.md b/tags/log.md index 8299879..94a3cf6 100644 --- a/tags/log.md +++ b/tags/log.md @@ -1,7 +1,6 @@ --- title: Upload Logs color: orange -aliases: ['sendlog', 'logs'] image: https://cdn.discordapp.com/attachments/1031694870756204566/1156971972232740874/image.png --- diff --git a/tags/macosarmjava.md b/tags/macosarmjava.md index a2b5af3..cf3f406 100644 --- a/tags/macosarmjava.md +++ b/tags/macosarmjava.md @@ -1,7 +1,6 @@ --- title: Install arm64 Java on macOS color: yellow -aliases: ['armmacosjava'] --- On macOS arm64, also known as Apple Silicon (M1, M2, etc.), you will need to install the arm64 version of Java. We recommend using builds from Azul, the links for which can be found below: diff --git a/tags/migrate.md b/tags/migrate.md index 4045365..16886f3 100644 --- a/tags/migrate.md +++ b/tags/migrate.md @@ -1,7 +1,6 @@ --- title: Migrating from MultiMC color: orange -aliases: ['migr', 'mmc', 'multimc'] --- https://prismlauncher.org/wiki/getting-started/migrating-multimc/ diff --git a/tags/nightly.md b/tags/nightly.md index 0c83907..475feb6 100644 --- a/tags/nightly.md +++ b/tags/nightly.md @@ -1,7 +1,6 @@ --- title: Where can I download unstable builds of Prism Launcher? color: green -aliases: ['unstable'] --- You can download unstable builds [here](https://nightly.link/PrismLauncher/PrismLauncher/workflows/trigger_builds/develop). diff --git a/tags/optifine.md b/tags/optifine.md index 4d0271e..403e099 100644 --- a/tags/optifine.md +++ b/tags/optifine.md @@ -1,7 +1,6 @@ --- title: OptiFine color: green -aliases: ['of', 'optimize', 'opticrap', 'notfine'] --- OptiFine is known to cause problems when paired with other mods. diff --git a/tags/paths.md b/tags/paths.md index 693db03..6ae5961 100644 --- a/tags/paths.md +++ b/tags/paths.md @@ -1,7 +1,6 @@ --- title: Data directories color: blue -aliases: ['dirs', 'locate'] fields: - name: Portable (Windows / Linux) diff --git a/tags/pluralkit.md b/tags/pluralkit.md index b7f34d5..3334af9 100644 --- a/tags/pluralkit.md +++ b/tags/pluralkit.md @@ -1,7 +1,6 @@ --- title: Why PluralKit? color: blue -aliases: ['pk'] --- Plurality is the existence of multiple self-aware entities inside the same brain. diff --git a/tags/update.md b/tags/update.md index 3d022b9..ec17f2a 100644 --- a/tags/update.md +++ b/tags/update.md @@ -1,7 +1,6 @@ --- title: Does Prism Launcher auto-update? color: blue -aliases: ['updating', 'autoupdate'] --- Windows auto-updating is only supported on 8.0+. For Prism 7.2 or below, you will need to download the installer and run it again in order to update. On 8.0 or newer, click the 'Update' button. You will not lose your instances. diff --git a/tags/vcredist.md b/tags/vcredist.md index 23af9d6..c71ed9c 100644 --- a/tags/vcredist.md +++ b/tags/vcredist.md @@ -1,7 +1,6 @@ --- title: vcredist is required for Prism to run Windows color: pink -aliases: ['msvc'] --- Like most apps on Windows, you have to install vcredist for Prism to run. Depending on what version of Prism you are using, you may need a different version. diff --git a/tags/why.md b/tags/why.md index cc31d2e..77d5cf0 100644 --- a/tags/why.md +++ b/tags/why.md @@ -1,12 +1,6 @@ --- title: But why? color: purple -aliases: - - 'whywasprismlaunchermade' - - 'whywasprismmade' - - 'whywaspolymcmade' - - 'mmcdrama' - - 'devlauncher' --- https://prismlauncher.org/wiki/overview/faq/#why-did-our-community-choose-to-fork diff --git a/tags/whyjava8.md b/tags/whyjava8.md index d069e49..f4eac04 100644 --- a/tags/whyjava8.md +++ b/tags/whyjava8.md @@ -1,7 +1,6 @@ --- title: Why does Prism Launcher ask me to change Java version? color: orange -aliases: ['isjava8ancient', 'whyisprismforcingme'] --- Minecraft versions before 1.17 required Java 8 and have issues with newer Java, while newer versions require Java 17, so you need to change Java version. Some people think Java 8 is very outdated, however, it's actually an LTS, meaning it's still getting updates. From f955cbb9330976e927c51ee024d79389252ba826 Mon Sep 17 00:00:00 2001 From: seth Date: Mon, 4 Dec 2023 19:15:59 -0500 Subject: [PATCH 20/90] feat: reintroduce PK support Signed-off-by: seth --- src/api/mod.rs | 1 + src/api/pluralkit.rs | 37 +++++++++++++++++++++++++++++ src/handlers/event/mod.rs | 13 +++++++++- src/handlers/event/pluralkit.rs | 42 +++++++++++++++++++++++++++++++++ src/main.rs | 10 ++++---- src/storage.rs | 40 +++++++++++++++++++++++++++++++ 6 files changed, 138 insertions(+), 5 deletions(-) create mode 100644 src/api/pluralkit.rs create mode 100644 src/handlers/event/pluralkit.rs create mode 100644 src/storage.rs diff --git a/src/api/mod.rs b/src/api/mod.rs index 149b974..a76d5bc 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -1,6 +1,7 @@ use once_cell::sync::Lazy; pub mod dadjoke; +pub mod pluralkit; pub mod prism_meta; pub mod rory; diff --git a/src/api/pluralkit.rs b/src/api/pluralkit.rs new file mode 100644 index 0000000..fd4dad5 --- /dev/null +++ b/src/api/pluralkit.rs @@ -0,0 +1,37 @@ +use crate::api::REQWEST_CLIENT; + +use color_eyre::eyre::{eyre, Context, Result}; +use log::*; +use poise::serenity_prelude::{MessageId, UserId}; +use reqwest::StatusCode; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct PluralKitMessage { + pub sender: String, +} + +const PLURAL_KIT: &str = "https://api.pluralkit.me/v2"; +const MESSAGES_ENDPOINT: &str = "/messages"; + +pub async fn get_sender(message_id: MessageId) -> Result { + let req = REQWEST_CLIENT + .get(format!("{PLURAL_KIT}{MESSAGES_ENDPOINT}/{message_id}")) + .build()?; + + info!("Making request to {}", req.url()); + let resp = REQWEST_CLIENT.execute(req).await?; + let status = resp.status(); + + if let StatusCode::OK = status { + let data = resp.json::().await?; + let id: u64 = data.sender.parse().wrap_err_with(|| format!("Couldn't parse response from PluralKit as a UserId! Here's the response:\n{data:#?}"))?; + let sender = UserId::from(id); + + Ok(sender) + } else { + Err(eyre!( + "Failed to get PluralKit message information from {PLURAL_KIT}{MESSAGES_ENDPOINT}/{message_id} with {status}", + )) + } +} diff --git a/src/handlers/event/mod.rs b/src/handlers/event/mod.rs index 8db6df5..07a82d0 100644 --- a/src/handlers/event/mod.rs +++ b/src/handlers/event/mod.rs @@ -8,13 +8,14 @@ use poise::{Event, FrameworkContext}; mod delete; mod eta; mod expand_link; +pub mod pluralkit; mod support_onboard; pub async fn handle( ctx: &Context, event: &Event<'_>, _framework: FrameworkContext<'_, Data, Report>, - _data: &Data, + data: &Data, ) -> Result<()> { match event { Event::Ready { data_about_bot } => { @@ -35,6 +36,16 @@ pub async fn handle( return Ok(()); } + // detect PK users first to make sure we don't respond to unproxied messages + pluralkit::handle(ctx, new_message, data).await?; + + if data.storage.is_user_plural(new_message.author.id).await? + && pluralkit::is_message_proxied(new_message).await? + { + debug!("Not replying to unproxied PluralKit message"); + return Ok(()); + } + eta::handle(ctx, new_message).await?; expand_link::handle(ctx, new_message).await?; } diff --git a/src/handlers/event/pluralkit.rs b/src/handlers/event/pluralkit.rs new file mode 100644 index 0000000..b44aff0 --- /dev/null +++ b/src/handlers/event/pluralkit.rs @@ -0,0 +1,42 @@ +use crate::{api, Data}; +use std::time::Duration; + +use color_eyre::eyre::Result; +use log::*; +use poise::serenity_prelude::{Context, Message}; +use tokio::time::sleep; + +const PK_DELAY_SEC: Duration = Duration::from_secs(1000); + +pub async fn is_message_proxied(message: &Message) -> Result { + debug!( + "Waiting on PluralKit API for {} seconds", + PK_DELAY_SEC.as_secs() + ); + sleep(PK_DELAY_SEC).await; + + let proxied = api::pluralkit::get_sender(message.id).await.is_ok(); + + Ok(proxied) +} + +pub async fn handle(_ctx: &Context, msg: &Message, data: &Data) -> Result<()> { + if msg.webhook_id.is_some() { + debug!( + "Message {} has a webhook ID. Checking if it was sent through PluralKit", + msg.id + ); + + debug!( + "Waiting on PluralKit API for {} seconds", + PK_DELAY_SEC.as_secs() + ); + sleep(PK_DELAY_SEC).await; + + if let Ok(sender) = api::pluralkit::get_sender(msg.id).await { + data.storage.store_user_plurality(sender).await?; + } + } + + Ok(()) +} diff --git a/src/main.rs b/src/main.rs index b26863d..a4e5daf 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,12 +6,14 @@ use log::*; use poise::{ serenity_prelude as serenity, EditTracker, Framework, FrameworkOptions, PrefixFrameworkOptions, }; +use storage::Storage; mod api; mod commands; mod config; mod consts; mod handlers; +mod storage; mod tags; mod utils; @@ -19,20 +21,20 @@ type Context<'a> = poise::Context<'a, Data, Report>; #[derive(Clone)] pub struct Data { - config: config::Config, - redis: redis::Client, + config: Config, + storage: Storage, octocrab: Arc, } impl Data { pub fn new() -> Result { let config = Config::new_from_env()?; - let redis = redis::Client::open(config.redis_url.clone())?; + let storage = Storage::new(&config.redis_url)?; let octocrab = octocrab::instance(); Ok(Self { config, - redis, + storage, octocrab, }) } diff --git a/src/storage.rs b/src/storage.rs new file mode 100644 index 0000000..6cec18f --- /dev/null +++ b/src/storage.rs @@ -0,0 +1,40 @@ +use color_eyre::eyre::Result; +use log::*; +use poise::serenity_prelude::UserId; +use redis::{AsyncCommands as _, Client}; + +pub const USER_KEY: &str = "users-v1"; + +#[derive(Clone, Debug)] +pub struct Storage { + client: Client, +} + +impl Storage { + pub fn new(redis_url: &str) -> Result { + let client = Client::open(redis_url)?; + + Ok(Self { client }) + } + + pub async fn store_user_plurality(&self, sender: UserId) -> Result<()> { + let mut con = self.client.get_async_connection().await?; + + info!("Marking {sender} as a PluralKit user"); + let key = format!("{USER_KEY}:{sender}:pk"); + + // Just store some value. We only care about the presence of this key + con.set(&key, 0).await?; + con.expire(key, 7 * 24 * 60 * 60).await?; + + Ok(()) + } + + pub async fn is_user_plural(&self, user_id: UserId) -> Result { + let key = format!("{USER_KEY}:{user_id}:pk"); + let mut con = self.client.get_async_connection().await?; + + let exists: bool = con.exists(key).await?; + Ok(exists) + } +} From 55ccfe4341a0d762d23c9935c90dcf5d063082fe Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Tue, 5 Dec 2023 14:36:18 +0000 Subject: [PATCH 21/90] fix: use camelCase for MinecraftPackageJson --- src/api/prism_meta.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/api/prism_meta.rs b/src/api/prism_meta.rs index 95dacaa..0bfa3fa 100644 --- a/src/api/prism_meta.rs +++ b/src/api/prism_meta.rs @@ -6,6 +6,7 @@ use reqwest::StatusCode; use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] pub struct MinecraftPackageJson { pub format_version: u8, pub name: String, From 174d93544c07b0092cb369142179b4baa369240e Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Tue, 5 Dec 2023 14:49:18 +0000 Subject: [PATCH 22/90] fix: do not --- src/commands/general/say.rs | 3 ++- src/commands/moderation/actions.rs | 6 ++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/commands/general/say.rs b/src/commands/general/say.rs index 3f61fe4..27cd82f 100644 --- a/src/commands/general/say.rs +++ b/src/commands/general/say.rs @@ -7,7 +7,8 @@ use color_eyre::eyre::{eyre, Result}; slash_command, prefix_command, ephemeral, - default_member_permissions = "MODERATE_MEMBERS" + default_member_permissions = "MODERATE_MEMBERS", + required_permissions = "MODERATE_MEMBERS" )] pub async fn say(ctx: Context<'_>, #[description = "Just content?"] content: String) -> Result<()> { let guild = ctx.guild().ok_or_else(|| eyre!("Couldn't get guild!"))?; diff --git a/src/commands/moderation/actions.rs b/src/commands/moderation/actions.rs index e776b0e..89b9904 100644 --- a/src/commands/moderation/actions.rs +++ b/src/commands/moderation/actions.rs @@ -26,7 +26,8 @@ fn create_moderation_embed( #[poise::command( slash_command, prefix_command, - default_member_permissions = "BAN_MEMBERS" + default_member_permissions = "BAN_MEMBERS", + required_permissions = "BAN_MEMBERS" )] pub async fn ban_user( ctx: Context<'_>, @@ -54,7 +55,8 @@ pub async fn ban_user( #[poise::command( slash_command, prefix_command, - default_member_permissions = "KICK_MEMBERS" + default_member_permissions = "KICK_MEMBERS", + required_permissions = "KICK_MEMBERS" )] pub async fn kick_user(ctx: Context<'_>, user: User, reason: Option) -> Result<()> { let guild = ctx From 20e2dbbe46bd70e330f6b503511b748400d1eb0a Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Wed, 6 Dec 2023 17:00:26 +0000 Subject: [PATCH 23/90] refactor moderation; change prefix to `r` --- src/commands/mod.rs | 4 +- src/commands/moderation/actions.rs | 169 ++++++++++++++++++++--------- src/main.rs | 2 +- 3 files changed, 123 insertions(+), 52 deletions(-) diff --git a/src/commands/mod.rs b/src/commands/mod.rs index da96285..dadec27 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -15,7 +15,7 @@ pub fn to_global_commands() -> Vec> { general::say(), general::stars(), general::tag(), - moderation::ban_user(), - moderation::kick_user(), + moderation::ban(), + moderation::kick(), ] } diff --git a/src/commands/moderation/actions.rs b/src/commands/moderation/actions.rs index 89b9904..4a0ed5a 100644 --- a/src/commands/moderation/actions.rs +++ b/src/commands/moderation/actions.rs @@ -1,25 +1,94 @@ use crate::{consts::COLORS, Context}; use color_eyre::eyre::{eyre, Result}; -use poise::serenity_prelude::{CreateEmbed, User}; +use poise::serenity_prelude::{ + futures::TryFutureExt, CreateEmbed, CreateMessage, FutureExt, Guild, Timestamp, User, UserId, +}; -fn create_moderation_embed( - title: String, - user: &User, - delete_messages_days: Option, - reason: Option, -) -> impl FnOnce(&mut CreateEmbed) -> &mut CreateEmbed { - let fields = [ - ("User", format!("{} ({})", user.name, user.id), false), - ("Reason", reason.unwrap_or("None".to_string()), false), - ( - "Deleted messages", - format!("Last {} days", delete_messages_days.unwrap_or(0)), - false, - ), - ]; +struct Action { + reason: String, + data: ActionData, +} - |e: &mut CreateEmbed| e.title(title).fields(fields).color(COLORS["red"]) +enum ActionData { + Kick, + Ban { purge: u8 }, + Timeout { until: Timestamp }, +} + +fn build_dm<'a, 'b>( + message: &'b mut CreateMessage<'a>, + guild: &Guild, + action: &Action, +) -> &'b mut CreateMessage<'a> { + let description = match &action.data { + ActionData::Kick => "kicked from".to_string(), + ActionData::Ban { purge: _ } => "banned from".to_string(), + ActionData::Timeout { until } => { + format!("timed out until in", until.unix_timestamp()) + } + }; + let guild_name = &guild.name; + let reason = &action.reason; + message.content(format!( + "You have been {description} {guild_name}.\nReason: {reason}" + )) +} + +async fn moderate( + ctx: &Context<'_>, + users: &Vec, + action: &Action, + quiet: bool, +) -> Result<()> { + let guild = ctx + .guild() + .ok_or_else(|| eyre!("Couldn't get guild from message!"))?; + let reason = &action.reason; + + let mut count = 0; + + for user in users { + if quiet { + if let Ok(channel) = user.create_dm_channel(ctx.http()).await { + let _ = channel + .send_message(ctx.http(), |message| build_dm(message, &guild, action)) + .await; + } + } + + let success = match action.data { + ActionData::Kick => guild + .kick_with_reason(ctx.http(), user, reason) + .await + .is_ok(), + + ActionData::Ban { purge } => guild + .ban_with_reason(ctx.http(), user, purge, reason) + .await + .is_ok(), + + ActionData::Timeout { until } => guild + .edit_member(ctx.http(), user, |member| { + member.disable_communication_until_datetime(until) + }) + .await + .is_ok(), + }; + if success { + count += 1; + } + } + + let total = users.len(); + if count == total { + ctx.reply("✅ Done!").await?; + } else { + ctx.reply(format!("⚠️ {count}/{total} succeeded!")) + .await?; + } + + Ok(()) } /// Ban a user @@ -27,28 +96,28 @@ fn create_moderation_embed( slash_command, prefix_command, default_member_permissions = "BAN_MEMBERS", - required_permissions = "BAN_MEMBERS" + required_permissions = "BAN_MEMBERS", + aliases("ban") )] -pub async fn ban_user( +pub async fn ban( ctx: Context<'_>, - user: User, - delete_messages_days: Option, + users: Vec, + purge: Option, reason: Option, + quiet: Option, ) -> Result<()> { - let days = delete_messages_days.unwrap_or(1); - let guild = ctx - .guild() - .ok_or_else(|| eyre!("Couldn't get guild from message; Unable to ban!"))?; - - guild - .ban_with_reason(ctx, &user, days, reason.clone().unwrap_or_default()) - .await?; - - let embed = create_moderation_embed("User banned!".to_string(), &user, Some(days), reason); - - ctx.send(|m| m.embed(embed)).await?; - - Ok(()) + moderate( + &ctx, + &users, + &Action { + reason: reason.unwrap_or_default(), + data: ActionData::Ban { + purge: purge.unwrap_or(0), + }, + }, + quiet.unwrap_or(false), + ) + .await } /// Kick a user @@ -58,18 +127,20 @@ pub async fn ban_user( default_member_permissions = "KICK_MEMBERS", required_permissions = "KICK_MEMBERS" )] -pub async fn kick_user(ctx: Context<'_>, user: User, reason: Option) -> Result<()> { - let guild = ctx - .guild() - .ok_or_else(|| eyre!("Couldn't get guild from message; Unable to ban!"))?; - - guild - .kick_with_reason(ctx, &user, &reason.clone().unwrap_or_default()) - .await?; - - let embed = create_moderation_embed("User kicked!".to_string(), &user, None, reason); - - ctx.send(|m| m.embed(embed)).await?; - - Ok(()) +pub async fn kick( + ctx: Context<'_>, + users: Vec, + reason: Option, + quiet: Option, +) -> Result<()> { + moderate( + &ctx, + &users, + &Action { + reason: reason.unwrap_or_default(), + data: ActionData::Kick {}, + }, + quiet.unwrap_or(false), + ) + .await } diff --git a/src/main.rs b/src/main.rs index a4e5daf..533ac89 100644 --- a/src/main.rs +++ b/src/main.rs @@ -62,7 +62,7 @@ async fn main() -> Result<()> { Box::pin(handlers::handle_event(ctx, event, framework, data)) }, prefix_options: PrefixFrameworkOptions { - prefix: Some("!".into()), + prefix: Some("r".into()), edit_tracker: Some(EditTracker::for_timespan(Duration::from_secs(3600))), ..Default::default() }, From ed496f5cdcecccc3ec73bb80d16a094f0c21b4a0 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Thu, 7 Dec 2023 14:40:52 +0000 Subject: [PATCH 24/90] chore: ignore .idea --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index bdd812e..f07ccc4 100644 --- a/.gitignore +++ b/.gitignore @@ -27,3 +27,6 @@ repl-result-out* .DS_Store *.rdb + +# JetBrains +.idea/ \ No newline at end of file From 78c8aa7a187d04e95a63ff200b7b0cb404f23bee Mon Sep 17 00:00:00 2001 From: seth Date: Thu, 7 Dec 2023 05:56:14 -0500 Subject: [PATCH 25/90] refactor: create ModAction struct --- Cargo.lock | 1 + Cargo.toml | 1 + src/commands/mod.rs | 5 +- src/commands/moderation/actions.rs | 337 ++++++++++++++++++----------- src/commands/moderation/mod.rs | 167 +++++++++++++- 5 files changed, 382 insertions(+), 129 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3d7601a..1778c94 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1224,6 +1224,7 @@ dependencies = [ name = "refraction" version = "2.0.0" dependencies = [ + "async-trait", "color-eyre", "dotenvy", "env_logger", diff --git a/Cargo.toml b/Cargo.toml index d38897d..a629f5c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,7 @@ serde = "1.0.193" serde_json = "1.0.108" [dependencies] +async-trait = "0.1.74" color-eyre = "0.6.2" dotenvy = "0.15.7" env_logger = "0.10.0" diff --git a/src/commands/mod.rs b/src/commands/mod.rs index dadec27..10f9681 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -15,7 +15,8 @@ pub fn to_global_commands() -> Vec> { general::say(), general::stars(), general::tag(), - moderation::ban(), - moderation::kick(), + moderation::ban_user(), + moderation::mass_ban(), + moderation::kick_user(), ] } diff --git a/src/commands/moderation/actions.rs b/src/commands/moderation/actions.rs index 4a0ed5a..b7ab8b2 100644 --- a/src/commands/moderation/actions.rs +++ b/src/commands/moderation/actions.rs @@ -1,146 +1,233 @@ use crate::{consts::COLORS, Context}; -use color_eyre::eyre::{eyre, Result}; -use poise::serenity_prelude::{ - futures::TryFutureExt, CreateEmbed, CreateMessage, FutureExt, Guild, Timestamp, User, UserId, -}; +use async_trait::async_trait; +use color_eyre::eyre::{eyre, Context as _, Result}; +use log::*; +use poise::serenity_prelude::{CacheHttp, Http, Member, Timestamp}; -struct Action { - reason: String, - data: ActionData, +type Fields<'a> = Vec<(&'a str, String, bool)>; + +#[async_trait] +pub trait ModActionInfo { + fn to_fields(&self) -> Option; + fn description(&self) -> String; + async fn run_action( + &self, + http: impl CacheHttp + AsRef, + user: &Member, + reason: String, + ) -> Result<()>; } -enum ActionData { - Kick, - Ban { purge: u8 }, - Timeout { until: Timestamp }, +pub struct ModAction +where + T: ModActionInfo, +{ + pub reason: Option, + pub data: T, } -fn build_dm<'a, 'b>( - message: &'b mut CreateMessage<'a>, - guild: &Guild, - action: &Action, -) -> &'b mut CreateMessage<'a> { - let description = match &action.data { - ActionData::Kick => "kicked from".to_string(), - ActionData::Ban { purge: _ } => "banned from".to_string(), - ActionData::Timeout { until } => { - format!("timed out until in", until.unix_timestamp()) - } - }; - let guild_name = &guild.name; - let reason = &action.reason; - message.content(format!( - "You have been {description} {guild_name}.\nReason: {reason}" - )) -} +impl ModAction { + fn get_all_fields(&self) -> Fields { + let mut fields = vec![]; -async fn moderate( - ctx: &Context<'_>, - users: &Vec, - action: &Action, - quiet: bool, -) -> Result<()> { - let guild = ctx - .guild() - .ok_or_else(|| eyre!("Couldn't get guild from message!"))?; - let reason = &action.reason; - - let mut count = 0; - - for user in users { - if quiet { - if let Ok(channel) = user.create_dm_channel(ctx.http()).await { - let _ = channel - .send_message(ctx.http(), |message| build_dm(message, &guild, action)) - .await; - } + if let Some(reason) = self.reason.clone() { + fields.push(("Reason:", reason, false)); } - let success = match action.data { - ActionData::Kick => guild - .kick_with_reason(ctx.http(), user, reason) - .await - .is_ok(), - - ActionData::Ban { purge } => guild - .ban_with_reason(ctx.http(), user, purge, reason) - .await - .is_ok(), - - ActionData::Timeout { until } => guild - .edit_member(ctx.http(), user, |member| { - member.disable_communication_until_datetime(until) - }) - .await - .is_ok(), - }; - if success { - count += 1; + if let Some(mut action_fields) = self.data.to_fields() { + fields.append(&mut action_fields); } + + fields } - let total = users.len(); - if count == total { - ctx.reply("✅ Done!").await?; - } else { - ctx.reply(format!("⚠️ {count}/{total} succeeded!")) + /// internal mod logging + pub async fn log_action(&self, ctx: &Context<'_>) -> Result<()> { + let channel_id = ctx + .data() + .config + .discord + .channels + .say_log_channel_id + .ok_or_else(|| eyre!("Couldn't find say_log_channel_id! Unable to log mod action"))?; + + let channel = ctx + .http() + .get_channel(channel_id.into()) + .await + .wrap_err_with(|| "Couldn't resolve say_log_channel_id as a Channel! Are you sure you sure you used the right one?")?; + + let channel = channel + .guild() + .ok_or_else(|| eyre!("Couldn't resolve say_log_channel_id as a GuildChannel! Are you sure you used the right one?"))?; + + let fields = self.get_all_fields(); + let title = format!("{} user!", self.data.description()); + + channel + .send_message(ctx, |m| { + m.embed(|e| e.title(title).fields(fields).color(COLORS["red"])) + }) .await?; + + Ok(()) } - Ok(()) + /// public facing message + pub async fn reply( + &self, + ctx: &Context<'_>, + user: &Member, + dm_user: Option, + ) -> Result<()> { + let mut resp = format!("{} {}!", self.data.description(), user.user.name); + + if dm_user.unwrap_or_default() { + resp = format!("{resp} (user notified with direct message)"); + } + + ctx.reply(resp).await?; + + Ok(()) + } + + pub async fn dm_user(&self, ctx: &Context<'_>, user: &Member) -> Result<()> { + let guild = ctx.http().get_guild(*user.guild_id.as_u64()).await?; + let title = format!("{} from {}!", self.data.description(), guild.name); + + user.user + .dm(ctx, |m| { + m.embed(|e| { + e.title(title).color(COLORS["red"]); + + if let Some(reason) = &self.reason { + e.description(format!("Reason: {}", reason)); + } + + e + }) + }) + .await?; + + Ok(()) + } + + pub async fn handle( + &self, + ctx: &Context<'_>, + user: &Member, + quiet: Option, + dm_user: Option, + handle_reply: bool, + ) -> Result<()> { + let actual_reason = self.reason.clone().unwrap_or("".to_string()); + self.data.run_action(ctx, user, actual_reason).await?; + + if quiet.unwrap_or_default() { + ctx.defer_ephemeral().await?; + } else { + ctx.defer().await?; + } + + self.log_action(ctx).await?; + + if dm_user.unwrap_or_default() { + self.dm_user(ctx, user).await?; + } + + if handle_reply { + self.reply(ctx, user, dm_user).await?; + } + + Ok(()) + } } -/// Ban a user -#[poise::command( - slash_command, - prefix_command, - default_member_permissions = "BAN_MEMBERS", - required_permissions = "BAN_MEMBERS", - aliases("ban") -)] -pub async fn ban( - ctx: Context<'_>, - users: Vec, - purge: Option, - reason: Option, - quiet: Option, -) -> Result<()> { - moderate( - &ctx, - &users, - &Action { - reason: reason.unwrap_or_default(), - data: ActionData::Ban { - purge: purge.unwrap_or(0), - }, - }, - quiet.unwrap_or(false), - ) - .await +pub struct Ban { + pub purge_messages_days: u8, } -/// Kick a user -#[poise::command( - slash_command, - prefix_command, - default_member_permissions = "KICK_MEMBERS", - required_permissions = "KICK_MEMBERS" -)] -pub async fn kick( - ctx: Context<'_>, - users: Vec, - reason: Option, - quiet: Option, -) -> Result<()> { - moderate( - &ctx, - &users, - &Action { - reason: reason.unwrap_or_default(), - data: ActionData::Kick {}, - }, - quiet.unwrap_or(false), - ) - .await +#[async_trait] +impl ModActionInfo for Ban { + fn to_fields(&self) -> Option { + let fields = vec![( + "Purged messages:", + format!("Last {} day(s)", self.purge_messages_days), + false, + )]; + + Some(fields) + } + + fn description(&self) -> String { + "Banned".to_string() + } + + async fn run_action( + &self, + http: impl CacheHttp + AsRef, + user: &Member, + reason: String, + ) -> Result<()> { + debug!("Banning user {user} with reason: \"{reason}\""); + + user.ban_with_reason(http, self.purge_messages_days, reason) + .await?; + + Ok(()) + } +} + +pub struct Timeout { + pub time_until: Timestamp, +} + +#[async_trait] +impl ModActionInfo for Timeout { + fn to_fields(&self) -> Option { + let fields = vec![("Timed out until:", self.time_until.to_string(), false)]; + + Some(fields) + } + + fn description(&self) -> String { + "Timed out".to_string() + } + + #[allow(unused_variables)] + async fn run_action( + &self, + http: impl CacheHttp + AsRef, + user: &Member, + reason: String, + ) -> Result<()> { + todo!() + } +} + +pub struct Kick {} + +#[async_trait] +impl ModActionInfo for Kick { + fn to_fields(&self) -> Option { + None + } + + fn description(&self) -> String { + "Kicked".to_string() + } + + async fn run_action( + &self, + http: impl CacheHttp + AsRef, + user: &Member, + reason: String, + ) -> Result<()> { + debug!("Kicked user {user} with reason: \"{reason}\""); + + user.kick_with_reason(http, &reason).await?; + + Ok(()) + } } diff --git a/src/commands/moderation/mod.rs b/src/commands/moderation/mod.rs index 13f51de..6f33185 100644 --- a/src/commands/moderation/mod.rs +++ b/src/commands/moderation/mod.rs @@ -1,3 +1,166 @@ -mod actions; +use crate::Context; +use std::error::Error; -pub use actions::*; +use color_eyre::eyre::{eyre, Result}; +use poise::serenity_prelude::{ArgumentConvert, ChannelId, GuildId, Member}; + +mod actions; +use actions::{Ban, Kick, ModAction}; + +async fn split_argument( + ctx: &Context<'_>, + guild_id: Option, + channel_id: Option, + list: String, +) -> Result> +where + T: ArgumentConvert, + T::Err: Error + Send + Sync + 'static, +{ + // yes i should be using something like `filter_map()` here. async closures + // are unstable though so woooooo + let mut res: Vec = vec![]; + for item in list.split(',') { + let item = T::convert(ctx.serenity_context(), guild_id, channel_id, item.trim()).await?; + + res.push(item); + } + + Ok(res) +} + +/// Ban a user +#[poise::command( + slash_command, + prefix_command, + guild_only, + default_member_permissions = "BAN_MEMBERS", + required_permissions = "BAN_MEMBERS", + aliases("ban") +)] +pub async fn ban_user( + ctx: Context<'_>, + #[description = "User to ban"] user: Member, + #[description = "Reason to ban"] reason: Option, + #[description = "Number of days to purge their messages from (defaults to 0)"] + purge_messages_days: Option, + #[description = "If true, the reply from the bot will be ephemeral"] quiet: Option, + #[description = "If true, the affected user will be sent a DM"] dm_user: Option, +) -> Result<()> { + let dmd = purge_messages_days.unwrap_or(0); + + let action = ModAction { + reason, + data: Ban { + purge_messages_days: dmd, + }, + }; + + action.handle(&ctx, &user, quiet, dm_user, true).await?; + + Ok(()) +} + +/// Ban multiple users +#[poise::command( + slash_command, + prefix_command, + guild_only, + default_member_permissions = "BAN_MEMBERS", + required_permissions = "BAN_MEMBERS", + aliases("ban_multi") +)] +pub async fn mass_ban( + ctx: Context<'_>, + #[description = "Comma separated list of users to ban"] users: String, + #[description = "Reason to ban"] reason: Option, + #[description = "Number of days to purge their messages from (defaults to 0)"] + purge_messages_days: Option, + #[description = "If true, the reply from the bot will be ephemeral"] quiet: Option, + #[description = "If true, the affected user will be sent a DM"] dm_user: Option, +) -> Result<()> { + let gid = ctx + .guild_id() + .ok_or_else(|| eyre!("Couldn't get GuildId!"))?; + + let dmd = purge_messages_days.unwrap_or(0); + let users: Vec = split_argument(&ctx, Some(gid), None, users).await?; + + for user in &users { + let action = ModAction { + reason: reason.clone(), + data: Ban { + purge_messages_days: dmd, + }, + }; + + action.handle(&ctx, user, quiet, dm_user, false).await?; + } + + let resp = format!("{} users banned!", users.len()); + ctx.reply(resp).await?; + + Ok(()) +} + +/// Kick a user +#[poise::command( + slash_command, + prefix_command, + guild_only, + default_member_permissions = "KICK_MEMBERS", + required_permissions = "KICK_MEMBERS", + aliases("kick") +)] +pub async fn kick_user( + ctx: Context<'_>, + #[description = "User to kick"] user: Member, + #[description = "Reason to kick"] reason: Option, + #[description = "If true, the reply from the bot will be ephemeral"] quiet: Option, + #[description = "If true, the affected user will be sent a DM"] dm_user: Option, +) -> Result<()> { + let action = ModAction { + reason, + data: Kick {}, + }; + + action.handle(&ctx, &user, quiet, dm_user, true).await?; + + Ok(()) +} + +/// Kick multiple users +#[poise::command( + slash_command, + prefix_command, + guild_only, + default_member_permissions = "KICK_MEMBERS", + required_permissions = "KICK_MEMBERS", + aliases("multi_kick") +)] +pub async fn mass_kick( + ctx: Context<'_>, + #[description = "Comma separated list of users to kick"] users: String, + #[description = "Reason to kick"] reason: Option, + #[description = "If true, the reply from the bot will be ephemeral"] quiet: Option, + #[description = "If true, the affected user will be sent a DM"] dm_user: Option, +) -> Result<()> { + let gid = ctx + .guild_id() + .ok_or_else(|| eyre!("Couldn't get GuildId!"))?; + let users: Vec = split_argument(&ctx, Some(gid), None, users).await?; + + for user in &users { + let action = ModAction { + reason: reason.clone(), + data: Kick {}, + }; + + action.handle(&ctx, user, quiet, dm_user, false).await?; + } + + let resp = format!("{} users kicked!", users.len()); + ctx.reply(resp).await?; + + Ok(()) +} From 8376c45c2d0ab8966dc8ef54974f8feb02f3f27c Mon Sep 17 00:00:00 2001 From: seth Date: Thu, 7 Dec 2023 22:18:21 -0500 Subject: [PATCH 26/90] feat: flesh out storage object --- src/commands/mod.rs | 2 +- src/commands/moderation/mod.rs | 2 +- src/storage.rs | 40 ---------- src/storage/message_logger.rs | 11 +++ src/storage/mod.rs | 137 +++++++++++++++++++++++++++++++++ 5 files changed, 150 insertions(+), 42 deletions(-) delete mode 100644 src/storage.rs create mode 100644 src/storage/message_logger.rs create mode 100644 src/storage/mod.rs diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 10f9681..99e4919 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -4,7 +4,7 @@ use color_eyre::eyre::Report; use poise::Command; mod general; -mod moderation; +pub mod moderation; pub fn to_global_commands() -> Vec> { vec![ diff --git a/src/commands/moderation/mod.rs b/src/commands/moderation/mod.rs index 6f33185..c3b862e 100644 --- a/src/commands/moderation/mod.rs +++ b/src/commands/moderation/mod.rs @@ -4,7 +4,7 @@ use std::error::Error; use color_eyre::eyre::{eyre, Result}; use poise::serenity_prelude::{ArgumentConvert, ChannelId, GuildId, Member}; -mod actions; +pub mod actions; use actions::{Ban, Kick, ModAction}; async fn split_argument( diff --git a/src/storage.rs b/src/storage.rs deleted file mode 100644 index 6cec18f..0000000 --- a/src/storage.rs +++ /dev/null @@ -1,40 +0,0 @@ -use color_eyre::eyre::Result; -use log::*; -use poise::serenity_prelude::UserId; -use redis::{AsyncCommands as _, Client}; - -pub const USER_KEY: &str = "users-v1"; - -#[derive(Clone, Debug)] -pub struct Storage { - client: Client, -} - -impl Storage { - pub fn new(redis_url: &str) -> Result { - let client = Client::open(redis_url)?; - - Ok(Self { client }) - } - - pub async fn store_user_plurality(&self, sender: UserId) -> Result<()> { - let mut con = self.client.get_async_connection().await?; - - info!("Marking {sender} as a PluralKit user"); - let key = format!("{USER_KEY}:{sender}:pk"); - - // Just store some value. We only care about the presence of this key - con.set(&key, 0).await?; - con.expire(key, 7 * 24 * 60 * 60).await?; - - Ok(()) - } - - pub async fn is_user_plural(&self, user_id: UserId) -> Result { - let key = format!("{USER_KEY}:{user_id}:pk"); - let mut con = self.client.get_async_connection().await?; - - let exists: bool = con.exists(key).await?; - Ok(exists) - } -} diff --git a/src/storage/message_logger.rs b/src/storage/message_logger.rs new file mode 100644 index 0000000..0ee2407 --- /dev/null +++ b/src/storage/message_logger.rs @@ -0,0 +1,11 @@ +use poise::serenity_prelude::UserId; +use redis_macros::{FromRedisValue, ToRedisArgs}; +use serde::{Deserialize, Serialize}; + +pub const MSG_LOG_KEY: &str = "message-log-v1"; + +#[derive(Clone, Debug, Deserialize, Serialize, FromRedisValue, ToRedisArgs)] +pub struct MessageLog { + pub author: UserId, + pub content: String, +} diff --git a/src/storage/mod.rs b/src/storage/mod.rs new file mode 100644 index 0000000..88f9544 --- /dev/null +++ b/src/storage/mod.rs @@ -0,0 +1,137 @@ +use std::fmt::Debug; + +use color_eyre::eyre::Result; +use log::*; +use poise::serenity_prelude::{ChannelId, MessageId, UserId}; +use redis::{AsyncCommands as _, Client, FromRedisValue, ToRedisArgs}; + +pub mod message_logger; +use message_logger::*; + +pub const PK_KEY: &str = "pluralkit-v1"; + +#[derive(Clone, Debug)] +pub struct Storage { + client: Client, +} + +impl Storage { + pub fn new(redis_url: &str) -> Result { + let client = Client::open(redis_url)?; + + Ok(Self { client }) + } + + /* + these are mainly light abstractions to avoid the `let mut con` + boilerplate, as well as not require the caller to format the + strings for keys + */ + + async fn get_key(&self, key: &str) -> Result + where + T: FromRedisValue, + { + debug!("Getting key {key}"); + + let mut con = self.client.get_async_connection().await?; + let res: T = con.get(key).await?; + + Ok(res) + } + + async fn set_key<'a>( + &self, + key: &str, + value: impl ToRedisArgs + Debug + Send + Sync + 'a, + ) -> Result<()> { + debug!("Creating key {key}:\n{value:#?}"); + + let mut con = self.client.get_async_connection().await?; + con.set(key, value).await?; + + Ok(()) + } + + async fn key_exists(&self, key: &str) -> Result { + debug!("Checking if key {key} exists"); + + let mut con = self.client.get_async_connection().await?; + let exists: u64 = con.exists(key).await?; + + Ok(exists > 0) + } + + async fn delete_key(&self, key: &str) -> Result<()> { + debug!("Deleting key {key}"); + + let mut con = self.client.get_async_connection().await?; + con.del(key).await?; + + Ok(()) + } + + async fn expire_key(&self, key: &str, expire_seconds: usize) -> Result<()> { + debug!("Expiring key {key} in {expire_seconds}"); + + let mut con = self.client.get_async_connection().await?; + con.expire(key, expire_seconds).await?; + + Ok(()) + } + + pub async fn store_user_plurality(&self, sender: UserId) -> Result<()> { + info!("Marking {sender} as a PluralKit user"); + let key = format!("{PK_KEY}:{sender}"); + + // Just store some value. We only care about the presence of this key + self.set_key(&key, 0).await?; + self.expire_key(&key, 7 * 24 * 60 * 60).await?; + + Ok(()) + } + + pub async fn is_user_plural(&self, user_id: UserId) -> Result { + let key = format!("{PK_KEY}:{user_id}"); + self.key_exists(&key).await + } + + pub async fn store_message( + &self, + channel_id: &ChannelId, + message_id: &MessageId, + content: String, + author: UserId, + ) -> Result<()> { + let key = format!("{MSG_LOG_KEY}:{channel_id}:{message_id}"); + + let val = MessageLog { author, content }; + + self.set_key(&key, val).await?; + self.expire_key(&key, 30 * 24 * 60 * 60).await?; // only store for 30 days + + Ok(()) + } + + pub async fn get_message( + &self, + channel_id: &ChannelId, + message_id: &MessageId, + ) -> Result { + let key = format!("{MSG_LOG_KEY}:{channel_id}:{message_id}"); + let res = self.get_key(&key).await?; + + Ok(res) + } + + pub async fn delete_message( + &self, + channel_id: &ChannelId, + message_id: &MessageId, + ) -> Result<()> { + let key = format!("{MSG_LOG_KEY}:{channel_id}:{message_id}"); + self.delete_key(&key).await?; + + Ok(()) + } +} From 7e96bced419e172afa8d18167332626f3d332bab Mon Sep 17 00:00:00 2001 From: seth Date: Thu, 7 Dec 2023 22:18:36 -0500 Subject: [PATCH 27/90] feat: add message logger --- .../{delete.rs => delete_on_reaction.rs} | 0 src/handlers/event/message_logger.rs | 79 +++++++++++++++++++ src/handlers/event/mod.rs | 24 +++++- 3 files changed, 101 insertions(+), 2 deletions(-) rename src/handlers/event/{delete.rs => delete_on_reaction.rs} (100%) create mode 100644 src/handlers/event/message_logger.rs diff --git a/src/handlers/event/delete.rs b/src/handlers/event/delete_on_reaction.rs similarity index 100% rename from src/handlers/event/delete.rs rename to src/handlers/event/delete_on_reaction.rs diff --git a/src/handlers/event/message_logger.rs b/src/handlers/event/message_logger.rs new file mode 100644 index 0000000..d4f00e4 --- /dev/null +++ b/src/handlers/event/message_logger.rs @@ -0,0 +1,79 @@ +use crate::consts::COLORS; +use crate::Data; + +use color_eyre::eyre::{eyre, Result}; +use log::debug; +use poise::serenity_prelude::{ + ChannelId, Colour, Http, Message, MessageId, MessageUpdateEvent, User, +}; + +#[allow(unused_variables)] +pub async fn log_msg(user: &User, content: String, color: T) -> Result<()> +where + T: Into, +{ + todo!() +} + +pub async fn handle_create(data: &Data, msg: &Message) -> Result<()> { + let channel_id = msg.channel_id; + let message_id = msg.id; + let content = &msg.content; + let author_id = msg.author.id; + + debug!("Logging message {message_id}"); + data.storage + .store_message(&channel_id, &message_id, content.to_string(), author_id) + .await?; + + Ok(()) +} + +pub async fn handle_update(data: &Data, event: &MessageUpdateEvent) -> Result<()> { + let stored = data + .storage + .get_message(&event.channel_id, &event.id) + .await?; + + let new_content = event.content.as_ref().ok_or_else(|| { + eyre!("Couldn't get content from event! Is the MESSAGE_CONTENT intent enabled?") + })?; + + let author = event + .author + .as_ref() + .ok_or_else(|| eyre!("Couldn't get author from message!"))?; + + if new_content != &stored.content { + log_msg(author, new_content.to_string(), COLORS["yellow"]).await?; + + debug!("Updating message {}", event.id); + data.storage + .store_message( + &event.channel_id, + &event.id, + new_content.to_string(), + author.id, + ) + .await?; + } + + Ok(()) +} + +pub async fn handle_delete( + http: impl AsRef, + data: &Data, + channel_id: &ChannelId, + message_id: &MessageId, +) -> Result<()> { + let stored = data.storage.get_message(channel_id, message_id).await?; + let user = http.as_ref().get_user(*stored.author.as_u64()).await?; + + log_msg(&user, stored.content, COLORS["red"]).await?; + + debug!("Deleting message {message_id}"); + data.storage.delete_message(channel_id, message_id).await?; + + Ok(()) +} diff --git a/src/handlers/event/mod.rs b/src/handlers/event/mod.rs index 07a82d0..bf0367e 100644 --- a/src/handlers/event/mod.rs +++ b/src/handlers/event/mod.rs @@ -5,9 +5,10 @@ use log::*; use poise::serenity_prelude::{Activity, Context, OnlineStatus}; use poise::{Event, FrameworkContext}; -mod delete; +mod delete_on_reaction; mod eta; mod expand_link; +mod message_logger; pub mod pluralkit; mod support_onboard; @@ -46,11 +47,30 @@ pub async fn handle( return Ok(()); } + // store all new messages to monitor edits and deletes + message_logger::handle_create(data, new_message).await?; + eta::handle(ctx, new_message).await?; expand_link::handle(ctx, new_message).await?; } - Event::ReactionAdd { add_reaction } => delete::handle(ctx, add_reaction).await?, + Event::MessageDelete { + channel_id, + deleted_message_id, + guild_id: _, + } => message_logger::handle_delete(ctx, data, channel_id, deleted_message_id).await?, + + Event::MessageUpdate { + old_if_available: _, + new: _, + event, + } => { + message_logger::handle_update(data, event).await?; + } + + Event::ReactionAdd { add_reaction } => { + delete_on_reaction::handle(ctx, add_reaction).await? + } Event::ThreadCreate { thread } => support_onboard::handle(ctx, thread).await?, From 026d4cb607785e20b3aa43a98d5a5a77551688dc Mon Sep 17 00:00:00 2001 From: seth Date: Fri, 8 Dec 2023 11:09:40 -0500 Subject: [PATCH 28/90] rory: handle errors from api --- src/api/dadjoke.rs | 2 +- src/api/prism_meta.rs | 10 +++++++--- src/api/rory.rs | 17 +++++++++++++---- src/commands/general/rory.rs | 14 +++++++++----- src/commands/general/say.rs | 1 + 5 files changed, 31 insertions(+), 13 deletions(-) diff --git a/src/api/dadjoke.rs b/src/api/dadjoke.rs index d3ac9c6..b29e2bf 100644 --- a/src/api/dadjoke.rs +++ b/src/api/dadjoke.rs @@ -16,6 +16,6 @@ pub async fn get_joke() -> Result { if let StatusCode::OK = status { Ok(resp.text().await?) } else { - Err(eyre!("Failed to fetch joke from {DADJOKE} with {status}")) + Err(eyre!("Couldn't get a joke!")) } } diff --git a/src/api/prism_meta.rs b/src/api/prism_meta.rs index 0bfa3fa..ce6adf1 100644 --- a/src/api/prism_meta.rs +++ b/src/api/prism_meta.rs @@ -1,6 +1,6 @@ use crate::api::REQWEST_CLIENT; -use color_eyre::eyre::{eyre, Result}; +use color_eyre::eyre::{eyre, Context, Result}; use log::*; use reqwest::StatusCode; use serde::{Deserialize, Serialize}; @@ -27,11 +27,15 @@ pub async fn get_latest_minecraft_version() -> Result { let status = resp.status(); if let StatusCode::OK = status { - let data = resp.json::().await?; + let data = resp + .json::() + .await + .wrap_err_with(|| "Couldn't parse Minecraft versions!")?; + let version = data .recommended .first() - .ok_or_else(|| eyre!("Couldn't find first recommended version!"))?; + .ok_or_else(|| eyre!("Couldn't find latest version of Minecraft!"))?; Ok(version.clone()) } else { diff --git a/src/api/rory.rs b/src/api/rory.rs index 9e56e8d..63be450 100644 --- a/src/api/rory.rs +++ b/src/api/rory.rs @@ -1,6 +1,6 @@ use crate::api::REQWEST_CLIENT; -use color_eyre::eyre::{eyre, Result}; +use color_eyre::eyre::{eyre, Context, Result}; use log::*; use reqwest::StatusCode; use serde::{Deserialize, Serialize}; @@ -9,6 +9,7 @@ use serde::{Deserialize, Serialize}; pub struct RoryResponse { pub id: u64, pub url: String, + pub error: Option, } const RORY: &str = "https://rory.cat"; @@ -25,14 +26,22 @@ pub async fn get_rory(id: Option) -> Result { let req = REQWEST_CLIENT .get(format!("{RORY}{ENDPOINT}/{target}")) - .build()?; + .build() + .wrap_err_with(|| "Couldn't build reqwest client!")?; info!("Making request to {}", req.url()); - let resp = REQWEST_CLIENT.execute(req).await?; + let resp = REQWEST_CLIENT + .execute(req) + .await + .wrap_err_with(|| "Couldn't make request for shiggy!")?; let status = resp.status(); if let StatusCode::OK = status { - let data = resp.json::().await?; + let data = resp + .json::() + .await + .wrap_err_with(|| "Couldn't parse the shiggy response!")?; + Ok(data) } else { Err(eyre!( diff --git a/src/commands/general/rory.rs b/src/commands/general/rory.rs index ea62d1d..59d157e 100644 --- a/src/commands/general/rory.rs +++ b/src/commands/general/rory.rs @@ -9,14 +9,18 @@ pub async fn rory( ctx: Context<'_>, #[description = "specify a Rory ID"] id: Option, ) -> Result<()> { - let resp = get_rory(id).await?; + let rory = get_rory(id).await?; ctx.send(|m| { m.embed(|e| { - e.title("Rory :3") - .url(&resp.url) - .image(resp.url) - .footer(|f| f.text(format!("ID {}", resp.id))) + if let Some(error) = rory.error { + e.title("Error!").description(error) + } else { + e.title("Rory :3") + .url(&rory.url) + .image(rory.url) + .footer(|f| f.text(format!("ID {}", rory.id))) + } }) }) .await?; diff --git a/src/commands/general/say.rs b/src/commands/general/say.rs index 27cd82f..6439f4b 100644 --- a/src/commands/general/say.rs +++ b/src/commands/general/say.rs @@ -17,6 +17,7 @@ pub async fn say(ctx: Context<'_>, #[description = "Just content?"] content: Str .await .ok_or_else(|| eyre!("Couldn't get channel!"))?; + ctx.defer_ephemeral().await?; channel.say(ctx, &content).await?; ctx.say("I said what you said!").await?; From c6f4295d6a6c74cc75c1a76660660f3d6fbb99fe Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Sat, 9 Dec 2023 16:54:35 +0000 Subject: [PATCH 29/90] feat: log analysis Signed-off-by: seth --- src/api/dadjoke.rs | 2 +- src/api/pluralkit.rs | 2 +- src/api/prism_meta.rs | 2 +- src/api/rory.rs | 15 +- src/consts.rs | 1 + src/handlers/error.rs | 2 +- src/handlers/event/analyze_logs/issues.rs | 181 ++++++++++++++++++ src/handlers/event/analyze_logs/mod.rs | 66 +++++++ .../event/analyze_logs/providers/0x0.rs | 24 +++ .../analyze_logs/providers/attachment.rs | 18 ++ .../event/analyze_logs/providers/haste.rs | 26 +++ .../event/analyze_logs/providers/mclogs.rs | 25 +++ .../event/analyze_logs/providers/mod.rs | 33 ++++ .../event/analyze_logs/providers/paste_gg.rs | 70 +++++++ .../event/analyze_logs/providers/pastebin.rs | 26 +++ src/handlers/event/eta.rs | 6 +- src/handlers/event/expand_link.rs | 6 +- src/handlers/event/mod.rs | 2 + 18 files changed, 487 insertions(+), 20 deletions(-) create mode 100644 src/handlers/event/analyze_logs/issues.rs create mode 100644 src/handlers/event/analyze_logs/mod.rs create mode 100644 src/handlers/event/analyze_logs/providers/0x0.rs create mode 100644 src/handlers/event/analyze_logs/providers/attachment.rs create mode 100644 src/handlers/event/analyze_logs/providers/haste.rs create mode 100644 src/handlers/event/analyze_logs/providers/mclogs.rs create mode 100644 src/handlers/event/analyze_logs/providers/mod.rs create mode 100644 src/handlers/event/analyze_logs/providers/paste_gg.rs create mode 100644 src/handlers/event/analyze_logs/providers/pastebin.rs diff --git a/src/api/dadjoke.rs b/src/api/dadjoke.rs index b29e2bf..8daef2c 100644 --- a/src/api/dadjoke.rs +++ b/src/api/dadjoke.rs @@ -9,7 +9,7 @@ const DADJOKE: &str = "https://icanhazdadjoke.com"; pub async fn get_joke() -> Result { let req = REQWEST_CLIENT.get(DADJOKE).build()?; - info!("Making request to {}", req.url()); + debug!("Making request to {}", req.url()); let resp = REQWEST_CLIENT.execute(req).await?; let status = resp.status(); diff --git a/src/api/pluralkit.rs b/src/api/pluralkit.rs index fd4dad5..89cfb88 100644 --- a/src/api/pluralkit.rs +++ b/src/api/pluralkit.rs @@ -19,7 +19,7 @@ pub async fn get_sender(message_id: MessageId) -> Result { .get(format!("{PLURAL_KIT}{MESSAGES_ENDPOINT}/{message_id}")) .build()?; - info!("Making request to {}", req.url()); + debug!("Making request to {}", req.url()); let resp = REQWEST_CLIENT.execute(req).await?; let status = resp.status(); diff --git a/src/api/prism_meta.rs b/src/api/prism_meta.rs index ce6adf1..3a99e40 100644 --- a/src/api/prism_meta.rs +++ b/src/api/prism_meta.rs @@ -22,7 +22,7 @@ pub async fn get_latest_minecraft_version() -> Result { .get(format!("{PRISM_META}{MINECRAFT_PACKAGEJSON_ENDPOINT}")) .build()?; - info!("Making request to {}", req.url()); + debug!("Making request to {}", req.url()); let resp = REQWEST_CLIENT.execute(req).await?; let status = resp.status(); diff --git a/src/api/rory.rs b/src/api/rory.rs index 63be450..399500c 100644 --- a/src/api/rory.rs +++ b/src/api/rory.rs @@ -16,31 +16,26 @@ const RORY: &str = "https://rory.cat"; const ENDPOINT: &str = "/purr"; pub async fn get_rory(id: Option) -> Result { - let target = { - if let Some(id) = id { - id.to_string() - } else { - "".to_string() - } - }; + let target = id.map(|id| id.to_string()).unwrap_or_default(); let req = REQWEST_CLIENT .get(format!("{RORY}{ENDPOINT}/{target}")) .build() .wrap_err_with(|| "Couldn't build reqwest client!")?; - info!("Making request to {}", req.url()); + debug!("Making request to {}", req.url()); let resp = REQWEST_CLIENT .execute(req) .await - .wrap_err_with(|| "Couldn't make request for shiggy!")?; + .wrap_err_with(|| "Couldn't make request for rory!")?; + let status = resp.status(); if let StatusCode::OK = status { let data = resp .json::() .await - .wrap_err_with(|| "Couldn't parse the shiggy response!")?; + .wrap_err_with(|| "Couldn't parse the rory response!")?; Ok(data) } else { diff --git a/src/consts.rs b/src/consts.rs index 1c305fe..35fdb61 100644 --- a/src/consts.rs +++ b/src/consts.rs @@ -10,6 +10,7 @@ pub static COLORS: Lazy> = Lazy::new(|| { ("blue", Color::from((96, 165, 250))), ("yellow", Color::from((253, 224, 71))), ("orange", Color::from((251, 146, 60))), + // TODO purple & pink :D ]) }); diff --git a/src/handlers/error.rs b/src/handlers/error.rs index 5842b49..de2121e 100644 --- a/src/handlers/error.rs +++ b/src/handlers/error.rs @@ -17,7 +17,7 @@ pub async fn handle(error: FrameworkError<'_, Data, Report>) { e.title("Something went wrong!") .description("oopsie") .timestamp(Timestamp::now()) - .color(COLORS["orange"]) + .color(COLORS["red"]) }) }) .await diff --git a/src/handlers/event/analyze_logs/issues.rs b/src/handlers/event/analyze_logs/issues.rs new file mode 100644 index 0000000..f75415f --- /dev/null +++ b/src/handlers/event/analyze_logs/issues.rs @@ -0,0 +1,181 @@ +use once_cell::sync::Lazy; +use regex::Regex; + +pub type Issue = Option<(String, String)>; + +pub fn find_issues(log: &str) -> Vec<(String, String)> { + let issues = [ + fabric_internal, + flatpak_nvidia, + forge_java, + intel_hd, + java_option, + macos_ns, + oom, + optinofine, + outdated_launcher, + wrong_java, + ]; + + issues.iter().filter_map(|issue| issue(log)).collect() +} + +fn fabric_internal(log: &str) -> Issue { + const CLASS_NOT_FOUND: &str = "Caused by: java.lang.ClassNotFoundException: "; + + let issue = ( + "Fabric Internal Access".to_string(), + "The mod you are using is using fabric internals that are not meant \ + to be used by anything but the loader itself. + Those mods break both on Quilt and with fabric updates. + If you're using fabric, downgrade your fabric loader could work, \ + on Quilt you can try updating to the latest beta version, \ + but there's nothing much to do unless the mod author stops using them." + .to_string(), + ); + + let errors = [ + &format!("{CLASS_NOT_FOUND}net.fabricmc.fabric.impl"), + &format!("{CLASS_NOT_FOUND}net.fabricmc.fabric.mixin"), + &format!("{CLASS_NOT_FOUND}net.fabricmc.fabric.loader.impl"), + &format!("{CLASS_NOT_FOUND}net.fabricmc.fabric.loader.mixin"), + "org.quiltmc.loader.impl.FormattedException: java.lang.NoSuchMethodError:", + ]; + + let found = errors.iter().any(|e| log.contains(e)); + found.then_some(issue) +} + +fn flatpak_nvidia(log: &str) -> Issue { + let issue = ( + "Outdated Nvidia Flatpak Driver".to_string(), + "The Nvidia driver for flatpak is outdated. + Please run `flatpak update` to fix this issue. \ + If that does not solve it, \ + please wait until the driver is added to Flathub and run it again." + .to_string(), + ); + + let found = log.contains("org.lwjgl.LWJGLException: Could not choose GLX13 config") + || log.contains("GLFW error 65545: GLX: Failed to find a suitable GLXFBConfig"); + + found.then_some(issue) +} + +fn forge_java(log: &str) -> Issue { + let issue = ( + "Forge Java Bug".to_string(), + "Old versions of Forge crash with Java 8u321+. + To fix this, update forge to the latest version via the Versions tab + (right click on Forge, click Change Version, and choose the latest one) + Alternatively, you can download 8u312 or lower. \ + See [archive](https://github.com/adoptium/temurin8-binaries/releases/tag/jdk8u312-b07)" + .to_string(), + ); + + let found = log.contains("java.lang.NoSuchMethodError: sun.security.util.ManifestEntryVerifier.(Ljava/util/jar/Manifest;)V"); + found.then_some(issue) +} + +fn intel_hd(log: &str) -> Issue { + let issue = + ( + "Intel HD Windows 10".to_string(), + "Your drivers don't support windows 10 officially + See https://prismlauncher.org/wiki/getting-started/installing-java/#a-note-about-intel-hd-20003000-on-windows-10 for more info".to_string() + ); + + let found = log.contains("java.lang.NoSuchMethodError: sun.security.util.ManifestEntryVerifier.(Ljava/util/jar/Manifest;)V"); + found.then_some(issue) +} + +fn java_option(log: &str) -> Issue { + static VM_OPTION_REGEX: Lazy = + Lazy::new(|| Regex::new(r"Unrecognized VM option '(.+)'[\r\n]").unwrap()); + static OPTION_REGEX: Lazy = + Lazy::new(|| Regex::new(r"Unrecognized option: (.+)[\r\n]").unwrap()); + + if let Some(captures) = VM_OPTION_REGEX.captures(log) { + let title = if &captures[1] == "UseShenandoahGC" { + "Wrong Java Arguments" + } else { + "Java 8 and below don't support ShenandoahGC" + }; + return Some(( + title.to_string(), + format!("Remove `-XX:{}` from your Java arguments", &captures[1]), + )); + } + + if let Some(captures) = OPTION_REGEX.captures(log) { + return Some(( + "Wrong Java Arguments".to_string(), + format!("Remove `{}` from your Java arguments", &captures[1]), + )); + } + + None +} + +fn macos_ns(log: &str) -> Issue { + let issue = ( + "MacOS NSInternalInconsistencyException".to_string(), + "You need to downgrade your Java 8 version. See https://prismlauncher.org/wiki/getting-started/installing-java/#older-minecraft-on-macos".to_string() +); + + let found = + log.contains("Terminating app due to uncaught exception 'NSInternalInconsistencyException"); + found.then_some(issue) +} + +fn oom(log: &str) -> Issue { + let issue = ( + "Out of Memory".to_string(), + "Allocating more RAM to your instance could help prevent this crash.".to_string(), + ); + + let found = log.contains("java.lang.OutOfMemoryError"); + found.then_some(issue) +} + +fn optinofine(log: &str) -> Issue { + let issue = ( + "Potential OptiFine Incompatibilities".to_string(), + "OptiFine is known to cause problems when paired with other mods. \ + Try to disable OptiFine and see if the issue persists. + Check `/tag optifine` for more info & some typically more compatible alternatives you can use." + .to_string(), + ); + + let found = log.contains("[✔] OptiFine_") || log.contains("[✔] optifabric-"); + found.then_some(issue) +} + +// TODO: @TheKodeToad +fn outdated_launcher(_log: &str) -> Issue { + todo!() +} + +fn wrong_java(log: &str) -> Issue { + static SWITCH_VERSION_REGEX: Lazy = Lazy::new(|| { + Regex::new( + r"(?m)Please switch to one of the following Java versions for this instance:[\r\n]+(Java version [\d.]+)", + ).unwrap() + }); + + if let Some(captures) = SWITCH_VERSION_REGEX.captures(log) { + let versions = captures[1].split('\n').collect::>().join(", "); + return Some(( + "Wrong Java Version".to_string(), + format!("Please switch to one of the following: `{versions}`\nFor more information, type `/tag java`"), + )); + } + + let issue = ( + "Java compatibility check skipped".to_string(), + "The Java major version may not work with your Minecraft instance. Please switch to a compatible version".to_string() + ); + + log.contains("Java major version is incompatible. Things might break.") + .then_some(issue) +} diff --git a/src/handlers/event/analyze_logs/mod.rs b/src/handlers/event/analyze_logs/mod.rs new file mode 100644 index 0000000..74b0c85 --- /dev/null +++ b/src/handlers/event/analyze_logs/mod.rs @@ -0,0 +1,66 @@ +use crate::consts::COLORS; +use color_eyre::eyre::Result; +use log::*; +use poise::serenity_prelude::{Context, Message}; + +mod issues; +mod providers; + +use issues::find_issues; +use providers::find_log; + +pub async fn handle(ctx: &Context, message: &Message) -> Result<()> { + let channel = message.channel_id; + + let log = find_log(message).await; + + if log.is_err() { + channel + .send_message(ctx, |m| { + m.reference_message(message) + .allowed_mentions(|am| am.replied_user(true)) + .embed(|e| { + e.title("Analyze failed!") + .description("Couldn't download log") + }) + }) + .await?; + + return Ok(()); + } + + let Some(log) = log? else { + debug!("No log found in message! Skipping analysis"); + return Ok(()); + }; + + let issues = find_issues(&log); + + channel + .send_message(ctx, |m| { + m.reference_message(message) + .allowed_mentions(|am| am.replied_user(true)) + .embed(|e| { + e.title("Log analysis"); + + if issues.is_empty() { + e.color(COLORS["green"]).field( + "Analyze failed!", + "No issues found automatically", + false, + ); + } else { + e.color(COLORS["red"]); + + for (title, description) in issues { + e.field(title, description, false); + } + } + + e + }) + }) + .await?; + + Ok(()) +} diff --git a/src/handlers/event/analyze_logs/providers/0x0.rs b/src/handlers/event/analyze_logs/providers/0x0.rs new file mode 100644 index 0000000..c958389 --- /dev/null +++ b/src/handlers/event/analyze_logs/providers/0x0.rs @@ -0,0 +1,24 @@ +use crate::api::REQWEST_CLIENT; + +use color_eyre::eyre::{eyre, Result}; +use once_cell::sync::Lazy; +use regex::Regex; +use reqwest::StatusCode; + +static REGEX: Lazy = Lazy::new(|| Regex::new(r"https://0x0\.st/\w*\.\w*").unwrap()); + +pub async fn find(content: &str) -> Result> { + let Some(url) = REGEX.find(content).map(|m| &content[m.range()]) else { + return Ok(None); + }; + + let request = REQWEST_CLIENT.get(url).build()?; + let response = REQWEST_CLIENT.execute(request).await?; + let status = response.status(); + + if let StatusCode::OK = status { + Ok(Some(response.text().await?)) + } else { + Err(eyre!("Failed to fetch paste from {url} with {status}",)) + } +} diff --git a/src/handlers/event/analyze_logs/providers/attachment.rs b/src/handlers/event/analyze_logs/providers/attachment.rs new file mode 100644 index 0000000..71d8d65 --- /dev/null +++ b/src/handlers/event/analyze_logs/providers/attachment.rs @@ -0,0 +1,18 @@ +use color_eyre::eyre::Result; +use poise::serenity_prelude::Message; + +pub async fn find(message: &Message) -> Result> { + // find first uploaded text file + if let Some(attachment) = message.attachments.iter().find(|a| { + a.content_type + .as_ref() + .and_then(|ct| ct.starts_with("text/").then_some(true)) + .is_some() + }) { + let bytes = attachment.download().await?; + let res = String::from_utf8(bytes)?; + Ok(Some(res)) + } else { + Ok(None) + } +} diff --git a/src/handlers/event/analyze_logs/providers/haste.rs b/src/handlers/event/analyze_logs/providers/haste.rs new file mode 100644 index 0000000..525da1b --- /dev/null +++ b/src/handlers/event/analyze_logs/providers/haste.rs @@ -0,0 +1,26 @@ +use crate::api::REQWEST_CLIENT; + +use color_eyre::eyre::{eyre, Result}; +use once_cell::sync::Lazy; +use regex::Regex; +use reqwest::StatusCode; + +static REGEX: Lazy = + Lazy::new(|| Regex::new(r"https://hst\.sh(?:/raw)?/(\w+(?:\.\w*)?)").unwrap()); + +pub async fn find(content: &str) -> Result> { + let Some(captures) = REGEX.captures(content) else { + return Ok(None); + }; + + let url = format!("https://hst.sh/raw/{}", &captures[1]); + let request = REQWEST_CLIENT.get(&url).build()?; + let response = REQWEST_CLIENT.execute(request).await?; + let status = response.status(); + + if let StatusCode::OK = status { + Ok(Some(response.text().await?)) + } else { + Err(eyre!("Failed to fetch paste from {url} with {status}")) + } +} diff --git a/src/handlers/event/analyze_logs/providers/mclogs.rs b/src/handlers/event/analyze_logs/providers/mclogs.rs new file mode 100644 index 0000000..c22f0c6 --- /dev/null +++ b/src/handlers/event/analyze_logs/providers/mclogs.rs @@ -0,0 +1,25 @@ +use crate::api::REQWEST_CLIENT; + +use color_eyre::eyre::{eyre, Result}; +use once_cell::sync::Lazy; +use regex::Regex; +use reqwest::StatusCode; + +static REGEX: Lazy = Lazy::new(|| Regex::new(r"https://mclo\.gs/(\w+)").unwrap()); + +pub async fn find(content: &str) -> Result> { + let Some(captures) = REGEX.captures(content) else { + return Ok(None); + }; + + let url = format!("https://api.mclo.gs/1/raw/{}", &captures[1]); + let request = REQWEST_CLIENT.get(&url).build()?; + let response = REQWEST_CLIENT.execute(request).await?; + let status = response.status(); + + if let StatusCode::OK = status { + Ok(Some(response.text().await?)) + } else { + Err(eyre!("Failed to fetch log from {url} with {status}")) + } +} diff --git a/src/handlers/event/analyze_logs/providers/mod.rs b/src/handlers/event/analyze_logs/providers/mod.rs new file mode 100644 index 0000000..3ebeb9a --- /dev/null +++ b/src/handlers/event/analyze_logs/providers/mod.rs @@ -0,0 +1,33 @@ +use color_eyre::eyre::Result; +use poise::serenity_prelude::Message; + +#[path = "0x0.rs"] +mod _0x0; +mod attachment; +mod haste; +mod mclogs; +mod paste_gg; +mod pastebin; + +pub type LogProvider = Result>; + +pub async fn find_log(message: &Message) -> LogProvider { + macro_rules! provider_impl { + ($provider:ident) => { + if let Some(content) = $provider::find(&message.content).await? { + return Ok(Some(content)); + } + }; + } + provider_impl!(_0x0); + provider_impl!(mclogs); + provider_impl!(haste); + provider_impl!(paste_gg); + provider_impl!(pastebin); + + if let Some(content) = attachment::find(message).await? { + return Ok(Some(content)); + } + + Ok(None) +} diff --git a/src/handlers/event/analyze_logs/providers/paste_gg.rs b/src/handlers/event/analyze_logs/providers/paste_gg.rs new file mode 100644 index 0000000..da52453 --- /dev/null +++ b/src/handlers/event/analyze_logs/providers/paste_gg.rs @@ -0,0 +1,70 @@ +use crate::api::REQWEST_CLIENT; + +use color_eyre::eyre::{eyre, Result}; +use once_cell::sync::Lazy; +use regex::Regex; +use reqwest::StatusCode; +use serde::{Deserialize, Serialize}; + +const PASTE_GG: &str = "https://api.paste.gg/v1"; +const PASTES_ENDPOINT: &str = "/pastes"; +static REGEX: Lazy = Lazy::new(|| Regex::new(r"https://paste.gg/p/\w+/(\w+)").unwrap()); + +#[derive(Clone, Debug, Deserialize, Serialize)] +struct PasteResponse { + status: String, + result: Option>, + error: Option, + message: Option, +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +struct PasteResult { + id: String, + name: Option, + description: Option, + visibility: Option, +} + +pub async fn find(content: &str) -> Result> { + let Some(captures) = REGEX.captures(content) else { + return Ok(None); + }; + + let paste_id = &captures[1]; + let files_url = format!("{PASTE_GG}{PASTES_ENDPOINT}/{paste_id}/files"); + + let resp = REQWEST_CLIENT + .execute(REQWEST_CLIENT.get(&files_url).build()?) + .await?; + let status = resp.status(); + + if resp.status() != StatusCode::OK { + return Err(eyre!( + "Couldn't get paste {paste_id} from {PASTE_GG} with status {status}!" + )); + } + + let paste_files: PasteResponse = resp.json().await?; + let file_id = &paste_files + .result + .ok_or_else(|| eyre!("Couldn't find any files associated with paste {paste_id}!"))?[0] + .id; + + let raw_url = format!("{PASTE_GG}{PASTES_ENDPOINT}/{paste_id}/files/{file_id}/raw"); + + let resp = REQWEST_CLIENT + .execute(REQWEST_CLIENT.get(&raw_url).build()?) + .await?; + let status = resp.status(); + + if status != StatusCode::OK { + return Err(eyre!( + "Couldn't get file {file_id} from paste {paste_id} with status {status}!" + )); + } + + let text = resp.text().await?; + + Ok(Some(text)) +} diff --git a/src/handlers/event/analyze_logs/providers/pastebin.rs b/src/handlers/event/analyze_logs/providers/pastebin.rs new file mode 100644 index 0000000..b8dc0bb --- /dev/null +++ b/src/handlers/event/analyze_logs/providers/pastebin.rs @@ -0,0 +1,26 @@ +use crate::api::REQWEST_CLIENT; + +use color_eyre::eyre::{eyre, Result}; +use once_cell::sync::Lazy; +use regex::Regex; +use reqwest::StatusCode; + +static REGEX: Lazy = + Lazy::new(|| Regex::new(r"https://pastebin\.com(?:/raw)?/(\w+)").unwrap()); + +pub async fn find(content: &str) -> Result> { + let Some(captures) = REGEX.captures(content) else { + return Ok(None); + }; + + let url = format!("https://pastebin.com/raw/{}", &captures[1]); + let request = REQWEST_CLIENT.get(&url).build()?; + let response = REQWEST_CLIENT.execute(request).await?; + let status = response.status(); + + if let StatusCode::OK = status { + Ok(Some(response.text().await?)) + } else { + Err(eyre!("Failed to fetch paste from {url} with {status}")) + } +} diff --git a/src/handlers/event/eta.rs b/src/handlers/event/eta.rs index 2eb8ae2..e62d22a 100644 --- a/src/handlers/event/eta.rs +++ b/src/handlers/event/eta.rs @@ -7,8 +7,8 @@ use regex::Regex; static ETA_REGEX: Lazy = Lazy::new(|| Regex::new(r"\beta\b").unwrap()); -pub async fn handle(ctx: &Context, msg: &Message) -> Result<()> { - if !ETA_REGEX.is_match(&msg.content) { +pub async fn handle(ctx: &Context, message: &Message) -> Result<()> { + if !ETA_REGEX.is_match(&message.content) { return Ok(()); } @@ -17,6 +17,6 @@ pub async fn handle(ctx: &Context, msg: &Message) -> Result<()> { utils::random_choice(consts::ETA_MESSAGES)? ); - msg.reply(ctx, response).await?; + message.reply(ctx, response).await?; Ok(()) } diff --git a/src/handlers/event/expand_link.rs b/src/handlers/event/expand_link.rs index 95065e1..0863115 100644 --- a/src/handlers/event/expand_link.rs +++ b/src/handlers/event/expand_link.rs @@ -3,12 +3,12 @@ use poise::serenity_prelude::{Context, Message}; use crate::utils; -pub async fn handle(ctx: &Context, msg: &Message) -> Result<()> { - let embeds = utils::resolve_message(ctx, msg).await?; +pub async fn handle(ctx: &Context, message: &Message) -> Result<()> { + let embeds = utils::resolve_message(ctx, message).await?; // TOOD getchoo: actually reply to user // ...not sure why Message doesn't give me a builder in reply() or equivalents - let our_channel = msg + let our_channel = message .channel(ctx) .await .wrap_err_with(|| "Couldn't get channel from message!")? diff --git a/src/handlers/event/mod.rs b/src/handlers/event/mod.rs index bf0367e..660b1da 100644 --- a/src/handlers/event/mod.rs +++ b/src/handlers/event/mod.rs @@ -5,6 +5,7 @@ use log::*; use poise::serenity_prelude::{Activity, Context, OnlineStatus}; use poise::{Event, FrameworkContext}; +mod analyze_logs; mod delete_on_reaction; mod eta; mod expand_link; @@ -52,6 +53,7 @@ pub async fn handle( eta::handle(ctx, new_message).await?; expand_link::handle(ctx, new_message).await?; + analyze_logs::handle(ctx, new_message).await?; } Event::MessageDelete { From 2067697ff1871c27e24aff1be5de83cf9fb343d1 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Sun, 10 Dec 2023 21:06:32 +0000 Subject: [PATCH 30/90] feat: LWJGL 2 & Java 9, Pre 1.12 native transport & Java 9 issues --- src/handlers/event/analyze_logs/issues.rs | 51 +++++++++++++++++++---- 1 file changed, 44 insertions(+), 7 deletions(-) diff --git a/src/handlers/event/analyze_logs/issues.rs b/src/handlers/event/analyze_logs/issues.rs index f75415f..8ea65f1 100644 --- a/src/handlers/event/analyze_logs/issues.rs +++ b/src/handlers/event/analyze_logs/issues.rs @@ -5,18 +5,18 @@ pub type Issue = Option<(String, String)>; pub fn find_issues(log: &str) -> Vec<(String, String)> { let issues = [ - fabric_internal, + wrong_java, flatpak_nvidia, forge_java, intel_hd, - java_option, macos_ns, + fabric_internal, oom, - optinofine, - outdated_launcher, - wrong_java, + optinotfine, + java_option, + lwjgl_2_java_9, + pre_1_12_native_transport_java_9, ]; - issues.iter().filter_map(|issue| issue(log)).collect() } @@ -138,7 +138,7 @@ fn oom(log: &str) -> Issue { found.then_some(issue) } -fn optinofine(log: &str) -> Issue { +fn optinotfine(log: &str) -> Issue { let issue = ( "Potential OptiFine Incompatibilities".to_string(), "OptiFine is known to cause problems when paired with other mods. \ @@ -179,3 +179,40 @@ fn wrong_java(log: &str) -> Issue { log.contains("Java major version is incompatible. Things might break.") .then_some(issue) } + +fn lwjgl_2_java_9(log: &str) -> Issue { + let issue = ( + "Linux: crash with pre-1.13 and Java 9+".to_string(), + "Using pre-1.13 (which uses LWJGL 2) with Java 9 or later usually causes a crash. \ + Switching to Java 8 or below will fix your issue. + Alternatively, you can use [Temurin](https://adoptium.net/temurin/releases). \ + However, multiplayer will not work in versions from 1.8 to 1.11. + For more information, type `/tag java`." + .to_string(), + ); + + let found = log.contains("check_match: Assertion `version->filename == NULL || ! _dl_name_match_p (version->filename, map)' failed!"); + found.then_some(issue) +} + +fn pre_1_12_native_transport_java_9(log: &str) -> Issue { + let issue = ( + "Linux: broken multiplayer with 1.8-1.11 and Java 9+".to_string(), + "These versions of Minecraft use an outdated version of Netty which does not properly support Java 9. + +Switching to Java 8 or below will fix this issue. For more information, type `/tag java`. + +If you must use a newer version, do the following: +- Open `options.txt` (in the main window Edit -> Open .minecraft) and change. +- Find `useNativeTransport:true` and change it to `useNativeTransport:false`. +Note: whilst Netty was introduced in 1.7, this option did not exist \ +which is why the issue was not present." + .to_string(), + ); + + let found = log.contains( + "java.lang.RuntimeException: Unable to access address of buffer\n\tat io.netty.channel.epoll" + ); + + found.then_some(issue) +} From a86bbfe0a9c7eb65d4d1f79cea9ae253485e1b75 Mon Sep 17 00:00:00 2001 From: seth Date: Tue, 12 Dec 2023 15:55:04 -0500 Subject: [PATCH 31/90] analyze_logs: detect outdated launchers --- src/handlers/event/analyze_logs/issues.rs | 137 ++++++++++++++-------- src/handlers/event/analyze_logs/mod.rs | 6 +- src/handlers/event/mod.rs | 2 +- src/storage/mod.rs | 21 +++- 4 files changed, 115 insertions(+), 51 deletions(-) diff --git a/src/handlers/event/analyze_logs/issues.rs b/src/handlers/event/analyze_logs/issues.rs index 8ea65f1..77acb2b 100644 --- a/src/handlers/event/analyze_logs/issues.rs +++ b/src/handlers/event/analyze_logs/issues.rs @@ -1,23 +1,33 @@ +use crate::Data; + +use color_eyre::eyre::Result; use once_cell::sync::Lazy; use regex::Regex; pub type Issue = Option<(String, String)>; -pub fn find_issues(log: &str) -> Vec<(String, String)> { +pub async fn find_issues(log: &str, data: &Data) -> Result> { let issues = [ - wrong_java, + fabric_internal, flatpak_nvidia, forge_java, intel_hd, - macos_ns, - fabric_internal, - oom, - optinotfine, java_option, lwjgl_2_java_9, + macos_ns, + oom, + optinotfine, pre_1_12_native_transport_java_9, + wrong_java, ]; - issues.iter().filter_map(|issue| issue(log)).collect() + + let mut res: Vec<(String, String)> = issues.iter().filter_map(|issue| issue(log)).collect(); + + if let Some(issues) = outdated_launcher(log, data).await? { + res.push(issues) + } + + Ok(res) } fn fabric_internal(log: &str) -> Issue { @@ -117,6 +127,21 @@ fn java_option(log: &str) -> Issue { None } +fn lwjgl_2_java_9(log: &str) -> Issue { + let issue = ( + "Linux: crash with pre-1.13 and Java 9+".to_string(), + "Using pre-1.13 (which uses LWJGL 2) with Java 9 or later usually causes a crash. \ + Switching to Java 8 or below will fix your issue. + Alternatively, you can use [Temurin](https://adoptium.net/temurin/releases). \ + However, multiplayer will not work in versions from 1.8 to 1.11. + For more information, type `/tag java`." + .to_string(), + ); + + let found = log.contains("check_match: Assertion `version->filename == NULL || ! _dl_name_match_p (version->filename, map)' failed!"); + found.then_some(issue) +} + fn macos_ns(log: &str) -> Issue { let issue = ( "MacOS NSInternalInconsistencyException".to_string(), @@ -151,9 +176,64 @@ fn optinotfine(log: &str) -> Issue { found.then_some(issue) } -// TODO: @TheKodeToad -fn outdated_launcher(_log: &str) -> Issue { - todo!() +async fn outdated_launcher(log: &str, data: &Data) -> Result { + static OUTDATED_LAUNCHER_REGEX: Lazy = + Lazy::new(|| Regex::new("Prism Launcher version: [0-9].[0-9].[0-9]").unwrap()); + + let Some(captures) = OUTDATED_LAUNCHER_REGEX.captures(log) else { + return Ok(None); + }; + + let version_from_log = captures[0].replace("Prism Launcher version: ", ""); + + let storage = &data.storage; + let latest_version = if storage.launcher_version_is_cached().await? { + storage.get_launcher_version().await? + } else { + let version = data + .octocrab + .repos("PrismLauncher", "PrismLauncher") + .releases() + .get_latest() + .await? + .tag_name; + + storage.cache_launcher_version(&version).await?; + version + }; + + if version_from_log < latest_version { + let issue = ( + "Outdated Prism Launcher".to_string(), + format!("Your installed version is {version_from_log}, while the newest version is {latest_version}.\nPlease update, for more info see https://prismlauncher.org/download/") + ); + + Ok(Some(issue)) + } else { + Ok(None) + } +} + +fn pre_1_12_native_transport_java_9(log: &str) -> Issue { + let issue = ( + "Linux: broken multiplayer with 1.8-1.11 and Java 9+".to_string(), + "These versions of Minecraft use an outdated version of Netty which does not properly support Java 9. + +Switching to Java 8 or below will fix this issue. For more information, type `/tag java`. + +If you must use a newer version, do the following: +- Open `options.txt` (in the main window Edit -> Open .minecraft) and change. +- Find `useNativeTransport:true` and change it to `useNativeTransport:false`. +Note: whilst Netty was introduced in 1.7, this option did not exist \ +which is why the issue was not present." + .to_string(), + ); + + let found = log.contains( + "java.lang.RuntimeException: Unable to access address of buffer\n\tat io.netty.channel.epoll" + ); + + found.then_some(issue) } fn wrong_java(log: &str) -> Issue { @@ -179,40 +259,3 @@ fn wrong_java(log: &str) -> Issue { log.contains("Java major version is incompatible. Things might break.") .then_some(issue) } - -fn lwjgl_2_java_9(log: &str) -> Issue { - let issue = ( - "Linux: crash with pre-1.13 and Java 9+".to_string(), - "Using pre-1.13 (which uses LWJGL 2) with Java 9 or later usually causes a crash. \ - Switching to Java 8 or below will fix your issue. - Alternatively, you can use [Temurin](https://adoptium.net/temurin/releases). \ - However, multiplayer will not work in versions from 1.8 to 1.11. - For more information, type `/tag java`." - .to_string(), - ); - - let found = log.contains("check_match: Assertion `version->filename == NULL || ! _dl_name_match_p (version->filename, map)' failed!"); - found.then_some(issue) -} - -fn pre_1_12_native_transport_java_9(log: &str) -> Issue { - let issue = ( - "Linux: broken multiplayer with 1.8-1.11 and Java 9+".to_string(), - "These versions of Minecraft use an outdated version of Netty which does not properly support Java 9. - -Switching to Java 8 or below will fix this issue. For more information, type `/tag java`. - -If you must use a newer version, do the following: -- Open `options.txt` (in the main window Edit -> Open .minecraft) and change. -- Find `useNativeTransport:true` and change it to `useNativeTransport:false`. -Note: whilst Netty was introduced in 1.7, this option did not exist \ -which is why the issue was not present." - .to_string(), - ); - - let found = log.contains( - "java.lang.RuntimeException: Unable to access address of buffer\n\tat io.netty.channel.epoll" - ); - - found.then_some(issue) -} diff --git a/src/handlers/event/analyze_logs/mod.rs b/src/handlers/event/analyze_logs/mod.rs index 74b0c85..0e12127 100644 --- a/src/handlers/event/analyze_logs/mod.rs +++ b/src/handlers/event/analyze_logs/mod.rs @@ -1,4 +1,6 @@ use crate::consts::COLORS; +use crate::Data; + use color_eyre::eyre::Result; use log::*; use poise::serenity_prelude::{Context, Message}; @@ -9,7 +11,7 @@ mod providers; use issues::find_issues; use providers::find_log; -pub async fn handle(ctx: &Context, message: &Message) -> Result<()> { +pub async fn handle(ctx: &Context, message: &Message, data: &Data) -> Result<()> { let channel = message.channel_id; let log = find_log(message).await; @@ -34,7 +36,7 @@ pub async fn handle(ctx: &Context, message: &Message) -> Result<()> { return Ok(()); }; - let issues = find_issues(&log); + let issues = find_issues(&log, data).await?; channel .send_message(ctx, |m| { diff --git a/src/handlers/event/mod.rs b/src/handlers/event/mod.rs index 660b1da..a0db8c2 100644 --- a/src/handlers/event/mod.rs +++ b/src/handlers/event/mod.rs @@ -53,7 +53,7 @@ pub async fn handle( eta::handle(ctx, new_message).await?; expand_link::handle(ctx, new_message).await?; - analyze_logs::handle(ctx, new_message).await?; + analyze_logs::handle(ctx, new_message, data).await?; } Event::MessageDelete { diff --git a/src/storage/mod.rs b/src/storage/mod.rs index 88f9544..3f0d419 100644 --- a/src/storage/mod.rs +++ b/src/storage/mod.rs @@ -8,7 +8,8 @@ use redis::{AsyncCommands as _, Client, FromRedisValue, ToRedisArgs}; pub mod message_logger; use message_logger::*; -pub const PK_KEY: &str = "pluralkit-v1"; +const PK_KEY: &str = "pluralkit-v1"; +const LAUNCHER_VERSION_KEY: &str = "launcher-version-v1"; #[derive(Clone, Debug)] pub struct Storage { @@ -134,4 +135,22 @@ impl Storage { Ok(()) } + + pub async fn cache_launcher_version(&self, version: &str) -> Result<()> { + self.set_key(LAUNCHER_VERSION_KEY, version).await?; + + Ok(()) + } + + pub async fn get_launcher_version(&self) -> Result { + let res = self.get_key(LAUNCHER_VERSION_KEY).await?; + + Ok(res) + } + + pub async fn launcher_version_is_cached(&self) -> Result { + let res = self.key_exists(LAUNCHER_VERSION_KEY).await?; + + Ok(res) + } } From bb521885adc3dab20cce76fbbfc66436bdbbf874 Mon Sep 17 00:00:00 2001 From: seth Date: Tue, 12 Dec 2023 16:08:57 -0500 Subject: [PATCH 32/90] deps: update cargo and flake lockfiles --- Cargo.lock | 92 +++++++++++++++++++++++++++--------------------------- flake.lock | 24 +++++++------- 2 files changed, 58 insertions(+), 58 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1778c94..b89b662 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -55,7 +55,7 @@ checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.40", ] [[package]] @@ -486,7 +486,7 @@ checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.40", ] [[package]] @@ -610,9 +610,9 @@ dependencies = [ [[package]] name = "http-body" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" dependencies = [ "bytes", "http", @@ -677,7 +677,7 @@ dependencies = [ "http", "hyper", "log", - "rustls 0.21.9", + "rustls 0.21.10", "rustls-native-certs", "tokio", "tokio-rustls 0.24.1", @@ -779,9 +779,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" +checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" [[package]] name = "js-sys" @@ -801,7 +801,7 @@ dependencies = [ "base64 0.21.5", "js-sys", "pem", - "ring 0.17.6", + "ring 0.17.7", "serde", "serde_json", "simple_asn1", @@ -815,9 +815,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.150" +version = "0.2.151" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" +checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4" [[package]] name = "linked-hash-map" @@ -880,9 +880,9 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.9" +version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dce281c5e46beae905d4de1870d8b1509a9142b62eedf18b443b011ca8343d0" +checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09" dependencies = [ "libc", "wasi", @@ -977,9 +977,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.18.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "openssl-probe" @@ -1027,9 +1027,9 @@ dependencies = [ [[package]] name = "pem" -version = "3.0.2" +version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3163d2912b7c3b52d651a055f2c7eec9ba5cd22d26ef75b8dd3a59980b185923" +checksum = "1b8fcc794035347fb64beda2d3b462595dd2753e3f268d89c5aae77e8cf2c310" dependencies = [ "base64 0.21.5", "serde", @@ -1058,7 +1058,7 @@ checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.40", ] [[package]] @@ -1177,7 +1177,7 @@ dependencies = [ "itoa", "percent-encoding", "pin-project-lite", - "rustls 0.21.9", + "rustls 0.21.10", "rustls-native-certs", "ryu", "sha1_smol", @@ -1297,7 +1297,7 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", - "rustls 0.21.9", + "rustls 0.21.10", "rustls-pemfile", "serde", "serde_json", @@ -1333,9 +1333,9 @@ dependencies = [ [[package]] name = "ring" -version = "0.17.6" +version = "0.17.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "684d5e6e18f669ccebf64a92236bb7db9a34f07be010e3627368182027180866" +checksum = "688c63d65483050968b2a8937f7995f443e27041a0f7700aa59b0822aedebb74" dependencies = [ "cc", "getrandom", @@ -1353,9 +1353,9 @@ checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" [[package]] name = "rustix" -version = "0.38.26" +version = "0.38.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9470c4bf8246c8daf25f9598dca807fb6510347b1e1cfa55749113850c79d88a" +checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316" dependencies = [ "bitflags 2.4.1", "errno", @@ -1378,12 +1378,12 @@ dependencies = [ [[package]] name = "rustls" -version = "0.21.9" +version = "0.21.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "629648aced5775d558af50b2b4c7b02983a04b312126d45eeead26e7caa498b9" +checksum = "f9d5a6813c0759e4609cd494e8e725babae6a2ca7b62a5536a13daaec6fcb7ba" dependencies = [ "log", - "ring 0.17.6", + "ring 0.17.7", "rustls-webpki", "sct", ] @@ -1415,7 +1415,7 @@ version = "0.101.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" dependencies = [ - "ring 0.17.6", + "ring 0.17.7", "untrusted 0.9.0", ] @@ -1427,9 +1427,9 @@ checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" [[package]] name = "ryu" -version = "1.0.15" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" +checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" [[package]] name = "schannel" @@ -1452,7 +1452,7 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" dependencies = [ - "ring 0.17.6", + "ring 0.17.7", "untrusted 0.9.0", ] @@ -1515,7 +1515,7 @@ checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.40", ] [[package]] @@ -1719,9 +1719,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.39" +version = "2.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a" +checksum = "13fa70a4ee923979ffb522cacce59d34421ebdea5625e1073c4326ef9d2dd42e" dependencies = [ "proc-macro2", "quote", @@ -1775,7 +1775,7 @@ checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.40", ] [[package]] @@ -1834,9 +1834,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.34.0" +version = "1.35.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0c014766411e834f7af5b8f4cf46257aab4036ca95e9d2c144a10f59ad6f5b9" +checksum = "841d45b238a16291a4e1584e61820b8ae57d696cc5015c459c229ccc6990cc1c" dependencies = [ "backtrace", "bytes", @@ -1868,7 +1868,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.40", ] [[package]] @@ -1888,7 +1888,7 @@ version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" dependencies = [ - "rustls 0.21.9", + "rustls 0.21.10", "tokio", ] @@ -1985,7 +1985,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.40", ] [[package]] @@ -2021,9 +2021,9 @@ dependencies = [ [[package]] name = "try-lock" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "tungstenite" @@ -2069,9 +2069,9 @@ dependencies = [ [[package]] name = "unicode-bidi" -version = "0.3.13" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" +checksum = "6f2528f27a9eb2b21e69c95319b30bd0efd85d09c379741b0f78ea1d86be2416" [[package]] name = "unicode-ident" @@ -2166,7 +2166,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.40", "wasm-bindgen-shared", ] @@ -2200,7 +2200,7 @@ checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.40", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -2240,7 +2240,7 @@ version = "0.22.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed63aea5ce73d0ff405984102c42de94fc55a6b75765d621c65262469b3c9b53" dependencies = [ - "ring 0.17.6", + "ring 0.17.7", "untrusted 0.9.0", ] diff --git a/flake.lock b/flake.lock index ba6aa30..bfa3c9f 100644 --- a/flake.lock +++ b/flake.lock @@ -8,11 +8,11 @@ "rust-analyzer-src": "rust-analyzer-src" }, "locked": { - "lastModified": 1701498074, - "narHash": "sha256-UNYTZtBYa/4G5+dRzNNXNcEi1RVm6yOUQNHYkcRag2Q=", + "lastModified": 1702362203, + "narHash": "sha256-etsSWZfvmVA9RWqixmoKTf+JLEMtjlOaLxh/tK80ZCg=", "owner": "nix-community", "repo": "fenix", - "rev": "ce8747b0d8d6605264651f9ab5c84db6d7a56728", + "rev": "7622e4a2d4378861d9e83fc02c1eeb0e084bf3f2", "type": "github" }, "original": { @@ -113,11 +113,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1701336116, - "narHash": "sha256-kEmpezCR/FpITc6yMbAh4WrOCiT2zg5pSjnKrq51h5Y=", + "lastModified": 1702272962, + "narHash": "sha256-D+zHwkwPc6oYQ4G3A1HuadopqRwUY/JkMwHz1YF7j4Q=", "owner": "nixos", "repo": "nixpkgs", - "rev": "f5c27c6136db4d76c30e533c20517df6864c46ee", + "rev": "e97b3e4186bcadf0ef1b6be22b8558eab1cdeb5d", "type": "github" }, "original": { @@ -174,11 +174,11 @@ "nixpkgs-stable": "nixpkgs-stable" }, "locked": { - "lastModified": 1700922917, - "narHash": "sha256-ej2fch/T584b5K9sk1UhmZF7W6wEfDHuoUYpFN8dtvM=", + "lastModified": 1702325376, + "narHash": "sha256-biLGx2LzU2+/qPwq+kWwVBgXs3MVYT1gPa0fCwpLplU=", "owner": "cachix", "repo": "pre-commit-hooks.nix", - "rev": "e5ee5c5f3844550c01d2131096c7271cec5e9b78", + "rev": "e1d203c2fa7e2593c777e490213958ef81f71977", "type": "github" }, "original": { @@ -216,11 +216,11 @@ "rust-analyzer-src": { "flake": false, "locked": { - "lastModified": 1701447636, - "narHash": "sha256-WaCcxLNAqo/FAK0QtYqweKCUVTGcbKpFIHClc+k2YlI=", + "lastModified": 1702322912, + "narHash": "sha256-CtQtHdJp6naa5xtMmrkNwZx0aVq4215RtWw5/f7l0zw=", "owner": "rust-lang", "repo": "rust-analyzer", - "rev": "e402c494b7c7d94a37c6d789a216187aaf9ccd3e", + "rev": "8c3e28e3e2d4f190b633fbd43d689b45b28b5598", "type": "github" }, "original": { From 3e992bf6833f1ad4ab13aad24040aa8bc3d45b8d Mon Sep 17 00:00:00 2001 From: seth Date: Tue, 12 Dec 2023 16:10:05 -0500 Subject: [PATCH 33/90] actions: update DeterminateSystems/nix-installer-action --- .github/workflows/docker.yml | 2 +- .github/workflows/nix.yml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 009aa4e..891362b 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -19,7 +19,7 @@ jobs: - uses: actions/checkout@v4 - name: Install Nix - uses: DeterminateSystems/nix-installer-action@v8 + uses: DeterminateSystems/nix-installer-action@v9 - name: Setup Nix cache uses: DeterminateSystems/magic-nix-cache-action@v2 diff --git a/.github/workflows/nix.yml b/.github/workflows/nix.yml index 57ae224..b3752c8 100644 --- a/.github/workflows/nix.yml +++ b/.github/workflows/nix.yml @@ -19,7 +19,7 @@ jobs: uses: actions/checkout@v4 - name: Install Nix - uses: DeterminateSystems/nix-installer-action@v8 + uses: DeterminateSystems/nix-installer-action@v9 - name: Setup Nix cache uses: DeterminateSystems/magic-nix-cache-action@v2 @@ -35,7 +35,7 @@ jobs: uses: actions/checkout@v4 - name: Install Nix - uses: DeterminateSystems/nix-installer-action@v8 + uses: DeterminateSystems/nix-installer-action@v9 - name: Setup Nix cache uses: DeterminateSystems/magic-nix-cache-action@v2 From 885e28f98fde85c572279e58b12e4254c6f39e89 Mon Sep 17 00:00:00 2001 From: seth Date: Wed, 13 Dec 2023 11:32:46 -0500 Subject: [PATCH 34/90] refactor framework setup --- src/main.rs | 35 ++++++++++++++++++++++------------- 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/src/main.rs b/src/main.rs index 533ac89..a90ea10 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,6 @@ use std::{sync::Arc, time::Duration}; -use color_eyre::eyre::{eyre, Context as _, Report, Result}; +use color_eyre::eyre::{Context as _, Report, Result}; use config::Config; use log::*; use poise::{ @@ -40,32 +40,50 @@ impl Data { } } +async fn setup( + ctx: &serenity::Context, + _ready: &serenity::Ready, + framework: &Framework, +) -> Result { + poise::builtins::register_globally(ctx, &framework.options().commands).await?; + info!("Registered global commands!"); + + let data = Data::new()?; + + Ok(data) +} + #[tokio::main] async fn main() -> Result<()> { dotenvy::dotenv().ok(); color_eyre::install()?; env_logger::init(); - let token = - std::env::var("TOKEN").wrap_err_with(|| eyre!("Couldn't find token in environment!"))?; + let token = std::env::var("DISCORD_BOT_TOKEN") + .wrap_err_with(|| "Couldn't find bot token in environment!")?; let intents = serenity::GatewayIntents::non_privileged() | serenity::GatewayIntents::MESSAGE_CONTENT; let options = FrameworkOptions { commands: commands::to_global_commands(), + on_error: |error| Box::pin(handlers::handle_error(error)), + command_check: Some(|ctx| { Box::pin(async move { Ok(ctx.author().id != ctx.framework().bot_id) }) }), + event_handler: |ctx, event, framework, data| { Box::pin(handlers::handle_event(ctx, event, framework, data)) }, + prefix_options: PrefixFrameworkOptions { prefix: Some("r".into()), edit_tracker: Some(EditTracker::for_timespan(Duration::from_secs(3600))), ..Default::default() }, + ..Default::default() }; @@ -73,16 +91,7 @@ async fn main() -> Result<()> { .token(token) .intents(intents) .options(options) - .setup(|ctx, _ready, framework| { - Box::pin(async move { - poise::builtins::register_globally(ctx, &framework.options().commands).await?; - info!("Registered global commands!"); - - let data = Data::new()?; - - Ok(data) - }) - }); + .setup(|ctx, ready, framework| Box::pin(setup(ctx, ready, framework))); tokio::select! { result = framework.run() => { result.map_err(Report::from) }, From 9ae8b98e1aa44d8d51419f05ea22c02ced54550a Mon Sep 17 00:00:00 2001 From: seth Date: Wed, 13 Dec 2023 11:40:39 -0500 Subject: [PATCH 35/90] feat: cleanly shutdown on ctrl+c & sigterm --- src/main.rs | 34 ++++++++++++++++++++++++++++------ 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/src/main.rs b/src/main.rs index a90ea10..92544fa 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,12 +1,18 @@ -use std::{sync::Arc, time::Duration}; +use std::sync::Arc; +use std::time::Duration; use color_eyre::eyre::{Context as _, Report, Result}; +use color_eyre::owo_colors::OwoColorize; use config::Config; use log::*; use poise::{ - serenity_prelude as serenity, EditTracker, Framework, FrameworkOptions, PrefixFrameworkOptions, + serenity_prelude::{self as serenity, ShardManager}, + EditTracker, Framework, FrameworkOptions, PrefixFrameworkOptions, }; use storage::Storage; +use tokio::signal::ctrl_c; +use tokio::signal::unix::{signal, SignalKind}; +use tokio::sync::Mutex; mod api; mod commands; @@ -53,6 +59,12 @@ async fn setup( Ok(data) } +async fn handle_shutdown(shard_manager: Arc>, reason: &str) { + warn!("{reason}! Shutting down bot..."); + shard_manager.lock().await.shutdown_all().await; + println!("{}", "Everything is shutdown. Goodbye!".green()) +} + #[tokio::main] async fn main() -> Result<()> { dotenvy::dotenv().ok(); @@ -91,12 +103,22 @@ async fn main() -> Result<()> { .token(token) .intents(intents) .options(options) - .setup(|ctx, ready, framework| Box::pin(setup(ctx, ready, framework))); + .setup(|ctx, ready, framework| Box::pin(setup(ctx, ready, framework))) + .build() + .await + .wrap_err_with(|| "Failed to build framework!")?; + + let shard_manager = framework.shard_manager().clone(); + let mut sigterm = signal(SignalKind::terminate())?; tokio::select! { - result = framework.run() => { result.map_err(Report::from) }, - _ = tokio::signal::ctrl_c() => { - info!("Interrupted! Exiting..."); + result = framework.start() => result.map_err(Report::from), + _ = sigterm.recv() => { + handle_shutdown(shard_manager, "Recieved SIGTERM").await; + std::process::exit(0); + } + _ = ctrl_c() => { + handle_shutdown(shard_manager, "Interrupted").await; std::process::exit(130); } } From 3e3be2aed507360018933de1e47354910169ef9a Mon Sep 17 00:00:00 2001 From: seth Date: Wed, 13 Dec 2023 11:47:08 -0500 Subject: [PATCH 36/90] feat: better handle errors during setup --- src/handlers/error.rs | 9 ++++++++- src/main.rs | 30 +++++++++++++++++++++++------- src/storage/mod.rs | 2 +- 3 files changed, 32 insertions(+), 9 deletions(-) diff --git a/src/handlers/error.rs b/src/handlers/error.rs index de2121e..66277e0 100644 --- a/src/handlers/error.rs +++ b/src/handlers/error.rs @@ -8,7 +8,14 @@ use poise::FrameworkError; pub async fn handle(error: FrameworkError<'_, Data, Report>) { match error { - FrameworkError::Setup { error, .. } => error!("Error setting up client!\n{error:#?}"), + FrameworkError::Setup { + error, framework, .. + } => { + error!("Error setting up client! Bailing out"); + framework.shard_manager().lock().await.shutdown_all().await; + + panic!("{error}") + } FrameworkError::Command { error, ctx } => { error!("Error in command {}:\n{error:?}", ctx.command().name); diff --git a/src/main.rs b/src/main.rs index 92544fa..f3b47e8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,15 +1,19 @@ use std::sync::Arc; use std::time::Duration; -use color_eyre::eyre::{Context as _, Report, Result}; +use color_eyre::eyre::{eyre, Context as _, Report, Result}; use color_eyre::owo_colors::OwoColorize; -use config::Config; + use log::*; + use poise::{ - serenity_prelude::{self as serenity, ShardManager}, - EditTracker, Framework, FrameworkOptions, PrefixFrameworkOptions, + serenity_prelude as serenity, EditTracker, Framework, FrameworkOptions, PrefixFrameworkOptions, }; -use storage::Storage; + +use serenity::ShardManager; + +use redis::ConnectionLike; + use tokio::signal::ctrl_c; use tokio::signal::unix::{signal, SignalKind}; use tokio::sync::Mutex; @@ -23,6 +27,9 @@ mod storage; mod tags; mod utils; +use config::Config; +use storage::Storage; + type Context<'a> = poise::Context<'a, Data, Report>; #[derive(Clone)] @@ -51,11 +58,20 @@ async fn setup( _ready: &serenity::Ready, framework: &Framework, ) -> Result { + let data = Data::new()?; + + // test redis connection + let mut client = data.storage.client.clone(); + + if !client.check_connection() { + return Err(eyre!( + "Couldn't connect to storage! Is your daemon running?" + )); + } + poise::builtins::register_globally(ctx, &framework.options().commands).await?; info!("Registered global commands!"); - let data = Data::new()?; - Ok(data) } diff --git a/src/storage/mod.rs b/src/storage/mod.rs index 3f0d419..885817a 100644 --- a/src/storage/mod.rs +++ b/src/storage/mod.rs @@ -13,7 +13,7 @@ const LAUNCHER_VERSION_KEY: &str = "launcher-version-v1"; #[derive(Clone, Debug)] pub struct Storage { - client: Client, + pub client: Client, } impl Storage { From 9dce57f52716b1d4b1b316f86704c85d812d4209 Mon Sep 17 00:00:00 2001 From: seth Date: Wed, 13 Dec 2023 11:51:53 -0500 Subject: [PATCH 37/90] moderation: dm user before taking action --- src/commands/moderation/actions.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/commands/moderation/actions.rs b/src/commands/moderation/actions.rs index b7ab8b2..86e013d 100644 --- a/src/commands/moderation/actions.rs +++ b/src/commands/moderation/actions.rs @@ -121,21 +121,21 @@ impl ModAction { dm_user: Option, handle_reply: bool, ) -> Result<()> { - let actual_reason = self.reason.clone().unwrap_or("".to_string()); - self.data.run_action(ctx, user, actual_reason).await?; - if quiet.unwrap_or_default() { ctx.defer_ephemeral().await?; } else { ctx.defer().await?; } - self.log_action(ctx).await?; + let actual_reason = self.reason.clone().unwrap_or("".to_string()); if dm_user.unwrap_or_default() { self.dm_user(ctx, user).await?; } + self.data.run_action(ctx, user, actual_reason).await?; + self.log_action(ctx).await?; + if handle_reply { self.reply(ctx, user, dm_user).await?; } From e0fea8d23ed0bc36bd6f5a95ac487c3fe5e95938 Mon Sep 17 00:00:00 2001 From: seth Date: Wed, 13 Dec 2023 11:58:51 -0500 Subject: [PATCH 38/90] refactor: use User over Member in moderation commands --- src/commands/moderation/actions.rs | 63 ++++++++++++++++-------------- src/commands/moderation/mod.rs | 10 ++--- 2 files changed, 39 insertions(+), 34 deletions(-) diff --git a/src/commands/moderation/actions.rs b/src/commands/moderation/actions.rs index 86e013d..0513238 100644 --- a/src/commands/moderation/actions.rs +++ b/src/commands/moderation/actions.rs @@ -3,7 +3,7 @@ use crate::{consts::COLORS, Context}; use async_trait::async_trait; use color_eyre::eyre::{eyre, Context as _, Result}; use log::*; -use poise::serenity_prelude::{CacheHttp, Http, Member, Timestamp}; +use poise::serenity_prelude::{CacheHttp, GuildId, Http, Timestamp, User}; type Fields<'a> = Vec<(&'a str, String, bool)>; @@ -14,7 +14,8 @@ pub trait ModActionInfo { async fn run_action( &self, http: impl CacheHttp + AsRef, - user: &Member, + user: &User, + guild_id: &GuildId, reason: String, ) -> Result<()>; } @@ -75,13 +76,8 @@ impl ModAction { } /// public facing message - pub async fn reply( - &self, - ctx: &Context<'_>, - user: &Member, - dm_user: Option, - ) -> Result<()> { - let mut resp = format!("{} {}!", self.data.description(), user.user.name); + pub async fn reply(&self, ctx: &Context<'_>, user: &User, dm_user: Option) -> Result<()> { + let mut resp = format!("{} {}!", self.data.description(), user.name); if dm_user.unwrap_or_default() { resp = format!("{resp} (user notified with direct message)"); @@ -92,23 +88,22 @@ impl ModAction { Ok(()) } - pub async fn dm_user(&self, ctx: &Context<'_>, user: &Member) -> Result<()> { - let guild = ctx.http().get_guild(*user.guild_id.as_u64()).await?; + pub async fn dm_user(&self, ctx: &Context<'_>, user: &User, guild_id: &GuildId) -> Result<()> { + let guild = ctx.http().get_guild(*guild_id.as_u64()).await?; let title = format!("{} from {}!", self.data.description(), guild.name); - user.user - .dm(ctx, |m| { - m.embed(|e| { - e.title(title).color(COLORS["red"]); + user.dm(ctx, |m| { + m.embed(|e| { + e.title(title).color(COLORS["red"]); - if let Some(reason) = &self.reason { - e.description(format!("Reason: {}", reason)); - } + if let Some(reason) = &self.reason { + e.description(format!("Reason: {}", reason)); + } - e - }) + e }) - .await?; + }) + .await?; Ok(()) } @@ -116,7 +111,7 @@ impl ModAction { pub async fn handle( &self, ctx: &Context<'_>, - user: &Member, + user: &User, quiet: Option, dm_user: Option, handle_reply: bool, @@ -127,13 +122,19 @@ impl ModAction { ctx.defer().await?; } + let guild_id = ctx + .guild_id() + .ok_or_else(|| eyre!("Couldn't get GuildId for context!"))?; let actual_reason = self.reason.clone().unwrap_or("".to_string()); if dm_user.unwrap_or_default() { - self.dm_user(ctx, user).await?; + self.dm_user(ctx, user, &guild_id).await?; } - self.data.run_action(ctx, user, actual_reason).await?; + self.data + .run_action(ctx, user, &guild_id, actual_reason) + .await?; + self.log_action(ctx).await?; if handle_reply { @@ -167,12 +168,14 @@ impl ModActionInfo for Ban { async fn run_action( &self, http: impl CacheHttp + AsRef, - user: &Member, + user: &User, + guild_id: &GuildId, reason: String, ) -> Result<()> { debug!("Banning user {user} with reason: \"{reason}\""); - user.ban_with_reason(http, self.purge_messages_days, reason) + guild_id + .ban_with_reason(http, user, self.purge_messages_days, reason) .await?; Ok(()) @@ -199,7 +202,8 @@ impl ModActionInfo for Timeout { async fn run_action( &self, http: impl CacheHttp + AsRef, - user: &Member, + user: &User, + guild_id: &GuildId, reason: String, ) -> Result<()> { todo!() @@ -221,12 +225,13 @@ impl ModActionInfo for Kick { async fn run_action( &self, http: impl CacheHttp + AsRef, - user: &Member, + user: &User, + guild_id: &GuildId, reason: String, ) -> Result<()> { debug!("Kicked user {user} with reason: \"{reason}\""); - user.kick_with_reason(http, &reason).await?; + guild_id.kick_with_reason(http, user, &reason).await?; Ok(()) } diff --git a/src/commands/moderation/mod.rs b/src/commands/moderation/mod.rs index c3b862e..21259f7 100644 --- a/src/commands/moderation/mod.rs +++ b/src/commands/moderation/mod.rs @@ -2,7 +2,7 @@ use crate::Context; use std::error::Error; use color_eyre::eyre::{eyre, Result}; -use poise::serenity_prelude::{ArgumentConvert, ChannelId, GuildId, Member}; +use poise::serenity_prelude::{ArgumentConvert, ChannelId, GuildId, User}; pub mod actions; use actions::{Ban, Kick, ModAction}; @@ -40,7 +40,7 @@ where )] pub async fn ban_user( ctx: Context<'_>, - #[description = "User to ban"] user: Member, + #[description = "User to ban"] user: User, #[description = "Reason to ban"] reason: Option, #[description = "Number of days to purge their messages from (defaults to 0)"] purge_messages_days: Option, @@ -84,7 +84,7 @@ pub async fn mass_ban( .ok_or_else(|| eyre!("Couldn't get GuildId!"))?; let dmd = purge_messages_days.unwrap_or(0); - let users: Vec = split_argument(&ctx, Some(gid), None, users).await?; + let users: Vec = split_argument(&ctx, Some(gid), None, users).await?; for user in &users { let action = ModAction { @@ -114,7 +114,7 @@ pub async fn mass_ban( )] pub async fn kick_user( ctx: Context<'_>, - #[description = "User to kick"] user: Member, + #[description = "User to kick"] user: User, #[description = "Reason to kick"] reason: Option, #[description = "If true, the reply from the bot will be ephemeral"] quiet: Option, #[description = "If true, the affected user will be sent a DM"] dm_user: Option, @@ -148,7 +148,7 @@ pub async fn mass_kick( let gid = ctx .guild_id() .ok_or_else(|| eyre!("Couldn't get GuildId!"))?; - let users: Vec = split_argument(&ctx, Some(gid), None, users).await?; + let users: Vec = split_argument(&ctx, Some(gid), None, users).await?; for user in &users { let action = ModAction { From f2979d4cde2ac491a42ac516183748ac6a1db325 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Mon, 8 Jan 2024 17:17:21 +0000 Subject: [PATCH 39/90] Goodbye moderation We'll probably keep zeppelin / move back to Lily -- however the reminders seem to currently be broken :/ --- src/commands/mod.rs | 4 - src/commands/moderation/actions.rs | 238 --------------------------- src/commands/moderation/mod.rs | 166 ------------------- src/handlers/event/message_logger.rs | 79 --------- src/handlers/event/mod.rs | 18 -- src/storage/message_logger.rs | 11 -- src/storage/mod.rs | 44 +---- 7 files changed, 1 insertion(+), 559 deletions(-) delete mode 100644 src/commands/moderation/actions.rs delete mode 100644 src/commands/moderation/mod.rs delete mode 100644 src/handlers/event/message_logger.rs delete mode 100644 src/storage/message_logger.rs diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 99e4919..45c23d6 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -4,7 +4,6 @@ use color_eyre::eyre::Report; use poise::Command; mod general; -pub mod moderation; pub fn to_global_commands() -> Vec> { vec![ @@ -15,8 +14,5 @@ pub fn to_global_commands() -> Vec> { general::say(), general::stars(), general::tag(), - moderation::ban_user(), - moderation::mass_ban(), - moderation::kick_user(), ] } diff --git a/src/commands/moderation/actions.rs b/src/commands/moderation/actions.rs deleted file mode 100644 index 0513238..0000000 --- a/src/commands/moderation/actions.rs +++ /dev/null @@ -1,238 +0,0 @@ -use crate::{consts::COLORS, Context}; - -use async_trait::async_trait; -use color_eyre::eyre::{eyre, Context as _, Result}; -use log::*; -use poise::serenity_prelude::{CacheHttp, GuildId, Http, Timestamp, User}; - -type Fields<'a> = Vec<(&'a str, String, bool)>; - -#[async_trait] -pub trait ModActionInfo { - fn to_fields(&self) -> Option; - fn description(&self) -> String; - async fn run_action( - &self, - http: impl CacheHttp + AsRef, - user: &User, - guild_id: &GuildId, - reason: String, - ) -> Result<()>; -} - -pub struct ModAction -where - T: ModActionInfo, -{ - pub reason: Option, - pub data: T, -} - -impl ModAction { - fn get_all_fields(&self) -> Fields { - let mut fields = vec![]; - - if let Some(reason) = self.reason.clone() { - fields.push(("Reason:", reason, false)); - } - - if let Some(mut action_fields) = self.data.to_fields() { - fields.append(&mut action_fields); - } - - fields - } - - /// internal mod logging - pub async fn log_action(&self, ctx: &Context<'_>) -> Result<()> { - let channel_id = ctx - .data() - .config - .discord - .channels - .say_log_channel_id - .ok_or_else(|| eyre!("Couldn't find say_log_channel_id! Unable to log mod action"))?; - - let channel = ctx - .http() - .get_channel(channel_id.into()) - .await - .wrap_err_with(|| "Couldn't resolve say_log_channel_id as a Channel! Are you sure you sure you used the right one?")?; - - let channel = channel - .guild() - .ok_or_else(|| eyre!("Couldn't resolve say_log_channel_id as a GuildChannel! Are you sure you used the right one?"))?; - - let fields = self.get_all_fields(); - let title = format!("{} user!", self.data.description()); - - channel - .send_message(ctx, |m| { - m.embed(|e| e.title(title).fields(fields).color(COLORS["red"])) - }) - .await?; - - Ok(()) - } - - /// public facing message - pub async fn reply(&self, ctx: &Context<'_>, user: &User, dm_user: Option) -> Result<()> { - let mut resp = format!("{} {}!", self.data.description(), user.name); - - if dm_user.unwrap_or_default() { - resp = format!("{resp} (user notified with direct message)"); - } - - ctx.reply(resp).await?; - - Ok(()) - } - - pub async fn dm_user(&self, ctx: &Context<'_>, user: &User, guild_id: &GuildId) -> Result<()> { - let guild = ctx.http().get_guild(*guild_id.as_u64()).await?; - let title = format!("{} from {}!", self.data.description(), guild.name); - - user.dm(ctx, |m| { - m.embed(|e| { - e.title(title).color(COLORS["red"]); - - if let Some(reason) = &self.reason { - e.description(format!("Reason: {}", reason)); - } - - e - }) - }) - .await?; - - Ok(()) - } - - pub async fn handle( - &self, - ctx: &Context<'_>, - user: &User, - quiet: Option, - dm_user: Option, - handle_reply: bool, - ) -> Result<()> { - if quiet.unwrap_or_default() { - ctx.defer_ephemeral().await?; - } else { - ctx.defer().await?; - } - - let guild_id = ctx - .guild_id() - .ok_or_else(|| eyre!("Couldn't get GuildId for context!"))?; - let actual_reason = self.reason.clone().unwrap_or("".to_string()); - - if dm_user.unwrap_or_default() { - self.dm_user(ctx, user, &guild_id).await?; - } - - self.data - .run_action(ctx, user, &guild_id, actual_reason) - .await?; - - self.log_action(ctx).await?; - - if handle_reply { - self.reply(ctx, user, dm_user).await?; - } - - Ok(()) - } -} - -pub struct Ban { - pub purge_messages_days: u8, -} - -#[async_trait] -impl ModActionInfo for Ban { - fn to_fields(&self) -> Option { - let fields = vec![( - "Purged messages:", - format!("Last {} day(s)", self.purge_messages_days), - false, - )]; - - Some(fields) - } - - fn description(&self) -> String { - "Banned".to_string() - } - - async fn run_action( - &self, - http: impl CacheHttp + AsRef, - user: &User, - guild_id: &GuildId, - reason: String, - ) -> Result<()> { - debug!("Banning user {user} with reason: \"{reason}\""); - - guild_id - .ban_with_reason(http, user, self.purge_messages_days, reason) - .await?; - - Ok(()) - } -} - -pub struct Timeout { - pub time_until: Timestamp, -} - -#[async_trait] -impl ModActionInfo for Timeout { - fn to_fields(&self) -> Option { - let fields = vec![("Timed out until:", self.time_until.to_string(), false)]; - - Some(fields) - } - - fn description(&self) -> String { - "Timed out".to_string() - } - - #[allow(unused_variables)] - async fn run_action( - &self, - http: impl CacheHttp + AsRef, - user: &User, - guild_id: &GuildId, - reason: String, - ) -> Result<()> { - todo!() - } -} - -pub struct Kick {} - -#[async_trait] -impl ModActionInfo for Kick { - fn to_fields(&self) -> Option { - None - } - - fn description(&self) -> String { - "Kicked".to_string() - } - - async fn run_action( - &self, - http: impl CacheHttp + AsRef, - user: &User, - guild_id: &GuildId, - reason: String, - ) -> Result<()> { - debug!("Kicked user {user} with reason: \"{reason}\""); - - guild_id.kick_with_reason(http, user, &reason).await?; - - Ok(()) - } -} diff --git a/src/commands/moderation/mod.rs b/src/commands/moderation/mod.rs deleted file mode 100644 index 21259f7..0000000 --- a/src/commands/moderation/mod.rs +++ /dev/null @@ -1,166 +0,0 @@ -use crate::Context; -use std::error::Error; - -use color_eyre::eyre::{eyre, Result}; -use poise::serenity_prelude::{ArgumentConvert, ChannelId, GuildId, User}; - -pub mod actions; -use actions::{Ban, Kick, ModAction}; - -async fn split_argument( - ctx: &Context<'_>, - guild_id: Option, - channel_id: Option, - list: String, -) -> Result> -where - T: ArgumentConvert, - T::Err: Error + Send + Sync + 'static, -{ - // yes i should be using something like `filter_map()` here. async closures - // are unstable though so woooooo - let mut res: Vec = vec![]; - for item in list.split(',') { - let item = T::convert(ctx.serenity_context(), guild_id, channel_id, item.trim()).await?; - - res.push(item); - } - - Ok(res) -} - -/// Ban a user -#[poise::command( - slash_command, - prefix_command, - guild_only, - default_member_permissions = "BAN_MEMBERS", - required_permissions = "BAN_MEMBERS", - aliases("ban") -)] -pub async fn ban_user( - ctx: Context<'_>, - #[description = "User to ban"] user: User, - #[description = "Reason to ban"] reason: Option, - #[description = "Number of days to purge their messages from (defaults to 0)"] - purge_messages_days: Option, - #[description = "If true, the reply from the bot will be ephemeral"] quiet: Option, - #[description = "If true, the affected user will be sent a DM"] dm_user: Option, -) -> Result<()> { - let dmd = purge_messages_days.unwrap_or(0); - - let action = ModAction { - reason, - data: Ban { - purge_messages_days: dmd, - }, - }; - - action.handle(&ctx, &user, quiet, dm_user, true).await?; - - Ok(()) -} - -/// Ban multiple users -#[poise::command( - slash_command, - prefix_command, - guild_only, - default_member_permissions = "BAN_MEMBERS", - required_permissions = "BAN_MEMBERS", - aliases("ban_multi") -)] -pub async fn mass_ban( - ctx: Context<'_>, - #[description = "Comma separated list of users to ban"] users: String, - #[description = "Reason to ban"] reason: Option, - #[description = "Number of days to purge their messages from (defaults to 0)"] - purge_messages_days: Option, - #[description = "If true, the reply from the bot will be ephemeral"] quiet: Option, - #[description = "If true, the affected user will be sent a DM"] dm_user: Option, -) -> Result<()> { - let gid = ctx - .guild_id() - .ok_or_else(|| eyre!("Couldn't get GuildId!"))?; - - let dmd = purge_messages_days.unwrap_or(0); - let users: Vec = split_argument(&ctx, Some(gid), None, users).await?; - - for user in &users { - let action = ModAction { - reason: reason.clone(), - data: Ban { - purge_messages_days: dmd, - }, - }; - - action.handle(&ctx, user, quiet, dm_user, false).await?; - } - - let resp = format!("{} users banned!", users.len()); - ctx.reply(resp).await?; - - Ok(()) -} - -/// Kick a user -#[poise::command( - slash_command, - prefix_command, - guild_only, - default_member_permissions = "KICK_MEMBERS", - required_permissions = "KICK_MEMBERS", - aliases("kick") -)] -pub async fn kick_user( - ctx: Context<'_>, - #[description = "User to kick"] user: User, - #[description = "Reason to kick"] reason: Option, - #[description = "If true, the reply from the bot will be ephemeral"] quiet: Option, - #[description = "If true, the affected user will be sent a DM"] dm_user: Option, -) -> Result<()> { - let action = ModAction { - reason, - data: Kick {}, - }; - - action.handle(&ctx, &user, quiet, dm_user, true).await?; - - Ok(()) -} - -/// Kick multiple users -#[poise::command( - slash_command, - prefix_command, - guild_only, - default_member_permissions = "KICK_MEMBERS", - required_permissions = "KICK_MEMBERS", - aliases("multi_kick") -)] -pub async fn mass_kick( - ctx: Context<'_>, - #[description = "Comma separated list of users to kick"] users: String, - #[description = "Reason to kick"] reason: Option, - #[description = "If true, the reply from the bot will be ephemeral"] quiet: Option, - #[description = "If true, the affected user will be sent a DM"] dm_user: Option, -) -> Result<()> { - let gid = ctx - .guild_id() - .ok_or_else(|| eyre!("Couldn't get GuildId!"))?; - let users: Vec = split_argument(&ctx, Some(gid), None, users).await?; - - for user in &users { - let action = ModAction { - reason: reason.clone(), - data: Kick {}, - }; - - action.handle(&ctx, user, quiet, dm_user, false).await?; - } - - let resp = format!("{} users kicked!", users.len()); - ctx.reply(resp).await?; - - Ok(()) -} diff --git a/src/handlers/event/message_logger.rs b/src/handlers/event/message_logger.rs deleted file mode 100644 index d4f00e4..0000000 --- a/src/handlers/event/message_logger.rs +++ /dev/null @@ -1,79 +0,0 @@ -use crate::consts::COLORS; -use crate::Data; - -use color_eyre::eyre::{eyre, Result}; -use log::debug; -use poise::serenity_prelude::{ - ChannelId, Colour, Http, Message, MessageId, MessageUpdateEvent, User, -}; - -#[allow(unused_variables)] -pub async fn log_msg(user: &User, content: String, color: T) -> Result<()> -where - T: Into, -{ - todo!() -} - -pub async fn handle_create(data: &Data, msg: &Message) -> Result<()> { - let channel_id = msg.channel_id; - let message_id = msg.id; - let content = &msg.content; - let author_id = msg.author.id; - - debug!("Logging message {message_id}"); - data.storage - .store_message(&channel_id, &message_id, content.to_string(), author_id) - .await?; - - Ok(()) -} - -pub async fn handle_update(data: &Data, event: &MessageUpdateEvent) -> Result<()> { - let stored = data - .storage - .get_message(&event.channel_id, &event.id) - .await?; - - let new_content = event.content.as_ref().ok_or_else(|| { - eyre!("Couldn't get content from event! Is the MESSAGE_CONTENT intent enabled?") - })?; - - let author = event - .author - .as_ref() - .ok_or_else(|| eyre!("Couldn't get author from message!"))?; - - if new_content != &stored.content { - log_msg(author, new_content.to_string(), COLORS["yellow"]).await?; - - debug!("Updating message {}", event.id); - data.storage - .store_message( - &event.channel_id, - &event.id, - new_content.to_string(), - author.id, - ) - .await?; - } - - Ok(()) -} - -pub async fn handle_delete( - http: impl AsRef, - data: &Data, - channel_id: &ChannelId, - message_id: &MessageId, -) -> Result<()> { - let stored = data.storage.get_message(channel_id, message_id).await?; - let user = http.as_ref().get_user(*stored.author.as_u64()).await?; - - log_msg(&user, stored.content, COLORS["red"]).await?; - - debug!("Deleting message {message_id}"); - data.storage.delete_message(channel_id, message_id).await?; - - Ok(()) -} diff --git a/src/handlers/event/mod.rs b/src/handlers/event/mod.rs index a0db8c2..57f614a 100644 --- a/src/handlers/event/mod.rs +++ b/src/handlers/event/mod.rs @@ -9,7 +9,6 @@ mod analyze_logs; mod delete_on_reaction; mod eta; mod expand_link; -mod message_logger; pub mod pluralkit; mod support_onboard; @@ -48,28 +47,11 @@ pub async fn handle( return Ok(()); } - // store all new messages to monitor edits and deletes - message_logger::handle_create(data, new_message).await?; - eta::handle(ctx, new_message).await?; expand_link::handle(ctx, new_message).await?; analyze_logs::handle(ctx, new_message, data).await?; } - Event::MessageDelete { - channel_id, - deleted_message_id, - guild_id: _, - } => message_logger::handle_delete(ctx, data, channel_id, deleted_message_id).await?, - - Event::MessageUpdate { - old_if_available: _, - new: _, - event, - } => { - message_logger::handle_update(data, event).await?; - } - Event::ReactionAdd { add_reaction } => { delete_on_reaction::handle(ctx, add_reaction).await? } diff --git a/src/storage/message_logger.rs b/src/storage/message_logger.rs deleted file mode 100644 index 0ee2407..0000000 --- a/src/storage/message_logger.rs +++ /dev/null @@ -1,11 +0,0 @@ -use poise::serenity_prelude::UserId; -use redis_macros::{FromRedisValue, ToRedisArgs}; -use serde::{Deserialize, Serialize}; - -pub const MSG_LOG_KEY: &str = "message-log-v1"; - -#[derive(Clone, Debug, Deserialize, Serialize, FromRedisValue, ToRedisArgs)] -pub struct MessageLog { - pub author: UserId, - pub content: String, -} diff --git a/src/storage/mod.rs b/src/storage/mod.rs index 885817a..2cfbe33 100644 --- a/src/storage/mod.rs +++ b/src/storage/mod.rs @@ -2,12 +2,9 @@ use std::fmt::Debug; use color_eyre::eyre::Result; use log::*; -use poise::serenity_prelude::{ChannelId, MessageId, UserId}; +use poise::serenity_prelude::UserId; use redis::{AsyncCommands as _, Client, FromRedisValue, ToRedisArgs}; -pub mod message_logger; -use message_logger::*; - const PK_KEY: &str = "pluralkit-v1"; const LAUNCHER_VERSION_KEY: &str = "launcher-version-v1"; @@ -97,45 +94,6 @@ impl Storage { self.key_exists(&key).await } - pub async fn store_message( - &self, - channel_id: &ChannelId, - message_id: &MessageId, - content: String, - author: UserId, - ) -> Result<()> { - let key = format!("{MSG_LOG_KEY}:{channel_id}:{message_id}"); - - let val = MessageLog { author, content }; - - self.set_key(&key, val).await?; - self.expire_key(&key, 30 * 24 * 60 * 60).await?; // only store for 30 days - - Ok(()) - } - - pub async fn get_message( - &self, - channel_id: &ChannelId, - message_id: &MessageId, - ) -> Result { - let key = format!("{MSG_LOG_KEY}:{channel_id}:{message_id}"); - let res = self.get_key(&key).await?; - - Ok(res) - } - - pub async fn delete_message( - &self, - channel_id: &ChannelId, - message_id: &MessageId, - ) -> Result<()> { - let key = format!("{MSG_LOG_KEY}:{channel_id}:{message_id}"); - self.delete_key(&key).await?; - - Ok(()) - } - pub async fn cache_launcher_version(&self, version: &str) -> Result<()> { self.set_key(LAUNCHER_VERSION_KEY, version).await?; From f0550dd429b9fe8d25cc3b0117e27527471050f9 Mon Sep 17 00:00:00 2001 From: seth Date: Mon, 8 Jan 2024 14:56:37 -0500 Subject: [PATCH 40/90] style: use tabs over spaces --- .rustfmt.toml | 1 + build.rs | 66 ++--- src/api/dadjoke.rs | 18 +- src/api/mod.rs | 12 +- src/api/pluralkit.rs | 32 ++- src/api/prism_meta.rs | 46 +-- src/api/rory.rs | 50 ++-- src/commands/general/joke.rs | 6 +- src/commands/general/members.rs | 32 +-- src/commands/general/ping.rs | 4 +- src/commands/general/rory.rs | 34 +-- src/commands/general/say.rs | 76 ++--- src/commands/general/stars.rs | 40 +-- src/commands/general/tag.rs | 70 ++--- src/commands/mod.rs | 18 +- src/config/discord.rs | 108 +++---- src/config/github.rs | 88 +++--- src/config/mod.rs | 42 +-- src/consts.rs | 48 ++-- src/handlers/error.rs | 70 ++--- src/handlers/event/analyze_logs/issues.rs | 264 +++++++++--------- src/handlers/event/analyze_logs/mod.rs | 86 +++--- .../event/analyze_logs/providers/0x0.rs | 22 +- .../analyze_logs/providers/attachment.rs | 26 +- .../event/analyze_logs/providers/haste.rs | 26 +- .../event/analyze_logs/providers/mclogs.rs | 24 +- .../event/analyze_logs/providers/mod.rs | 32 +-- .../event/analyze_logs/providers/paste_gg.rs | 78 +++--- .../event/analyze_logs/providers/pastebin.rs | 26 +- src/handlers/event/delete_on_reaction.rs | 34 +-- src/handlers/event/eta.rs | 18 +- src/handlers/event/expand_link.rs | 36 +-- src/handlers/event/mod.rs | 76 ++--- src/handlers/event/pluralkit.rs | 44 +-- src/handlers/event/support_onboard.rs | 50 ++-- src/main.rs | 150 +++++----- src/storage/mod.rs | 142 +++++----- src/tags.rs | 14 +- src/utils/macros.rs | 6 +- src/utils/mod.rs | 10 +- src/utils/resolve_message.rs | 196 ++++++------- 41 files changed, 1112 insertions(+), 1109 deletions(-) create mode 100644 .rustfmt.toml diff --git a/.rustfmt.toml b/.rustfmt.toml new file mode 100644 index 0000000..218e203 --- /dev/null +++ b/.rustfmt.toml @@ -0,0 +1 @@ +hard_tabs = true diff --git a/build.rs b/build.rs index 1426363..5cd6329 100644 --- a/build.rs +++ b/build.rs @@ -8,15 +8,15 @@ include!("src/tags.rs"); /// generate the ChoiceParameter enum and tag data we will use in the `tag` command #[allow(dead_code)] fn main() { - let out_dir = env::var_os("OUT_DIR").unwrap(); - let generated = Path::new(&out_dir).join("generated.rs"); + let out_dir = env::var_os("OUT_DIR").unwrap(); + let generated = Path::new(&out_dir).join("generated.rs"); - let tag_files: Vec = fs::read_dir(TAG_DIR) - .unwrap() - .map(|f| f.unwrap().file_name().to_string_lossy().to_string()) - .collect(); + let tag_files: Vec = fs::read_dir(TAG_DIR) + .unwrap() + .map(|f| f.unwrap().file_name().to_string_lossy().to_string()) + .collect(); - let tags: Vec = tag_files + let tags: Vec = tag_files .clone() .into_iter() .map(|name| { @@ -45,23 +45,23 @@ fn main() { }) .collect(); - let formatted_names: Vec = tags - .iter() - .map(|t| t.file_name.replace(".md", "").replace('-', "_")) - .collect(); + let formatted_names: Vec = tags + .iter() + .map(|t| t.file_name.replace(".md", "").replace('-', "_")) + .collect(); - let tag_choice = format!( - r#" + let tag_choice = format!( + r#" #[allow(non_camel_case_types, clippy::upper_case_acronyms)] #[derive(Clone, Debug, poise::ChoiceParameter)] pub enum TagChoice {{ {} }}"#, - formatted_names.join(",\n") - ); + formatted_names.join(",\n") + ); - let to_str = format!( - r#" + let to_str = format!( + r#" impl TagChoice {{ fn as_str(&self) -> &str {{ match &self {{ @@ -70,22 +70,22 @@ fn main() { }} }} "#, - formatted_names - .iter() - .map(|n| { - let file_name = n.replace('_', "-") + ".md"; - format!("Self::{n} => \"{file_name}\",") - }) - .collect::>() - .join("\n") - ); + formatted_names + .iter() + .map(|n| { + let file_name = n.replace('_', "-") + ".md"; + format!("Self::{n} => \"{file_name}\",") + }) + .collect::>() + .join("\n") + ); - let contents = Vec::from([tag_choice, to_str]).join("\n\n"); + let contents = Vec::from([tag_choice, to_str]).join("\n\n"); - fs::write(generated, contents).unwrap(); - println!( - "cargo:rustc-env=TAGS={}", - // make sure we can deserialize with env! at runtime - serde_json::to_string(&tags).unwrap() - ); + fs::write(generated, contents).unwrap(); + println!( + "cargo:rustc-env=TAGS={}", + // make sure we can deserialize with env! at runtime + serde_json::to_string(&tags).unwrap() + ); } diff --git a/src/api/dadjoke.rs b/src/api/dadjoke.rs index 8daef2c..c0eed87 100644 --- a/src/api/dadjoke.rs +++ b/src/api/dadjoke.rs @@ -7,15 +7,15 @@ use reqwest::StatusCode; const DADJOKE: &str = "https://icanhazdadjoke.com"; pub async fn get_joke() -> Result { - let req = REQWEST_CLIENT.get(DADJOKE).build()?; + let req = REQWEST_CLIENT.get(DADJOKE).build()?; - debug!("Making request to {}", req.url()); - let resp = REQWEST_CLIENT.execute(req).await?; - let status = resp.status(); + debug!("Making request to {}", req.url()); + let resp = REQWEST_CLIENT.execute(req).await?; + let status = resp.status(); - if let StatusCode::OK = status { - Ok(resp.text().await?) - } else { - Err(eyre!("Couldn't get a joke!")) - } + if let StatusCode::OK = status { + Ok(resp.text().await?) + } else { + Err(eyre!("Couldn't get a joke!")) + } } diff --git a/src/api/mod.rs b/src/api/mod.rs index a76d5bc..5198953 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -6,14 +6,14 @@ pub mod prism_meta; pub mod rory; pub static USER_AGENT: Lazy = Lazy::new(|| { - let version = option_env!("CARGO_PKG_VERSION").unwrap_or("development"); + let version = option_env!("CARGO_PKG_VERSION").unwrap_or("development"); - format!("refraction/{version}") + format!("refraction/{version}") }); pub static REQWEST_CLIENT: Lazy = Lazy::new(|| { - reqwest::Client::builder() - .user_agent(USER_AGENT.to_string()) - .build() - .unwrap_or_default() + reqwest::Client::builder() + .user_agent(USER_AGENT.to_string()) + .build() + .unwrap_or_default() }); diff --git a/src/api/pluralkit.rs b/src/api/pluralkit.rs index 89cfb88..439670a 100644 --- a/src/api/pluralkit.rs +++ b/src/api/pluralkit.rs @@ -8,30 +8,32 @@ use serde::{Deserialize, Serialize}; #[derive(Clone, Debug, Serialize, Deserialize)] pub struct PluralKitMessage { - pub sender: String, + pub sender: String, } const PLURAL_KIT: &str = "https://api.pluralkit.me/v2"; const MESSAGES_ENDPOINT: &str = "/messages"; pub async fn get_sender(message_id: MessageId) -> Result { - let req = REQWEST_CLIENT - .get(format!("{PLURAL_KIT}{MESSAGES_ENDPOINT}/{message_id}")) - .build()?; + let req = REQWEST_CLIENT + .get(format!("{PLURAL_KIT}{MESSAGES_ENDPOINT}/{message_id}")) + .build()?; - debug!("Making request to {}", req.url()); - let resp = REQWEST_CLIENT.execute(req).await?; - let status = resp.status(); + debug!("Making request to {}", req.url()); + let resp = REQWEST_CLIENT.execute(req).await?; + let status = resp.status(); - if let StatusCode::OK = status { - let data = resp.json::().await?; - let id: u64 = data.sender.parse().wrap_err_with(|| format!("Couldn't parse response from PluralKit as a UserId! Here's the response:\n{data:#?}"))?; - let sender = UserId::from(id); + if let StatusCode::OK = status { + let data = resp.json::().await?; + let id: u64 = data.sender.parse().wrap_err_with(|| { + format!("Couldn't parse response from PluralKit as a UserId! Here's the response:\n{data:#?}") + })?; + let sender = UserId::from(id); - Ok(sender) - } else { - Err(eyre!( + Ok(sender) + } else { + Err(eyre!( "Failed to get PluralKit message information from {PLURAL_KIT}{MESSAGES_ENDPOINT}/{message_id} with {status}", )) - } + } } diff --git a/src/api/prism_meta.rs b/src/api/prism_meta.rs index 3a99e40..7e24d08 100644 --- a/src/api/prism_meta.rs +++ b/src/api/prism_meta.rs @@ -8,39 +8,39 @@ use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct MinecraftPackageJson { - pub format_version: u8, - pub name: String, - pub recommended: Vec, - pub uid: String, + pub format_version: u8, + pub name: String, + pub recommended: Vec, + pub uid: String, } const PRISM_META: &str = "https://meta.prismlauncher.org/v1"; const MINECRAFT_PACKAGEJSON_ENDPOINT: &str = "/net.minecraft/package.json"; pub async fn get_latest_minecraft_version() -> Result { - let req = REQWEST_CLIENT - .get(format!("{PRISM_META}{MINECRAFT_PACKAGEJSON_ENDPOINT}")) - .build()?; + let req = REQWEST_CLIENT + .get(format!("{PRISM_META}{MINECRAFT_PACKAGEJSON_ENDPOINT}")) + .build()?; - debug!("Making request to {}", req.url()); - let resp = REQWEST_CLIENT.execute(req).await?; - let status = resp.status(); + debug!("Making request to {}", req.url()); + let resp = REQWEST_CLIENT.execute(req).await?; + let status = resp.status(); - if let StatusCode::OK = status { - let data = resp - .json::() - .await - .wrap_err_with(|| "Couldn't parse Minecraft versions!")?; + if let StatusCode::OK = status { + let data = resp + .json::() + .await + .wrap_err_with(|| "Couldn't parse Minecraft versions!")?; - let version = data - .recommended - .first() - .ok_or_else(|| eyre!("Couldn't find latest version of Minecraft!"))?; + let version = data + .recommended + .first() + .ok_or_else(|| eyre!("Couldn't find latest version of Minecraft!"))?; - Ok(version.clone()) - } else { - Err(eyre!( + Ok(version.clone()) + } else { + Err(eyre!( "Failed to get latest Minecraft version from {PRISM_META}{MINECRAFT_PACKAGEJSON_ENDPOINT} with {status}", )) - } + } } diff --git a/src/api/rory.rs b/src/api/rory.rs index 399500c..e527bba 100644 --- a/src/api/rory.rs +++ b/src/api/rory.rs @@ -7,40 +7,40 @@ use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize)] pub struct RoryResponse { - pub id: u64, - pub url: String, - pub error: Option, + pub id: u64, + pub url: String, + pub error: Option, } const RORY: &str = "https://rory.cat"; const ENDPOINT: &str = "/purr"; pub async fn get_rory(id: Option) -> Result { - let target = id.map(|id| id.to_string()).unwrap_or_default(); + let target = id.map(|id| id.to_string()).unwrap_or_default(); - let req = REQWEST_CLIENT - .get(format!("{RORY}{ENDPOINT}/{target}")) - .build() - .wrap_err_with(|| "Couldn't build reqwest client!")?; + let req = REQWEST_CLIENT + .get(format!("{RORY}{ENDPOINT}/{target}")) + .build() + .wrap_err_with(|| "Couldn't build reqwest client!")?; - debug!("Making request to {}", req.url()); - let resp = REQWEST_CLIENT - .execute(req) - .await - .wrap_err_with(|| "Couldn't make request for rory!")?; + debug!("Making request to {}", req.url()); + let resp = REQWEST_CLIENT + .execute(req) + .await + .wrap_err_with(|| "Couldn't make request for rory!")?; - let status = resp.status(); + let status = resp.status(); - if let StatusCode::OK = status { - let data = resp - .json::() - .await - .wrap_err_with(|| "Couldn't parse the rory response!")?; + if let StatusCode::OK = status { + let data = resp + .json::() + .await + .wrap_err_with(|| "Couldn't parse the rory response!")?; - Ok(data) - } else { - Err(eyre!( - "Failed to get rory from {RORY}{ENDPOINT}/{target} with {status}", - )) - } + Ok(data) + } else { + Err(eyre!( + "Failed to get rory from {RORY}{ENDPOINT}/{target} with {status}", + )) + } } diff --git a/src/commands/general/joke.rs b/src/commands/general/joke.rs index 638db3e..f1790a0 100644 --- a/src/commands/general/joke.rs +++ b/src/commands/general/joke.rs @@ -6,8 +6,8 @@ use color_eyre::eyre::Result; /// It's a joke #[poise::command(slash_command, prefix_command)] pub async fn joke(ctx: Context<'_>) -> Result<()> { - let joke = dadjoke::get_joke().await?; + let joke = dadjoke::get_joke().await?; - ctx.reply(joke).await?; - Ok(()) + ctx.reply(joke).await?; + Ok(()) } diff --git a/src/commands/general/members.rs b/src/commands/general/members.rs index 5498dd0..a971e65 100644 --- a/src/commands/general/members.rs +++ b/src/commands/general/members.rs @@ -5,22 +5,22 @@ use color_eyre::eyre::{eyre, Result}; /// Returns the number of members in the server #[poise::command(slash_command, prefix_command)] pub async fn members(ctx: Context<'_>) -> Result<()> { - let guild = ctx.guild().ok_or_else(|| eyre!("Couldn't fetch guild!"))?; + let guild = ctx.guild().ok_or_else(|| eyre!("Couldn't fetch guild!"))?; - let count = guild.member_count; - let online = if let Some(count) = guild.approximate_presence_count { - count.to_string() - } else { - "Undefined".to_string() - }; + let count = guild.member_count; + let online = if let Some(count) = guild.approximate_presence_count { + count.to_string() + } else { + "Undefined".to_string() + }; - ctx.send(|m| { - m.embed(|e| { - e.title(format!("{count} total members!")) - .description(format!("{online} online members")) - .color(consts::COLORS["blue"]) - }) - }) - .await?; - Ok(()) + ctx.send(|m| { + m.embed(|e| { + e.title(format!("{count} total members!")) + .description(format!("{online} online members")) + .color(consts::COLORS["blue"]) + }) + }) + .await?; + Ok(()) } diff --git a/src/commands/general/ping.rs b/src/commands/general/ping.rs index 4563af3..41634dc 100644 --- a/src/commands/general/ping.rs +++ b/src/commands/general/ping.rs @@ -5,6 +5,6 @@ use color_eyre::eyre::Result; /// Replies with pong! #[poise::command(slash_command, prefix_command, ephemeral)] pub async fn ping(ctx: Context<'_>) -> Result<()> { - ctx.reply("Pong!").await?; - Ok(()) + ctx.reply("Pong!").await?; + Ok(()) } diff --git a/src/commands/general/rory.rs b/src/commands/general/rory.rs index 59d157e..f20700c 100644 --- a/src/commands/general/rory.rs +++ b/src/commands/general/rory.rs @@ -6,24 +6,24 @@ use color_eyre::eyre::Result; /// Gets a Rory photo! #[poise::command(slash_command, prefix_command)] pub async fn rory( - ctx: Context<'_>, - #[description = "specify a Rory ID"] id: Option, + ctx: Context<'_>, + #[description = "specify a Rory ID"] id: Option, ) -> Result<()> { - let rory = get_rory(id).await?; + let rory = get_rory(id).await?; - ctx.send(|m| { - m.embed(|e| { - if let Some(error) = rory.error { - e.title("Error!").description(error) - } else { - e.title("Rory :3") - .url(&rory.url) - .image(rory.url) - .footer(|f| f.text(format!("ID {}", rory.id))) - } - }) - }) - .await?; + ctx.send(|m| { + m.embed(|e| { + if let Some(error) = rory.error { + e.title("Error!").description(error) + } else { + e.title("Rory :3") + .url(&rory.url) + .image(rory.url) + .footer(|f| f.text(format!("ID {}", rory.id))) + } + }) + }) + .await?; - Ok(()) + Ok(()) } diff --git a/src/commands/general/say.rs b/src/commands/general/say.rs index 6439f4b..aad9836 100644 --- a/src/commands/general/say.rs +++ b/src/commands/general/say.rs @@ -4,48 +4,48 @@ use color_eyre::eyre::{eyre, Result}; /// Say something through the bot #[poise::command( - slash_command, - prefix_command, - ephemeral, - default_member_permissions = "MODERATE_MEMBERS", - required_permissions = "MODERATE_MEMBERS" + slash_command, + prefix_command, + ephemeral, + default_member_permissions = "MODERATE_MEMBERS", + required_permissions = "MODERATE_MEMBERS" )] pub async fn say(ctx: Context<'_>, #[description = "Just content?"] content: String) -> Result<()> { - let guild = ctx.guild().ok_or_else(|| eyre!("Couldn't get guild!"))?; - let channel = ctx - .guild_channel() - .await - .ok_or_else(|| eyre!("Couldn't get channel!"))?; + let guild = ctx.guild().ok_or_else(|| eyre!("Couldn't get guild!"))?; + let channel = ctx + .guild_channel() + .await + .ok_or_else(|| eyre!("Couldn't get channel!"))?; - ctx.defer_ephemeral().await?; - channel.say(ctx, &content).await?; - ctx.say("I said what you said!").await?; + ctx.defer_ephemeral().await?; + channel.say(ctx, &content).await?; + ctx.say("I said what you said!").await?; - if let Some(channel_id) = ctx.data().config.discord.channels.say_log_channel_id { - let log_channel = guild - .channels - .iter() - .find(|c| c.0 == &channel_id) - .ok_or_else(|| eyre!("Couldn't get log channel from guild!"))?; + if let Some(channel_id) = ctx.data().config.discord.channels.say_log_channel_id { + let log_channel = guild + .channels + .iter() + .find(|c| c.0 == &channel_id) + .ok_or_else(|| eyre!("Couldn't get log channel from guild!"))?; - log_channel - .1 - .clone() - .guild() - .ok_or_else(|| eyre!("Couldn't cast channel we found from guild as GuildChannel?????"))? - .send_message(ctx, |m| { - m.embed(|e| { - e.title("Say command used!") - .description(content) - .author(|a| { - a.name(ctx.author().tag()).icon_url( - ctx.author().avatar_url().unwrap_or("undefined".to_string()), - ) - }) - }) - }) - .await?; - } + log_channel + .1 + .clone() + .guild() + .ok_or_else(|| eyre!("Couldn't cast channel we found from guild as GuildChannel?????"))? + .send_message(ctx, |m| { + m.embed(|e| { + e.title("Say command used!") + .description(content) + .author(|a| { + a.name(ctx.author().tag()).icon_url( + ctx.author().avatar_url().unwrap_or("undefined".to_string()), + ) + }) + }) + }) + .await?; + } - Ok(()) + Ok(()) } diff --git a/src/commands/general/stars.rs b/src/commands/general/stars.rs index fef4fe8..470712f 100644 --- a/src/commands/general/stars.rs +++ b/src/commands/general/stars.rs @@ -5,27 +5,27 @@ use color_eyre::eyre::{Context as _, Result}; /// Returns GitHub stargazer count #[poise::command(slash_command, prefix_command)] pub async fn stars(ctx: Context<'_>) -> Result<()> { - let prismlauncher = ctx - .data() - .octocrab - .repos("PrismLauncher", "PrismLauncher") - .get() - .await - .wrap_err_with(|| "Couldn't get PrismLauncher/PrismLauncher from GitHub!")?; + let prismlauncher = ctx + .data() + .octocrab + .repos("PrismLauncher", "PrismLauncher") + .get() + .await + .wrap_err_with(|| "Couldn't get PrismLauncher/PrismLauncher from GitHub!")?; - let count = if let Some(count) = prismlauncher.stargazers_count { - count.to_string() - } else { - "undefined".to_string() - }; + let count = if let Some(count) = prismlauncher.stargazers_count { + count.to_string() + } else { + "undefined".to_string() + }; - ctx.send(|m| { - m.embed(|e| { - e.title(format!("⭐ {count} total stars!")) - .color(COLORS["yellow"]) - }) - }) - .await?; + ctx.send(|m| { + m.embed(|e| { + e.title(format!("⭐ {count} total stars!")) + .color(COLORS["yellow"]) + }) + }) + .await?; - Ok(()) + Ok(()) } diff --git a/src/commands/general/tag.rs b/src/commands/general/tag.rs index ff1138c..7d1c55f 100644 --- a/src/commands/general/tag.rs +++ b/src/commands/general/tag.rs @@ -13,48 +13,48 @@ static TAGS: Lazy> = Lazy::new(|| serde_json::from_str(env!("TAGS")).un /// Send a tag #[poise::command(slash_command, prefix_command)] pub async fn tag( - ctx: Context<'_>, - #[description = "the copypasta you want to send"] name: TagChoice, - user: Option, + ctx: Context<'_>, + #[description = "the copypasta you want to send"] name: TagChoice, + user: Option, ) -> Result<()> { - let tag_file = name.as_str(); - let tag = TAGS - .iter() - .find(|t| t.file_name == tag_file) - .ok_or_else(|| eyre!("Tried to get non-existent tag: {tag_file}"))?; + let tag_file = name.as_str(); + let tag = TAGS + .iter() + .find(|t| t.file_name == tag_file) + .ok_or_else(|| eyre!("Tried to get non-existent tag: {tag_file}"))?; - let frontmatter = &tag.frontmatter; + let frontmatter = &tag.frontmatter; - ctx.send(|m| { - if let Some(user) = user { - m.content(format!("<@{}>", user.id)); - } + ctx.send(|m| { + if let Some(user) = user { + m.content(format!("<@{}>", user.id)); + } - m.embed(|e| { - e.title(&frontmatter.title); - e.description(&tag.content); + m.embed(|e| { + e.title(&frontmatter.title); + e.description(&tag.content); - if let Some(color) = &frontmatter.color { - let color = *consts::COLORS - .get(color.as_str()) - .unwrap_or(&Color::default()); - e.color(color); - } + if let Some(color) = &frontmatter.color { + let color = *consts::COLORS + .get(color.as_str()) + .unwrap_or(&Color::default()); + e.color(color); + } - if let Some(image) = &frontmatter.image { - e.image(image); - } + if let Some(image) = &frontmatter.image { + e.image(image); + } - if let Some(fields) = &frontmatter.fields { - for field in fields { - e.field(&field.name, &field.value, field.inline); - } - } + if let Some(fields) = &frontmatter.fields { + for field in fields { + e.field(&field.name, &field.value, field.inline); + } + } - e - }) - }) - .await?; + e + }) + }) + .await?; - Ok(()) + Ok(()) } diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 45c23d6..0a2293f 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -6,13 +6,13 @@ use poise::Command; mod general; pub fn to_global_commands() -> Vec> { - vec![ - general::joke(), - general::members(), - general::ping(), - general::rory(), - general::say(), - general::stars(), - general::tag(), - ] + vec![ + general::joke(), + general::members(), + general::ping(), + general::rory(), + general::say(), + general::stars(), + general::tag(), + ] } diff --git a/src/config/discord.rs b/src/config/discord.rs index 77cc392..9897ef7 100644 --- a/src/config/discord.rs +++ b/src/config/discord.rs @@ -7,81 +7,81 @@ use url::Url; #[derive(Debug, Clone)] pub struct RefractionOAuth2 { - pub redirect_uri: Url, - pub scope: String, + pub redirect_uri: Url, + pub scope: String, } #[derive(Debug, Clone, Default)] pub struct RefractionChannels { - pub say_log_channel_id: Option, + pub say_log_channel_id: Option, } #[derive(Debug, Clone, Default)] pub struct DiscordConfig { - pub client_id: ApplicationId, - pub client_secret: String, - pub bot_token: String, - pub oauth2: RefractionOAuth2, - pub channels: RefractionChannels, + pub client_id: ApplicationId, + pub client_secret: String, + pub bot_token: String, + pub oauth2: RefractionOAuth2, + pub channels: RefractionChannels, } impl Default for RefractionOAuth2 { - fn default() -> Self { - Self { - scope: "identify connections role_connections.write".to_string(), - redirect_uri: Url::parse("https://google.com").unwrap(), - } - } + fn default() -> Self { + Self { + scope: "identify connections role_connections.write".to_string(), + redirect_uri: Url::parse("https://google.com").unwrap(), + } + } } impl RefractionOAuth2 { - pub fn new_from_env() -> Result { - let unparsed = format!("{}/oauth2/callback", required_var!("PUBLIC_URI")); - let redirect_uri = Url::parse(&unparsed)?; + pub fn new_from_env() -> Result { + let unparsed = format!("{}/oauth2/callback", required_var!("PUBLIC_URI")); + let redirect_uri = Url::parse(&unparsed)?; - debug!("OAuth2 Redirect URI is {redirect_uri}"); - Ok(Self { - redirect_uri, - ..Default::default() - }) - } + debug!("OAuth2 Redirect URI is {redirect_uri}"); + Ok(Self { + redirect_uri, + ..Default::default() + }) + } } impl RefractionChannels { - pub fn new_from_env() -> Result { - let unparsed = std::env::var("DISCORD_SAY_LOG_CHANNELID"); - if let Ok(unparsed) = unparsed { - let id = unparsed.parse::()?; - let channel_id = ChannelId::from(id); + pub fn new_from_env() -> Result { + let unparsed = std::env::var("DISCORD_SAY_LOG_CHANNELID"); + if let Ok(unparsed) = unparsed { + let id = unparsed.parse::()?; + let channel_id = ChannelId::from(id); - debug!("Log channel is {id}"); - Ok(Self { - say_log_channel_id: Some(channel_id), - }) - } else { - warn!("DISCORD_SAY_LOG_CHANNELID is empty; this will disable logging in your server."); - Ok(Self { - say_log_channel_id: None, - }) - } - } + debug!("Log channel is {id}"); + Ok(Self { + say_log_channel_id: Some(channel_id), + }) + } else { + warn!("DISCORD_SAY_LOG_CHANNELID is empty; this will disable logging in your server."); + Ok(Self { + say_log_channel_id: None, + }) + } + } } impl DiscordConfig { - pub fn new_from_env() -> Result { - let unparsed_client = required_var!("DISCORD_CLIENT_ID").parse::()?; - let client_id = ApplicationId::from(unparsed_client); - let client_secret = required_var!("DISCORD_CLIENT_SECRET"); - let bot_token = required_var!("DISCORD_BOT_TOKEN"); - let oauth2 = RefractionOAuth2::new_from_env()?; - let channels = RefractionChannels::new_from_env()?; + pub fn new_from_env() -> Result { + let unparsed_client = required_var!("DISCORD_CLIENT_ID").parse::()?; + let client_id = ApplicationId::from(unparsed_client); + let client_secret = required_var!("DISCORD_CLIENT_SECRET"); + let bot_token = required_var!("DISCORD_BOT_TOKEN"); + let oauth2 = RefractionOAuth2::new_from_env()?; + let channels = RefractionChannels::new_from_env()?; - Ok(Self { - client_id, - client_secret, - bot_token, - oauth2, - channels, - }) - } + Ok(Self { + client_id, + client_secret, + bot_token, + oauth2, + channels, + }) + } } diff --git a/src/config/github.rs b/src/config/github.rs index d9ca12e..d7b2c04 100644 --- a/src/config/github.rs +++ b/src/config/github.rs @@ -4,62 +4,62 @@ use crate::required_var; #[derive(Debug, Clone)] pub struct RefractionRepo { - pub owner: String, - pub repo: String, - pub key: String, - pub name: String, + pub owner: String, + pub repo: String, + pub key: String, + pub name: String, } #[derive(Debug, Clone)] pub struct GithubConfig { - pub token: String, - pub repos: Vec, - pub cache_sec: u16, - pub update_cron_job: String, + pub token: String, + pub repos: Vec, + pub cache_sec: u16, + pub update_cron_job: String, } impl Default for GithubConfig { - fn default() -> Self { - let owner = "PrismLauncher".to_string(); - let repos = Vec::::from([ - RefractionRepo { - owner: owner.clone(), - repo: "PrismLauncher".to_string(), - key: "launcher".to_string(), - name: "Launcher contributor".to_string(), - }, - RefractionRepo { - owner: owner.clone(), - repo: "prismlauncher.org".to_string(), + fn default() -> Self { + let owner = "PrismLauncher".to_string(); + let repos = Vec::::from([ + RefractionRepo { + owner: owner.clone(), + repo: "PrismLauncher".to_string(), + key: "launcher".to_string(), + name: "Launcher contributor".to_string(), + }, + RefractionRepo { + owner: owner.clone(), + repo: "prismlauncher.org".to_string(), - key: "website".to_string(), - name: "Web developer".to_string(), - }, - RefractionRepo { - owner: owner.clone(), - repo: "Translations".to_string(), + key: "website".to_string(), + name: "Web developer".to_string(), + }, + RefractionRepo { + owner: owner.clone(), + repo: "Translations".to_string(), - key: "translations".to_string(), - name: "Translator".to_string(), - }, - ]); + key: "translations".to_string(), + name: "Translator".to_string(), + }, + ]); - Self { - repos, - cache_sec: 3600, - update_cron_job: "0 */10 * * * *".to_string(), // every 10 minutes - token: String::default(), - } - } + Self { + repos, + cache_sec: 3600, + update_cron_job: "0 */10 * * * *".to_string(), // every 10 minutes + token: String::default(), + } + } } impl GithubConfig { - pub fn new_from_env() -> Result { - let token = required_var!("GITHUB_TOKEN"); + pub fn new_from_env() -> Result { + let token = required_var!("GITHUB_TOKEN"); - Ok(Self { - token, - ..Default::default() - }) - } + Ok(Self { + token, + ..Default::default() + }) + } } diff --git a/src/config/mod.rs b/src/config/mod.rs index a2bec63..b04938c 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -8,32 +8,32 @@ pub use github::*; #[derive(Debug, Clone)] pub struct Config { - pub discord: DiscordConfig, - pub github: GithubConfig, - pub http_port: u16, - pub redis_url: String, + pub discord: DiscordConfig, + pub github: GithubConfig, + pub http_port: u16, + pub redis_url: String, } impl Default for Config { - fn default() -> Self { - Self { - discord: DiscordConfig::default(), - github: GithubConfig::default(), - http_port: 3000, - redis_url: "redis://localhost:6379".to_string(), - } - } + fn default() -> Self { + Self { + discord: DiscordConfig::default(), + github: GithubConfig::default(), + http_port: 3000, + redis_url: "redis://localhost:6379".to_string(), + } + } } impl Config { - pub fn new_from_env() -> Result { - let discord = DiscordConfig::new_from_env()?; - let github = GithubConfig::new_from_env()?; + pub fn new_from_env() -> Result { + let discord = DiscordConfig::new_from_env()?; + let github = GithubConfig::new_from_env()?; - Ok(Self { - discord, - github, - ..Default::default() - }) - } + Ok(Self { + discord, + github, + ..Default::default() + }) + } } diff --git a/src/consts.rs b/src/consts.rs index 35fdb61..3e9f541 100644 --- a/src/consts.rs +++ b/src/consts.rs @@ -4,31 +4,31 @@ use once_cell::sync::Lazy; use poise::serenity_prelude::Color; pub static COLORS: Lazy> = Lazy::new(|| { - HashMap::from([ - ("red", Color::from((239, 68, 68))), - ("green", Color::from((34, 197, 94))), - ("blue", Color::from((96, 165, 250))), - ("yellow", Color::from((253, 224, 71))), - ("orange", Color::from((251, 146, 60))), - // TODO purple & pink :D - ]) + HashMap::from([ + ("red", Color::from((239, 68, 68))), + ("green", Color::from((34, 197, 94))), + ("blue", Color::from((96, 165, 250))), + ("yellow", Color::from((253, 224, 71))), + ("orange", Color::from((251, 146, 60))), + // TODO purple & pink :D + ]) }); pub const ETA_MESSAGES: [&str; 16] = [ - "Sometime", - "Some day", - "Not far", - "The future", - "Never", - "Perhaps tomorrow?", - "There are no ETAs", - "No", - "Nah", - "Yes", - "Yas", - "Next month", - "Next year", - "Next week", - "In Prism Launcher 2.0.0", - "At the appropriate juncture, in due course, in the fullness of time", + "Sometime", + "Some day", + "Not far", + "The future", + "Never", + "Perhaps tomorrow?", + "There are no ETAs", + "No", + "Nah", + "Yes", + "Yas", + "Next month", + "Next year", + "Next week", + "In Prism Launcher 2.0.0", + "At the appropriate juncture, in due course, in the fullness of time", ]; diff --git a/src/handlers/error.rs b/src/handlers/error.rs index 66277e0..12e4980 100644 --- a/src/handlers/error.rs +++ b/src/handlers/error.rs @@ -7,43 +7,43 @@ use poise::serenity_prelude::Timestamp; use poise::FrameworkError; pub async fn handle(error: FrameworkError<'_, Data, Report>) { - match error { - FrameworkError::Setup { - error, framework, .. - } => { - error!("Error setting up client! Bailing out"); - framework.shard_manager().lock().await.shutdown_all().await; + match error { + FrameworkError::Setup { + error, framework, .. + } => { + error!("Error setting up client! Bailing out"); + framework.shard_manager().lock().await.shutdown_all().await; - panic!("{error}") - } + panic!("{error}") + } - FrameworkError::Command { error, ctx } => { - error!("Error in command {}:\n{error:?}", ctx.command().name); - ctx.send(|c| { - c.embed(|e| { - e.title("Something went wrong!") - .description("oopsie") - .timestamp(Timestamp::now()) - .color(COLORS["red"]) - }) - }) - .await - .ok(); - } + FrameworkError::Command { error, ctx } => { + error!("Error in command {}:\n{error:?}", ctx.command().name); + ctx.send(|c| { + c.embed(|e| { + e.title("Something went wrong!") + .description("oopsie") + .timestamp(Timestamp::now()) + .color(COLORS["red"]) + }) + }) + .await + .ok(); + } - FrameworkError::EventHandler { - error, - ctx: _, - event, - framework: _, - } => { - error!("Error while handling event {}:\n{error:?}", event.name()); - } + FrameworkError::EventHandler { + error, + ctx: _, + event, + framework: _, + } => { + error!("Error while handling event {}:\n{error:?}", event.name()); + } - error => { - if let Err(e) = poise::builtins::on_error(error).await { - error!("Unhandled error occured:\n{e:#?}"); - } - } - } + error => { + if let Err(e) = poise::builtins::on_error(error).await { + error!("Unhandled error occured:\n{e:#?}"); + } + } + } } diff --git a/src/handlers/event/analyze_logs/issues.rs b/src/handlers/event/analyze_logs/issues.rs index 77acb2b..0f411e9 100644 --- a/src/handlers/event/analyze_logs/issues.rs +++ b/src/handlers/event/analyze_logs/issues.rs @@ -7,164 +7,164 @@ use regex::Regex; pub type Issue = Option<(String, String)>; pub async fn find_issues(log: &str, data: &Data) -> Result> { - let issues = [ - fabric_internal, - flatpak_nvidia, - forge_java, - intel_hd, - java_option, - lwjgl_2_java_9, - macos_ns, - oom, - optinotfine, - pre_1_12_native_transport_java_9, - wrong_java, - ]; + let issues = [ + fabric_internal, + flatpak_nvidia, + forge_java, + intel_hd, + java_option, + lwjgl_2_java_9, + macos_ns, + oom, + optinotfine, + pre_1_12_native_transport_java_9, + wrong_java, + ]; - let mut res: Vec<(String, String)> = issues.iter().filter_map(|issue| issue(log)).collect(); + let mut res: Vec<(String, String)> = issues.iter().filter_map(|issue| issue(log)).collect(); - if let Some(issues) = outdated_launcher(log, data).await? { - res.push(issues) - } + if let Some(issues) = outdated_launcher(log, data).await? { + res.push(issues) + } - Ok(res) + Ok(res) } fn fabric_internal(log: &str) -> Issue { - const CLASS_NOT_FOUND: &str = "Caused by: java.lang.ClassNotFoundException: "; + const CLASS_NOT_FOUND: &str = "Caused by: java.lang.ClassNotFoundException: "; - let issue = ( - "Fabric Internal Access".to_string(), - "The mod you are using is using fabric internals that are not meant \ + let issue = ( + "Fabric Internal Access".to_string(), + "The mod you are using is using fabric internals that are not meant \ to be used by anything but the loader itself. Those mods break both on Quilt and with fabric updates. If you're using fabric, downgrade your fabric loader could work, \ on Quilt you can try updating to the latest beta version, \ but there's nothing much to do unless the mod author stops using them." - .to_string(), - ); + .to_string(), + ); - let errors = [ - &format!("{CLASS_NOT_FOUND}net.fabricmc.fabric.impl"), - &format!("{CLASS_NOT_FOUND}net.fabricmc.fabric.mixin"), - &format!("{CLASS_NOT_FOUND}net.fabricmc.fabric.loader.impl"), - &format!("{CLASS_NOT_FOUND}net.fabricmc.fabric.loader.mixin"), - "org.quiltmc.loader.impl.FormattedException: java.lang.NoSuchMethodError:", - ]; + let errors = [ + &format!("{CLASS_NOT_FOUND}net.fabricmc.fabric.impl"), + &format!("{CLASS_NOT_FOUND}net.fabricmc.fabric.mixin"), + &format!("{CLASS_NOT_FOUND}net.fabricmc.fabric.loader.impl"), + &format!("{CLASS_NOT_FOUND}net.fabricmc.fabric.loader.mixin"), + "org.quiltmc.loader.impl.FormattedException: java.lang.NoSuchMethodError:", + ]; - let found = errors.iter().any(|e| log.contains(e)); - found.then_some(issue) + let found = errors.iter().any(|e| log.contains(e)); + found.then_some(issue) } fn flatpak_nvidia(log: &str) -> Issue { - let issue = ( - "Outdated Nvidia Flatpak Driver".to_string(), - "The Nvidia driver for flatpak is outdated. + let issue = ( + "Outdated Nvidia Flatpak Driver".to_string(), + "The Nvidia driver for flatpak is outdated. Please run `flatpak update` to fix this issue. \ If that does not solve it, \ please wait until the driver is added to Flathub and run it again." - .to_string(), - ); + .to_string(), + ); - let found = log.contains("org.lwjgl.LWJGLException: Could not choose GLX13 config") - || log.contains("GLFW error 65545: GLX: Failed to find a suitable GLXFBConfig"); + let found = log.contains("org.lwjgl.LWJGLException: Could not choose GLX13 config") + || log.contains("GLFW error 65545: GLX: Failed to find a suitable GLXFBConfig"); - found.then_some(issue) + found.then_some(issue) } fn forge_java(log: &str) -> Issue { - let issue = ( - "Forge Java Bug".to_string(), - "Old versions of Forge crash with Java 8u321+. + let issue = ( + "Forge Java Bug".to_string(), + "Old versions of Forge crash with Java 8u321+. To fix this, update forge to the latest version via the Versions tab (right click on Forge, click Change Version, and choose the latest one) Alternatively, you can download 8u312 or lower. \ See [archive](https://github.com/adoptium/temurin8-binaries/releases/tag/jdk8u312-b07)" - .to_string(), - ); + .to_string(), + ); - let found = log.contains("java.lang.NoSuchMethodError: sun.security.util.ManifestEntryVerifier.(Ljava/util/jar/Manifest;)V"); - found.then_some(issue) + let found = log.contains("java.lang.NoSuchMethodError: sun.security.util.ManifestEntryVerifier.(Ljava/util/jar/Manifest;)V"); + found.then_some(issue) } fn intel_hd(log: &str) -> Issue { - let issue = + let issue = ( "Intel HD Windows 10".to_string(), "Your drivers don't support windows 10 officially See https://prismlauncher.org/wiki/getting-started/installing-java/#a-note-about-intel-hd-20003000-on-windows-10 for more info".to_string() ); - let found = log.contains("java.lang.NoSuchMethodError: sun.security.util.ManifestEntryVerifier.(Ljava/util/jar/Manifest;)V"); - found.then_some(issue) + let found = log.contains("java.lang.NoSuchMethodError: sun.security.util.ManifestEntryVerifier.(Ljava/util/jar/Manifest;)V"); + found.then_some(issue) } fn java_option(log: &str) -> Issue { - static VM_OPTION_REGEX: Lazy = - Lazy::new(|| Regex::new(r"Unrecognized VM option '(.+)'[\r\n]").unwrap()); - static OPTION_REGEX: Lazy = - Lazy::new(|| Regex::new(r"Unrecognized option: (.+)[\r\n]").unwrap()); + static VM_OPTION_REGEX: Lazy = + Lazy::new(|| Regex::new(r"Unrecognized VM option '(.+)'[\r\n]").unwrap()); + static OPTION_REGEX: Lazy = + Lazy::new(|| Regex::new(r"Unrecognized option: (.+)[\r\n]").unwrap()); - if let Some(captures) = VM_OPTION_REGEX.captures(log) { - let title = if &captures[1] == "UseShenandoahGC" { - "Wrong Java Arguments" - } else { - "Java 8 and below don't support ShenandoahGC" - }; - return Some(( - title.to_string(), - format!("Remove `-XX:{}` from your Java arguments", &captures[1]), - )); - } + if let Some(captures) = VM_OPTION_REGEX.captures(log) { + let title = if &captures[1] == "UseShenandoahGC" { + "Wrong Java Arguments" + } else { + "Java 8 and below don't support ShenandoahGC" + }; + return Some(( + title.to_string(), + format!("Remove `-XX:{}` from your Java arguments", &captures[1]), + )); + } - if let Some(captures) = OPTION_REGEX.captures(log) { - return Some(( - "Wrong Java Arguments".to_string(), - format!("Remove `{}` from your Java arguments", &captures[1]), - )); - } + if let Some(captures) = OPTION_REGEX.captures(log) { + return Some(( + "Wrong Java Arguments".to_string(), + format!("Remove `{}` from your Java arguments", &captures[1]), + )); + } - None + None } fn lwjgl_2_java_9(log: &str) -> Issue { - let issue = ( - "Linux: crash with pre-1.13 and Java 9+".to_string(), - "Using pre-1.13 (which uses LWJGL 2) with Java 9 or later usually causes a crash. \ + let issue = ( + "Linux: crash with pre-1.13 and Java 9+".to_string(), + "Using pre-1.13 (which uses LWJGL 2) with Java 9 or later usually causes a crash. \ Switching to Java 8 or below will fix your issue. Alternatively, you can use [Temurin](https://adoptium.net/temurin/releases). \ However, multiplayer will not work in versions from 1.8 to 1.11. For more information, type `/tag java`." - .to_string(), - ); + .to_string(), + ); - let found = log.contains("check_match: Assertion `version->filename == NULL || ! _dl_name_match_p (version->filename, map)' failed!"); - found.then_some(issue) + let found = log.contains("check_match: Assertion `version->filename == NULL || ! _dl_name_match_p (version->filename, map)' failed!"); + found.then_some(issue) } fn macos_ns(log: &str) -> Issue { - let issue = ( + let issue = ( "MacOS NSInternalInconsistencyException".to_string(), "You need to downgrade your Java 8 version. See https://prismlauncher.org/wiki/getting-started/installing-java/#older-minecraft-on-macos".to_string() ); - let found = - log.contains("Terminating app due to uncaught exception 'NSInternalInconsistencyException"); - found.then_some(issue) + let found = + log.contains("Terminating app due to uncaught exception 'NSInternalInconsistencyException"); + found.then_some(issue) } fn oom(log: &str) -> Issue { - let issue = ( - "Out of Memory".to_string(), - "Allocating more RAM to your instance could help prevent this crash.".to_string(), - ); + let issue = ( + "Out of Memory".to_string(), + "Allocating more RAM to your instance could help prevent this crash.".to_string(), + ); - let found = log.contains("java.lang.OutOfMemoryError"); - found.then_some(issue) + let found = log.contains("java.lang.OutOfMemoryError"); + found.then_some(issue) } fn optinotfine(log: &str) -> Issue { - let issue = ( + let issue = ( "Potential OptiFine Incompatibilities".to_string(), "OptiFine is known to cause problems when paired with other mods. \ Try to disable OptiFine and see if the issue persists. @@ -172,50 +172,50 @@ fn optinotfine(log: &str) -> Issue { .to_string(), ); - let found = log.contains("[✔] OptiFine_") || log.contains("[✔] optifabric-"); - found.then_some(issue) + let found = log.contains("[✔] OptiFine_") || log.contains("[✔] optifabric-"); + found.then_some(issue) } async fn outdated_launcher(log: &str, data: &Data) -> Result { - static OUTDATED_LAUNCHER_REGEX: Lazy = - Lazy::new(|| Regex::new("Prism Launcher version: [0-9].[0-9].[0-9]").unwrap()); + static OUTDATED_LAUNCHER_REGEX: Lazy = + Lazy::new(|| Regex::new("Prism Launcher version: [0-9].[0-9].[0-9]").unwrap()); - let Some(captures) = OUTDATED_LAUNCHER_REGEX.captures(log) else { - return Ok(None); - }; + let Some(captures) = OUTDATED_LAUNCHER_REGEX.captures(log) else { + return Ok(None); + }; - let version_from_log = captures[0].replace("Prism Launcher version: ", ""); + let version_from_log = captures[0].replace("Prism Launcher version: ", ""); - let storage = &data.storage; - let latest_version = if storage.launcher_version_is_cached().await? { - storage.get_launcher_version().await? - } else { - let version = data - .octocrab - .repos("PrismLauncher", "PrismLauncher") - .releases() - .get_latest() - .await? - .tag_name; + let storage = &data.storage; + let latest_version = if storage.launcher_version_is_cached().await? { + storage.get_launcher_version().await? + } else { + let version = data + .octocrab + .repos("PrismLauncher", "PrismLauncher") + .releases() + .get_latest() + .await? + .tag_name; - storage.cache_launcher_version(&version).await?; - version - }; + storage.cache_launcher_version(&version).await?; + version + }; - if version_from_log < latest_version { - let issue = ( + if version_from_log < latest_version { + let issue = ( "Outdated Prism Launcher".to_string(), format!("Your installed version is {version_from_log}, while the newest version is {latest_version}.\nPlease update, for more info see https://prismlauncher.org/download/") ); - Ok(Some(issue)) - } else { - Ok(None) - } + Ok(Some(issue)) + } else { + Ok(None) + } } fn pre_1_12_native_transport_java_9(log: &str) -> Issue { - let issue = ( + let issue = ( "Linux: broken multiplayer with 1.8-1.11 and Java 9+".to_string(), "These versions of Minecraft use an outdated version of Netty which does not properly support Java 9. @@ -229,33 +229,33 @@ which is why the issue was not present." .to_string(), ); - let found = log.contains( + let found = log.contains( "java.lang.RuntimeException: Unable to access address of buffer\n\tat io.netty.channel.epoll" ); - found.then_some(issue) + found.then_some(issue) } fn wrong_java(log: &str) -> Issue { - static SWITCH_VERSION_REGEX: Lazy = Lazy::new(|| { - Regex::new( + static SWITCH_VERSION_REGEX: Lazy = Lazy::new(|| { + Regex::new( r"(?m)Please switch to one of the following Java versions for this instance:[\r\n]+(Java version [\d.]+)", ).unwrap() - }); + }); - if let Some(captures) = SWITCH_VERSION_REGEX.captures(log) { - let versions = captures[1].split('\n').collect::>().join(", "); - return Some(( + if let Some(captures) = SWITCH_VERSION_REGEX.captures(log) { + let versions = captures[1].split('\n').collect::>().join(", "); + return Some(( "Wrong Java Version".to_string(), format!("Please switch to one of the following: `{versions}`\nFor more information, type `/tag java`"), )); - } + } - let issue = ( + let issue = ( "Java compatibility check skipped".to_string(), "The Java major version may not work with your Minecraft instance. Please switch to a compatible version".to_string() ); - log.contains("Java major version is incompatible. Things might break.") - .then_some(issue) + log.contains("Java major version is incompatible. Things might break.") + .then_some(issue) } diff --git a/src/handlers/event/analyze_logs/mod.rs b/src/handlers/event/analyze_logs/mod.rs index 0e12127..80c660d 100644 --- a/src/handlers/event/analyze_logs/mod.rs +++ b/src/handlers/event/analyze_logs/mod.rs @@ -12,57 +12,57 @@ use issues::find_issues; use providers::find_log; pub async fn handle(ctx: &Context, message: &Message, data: &Data) -> Result<()> { - let channel = message.channel_id; + let channel = message.channel_id; - let log = find_log(message).await; + let log = find_log(message).await; - if log.is_err() { - channel - .send_message(ctx, |m| { - m.reference_message(message) - .allowed_mentions(|am| am.replied_user(true)) - .embed(|e| { - e.title("Analyze failed!") - .description("Couldn't download log") - }) - }) - .await?; + if log.is_err() { + channel + .send_message(ctx, |m| { + m.reference_message(message) + .allowed_mentions(|am| am.replied_user(true)) + .embed(|e| { + e.title("Analyze failed!") + .description("Couldn't download log") + }) + }) + .await?; - return Ok(()); - } + return Ok(()); + } - let Some(log) = log? else { - debug!("No log found in message! Skipping analysis"); - return Ok(()); - }; + let Some(log) = log? else { + debug!("No log found in message! Skipping analysis"); + return Ok(()); + }; - let issues = find_issues(&log, data).await?; + let issues = find_issues(&log, data).await?; - channel - .send_message(ctx, |m| { - m.reference_message(message) - .allowed_mentions(|am| am.replied_user(true)) - .embed(|e| { - e.title("Log analysis"); + channel + .send_message(ctx, |m| { + m.reference_message(message) + .allowed_mentions(|am| am.replied_user(true)) + .embed(|e| { + e.title("Log analysis"); - if issues.is_empty() { - e.color(COLORS["green"]).field( - "Analyze failed!", - "No issues found automatically", - false, - ); - } else { - e.color(COLORS["red"]); + if issues.is_empty() { + e.color(COLORS["green"]).field( + "Analyze failed!", + "No issues found automatically", + false, + ); + } else { + e.color(COLORS["red"]); - for (title, description) in issues { - e.field(title, description, false); - } - } + for (title, description) in issues { + e.field(title, description, false); + } + } - e - }) - }) - .await?; + e + }) + }) + .await?; - Ok(()) + Ok(()) } diff --git a/src/handlers/event/analyze_logs/providers/0x0.rs b/src/handlers/event/analyze_logs/providers/0x0.rs index c958389..9a81771 100644 --- a/src/handlers/event/analyze_logs/providers/0x0.rs +++ b/src/handlers/event/analyze_logs/providers/0x0.rs @@ -8,17 +8,17 @@ use reqwest::StatusCode; static REGEX: Lazy = Lazy::new(|| Regex::new(r"https://0x0\.st/\w*\.\w*").unwrap()); pub async fn find(content: &str) -> Result> { - let Some(url) = REGEX.find(content).map(|m| &content[m.range()]) else { - return Ok(None); - }; + let Some(url) = REGEX.find(content).map(|m| &content[m.range()]) else { + return Ok(None); + }; - let request = REQWEST_CLIENT.get(url).build()?; - let response = REQWEST_CLIENT.execute(request).await?; - let status = response.status(); + let request = REQWEST_CLIENT.get(url).build()?; + let response = REQWEST_CLIENT.execute(request).await?; + let status = response.status(); - if let StatusCode::OK = status { - Ok(Some(response.text().await?)) - } else { - Err(eyre!("Failed to fetch paste from {url} with {status}",)) - } + if let StatusCode::OK = status { + Ok(Some(response.text().await?)) + } else { + Err(eyre!("Failed to fetch paste from {url} with {status}",)) + } } diff --git a/src/handlers/event/analyze_logs/providers/attachment.rs b/src/handlers/event/analyze_logs/providers/attachment.rs index 71d8d65..b69640b 100644 --- a/src/handlers/event/analyze_logs/providers/attachment.rs +++ b/src/handlers/event/analyze_logs/providers/attachment.rs @@ -2,17 +2,17 @@ use color_eyre::eyre::Result; use poise::serenity_prelude::Message; pub async fn find(message: &Message) -> Result> { - // find first uploaded text file - if let Some(attachment) = message.attachments.iter().find(|a| { - a.content_type - .as_ref() - .and_then(|ct| ct.starts_with("text/").then_some(true)) - .is_some() - }) { - let bytes = attachment.download().await?; - let res = String::from_utf8(bytes)?; - Ok(Some(res)) - } else { - Ok(None) - } + // find first uploaded text file + if let Some(attachment) = message.attachments.iter().find(|a| { + a.content_type + .as_ref() + .and_then(|ct| ct.starts_with("text/").then_some(true)) + .is_some() + }) { + let bytes = attachment.download().await?; + let res = String::from_utf8(bytes)?; + Ok(Some(res)) + } else { + Ok(None) + } } diff --git a/src/handlers/event/analyze_logs/providers/haste.rs b/src/handlers/event/analyze_logs/providers/haste.rs index 525da1b..885d70e 100644 --- a/src/handlers/event/analyze_logs/providers/haste.rs +++ b/src/handlers/event/analyze_logs/providers/haste.rs @@ -6,21 +6,21 @@ use regex::Regex; use reqwest::StatusCode; static REGEX: Lazy = - Lazy::new(|| Regex::new(r"https://hst\.sh(?:/raw)?/(\w+(?:\.\w*)?)").unwrap()); + Lazy::new(|| Regex::new(r"https://hst\.sh(?:/raw)?/(\w+(?:\.\w*)?)").unwrap()); pub async fn find(content: &str) -> Result> { - let Some(captures) = REGEX.captures(content) else { - return Ok(None); - }; + let Some(captures) = REGEX.captures(content) else { + return Ok(None); + }; - let url = format!("https://hst.sh/raw/{}", &captures[1]); - let request = REQWEST_CLIENT.get(&url).build()?; - let response = REQWEST_CLIENT.execute(request).await?; - let status = response.status(); + let url = format!("https://hst.sh/raw/{}", &captures[1]); + let request = REQWEST_CLIENT.get(&url).build()?; + let response = REQWEST_CLIENT.execute(request).await?; + let status = response.status(); - if let StatusCode::OK = status { - Ok(Some(response.text().await?)) - } else { - Err(eyre!("Failed to fetch paste from {url} with {status}")) - } + if let StatusCode::OK = status { + Ok(Some(response.text().await?)) + } else { + Err(eyre!("Failed to fetch paste from {url} with {status}")) + } } diff --git a/src/handlers/event/analyze_logs/providers/mclogs.rs b/src/handlers/event/analyze_logs/providers/mclogs.rs index c22f0c6..6663940 100644 --- a/src/handlers/event/analyze_logs/providers/mclogs.rs +++ b/src/handlers/event/analyze_logs/providers/mclogs.rs @@ -8,18 +8,18 @@ use reqwest::StatusCode; static REGEX: Lazy = Lazy::new(|| Regex::new(r"https://mclo\.gs/(\w+)").unwrap()); pub async fn find(content: &str) -> Result> { - let Some(captures) = REGEX.captures(content) else { - return Ok(None); - }; + let Some(captures) = REGEX.captures(content) else { + return Ok(None); + }; - let url = format!("https://api.mclo.gs/1/raw/{}", &captures[1]); - let request = REQWEST_CLIENT.get(&url).build()?; - let response = REQWEST_CLIENT.execute(request).await?; - let status = response.status(); + let url = format!("https://api.mclo.gs/1/raw/{}", &captures[1]); + let request = REQWEST_CLIENT.get(&url).build()?; + let response = REQWEST_CLIENT.execute(request).await?; + let status = response.status(); - if let StatusCode::OK = status { - Ok(Some(response.text().await?)) - } else { - Err(eyre!("Failed to fetch log from {url} with {status}")) - } + if let StatusCode::OK = status { + Ok(Some(response.text().await?)) + } else { + Err(eyre!("Failed to fetch log from {url} with {status}")) + } } diff --git a/src/handlers/event/analyze_logs/providers/mod.rs b/src/handlers/event/analyze_logs/providers/mod.rs index 3ebeb9a..8185056 100644 --- a/src/handlers/event/analyze_logs/providers/mod.rs +++ b/src/handlers/event/analyze_logs/providers/mod.rs @@ -12,22 +12,22 @@ mod pastebin; pub type LogProvider = Result>; pub async fn find_log(message: &Message) -> LogProvider { - macro_rules! provider_impl { - ($provider:ident) => { - if let Some(content) = $provider::find(&message.content).await? { - return Ok(Some(content)); - } - }; - } - provider_impl!(_0x0); - provider_impl!(mclogs); - provider_impl!(haste); - provider_impl!(paste_gg); - provider_impl!(pastebin); + macro_rules! provider_impl { + ($provider:ident) => { + if let Some(content) = $provider::find(&message.content).await? { + return Ok(Some(content)); + } + }; + } + provider_impl!(_0x0); + provider_impl!(mclogs); + provider_impl!(haste); + provider_impl!(paste_gg); + provider_impl!(pastebin); - if let Some(content) = attachment::find(message).await? { - return Ok(Some(content)); - } + if let Some(content) = attachment::find(message).await? { + return Ok(Some(content)); + } - Ok(None) + Ok(None) } diff --git a/src/handlers/event/analyze_logs/providers/paste_gg.rs b/src/handlers/event/analyze_logs/providers/paste_gg.rs index da52453..a5a53c9 100644 --- a/src/handlers/event/analyze_logs/providers/paste_gg.rs +++ b/src/handlers/event/analyze_logs/providers/paste_gg.rs @@ -12,59 +12,59 @@ static REGEX: Lazy = Lazy::new(|| Regex::new(r"https://paste.gg/p/\w+/(\w #[derive(Clone, Debug, Deserialize, Serialize)] struct PasteResponse { - status: String, - result: Option>, - error: Option, - message: Option, + status: String, + result: Option>, + error: Option, + message: Option, } #[derive(Clone, Debug, Deserialize, Serialize)] struct PasteResult { - id: String, - name: Option, - description: Option, - visibility: Option, + id: String, + name: Option, + description: Option, + visibility: Option, } pub async fn find(content: &str) -> Result> { - let Some(captures) = REGEX.captures(content) else { - return Ok(None); - }; + let Some(captures) = REGEX.captures(content) else { + return Ok(None); + }; - let paste_id = &captures[1]; - let files_url = format!("{PASTE_GG}{PASTES_ENDPOINT}/{paste_id}/files"); + let paste_id = &captures[1]; + let files_url = format!("{PASTE_GG}{PASTES_ENDPOINT}/{paste_id}/files"); - let resp = REQWEST_CLIENT - .execute(REQWEST_CLIENT.get(&files_url).build()?) - .await?; - let status = resp.status(); + let resp = REQWEST_CLIENT + .execute(REQWEST_CLIENT.get(&files_url).build()?) + .await?; + let status = resp.status(); - if resp.status() != StatusCode::OK { - return Err(eyre!( - "Couldn't get paste {paste_id} from {PASTE_GG} with status {status}!" - )); - } + if resp.status() != StatusCode::OK { + return Err(eyre!( + "Couldn't get paste {paste_id} from {PASTE_GG} with status {status}!" + )); + } - let paste_files: PasteResponse = resp.json().await?; - let file_id = &paste_files - .result - .ok_or_else(|| eyre!("Couldn't find any files associated with paste {paste_id}!"))?[0] - .id; + let paste_files: PasteResponse = resp.json().await?; + let file_id = &paste_files + .result + .ok_or_else(|| eyre!("Couldn't find any files associated with paste {paste_id}!"))?[0] + .id; - let raw_url = format!("{PASTE_GG}{PASTES_ENDPOINT}/{paste_id}/files/{file_id}/raw"); + let raw_url = format!("{PASTE_GG}{PASTES_ENDPOINT}/{paste_id}/files/{file_id}/raw"); - let resp = REQWEST_CLIENT - .execute(REQWEST_CLIENT.get(&raw_url).build()?) - .await?; - let status = resp.status(); + let resp = REQWEST_CLIENT + .execute(REQWEST_CLIENT.get(&raw_url).build()?) + .await?; + let status = resp.status(); - if status != StatusCode::OK { - return Err(eyre!( - "Couldn't get file {file_id} from paste {paste_id} with status {status}!" - )); - } + if status != StatusCode::OK { + return Err(eyre!( + "Couldn't get file {file_id} from paste {paste_id} with status {status}!" + )); + } - let text = resp.text().await?; + let text = resp.text().await?; - Ok(Some(text)) + Ok(Some(text)) } diff --git a/src/handlers/event/analyze_logs/providers/pastebin.rs b/src/handlers/event/analyze_logs/providers/pastebin.rs index b8dc0bb..0d870ab 100644 --- a/src/handlers/event/analyze_logs/providers/pastebin.rs +++ b/src/handlers/event/analyze_logs/providers/pastebin.rs @@ -6,21 +6,21 @@ use regex::Regex; use reqwest::StatusCode; static REGEX: Lazy = - Lazy::new(|| Regex::new(r"https://pastebin\.com(?:/raw)?/(\w+)").unwrap()); + Lazy::new(|| Regex::new(r"https://pastebin\.com(?:/raw)?/(\w+)").unwrap()); pub async fn find(content: &str) -> Result> { - let Some(captures) = REGEX.captures(content) else { - return Ok(None); - }; + let Some(captures) = REGEX.captures(content) else { + return Ok(None); + }; - let url = format!("https://pastebin.com/raw/{}", &captures[1]); - let request = REQWEST_CLIENT.get(&url).build()?; - let response = REQWEST_CLIENT.execute(request).await?; - let status = response.status(); + let url = format!("https://pastebin.com/raw/{}", &captures[1]); + let request = REQWEST_CLIENT.get(&url).build()?; + let response = REQWEST_CLIENT.execute(request).await?; + let status = response.status(); - if let StatusCode::OK = status { - Ok(Some(response.text().await?)) - } else { - Err(eyre!("Failed to fetch paste from {url} with {status}")) - } + if let StatusCode::OK = status { + Ok(Some(response.text().await?)) + } else { + Err(eyre!("Failed to fetch paste from {url} with {status}")) + } } diff --git a/src/handlers/event/delete_on_reaction.rs b/src/handlers/event/delete_on_reaction.rs index dddcc9a..c17deb5 100644 --- a/src/handlers/event/delete_on_reaction.rs +++ b/src/handlers/event/delete_on_reaction.rs @@ -2,24 +2,24 @@ use color_eyre::eyre::{Context as _, Result}; use poise::serenity_prelude::{Context, InteractionType, Reaction}; pub async fn handle(ctx: &Context, reaction: &Reaction) -> Result<()> { - let user = reaction - .user(ctx) - .await - .wrap_err_with(|| "Couldn't fetch user from reaction!")?; + let user = reaction + .user(ctx) + .await + .wrap_err_with(|| "Couldn't fetch user from reaction!")?; - let message = reaction - .message(ctx) - .await - .wrap_err_with(|| "Couldn't fetch message from reaction!")?; + let message = reaction + .message(ctx) + .await + .wrap_err_with(|| "Couldn't fetch message from reaction!")?; - if let Some(interaction) = &message.interaction { - if interaction.kind == InteractionType::ApplicationCommand - && interaction.user == user - && reaction.emoji.unicode_eq("❌") - { - message.delete(ctx).await?; - } - } + if let Some(interaction) = &message.interaction { + if interaction.kind == InteractionType::ApplicationCommand + && interaction.user == user + && reaction.emoji.unicode_eq("❌") + { + message.delete(ctx).await?; + } + } - Ok(()) + Ok(()) } diff --git a/src/handlers/event/eta.rs b/src/handlers/event/eta.rs index e62d22a..8480189 100644 --- a/src/handlers/event/eta.rs +++ b/src/handlers/event/eta.rs @@ -8,15 +8,15 @@ use regex::Regex; static ETA_REGEX: Lazy = Lazy::new(|| Regex::new(r"\beta\b").unwrap()); pub async fn handle(ctx: &Context, message: &Message) -> Result<()> { - if !ETA_REGEX.is_match(&message.content) { - return Ok(()); - } + if !ETA_REGEX.is_match(&message.content) { + return Ok(()); + } - let response = format!( - "{} <:pofat:1031701005559144458>", - utils::random_choice(consts::ETA_MESSAGES)? - ); + let response = format!( + "{} <:pofat:1031701005559144458>", + utils::random_choice(consts::ETA_MESSAGES)? + ); - message.reply(ctx, response).await?; - Ok(()) + message.reply(ctx, response).await?; + Ok(()) } diff --git a/src/handlers/event/expand_link.rs b/src/handlers/event/expand_link.rs index 0863115..30504f1 100644 --- a/src/handlers/event/expand_link.rs +++ b/src/handlers/event/expand_link.rs @@ -4,25 +4,25 @@ use poise::serenity_prelude::{Context, Message}; use crate::utils; pub async fn handle(ctx: &Context, message: &Message) -> Result<()> { - let embeds = utils::resolve_message(ctx, message).await?; + let embeds = utils::resolve_message(ctx, message).await?; - // TOOD getchoo: actually reply to user - // ...not sure why Message doesn't give me a builder in reply() or equivalents - let our_channel = message - .channel(ctx) - .await - .wrap_err_with(|| "Couldn't get channel from message!")? - .guild() - .ok_or_else(|| eyre!("Couldn't convert to GuildChannel!"))?; + // TOOD getchoo: actually reply to user + // ...not sure why Message doesn't give me a builder in reply() or equivalents + let our_channel = message + .channel(ctx) + .await + .wrap_err_with(|| "Couldn't get channel from message!")? + .guild() + .ok_or_else(|| eyre!("Couldn't convert to GuildChannel!"))?; - if !embeds.is_empty() { - our_channel - .send_message(ctx, |m| { - m.set_embeds(embeds) - .allowed_mentions(|am| am.replied_user(false)) - }) - .await?; - } + if !embeds.is_empty() { + our_channel + .send_message(ctx, |m| { + m.set_embeds(embeds) + .allowed_mentions(|am| am.replied_user(false)) + }) + .await?; + } - Ok(()) + Ok(()) } diff --git a/src/handlers/event/mod.rs b/src/handlers/event/mod.rs index 57f614a..8d82984 100644 --- a/src/handlers/event/mod.rs +++ b/src/handlers/event/mod.rs @@ -13,53 +13,53 @@ pub mod pluralkit; mod support_onboard; pub async fn handle( - ctx: &Context, - event: &Event<'_>, - _framework: FrameworkContext<'_, Data, Report>, - data: &Data, + ctx: &Context, + event: &Event<'_>, + _framework: FrameworkContext<'_, Data, Report>, + data: &Data, ) -> Result<()> { - match event { - Event::Ready { data_about_bot } => { - info!("Logged in as {}!", data_about_bot.user.name); + match event { + Event::Ready { data_about_bot } => { + info!("Logged in as {}!", data_about_bot.user.name); - let latest_minecraft_version = api::prism_meta::get_latest_minecraft_version().await?; - let activity = Activity::playing(format!("Minecraft {}", latest_minecraft_version)); + let latest_minecraft_version = api::prism_meta::get_latest_minecraft_version().await?; + let activity = Activity::playing(format!("Minecraft {}", latest_minecraft_version)); - info!("Setting presence to activity {activity:#?}"); - ctx.set_presence(Some(activity), OnlineStatus::Online).await; - } + info!("Setting presence to activity {activity:#?}"); + ctx.set_presence(Some(activity), OnlineStatus::Online).await; + } - Event::Message { new_message } => { - // ignore new messages from bots - // NOTE: the webhook_id check allows us to still respond to PK users - if new_message.author.bot && new_message.webhook_id.is_none() { - debug!("Ignoring message {} from bot", new_message.id); - return Ok(()); - } + Event::Message { new_message } => { + // ignore new messages from bots + // NOTE: the webhook_id check allows us to still respond to PK users + if new_message.author.bot && new_message.webhook_id.is_none() { + debug!("Ignoring message {} from bot", new_message.id); + return Ok(()); + } - // detect PK users first to make sure we don't respond to unproxied messages - pluralkit::handle(ctx, new_message, data).await?; + // detect PK users first to make sure we don't respond to unproxied messages + pluralkit::handle(ctx, new_message, data).await?; - if data.storage.is_user_plural(new_message.author.id).await? - && pluralkit::is_message_proxied(new_message).await? - { - debug!("Not replying to unproxied PluralKit message"); - return Ok(()); - } + if data.storage.is_user_plural(new_message.author.id).await? + && pluralkit::is_message_proxied(new_message).await? + { + debug!("Not replying to unproxied PluralKit message"); + return Ok(()); + } - eta::handle(ctx, new_message).await?; - expand_link::handle(ctx, new_message).await?; - analyze_logs::handle(ctx, new_message, data).await?; - } + eta::handle(ctx, new_message).await?; + expand_link::handle(ctx, new_message).await?; + analyze_logs::handle(ctx, new_message, data).await?; + } - Event::ReactionAdd { add_reaction } => { - delete_on_reaction::handle(ctx, add_reaction).await? - } + Event::ReactionAdd { add_reaction } => { + delete_on_reaction::handle(ctx, add_reaction).await? + } - Event::ThreadCreate { thread } => support_onboard::handle(ctx, thread).await?, + Event::ThreadCreate { thread } => support_onboard::handle(ctx, thread).await?, - _ => {} - } + _ => {} + } - Ok(()) + Ok(()) } diff --git a/src/handlers/event/pluralkit.rs b/src/handlers/event/pluralkit.rs index b44aff0..0fdaf3c 100644 --- a/src/handlers/event/pluralkit.rs +++ b/src/handlers/event/pluralkit.rs @@ -9,34 +9,34 @@ use tokio::time::sleep; const PK_DELAY_SEC: Duration = Duration::from_secs(1000); pub async fn is_message_proxied(message: &Message) -> Result { - debug!( - "Waiting on PluralKit API for {} seconds", - PK_DELAY_SEC.as_secs() - ); - sleep(PK_DELAY_SEC).await; + debug!( + "Waiting on PluralKit API for {} seconds", + PK_DELAY_SEC.as_secs() + ); + sleep(PK_DELAY_SEC).await; - let proxied = api::pluralkit::get_sender(message.id).await.is_ok(); + let proxied = api::pluralkit::get_sender(message.id).await.is_ok(); - Ok(proxied) + Ok(proxied) } pub async fn handle(_ctx: &Context, msg: &Message, data: &Data) -> Result<()> { - if msg.webhook_id.is_some() { - debug!( - "Message {} has a webhook ID. Checking if it was sent through PluralKit", - msg.id - ); + if msg.webhook_id.is_some() { + debug!( + "Message {} has a webhook ID. Checking if it was sent through PluralKit", + msg.id + ); - debug!( - "Waiting on PluralKit API for {} seconds", - PK_DELAY_SEC.as_secs() - ); - sleep(PK_DELAY_SEC).await; + debug!( + "Waiting on PluralKit API for {} seconds", + PK_DELAY_SEC.as_secs() + ); + sleep(PK_DELAY_SEC).await; - if let Ok(sender) = api::pluralkit::get_sender(msg.id).await { - data.storage.store_user_plurality(sender).await?; - } - } + if let Ok(sender) = api::pluralkit::get_sender(msg.id).await { + data.storage.store_user_plurality(sender).await?; + } + } - Ok(()) + Ok(()) } diff --git a/src/handlers/event/support_onboard.rs b/src/handlers/event/support_onboard.rs index 9b6bc16..e83a239 100644 --- a/src/handlers/event/support_onboard.rs +++ b/src/handlers/event/support_onboard.rs @@ -3,41 +3,41 @@ use log::*; use poise::serenity_prelude::{ChannelType, Context, GuildChannel}; pub async fn handle(ctx: &Context, thread: &GuildChannel) -> Result<()> { - if thread.kind != ChannelType::PublicThread { - return Ok(()); - } + if thread.kind != ChannelType::PublicThread { + return Ok(()); + } - let parent_id = thread - .parent_id - .ok_or_else(|| eyre!("Couldn't get parent ID from thread {}!", thread.name))?; + let parent_id = thread + .parent_id + .ok_or_else(|| eyre!("Couldn't get parent ID from thread {}!", thread.name))?; - let parent_channel = ctx - .cache - .guild_channel(parent_id) - .ok_or_else(|| eyre!("Couldn't get GuildChannel {}!", parent_id))?; + let parent_channel = ctx + .cache + .guild_channel(parent_id) + .ok_or_else(|| eyre!("Couldn't get GuildChannel {}!", parent_id))?; - if parent_channel.name != "support" { - debug!("Not posting onboarding message to threads outside of support"); - return Ok(()); - } + if parent_channel.name != "support" { + debug!("Not posting onboarding message to threads outside of support"); + return Ok(()); + } - let owner = thread - .owner_id - .ok_or_else(|| eyre!("Couldn't get owner of thread!"))?; + let owner = thread + .owner_id + .ok_or_else(|| eyre!("Couldn't get owner of thread!"))?; - let msg = format!( + let msg = format!( "<@{}> We've received your support ticket! {} {}", owner, "Please upload your logs and post the link here if possible (run `tag log` to find out how).", "Please don't ping people for support questions, unless you have their permission." ); - thread - .send_message(ctx, |m| { - m.content(msg) - .allowed_mentions(|am| am.replied_user(true).users(Vec::from([owner]))) - }) - .await?; + thread + .send_message(ctx, |m| { + m.content(msg) + .allowed_mentions(|am| am.replied_user(true).users(Vec::from([owner]))) + }) + .await?; - Ok(()) + Ok(()) } diff --git a/src/main.rs b/src/main.rs index f3b47e8..4f83a70 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,7 +7,7 @@ use color_eyre::owo_colors::OwoColorize; use log::*; use poise::{ - serenity_prelude as serenity, EditTracker, Framework, FrameworkOptions, PrefixFrameworkOptions, + serenity_prelude as serenity, EditTracker, Framework, FrameworkOptions, PrefixFrameworkOptions, }; use serenity::ShardManager; @@ -34,108 +34,108 @@ type Context<'a> = poise::Context<'a, Data, Report>; #[derive(Clone)] pub struct Data { - config: Config, - storage: Storage, - octocrab: Arc, + config: Config, + storage: Storage, + octocrab: Arc, } impl Data { - pub fn new() -> Result { - let config = Config::new_from_env()?; - let storage = Storage::new(&config.redis_url)?; - let octocrab = octocrab::instance(); + pub fn new() -> Result { + let config = Config::new_from_env()?; + let storage = Storage::new(&config.redis_url)?; + let octocrab = octocrab::instance(); - Ok(Self { - config, - storage, - octocrab, - }) - } + Ok(Self { + config, + storage, + octocrab, + }) + } } async fn setup( - ctx: &serenity::Context, - _ready: &serenity::Ready, - framework: &Framework, + ctx: &serenity::Context, + _ready: &serenity::Ready, + framework: &Framework, ) -> Result { - let data = Data::new()?; + let data = Data::new()?; - // test redis connection - let mut client = data.storage.client.clone(); + // test redis connection + let mut client = data.storage.client.clone(); - if !client.check_connection() { - return Err(eyre!( - "Couldn't connect to storage! Is your daemon running?" - )); - } + if !client.check_connection() { + return Err(eyre!( + "Couldn't connect to storage! Is your daemon running?" + )); + } - poise::builtins::register_globally(ctx, &framework.options().commands).await?; - info!("Registered global commands!"); + poise::builtins::register_globally(ctx, &framework.options().commands).await?; + info!("Registered global commands!"); - Ok(data) + Ok(data) } async fn handle_shutdown(shard_manager: Arc>, reason: &str) { - warn!("{reason}! Shutting down bot..."); - shard_manager.lock().await.shutdown_all().await; - println!("{}", "Everything is shutdown. Goodbye!".green()) + warn!("{reason}! Shutting down bot..."); + shard_manager.lock().await.shutdown_all().await; + println!("{}", "Everything is shutdown. Goodbye!".green()) } #[tokio::main] async fn main() -> Result<()> { - dotenvy::dotenv().ok(); - color_eyre::install()?; - env_logger::init(); + dotenvy::dotenv().ok(); + color_eyre::install()?; + env_logger::init(); - let token = std::env::var("DISCORD_BOT_TOKEN") - .wrap_err_with(|| "Couldn't find bot token in environment!")?; + let token = std::env::var("DISCORD_BOT_TOKEN") + .wrap_err_with(|| "Couldn't find bot token in environment!")?; - let intents = - serenity::GatewayIntents::non_privileged() | serenity::GatewayIntents::MESSAGE_CONTENT; + let intents = + serenity::GatewayIntents::non_privileged() | serenity::GatewayIntents::MESSAGE_CONTENT; - let options = FrameworkOptions { - commands: commands::to_global_commands(), + let options = FrameworkOptions { + commands: commands::to_global_commands(), - on_error: |error| Box::pin(handlers::handle_error(error)), + on_error: |error| Box::pin(handlers::handle_error(error)), - command_check: Some(|ctx| { - Box::pin(async move { Ok(ctx.author().id != ctx.framework().bot_id) }) - }), + command_check: Some(|ctx| { + Box::pin(async move { Ok(ctx.author().id != ctx.framework().bot_id) }) + }), - event_handler: |ctx, event, framework, data| { - Box::pin(handlers::handle_event(ctx, event, framework, data)) - }, + event_handler: |ctx, event, framework, data| { + Box::pin(handlers::handle_event(ctx, event, framework, data)) + }, - prefix_options: PrefixFrameworkOptions { - prefix: Some("r".into()), - edit_tracker: Some(EditTracker::for_timespan(Duration::from_secs(3600))), - ..Default::default() - }, + prefix_options: PrefixFrameworkOptions { + prefix: Some("r".into()), + edit_tracker: Some(EditTracker::for_timespan(Duration::from_secs(3600))), + ..Default::default() + }, - ..Default::default() - }; + ..Default::default() + }; - let framework = Framework::builder() - .token(token) - .intents(intents) - .options(options) - .setup(|ctx, ready, framework| Box::pin(setup(ctx, ready, framework))) - .build() - .await - .wrap_err_with(|| "Failed to build framework!")?; + let framework = Framework::builder() + .token(token) + .intents(intents) + .options(options) + .setup(|ctx, ready, framework| Box::pin(setup(ctx, ready, framework))) + .build() + .await + .wrap_err_with(|| "Failed to build framework!")?; - let shard_manager = framework.shard_manager().clone(); - let mut sigterm = signal(SignalKind::terminate())?; + let shard_manager = framework.shard_manager().clone(); + let mut sigterm = signal(SignalKind::terminate())?; - tokio::select! { - result = framework.start() => result.map_err(Report::from), - _ = sigterm.recv() => { - handle_shutdown(shard_manager, "Recieved SIGTERM").await; - std::process::exit(0); - } - _ = ctrl_c() => { - handle_shutdown(shard_manager, "Interrupted").await; - std::process::exit(130); - } - } + tokio::select! { + result = framework.start() => result.map_err(Report::from), + _ = sigterm.recv() => { + handle_shutdown(shard_manager, "Recieved SIGTERM").await; + std::process::exit(0); + } + _ = ctrl_c() => { + handle_shutdown(shard_manager, "Interrupted").await; + std::process::exit(130); + } + } } diff --git a/src/storage/mod.rs b/src/storage/mod.rs index 2cfbe33..022a051 100644 --- a/src/storage/mod.rs +++ b/src/storage/mod.rs @@ -10,105 +10,105 @@ const LAUNCHER_VERSION_KEY: &str = "launcher-version-v1"; #[derive(Clone, Debug)] pub struct Storage { - pub client: Client, + pub client: Client, } impl Storage { - pub fn new(redis_url: &str) -> Result { - let client = Client::open(redis_url)?; + pub fn new(redis_url: &str) -> Result { + let client = Client::open(redis_url)?; - Ok(Self { client }) - } + Ok(Self { client }) + } - /* - these are mainly light abstractions to avoid the `let mut con` - boilerplate, as well as not require the caller to format the - strings for keys - */ + /* + these are mainly light abstractions to avoid the `let mut con` + boilerplate, as well as not require the caller to format the + strings for keys + */ - async fn get_key(&self, key: &str) -> Result - where - T: FromRedisValue, - { - debug!("Getting key {key}"); + async fn get_key(&self, key: &str) -> Result + where + T: FromRedisValue, + { + debug!("Getting key {key}"); - let mut con = self.client.get_async_connection().await?; - let res: T = con.get(key).await?; + let mut con = self.client.get_async_connection().await?; + let res: T = con.get(key).await?; - Ok(res) - } + Ok(res) + } - async fn set_key<'a>( - &self, - key: &str, - value: impl ToRedisArgs + Debug + Send + Sync + 'a, - ) -> Result<()> { - debug!("Creating key {key}:\n{value:#?}"); + async fn set_key<'a>( + &self, + key: &str, + value: impl ToRedisArgs + Debug + Send + Sync + 'a, + ) -> Result<()> { + debug!("Creating key {key}:\n{value:#?}"); - let mut con = self.client.get_async_connection().await?; - con.set(key, value).await?; + let mut con = self.client.get_async_connection().await?; + con.set(key, value).await?; - Ok(()) - } + Ok(()) + } - async fn key_exists(&self, key: &str) -> Result { - debug!("Checking if key {key} exists"); + async fn key_exists(&self, key: &str) -> Result { + debug!("Checking if key {key} exists"); - let mut con = self.client.get_async_connection().await?; - let exists: u64 = con.exists(key).await?; + let mut con = self.client.get_async_connection().await?; + let exists: u64 = con.exists(key).await?; - Ok(exists > 0) - } + Ok(exists > 0) + } - async fn delete_key(&self, key: &str) -> Result<()> { - debug!("Deleting key {key}"); + async fn delete_key(&self, key: &str) -> Result<()> { + debug!("Deleting key {key}"); - let mut con = self.client.get_async_connection().await?; - con.del(key).await?; + let mut con = self.client.get_async_connection().await?; + con.del(key).await?; - Ok(()) - } + Ok(()) + } - async fn expire_key(&self, key: &str, expire_seconds: usize) -> Result<()> { - debug!("Expiring key {key} in {expire_seconds}"); + async fn expire_key(&self, key: &str, expire_seconds: usize) -> Result<()> { + debug!("Expiring key {key} in {expire_seconds}"); - let mut con = self.client.get_async_connection().await?; - con.expire(key, expire_seconds).await?; + let mut con = self.client.get_async_connection().await?; + con.expire(key, expire_seconds).await?; - Ok(()) - } + Ok(()) + } - pub async fn store_user_plurality(&self, sender: UserId) -> Result<()> { - info!("Marking {sender} as a PluralKit user"); - let key = format!("{PK_KEY}:{sender}"); + pub async fn store_user_plurality(&self, sender: UserId) -> Result<()> { + info!("Marking {sender} as a PluralKit user"); + let key = format!("{PK_KEY}:{sender}"); - // Just store some value. We only care about the presence of this key - self.set_key(&key, 0).await?; - self.expire_key(&key, 7 * 24 * 60 * 60).await?; + // Just store some value. We only care about the presence of this key + self.set_key(&key, 0).await?; + self.expire_key(&key, 7 * 24 * 60 * 60).await?; - Ok(()) - } + Ok(()) + } - pub async fn is_user_plural(&self, user_id: UserId) -> Result { - let key = format!("{PK_KEY}:{user_id}"); - self.key_exists(&key).await - } + pub async fn is_user_plural(&self, user_id: UserId) -> Result { + let key = format!("{PK_KEY}:{user_id}"); + self.key_exists(&key).await + } - pub async fn cache_launcher_version(&self, version: &str) -> Result<()> { - self.set_key(LAUNCHER_VERSION_KEY, version).await?; + pub async fn cache_launcher_version(&self, version: &str) -> Result<()> { + self.set_key(LAUNCHER_VERSION_KEY, version).await?; - Ok(()) - } + Ok(()) + } - pub async fn get_launcher_version(&self) -> Result { - let res = self.get_key(LAUNCHER_VERSION_KEY).await?; + pub async fn get_launcher_version(&self) -> Result { + let res = self.get_key(LAUNCHER_VERSION_KEY).await?; - Ok(res) - } + Ok(res) + } - pub async fn launcher_version_is_cached(&self) -> Result { - let res = self.key_exists(LAUNCHER_VERSION_KEY).await?; + pub async fn launcher_version_is_cached(&self) -> Result { + let res = self.key_exists(LAUNCHER_VERSION_KEY).await?; - Ok(res) - } + Ok(res) + } } diff --git a/src/tags.rs b/src/tags.rs index dacdd01..ab77b1e 100644 --- a/src/tags.rs +++ b/src/tags.rs @@ -6,15 +6,15 @@ pub const TAG_DIR: &str = "tags"; #[derive(Clone, Debug, Deserialize, Serialize)] pub struct TagFrontmatter { - pub title: String, - pub color: Option, - pub image: Option, - pub fields: Option>, + pub title: String, + pub color: Option, + pub image: Option, + pub fields: Option>, } #[derive(Clone, Debug, Deserialize, Serialize)] pub struct Tag { - pub content: String, - pub file_name: String, - pub frontmatter: TagFrontmatter, + pub content: String, + pub file_name: String, + pub frontmatter: TagFrontmatter, } diff --git a/src/utils/macros.rs b/src/utils/macros.rs index a473959..c167420 100644 --- a/src/utils/macros.rs +++ b/src/utils/macros.rs @@ -1,6 +1,6 @@ #[macro_export] macro_rules! required_var { - ($name: expr) => { - std::env::var($name).wrap_err_with(|| format!("Couldn't find {} in environment!", $name))? - }; + ($name: expr) => { + std::env::var($name).wrap_err_with(|| format!("Couldn't find {} in environment!", $name))? + }; } diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 700ce3d..e8928e2 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -11,10 +11,10 @@ pub use resolve_message::resolve as resolve_message; * chooses a random element from an array */ pub fn random_choice(arr: [&str; N]) -> Result { - let mut rng = rand::thread_rng(); - let resp = arr - .choose(&mut rng) - .ok_or_else(|| eyre!("Couldn't choose random object from array:\n{arr:#?}!"))?; + let mut rng = rand::thread_rng(); + let resp = arr + .choose(&mut rng) + .ok_or_else(|| eyre!("Couldn't choose random object from array:\n{arr:#?}!"))?; - Ok((*resp).to_string()) + Ok((*resp).to_string()) } diff --git a/src/utils/resolve_message.rs b/src/utils/resolve_message.rs index cbf7a82..165e8c9 100644 --- a/src/utils/resolve_message.rs +++ b/src/utils/resolve_message.rs @@ -5,120 +5,120 @@ use poise::serenity_prelude::{ChannelType, Colour, Context, CreateEmbed, Message use regex::Regex; static MESSAGE_PATTERN: Lazy = Lazy::new(|| { - Regex::new(r"/(https?:\/\/)?(?:canary\.|ptb\.)?discord(?:app)?\.com\/channels\/(?\d+)\/(?\d+)\/(?\d+)/g;").unwrap() + Regex::new(r"/(https?:\/\/)?(?:canary\.|ptb\.)?discord(?:app)?\.com\/channels\/(?\d+)\/(?\d+)\/(?\d+)/g;").unwrap() }); pub fn find_first_image(msg: &Message) -> Option { - msg.attachments - .iter() - .find(|a| { - a.content_type - .as_ref() - .unwrap_or(&"".to_string()) - .starts_with("image/") - }) - .map(|res| res.url.clone()) + msg.attachments + .iter() + .find(|a| { + a.content_type + .as_ref() + .unwrap_or(&"".to_string()) + .starts_with("image/") + }) + .map(|res| res.url.clone()) } pub async fn resolve(ctx: &Context, msg: &Message) -> Result> { - let matches = MESSAGE_PATTERN.captures_iter(&msg.content); - let mut embeds: Vec = vec![]; + let matches = MESSAGE_PATTERN.captures_iter(&msg.content); + let mut embeds: Vec = vec![]; - for captured in matches.take(3) { - // don't leak messages from other servers - if let Some(server_id) = captured.get(0) { - let other_server: u64 = server_id.as_str().parse().unwrap_or_default(); - let current_id = msg.guild_id.unwrap_or_default(); + for captured in matches.take(3) { + // don't leak messages from other servers + if let Some(server_id) = captured.get(0) { + let other_server: u64 = server_id.as_str().parse().unwrap_or_default(); + let current_id = msg.guild_id.unwrap_or_default(); - if &other_server != current_id.as_u64() { - debug!("Not resolving message of other guild."); - continue; - } - } else { - warn!("Couldn't find server_id from Discord link! Not resolving message to be safe"); - continue; - } + if &other_server != current_id.as_u64() { + debug!("Not resolving message of other guild."); + continue; + } + } else { + warn!("Couldn't find server_id from Discord link! Not resolving message to be safe"); + continue; + } - if let Some(channel_id) = captured.get(1) { - let parsed: u64 = channel_id.as_str().parse().unwrap_or_default(); - let req_channel = ctx - .cache - .channel(parsed) - .ok_or_else(|| eyre!("Couldn't get channel_id from Discord regex!"))? - .guild() - .ok_or_else(|| { - eyre!("Couldn't convert to GuildChannel from channel_id {parsed}!") - })?; + if let Some(channel_id) = captured.get(1) { + let parsed: u64 = channel_id.as_str().parse().unwrap_or_default(); + let req_channel = ctx + .cache + .channel(parsed) + .ok_or_else(|| eyre!("Couldn't get channel_id from Discord regex!"))? + .guild() + .ok_or_else(|| { + eyre!("Couldn't convert to GuildChannel from channel_id {parsed}!") + })?; - if !req_channel.is_text_based() { - debug!("Not resolving message is non-text-based channel."); - continue; - } + if !req_channel.is_text_based() { + debug!("Not resolving message is non-text-based channel."); + continue; + } - if req_channel.kind == ChannelType::PrivateThread { - if let Some(id) = req_channel.parent_id { - let parent = ctx.cache.guild_channel(id).ok_or_else(|| { - eyre!("Couldn't get parent channel {id} for thread {req_channel}!") - })?; - let parent_members = parent.members(ctx).await.unwrap_or_default(); + if req_channel.kind == ChannelType::PrivateThread { + if let Some(id) = req_channel.parent_id { + let parent = ctx.cache.guild_channel(id).ok_or_else(|| { + eyre!("Couldn't get parent channel {id} for thread {req_channel}!") + })?; + let parent_members = parent.members(ctx).await.unwrap_or_default(); - if !parent_members.iter().any(|m| m.user.id == msg.author.id) { - debug!("Not resolving message for user not a part of a private thread."); - continue; - } - } - } else if req_channel - .members(ctx) - .await? - .iter() - .any(|m| m.user.id == msg.author.id) - { - debug!("Not resolving for message for user not a part of a channel"); - continue; - } + if !parent_members.iter().any(|m| m.user.id == msg.author.id) { + debug!("Not resolving message for user not a part of a private thread."); + continue; + } + } + } else if req_channel + .members(ctx) + .await? + .iter() + .any(|m| m.user.id == msg.author.id) + { + debug!("Not resolving for message for user not a part of a channel"); + continue; + } - let message_id: u64 = captured - .get(2) - .ok_or_else(|| eyre!("Couldn't get message_id from Discord regex!"))? - .as_str() - .parse() - .wrap_err_with(|| { - eyre!("Couldn't parse message_id from Discord regex as a MessageId!") - })?; + let message_id: u64 = captured + .get(2) + .ok_or_else(|| eyre!("Couldn't get message_id from Discord regex!"))? + .as_str() + .parse() + .wrap_err_with(|| { + eyre!("Couldn't parse message_id from Discord regex as a MessageId!") + })?; - let original_message = req_channel.message(ctx, message_id).await?; - let mut embed = CreateEmbed::default(); - embed - .author(|a| { - a.name(original_message.author.tag()) - .icon_url(original_message.author.default_avatar_url()) - }) - .color(Colour::BLITZ_BLUE) - .timestamp(original_message.timestamp) - .footer(|f| f.text(format!("#{}", req_channel.name))) - .description(format!( - "{}\n\n[Jump to original message]({})", - original_message.content, - original_message.link() - )); + let original_message = req_channel.message(ctx, message_id).await?; + let mut embed = CreateEmbed::default(); + embed + .author(|a| { + a.name(original_message.author.tag()) + .icon_url(original_message.author.default_avatar_url()) + }) + .color(Colour::BLITZ_BLUE) + .timestamp(original_message.timestamp) + .footer(|f| f.text(format!("#{}", req_channel.name))) + .description(format!( + "{}\n\n[Jump to original message]({})", + original_message.content, + original_message.link() + )); - if !original_message.attachments.is_empty() { - embed.fields(original_message.attachments.iter().map(|a| { - ( - "Attachments".to_string(), - format!("[{}]({})", a.filename, a.url), - false, - ) - })); + if !original_message.attachments.is_empty() { + embed.fields(original_message.attachments.iter().map(|a| { + ( + "Attachments".to_string(), + format!("[{}]({})", a.filename, a.url), + false, + ) + })); - if let Some(image) = find_first_image(msg) { - embed.image(image); - } - } + if let Some(image) = find_first_image(msg) { + embed.image(image); + } + } - embeds.push(embed); - } - } + embeds.push(embed); + } + } - Ok(embeds) + Ok(embeds) } From f2a3582c7713704a18296b1815811c92e9f4ecdc Mon Sep 17 00:00:00 2001 From: seth Date: Mon, 8 Jan 2024 15:02:36 -0500 Subject: [PATCH 41/90] nix/deployment: pre-select system in crossPkgsFor --- nix/deployment.nix | 55 ++++++++++++++++++++++++---------------------- nix/dev.nix | 3 ++- 2 files changed, 31 insertions(+), 27 deletions(-) diff --git a/nix/deployment.nix b/nix/deployment.nix index 84044fc..49cd465 100644 --- a/nix/deployment.nix +++ b/nix/deployment.nix @@ -9,28 +9,30 @@ lib, pkgs, system, - config, + self', inputs', ... }: let - crossPkgsFor = lib.fix (finalAttrs: { - "x86_64-linux" = { - "x86_64" = pkgs.pkgsStatic; - "aarch64" = pkgs.pkgsCross.aarch64-multiplatform.pkgsStatic; - }; + crossPkgsFor = + rec { + x86_64-linux = { + x86_64 = pkgs.pkgsStatic; + aarch64 = pkgs.pkgsCross.aarch64-multiplatform.pkgsStatic; + }; - "aarch64-linux" = { - "x86_64" = pkgs.pkgsCross.musl64; - "aarch64" = pkgs.pkgsStatic; - }; + aarch64-linux = { + x86_64 = pkgs.pkgsCross.musl64; + aarch64 = pkgs.pkgsStatic; + }; - "x86_64-darwin" = { - "x86_64" = pkgs.pkgsCross.musl64; - "aarch64" = pkgs.pkgsCross.aarch64-multiplatform.pkgsStatic; - }; + x86_64-darwin = { + x86_64 = pkgs.pkgsCross.musl64; + aarch64 = pkgs.pkgsCross.aarch64-multiplatform.pkgsStatic; + }; - "aarch64-darwin" = finalAttrs."x86_64-darwin"; - }); + aarch64-darwin = x86_64-darwin; + } + .${system}; exeFor = arch: let target = "${arch}-unknown-linux-musl"; @@ -49,21 +51,22 @@ rustc = toolchain; }; - refraction = config.packages.refraction.override { + refraction = self'.packages.refraction.override { naersk = naersk'; optimizeSize = true; }; - inherit (crossPkgsFor.${system}.${arch}.stdenv) cc; + newAttrs = lib.fix (finalAttrs: { + CARGO_BUILD_TARGET = target; + "CC_${target'}" = "${cc}/bin/${cc.targetPrefix}cc"; + "CARGO_TARGET_${targetUpper}_RUSTFLAGS" = "-C target-feature=+crt-static"; + "CARGO_TARGET_${targetUpper}_LINKER" = finalAttrs."CC_${target'}"; + }); + + inherit (crossPkgsFor.${arch}.stdenv) cc; in lib.getExe ( - refraction.overrideAttrs (_: - lib.fix (finalAttrs: { - CARGO_BUILD_TARGET = target; - "CC_${target'}" = "${cc}/bin/${cc.targetPrefix}cc"; - "CARGO_TARGET_${targetUpper}_RUSTFLAGS" = "-C target-feature=+crt-static"; - "CARGO_TARGET_${targetUpper}_LINKER" = finalAttrs."CC_${target'}"; - })) + refraction.overrideAttrs (_: newAttrs) ); containerFor = arch: @@ -73,7 +76,7 @@ contents = [pkgs.dockerTools.caCertificates]; config.Cmd = [(exeFor arch)]; - architecture = crossPkgsFor.${system}.${arch}.go.GOARCH; + architecture = crossPkgsFor.${arch}.go.GOARCH; }; in { legacyPackages = { diff --git a/nix/dev.nix b/nix/dev.nix index 9697dac..9591def 100644 --- a/nix/dev.nix +++ b/nix/dev.nix @@ -3,6 +3,7 @@ lib, pkgs, config, + self', ... }: { pre-commit.settings.hooks = { @@ -38,7 +39,7 @@ rust-analyzer # nix - config.formatter + self'.formatter nil ]; From 48c2dba554460300a08f125c5ad3c91418a9d829 Mon Sep 17 00:00:00 2001 From: seth Date: Mon, 8 Jan 2024 15:06:56 -0500 Subject: [PATCH 42/90] chore: cleanup and bump workflow actions --- .github/workflows/check.yml | 4 ++-- .github/workflows/docker.yml | 8 ++++---- .github/workflows/update-flake.yml | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 6b27d70..09bab7e 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -53,7 +53,7 @@ jobs: - name: Run Clippy continue-on-error: true run: | - set -euxo pipefail + set -euo pipefail cargo clippy \ --all-features \ @@ -62,7 +62,7 @@ jobs: | clippy-sarif | tee /tmp/clippy.sarif | sarif-fmt - name: Upload results - uses: github/codeql-action/upload-sarif@v2 + uses: github/codeql-action/upload-sarif@v3 with: sarif_file: /tmp/clippy.sarif wait-for-processing: true diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 891362b..7138f6e 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -9,8 +9,8 @@ on: jobs: build: name: Build image - runs-on: ubuntu-latest + strategy: matrix: arch: [x86_64, aarch64] @@ -32,7 +32,7 @@ jobs: echo "path=$(realpath result)" >> "$GITHUB_OUTPUT" - name: Upload image - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: container-${{ matrix.arch }} path: ${{ steps.build.outputs.path }} @@ -59,7 +59,7 @@ jobs: - uses: actions/checkout@v4 - name: Download images - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: path: images @@ -74,7 +74,7 @@ jobs: env: TAG: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest run: | - set -eux + set -eu architectures=("x86_64" "aarch64") for arch in "${architectures[@]}"; do diff --git a/.github/workflows/update-flake.yml b/.github/workflows/update-flake.yml index 014a603..fabcaf0 100644 --- a/.github/workflows/update-flake.yml +++ b/.github/workflows/update-flake.yml @@ -23,6 +23,6 @@ jobs: - name: Update and create PR uses: DeterminateSystems/update-flake-lock@v20 with: - commit-msg: 'flake: update inputs' - pr-title: 'flake: update inputs' + commit-msg: 'nix: update inputs' + pr-title: 'nix: update inputs' token: ${{ github.token }} From 203ba111cc887a2be9f9ead4a33cc57f81f8853c Mon Sep 17 00:00:00 2001 From: seth Date: Sat, 27 Jan 2024 01:50:51 -0500 Subject: [PATCH 43/90] fix: compilation on windows --- src/main.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index 4f83a70..cf89bd6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -15,7 +15,10 @@ use serenity::ShardManager; use redis::ConnectionLike; use tokio::signal::ctrl_c; +#[cfg(target_family = "unix")] use tokio::signal::unix::{signal, SignalKind}; +#[cfg(target_family = "windows")] +use tokio::signal::windows::ctrl_close; use tokio::sync::Mutex; mod api; @@ -125,12 +128,15 @@ async fn main() -> Result<()> { .wrap_err_with(|| "Failed to build framework!")?; let shard_manager = framework.shard_manager().clone(); + #[cfg(target_family = "unix")] let mut sigterm = signal(SignalKind::terminate())?; + #[cfg(target_family = "windows")] + let mut sigterm = ctrl_close()?; tokio::select! { result = framework.start() => result.map_err(Report::from), _ = sigterm.recv() => { - handle_shutdown(shard_manager, "Recieved SIGTERM").await; + handle_shutdown(shard_manager, "Received SIGTERM").await; std::process::exit(0); } _ = ctrl_c() => { From 7252ced3cb52c377ad3c4af64ab5f19d6fba16a6 Mon Sep 17 00:00:00 2001 From: seth Date: Sat, 27 Jan 2024 22:29:56 -0500 Subject: [PATCH 44/90] refactor!: use poise 0.6.1 --- Cargo.lock | 642 +++++++++++++++-------- Cargo.toml | 28 +- src/commands/general/members.rs | 22 +- src/commands/general/rory.rs | 31 +- src/commands/general/say.rs | 33 +- src/commands/general/stars.rs | 17 +- src/commands/general/tag.rs | 53 +- src/handlers/error.rs | 36 +- src/handlers/event/analyze_logs/mod.rs | 69 +-- src/handlers/event/delete_on_reaction.rs | 2 +- src/handlers/event/expand_link.rs | 25 +- src/handlers/event/mod.rs | 22 +- src/handlers/event/support_onboard.rs | 36 +- src/main.rs | 26 +- src/storage/mod.rs | 2 +- src/utils/resolve_message.rs | 168 +++--- 16 files changed, 700 insertions(+), 512 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b89b662..890eb89 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -41,6 +41,54 @@ dependencies = [ "libc", ] +[[package]] +name = "anstream" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2e1ebcb11de5c03c67de28a7df593d32191b44939c482e97702baaaa6ab6a5" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" + +[[package]] +name = "anstyle-parse" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" +dependencies = [ + "anstyle", + "windows-sys 0.52.0", +] + [[package]] name = "arc-swap" version = "1.6.0" @@ -48,30 +96,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bddcadddf5e9015d310179a59bb28c4d4b9920ad0f11e8e14dbadf654890c9a6" [[package]] -name = "async-trait" -version = "0.1.74" +name = "arrayvec" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.40", + "serde", ] [[package]] -name = "async-tungstenite" -version = "0.17.2" +name = "async-trait" +version = "0.1.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1b71b31561643aa8e7df3effe284fa83ab1a840e52294c5f4bd7bfd8b2becbb" +checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9" dependencies = [ - "futures-io", - "futures-util", - "log", - "pin-project-lite", - "tokio", - "tokio-rustls 0.23.4", - "tungstenite", - "webpki-roots 0.22.6", + "proc-macro2", + "quote", + "syn 2.0.48", ] [[package]] @@ -95,12 +136,6 @@ dependencies = [ "rustc-demangle", ] -[[package]] -name = "base64" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" - [[package]] name = "base64" version = "0.21.5" @@ -134,6 +169,12 @@ version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" 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.5.0" @@ -146,6 +187,37 @@ 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.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ceed8ef69d8518a5dda55c07425450b58a4e1946f4951eab6d7191ee86c2443d" +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.83" @@ -201,6 +273,12 @@ dependencies = [ "tracing-error", ] +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + [[package]] name = "combine" version = "4.6.6" @@ -249,6 +327,21 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crossbeam-channel" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "176dc175b78f56c0f321911d9c8eb2b77a78a4860b9c19db83835fea1a46649b" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" + [[package]] name = "crypto-common" version = "0.1.6" @@ -261,9 +354,9 @@ dependencies = [ [[package]] name = "darling" -version = "0.14.4" +version = "0.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b750cb3417fd1b327431a470f388520309479ab0bf5e323505daf0290cd3850" +checksum = "0209d94da627ab5605dcccf08bb18afa5009cfbef48d8a8b7d7bdbc79be25c5e" dependencies = [ "darling_core", "darling_macro", @@ -271,27 +364,27 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.14.4" +version = "0.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "109c1ca6e6b7f82cc233a97004ea8ed7ca123a9af07a8230878fcfda9b158bf0" +checksum = "177e3443818124b357d8e76f53be906d60937f0d3a90773a664fa63fa253e621" dependencies = [ "fnv", "ident_case", "proc-macro2", "quote", "strsim", - "syn 1.0.109", + "syn 2.0.48", ] [[package]] name = "darling_macro" -version = "0.14.4" +version = "0.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4aab4dbc9f7611d8b55048a3a16d2d010c2c8334e46304b40ac1cc14bf3b48e" +checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5" dependencies = [ "darling_core", "quote", - "syn 1.0.109", + "syn 2.0.48", ] [[package]] @@ -308,6 +401,12 @@ 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.10" @@ -367,16 +466,26 @@ dependencies = [ ] [[package]] -name = "env_logger" -version = "0.10.1" +name = "env_filter" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95b3f3e67048839cb0d0781f445682a35113da7121f7c949db0e2be96a4fbece" +checksum = "a009aa4810eb158359dda09d0c87378e4bbb89b5a801f016885a4707ba24f7ea" dependencies = [ - "humantime", - "is-terminal", "log", "regex", - "termcolor", +] + +[[package]] +name = "env_logger" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05e7cf40684ae96ade6232ed84582f40ce0a66efcd43a5117aef610534f8e0b8" +dependencies = [ + "anstream", + "anstyle", + "env_filter", + "humantime", + "log", ] [[package]] @@ -395,6 +504,15 @@ dependencies = [ "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 = "eyre" version = "0.6.9" @@ -405,6 +523,12 @@ dependencies = [ "once_cell", ] +[[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.28" @@ -486,7 +610,7 @@ checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.40", + "syn 2.0.48", ] [[package]] @@ -519,6 +643,15 @@ 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.7" @@ -548,6 +681,12 @@ 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 = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + [[package]] name = "gray_matter" version = "0.2.6" @@ -677,10 +816,10 @@ dependencies = [ "http", "hyper", "log", - "rustls 0.21.10", + "rustls", "rustls-native-certs", "tokio", - "tokio-rustls 0.24.1", + "tokio-rustls", ] [[package]] @@ -766,17 +905,6 @@ dependencies = [ "serde", ] -[[package]] -name = "is-terminal" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" -dependencies = [ - "hermit-abi", - "rustix", - "windows-sys 0.48.0", -] - [[package]] name = "itoa" version = "1.0.10" @@ -798,10 +926,10 @@ version = "9.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c7ea04a7c5c055c175f189b6dc6ba036fd62306b58c66c9f6389036c503a3f4" dependencies = [ - "base64 0.21.5", + "base64", "js-sys", "pem", - "ring 0.17.7", + "ring", "serde", "serde_json", "simple_asn1", @@ -869,6 +997,21 @@ dependencies = [ "unicase", ] +[[package]] +name = "mini-moka" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c325dfab65f261f386debee8b0969da215b3fa0037e74c8a1234db7ba986d803" +dependencies = [ + "crossbeam-channel", + "crossbeam-utils", + "dashmap", + "skeptic", + "smallvec", + "tagptr", + "triomphe", +] + [[package]] name = "miniz_oxide" version = "0.7.1" @@ -940,13 +1083,13 @@ dependencies = [ [[package]] name = "octocrab" -version = "0.32.0" +version = "0.33.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abfeeafb5fa0da7046229ec3c7b3bd2981aae05c549871192c408d59fc0fffd5" +checksum = "054a8bf47dfa8f89bb0dcf17e485e9f49f2586c05bf0aa67b8ec5a9e7bc8dfd5" dependencies = [ "arc-swap", "async-trait", - "base64 0.21.5", + "base64", "bytes", "cfg-if", "chrono", @@ -987,15 +1130,6 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" -[[package]] -name = "ordered-float" -version = "2.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68f19d67e5a2795c94e73e0bb1cc1a7edeb2e28efd39e2e1c9b7a40c1108b11c" -dependencies = [ - "num-traits", -] - [[package]] name = "owo-colors" version = "3.5.0" @@ -1031,7 +1165,7 @@ version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b8fcc794035347fb64beda2d3b462595dd2753e3f268d89c5aae77e8cf2c310" dependencies = [ - "base64 0.21.5", + "base64", "serde", ] @@ -1058,7 +1192,7 @@ checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" dependencies = [ "proc-macro2", "quote", - "syn 2.0.40", + "syn 2.0.48", ] [[package]] @@ -1075,33 +1209,31 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "poise" -version = "0.5.7" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d104e4b5847283b2fbd6a7ec19fb6a8af328e2145623d056b66d750a30073fdf" +checksum = "1819d5a45e3590ef33754abce46432570c54a120798bdbf893112b4211fa09a6" dependencies = [ "async-trait", "derivative", - "futures-core", "futures-util", - "log", - "once_cell", "parking_lot", "poise_macros", "regex", "serenity", "tokio", + "tracing", ] [[package]] name = "poise_macros" -version = "0.5.7" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fb516a8cf4e4ae4bd7ef5819d08c6ca408976461a9bea3ee3eec5138ac070c1" +checksum = "8fa2c123c961e78315cd3deac7663177f12be4460f5440dbf62a7ed37b1effea" dependencies = [ "darling", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.48", ] [[package]] @@ -1118,18 +1250,29 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "proc-macro2" -version = "1.0.70" +version = "1.0.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b" +checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" dependencies = [ "unicode-ident", ] [[package]] -name = "quote" -version = "1.0.33" +name = "pulldown-cmark" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +checksum = "77a1a2f1f0a7ecff9c31abbe177637be0e97a0aef46cf8738ece09327985d998" +dependencies = [ + "bitflags 1.3.2", + "memchr", + "unicase", +] + +[[package]] +name = "quote" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" dependencies = [ "proc-macro2", ] @@ -1169,6 +1312,21 @@ name = "redis" version = "0.23.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4f49cdc0bb3f412bf8e7d1bd90fe1d9eb10bc5c399ba90973c14662a27b3f8ba" +dependencies = [ + "combine", + "itoa", + "percent-encoding", + "ryu", + "sha1_smol", + "socket2 0.4.10", + "url", +] + +[[package]] +name = "redis" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c580d9cbbe1d1b479e8d67cf9daf6a62c957e6846048408b80b43ac3f6af84cd" dependencies = [ "async-trait", "bytes", @@ -1177,13 +1335,15 @@ dependencies = [ "itoa", "percent-encoding", "pin-project-lite", - "rustls 0.21.10", + "rustls", "rustls-native-certs", + "rustls-pemfile", + "rustls-webpki", "ryu", "sha1_smol", "socket2 0.4.10", "tokio", - "tokio-rustls 0.24.1", + "tokio-rustls", "tokio-util", "url", ] @@ -1194,7 +1354,7 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60eb39e2b44d4c0f9c84e7c5fc4fc3adc8dd26ec48f1ac3a160033f7c03b18fd" dependencies = [ - "redis", + "redis 0.23.3", "redis-macros-derive", "serde", "serde_json", @@ -1234,7 +1394,7 @@ dependencies = [ "once_cell", "poise", "rand", - "redis", + "redis 0.24.0", "redis-macros", "regex", "reqwest", @@ -1246,9 +1406,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.2" +version = "1.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" +checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" dependencies = [ "aho-corasick", "memchr", @@ -1258,9 +1418,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.3" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" +checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd" dependencies = [ "aho-corasick", "memchr", @@ -1275,11 +1435,11 @@ checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" [[package]] name = "reqwest" -version = "0.11.22" +version = "0.11.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "046cd98826c46c2ac8ddecae268eb5c2e58628688a5fc7a2643704a73faba95b" +checksum = "37b1ae8d9ac08420c66222fb9096fc5de435c3c48542bc5336c51892cffafb41" dependencies = [ - "base64 0.21.5", + "base64", "bytes", "encoding_rs", "futures-core", @@ -1297,14 +1457,14 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", - "rustls 0.21.10", + "rustls", "rustls-pemfile", "serde", "serde_json", "serde_urlencoded", "system-configuration", "tokio", - "tokio-rustls 0.24.1", + "tokio-rustls", "tokio-util", "tower-service", "url", @@ -1312,25 +1472,10 @@ dependencies = [ "wasm-bindgen-futures", "wasm-streams", "web-sys", - "webpki-roots 0.25.3", + "webpki-roots", "winreg", ] -[[package]] -name = "ring" -version = "0.16.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" -dependencies = [ - "cc", - "libc", - "once_cell", - "spin 0.5.2", - "untrusted 0.7.1", - "web-sys", - "winapi", -] - [[package]] name = "ring" version = "0.17.7" @@ -1340,8 +1485,8 @@ dependencies = [ "cc", "getrandom", "libc", - "spin 0.9.8", - "untrusted 0.9.0", + "spin", + "untrusted", "windows-sys 0.48.0", ] @@ -1364,18 +1509,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "rustls" -version = "0.20.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b80e3dec595989ea8510028f30c408a4630db12c9cbb8de34203b89d6577e99" -dependencies = [ - "log", - "ring 0.16.20", - "sct", - "webpki", -] - [[package]] name = "rustls" version = "0.21.10" @@ -1383,7 +1516,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9d5a6813c0759e4609cd494e8e725babae6a2ca7b62a5536a13daaec6fcb7ba" dependencies = [ "log", - "ring 0.17.7", + "ring", "rustls-webpki", "sct", ] @@ -1406,7 +1539,7 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" dependencies = [ - "base64 0.21.5", + "base64", ] [[package]] @@ -1415,22 +1548,25 @@ version = "0.101.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" dependencies = [ - "ring 0.17.7", - "untrusted 0.9.0", + "ring", + "untrusted", ] -[[package]] -name = "rustversion" -version = "1.0.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" - [[package]] name = "ryu" version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + [[package]] name = "schannel" version = "0.1.22" @@ -1452,8 +1588,8 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" dependencies = [ - "ring 0.17.7", - "untrusted 0.9.0", + "ring", + "untrusted", ] [[package]] @@ -1462,6 +1598,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9bd1c54ea06cfd2f6b63219704de0b9b4f72dcc2b8fdef820be6cd799780e91e" dependencies = [ + "serde", "zeroize", ] @@ -1489,40 +1626,39 @@ dependencies = [ ] [[package]] -name = "serde" -version = "1.0.193" +name = "semver" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" +checksum = "b97ed7a9823b74f99c7742f5336af7be5ecd3eeafcb1507d1fa93347b1d589b0" +dependencies = [ + "serde", +] + +[[package]] +name = "serde" +version = "1.0.196" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32" dependencies = [ "serde_derive", ] -[[package]] -name = "serde-value" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c" -dependencies = [ - "ordered-float", - "serde", -] - [[package]] name = "serde_derive" -version = "1.0.193" +version = "1.0.196" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" +checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67" dependencies = [ "proc-macro2", "quote", - "syn 2.0.40", + "syn 2.0.48", ] [[package]] name = "serde_json" -version = "1.0.108" +version = "1.0.112" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" +checksum = "4d1bd37ce2324cf3bf85e5a25f96eb4baf0d5aa6eba43e7ae8958870c4ec48ed" dependencies = [ "itoa", "ryu", @@ -1553,41 +1689,41 @@ dependencies = [ [[package]] name = "serenity" -version = "0.11.7" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a7a89cef23483fc9d4caf2df41e6d3928e18aada84c56abd237439d929622c6" +checksum = "385647faa24a889929028973650a4f158fb1b4272b2fcf94feb9fcc3c009e813" dependencies = [ + "arrayvec", "async-trait", - "async-tungstenite", - "base64 0.21.5", - "bitflags 1.3.2", + "base64", + "bitflags 2.4.1", "bytes", - "cfg-if", "chrono", "dashmap", "flate2", "futures", - "mime", + "fxhash", "mime_guess", "parking_lot", "percent-encoding", "reqwest", - "rustversion", + "secrecy", "serde", - "serde-value", "serde_json", "time", "tokio", + "tokio-tungstenite", "tracing", "typemap_rev", + "typesize", "url", ] [[package]] -name = "sha-1" -version = "0.10.1" +name = "sha1" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5058ada175748e33390e40e872bd0fe59a19f265d0158daa551c5a88a76009c" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ "cfg-if", "cpufeatures", @@ -1630,6 +1766,21 @@ dependencies = [ "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.9" @@ -1688,12 +1839,6 @@ dependencies = [ "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" @@ -1719,9 +1864,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.40" +version = "2.0.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13fa70a4ee923979ffb522cacce59d34421ebdea5625e1073c4326ef9d2dd42e" +checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" dependencies = [ "proc-macro2", "quote", @@ -1750,12 +1895,22 @@ dependencies = [ ] [[package]] -name = "termcolor" -version = "1.4.0" +name = "tagptr" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff1bc3d3f05aff0403e8ac0d92ced918ec05b666a43f83297ccef5bea8a3d449" +checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" + +[[package]] +name = "tempfile" +version = "3.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01ce4141aa927a6d1bd34a041795abd0db1cccba5d5f24b009f694bdf3a1f3fa" dependencies = [ - "winapi-util", + "cfg-if", + "fastrand", + "redox_syscall", + "rustix", + "windows-sys 0.52.0", ] [[package]] @@ -1775,7 +1930,7 @@ checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.40", + "syn 2.0.48", ] [[package]] @@ -1834,9 +1989,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.35.0" +version = "1.35.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "841d45b238a16291a4e1584e61820b8ae57d696cc5015c459c229ccc6990cc1c" +checksum = "c89b4efa943be685f629b149f53829423f8f5531ea21249408e8e2f8671ec104" dependencies = [ "backtrace", "bytes", @@ -1868,18 +2023,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.40", -] - -[[package]] -name = "tokio-rustls" -version = "0.23.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" -dependencies = [ - "rustls 0.20.9", - "tokio", - "webpki", + "syn 2.0.48", ] [[package]] @@ -1888,10 +2032,25 @@ version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" dependencies = [ - "rustls 0.21.10", + "rustls", "tokio", ] +[[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.10" @@ -1985,7 +2144,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.40", + "syn 2.0.48", ] [[package]] @@ -2019,6 +2178,12 @@ dependencies = [ "tracing-core", ] +[[package]] +name = "triomphe" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "859eb650cfee7434994602c3a68b25d77ad9e68c8a6cd491616ef86661382eb3" + [[package]] name = "try-lock" version = "0.2.5" @@ -2027,30 +2192,29 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[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 0.13.1", "byteorder", "bytes", + "data-encoding", "http", "httparse", "log", "rand", - "rustls 0.20.9", - "sha-1", + "rustls", + "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" @@ -2058,6 +2222,35 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +[[package]] +name = "typesize" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36924509726e38224322c8c90ddfbf4317324338327b7c11b7cf8672cb786da1" +dependencies = [ + "chrono", + "dashmap", + "hashbrown", + "mini-moka", + "parking_lot", + "secrecy", + "serde_json", + "time", + "typesize-derive", + "url", +] + +[[package]] +name = "typesize-derive" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b122284365ba8497be951b9a21491f70c9688eb6fddc582931a0703f6a00ece" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + [[package]] name = "unicase" version = "2.7.0" @@ -2088,12 +2281,6 @@ dependencies = [ "tinyvec", ] -[[package]] -name = "untrusted" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" - [[package]] name = "untrusted" version = "0.9.0" @@ -2118,6 +2305,12 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + [[package]] name = "valuable" version = "0.1.0" @@ -2130,6 +2323,16 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "walkdir" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "want" version = "0.3.1" @@ -2166,7 +2369,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.40", + "syn 2.0.48", "wasm-bindgen-shared", ] @@ -2200,7 +2403,7 @@ checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283" dependencies = [ "proc-macro2", "quote", - "syn 2.0.40", + "syn 2.0.48", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -2234,25 +2437,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "webpki" -version = "0.22.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed63aea5ce73d0ff405984102c42de94fc55a6b75765d621c65262469b3c9b53" -dependencies = [ - "ring 0.17.7", - "untrusted 0.9.0", -] - -[[package]] -name = "webpki-roots" -version = "0.22.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c71e40d7d2c34a5106301fb632274ca37242cd0c9d3e64dbece371a40a2d87" -dependencies = [ - "webpki", -] - [[package]] name = "webpki-roots" version = "0.25.3" diff --git a/Cargo.toml b/Cargo.toml index a629f5c..8c3f3df 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,30 +10,30 @@ build = "build.rs" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [build-dependencies] gray_matter = "0.2.6" -poise = "0.5.7" -serde = "1.0.193" -serde_json = "1.0.108" +poise = "0.6.1" +serde = "1.0.196" +serde_json = "1.0.112" [dependencies] -async-trait = "0.1.74" +async-trait = "0.1.77" color-eyre = "0.6.2" dotenvy = "0.15.7" -env_logger = "0.10.0" +env_logger = "0.11.1" log = "0.4.20" -poise = "0.5.7" -octocrab = "0.32.0" -once_cell = "1.18.0" +poise = "0.6.1" +octocrab = "0.33.3" +once_cell = "1.19.0" rand = "0.8.5" -redis = { version = "0.23.3", features = ["tokio-comp", "tokio-rustls-comp"] } +redis = { version = "0.24.0", features = ["tokio-comp", "tokio-rustls-comp"] } redis-macros = "0.2.1" -regex = "1.10.2" -reqwest = { version = "0.11.22", default-features = false, features = [ +regex = "1.10.3" +reqwest = { version = "0.11.23", default-features = false, features = [ "rustls-tls", "json", ] } -serde = "1.0.193" -serde_json = "1.0.108" -tokio = { version = "1.33.0", features = [ +serde = "1.0.196" +serde_json = "1.0.112" +tokio = { version = "1.35.1", features = [ "macros", "rt-multi-thread", "signal", diff --git a/src/commands/general/members.rs b/src/commands/general/members.rs index a971e65..99a3c03 100644 --- a/src/commands/general/members.rs +++ b/src/commands/general/members.rs @@ -1,11 +1,16 @@ use crate::{consts, Context}; use color_eyre::eyre::{eyre, Result}; +use poise::serenity_prelude::CreateEmbed; +use poise::CreateReply; /// Returns the number of members in the server #[poise::command(slash_command, prefix_command)] pub async fn members(ctx: Context<'_>) -> Result<()> { - let guild = ctx.guild().ok_or_else(|| eyre!("Couldn't fetch guild!"))?; + let guild = ctx + .guild() + .ok_or_else(|| eyre!("Couldn't fetch guild!"))? + .to_owned(); let count = guild.member_count; let online = if let Some(count) = guild.approximate_presence_count { @@ -14,13 +19,12 @@ pub async fn members(ctx: Context<'_>) -> Result<()> { "Undefined".to_string() }; - ctx.send(|m| { - m.embed(|e| { - e.title(format!("{count} total members!")) - .description(format!("{online} online members")) - .color(consts::COLORS["blue"]) - }) - }) - .await?; + let embed = CreateEmbed::new() + .title(format!("{count} total members!")) + .description(format!("{online} online members")) + .color(consts::COLORS["blue"]); + let reply = CreateReply::default().embed(embed); + + ctx.send(reply).await?; Ok(()) } diff --git a/src/commands/general/rory.rs b/src/commands/general/rory.rs index f20700c..6bddc5f 100644 --- a/src/commands/general/rory.rs +++ b/src/commands/general/rory.rs @@ -2,6 +2,8 @@ use crate::api::rory::get_rory; use crate::Context; use color_eyre::eyre::Result; +use poise::serenity_prelude::{CreateEmbed, CreateEmbedFooter}; +use poise::CreateReply; /// Gets a Rory photo! #[poise::command(slash_command, prefix_command)] @@ -11,19 +13,22 @@ pub async fn rory( ) -> Result<()> { let rory = get_rory(id).await?; - ctx.send(|m| { - m.embed(|e| { - if let Some(error) = rory.error { - e.title("Error!").description(error) - } else { - e.title("Rory :3") - .url(&rory.url) - .image(rory.url) - .footer(|f| f.text(format!("ID {}", rory.id))) - } - }) - }) - .await?; + let embed = { + let embed = CreateEmbed::new(); + if let Some(error) = rory.error { + embed.title("Error!").description(error) + } else { + let footer = CreateEmbedFooter::new(format!("ID {}", rory.id)); + embed + .title("Rory :3") + .url(&rory.url) + .image(rory.url) + .footer(footer) + } + }; + + let reply = CreateReply::default().embed(embed); + ctx.send(reply).await?; Ok(()) } diff --git a/src/commands/general/say.rs b/src/commands/general/say.rs index aad9836..796d55b 100644 --- a/src/commands/general/say.rs +++ b/src/commands/general/say.rs @@ -1,6 +1,7 @@ use crate::Context; use color_eyre::eyre::{eyre, Result}; +use poise::serenity_prelude::{CreateEmbed, CreateEmbedAuthor, CreateMessage}; /// Say something through the bot #[poise::command( @@ -11,7 +12,10 @@ use color_eyre::eyre::{eyre, Result}; required_permissions = "MODERATE_MEMBERS" )] pub async fn say(ctx: Context<'_>, #[description = "Just content?"] content: String) -> Result<()> { - let guild = ctx.guild().ok_or_else(|| eyre!("Couldn't get guild!"))?; + let guild = ctx + .guild() + .ok_or_else(|| eyre!("Couldn't get guild!"))? + .to_owned(); let channel = ctx .guild_channel() .await @@ -28,23 +32,16 @@ pub async fn say(ctx: Context<'_>, #[description = "Just content?"] content: Str .find(|c| c.0 == &channel_id) .ok_or_else(|| eyre!("Couldn't get log channel from guild!"))?; - log_channel - .1 - .clone() - .guild() - .ok_or_else(|| eyre!("Couldn't cast channel we found from guild as GuildChannel?????"))? - .send_message(ctx, |m| { - m.embed(|e| { - e.title("Say command used!") - .description(content) - .author(|a| { - a.name(ctx.author().tag()).icon_url( - ctx.author().avatar_url().unwrap_or("undefined".to_string()), - ) - }) - }) - }) - .await?; + let author = CreateEmbedAuthor::new(ctx.author().tag()) + .icon_url(ctx.author().avatar_url().unwrap_or("Undefined".to_string())); + + let embed = CreateEmbed::default() + .title("Say command used!") + .description(content) + .author(author); + + let message = CreateMessage::new().embed(embed); + log_channel.1.send_message(ctx, message).await?; } Ok(()) diff --git a/src/commands/general/stars.rs b/src/commands/general/stars.rs index 470712f..c1b850c 100644 --- a/src/commands/general/stars.rs +++ b/src/commands/general/stars.rs @@ -1,6 +1,8 @@ -use crate::{consts::COLORS, Context}; +use crate::{consts, Context}; use color_eyre::eyre::{Context as _, Result}; +use poise::serenity_prelude::CreateEmbed; +use poise::CreateReply; /// Returns GitHub stargazer count #[poise::command(slash_command, prefix_command)] @@ -19,13 +21,12 @@ pub async fn stars(ctx: Context<'_>) -> Result<()> { "undefined".to_string() }; - ctx.send(|m| { - m.embed(|e| { - e.title(format!("⭐ {count} total stars!")) - .color(COLORS["yellow"]) - }) - }) - .await?; + let embed = CreateEmbed::new() + .title(format!("⭐ {count} total stars!")) + .color(consts::COLORS["yellow"]); + let reply = CreateReply::default().embed(embed); + + ctx.send(reply).await?; Ok(()) } diff --git a/src/commands/general/tag.rs b/src/commands/general/tag.rs index 7d1c55f..4273d03 100644 --- a/src/commands/general/tag.rs +++ b/src/commands/general/tag.rs @@ -5,7 +5,8 @@ use std::env; use color_eyre::eyre::{eyre, Result}; use once_cell::sync::Lazy; -use poise::serenity_prelude::{Color, User}; +use poise::serenity_prelude::{Color, CreateEmbed, User}; +use poise::CreateReply; include!(concat!(env!("OUT_DIR"), "/generated.rs")); static TAGS: Lazy> = Lazy::new(|| serde_json::from_str(env!("TAGS")).unwrap()); @@ -25,36 +26,40 @@ pub async fn tag( let frontmatter = &tag.frontmatter; - ctx.send(|m| { - if let Some(user) = user { - m.content(format!("<@{}>", user.id)); + let embed = { + let mut e = CreateEmbed::new(); + + if let Some(color) = &frontmatter.color { + let color = *consts::COLORS + .get(color.as_str()) + .unwrap_or(&Color::default()); + e = e.color(color); } - m.embed(|e| { - e.title(&frontmatter.title); - e.description(&tag.content); + if let Some(image) = &frontmatter.image { + e = e.image(image); + } - if let Some(color) = &frontmatter.color { - let color = *consts::COLORS - .get(color.as_str()) - .unwrap_or(&Color::default()); - e.color(color); + if let Some(fields) = &frontmatter.fields { + for field in fields { + e = e.field(&field.name, &field.value, field.inline); } + } - if let Some(image) = &frontmatter.image { - e.image(image); - } + e + }; - if let Some(fields) = &frontmatter.fields { - for field in fields { - e.field(&field.name, &field.value, field.inline); - } - } + let reply = { + let mut r = CreateReply::default(); - e - }) - }) - .await?; + if let Some(user) = user { + r = r.content(format!("<@{}>", user.id)); + } + + r.embed(embed) + }; + + ctx.send(reply).await?; Ok(()) } diff --git a/src/handlers/error.rs b/src/handlers/error.rs index 12e4980..1d0cc2c 100644 --- a/src/handlers/error.rs +++ b/src/handlers/error.rs @@ -3,8 +3,8 @@ use crate::Data; use color_eyre::eyre::Report; use log::*; -use poise::serenity_prelude::Timestamp; -use poise::FrameworkError; +use poise::serenity_prelude::{CreateEmbed, Timestamp}; +use poise::{CreateReply, FrameworkError}; pub async fn handle(error: FrameworkError<'_, Data, Report>) { match error { @@ -12,23 +12,23 @@ pub async fn handle(error: FrameworkError<'_, Data, Report>) { error, framework, .. } => { error!("Error setting up client! Bailing out"); - framework.shard_manager().lock().await.shutdown_all().await; + framework.shard_manager().shutdown_all().await; panic!("{error}") } - FrameworkError::Command { error, ctx } => { + FrameworkError::Command { error, ctx, .. } => { error!("Error in command {}:\n{error:?}", ctx.command().name); - ctx.send(|c| { - c.embed(|e| { - e.title("Something went wrong!") - .description("oopsie") - .timestamp(Timestamp::now()) - .color(COLORS["red"]) - }) - }) - .await - .ok(); + + let embed = CreateEmbed::new() + .title("Something went wrong!") + .description("oopsie") + .timestamp(Timestamp::now()) + .color(COLORS["red"]); + + let reply = CreateReply::default().embed(embed); + + ctx.send(reply).await.ok(); } FrameworkError::EventHandler { @@ -36,13 +36,17 @@ pub async fn handle(error: FrameworkError<'_, Data, Report>) { ctx: _, event, framework: _, + .. } => { - error!("Error while handling event {}:\n{error:?}", event.name()); + error!( + "Error while handling event {}:\n{error:?}", + event.snake_case_name() + ); } error => { if let Err(e) = poise::builtins::on_error(error).await { - error!("Unhandled error occured:\n{e:#?}"); + error!("Unhandled error occurred:\n{e:#?}"); } } } diff --git a/src/handlers/event/analyze_logs/mod.rs b/src/handlers/event/analyze_logs/mod.rs index 80c660d..d8823d2 100644 --- a/src/handlers/event/analyze_logs/mod.rs +++ b/src/handlers/event/analyze_logs/mod.rs @@ -3,7 +3,9 @@ use crate::Data; use color_eyre::eyre::Result; use log::*; -use poise::serenity_prelude::{Context, Message}; +use poise::serenity_prelude::{ + Context, CreateAllowedMentions, CreateEmbed, CreateMessage, Message, +}; mod issues; mod providers; @@ -17,16 +19,16 @@ pub async fn handle(ctx: &Context, message: &Message, data: &Data) -> Result<()> let log = find_log(message).await; if log.is_err() { - channel - .send_message(ctx, |m| { - m.reference_message(message) - .allowed_mentions(|am| am.replied_user(true)) - .embed(|e| { - e.title("Analyze failed!") - .description("Couldn't download log") - }) - }) - .await?; + let embed = CreateEmbed::new() + .title("Analyze failed!") + .description("Couldn't download log"); + let allowed_mentions = CreateAllowedMentions::new().replied_user(true); + let our_message = CreateMessage::new() + .reference_message(message) + .allowed_mentions(allowed_mentions) + .embed(embed); + + channel.send_message(ctx, our_message).await?; return Ok(()); } @@ -38,31 +40,32 @@ pub async fn handle(ctx: &Context, message: &Message, data: &Data) -> Result<()> let issues = find_issues(&log, data).await?; - channel - .send_message(ctx, |m| { - m.reference_message(message) - .allowed_mentions(|am| am.replied_user(true)) - .embed(|e| { - e.title("Log analysis"); + let embed = { + let mut e = CreateEmbed::new().title("Log analysis"); - if issues.is_empty() { - e.color(COLORS["green"]).field( - "Analyze failed!", - "No issues found automatically", - false, - ); - } else { - e.color(COLORS["red"]); + if issues.is_empty() { + e = e.color(COLORS["green"]).field( + "Analyze failed!", + "No issues found automatically", + false, + ); + } else { + e = e.color(COLORS["red"]); - for (title, description) in issues { - e.field(title, description, false); - } - } + for (title, description) in issues { + e = e.field(title, description, false); + } + } - e - }) - }) - .await?; + e + }; + + let allowed_mentions = CreateAllowedMentions::new().replied_user(true); + let message = CreateMessage::new() + .allowed_mentions(allowed_mentions) + .embed(embed); + + channel.send_message(ctx, message).await?; Ok(()) } diff --git a/src/handlers/event/delete_on_reaction.rs b/src/handlers/event/delete_on_reaction.rs index c17deb5..cd8b65e 100644 --- a/src/handlers/event/delete_on_reaction.rs +++ b/src/handlers/event/delete_on_reaction.rs @@ -13,7 +13,7 @@ pub async fn handle(ctx: &Context, reaction: &Reaction) -> Result<()> { .wrap_err_with(|| "Couldn't fetch message from reaction!")?; if let Some(interaction) = &message.interaction { - if interaction.kind == InteractionType::ApplicationCommand + if interaction.kind == InteractionType::Command && interaction.user == user && reaction.emoji.unicode_eq("❌") { diff --git a/src/handlers/event/expand_link.rs b/src/handlers/event/expand_link.rs index 30504f1..3c41c61 100644 --- a/src/handlers/event/expand_link.rs +++ b/src/handlers/event/expand_link.rs @@ -1,27 +1,20 @@ -use color_eyre::eyre::{eyre, Context as _, Result}; -use poise::serenity_prelude::{Context, Message}; +use color_eyre::eyre::Result; +use poise::serenity_prelude::{Context, CreateAllowedMentions, CreateMessage, Message}; use crate::utils; pub async fn handle(ctx: &Context, message: &Message) -> Result<()> { let embeds = utils::resolve_message(ctx, message).await?; - // TOOD getchoo: actually reply to user + // TODO getchoo: actually reply to user // ...not sure why Message doesn't give me a builder in reply() or equivalents - let our_channel = message - .channel(ctx) - .await - .wrap_err_with(|| "Couldn't get channel from message!")? - .guild() - .ok_or_else(|| eyre!("Couldn't convert to GuildChannel!"))?; - if !embeds.is_empty() { - our_channel - .send_message(ctx, |m| { - m.set_embeds(embeds) - .allowed_mentions(|am| am.replied_user(false)) - }) - .await?; + let allowed_mentions = CreateAllowedMentions::new().replied_user(false); + let reply = CreateMessage::new() + .embeds(embeds) + .allowed_mentions(allowed_mentions); + + message.channel_id.send_message(ctx, reply).await?; } Ok(()) diff --git a/src/handlers/event/mod.rs b/src/handlers/event/mod.rs index 8d82984..a916ed0 100644 --- a/src/handlers/event/mod.rs +++ b/src/handlers/event/mod.rs @@ -2,8 +2,8 @@ use crate::{api, Data}; use color_eyre::eyre::{Report, Result}; use log::*; -use poise::serenity_prelude::{Activity, Context, OnlineStatus}; -use poise::{Event, FrameworkContext}; +use poise::serenity_prelude::{ActivityData, Context, FullEvent, OnlineStatus}; +use poise::FrameworkContext; mod analyze_logs; mod delete_on_reaction; @@ -14,22 +14,22 @@ mod support_onboard; pub async fn handle( ctx: &Context, - event: &Event<'_>, + event: &FullEvent, _framework: FrameworkContext<'_, Data, Report>, data: &Data, ) -> Result<()> { match event { - Event::Ready { data_about_bot } => { + FullEvent::Ready { data_about_bot } => { info!("Logged in as {}!", data_about_bot.user.name); let latest_minecraft_version = api::prism_meta::get_latest_minecraft_version().await?; - let activity = Activity::playing(format!("Minecraft {}", latest_minecraft_version)); + let activity = ActivityData::playing(format!("Minecraft {}", latest_minecraft_version)); info!("Setting presence to activity {activity:#?}"); - ctx.set_presence(Some(activity), OnlineStatus::Online).await; + ctx.set_presence(Some(activity), OnlineStatus::Online); } - Event::Message { new_message } => { + FullEvent::Message { new_message } => { // ignore new messages from bots // NOTE: the webhook_id check allows us to still respond to PK users if new_message.author.bot && new_message.webhook_id.is_none() { @@ -52,11 +52,13 @@ pub async fn handle( analyze_logs::handle(ctx, new_message, data).await?; } - Event::ReactionAdd { add_reaction } => { - delete_on_reaction::handle(ctx, add_reaction).await? + FullEvent::ReactionAdd { add_reaction } => { + delete_on_reaction::handle(ctx, add_reaction).await?; } - Event::ThreadCreate { thread } => support_onboard::handle(ctx, thread).await?, + FullEvent::ThreadCreate { thread } => { + support_onboard::handle(ctx, thread).await?; + } _ => {} } diff --git a/src/handlers/event/support_onboard.rs b/src/handlers/event/support_onboard.rs index e83a239..be08309 100644 --- a/src/handlers/event/support_onboard.rs +++ b/src/handlers/event/support_onboard.rs @@ -1,22 +1,23 @@ use color_eyre::eyre::{eyre, Result}; use log::*; -use poise::serenity_prelude::{ChannelType, Context, GuildChannel}; +use poise::serenity_prelude::{ + ChannelType, Context, CreateAllowedMentions, CreateMessage, GuildChannel, +}; pub async fn handle(ctx: &Context, thread: &GuildChannel) -> Result<()> { if thread.kind != ChannelType::PublicThread { + debug!("Not doing support onboard in non-thread channel"); return Ok(()); } - let parent_id = thread + if thread .parent_id - .ok_or_else(|| eyre!("Couldn't get parent ID from thread {}!", thread.name))?; - - let parent_channel = ctx - .cache - .guild_channel(parent_id) - .ok_or_else(|| eyre!("Couldn't get GuildChannel {}!", parent_id))?; - - if parent_channel.name != "support" { + .ok_or_else(|| eyre!("Couldn't get parent ID from thread {}!", thread.name))? + .name(ctx) + .await + .unwrap_or("".to_string()) + != "support" + { debug!("Not posting onboarding message to threads outside of support"); return Ok(()); } @@ -32,12 +33,15 @@ pub async fn handle(ctx: &Context, thread: &GuildChannel) -> Result<()> { "Please don't ping people for support questions, unless you have their permission." ); - thread - .send_message(ctx, |m| { - m.content(msg) - .allowed_mentions(|am| am.replied_user(true).users(Vec::from([owner]))) - }) - .await?; + let allowed_mentions = CreateAllowedMentions::new() + .replied_user(true) + .users(Vec::from([owner])); + + let message = CreateMessage::new() + .content(msg) + .allowed_mentions(allowed_mentions); + + thread.send_message(ctx, message).await?; Ok(()) } diff --git a/src/main.rs b/src/main.rs index cf89bd6..72ccddc 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,8 +10,6 @@ use poise::{ serenity_prelude as serenity, EditTracker, Framework, FrameworkOptions, PrefixFrameworkOptions, }; -use serenity::ShardManager; - use redis::ConnectionLike; use tokio::signal::ctrl_c; @@ -19,7 +17,6 @@ use tokio::signal::ctrl_c; use tokio::signal::unix::{signal, SignalKind}; #[cfg(target_family = "windows")] use tokio::signal::windows::ctrl_close; -use tokio::sync::Mutex; mod api; mod commands; @@ -78,9 +75,9 @@ async fn setup( Ok(data) } -async fn handle_shutdown(shard_manager: Arc>, reason: &str) { +async fn handle_shutdown(shard_manager: Arc, reason: &str) { warn!("{reason}! Shutting down bot..."); - shard_manager.lock().await.shutdown_all().await; + shard_manager.shutdown_all().await; println!("{}", "Everything is shutdown. Goodbye!".green()) } @@ -111,7 +108,9 @@ async fn main() -> Result<()> { prefix_options: PrefixFrameworkOptions { prefix: Some("r".into()), - edit_tracker: Some(EditTracker::for_timespan(Duration::from_secs(3600))), + edit_tracker: Some(Arc::from(EditTracker::for_timespan(Duration::from_secs( + 3600, + )))), ..Default::default() }, @@ -119,22 +118,23 @@ async fn main() -> Result<()> { }; let framework = Framework::builder() - .token(token) - .intents(intents) .options(options) .setup(|ctx, ready, framework| Box::pin(setup(ctx, ready, framework))) - .build() - .await - .wrap_err_with(|| "Failed to build framework!")?; + .build(); + + let mut client = serenity::ClientBuilder::new(token, intents) + .framework(framework) + .await?; + + let shard_manager = client.shard_manager.clone(); - let shard_manager = framework.shard_manager().clone(); #[cfg(target_family = "unix")] let mut sigterm = signal(SignalKind::terminate())?; #[cfg(target_family = "windows")] let mut sigterm = ctrl_close()?; tokio::select! { - result = framework.start() => result.map_err(Report::from), + result = client.start() => result.map_err(Report::from), _ = sigterm.recv() => { handle_shutdown(shard_manager, "Received SIGTERM").await; std::process::exit(0); diff --git a/src/storage/mod.rs b/src/storage/mod.rs index 022a051..6bbed10 100644 --- a/src/storage/mod.rs +++ b/src/storage/mod.rs @@ -69,7 +69,7 @@ impl Storage { Ok(()) } - async fn expire_key(&self, key: &str, expire_seconds: usize) -> Result<()> { + async fn expire_key(&self, key: &str, expire_seconds: i64) -> Result<()> { debug!("Expiring key {key} in {expire_seconds}"); let mut con = self.client.get_async_connection().await?; diff --git a/src/utils/resolve_message.rs b/src/utils/resolve_message.rs index 165e8c9..b29de97 100644 --- a/src/utils/resolve_message.rs +++ b/src/utils/resolve_message.rs @@ -1,7 +1,12 @@ +use std::str::FromStr; + use color_eyre::eyre::{eyre, Context as _, Result}; use log::*; use once_cell::sync::Lazy; -use poise::serenity_prelude::{ChannelType, Colour, Context, CreateEmbed, Message}; +use poise::serenity_prelude::{ + ChannelId, ChannelType, Colour, Context, CreateEmbed, CreateEmbedAuthor, CreateEmbedFooter, + Message, MessageId, +}; use regex::Regex; static MESSAGE_PATTERN: Lazy = Lazy::new(|| { @@ -21,103 +26,84 @@ pub fn find_first_image(msg: &Message) -> Option { } pub async fn resolve(ctx: &Context, msg: &Message) -> Result> { - let matches = MESSAGE_PATTERN.captures_iter(&msg.content); + let matches = MESSAGE_PATTERN + .captures_iter(&msg.content) + .map(|capture| capture.extract()); + let mut embeds: Vec = vec![]; - for captured in matches.take(3) { - // don't leak messages from other servers - if let Some(server_id) = captured.get(0) { - let other_server: u64 = server_id.as_str().parse().unwrap_or_default(); - let current_id = msg.guild_id.unwrap_or_default(); + for (_, [_server_id, channel_id, message_id]) in matches { + let channel = ChannelId::from_str(channel_id) + .wrap_err_with(|| format!("Couldn't parse channel ID {channel_id}!"))? + .to_channel_cached(ctx.as_ref()) + .ok_or_else(|| eyre!("Couldn't find Guild Channel from {channel_id}!"))? + .to_owned(); - if &other_server != current_id.as_u64() { - debug!("Not resolving message of other guild."); - continue; - } - } else { - warn!("Couldn't find server_id from Discord link! Not resolving message to be safe"); - continue; - } + let author_can_view = if channel.kind == ChannelType::PublicThread + || channel.kind == ChannelType::PrivateThread + { + let thread_members = channel + .id + .get_thread_members(ctx) + .await + .wrap_err("Couldn't get members from thread!")?; - if let Some(channel_id) = captured.get(1) { - let parsed: u64 = channel_id.as_str().parse().unwrap_or_default(); - let req_channel = ctx - .cache - .channel(parsed) - .ok_or_else(|| eyre!("Couldn't get channel_id from Discord regex!"))? - .guild() - .ok_or_else(|| { - eyre!("Couldn't convert to GuildChannel from channel_id {parsed}!") - })?; - - if !req_channel.is_text_based() { - debug!("Not resolving message is non-text-based channel."); - continue; - } - - if req_channel.kind == ChannelType::PrivateThread { - if let Some(id) = req_channel.parent_id { - let parent = ctx.cache.guild_channel(id).ok_or_else(|| { - eyre!("Couldn't get parent channel {id} for thread {req_channel}!") - })?; - let parent_members = parent.members(ctx).await.unwrap_or_default(); - - if !parent_members.iter().any(|m| m.user.id == msg.author.id) { - debug!("Not resolving message for user not a part of a private thread."); - continue; - } - } - } else if req_channel - .members(ctx) - .await? + thread_members .iter() - .any(|m| m.user.id == msg.author.id) - { - debug!("Not resolving for message for user not a part of a channel"); - continue; - } + .any(|member| member.user_id == msg.author.id) + } else { + channel + .members(ctx) + .wrap_err_with(|| format!("Couldn't get members for channel {channel_id}!"))? + .iter() + .any(|member| member.user.id == msg.author.id) + }; - let message_id: u64 = captured - .get(2) - .ok_or_else(|| eyre!("Couldn't get message_id from Discord regex!"))? - .as_str() - .parse() - .wrap_err_with(|| { - eyre!("Couldn't parse message_id from Discord regex as a MessageId!") - })?; - - let original_message = req_channel.message(ctx, message_id).await?; - let mut embed = CreateEmbed::default(); - embed - .author(|a| { - a.name(original_message.author.tag()) - .icon_url(original_message.author.default_avatar_url()) - }) - .color(Colour::BLITZ_BLUE) - .timestamp(original_message.timestamp) - .footer(|f| f.text(format!("#{}", req_channel.name))) - .description(format!( - "{}\n\n[Jump to original message]({})", - original_message.content, - original_message.link() - )); - - if !original_message.attachments.is_empty() { - embed.fields(original_message.attachments.iter().map(|a| { - ( - "Attachments".to_string(), - format!("[{}]({})", a.filename, a.url), - false, - ) - })); - - if let Some(image) = find_first_image(msg) { - embed.image(image); - } - } - - embeds.push(embed); + if !author_can_view { + debug!("Not resolving message for author who can't see it"); } + + let original_message = channel + .message( + ctx, + MessageId::from_str(message_id) + .wrap_err_with(|| format!("Couldn't parse message ID {message_id}!"))?, + ) + .await + .wrap_err_with(|| { + format!("Couldn't get message from ID {message_id} in channel {channel_id}!") + })?; + + let author = CreateEmbedAuthor::new(original_message.author.tag()) + .icon_url(original_message.author.default_avatar_url()); + let footer = CreateEmbedFooter::new(format!("#{}", channel.name)); + + let mut embed = CreateEmbed::new() + .author(author) + .color(Colour::BLITZ_BLUE) + .timestamp(original_message.timestamp) + .footer(footer) + .description(format!( + "{}\n\n[Jump to original message]({})", + original_message.content, + original_message.link() + )); + + if !original_message.attachments.is_empty() { + embed = embed.fields(original_message.attachments.iter().map(|a| { + ( + "Attachments".to_string(), + format!("[{}]({})", a.filename, a.url), + false, + ) + })); + + if let Some(image) = find_first_image(msg) { + embed = embed.image(image); + } + } + + embeds.push(embed); } Ok(embeds) From 2acb319821724819112e3fdb8a32b20473720a05 Mon Sep 17 00:00:00 2001 From: seth Date: Sat, 27 Jan 2024 22:35:43 -0500 Subject: [PATCH 45/90] fix(storage): set expiry for launcher version key --- src/handlers/event/analyze_logs/issues.rs | 4 ++-- src/storage/mod.rs | 11 ++++------- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/src/handlers/event/analyze_logs/issues.rs b/src/handlers/event/analyze_logs/issues.rs index 0f411e9..191e597 100644 --- a/src/handlers/event/analyze_logs/issues.rs +++ b/src/handlers/event/analyze_logs/issues.rs @@ -187,8 +187,8 @@ async fn outdated_launcher(log: &str, data: &Data) -> Result { let version_from_log = captures[0].replace("Prism Launcher version: ", ""); let storage = &data.storage; - let latest_version = if storage.launcher_version_is_cached().await? { - storage.get_launcher_version().await? + let latest_version = if let Ok(version) = storage.get_launcher_version().await { + version } else { let version = data .octocrab diff --git a/src/storage/mod.rs b/src/storage/mod.rs index 6bbed10..313aebc 100644 --- a/src/storage/mod.rs +++ b/src/storage/mod.rs @@ -60,6 +60,7 @@ impl Storage { Ok(exists > 0) } + /* we'll probably use this again async fn delete_key(&self, key: &str) -> Result<()> { debug!("Deleting key {key}"); @@ -68,6 +69,7 @@ impl Storage { Ok(()) } + */ async fn expire_key(&self, key: &str, expire_seconds: i64) -> Result<()> { debug!("Expiring key {key} in {expire_seconds}"); @@ -84,7 +86,7 @@ impl Storage { // Just store some value. We only care about the presence of this key self.set_key(&key, 0).await?; - self.expire_key(&key, 7 * 24 * 60 * 60).await?; + self.expire_key(&key, 7 * 24 * 60 * 60).await?; // weekly Ok(()) } @@ -96,6 +98,7 @@ impl Storage { pub async fn cache_launcher_version(&self, version: &str) -> Result<()> { self.set_key(LAUNCHER_VERSION_KEY, version).await?; + self.expire_key(LAUNCHER_VERSION_KEY, 24 * 60 * 60).await?; // 1 day Ok(()) } @@ -105,10 +108,4 @@ impl Storage { Ok(res) } - - pub async fn launcher_version_is_cached(&self) -> Result { - let res = self.key_exists(LAUNCHER_VERSION_KEY).await?; - - Ok(res) - } } From 72e171b960872238053d1705cd859e931052a51b Mon Sep 17 00:00:00 2001 From: seth Date: Sat, 27 Jan 2024 23:03:34 -0500 Subject: [PATCH 46/90] chore: cleanup unused config properties --- src/config/discord.rs | 79 ++++++++++--------------------------------- src/config/github.rs | 65 ----------------------------------- src/config/mod.rs | 21 +++--------- src/main.rs | 2 +- src/utils/macros.rs | 6 ---- src/utils/mod.rs | 2 -- 6 files changed, 23 insertions(+), 152 deletions(-) delete mode 100644 src/config/github.rs delete mode 100644 src/utils/macros.rs diff --git a/src/config/discord.rs b/src/config/discord.rs index 9897ef7..1201c83 100644 --- a/src/config/discord.rs +++ b/src/config/discord.rs @@ -1,15 +1,7 @@ -use crate::required_var; +use std::str::FromStr; -use color_eyre::eyre::{Context as _, Result}; use log::*; -use poise::serenity_prelude::{ApplicationId, ChannelId}; -use url::Url; - -#[derive(Debug, Clone)] -pub struct RefractionOAuth2 { - pub redirect_uri: Url, - pub scope: String, -} +use poise::serenity_prelude::ChannelId; #[derive(Debug, Clone, Default)] pub struct RefractionChannels { @@ -18,70 +10,33 @@ pub struct RefractionChannels { #[derive(Debug, Clone, Default)] pub struct DiscordConfig { - pub client_id: ApplicationId, - pub client_secret: String, - pub bot_token: String, - pub oauth2: RefractionOAuth2, pub channels: RefractionChannels, } -impl Default for RefractionOAuth2 { - fn default() -> Self { - Self { - scope: "identify connections role_connections.write".to_string(), - redirect_uri: Url::parse("https://google.com").unwrap(), - } - } -} - -impl RefractionOAuth2 { - pub fn new_from_env() -> Result { - let unparsed = format!("{}/oauth2/callback", required_var!("PUBLIC_URI")); - let redirect_uri = Url::parse(&unparsed)?; - - debug!("OAuth2 Redirect URI is {redirect_uri}"); - Ok(Self { - redirect_uri, - ..Default::default() - }) - } -} - impl RefractionChannels { - pub fn new_from_env() -> Result { - let unparsed = std::env::var("DISCORD_SAY_LOG_CHANNELID"); - if let Ok(unparsed) = unparsed { - let id = unparsed.parse::()?; - let channel_id = ChannelId::from(id); + pub fn new_from_env() -> Self { + let say_log_channel_id = Self::get_channel_from_env("DISCORD_SAY_LOG_CHANNELID"); - debug!("Log channel is {id}"); - Ok(Self { - say_log_channel_id: Some(channel_id), - }) + if let Some(channel_id) = say_log_channel_id { + info!("Log channel is {channel_id}"); } else { warn!("DISCORD_SAY_LOG_CHANNELID is empty; this will disable logging in your server."); - Ok(Self { - say_log_channel_id: None, - }) } + + Self { say_log_channel_id } + } + + fn get_channel_from_env(var: &str) -> Option { + std::env::var(var) + .ok() + .and_then(|env_var| ChannelId::from_str(&env_var).ok()) } } impl DiscordConfig { - pub fn new_from_env() -> Result { - let unparsed_client = required_var!("DISCORD_CLIENT_ID").parse::()?; - let client_id = ApplicationId::from(unparsed_client); - let client_secret = required_var!("DISCORD_CLIENT_SECRET"); - let bot_token = required_var!("DISCORD_BOT_TOKEN"); - let oauth2 = RefractionOAuth2::new_from_env()?; - let channels = RefractionChannels::new_from_env()?; + pub fn new_from_env() -> Self { + let channels = RefractionChannels::new_from_env(); - Ok(Self { - client_id, - client_secret, - bot_token, - oauth2, - channels, - }) + Self { channels } } } diff --git a/src/config/github.rs b/src/config/github.rs deleted file mode 100644 index d7b2c04..0000000 --- a/src/config/github.rs +++ /dev/null @@ -1,65 +0,0 @@ -use color_eyre::eyre::{Context as _, Result}; - -use crate::required_var; - -#[derive(Debug, Clone)] -pub struct RefractionRepo { - pub owner: String, - pub repo: String, - pub key: String, - pub name: String, -} - -#[derive(Debug, Clone)] -pub struct GithubConfig { - pub token: String, - pub repos: Vec, - pub cache_sec: u16, - pub update_cron_job: String, -} - -impl Default for GithubConfig { - fn default() -> Self { - let owner = "PrismLauncher".to_string(); - let repos = Vec::::from([ - RefractionRepo { - owner: owner.clone(), - repo: "PrismLauncher".to_string(), - key: "launcher".to_string(), - name: "Launcher contributor".to_string(), - }, - RefractionRepo { - owner: owner.clone(), - repo: "prismlauncher.org".to_string(), - - key: "website".to_string(), - name: "Web developer".to_string(), - }, - RefractionRepo { - owner: owner.clone(), - repo: "Translations".to_string(), - - key: "translations".to_string(), - name: "Translator".to_string(), - }, - ]); - - Self { - repos, - cache_sec: 3600, - update_cron_job: "0 */10 * * * *".to_string(), // every 10 minutes - token: String::default(), - } - } -} - -impl GithubConfig { - pub fn new_from_env() -> Result { - let token = required_var!("GITHUB_TOKEN"); - - Ok(Self { - token, - ..Default::default() - }) - } -} diff --git a/src/config/mod.rs b/src/config/mod.rs index b04938c..156da9a 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -1,16 +1,9 @@ -use color_eyre::eyre::Result; - mod discord; -mod github; - -pub use discord::*; -pub use github::*; +use discord::DiscordConfig; #[derive(Debug, Clone)] pub struct Config { pub discord: DiscordConfig, - pub github: GithubConfig, - pub http_port: u16, pub redis_url: String, } @@ -18,22 +11,18 @@ impl Default for Config { fn default() -> Self { Self { discord: DiscordConfig::default(), - github: GithubConfig::default(), - http_port: 3000, redis_url: "redis://localhost:6379".to_string(), } } } impl Config { - pub fn new_from_env() -> Result { - let discord = DiscordConfig::new_from_env()?; - let github = GithubConfig::new_from_env()?; + pub fn new_from_env() -> Self { + let discord = DiscordConfig::new_from_env(); - Ok(Self { + Self { discord, - github, ..Default::default() - }) + } } } diff --git a/src/main.rs b/src/main.rs index 72ccddc..9cf8bd6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -41,7 +41,7 @@ pub struct Data { impl Data { pub fn new() -> Result { - let config = Config::new_from_env()?; + let config = Config::new_from_env(); let storage = Storage::new(&config.redis_url)?; let octocrab = octocrab::instance(); diff --git a/src/utils/macros.rs b/src/utils/macros.rs deleted file mode 100644 index c167420..0000000 --- a/src/utils/macros.rs +++ /dev/null @@ -1,6 +0,0 @@ -#[macro_export] -macro_rules! required_var { - ($name: expr) => { - std::env::var($name).wrap_err_with(|| format!("Couldn't find {} in environment!", $name))? - }; -} diff --git a/src/utils/mod.rs b/src/utils/mod.rs index e8928e2..7592306 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -1,8 +1,6 @@ use color_eyre::eyre::{eyre, Result}; use rand::seq::SliceRandom; -#[macro_use] -mod macros; mod resolve_message; pub use resolve_message::resolve as resolve_message; From 2b3d81cfa4d746e7bd4920a36fcb0a8a1355d849 Mon Sep 17 00:00:00 2001 From: seth Date: Sat, 27 Jan 2024 23:10:00 -0500 Subject: [PATCH 47/90] refactor: better scope eta messages const --- src/consts.rs | 19 ------------------- src/handlers/event/eta.rs | 26 +++++++++++++++++++++++--- src/utils/mod.rs | 15 --------------- 3 files changed, 23 insertions(+), 37 deletions(-) diff --git a/src/consts.rs b/src/consts.rs index 3e9f541..5a0aa29 100644 --- a/src/consts.rs +++ b/src/consts.rs @@ -13,22 +13,3 @@ pub static COLORS: Lazy> = Lazy::new(|| { // TODO purple & pink :D ]) }); - -pub const ETA_MESSAGES: [&str; 16] = [ - "Sometime", - "Some day", - "Not far", - "The future", - "Never", - "Perhaps tomorrow?", - "There are no ETAs", - "No", - "Nah", - "Yes", - "Yas", - "Next month", - "Next year", - "Next week", - "In Prism Launcher 2.0.0", - "At the appropriate juncture, in due course, in the fullness of time", -]; diff --git a/src/handlers/event/eta.rs b/src/handlers/event/eta.rs index 8480189..dcf9dbe 100644 --- a/src/handlers/event/eta.rs +++ b/src/handlers/event/eta.rs @@ -1,12 +1,30 @@ -use crate::{consts, utils}; - use color_eyre::eyre::Result; use once_cell::sync::Lazy; use poise::serenity_prelude::{Context, Message}; +use rand::seq::SliceRandom; use regex::Regex; static ETA_REGEX: Lazy = Lazy::new(|| Regex::new(r"\beta\b").unwrap()); +pub const ETA_MESSAGES: [&str; 16] = [ + "Sometime", + "Some day", + "Not far", + "The future", + "Never", + "Perhaps tomorrow?", + "There are no ETAs", + "No", + "Nah", + "Yes", + "Yas", + "Next month", + "Next year", + "Next week", + "In Prism Launcher 2.0.0", + "At the appropriate juncture, in due course, in the fullness of time", +]; + pub async fn handle(ctx: &Context, message: &Message) -> Result<()> { if !ETA_REGEX.is_match(&message.content) { return Ok(()); @@ -14,7 +32,9 @@ pub async fn handle(ctx: &Context, message: &Message) -> Result<()> { let response = format!( "{} <:pofat:1031701005559144458>", - utils::random_choice(consts::ETA_MESSAGES)? + ETA_MESSAGES + .choose(&mut rand::thread_rng()) + .unwrap_or(&"sometime") ); message.reply(ctx, response).await?; diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 7592306..8cfb89f 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -1,18 +1,3 @@ -use color_eyre::eyre::{eyre, Result}; -use rand::seq::SliceRandom; - mod resolve_message; pub use resolve_message::resolve as resolve_message; - -/* - * chooses a random element from an array - */ -pub fn random_choice(arr: [&str; N]) -> Result { - let mut rng = rand::thread_rng(); - let resp = arr - .choose(&mut rng) - .ok_or_else(|| eyre!("Couldn't choose random object from array:\n{arr:#?}!"))?; - - Ok((*resp).to_string()) -} From a4abdd72e4f3bf719b6d232fb901202cb03d5d6f Mon Sep 17 00:00:00 2001 From: seth Date: Sat, 27 Jan 2024 23:18:35 -0500 Subject: [PATCH 48/90] refactor: harden clippy lints --- build.rs | 4 ++-- src/api/dadjoke.rs | 2 +- src/api/pluralkit.rs | 2 +- src/api/prism_meta.rs | 2 +- src/api/rory.rs | 8 ++++---- src/commands/general/rory.rs | 4 ++-- src/commands/general/tag.rs | 2 +- src/commands/mod.rs | 2 +- src/config/discord.rs | 6 +++--- src/config/mod.rs | 7 +++---- src/handlers/error.rs | 2 +- src/handlers/event/analyze_logs/issues.rs | 4 ++-- src/handlers/event/analyze_logs/mod.rs | 5 ++--- src/handlers/event/mod.rs | 4 ++-- src/handlers/event/pluralkit.rs | 2 +- src/handlers/event/support_onboard.rs | 4 ++-- src/main.rs | 10 +++++++--- src/storage/mod.rs | 2 +- src/utils/resolve_message.rs | 4 ++-- 19 files changed, 39 insertions(+), 37 deletions(-) diff --git a/build.rs b/build.rs index 5cd6329..9331c6d 100644 --- a/build.rs +++ b/build.rs @@ -54,7 +54,7 @@ fn main() { r#" #[allow(non_camel_case_types, clippy::upper_case_acronyms)] #[derive(Clone, Debug, poise::ChoiceParameter)] - pub enum TagChoice {{ + pub enum Choice {{ {} }}"#, formatted_names.join(",\n") @@ -62,7 +62,7 @@ fn main() { let to_str = format!( r#" - impl TagChoice {{ + impl Choice {{ fn as_str(&self) -> &str {{ match &self {{ {} diff --git a/src/api/dadjoke.rs b/src/api/dadjoke.rs index c0eed87..aecbb5e 100644 --- a/src/api/dadjoke.rs +++ b/src/api/dadjoke.rs @@ -1,7 +1,7 @@ use crate::api::REQWEST_CLIENT; use color_eyre::eyre::{eyre, Result}; -use log::*; +use log::debug; use reqwest::StatusCode; const DADJOKE: &str = "https://icanhazdadjoke.com"; diff --git a/src/api/pluralkit.rs b/src/api/pluralkit.rs index 439670a..9370418 100644 --- a/src/api/pluralkit.rs +++ b/src/api/pluralkit.rs @@ -1,7 +1,7 @@ use crate::api::REQWEST_CLIENT; use color_eyre::eyre::{eyre, Context, Result}; -use log::*; +use log::debug; use poise::serenity_prelude::{MessageId, UserId}; use reqwest::StatusCode; use serde::{Deserialize, Serialize}; diff --git a/src/api/prism_meta.rs b/src/api/prism_meta.rs index 7e24d08..aaa21ea 100644 --- a/src/api/prism_meta.rs +++ b/src/api/prism_meta.rs @@ -1,7 +1,7 @@ use crate::api::REQWEST_CLIENT; use color_eyre::eyre::{eyre, Context, Result}; -use log::*; +use log::debug; use reqwest::StatusCode; use serde::{Deserialize, Serialize}; diff --git a/src/api/rory.rs b/src/api/rory.rs index e527bba..7dd95c5 100644 --- a/src/api/rory.rs +++ b/src/api/rory.rs @@ -1,12 +1,12 @@ use crate::api::REQWEST_CLIENT; use color_eyre::eyre::{eyre, Context, Result}; -use log::*; +use log::debug; use reqwest::StatusCode; use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize)] -pub struct RoryResponse { +pub struct Response { pub id: u64, pub url: String, pub error: Option, @@ -15,7 +15,7 @@ pub struct RoryResponse { const RORY: &str = "https://rory.cat"; const ENDPOINT: &str = "/purr"; -pub async fn get_rory(id: Option) -> Result { +pub async fn get(id: Option) -> Result { let target = id.map(|id| id.to_string()).unwrap_or_default(); let req = REQWEST_CLIENT @@ -33,7 +33,7 @@ pub async fn get_rory(id: Option) -> Result { if let StatusCode::OK = status { let data = resp - .json::() + .json::() .await .wrap_err_with(|| "Couldn't parse the rory response!")?; diff --git a/src/commands/general/rory.rs b/src/commands/general/rory.rs index 6bddc5f..5047a83 100644 --- a/src/commands/general/rory.rs +++ b/src/commands/general/rory.rs @@ -1,4 +1,4 @@ -use crate::api::rory::get_rory; +use crate::api::rory; use crate::Context; use color_eyre::eyre::Result; @@ -11,7 +11,7 @@ pub async fn rory( ctx: Context<'_>, #[description = "specify a Rory ID"] id: Option, ) -> Result<()> { - let rory = get_rory(id).await?; + let rory = rory::get(id).await?; let embed = { let embed = CreateEmbed::new(); diff --git a/src/commands/general/tag.rs b/src/commands/general/tag.rs index 4273d03..d07c821 100644 --- a/src/commands/general/tag.rs +++ b/src/commands/general/tag.rs @@ -15,7 +15,7 @@ static TAGS: Lazy> = Lazy::new(|| serde_json::from_str(env!("TAGS")).un #[poise::command(slash_command, prefix_command)] pub async fn tag( ctx: Context<'_>, - #[description = "the copypasta you want to send"] name: TagChoice, + #[description = "the copypasta you want to send"] name: Choice, user: Option, ) -> Result<()> { let tag_file = name.as_str(); diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 0a2293f..c13691d 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -5,7 +5,7 @@ use poise::Command; mod general; -pub fn to_global_commands() -> Vec> { +pub fn get() -> Vec> { vec![ general::joke(), general::members(), diff --git a/src/config/discord.rs b/src/config/discord.rs index 1201c83..0e1ea7e 100644 --- a/src/config/discord.rs +++ b/src/config/discord.rs @@ -1,6 +1,6 @@ use std::str::FromStr; -use log::*; +use log::{info, warn}; use poise::serenity_prelude::ChannelId; #[derive(Debug, Clone, Default)] @@ -9,7 +9,7 @@ pub struct RefractionChannels { } #[derive(Debug, Clone, Default)] -pub struct DiscordConfig { +pub struct Config { pub channels: RefractionChannels, } @@ -33,7 +33,7 @@ impl RefractionChannels { } } -impl DiscordConfig { +impl Config { pub fn new_from_env() -> Self { let channels = RefractionChannels::new_from_env(); diff --git a/src/config/mod.rs b/src/config/mod.rs index 156da9a..11bcf09 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -1,16 +1,15 @@ mod discord; -use discord::DiscordConfig; #[derive(Debug, Clone)] pub struct Config { - pub discord: DiscordConfig, + pub discord: discord::Config, pub redis_url: String, } impl Default for Config { fn default() -> Self { Self { - discord: DiscordConfig::default(), + discord: discord::Config::default(), redis_url: "redis://localhost:6379".to_string(), } } @@ -18,7 +17,7 @@ impl Default for Config { impl Config { pub fn new_from_env() -> Self { - let discord = DiscordConfig::new_from_env(); + let discord = discord::Config::new_from_env(); Self { discord, diff --git a/src/handlers/error.rs b/src/handlers/error.rs index 1d0cc2c..2884ed9 100644 --- a/src/handlers/error.rs +++ b/src/handlers/error.rs @@ -2,7 +2,7 @@ use crate::consts::COLORS; use crate::Data; use color_eyre::eyre::Report; -use log::*; +use log::error; use poise::serenity_prelude::{CreateEmbed, Timestamp}; use poise::{CreateReply, FrameworkError}; diff --git a/src/handlers/event/analyze_logs/issues.rs b/src/handlers/event/analyze_logs/issues.rs index 191e597..79968dd 100644 --- a/src/handlers/event/analyze_logs/issues.rs +++ b/src/handlers/event/analyze_logs/issues.rs @@ -6,7 +6,7 @@ use regex::Regex; pub type Issue = Option<(String, String)>; -pub async fn find_issues(log: &str, data: &Data) -> Result> { +pub async fn find(log: &str, data: &Data) -> Result> { let issues = [ fabric_internal, flatpak_nvidia, @@ -24,7 +24,7 @@ pub async fn find_issues(log: &str, data: &Data) -> Result let mut res: Vec<(String, String)> = issues.iter().filter_map(|issue| issue(log)).collect(); if let Some(issues) = outdated_launcher(log, data).await? { - res.push(issues) + res.push(issues); } Ok(res) diff --git a/src/handlers/event/analyze_logs/mod.rs b/src/handlers/event/analyze_logs/mod.rs index d8823d2..aa25679 100644 --- a/src/handlers/event/analyze_logs/mod.rs +++ b/src/handlers/event/analyze_logs/mod.rs @@ -2,7 +2,7 @@ use crate::consts::COLORS; use crate::Data; use color_eyre::eyre::Result; -use log::*; +use log::debug; use poise::serenity_prelude::{ Context, CreateAllowedMentions, CreateEmbed, CreateMessage, Message, }; @@ -10,7 +10,6 @@ use poise::serenity_prelude::{ mod issues; mod providers; -use issues::find_issues; use providers::find_log; pub async fn handle(ctx: &Context, message: &Message, data: &Data) -> Result<()> { @@ -38,7 +37,7 @@ pub async fn handle(ctx: &Context, message: &Message, data: &Data) -> Result<()> return Ok(()); }; - let issues = find_issues(&log, data).await?; + let issues = issues::find(&log, data).await?; let embed = { let mut e = CreateEmbed::new().title("Log analysis"); diff --git a/src/handlers/event/mod.rs b/src/handlers/event/mod.rs index a916ed0..5ad4baa 100644 --- a/src/handlers/event/mod.rs +++ b/src/handlers/event/mod.rs @@ -1,7 +1,7 @@ use crate::{api, Data}; use color_eyre::eyre::{Report, Result}; -use log::*; +use log::{debug, info}; use poise::serenity_prelude::{ActivityData, Context, FullEvent, OnlineStatus}; use poise::FrameworkContext; @@ -23,7 +23,7 @@ pub async fn handle( info!("Logged in as {}!", data_about_bot.user.name); let latest_minecraft_version = api::prism_meta::get_latest_minecraft_version().await?; - let activity = ActivityData::playing(format!("Minecraft {}", latest_minecraft_version)); + let activity = ActivityData::playing(format!("Minecraft {latest_minecraft_version}")); info!("Setting presence to activity {activity:#?}"); ctx.set_presence(Some(activity), OnlineStatus::Online); diff --git a/src/handlers/event/pluralkit.rs b/src/handlers/event/pluralkit.rs index 0fdaf3c..12f3fc7 100644 --- a/src/handlers/event/pluralkit.rs +++ b/src/handlers/event/pluralkit.rs @@ -2,7 +2,7 @@ use crate::{api, Data}; use std::time::Duration; use color_eyre::eyre::Result; -use log::*; +use log::debug; use poise::serenity_prelude::{Context, Message}; use tokio::time::sleep; diff --git a/src/handlers/event/support_onboard.rs b/src/handlers/event/support_onboard.rs index be08309..3c7a6a1 100644 --- a/src/handlers/event/support_onboard.rs +++ b/src/handlers/event/support_onboard.rs @@ -1,5 +1,5 @@ use color_eyre::eyre::{eyre, Result}; -use log::*; +use log::debug; use poise::serenity_prelude::{ ChannelType, Context, CreateAllowedMentions, CreateMessage, GuildChannel, }; @@ -15,7 +15,7 @@ pub async fn handle(ctx: &Context, thread: &GuildChannel) -> Result<()> { .ok_or_else(|| eyre!("Couldn't get parent ID from thread {}!", thread.name))? .name(ctx) .await - .unwrap_or("".to_string()) + .unwrap_or(String::new()) != "support" { debug!("Not posting onboarding message to threads outside of support"); diff --git a/src/main.rs b/src/main.rs index 9cf8bd6..31051ff 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,10 +1,14 @@ +#![warn(clippy::all, clippy::pedantic, clippy::perf)] +#![allow(clippy::missing_errors_doc)] +#![forbid(unsafe_code)] + use std::sync::Arc; use std::time::Duration; use color_eyre::eyre::{eyre, Context as _, Report, Result}; use color_eyre::owo_colors::OwoColorize; -use log::*; +use log::{info, warn}; use poise::{ serenity_prelude as serenity, EditTracker, Framework, FrameworkOptions, PrefixFrameworkOptions, @@ -78,7 +82,7 @@ async fn setup( async fn handle_shutdown(shard_manager: Arc, reason: &str) { warn!("{reason}! Shutting down bot..."); shard_manager.shutdown_all().await; - println!("{}", "Everything is shutdown. Goodbye!".green()) + println!("{}", "Everything is shutdown. Goodbye!".green()); } #[tokio::main] @@ -94,7 +98,7 @@ async fn main() -> Result<()> { serenity::GatewayIntents::non_privileged() | serenity::GatewayIntents::MESSAGE_CONTENT; let options = FrameworkOptions { - commands: commands::to_global_commands(), + commands: commands::get(), on_error: |error| Box::pin(handlers::handle_error(error)), diff --git a/src/storage/mod.rs b/src/storage/mod.rs index 313aebc..ab2c63a 100644 --- a/src/storage/mod.rs +++ b/src/storage/mod.rs @@ -1,7 +1,7 @@ use std::fmt::Debug; use color_eyre::eyre::Result; -use log::*; +use log::{debug, info}; use poise::serenity_prelude::UserId; use redis::{AsyncCommands as _, Client, FromRedisValue, ToRedisArgs}; diff --git a/src/utils/resolve_message.rs b/src/utils/resolve_message.rs index b29de97..f20d337 100644 --- a/src/utils/resolve_message.rs +++ b/src/utils/resolve_message.rs @@ -1,7 +1,7 @@ use std::str::FromStr; use color_eyre::eyre::{eyre, Context as _, Result}; -use log::*; +use log::debug; use once_cell::sync::Lazy; use poise::serenity_prelude::{ ChannelId, ChannelType, Colour, Context, CreateEmbed, CreateEmbedAuthor, CreateEmbedFooter, @@ -19,7 +19,7 @@ pub fn find_first_image(msg: &Message) -> Option { .find(|a| { a.content_type .as_ref() - .unwrap_or(&"".to_string()) + .unwrap_or(&String::new()) .starts_with("image/") }) .map(|res| res.url.clone()) From fafa0bf689ab91840c51cac492fe8e730e2f84e8 Mon Sep 17 00:00:00 2001 From: seth Date: Sat, 27 Jan 2024 23:25:38 -0500 Subject: [PATCH 49/90] refactor: don't use re-exports of eyre & owo-colors --- Cargo.lock | 16 ++++++++++++---- Cargo.toml | 2 ++ src/api/dadjoke.rs | 2 +- src/api/pluralkit.rs | 2 +- src/api/prism_meta.rs | 2 +- src/api/rory.rs | 2 +- src/commands/general/joke.rs | 2 +- src/commands/general/members.rs | 2 +- src/commands/general/ping.rs | 2 +- src/commands/general/rory.rs | 2 +- src/commands/general/say.rs | 2 +- src/commands/general/stars.rs | 2 +- src/commands/general/tag.rs | 2 +- src/commands/mod.rs | 2 +- src/handlers/error.rs | 6 +++--- src/handlers/event/analyze_logs/issues.rs | 2 +- src/handlers/event/analyze_logs/mod.rs | 2 +- src/handlers/event/analyze_logs/providers/0x0.rs | 2 +- .../event/analyze_logs/providers/attachment.rs | 2 +- .../event/analyze_logs/providers/haste.rs | 2 +- .../event/analyze_logs/providers/mclogs.rs | 2 +- src/handlers/event/analyze_logs/providers/mod.rs | 2 +- .../event/analyze_logs/providers/paste_gg.rs | 2 +- .../event/analyze_logs/providers/pastebin.rs | 2 +- src/handlers/event/delete_on_reaction.rs | 2 +- src/handlers/event/eta.rs | 2 +- src/handlers/event/expand_link.rs | 2 +- src/handlers/event/mod.rs | 2 +- src/handlers/event/pluralkit.rs | 2 +- src/handlers/event/support_onboard.rs | 2 +- src/main.rs | 5 ++--- src/storage/mod.rs | 2 +- src/utils/resolve_message.rs | 2 +- 33 files changed, 48 insertions(+), 39 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 890eb89..7014914 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -257,7 +257,7 @@ dependencies = [ "eyre", "indenter", "once_cell", - "owo-colors", + "owo-colors 3.5.0", "tracing-error", ] @@ -268,7 +268,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd6be1b2a7e382e2b98b43b2adcca6bb0e465af0bdd38123873ae61eb17a72c2" dependencies = [ "once_cell", - "owo-colors", + "owo-colors 3.5.0", "tracing-core", "tracing-error", ] @@ -515,9 +515,9 @@ dependencies = [ [[package]] name = "eyre" -version = "0.6.9" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80f656be11ddf91bd709454d15d5bd896fbaf4cc3314e69349e4d1569f5b46cd" +checksum = "b6267a1fa6f59179ea4afc8e50fd8612a3cc60bc858f786ff877a4a8cb042799" dependencies = [ "indenter", "once_cell", @@ -1136,6 +1136,12 @@ version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" +[[package]] +name = "owo-colors" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caff54706df99d2a78a5a4e3455ff45448d81ef1bb63c22cd14052ca0e993a3f" + [[package]] name = "parking_lot" version = "0.12.1" @@ -1388,10 +1394,12 @@ dependencies = [ "color-eyre", "dotenvy", "env_logger", + "eyre", "gray_matter", "log", "octocrab", "once_cell", + "owo-colors 4.0.0", "poise", "rand", "redis 0.24.0", diff --git a/Cargo.toml b/Cargo.toml index 8c3f3df..13040b8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,10 +19,12 @@ async-trait = "0.1.77" color-eyre = "0.6.2" dotenvy = "0.15.7" env_logger = "0.11.1" +eyre = "0.6.11" log = "0.4.20" poise = "0.6.1" octocrab = "0.33.3" once_cell = "1.19.0" +owo-colors = "4.0.0" rand = "0.8.5" redis = { version = "0.24.0", features = ["tokio-comp", "tokio-rustls-comp"] } redis-macros = "0.2.1" diff --git a/src/api/dadjoke.rs b/src/api/dadjoke.rs index aecbb5e..b80d5ec 100644 --- a/src/api/dadjoke.rs +++ b/src/api/dadjoke.rs @@ -1,6 +1,6 @@ use crate::api::REQWEST_CLIENT; -use color_eyre::eyre::{eyre, Result}; +use eyre::{eyre, Result}; use log::debug; use reqwest::StatusCode; diff --git a/src/api/pluralkit.rs b/src/api/pluralkit.rs index 9370418..4c18a6b 100644 --- a/src/api/pluralkit.rs +++ b/src/api/pluralkit.rs @@ -1,6 +1,6 @@ use crate::api::REQWEST_CLIENT; -use color_eyre::eyre::{eyre, Context, Result}; +use eyre::{eyre, Context, Result}; use log::debug; use poise::serenity_prelude::{MessageId, UserId}; use reqwest::StatusCode; diff --git a/src/api/prism_meta.rs b/src/api/prism_meta.rs index aaa21ea..de6c04b 100644 --- a/src/api/prism_meta.rs +++ b/src/api/prism_meta.rs @@ -1,6 +1,6 @@ use crate::api::REQWEST_CLIENT; -use color_eyre::eyre::{eyre, Context, Result}; +use eyre::{eyre, Context, Result}; use log::debug; use reqwest::StatusCode; use serde::{Deserialize, Serialize}; diff --git a/src/api/rory.rs b/src/api/rory.rs index 7dd95c5..7e2a1a6 100644 --- a/src/api/rory.rs +++ b/src/api/rory.rs @@ -1,6 +1,6 @@ use crate::api::REQWEST_CLIENT; -use color_eyre::eyre::{eyre, Context, Result}; +use eyre::{eyre, Context, Result}; use log::debug; use reqwest::StatusCode; use serde::{Deserialize, Serialize}; diff --git a/src/commands/general/joke.rs b/src/commands/general/joke.rs index f1790a0..e31c395 100644 --- a/src/commands/general/joke.rs +++ b/src/commands/general/joke.rs @@ -1,7 +1,7 @@ use crate::api::dadjoke; use crate::Context; -use color_eyre::eyre::Result; +use eyre::Result; /// It's a joke #[poise::command(slash_command, prefix_command)] diff --git a/src/commands/general/members.rs b/src/commands/general/members.rs index 99a3c03..adbd5c2 100644 --- a/src/commands/general/members.rs +++ b/src/commands/general/members.rs @@ -1,6 +1,6 @@ use crate::{consts, Context}; -use color_eyre::eyre::{eyre, Result}; +use eyre::{eyre, Result}; use poise::serenity_prelude::CreateEmbed; use poise::CreateReply; diff --git a/src/commands/general/ping.rs b/src/commands/general/ping.rs index 41634dc..8e46531 100644 --- a/src/commands/general/ping.rs +++ b/src/commands/general/ping.rs @@ -1,6 +1,6 @@ use crate::Context; -use color_eyre::eyre::Result; +use eyre::Result; /// Replies with pong! #[poise::command(slash_command, prefix_command, ephemeral)] diff --git a/src/commands/general/rory.rs b/src/commands/general/rory.rs index 5047a83..ae61ab6 100644 --- a/src/commands/general/rory.rs +++ b/src/commands/general/rory.rs @@ -1,7 +1,7 @@ use crate::api::rory; use crate::Context; -use color_eyre::eyre::Result; +use eyre::Result; use poise::serenity_prelude::{CreateEmbed, CreateEmbedFooter}; use poise::CreateReply; diff --git a/src/commands/general/say.rs b/src/commands/general/say.rs index 796d55b..645cf4e 100644 --- a/src/commands/general/say.rs +++ b/src/commands/general/say.rs @@ -1,6 +1,6 @@ use crate::Context; -use color_eyre::eyre::{eyre, Result}; +use eyre::{eyre, Result}; use poise::serenity_prelude::{CreateEmbed, CreateEmbedAuthor, CreateMessage}; /// Say something through the bot diff --git a/src/commands/general/stars.rs b/src/commands/general/stars.rs index c1b850c..2cd9013 100644 --- a/src/commands/general/stars.rs +++ b/src/commands/general/stars.rs @@ -1,6 +1,6 @@ use crate::{consts, Context}; -use color_eyre::eyre::{Context as _, Result}; +use eyre::{Context as _, Result}; use poise::serenity_prelude::CreateEmbed; use poise::CreateReply; diff --git a/src/commands/general/tag.rs b/src/commands/general/tag.rs index d07c821..a636ad5 100644 --- a/src/commands/general/tag.rs +++ b/src/commands/general/tag.rs @@ -3,7 +3,7 @@ use crate::tags::Tag; use crate::{consts, Context}; use std::env; -use color_eyre::eyre::{eyre, Result}; +use eyre::{eyre, Result}; use once_cell::sync::Lazy; use poise::serenity_prelude::{Color, CreateEmbed, User}; use poise::CreateReply; diff --git a/src/commands/mod.rs b/src/commands/mod.rs index c13691d..00130a7 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -1,6 +1,6 @@ use crate::Data; -use color_eyre::eyre::Report; +use eyre::Report; use poise::Command; mod general; diff --git a/src/handlers/error.rs b/src/handlers/error.rs index 2884ed9..4dce9b5 100644 --- a/src/handlers/error.rs +++ b/src/handlers/error.rs @@ -1,7 +1,7 @@ -use crate::consts::COLORS; +use crate::consts; use crate::Data; -use color_eyre::eyre::Report; +use eyre::Report; use log::error; use poise::serenity_prelude::{CreateEmbed, Timestamp}; use poise::{CreateReply, FrameworkError}; @@ -24,7 +24,7 @@ pub async fn handle(error: FrameworkError<'_, Data, Report>) { .title("Something went wrong!") .description("oopsie") .timestamp(Timestamp::now()) - .color(COLORS["red"]); + .color(consts::COLORS["red"]); let reply = CreateReply::default().embed(embed); diff --git a/src/handlers/event/analyze_logs/issues.rs b/src/handlers/event/analyze_logs/issues.rs index 79968dd..11208c7 100644 --- a/src/handlers/event/analyze_logs/issues.rs +++ b/src/handlers/event/analyze_logs/issues.rs @@ -1,6 +1,6 @@ use crate::Data; -use color_eyre::eyre::Result; +use eyre::Result; use once_cell::sync::Lazy; use regex::Regex; diff --git a/src/handlers/event/analyze_logs/mod.rs b/src/handlers/event/analyze_logs/mod.rs index aa25679..15df9eb 100644 --- a/src/handlers/event/analyze_logs/mod.rs +++ b/src/handlers/event/analyze_logs/mod.rs @@ -1,7 +1,7 @@ use crate::consts::COLORS; use crate::Data; -use color_eyre::eyre::Result; +use eyre::Result; use log::debug; use poise::serenity_prelude::{ Context, CreateAllowedMentions, CreateEmbed, CreateMessage, Message, diff --git a/src/handlers/event/analyze_logs/providers/0x0.rs b/src/handlers/event/analyze_logs/providers/0x0.rs index 9a81771..fe8c7f0 100644 --- a/src/handlers/event/analyze_logs/providers/0x0.rs +++ b/src/handlers/event/analyze_logs/providers/0x0.rs @@ -1,6 +1,6 @@ use crate::api::REQWEST_CLIENT; -use color_eyre::eyre::{eyre, Result}; +use eyre::{eyre, Result}; use once_cell::sync::Lazy; use regex::Regex; use reqwest::StatusCode; diff --git a/src/handlers/event/analyze_logs/providers/attachment.rs b/src/handlers/event/analyze_logs/providers/attachment.rs index b69640b..892f19b 100644 --- a/src/handlers/event/analyze_logs/providers/attachment.rs +++ b/src/handlers/event/analyze_logs/providers/attachment.rs @@ -1,4 +1,4 @@ -use color_eyre::eyre::Result; +use eyre::Result; use poise::serenity_prelude::Message; pub async fn find(message: &Message) -> Result> { diff --git a/src/handlers/event/analyze_logs/providers/haste.rs b/src/handlers/event/analyze_logs/providers/haste.rs index 885d70e..c72e3b3 100644 --- a/src/handlers/event/analyze_logs/providers/haste.rs +++ b/src/handlers/event/analyze_logs/providers/haste.rs @@ -1,6 +1,6 @@ use crate::api::REQWEST_CLIENT; -use color_eyre::eyre::{eyre, Result}; +use eyre::{eyre, Result}; use once_cell::sync::Lazy; use regex::Regex; use reqwest::StatusCode; diff --git a/src/handlers/event/analyze_logs/providers/mclogs.rs b/src/handlers/event/analyze_logs/providers/mclogs.rs index 6663940..96a9108 100644 --- a/src/handlers/event/analyze_logs/providers/mclogs.rs +++ b/src/handlers/event/analyze_logs/providers/mclogs.rs @@ -1,6 +1,6 @@ use crate::api::REQWEST_CLIENT; -use color_eyre::eyre::{eyre, Result}; +use eyre::{eyre, Result}; use once_cell::sync::Lazy; use regex::Regex; use reqwest::StatusCode; diff --git a/src/handlers/event/analyze_logs/providers/mod.rs b/src/handlers/event/analyze_logs/providers/mod.rs index 8185056..2d13d6d 100644 --- a/src/handlers/event/analyze_logs/providers/mod.rs +++ b/src/handlers/event/analyze_logs/providers/mod.rs @@ -1,4 +1,4 @@ -use color_eyre::eyre::Result; +use eyre::Result; use poise::serenity_prelude::Message; #[path = "0x0.rs"] diff --git a/src/handlers/event/analyze_logs/providers/paste_gg.rs b/src/handlers/event/analyze_logs/providers/paste_gg.rs index a5a53c9..92063b9 100644 --- a/src/handlers/event/analyze_logs/providers/paste_gg.rs +++ b/src/handlers/event/analyze_logs/providers/paste_gg.rs @@ -1,6 +1,6 @@ use crate::api::REQWEST_CLIENT; -use color_eyre::eyre::{eyre, Result}; +use eyre::{eyre, Result}; use once_cell::sync::Lazy; use regex::Regex; use reqwest::StatusCode; diff --git a/src/handlers/event/analyze_logs/providers/pastebin.rs b/src/handlers/event/analyze_logs/providers/pastebin.rs index 0d870ab..75b9c89 100644 --- a/src/handlers/event/analyze_logs/providers/pastebin.rs +++ b/src/handlers/event/analyze_logs/providers/pastebin.rs @@ -1,6 +1,6 @@ use crate::api::REQWEST_CLIENT; -use color_eyre::eyre::{eyre, Result}; +use eyre::{eyre, Result}; use once_cell::sync::Lazy; use regex::Regex; use reqwest::StatusCode; diff --git a/src/handlers/event/delete_on_reaction.rs b/src/handlers/event/delete_on_reaction.rs index cd8b65e..a15e113 100644 --- a/src/handlers/event/delete_on_reaction.rs +++ b/src/handlers/event/delete_on_reaction.rs @@ -1,4 +1,4 @@ -use color_eyre::eyre::{Context as _, Result}; +use eyre::{Context as _, Result}; use poise::serenity_prelude::{Context, InteractionType, Reaction}; pub async fn handle(ctx: &Context, reaction: &Reaction) -> Result<()> { diff --git a/src/handlers/event/eta.rs b/src/handlers/event/eta.rs index dcf9dbe..db6cc41 100644 --- a/src/handlers/event/eta.rs +++ b/src/handlers/event/eta.rs @@ -1,4 +1,4 @@ -use color_eyre::eyre::Result; +use eyre::Result; use once_cell::sync::Lazy; use poise::serenity_prelude::{Context, Message}; use rand::seq::SliceRandom; diff --git a/src/handlers/event/expand_link.rs b/src/handlers/event/expand_link.rs index 3c41c61..05f6be0 100644 --- a/src/handlers/event/expand_link.rs +++ b/src/handlers/event/expand_link.rs @@ -1,4 +1,4 @@ -use color_eyre::eyre::Result; +use eyre::Result; use poise::serenity_prelude::{Context, CreateAllowedMentions, CreateMessage, Message}; use crate::utils; diff --git a/src/handlers/event/mod.rs b/src/handlers/event/mod.rs index 5ad4baa..508b411 100644 --- a/src/handlers/event/mod.rs +++ b/src/handlers/event/mod.rs @@ -1,6 +1,6 @@ use crate::{api, Data}; -use color_eyre::eyre::{Report, Result}; +use eyre::{Report, Result}; use log::{debug, info}; use poise::serenity_prelude::{ActivityData, Context, FullEvent, OnlineStatus}; use poise::FrameworkContext; diff --git a/src/handlers/event/pluralkit.rs b/src/handlers/event/pluralkit.rs index 12f3fc7..0089403 100644 --- a/src/handlers/event/pluralkit.rs +++ b/src/handlers/event/pluralkit.rs @@ -1,7 +1,7 @@ use crate::{api, Data}; use std::time::Duration; -use color_eyre::eyre::Result; +use eyre::Result; use log::debug; use poise::serenity_prelude::{Context, Message}; use tokio::time::sleep; diff --git a/src/handlers/event/support_onboard.rs b/src/handlers/event/support_onboard.rs index 3c7a6a1..4308de6 100644 --- a/src/handlers/event/support_onboard.rs +++ b/src/handlers/event/support_onboard.rs @@ -1,4 +1,4 @@ -use color_eyre::eyre::{eyre, Result}; +use eyre::{eyre, Result}; use log::debug; use poise::serenity_prelude::{ ChannelType, Context, CreateAllowedMentions, CreateMessage, GuildChannel, diff --git a/src/main.rs b/src/main.rs index 31051ff..6a8825f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,15 +5,14 @@ use std::sync::Arc; use std::time::Duration; -use color_eyre::eyre::{eyre, Context as _, Report, Result}; -use color_eyre::owo_colors::OwoColorize; - +use eyre::{eyre, Context as _, Report, Result}; use log::{info, warn}; use poise::{ serenity_prelude as serenity, EditTracker, Framework, FrameworkOptions, PrefixFrameworkOptions, }; +use owo_colors::OwoColorize; use redis::ConnectionLike; use tokio::signal::ctrl_c; diff --git a/src/storage/mod.rs b/src/storage/mod.rs index ab2c63a..a1c17e3 100644 --- a/src/storage/mod.rs +++ b/src/storage/mod.rs @@ -1,6 +1,6 @@ use std::fmt::Debug; -use color_eyre::eyre::Result; +use eyre::Result; use log::{debug, info}; use poise::serenity_prelude::UserId; use redis::{AsyncCommands as _, Client, FromRedisValue, ToRedisArgs}; diff --git a/src/utils/resolve_message.rs b/src/utils/resolve_message.rs index f20d337..58657f5 100644 --- a/src/utils/resolve_message.rs +++ b/src/utils/resolve_message.rs @@ -1,6 +1,6 @@ use std::str::FromStr; -use color_eyre::eyre::{eyre, Context as _, Result}; +use eyre::{eyre, Context as _, Result}; use log::debug; use once_cell::sync::Lazy; use poise::serenity_prelude::{ From f4fa7371245b3a72b8e203b09880a8af2d4dc1c4 Mon Sep 17 00:00:00 2001 From: seth Date: Sat, 27 Jan 2024 23:33:00 -0500 Subject: [PATCH 50/90] refactor: `ok_or_else()` -> `ok_or_eyre()` --- src/api/prism_meta.rs | 6 +++--- src/api/rory.rs | 6 +++--- src/commands/general/members.rs | 7 ++----- src/commands/general/say.rs | 11 ++++------- src/commands/general/stars.rs | 2 +- src/commands/general/tag.rs | 4 ++-- src/handlers/event/analyze_logs/providers/paste_gg.rs | 4 ++-- src/handlers/event/delete_on_reaction.rs | 4 ++-- src/handlers/event/support_onboard.rs | 4 ++-- src/main.rs | 4 ++-- 10 files changed, 23 insertions(+), 29 deletions(-) diff --git a/src/api/prism_meta.rs b/src/api/prism_meta.rs index de6c04b..f59e126 100644 --- a/src/api/prism_meta.rs +++ b/src/api/prism_meta.rs @@ -1,6 +1,6 @@ use crate::api::REQWEST_CLIENT; -use eyre::{eyre, Context, Result}; +use eyre::{eyre, Context, OptionExt, Result}; use log::debug; use reqwest::StatusCode; use serde::{Deserialize, Serialize}; @@ -30,12 +30,12 @@ pub async fn get_latest_minecraft_version() -> Result { let data = resp .json::() .await - .wrap_err_with(|| "Couldn't parse Minecraft versions!")?; + .wrap_err("Couldn't parse Minecraft versions!")?; let version = data .recommended .first() - .ok_or_else(|| eyre!("Couldn't find latest version of Minecraft!"))?; + .ok_or_eyre("Couldn't find latest version of Minecraft!")?; Ok(version.clone()) } else { diff --git a/src/api/rory.rs b/src/api/rory.rs index 7e2a1a6..e6ae45d 100644 --- a/src/api/rory.rs +++ b/src/api/rory.rs @@ -21,13 +21,13 @@ pub async fn get(id: Option) -> Result { let req = REQWEST_CLIENT .get(format!("{RORY}{ENDPOINT}/{target}")) .build() - .wrap_err_with(|| "Couldn't build reqwest client!")?; + .wrap_err("Couldn't build reqwest client!")?; debug!("Making request to {}", req.url()); let resp = REQWEST_CLIENT .execute(req) .await - .wrap_err_with(|| "Couldn't make request for rory!")?; + .wrap_err("Couldn't make request for rory!")?; let status = resp.status(); @@ -35,7 +35,7 @@ pub async fn get(id: Option) -> Result { let data = resp .json::() .await - .wrap_err_with(|| "Couldn't parse the rory response!")?; + .wrap_err("Couldn't parse the rory response!")?; Ok(data) } else { diff --git a/src/commands/general/members.rs b/src/commands/general/members.rs index adbd5c2..5387681 100644 --- a/src/commands/general/members.rs +++ b/src/commands/general/members.rs @@ -1,16 +1,13 @@ use crate::{consts, Context}; -use eyre::{eyre, Result}; +use eyre::{OptionExt, Result}; use poise::serenity_prelude::CreateEmbed; use poise::CreateReply; /// Returns the number of members in the server #[poise::command(slash_command, prefix_command)] pub async fn members(ctx: Context<'_>) -> Result<()> { - let guild = ctx - .guild() - .ok_or_else(|| eyre!("Couldn't fetch guild!"))? - .to_owned(); + let guild = ctx.guild().ok_or_eyre("Couldn't fetch guild!")?.to_owned(); let count = guild.member_count; let online = if let Some(count) = guild.approximate_presence_count { diff --git a/src/commands/general/say.rs b/src/commands/general/say.rs index 645cf4e..1413b60 100644 --- a/src/commands/general/say.rs +++ b/src/commands/general/say.rs @@ -1,6 +1,6 @@ use crate::Context; -use eyre::{eyre, Result}; +use eyre::{OptionExt, Result}; use poise::serenity_prelude::{CreateEmbed, CreateEmbedAuthor, CreateMessage}; /// Say something through the bot @@ -12,14 +12,11 @@ use poise::serenity_prelude::{CreateEmbed, CreateEmbedAuthor, CreateMessage}; required_permissions = "MODERATE_MEMBERS" )] pub async fn say(ctx: Context<'_>, #[description = "Just content?"] content: String) -> Result<()> { - let guild = ctx - .guild() - .ok_or_else(|| eyre!("Couldn't get guild!"))? - .to_owned(); + let guild = ctx.guild().ok_or_eyre("Couldn't get guild!")?.to_owned(); let channel = ctx .guild_channel() .await - .ok_or_else(|| eyre!("Couldn't get channel!"))?; + .ok_or_eyre("Couldn't get channel!")?; ctx.defer_ephemeral().await?; channel.say(ctx, &content).await?; @@ -30,7 +27,7 @@ pub async fn say(ctx: Context<'_>, #[description = "Just content?"] content: Str .channels .iter() .find(|c| c.0 == &channel_id) - .ok_or_else(|| eyre!("Couldn't get log channel from guild!"))?; + .ok_or_eyre("Couldn't get log channel from guild!")?; let author = CreateEmbedAuthor::new(ctx.author().tag()) .icon_url(ctx.author().avatar_url().unwrap_or("Undefined".to_string())); diff --git a/src/commands/general/stars.rs b/src/commands/general/stars.rs index 2cd9013..ecb4051 100644 --- a/src/commands/general/stars.rs +++ b/src/commands/general/stars.rs @@ -13,7 +13,7 @@ pub async fn stars(ctx: Context<'_>) -> Result<()> { .repos("PrismLauncher", "PrismLauncher") .get() .await - .wrap_err_with(|| "Couldn't get PrismLauncher/PrismLauncher from GitHub!")?; + .wrap_err("Couldn't get PrismLauncher/PrismLauncher from GitHub!")?; let count = if let Some(count) = prismlauncher.stargazers_count { count.to_string() diff --git a/src/commands/general/tag.rs b/src/commands/general/tag.rs index a636ad5..ccf1325 100644 --- a/src/commands/general/tag.rs +++ b/src/commands/general/tag.rs @@ -3,7 +3,7 @@ use crate::tags::Tag; use crate::{consts, Context}; use std::env; -use eyre::{eyre, Result}; +use eyre::{OptionExt, Result}; use once_cell::sync::Lazy; use poise::serenity_prelude::{Color, CreateEmbed, User}; use poise::CreateReply; @@ -22,7 +22,7 @@ pub async fn tag( let tag = TAGS .iter() .find(|t| t.file_name == tag_file) - .ok_or_else(|| eyre!("Tried to get non-existent tag: {tag_file}"))?; + .ok_or_eyre("Tried to get non-existent tag: {tag_file}")?; let frontmatter = &tag.frontmatter; diff --git a/src/handlers/event/analyze_logs/providers/paste_gg.rs b/src/handlers/event/analyze_logs/providers/paste_gg.rs index 92063b9..de7cd04 100644 --- a/src/handlers/event/analyze_logs/providers/paste_gg.rs +++ b/src/handlers/event/analyze_logs/providers/paste_gg.rs @@ -1,6 +1,6 @@ use crate::api::REQWEST_CLIENT; -use eyre::{eyre, Result}; +use eyre::{eyre, OptionExt, Result}; use once_cell::sync::Lazy; use regex::Regex; use reqwest::StatusCode; @@ -48,7 +48,7 @@ pub async fn find(content: &str) -> Result> { let paste_files: PasteResponse = resp.json().await?; let file_id = &paste_files .result - .ok_or_else(|| eyre!("Couldn't find any files associated with paste {paste_id}!"))?[0] + .ok_or_eyre("Couldn't find any files associated with paste {paste_id}!")?[0] .id; let raw_url = format!("{PASTE_GG}{PASTES_ENDPOINT}/{paste_id}/files/{file_id}/raw"); diff --git a/src/handlers/event/delete_on_reaction.rs b/src/handlers/event/delete_on_reaction.rs index a15e113..671a608 100644 --- a/src/handlers/event/delete_on_reaction.rs +++ b/src/handlers/event/delete_on_reaction.rs @@ -5,12 +5,12 @@ pub async fn handle(ctx: &Context, reaction: &Reaction) -> Result<()> { let user = reaction .user(ctx) .await - .wrap_err_with(|| "Couldn't fetch user from reaction!")?; + .wrap_err("Couldn't fetch user from reaction!")?; let message = reaction .message(ctx) .await - .wrap_err_with(|| "Couldn't fetch message from reaction!")?; + .wrap_err("Couldn't fetch message from reaction!")?; if let Some(interaction) = &message.interaction { if interaction.kind == InteractionType::Command diff --git a/src/handlers/event/support_onboard.rs b/src/handlers/event/support_onboard.rs index 4308de6..2d9fc52 100644 --- a/src/handlers/event/support_onboard.rs +++ b/src/handlers/event/support_onboard.rs @@ -1,4 +1,4 @@ -use eyre::{eyre, Result}; +use eyre::{eyre, OptionExt, Result}; use log::debug; use poise::serenity_prelude::{ ChannelType, Context, CreateAllowedMentions, CreateMessage, GuildChannel, @@ -24,7 +24,7 @@ pub async fn handle(ctx: &Context, thread: &GuildChannel) -> Result<()> { let owner = thread .owner_id - .ok_or_else(|| eyre!("Couldn't get owner of thread!"))?; + .ok_or_eyre("Couldn't get owner of thread!")?; let msg = format!( "<@{}> We've received your support ticket! {} {}", diff --git a/src/main.rs b/src/main.rs index 6a8825f..6c60d49 100644 --- a/src/main.rs +++ b/src/main.rs @@ -90,8 +90,8 @@ async fn main() -> Result<()> { color_eyre::install()?; env_logger::init(); - let token = std::env::var("DISCORD_BOT_TOKEN") - .wrap_err_with(|| "Couldn't find bot token in environment!")?; + let token = + std::env::var("DISCORD_BOT_TOKEN").wrap_err("Couldn't find bot token in environment!")?; let intents = serenity::GatewayIntents::non_privileged() | serenity::GatewayIntents::MESSAGE_CONTENT; From 24c8406590152384c7b63d7b16d3b7a70e34c9bd Mon Sep 17 00:00:00 2001 From: seth Date: Sat, 27 Jan 2024 23:49:35 -0500 Subject: [PATCH 51/90] chore: cleanup + update nix flake --- flake.lock | 95 ++++++++++++++++++++-------------------------- flake.nix | 9 +++-- nix/deployment.nix | 12 +++--- nix/derivation.nix | 68 ++++++++++++++------------------- nix/dev.nix | 10 +++-- nix/packages.nix | 6 +-- 6 files changed, 91 insertions(+), 109 deletions(-) diff --git a/flake.lock b/flake.lock index bfa3c9f..6393c20 100644 --- a/flake.lock +++ b/flake.lock @@ -8,11 +8,11 @@ "rust-analyzer-src": "rust-analyzer-src" }, "locked": { - "lastModified": 1702362203, - "narHash": "sha256-etsSWZfvmVA9RWqixmoKTf+JLEMtjlOaLxh/tK80ZCg=", + "lastModified": 1706336364, + "narHash": "sha256-mJ5i2YIVKv6jTN2+l3oOUUej2NUVjJX/H3bAq6019ks=", "owner": "nix-community", "repo": "fenix", - "rev": "7622e4a2d4378861d9e83fc02c1eeb0e084bf3f2", + "rev": "eb683549b7d76b12d1a009f888b91b70ed34485f", "type": "github" }, "original": { @@ -24,11 +24,11 @@ "flake-compat": { "flake": false, "locked": { - "lastModified": 1673956053, - "narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=", + "lastModified": 1696426674, + "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=", "owner": "edolstra", "repo": "flake-compat", - "rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9", + "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33", "type": "github" }, "original": { @@ -37,31 +37,16 @@ "type": "github" } }, - "flake-root": { - "locked": { - "lastModified": 1692742795, - "narHash": "sha256-f+Y0YhVCIJ06LemO+3Xx00lIcqQxSKJHXT/yk1RTKxw=", - "owner": "srid", - "repo": "flake-root", - "rev": "d9a70d9c7a5fd7f3258ccf48da9335e9b47c3937", - "type": "github" - }, - "original": { - "owner": "srid", - "repo": "flake-root", - "type": "github" - } - }, "flake-utils": { "inputs": { "systems": "systems" }, "locked": { - "lastModified": 1685518550, - "narHash": "sha256-o2d0KcvaXzTrPRIo0kOLV0/QXHhDQ5DTi+OxcjO8xqY=", + "lastModified": 1701680307, + "narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=", "owner": "numtide", "repo": "flake-utils", - "rev": "a1720a10a6cfe8234c0e93907ffe81be440f4cef", + "rev": "4022d587cbbfd70fe950c1e2083a02621806a725", "type": "github" }, "original": { @@ -78,11 +63,11 @@ ] }, "locked": { - "lastModified": 1660459072, - "narHash": "sha256-8DFJjXG8zqoONA1vXtgeKXy68KdJL5UaXR8NtVMUbx8=", + "lastModified": 1703887061, + "narHash": "sha256-gGPa9qWNc6eCXT/+Z5/zMkyYOuRZqeFZBDbopNZQkuY=", "owner": "hercules-ci", "repo": "gitignore.nix", - "rev": "a20de23b925fd8264fd7fad6454652e142fd7f73", + "rev": "43e1aa1308018f37118e34d3a9cb4f5e75dc11d5", "type": "github" }, "original": { @@ -113,11 +98,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1702272962, - "narHash": "sha256-D+zHwkwPc6oYQ4G3A1HuadopqRwUY/JkMwHz1YF7j4Q=", + "lastModified": 1706173671, + "narHash": "sha256-lciR7kQUK2FCAYuszyd7zyRRmTaXVeoZsCyK6QFpGdk=", "owner": "nixos", "repo": "nixpkgs", - "rev": "e97b3e4186bcadf0ef1b6be22b8558eab1cdeb5d", + "rev": "4fddc9be4eaf195d631333908f2a454b03628ee5", "type": "github" }, "original": { @@ -129,16 +114,16 @@ }, "nixpkgs-stable": { "locked": { - "lastModified": 1685801374, - "narHash": "sha256-otaSUoFEMM+LjBI1XL/xGB5ao6IwnZOXc47qhIgJe8U=", + "lastModified": 1704874635, + "narHash": "sha256-YWuCrtsty5vVZvu+7BchAxmcYzTMfolSPP5io8+WYCg=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "c37ca420157f4abc31e26f436c1145f8951ff373", + "rev": "3dc440faeee9e889fe2d1b4d25ad0f430d449356", "type": "github" }, "original": { "owner": "NixOS", - "ref": "nixos-23.05", + "ref": "nixos-23.11", "repo": "nixpkgs", "type": "github" } @@ -150,11 +135,11 @@ ] }, "locked": { - "lastModified": 1701473968, - "narHash": "sha256-YcVE5emp1qQ8ieHUnxt1wCZCC3ZfAS+SRRWZ2TMda7E=", + "lastModified": 1704982712, + "narHash": "sha256-2Ptt+9h8dczgle2Oo6z5ni5rt/uLMG47UFTR1ry/wgg=", "owner": "hercules-ci", "repo": "flake-parts", - "rev": "34fed993f1674c8d06d58b37ce1e0fe5eebcb9f5", + "rev": "07f6395285469419cf9d078f59b5b49993198c00", "type": "github" }, "original": { @@ -174,11 +159,11 @@ "nixpkgs-stable": "nixpkgs-stable" }, "locked": { - "lastModified": 1702325376, - "narHash": "sha256-biLGx2LzU2+/qPwq+kWwVBgXs3MVYT1gPa0fCwpLplU=", + "lastModified": 1705757126, + "narHash": "sha256-Eksr+n4Q8EYZKAN0Scef5JK4H6FcHc+TKNHb95CWm+c=", "owner": "cachix", "repo": "pre-commit-hooks.nix", - "rev": "e1d203c2fa7e2593c777e490213958ef81f71977", + "rev": "f56597d53fd174f796b5a7d3ee0b494f9e2285cc", "type": "github" }, "original": { @@ -187,40 +172,44 @@ "type": "github" } }, - "proc-flake": { + "procfile-nix": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, "locked": { - "lastModified": 1692742849, - "narHash": "sha256-Nv8SOX+O6twFfPnA9BfubbPLZpqc+UeK6JvIWnWkdb0=", - "owner": "srid", - "repo": "proc-flake", - "rev": "25291b6e3074ad5dd573c1cb7d96110a9591e10f", + "lastModified": 1706387387, + "narHash": "sha256-7C3HncC25yK1kvLp+/9KoBa1Iz5Ly2JtICqmCz2nvio=", + "owner": "getchoo", + "repo": "procfile-nix", + "rev": "31a33e4264e5c6214844993c5b508fb3500ef5cd", "type": "github" }, "original": { - "owner": "srid", - "repo": "proc-flake", + "owner": "getchoo", + "repo": "procfile-nix", "type": "github" } }, "root": { "inputs": { "fenix": "fenix", - "flake-root": "flake-root", "naersk": "naersk", "nixpkgs": "nixpkgs", "parts": "parts", "pre-commit-hooks": "pre-commit-hooks", - "proc-flake": "proc-flake" + "procfile-nix": "procfile-nix" } }, "rust-analyzer-src": { "flake": false, "locked": { - "lastModified": 1702322912, - "narHash": "sha256-CtQtHdJp6naa5xtMmrkNwZx0aVq4215RtWw5/f7l0zw=", + "lastModified": 1706295183, + "narHash": "sha256-VSyMaUsXfjb31B8/uT5cM5qXC1VOHLVsCi/bQuo3O/g=", "owner": "rust-lang", "repo": "rust-analyzer", - "rev": "8c3e28e3e2d4f190b633fbd43d689b45b28b5598", + "rev": "596e5c77cf5b2b660b3ac2ce732fa0596c246d9b", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index c311d1f..31f6c29 100644 --- a/flake.nix +++ b/flake.nix @@ -23,8 +23,10 @@ inputs.nixpkgs.follows = "nixpkgs"; }; - flake-root.url = "github:srid/flake-root"; - proc-flake.url = "github:srid/proc-flake"; + procfile-nix = { + url = "github:getchoo/procfile-nix"; + inputs.nixpkgs.follows = "nixpkgs"; + }; }; outputs = {parts, ...} @ inputs: @@ -35,8 +37,7 @@ ./nix/deployment.nix inputs.pre-commit-hooks.flakeModule - inputs.flake-root.flakeModule - inputs.proc-flake.flakeModule + inputs.procfile-nix.flakeModule ]; systems = [ diff --git a/nix/deployment.nix b/nix/deployment.nix index 49cd465..f22a9fe 100644 --- a/nix/deployment.nix +++ b/nix/deployment.nix @@ -14,7 +14,7 @@ ... }: let crossPkgsFor = - rec { + { x86_64-linux = { x86_64 = pkgs.pkgsStatic; aarch64 = pkgs.pkgsCross.aarch64-multiplatform.pkgsStatic; @@ -30,7 +30,7 @@ aarch64 = pkgs.pkgsCross.aarch64-multiplatform.pkgsStatic; }; - aarch64-darwin = x86_64-darwin; + aarch64-darwin = crossPkgsFor.x86_64-darwin; } .${system}; @@ -56,17 +56,17 @@ optimizeSize = true; }; - newAttrs = lib.fix (finalAttrs: { + newAttrs = { CARGO_BUILD_TARGET = target; "CC_${target'}" = "${cc}/bin/${cc.targetPrefix}cc"; "CARGO_TARGET_${targetUpper}_RUSTFLAGS" = "-C target-feature=+crt-static"; - "CARGO_TARGET_${targetUpper}_LINKER" = finalAttrs."CC_${target'}"; - }); + "CARGO_TARGET_${targetUpper}_LINKER" = newAttrs."CC_${target'}"; + }; inherit (crossPkgsFor.${arch}.stdenv) cc; in lib.getExe ( - refraction.overrideAttrs (_: newAttrs) + refraction.overrideAttrs (lib.const newAttrs) ); containerFor = arch: diff --git a/nix/derivation.nix b/nix/derivation.nix index 5f0ebee..6553dda 100644 --- a/nix/derivation.nix +++ b/nix/derivation.nix @@ -7,47 +7,35 @@ SystemConfiguration, version, optimizeSize ? false, -}: let - filter = path: type: let - path' = toString path; - base = baseNameOf path'; - parent = baseNameOf (dirOf path'); +}: +naersk.buildPackage { + pname = "refraction"; + inherit version; - dirBlocklist = ["nix"]; - - matches = lib.any (suffix: lib.hasSuffix suffix base) [".rs"]; - isCargo = base == "Cargo.lock" || base == "Cargo.toml"; - isTag = parent == "tags"; - isAllowedDir = !(builtins.elem base dirBlocklist); - in - (type == "directory" && isAllowedDir) || matches || isCargo || isTag; - - filterSource = src: - lib.cleanSourceWith { - src = lib.cleanSource src; - inherit filter; - }; -in - naersk.buildPackage { - pname = "refraction"; - inherit version; - - src = filterSource ../.; - - buildInputs = lib.optionals stdenv.hostPlatform.isDarwin [ - CoreFoundation - Security - SystemConfiguration + src = lib.fileset.toSource { + root = ../.; + fileset = lib.fileset.unions [ + ../src + ../build.rs + ../Cargo.lock + ../Cargo.toml + ../tags ]; + }; - RUSTFLAGS = lib.optionalString optimizeSize "-C codegen-units=1 -C strip=symbols -C opt-level=z"; + buildInputs = lib.optionals stdenv.hostPlatform.isDarwin [ + CoreFoundation + Security + SystemConfiguration + ]; - meta = with lib; { - mainProgram = "refraction"; - description = "Discord bot for Prism Launcher"; - homepage = "https://github.com/PrismLauncher/refraction"; - license = licenses.gpl3Plus; - platforms = with platforms; linux ++ darwin; - maintainers = with maintainers; [getchoo Scrumplex]; - }; - } + cargoBuildFlags = lib.optionals optimizeSize ["-C" "codegen-units=1" "-C" "strip=symbols" "-C" "opt-level=z"]; + + meta = with lib; { + mainProgram = "refraction"; + description = "Discord bot for Prism Launcher"; + homepage = "https://github.com/PrismLauncher/refraction"; + license = licenses.gpl3Plus; + maintainers = with maintainers; [getchoo Scrumplex]; + }; +} diff --git a/nix/dev.nix b/nix/dev.nix index 9591def..7d1a47e 100644 --- a/nix/dev.nix +++ b/nix/dev.nix @@ -9,7 +9,9 @@ pre-commit.settings.hooks = { actionlint.enable = true; alejandra.enable = true; + deadnix.enable = true; rustfmt.enable = true; + statix.enable = true; nil.enable = true; prettier = { enable = true; @@ -17,8 +19,8 @@ }; }; - proc.groups.daemons.processes = { - redis.command = "${lib.getExe' pkgs.redis "redis-server"}"; + procfiles.daemons.processes = { + redis = lib.getExe' pkgs.redis "redis-server"; }; devShells.default = pkgs.mkShell { @@ -29,7 +31,7 @@ packages = with pkgs; [ # general actionlint - config.proc.groups.daemons.package + config.procfiles.daemons.package # rust cargo @@ -40,7 +42,9 @@ # nix self'.formatter + deadnix nil + statix ]; RUST_SRC_PATH = "${pkgs.rust.packages.stable.rustPlatform.rustLibSrc}"; diff --git a/nix/packages.nix b/nix/packages.nix index e973cf9..a7d4333 100644 --- a/nix/packages.nix +++ b/nix/packages.nix @@ -6,12 +6,12 @@ perSystem = { pkgs, system, - config, + self', ... }: { packages = { refraction = pkgs.callPackage ./derivation.nix { - version = builtins.substring 0 8 self.lastModifiedDate or "dirty"; + version = builtins.substring 0 7 self.rev or "dirty"; inherit (pkgs.darwin.apple_sdk.frameworks) @@ -23,7 +23,7 @@ naersk = inputs.naersk.lib.${system}; }; - default = config.packages.refraction; + default = self'.packages.refraction; }; }; } From 46fd02605f935ffa5899b4461c915ce41730de25 Mon Sep 17 00:00:00 2001 From: seth Date: Sat, 27 Jan 2024 23:53:23 -0500 Subject: [PATCH 52/90] chore: cleanup docker & nix workflows --- .github/workflows/docker.yml | 12 ++++++------ .github/workflows/nix.yml | 2 ++ 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 7138f6e..44776b0 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -37,7 +37,7 @@ jobs: name: container-${{ matrix.arch }} path: ${{ steps.build.outputs.path }} if-no-files-found: error - retention-days: 1 + retention-days: 3 push: name: Push image @@ -79,12 +79,12 @@ jobs: architectures=("x86_64" "aarch64") for arch in "${architectures[@]}"; do docker load < images/container-"$arch"/*.tar.gz - docker tag refraction:latest-"$arch" ${{ env.TAG }}-"$arch" + docker tag refraction:latest-"$arch" "$TAG"-"$arch" docker push ${{ env.TAG }}-"$arch" done - docker manifest create ${{ env.TAG }} \ - --amend ${{ env.TAG }}-x86_64 \ - --amend ${{ env.TAG }}-aarch64 + docker manifest create "$TAG" \ + --amend "$TAG"-x86_64 \ + --amend "$TAG"-aarch64 - docker manifest push ${{ env.TAG }} + docker manifest push "$TAG" diff --git a/.github/workflows/nix.yml b/.github/workflows/nix.yml index b3752c8..e44af28 100644 --- a/.github/workflows/nix.yml +++ b/.github/workflows/nix.yml @@ -8,6 +8,7 @@ on: jobs: build: + name: Build strategy: matrix: os: [ubuntu-latest, macos-latest] @@ -28,6 +29,7 @@ jobs: run: nix build -L --fallback check: + name: Check runs-on: ubuntu-latest steps: From 651f14d724d2bd840fd607711b885e377f4391ed Mon Sep 17 00:00:00 2001 From: seth Date: Sun, 3 Mar 2024 18:06:40 -0500 Subject: [PATCH 53/90] storage: make constructors more idiomatic --- src/handlers/event/mod.rs | 6 +- src/handlers/event/pluralkit.rs | 6 +- src/main.rs | 16 +++--- src/storage/mod.rs | 99 ++++++++++----------------------- src/utils/resolve_message.rs | 2 +- 5 files changed, 44 insertions(+), 85 deletions(-) diff --git a/src/handlers/event/mod.rs b/src/handlers/event/mod.rs index 508b411..2fd32b0 100644 --- a/src/handlers/event/mod.rs +++ b/src/handlers/event/mod.rs @@ -1,7 +1,7 @@ use crate::{api, Data}; use eyre::{Report, Result}; -use log::{debug, info}; +use log::{debug, info, trace}; use poise::serenity_prelude::{ActivityData, Context, FullEvent, OnlineStatus}; use poise::FrameworkContext; @@ -15,7 +15,7 @@ mod support_onboard; pub async fn handle( ctx: &Context, event: &FullEvent, - _framework: FrameworkContext<'_, Data, Report>, + _: FrameworkContext<'_, Data, Report>, data: &Data, ) -> Result<()> { match event { @@ -33,7 +33,7 @@ pub async fn handle( // ignore new messages from bots // NOTE: the webhook_id check allows us to still respond to PK users if new_message.author.bot && new_message.webhook_id.is_none() { - debug!("Ignoring message {} from bot", new_message.id); + trace!("Ignoring message {} from bot", new_message.id); return Ok(()); } diff --git a/src/handlers/event/pluralkit.rs b/src/handlers/event/pluralkit.rs index 0089403..38a845f 100644 --- a/src/handlers/event/pluralkit.rs +++ b/src/handlers/event/pluralkit.rs @@ -2,7 +2,7 @@ use crate::{api, Data}; use std::time::Duration; use eyre::Result; -use log::debug; +use log::{debug, trace}; use poise::serenity_prelude::{Context, Message}; use tokio::time::sleep; @@ -20,14 +20,14 @@ pub async fn is_message_proxied(message: &Message) -> Result { Ok(proxied) } -pub async fn handle(_ctx: &Context, msg: &Message, data: &Data) -> Result<()> { +pub async fn handle(_: &Context, msg: &Message, data: &Data) -> Result<()> { if msg.webhook_id.is_some() { debug!( "Message {} has a webhook ID. Checking if it was sent through PluralKit", msg.id ); - debug!( + trace!( "Waiting on PluralKit API for {} seconds", PK_DELAY_SEC.as_secs() ); diff --git a/src/main.rs b/src/main.rs index 6c60d49..3637034 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,6 +8,7 @@ use std::time::Duration; use eyre::{eyre, Context as _, Report, Result}; use log::{info, warn}; +use octocrab::Octocrab; use poise::{ serenity_prelude as serenity, EditTracker, Framework, FrameworkOptions, PrefixFrameworkOptions, }; @@ -43,11 +44,7 @@ pub struct Data { } impl Data { - pub fn new() -> Result { - let config = Config::new_from_env(); - let storage = Storage::new(&config.redis_url)?; - let octocrab = octocrab::instance(); - + pub fn new(config: Config, storage: Storage, octocrab: Arc) -> Result { Ok(Self { config, storage, @@ -58,13 +55,16 @@ impl Data { async fn setup( ctx: &serenity::Context, - _ready: &serenity::Ready, + _: &serenity::Ready, framework: &Framework, ) -> Result { - let data = Data::new()?; + let config = Config::new_from_env(); + let storage = Storage::from_url(&config.redis_url)?; + let octocrab = octocrab::instance(); + let data = Data::new(config, storage, octocrab)?; // test redis connection - let mut client = data.storage.client.clone(); + let mut client = data.storage.client().clone(); if !client.check_connection() { return Err(eyre!( diff --git a/src/storage/mod.rs b/src/storage/mod.rs index a1c17e3..af2b5f5 100644 --- a/src/storage/mod.rs +++ b/src/storage/mod.rs @@ -1,110 +1,69 @@ use std::fmt::Debug; use eyre::Result; -use log::{debug, info}; +use log::debug; use poise::serenity_prelude::UserId; -use redis::{AsyncCommands as _, Client, FromRedisValue, ToRedisArgs}; +use redis::{AsyncCommands, Client}; const PK_KEY: &str = "pluralkit-v1"; const LAUNCHER_VERSION_KEY: &str = "launcher-version-v1"; #[derive(Clone, Debug)] pub struct Storage { - pub client: Client, + client: Client, } impl Storage { - pub fn new(redis_url: &str) -> Result { + pub fn new(client: Client) -> Self { + Self { client } + } + + pub fn from_url(redis_url: &str) -> Result { let client = Client::open(redis_url)?; - Ok(Self { client }) + Ok(Self::new(client)) } - /* - these are mainly light abstractions to avoid the `let mut con` - boilerplate, as well as not require the caller to format the - strings for keys - */ - - async fn get_key(&self, key: &str) -> Result - where - T: FromRedisValue, - { - debug!("Getting key {key}"); - - let mut con = self.client.get_async_connection().await?; - let res: T = con.get(key).await?; - - Ok(res) - } - - async fn set_key<'a>( - &self, - key: &str, - value: impl ToRedisArgs + Debug + Send + Sync + 'a, - ) -> Result<()> { - debug!("Creating key {key}:\n{value:#?}"); - - let mut con = self.client.get_async_connection().await?; - con.set(key, value).await?; - - Ok(()) - } - - async fn key_exists(&self, key: &str) -> Result { - debug!("Checking if key {key} exists"); - - let mut con = self.client.get_async_connection().await?; - let exists: u64 = con.exists(key).await?; - - Ok(exists > 0) - } - - /* we'll probably use this again - async fn delete_key(&self, key: &str) -> Result<()> { - debug!("Deleting key {key}"); - - let mut con = self.client.get_async_connection().await?; - con.del(key).await?; - - Ok(()) - } - */ - - async fn expire_key(&self, key: &str, expire_seconds: i64) -> Result<()> { - debug!("Expiring key {key} in {expire_seconds}"); - - let mut con = self.client.get_async_connection().await?; - con.expire(key, expire_seconds).await?; - - Ok(()) + pub fn client(&self) -> &Client { + &self.client } pub async fn store_user_plurality(&self, sender: UserId) -> Result<()> { - info!("Marking {sender} as a PluralKit user"); + debug!("Marking {sender} as a PluralKit user"); let key = format!("{PK_KEY}:{sender}"); + let mut con = self.client.get_async_connection().await?; // Just store some value. We only care about the presence of this key - self.set_key(&key, 0).await?; - self.expire_key(&key, 7 * 24 * 60 * 60).await?; // weekly + con.set_ex(key, 0, 7 * 24 * 60 * 60).await?; // 1 week Ok(()) } pub async fn is_user_plural(&self, user_id: UserId) -> Result { + debug!("Checking if user {user_id} is plural"); let key = format!("{PK_KEY}:{user_id}"); - self.key_exists(&key).await + + let mut con = self.client.get_async_connection().await?; + let exists = con.exists(key).await?; + + Ok(exists) } pub async fn cache_launcher_version(&self, version: &str) -> Result<()> { - self.set_key(LAUNCHER_VERSION_KEY, version).await?; - self.expire_key(LAUNCHER_VERSION_KEY, 24 * 60 * 60).await?; // 1 day + debug!("Caching launcher version as {version}"); + + let mut con = self.client.get_async_connection().await?; + con.set_ex(LAUNCHER_VERSION_KEY, version, 24 * 60 * 60) + .await?; // 1 day Ok(()) } pub async fn get_launcher_version(&self) -> Result { - let res = self.get_key(LAUNCHER_VERSION_KEY).await?; + debug!("Fetching launcher version"); + + let mut con = self.client.get_async_connection().await?; + let res = con.get(LAUNCHER_VERSION_KEY).await?; Ok(res) } diff --git a/src/utils/resolve_message.rs b/src/utils/resolve_message.rs index 58657f5..a1554c5 100644 --- a/src/utils/resolve_message.rs +++ b/src/utils/resolve_message.rs @@ -13,7 +13,7 @@ static MESSAGE_PATTERN: Lazy = Lazy::new(|| { Regex::new(r"/(https?:\/\/)?(?:canary\.|ptb\.)?discord(?:app)?\.com\/channels\/(?\d+)\/(?\d+)\/(?\d+)/g;").unwrap() }); -pub fn find_first_image(msg: &Message) -> Option { +fn find_first_image(msg: &Message) -> Option { msg.attachments .iter() .find(|a| { From 915ef54dc3bd8de729e35e286efbc9ed53b39768 Mon Sep 17 00:00:00 2001 From: seth Date: Sun, 3 Mar 2024 18:32:44 -0500 Subject: [PATCH 54/90] log more actions + tidy up things --- src/commands/general/joke.rs | 2 ++ src/commands/general/members.rs | 2 ++ src/commands/general/ping.rs | 2 ++ src/commands/general/rory.rs | 2 ++ src/commands/general/say.rs | 2 +- src/commands/general/stars.rs | 3 +++ src/commands/general/tag.rs | 7 ++++-- src/config/discord.rs | 28 +++++++++++++++++++----- src/handlers/event/delete_on_reaction.rs | 2 ++ src/handlers/event/eta.rs | 4 +++- src/handlers/event/pluralkit.rs | 6 ++--- src/handlers/event/support_onboard.rs | 6 ++--- src/main.rs | 3 ++- src/utils/resolve_message.rs | 6 +++-- 14 files changed, 56 insertions(+), 19 deletions(-) diff --git a/src/commands/general/joke.rs b/src/commands/general/joke.rs index e31c395..b6b2c60 100644 --- a/src/commands/general/joke.rs +++ b/src/commands/general/joke.rs @@ -2,10 +2,12 @@ use crate::api::dadjoke; use crate::Context; use eyre::Result; +use log::trace; /// It's a joke #[poise::command(slash_command, prefix_command)] pub async fn joke(ctx: Context<'_>) -> Result<()> { + trace!("Running joke command"); let joke = dadjoke::get_joke().await?; ctx.reply(joke).await?; diff --git a/src/commands/general/members.rs b/src/commands/general/members.rs index 5387681..39da469 100644 --- a/src/commands/general/members.rs +++ b/src/commands/general/members.rs @@ -1,12 +1,14 @@ use crate::{consts, Context}; use eyre::{OptionExt, Result}; +use log::trace; use poise::serenity_prelude::CreateEmbed; use poise::CreateReply; /// Returns the number of members in the server #[poise::command(slash_command, prefix_command)] pub async fn members(ctx: Context<'_>) -> Result<()> { + trace!("Running members command"); let guild = ctx.guild().ok_or_eyre("Couldn't fetch guild!")?.to_owned(); let count = guild.member_count; diff --git a/src/commands/general/ping.rs b/src/commands/general/ping.rs index 8e46531..f21a7c0 100644 --- a/src/commands/general/ping.rs +++ b/src/commands/general/ping.rs @@ -1,10 +1,12 @@ use crate::Context; use eyre::Result; +use log::trace; /// Replies with pong! #[poise::command(slash_command, prefix_command, ephemeral)] pub async fn ping(ctx: Context<'_>) -> Result<()> { + trace!("Running ping command!"); ctx.reply("Pong!").await?; Ok(()) } diff --git a/src/commands/general/rory.rs b/src/commands/general/rory.rs index ae61ab6..ea2b0a1 100644 --- a/src/commands/general/rory.rs +++ b/src/commands/general/rory.rs @@ -2,6 +2,7 @@ use crate::api::rory; use crate::Context; use eyre::Result; +use log::trace; use poise::serenity_prelude::{CreateEmbed, CreateEmbedFooter}; use poise::CreateReply; @@ -11,6 +12,7 @@ pub async fn rory( ctx: Context<'_>, #[description = "specify a Rory ID"] id: Option, ) -> Result<()> { + trace!("Running rory command"); let rory = rory::get(id).await?; let embed = { diff --git a/src/commands/general/say.rs b/src/commands/general/say.rs index 1413b60..eeb2e9b 100644 --- a/src/commands/general/say.rs +++ b/src/commands/general/say.rs @@ -22,7 +22,7 @@ pub async fn say(ctx: Context<'_>, #[description = "Just content?"] content: Str channel.say(ctx, &content).await?; ctx.say("I said what you said!").await?; - if let Some(channel_id) = ctx.data().config.discord.channels.say_log_channel_id { + if let Some(channel_id) = ctx.data().config.discord.channels().say_log_channel_id() { let log_channel = guild .channels .iter() diff --git a/src/commands/general/stars.rs b/src/commands/general/stars.rs index ecb4051..200a562 100644 --- a/src/commands/general/stars.rs +++ b/src/commands/general/stars.rs @@ -1,12 +1,15 @@ use crate::{consts, Context}; use eyre::{Context as _, Result}; +use log::trace; use poise::serenity_prelude::CreateEmbed; use poise::CreateReply; /// Returns GitHub stargazer count #[poise::command(slash_command, prefix_command)] pub async fn stars(ctx: Context<'_>) -> Result<()> { + trace!("Running stars command"); + let prismlauncher = ctx .data() .octocrab diff --git a/src/commands/general/tag.rs b/src/commands/general/tag.rs index ccf1325..5d95e28 100644 --- a/src/commands/general/tag.rs +++ b/src/commands/general/tag.rs @@ -3,7 +3,8 @@ use crate::tags::Tag; use crate::{consts, Context}; use std::env; -use eyre::{OptionExt, Result}; +use eyre::{eyre, Result}; +use log::trace; use once_cell::sync::Lazy; use poise::serenity_prelude::{Color, CreateEmbed, User}; use poise::CreateReply; @@ -18,11 +19,13 @@ pub async fn tag( #[description = "the copypasta you want to send"] name: Choice, user: Option, ) -> Result<()> { + trace!("Running tag command"); + let tag_file = name.as_str(); let tag = TAGS .iter() .find(|t| t.file_name == tag_file) - .ok_or_eyre("Tried to get non-existent tag: {tag_file}")?; + .ok_or_else(|| eyre!("Tried to get non-existent tag: {tag_file}"))?; let frontmatter = &tag.frontmatter; diff --git a/src/config/discord.rs b/src/config/discord.rs index 0e1ea7e..f5f9879 100644 --- a/src/config/discord.rs +++ b/src/config/discord.rs @@ -3,17 +3,21 @@ use std::str::FromStr; use log::{info, warn}; use poise::serenity_prelude::ChannelId; -#[derive(Debug, Clone, Default)] +#[derive(Clone, Copy, Debug, Default)] pub struct RefractionChannels { - pub say_log_channel_id: Option, + say_log_channel_id: Option, } -#[derive(Debug, Clone, Default)] +#[derive(Clone, Copy, Debug, Default)] pub struct Config { - pub channels: RefractionChannels, + channels: RefractionChannels, } impl RefractionChannels { + pub fn new(say_log_channel_id: Option) -> Self { + Self { say_log_channel_id } + } + pub fn new_from_env() -> Self { let say_log_channel_id = Self::get_channel_from_env("DISCORD_SAY_LOG_CHANNELID"); @@ -23,7 +27,7 @@ impl RefractionChannels { warn!("DISCORD_SAY_LOG_CHANNELID is empty; this will disable logging in your server."); } - Self { say_log_channel_id } + Self::new(say_log_channel_id) } fn get_channel_from_env(var: &str) -> Option { @@ -31,12 +35,24 @@ impl RefractionChannels { .ok() .and_then(|env_var| ChannelId::from_str(&env_var).ok()) } + + pub fn say_log_channel_id(self) -> Option { + self.say_log_channel_id + } } impl Config { + pub fn new(channels: RefractionChannels) -> Self { + Self { channels } + } + pub fn new_from_env() -> Self { let channels = RefractionChannels::new_from_env(); - Self { channels } + Self::new(channels) + } + + pub fn channels(self) -> RefractionChannels { + self.channels } } diff --git a/src/handlers/event/delete_on_reaction.rs b/src/handlers/event/delete_on_reaction.rs index 671a608..de3b314 100644 --- a/src/handlers/event/delete_on_reaction.rs +++ b/src/handlers/event/delete_on_reaction.rs @@ -1,4 +1,5 @@ use eyre::{Context as _, Result}; +use log::trace; use poise::serenity_prelude::{Context, InteractionType, Reaction}; pub async fn handle(ctx: &Context, reaction: &Reaction) -> Result<()> { @@ -17,6 +18,7 @@ pub async fn handle(ctx: &Context, reaction: &Reaction) -> Result<()> { && interaction.user == user && reaction.emoji.unicode_eq("❌") { + trace!("Deleting our own message at the request of {}", user.tag()); message.delete(ctx).await?; } } diff --git a/src/handlers/event/eta.rs b/src/handlers/event/eta.rs index db6cc41..b1c2d72 100644 --- a/src/handlers/event/eta.rs +++ b/src/handlers/event/eta.rs @@ -1,4 +1,5 @@ use eyre::Result; +use log::trace; use once_cell::sync::Lazy; use poise::serenity_prelude::{Context, Message}; use rand::seq::SliceRandom; @@ -6,7 +7,7 @@ use regex::Regex; static ETA_REGEX: Lazy = Lazy::new(|| Regex::new(r"\beta\b").unwrap()); -pub const ETA_MESSAGES: [&str; 16] = [ +const ETA_MESSAGES: [&str; 16] = [ "Sometime", "Some day", "Not far", @@ -27,6 +28,7 @@ pub const ETA_MESSAGES: [&str; 16] = [ pub async fn handle(ctx: &Context, message: &Message) -> Result<()> { if !ETA_REGEX.is_match(&message.content) { + trace!("The message '{}' (probably) doesn't say ETA", message.content); return Ok(()); } diff --git a/src/handlers/event/pluralkit.rs b/src/handlers/event/pluralkit.rs index 38a845f..e37b79a 100644 --- a/src/handlers/event/pluralkit.rs +++ b/src/handlers/event/pluralkit.rs @@ -2,14 +2,14 @@ use crate::{api, Data}; use std::time::Duration; use eyre::Result; -use log::{debug, trace}; +use log::trace; use poise::serenity_prelude::{Context, Message}; use tokio::time::sleep; const PK_DELAY_SEC: Duration = Duration::from_secs(1000); pub async fn is_message_proxied(message: &Message) -> Result { - debug!( + trace!( "Waiting on PluralKit API for {} seconds", PK_DELAY_SEC.as_secs() ); @@ -22,7 +22,7 @@ pub async fn is_message_proxied(message: &Message) -> Result { pub async fn handle(_: &Context, msg: &Message, data: &Data) -> Result<()> { if msg.webhook_id.is_some() { - debug!( + trace!( "Message {} has a webhook ID. Checking if it was sent through PluralKit", msg.id ); diff --git a/src/handlers/event/support_onboard.rs b/src/handlers/event/support_onboard.rs index 2d9fc52..ed170e1 100644 --- a/src/handlers/event/support_onboard.rs +++ b/src/handlers/event/support_onboard.rs @@ -1,12 +1,12 @@ use eyre::{eyre, OptionExt, Result}; -use log::debug; +use log::{debug, trace}; use poise::serenity_prelude::{ ChannelType, Context, CreateAllowedMentions, CreateMessage, GuildChannel, }; pub async fn handle(ctx: &Context, thread: &GuildChannel) -> Result<()> { if thread.kind != ChannelType::PublicThread { - debug!("Not doing support onboard in non-thread channel"); + trace!("Not doing support onboard in non-public thread channel"); return Ok(()); } @@ -15,7 +15,7 @@ pub async fn handle(ctx: &Context, thread: &GuildChannel) -> Result<()> { .ok_or_else(|| eyre!("Couldn't get parent ID from thread {}!", thread.name))? .name(ctx) .await - .unwrap_or(String::new()) + .unwrap_or_default() != "support" { debug!("Not posting onboarding message to threads outside of support"); diff --git a/src/main.rs b/src/main.rs index 3637034..94b35e6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,7 +6,7 @@ use std::sync::Arc; use std::time::Duration; use eyre::{eyre, Context as _, Report, Result}; -use log::{info, warn}; +use log::{info, trace, warn}; use octocrab::Octocrab; use poise::{ @@ -71,6 +71,7 @@ async fn setup( "Couldn't connect to storage! Is your daemon running?" )); } + trace!("Redis connection looks good!"); poise::builtins::register_globally(ctx, &framework.options().commands).await?; info!("Registered global commands!"); diff --git a/src/utils/resolve_message.rs b/src/utils/resolve_message.rs index a1554c5..4e4d9b5 100644 --- a/src/utils/resolve_message.rs +++ b/src/utils/resolve_message.rs @@ -1,7 +1,7 @@ use std::str::FromStr; use eyre::{eyre, Context as _, Result}; -use log::debug; +use log::{debug, trace}; use once_cell::sync::Lazy; use poise::serenity_prelude::{ ChannelId, ChannelType, Colour, Context, CreateEmbed, CreateEmbedAuthor, CreateEmbedFooter, @@ -32,7 +32,9 @@ pub async fn resolve(ctx: &Context, msg: &Message) -> Result> { let mut embeds: Vec = vec![]; - for (_, [_server_id, channel_id, message_id]) in matches { + for (url, [_server_id, channel_id, message_id]) in matches { + trace!("Attempting to resolve message {message_id} from URL {url}"); + let channel = ChannelId::from_str(channel_id) .wrap_err_with(|| format!("Couldn't parse channel ID {channel_id}!"))? .to_channel_cached(ctx.as_ref()) From cd1e3220c7631d717b04a92dd8af1fd55f99877c Mon Sep 17 00:00:00 2001 From: seth Date: Sun, 3 Mar 2024 18:46:34 -0500 Subject: [PATCH 55/90] support_onboard: check if bot has already joined thread --- src/handlers/event/eta.rs | 5 ++++- src/handlers/event/mod.rs | 4 ++-- src/handlers/event/support_onboard.rs | 28 +++++++++++++++++++++++++-- 3 files changed, 32 insertions(+), 5 deletions(-) diff --git a/src/handlers/event/eta.rs b/src/handlers/event/eta.rs index b1c2d72..f818c2e 100644 --- a/src/handlers/event/eta.rs +++ b/src/handlers/event/eta.rs @@ -28,7 +28,10 @@ const ETA_MESSAGES: [&str; 16] = [ pub async fn handle(ctx: &Context, message: &Message) -> Result<()> { if !ETA_REGEX.is_match(&message.content) { - trace!("The message '{}' (probably) doesn't say ETA", message.content); + trace!( + "The message '{}' (probably) doesn't say ETA", + message.content + ); return Ok(()); } diff --git a/src/handlers/event/mod.rs b/src/handlers/event/mod.rs index 2fd32b0..83cb7bf 100644 --- a/src/handlers/event/mod.rs +++ b/src/handlers/event/mod.rs @@ -15,7 +15,7 @@ mod support_onboard; pub async fn handle( ctx: &Context, event: &FullEvent, - _: FrameworkContext<'_, Data, Report>, + framework: FrameworkContext<'_, Data, Report>, data: &Data, ) -> Result<()> { match event { @@ -57,7 +57,7 @@ pub async fn handle( } FullEvent::ThreadCreate { thread } => { - support_onboard::handle(ctx, thread).await?; + support_onboard::handle(ctx, thread, framework).await?; } _ => {} diff --git a/src/handlers/event/support_onboard.rs b/src/handlers/event/support_onboard.rs index ed170e1..f3aa6b1 100644 --- a/src/handlers/event/support_onboard.rs +++ b/src/handlers/event/support_onboard.rs @@ -1,15 +1,39 @@ -use eyre::{eyre, OptionExt, Result}; +use crate::Data; + +use eyre::{eyre, Context as _, OptionExt, Report, Result}; use log::{debug, trace}; use poise::serenity_prelude::{ ChannelType, Context, CreateAllowedMentions, CreateMessage, GuildChannel, }; +use poise::FrameworkContext; -pub async fn handle(ctx: &Context, thread: &GuildChannel) -> Result<()> { +pub async fn handle( + ctx: &Context, + thread: &GuildChannel, + framework: FrameworkContext<'_, Data, Report>, +) -> Result<()> { if thread.kind != ChannelType::PublicThread { trace!("Not doing support onboard in non-public thread channel"); return Ok(()); } + // TODO @getchoo: it seems like we can get multiple ThreadCreate events + // should probably figure out a better way to not repeat ourselves here + if thread + .members(ctx) + .wrap_err_with(|| { + format!( + "Couldn't fetch members from thread {}! Not sending a support onboard message.", + thread.id + ) + })? + .iter() + .any(|member| member.user.id == framework.bot_id) + { + debug!("Not sending support onboard message...I think i've been here before :p"); + return Ok(()); + } + if thread .parent_id .ok_or_else(|| eyre!("Couldn't get parent ID from thread {}!", thread.name))? From 1ea08671fb245b226590d17c9758dc7d5006dd57 Mon Sep 17 00:00:00 2001 From: seth Date: Sun, 3 Mar 2024 19:03:25 -0500 Subject: [PATCH 56/90] ci: automerge dependabot PRs --- .github/workflows/autobot.yaml | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 .github/workflows/autobot.yaml diff --git a/.github/workflows/autobot.yaml b/.github/workflows/autobot.yaml new file mode 100644 index 0000000..3a5606e --- /dev/null +++ b/.github/workflows/autobot.yaml @@ -0,0 +1,28 @@ +name: Auto-merge Dependabot + +on: pull_request + +jobs: + automerge: + name: Check and merge PR + runs-on: ubuntu-latest + + permissions: + contents: write + pull-requests: write + + if: github.actor == 'dependabot[bot]' + + steps: + - uses: dependabot/fetch-metadata@v1 + id: metadata + with: + github-token: ${{ github.token }} + + # auto merge minor releases + - name: Enable auto-merge + if: steps.metadata.outputs.update-type == 'version-update:semver-patch' || steps.metadata.outputs.update-type == 'version-update:semver-minor' + run: gh pr merge --auto --rebase "$PR" + env: + GH_TOKEN: ${{ github.token }} + PR: ${{ github.event.pull_request.html_url }} From 9d0c022c6838ca30ac68491bc63cb800726c3a83 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Mon, 18 Mar 2024 01:01:46 +0000 Subject: [PATCH 57/90] Many fixes and tweaks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix tags (again?) * Make tag names more consistent * Remove prefix commands (not implemented well and not worth fixing) * Allow '-'s in tags * Fix /joke * Fix /members * Fix intel_hd issue match * Fix log analysis reply * Clearer log analysis messages It's weird to say the process failed when no issues were found. * Clippy * Final doc cleanup * Fix link expanding * Fix duplicate event filtering The other code simply does not work. ChannelId does have a method to grab members but I'm not sure whether it would work either. * Remove message resolution It's surprisingly hard to create an bug-free implementation. * Fix pluralkit detection * simplify tag codegen * commands: improve error handling in members unwrap() bad!!!11!! * events: use debug logs for pk checks * Revert "Remove message resolution" This reverts commit 0d9f224a81917212adafdeb2213f3cc11b44cf88. * Bring back prefix commands with "." (it's easier to type) * Add help * Fix messsage resolution * utils: factor out message resolution * Improve tag message * Disable VC support for message resolution for now * Improve prefix command usage Update on edit, display additional tip with wrong usage. * Check invoke_on_edit to display tip * Add defer in commands which make http requests * Apply tag sorting to slash commands too * handlers::error: `+=` -> `writeln!` * handlers::event: ignore own new messages * help: remove unneeded format! * optimize for size in release builds * nix: cleanup deployment expressions * nix: use treefmt * nix: update flake.lock Flake lock file updates: • Updated input 'fenix': 'github:nix-community/fenix/eb683549b7d76b12d1a009f888b91b70ed34485f' (2024-01-27) → 'github:nix-community/fenix/c53bb4a32f2fce7acf4e8e160a54779c4460ffdb' (2024-03-17) • Updated input 'fenix/rust-analyzer-src': 'github:rust-lang/rust-analyzer/596e5c77cf5b2b660b3ac2ce732fa0596c246d9b' (2024-01-26) → 'github:rust-lang/rust-analyzer/5ecace48f693afaa6adf8cb23086b651db3aec96' (2024-03-16) • Updated input 'nixpkgs': 'github:nixos/nixpkgs/4fddc9be4eaf195d631333908f2a454b03628ee5' (2024-01-25) → 'github:nixos/nixpkgs/34ad8c9f29a18b4dd97a9ad40ceb16954f24afe7' (2024-03-17) • Updated input 'pre-commit-hooks': 'github:cachix/pre-commit-hooks.nix/f56597d53fd174f796b5a7d3ee0b494f9e2285cc' (2024-01-20) → 'github:cachix/pre-commit-hooks.nix/5df5a70ad7575f6601d91f0efec95dd9bc619431' (2024-02-15) • Updated input 'procfile-nix': 'github:getchoo/procfile-nix/31a33e4264e5c6214844993c5b508fb3500ef5cd' (2024-01-27) → 'github:getchoo/procfile-nix/7a0ab379a4ab71c9deccaca9fb463e9aaea363d8' (2024-03-14) --------- Co-authored-by: seth --- Cargo.toml | 6 + build.rs | 63 +++--- flake.lock | 95 +++++---- flake.nix | 12 +- nix/deployment.nix | 41 ++-- nix/derivation.nix | 12 +- nix/dev.nix | 56 +++-- nix/module.nix | 16 +- nix/packages.nix | 8 - src/api/dadjoke.rs | 5 +- src/commands/general/help.rs | 23 +++ src/commands/general/joke.rs | 7 +- src/commands/general/members.rs | 30 ++- src/commands/general/mod.rs | 2 + src/commands/general/ping.rs | 4 +- src/commands/general/rory.rs | 5 +- src/commands/general/say.rs | 25 ++- src/commands/general/stars.rs | 4 +- src/commands/general/tag.rs | 30 ++- src/commands/mod.rs | 1 + src/handlers/error.rs | 46 +++++ src/handlers/event/analyze_logs/issues.rs | 4 +- src/handlers/event/analyze_logs/mod.rs | 11 +- src/handlers/event/expand_link.rs | 5 +- src/handlers/event/mod.rs | 10 +- src/handlers/event/pluralkit.rs | 36 ++-- src/handlers/event/support_onboard.rs | 27 +-- src/main.rs | 5 +- src/tags.rs | 2 +- src/utils/mod.rs | 4 +- src/utils/resolve_message.rs | 193 +++++++++++------- tags/{always-offline.md => always_offline.md} | 0 tags/{binary-search.md => binary_search.md} | 0 tags/{FTB.md => ftb.md} | 0 ...avaforgebugfix.md => java_forge_bugfix.md} | 0 ...egacyjavafixer.md => legacy_java_fixer.md} | 0 tags/{macosarmjava.md => macos_arm_java.md} | 0 tags/{whyjava8.md => why_java_8.md} | 0 38 files changed, 492 insertions(+), 296 deletions(-) create mode 100644 src/commands/general/help.rs rename tags/{always-offline.md => always_offline.md} (100%) rename tags/{binary-search.md => binary_search.md} (100%) rename tags/{FTB.md => ftb.md} (100%) rename tags/{javaforgebugfix.md => java_forge_bugfix.md} (100%) rename tags/{legacyjavafixer.md => legacy_java_fixer.md} (100%) rename tags/{macosarmjava.md => macos_arm_java.md} (100%) rename tags/{whyjava8.md => why_java_8.md} (100%) diff --git a/Cargo.toml b/Cargo.toml index 13040b8..ea5bc06 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,3 +41,9 @@ tokio = { version = "1.35.1", features = [ "signal", ] } url = { version = "2.5.0", features = ["serde"] } + +[profile.release] +codegen-units = 1 +opt-level = "s" +panic = "abort" +strip = "symbols" diff --git a/build.rs b/build.rs index 9331c6d..9925fb4 100644 --- a/build.rs +++ b/build.rs @@ -1,3 +1,4 @@ +use std::io::Write; use std::path::Path; use std::{env, fs}; @@ -9,14 +10,15 @@ include!("src/tags.rs"); #[allow(dead_code)] fn main() { let out_dir = env::var_os("OUT_DIR").unwrap(); - let generated = Path::new(&out_dir).join("generated.rs"); + let dest_file = Path::new(&out_dir).join("generated.rs"); + let mut file = fs::File::create(dest_file).unwrap(); let tag_files: Vec = fs::read_dir(TAG_DIR) .unwrap() .map(|f| f.unwrap().file_name().to_string_lossy().to_string()) .collect(); - let tags: Vec = tag_files + let mut tags: Vec = tag_files .clone() .into_iter() .map(|name| { @@ -39,50 +41,45 @@ fn main() { Tag { content, - file_name: name, + id: name.trim_end_matches(".md").to_string(), frontmatter: data, } }) .collect(); - let formatted_names: Vec = tags + tags.sort_by(|t1, t2| t1.id.cmp(&t2.id)); + + let tag_names: Vec = tags.iter().map(|t| format!("{},", t.id)).collect(); + + let tag_matches: Vec = tags .iter() - .map(|t| t.file_name.replace(".md", "").replace('-', "_")) + .map(|t| format!("Self::{} => \"{}\",", t.id, t.id)) .collect(); - let tag_choice = format!( - r#" - #[allow(non_camel_case_types, clippy::upper_case_acronyms)] - #[derive(Clone, Debug, poise::ChoiceParameter)] - pub enum Choice {{ + writeln!( + file, + r#"#[allow(non_camel_case_types, clippy::upper_case_acronyms)] +#[derive(Clone, Debug, poise::ChoiceParameter)] +pub enum Choice {{ {} - }}"#, - formatted_names.join(",\n") - ); +}}"#, + tag_names.join("\n") + ) + .unwrap(); - let to_str = format!( - r#" - impl Choice {{ - fn as_str(&self) -> &str {{ + writeln!( + file, + r#"impl Choice {{ + fn as_str(&self) -> &str {{ match &self {{ - {} + {} }} - }} - }} - "#, - formatted_names - .iter() - .map(|n| { - let file_name = n.replace('_', "-") + ".md"; - format!("Self::{n} => \"{file_name}\",") - }) - .collect::>() - .join("\n") - ); + }} +}}"#, + tag_matches.join("\n") + ) + .unwrap(); - let contents = Vec::from([tag_choice, to_str]).join("\n\n"); - - fs::write(generated, contents).unwrap(); println!( "cargo:rustc-env=TAGS={}", // make sure we can deserialize with env! at runtime diff --git a/flake.lock b/flake.lock index 6393c20..f88b7db 100644 --- a/flake.lock +++ b/flake.lock @@ -8,11 +8,11 @@ "rust-analyzer-src": "rust-analyzer-src" }, "locked": { - "lastModified": 1706336364, - "narHash": "sha256-mJ5i2YIVKv6jTN2+l3oOUUej2NUVjJX/H3bAq6019ks=", + "lastModified": 1710656467, + "narHash": "sha256-4Plj0vNP+ckWVNi6EtVojL9YV2dwSH7H4UMFCV40VE8=", "owner": "nix-community", "repo": "fenix", - "rev": "eb683549b7d76b12d1a009f888b91b70ed34485f", + "rev": "c53bb4a32f2fce7acf4e8e160a54779c4460ffdb", "type": "github" }, "original": { @@ -37,6 +37,26 @@ "type": "github" } }, + "flake-parts": { + "inputs": { + "nixpkgs-lib": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1709336216, + "narHash": "sha256-Dt/wOWeW6Sqm11Yh+2+t0dfEWxoMxGBvv3JpIocFl9E=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "f7b3c975cf067e56e7cda6cb098ebe3fb4d74ca2", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "flake-parts", + "type": "github" + } + }, "flake-utils": { "inputs": { "systems": "systems" @@ -98,11 +118,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1706173671, - "narHash": "sha256-lciR7kQUK2FCAYuszyd7zyRRmTaXVeoZsCyK6QFpGdk=", + "lastModified": 1710701596, + "narHash": "sha256-v4lsAi3vE/sEWg0G8AydMjs3NTHlsNw8K01xw06cKLg=", "owner": "nixos", "repo": "nixpkgs", - "rev": "4fddc9be4eaf195d631333908f2a454b03628ee5", + "rev": "34ad8c9f29a18b4dd97a9ad40ceb16954f24afe7", "type": "github" }, "original": { @@ -128,26 +148,6 @@ "type": "github" } }, - "parts": { - "inputs": { - "nixpkgs-lib": [ - "nixpkgs" - ] - }, - "locked": { - "lastModified": 1704982712, - "narHash": "sha256-2Ptt+9h8dczgle2Oo6z5ni5rt/uLMG47UFTR1ry/wgg=", - "owner": "hercules-ci", - "repo": "flake-parts", - "rev": "07f6395285469419cf9d078f59b5b49993198c00", - "type": "github" - }, - "original": { - "owner": "hercules-ci", - "repo": "flake-parts", - "type": "github" - } - }, "pre-commit-hooks": { "inputs": { "flake-compat": "flake-compat", @@ -159,11 +159,11 @@ "nixpkgs-stable": "nixpkgs-stable" }, "locked": { - "lastModified": 1705757126, - "narHash": "sha256-Eksr+n4Q8EYZKAN0Scef5JK4H6FcHc+TKNHb95CWm+c=", + "lastModified": 1708018599, + "narHash": "sha256-M+Ng6+SePmA8g06CmUZWi1AjG2tFBX9WCXElBHEKnyM=", "owner": "cachix", "repo": "pre-commit-hooks.nix", - "rev": "f56597d53fd174f796b5a7d3ee0b494f9e2285cc", + "rev": "5df5a70ad7575f6601d91f0efec95dd9bc619431", "type": "github" }, "original": { @@ -179,11 +179,11 @@ ] }, "locked": { - "lastModified": 1706387387, - "narHash": "sha256-7C3HncC25yK1kvLp+/9KoBa1Iz5Ly2JtICqmCz2nvio=", + "lastModified": 1710407041, + "narHash": "sha256-rCHklFHPzrq341KoTgXNdknNZbjOJ+VmalqX5s5YdGM=", "owner": "getchoo", "repo": "procfile-nix", - "rev": "31a33e4264e5c6214844993c5b508fb3500ef5cd", + "rev": "7a0ab379a4ab71c9deccaca9fb463e9aaea363d8", "type": "github" }, "original": { @@ -195,21 +195,22 @@ "root": { "inputs": { "fenix": "fenix", + "flake-parts": "flake-parts", "naersk": "naersk", "nixpkgs": "nixpkgs", - "parts": "parts", "pre-commit-hooks": "pre-commit-hooks", - "procfile-nix": "procfile-nix" + "procfile-nix": "procfile-nix", + "treefmt-nix": "treefmt-nix" } }, "rust-analyzer-src": { "flake": false, "locked": { - "lastModified": 1706295183, - "narHash": "sha256-VSyMaUsXfjb31B8/uT5cM5qXC1VOHLVsCi/bQuo3O/g=", + "lastModified": 1710610549, + "narHash": "sha256-xFIGLn5u+msUazlLbdjZ3gQgXrt7Lrlhq+XXUH0XU/0=", "owner": "rust-lang", "repo": "rust-analyzer", - "rev": "596e5c77cf5b2b660b3ac2ce732fa0596c246d9b", + "rev": "5ecace48f693afaa6adf8cb23086b651db3aec96", "type": "github" }, "original": { @@ -233,6 +234,26 @@ "repo": "default", "type": "github" } + }, + "treefmt-nix": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1710278050, + "narHash": "sha256-Oc6BP7soXqb8itlHI8UKkdf3V9GeJpa1S39SR5+HJys=", + "owner": "numtide", + "repo": "treefmt-nix", + "rev": "35791f76524086ab4b785a33e4abbedfda64bd22", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "treefmt-nix", + "type": "github" + } } }, "root": "root", diff --git a/flake.nix b/flake.nix index 31f6c29..47a0194 100644 --- a/flake.nix +++ b/flake.nix @@ -3,7 +3,7 @@ inputs = { nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable"; - parts = { + flake-parts = { url = "github:hercules-ci/flake-parts"; inputs.nixpkgs-lib.follows = "nixpkgs"; }; @@ -27,10 +27,15 @@ url = "github:getchoo/procfile-nix"; inputs.nixpkgs.follows = "nixpkgs"; }; + + treefmt-nix = { + url = "github:numtide/treefmt-nix"; + inputs.nixpkgs.follows = "nixpkgs"; + }; }; - outputs = {parts, ...} @ inputs: - parts.lib.mkFlake {inherit inputs;} { + outputs = {flake-parts, ...} @ inputs: + flake-parts.lib.mkFlake {inherit inputs;} { imports = [ ./nix/dev.nix ./nix/packages.nix @@ -38,6 +43,7 @@ inputs.pre-commit-hooks.flakeModule inputs.procfile-nix.flakeModule + inputs.treefmt-nix.flakeModule ]; systems = [ diff --git a/nix/deployment.nix b/nix/deployment.nix index f22a9fe..115318b 100644 --- a/nix/deployment.nix +++ b/nix/deployment.nix @@ -1,9 +1,12 @@ { inputs, - self, + flake-parts-lib, + withSystem, ... }: { - flake.nixosModules.default = import ./module.nix self; + flake.nixosModules.default = flake-parts-lib.importApply ./module.nix { + inherit withSystem; + }; perSystem = { lib, @@ -13,8 +16,8 @@ inputs', ... }: let - crossPkgsFor = - { + crossPkgs = + rec { x86_64-linux = { x86_64 = pkgs.pkgsStatic; aarch64 = pkgs.pkgsCross.aarch64-multiplatform.pkgsStatic; @@ -30,11 +33,13 @@ aarch64 = pkgs.pkgsCross.aarch64-multiplatform.pkgsStatic; }; - aarch64-darwin = crossPkgsFor.x86_64-darwin; + aarch64-darwin = x86_64-darwin; } .${system}; - exeFor = arch: let + refractionFor = arch: let + inherit (crossPkgs.${arch}.stdenv) cc; + target = "${arch}-unknown-linux-musl"; target' = builtins.replaceStrings ["-"] ["_"] target; targetUpper = lib.toUpper target'; @@ -52,8 +57,8 @@ }; refraction = self'.packages.refraction.override { + lto = true; naersk = naersk'; - optimizeSize = true; }; newAttrs = { @@ -62,26 +67,26 @@ "CARGO_TARGET_${targetUpper}_RUSTFLAGS" = "-C target-feature=+crt-static"; "CARGO_TARGET_${targetUpper}_LINKER" = newAttrs."CC_${target'}"; }; - - inherit (crossPkgsFor.${arch}.stdenv) cc; in - lib.getExe ( - refraction.overrideAttrs (lib.const newAttrs) - ); + refraction.overrideAttrs newAttrs; containerFor = arch: pkgs.dockerTools.buildLayeredImage { name = "refraction"; tag = "latest-${arch}"; contents = [pkgs.dockerTools.caCertificates]; - config.Cmd = [(exeFor arch)]; + config.Cmd = [ + (lib.getExe (refractionFor arch)) + ]; - architecture = crossPkgsFor.${arch}.go.GOARCH; + architecture = crossPkgs.${arch}.go.GOARCH; }; - in { - legacyPackages = { - container-x86_64 = containerFor "x86_64"; - container-aarch64 = containerFor "aarch64"; + + mkPackagesFor = arch: { + "refraction-static-${arch}" = refractionFor arch; + "container-${arch}" = containerFor arch; }; + in { + legacyPackages = lib.attrsets.mergeAttrsList (map mkPackagesFor ["x86_64" "aarch64"]); }; } diff --git a/nix/derivation.nix b/nix/derivation.nix index 6553dda..9e37e05 100644 --- a/nix/derivation.nix +++ b/nix/derivation.nix @@ -2,11 +2,9 @@ lib, stdenv, naersk, - CoreFoundation, - Security, - SystemConfiguration, + darwin, version, - optimizeSize ? false, + lto ? false, }: naersk.buildPackage { pname = "refraction"; @@ -23,13 +21,13 @@ naersk.buildPackage { ]; }; - buildInputs = lib.optionals stdenv.hostPlatform.isDarwin [ + buildInputs = lib.optionals stdenv.hostPlatform.isDarwin (with darwin.apple_sdk.frameworks; [ CoreFoundation Security SystemConfiguration - ]; + ]); - cargoBuildFlags = lib.optionals optimizeSize ["-C" "codegen-units=1" "-C" "strip=symbols" "-C" "opt-level=z"]; + cargoBuildFlags = lib.optionals lto ["-C" "lto=thin" "-C" "embed-bitcode=yes" "-Zdylib-lto"]; meta = with lib; { mainProgram = "refraction"; diff --git a/nix/dev.nix b/nix/dev.nix index 7d1a47e..b9bdf2f 100644 --- a/nix/dev.nix +++ b/nix/dev.nix @@ -5,24 +5,9 @@ config, self', ... - }: { - pre-commit.settings.hooks = { - actionlint.enable = true; - alejandra.enable = true; - deadnix.enable = true; - rustfmt.enable = true; - statix.enable = true; - nil.enable = true; - prettier = { - enable = true; - excludes = ["flake.lock"]; - }; - }; - - procfiles.daemons.processes = { - redis = lib.getExe' pkgs.redis "redis-server"; - }; - + }: let + enableAll = lib.flip lib.genAttrs (lib.const {enable = true;}); + in { devShells.default = pkgs.mkShell { shellHook = '' ${config.pre-commit.installationScript} @@ -31,6 +16,7 @@ packages = with pkgs; [ # general actionlint + nodePackages.prettier config.procfiles.daemons.package # rust @@ -50,6 +36,38 @@ RUST_SRC_PATH = "${pkgs.rust.packages.stable.rustPlatform.rustLibSrc}"; }; - formatter = pkgs.alejandra; + treefmt = { + projectRootFile = "flake.nix"; + + programs = enableAll [ + "alejandra" + "deadnix" + "prettier" + "rustfmt" + ]; + + settings.global = { + excludes = [ + "./target" + "./flake.lock" + "./Cargo.lock" + ]; + }; + }; + + pre-commit.settings = { + settings.treefmt.package = config.treefmt.build.wrapper; + + hooks = enableAll [ + "actionlint" + "nil" + "statix" + "treefmt" + ]; + }; + + procfiles.daemons.processes = { + redis = lib.getExe' pkgs.redis "redis-server"; + }; }; } diff --git a/nix/module.nix b/nix/module.nix index baee5fe..eccdbc2 100644 --- a/nix/module.nix +++ b/nix/module.nix @@ -1,4 +1,4 @@ -self: { +{withSystem, ...}: { config, lib, pkgs, @@ -22,7 +22,9 @@ self: { in { options.services.refraction = { enable = mkEnableOption "refraction"; - package = mkPackageOption self.packages.${pkgs.stdenv.hostPlatform.system} "refraction" {}; + package = mkPackageOption ( + withSystem pkgs.stdenv.hostPlatform.system ({pkgs, ...}: pkgs) + ) "refraction" {}; user = mkOption { description = mdDoc '' @@ -102,7 +104,7 @@ in { serviceConfig = { Type = "simple"; - Restart = "always"; + Restart = "on-failure"; EnvironmentFile = mkIf (cfg.environmentFile != null) cfg.environmentFile; @@ -122,8 +124,14 @@ in { ProtectKernelModules = true; ProtectKernelTunables = true; ProtectSystem = "strict"; - RestrictNamespaces = "uts ipc pid user cgroup"; + RestrictNamespaces = true; RestrictSUIDSGID = true; + SystemCallArchitectures = "native"; + SystemCallFilter = [ + "@system-service" + "~@resources" + "~@privileged" + ]; }; }; diff --git a/nix/packages.nix b/nix/packages.nix index a7d4333..19f5730 100644 --- a/nix/packages.nix +++ b/nix/packages.nix @@ -12,14 +12,6 @@ packages = { refraction = pkgs.callPackage ./derivation.nix { version = builtins.substring 0 7 self.rev or "dirty"; - - inherit - (pkgs.darwin.apple_sdk.frameworks) - CoreFoundation - Security - SystemConfiguration - ; - naersk = inputs.naersk.lib.${system}; }; diff --git a/src/api/dadjoke.rs b/src/api/dadjoke.rs index b80d5ec..e1e7962 100644 --- a/src/api/dadjoke.rs +++ b/src/api/dadjoke.rs @@ -7,7 +7,10 @@ use reqwest::StatusCode; const DADJOKE: &str = "https://icanhazdadjoke.com"; pub async fn get_joke() -> Result { - let req = REQWEST_CLIENT.get(DADJOKE).build()?; + let req = REQWEST_CLIENT + .get(DADJOKE) + .header("Accept", "text/plain") + .build()?; debug!("Making request to {}", req.url()); let resp = REQWEST_CLIENT.execute(req).await?; diff --git a/src/commands/general/help.rs b/src/commands/general/help.rs new file mode 100644 index 0000000..bdf3047 --- /dev/null +++ b/src/commands/general/help.rs @@ -0,0 +1,23 @@ +use eyre::Result; +use poise::{builtins, samples::HelpConfiguration}; + +use crate::Context; + +/// View the help menu +#[poise::command(slash_command, prefix_command, track_edits = true)] +pub async fn help( + ctx: Context<'_>, + #[description = "provide information about a specific command"] command: Option, +) -> Result<()> { + builtins::help( + ctx, + command.as_deref(), + HelpConfiguration { + extra_text_at_bottom: "Use /help for more information about a specific command!", + ..HelpConfiguration::default() + }, + ) + .await?; + + Ok(()) +} diff --git a/src/commands/general/joke.rs b/src/commands/general/joke.rs index b6b2c60..fb39d20 100644 --- a/src/commands/general/joke.rs +++ b/src/commands/general/joke.rs @@ -5,11 +5,14 @@ use eyre::Result; use log::trace; /// It's a joke -#[poise::command(slash_command, prefix_command)] +#[poise::command(slash_command, prefix_command, track_edits = true)] pub async fn joke(ctx: Context<'_>) -> Result<()> { trace!("Running joke command"); + + ctx.defer().await?; + let joke = dadjoke::get_joke().await?; - ctx.reply(joke).await?; + ctx.say(joke).await?; Ok(()) } diff --git a/src/commands/general/members.rs b/src/commands/general/members.rs index 39da469..94204cc 100644 --- a/src/commands/general/members.rs +++ b/src/commands/general/members.rs @@ -1,26 +1,34 @@ use crate::{consts, Context}; -use eyre::{OptionExt, Result}; +use eyre::{eyre, Context as _, OptionExt, Result}; use log::trace; use poise::serenity_prelude::CreateEmbed; use poise::CreateReply; /// Returns the number of members in the server -#[poise::command(slash_command, prefix_command)] +#[poise::command(slash_command, prefix_command, guild_only = true, track_edits = true)] pub async fn members(ctx: Context<'_>) -> Result<()> { trace!("Running members command"); - let guild = ctx.guild().ok_or_eyre("Couldn't fetch guild!")?.to_owned(); - let count = guild.member_count; - let online = if let Some(count) = guild.approximate_presence_count { - count.to_string() - } else { - "Undefined".to_string() - }; + ctx.defer().await?; + + let guild_id = ctx.guild_id().ok_or_eyre("Couldn't get guild ID!")?; + let guild = ctx + .http() + .get_guild_with_counts(guild_id) + .await + .wrap_err_with(|| format!("Couldn't fetch guild {guild_id} with counts!"))?; + + let member_count = guild + .approximate_member_count + .ok_or_else(|| eyre!("Couldn't get member count for guild {guild_id}!"))?; + let online_count = guild + .approximate_presence_count + .ok_or_else(|| eyre!("Couldn't get online count for guild {guild_id}!"))?; let embed = CreateEmbed::new() - .title(format!("{count} total members!")) - .description(format!("{online} online members")) + .title(format!("{member_count} total members!",)) + .description(format!("{online_count} online members",)) .color(consts::COLORS["blue"]); let reply = CreateReply::default().embed(embed); diff --git a/src/commands/general/mod.rs b/src/commands/general/mod.rs index e514e39..050caff 100644 --- a/src/commands/general/mod.rs +++ b/src/commands/general/mod.rs @@ -1,3 +1,4 @@ +mod help; mod joke; mod members; mod ping; @@ -6,6 +7,7 @@ mod say; mod stars; mod tag; +pub use help::help; pub use joke::joke; pub use members::members; pub use ping::ping; diff --git a/src/commands/general/ping.rs b/src/commands/general/ping.rs index f21a7c0..90fc53f 100644 --- a/src/commands/general/ping.rs +++ b/src/commands/general/ping.rs @@ -4,9 +4,9 @@ use eyre::Result; use log::trace; /// Replies with pong! -#[poise::command(slash_command, prefix_command, ephemeral)] +#[poise::command(slash_command, prefix_command, track_edits = true, ephemeral)] pub async fn ping(ctx: Context<'_>) -> Result<()> { trace!("Running ping command!"); - ctx.reply("Pong!").await?; + ctx.say("Pong!").await?; Ok(()) } diff --git a/src/commands/general/rory.rs b/src/commands/general/rory.rs index ea2b0a1..6bdc6f5 100644 --- a/src/commands/general/rory.rs +++ b/src/commands/general/rory.rs @@ -7,12 +7,15 @@ use poise::serenity_prelude::{CreateEmbed, CreateEmbedFooter}; use poise::CreateReply; /// Gets a Rory photo! -#[poise::command(slash_command, prefix_command)] +#[poise::command(slash_command, prefix_command, track_edits = true)] pub async fn rory( ctx: Context<'_>, #[description = "specify a Rory ID"] id: Option, ) -> Result<()> { trace!("Running rory command"); + + ctx.defer().await?; + let rory = rory::get(id).await?; let embed = { diff --git a/src/commands/general/say.rs b/src/commands/general/say.rs index eeb2e9b..afeee0d 100644 --- a/src/commands/general/say.rs +++ b/src/commands/general/say.rs @@ -9,18 +9,30 @@ use poise::serenity_prelude::{CreateEmbed, CreateEmbedAuthor, CreateMessage}; prefix_command, ephemeral, default_member_permissions = "MODERATE_MEMBERS", - required_permissions = "MODERATE_MEMBERS" + required_permissions = "MODERATE_MEMBERS", + guild_only = true )] -pub async fn say(ctx: Context<'_>, #[description = "Just content?"] content: String) -> Result<()> { +pub async fn say( + ctx: Context<'_>, + #[description = "the message content"] content: String, +) -> Result<()> { let guild = ctx.guild().ok_or_eyre("Couldn't get guild!")?.to_owned(); let channel = ctx .guild_channel() .await .ok_or_eyre("Couldn't get channel!")?; + if let Context::Prefix(prefix) = ctx { + // ignore error, we might not have perm + let _ = prefix.msg.delete(ctx).await; + } + ctx.defer_ephemeral().await?; channel.say(ctx, &content).await?; - ctx.say("I said what you said!").await?; + + if let Context::Application(_) = ctx { + ctx.say("I said what you said!").await?; + } if let Some(channel_id) = ctx.data().config.discord.channels().say_log_channel_id() { let log_channel = guild @@ -29,8 +41,11 @@ pub async fn say(ctx: Context<'_>, #[description = "Just content?"] content: Str .find(|c| c.0 == &channel_id) .ok_or_eyre("Couldn't get log channel from guild!")?; - let author = CreateEmbedAuthor::new(ctx.author().tag()) - .icon_url(ctx.author().avatar_url().unwrap_or("Undefined".to_string())); + let author = CreateEmbedAuthor::new(ctx.author().tag()).icon_url( + ctx.author() + .avatar_url() + .unwrap_or_else(|| ctx.author().default_avatar_url()), + ); let embed = CreateEmbed::default() .title("Say command used!") diff --git a/src/commands/general/stars.rs b/src/commands/general/stars.rs index 200a562..7cbbd9b 100644 --- a/src/commands/general/stars.rs +++ b/src/commands/general/stars.rs @@ -6,10 +6,12 @@ use poise::serenity_prelude::CreateEmbed; use poise::CreateReply; /// Returns GitHub stargazer count -#[poise::command(slash_command, prefix_command)] +#[poise::command(slash_command, prefix_command, track_edits = true)] pub async fn stars(ctx: Context<'_>) -> Result<()> { trace!("Running stars command"); + ctx.defer().await?; + let prismlauncher = ctx .data() .octocrab diff --git a/src/commands/general/tag.rs b/src/commands/general/tag.rs index 5d95e28..43fa7a8 100644 --- a/src/commands/general/tag.rs +++ b/src/commands/general/tag.rs @@ -13,19 +13,24 @@ include!(concat!(env!("OUT_DIR"), "/generated.rs")); static TAGS: Lazy> = Lazy::new(|| serde_json::from_str(env!("TAGS")).unwrap()); /// Send a tag -#[poise::command(slash_command, prefix_command)] +#[poise::command( + slash_command, + prefix_command, + track_edits = true, + help_text_fn = help +)] pub async fn tag( ctx: Context<'_>, - #[description = "the copypasta you want to send"] name: Choice, - user: Option, + #[description = "the tag to send"] name: Choice, + #[description = "a user to mention"] user: Option, ) -> Result<()> { trace!("Running tag command"); - let tag_file = name.as_str(); + let tag_id = name.as_str(); let tag = TAGS .iter() - .find(|t| t.file_name == tag_file) - .ok_or_else(|| eyre!("Tried to get non-existent tag: {tag_file}"))?; + .find(|t| t.id == tag_id) + .ok_or_else(|| eyre!("Tried to get non-existent tag: {tag_id}"))?; let frontmatter = &tag.frontmatter; @@ -49,6 +54,9 @@ pub async fn tag( } } + e = e.title(&frontmatter.title); + e = e.description(&tag.content); + e }; @@ -66,3 +74,13 @@ pub async fn tag( Ok(()) } + +fn help() -> String { + let tag_list = TAGS + .iter() + .map(|tag| format!("`{}`", tag.id)) + .collect::>() + .join(", "); + + format!("Available tags: {tag_list}") +} diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 00130a7..0d44cbe 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -14,5 +14,6 @@ pub fn get() -> Vec> { general::say(), general::stars(), general::tag(), + general::help(), ] } diff --git a/src/handlers/error.rs b/src/handlers/error.rs index 4dce9b5..b0e8462 100644 --- a/src/handlers/error.rs +++ b/src/handlers/error.rs @@ -1,11 +1,21 @@ use crate::consts; use crate::Data; +use std::fmt::Write; use eyre::Report; use log::error; use poise::serenity_prelude::{CreateEmbed, Timestamp}; use poise::{CreateReply, FrameworkError}; +// getchoo: i like writeln! and don't like +macro_rules! writelne { + ($dst:expr, $($arg:tt)*) => { + if let Err(why) = writeln!($dst, $($arg)*) { + error!("We somehow cannot write to what should be on the heap. What are you using this macro with? Anyways, here's the error:\n{why:#?}"); + } + } +} + pub async fn handle(error: FrameworkError<'_, Data, Report>) { match error { FrameworkError::Setup { @@ -44,6 +54,42 @@ pub async fn handle(error: FrameworkError<'_, Data, Report>) { ); } + FrameworkError::ArgumentParse { + error, input, ctx, .. + } => { + let mut response = String::new(); + + if let Some(input) = input { + writelne!( + &mut response, + "**Cannot parse `{input}` as argument: {error}**\n" + ); + } else { + writelne!(&mut response, "**{error}**\n"); + } + + if let Some(help_text) = ctx.command().help_text.as_ref() { + writelne!(&mut response, "{help_text}\n"); + } + + if ctx.command().invoke_on_edit { + writelne!( + &mut response, + "**Tip:** Edit your message to update the response." + ); + } + + writelne!( + &mut response, + "For more information, refer to /help {}.", + ctx.command().name + ); + + if let Err(why) = ctx.say(response).await { + error!("Unhandled error displaying ArgumentParse error\n{why:#?}"); + } + } + error => { if let Err(e) = poise::builtins::on_error(error).await { error!("Unhandled error occurred:\n{e:#?}"); diff --git a/src/handlers/event/analyze_logs/issues.rs b/src/handlers/event/analyze_logs/issues.rs index 11208c7..575be42 100644 --- a/src/handlers/event/analyze_logs/issues.rs +++ b/src/handlers/event/analyze_logs/issues.rs @@ -95,7 +95,7 @@ fn intel_hd(log: &str) -> Issue { See https://prismlauncher.org/wiki/getting-started/installing-java/#a-note-about-intel-hd-20003000-on-windows-10 for more info".to_string() ); - let found = log.contains("java.lang.NoSuchMethodError: sun.security.util.ManifestEntryVerifier.(Ljava/util/jar/Manifest;)V"); + let found = log.contains("org.lwjgl.LWJGLException: Pixel format not accelerated"); found.then_some(issue) } @@ -204,7 +204,7 @@ async fn outdated_launcher(log: &str, data: &Data) -> Result { if version_from_log < latest_version { let issue = ( - "Outdated Prism Launcher".to_string(), + "Outdated Prism Launcher".to_string(), format!("Your installed version is {version_from_log}, while the newest version is {latest_version}.\nPlease update, for more info see https://prismlauncher.org/download/") ); diff --git a/src/handlers/event/analyze_logs/mod.rs b/src/handlers/event/analyze_logs/mod.rs index 15df9eb..1d5481d 100644 --- a/src/handlers/event/analyze_logs/mod.rs +++ b/src/handlers/event/analyze_logs/mod.rs @@ -19,7 +19,7 @@ pub async fn handle(ctx: &Context, message: &Message, data: &Data) -> Result<()> if log.is_err() { let embed = CreateEmbed::new() - .title("Analyze failed!") + .title("Analysis failed!") .description("Couldn't download log"); let allowed_mentions = CreateAllowedMentions::new().replied_user(true); let our_message = CreateMessage::new() @@ -43,11 +43,9 @@ pub async fn handle(ctx: &Context, message: &Message, data: &Data) -> Result<()> let mut e = CreateEmbed::new().title("Log analysis"); if issues.is_empty() { - e = e.color(COLORS["green"]).field( - "Analyze failed!", - "No issues found automatically", - false, - ); + e = e + .color(COLORS["green"]) + .description("No issues found automatically"); } else { e = e.color(COLORS["red"]); @@ -61,6 +59,7 @@ pub async fn handle(ctx: &Context, message: &Message, data: &Data) -> Result<()> let allowed_mentions = CreateAllowedMentions::new().replied_user(true); let message = CreateMessage::new() + .reference_message(message) .allowed_mentions(allowed_mentions) .embed(embed); diff --git a/src/handlers/event/expand_link.rs b/src/handlers/event/expand_link.rs index 05f6be0..ae73b56 100644 --- a/src/handlers/event/expand_link.rs +++ b/src/handlers/event/expand_link.rs @@ -4,13 +4,12 @@ use poise::serenity_prelude::{Context, CreateAllowedMentions, CreateMessage, Mes use crate::utils; pub async fn handle(ctx: &Context, message: &Message) -> Result<()> { - let embeds = utils::resolve_message(ctx, message).await?; + let embeds = utils::resolve_message::from_message(ctx, message).await?; - // TODO getchoo: actually reply to user - // ...not sure why Message doesn't give me a builder in reply() or equivalents if !embeds.is_empty() { let allowed_mentions = CreateAllowedMentions::new().replied_user(false); let reply = CreateMessage::new() + .reference_message(message) .embeds(embeds) .allowed_mentions(allowed_mentions); diff --git a/src/handlers/event/mod.rs b/src/handlers/event/mod.rs index 83cb7bf..53b2952 100644 --- a/src/handlers/event/mod.rs +++ b/src/handlers/event/mod.rs @@ -15,7 +15,7 @@ mod support_onboard; pub async fn handle( ctx: &Context, event: &FullEvent, - framework: FrameworkContext<'_, Data, Report>, + _framework: FrameworkContext<'_, Data, Report>, data: &Data, ) -> Result<()> { match event { @@ -31,8 +31,10 @@ pub async fn handle( FullEvent::Message { new_message } => { // ignore new messages from bots - // NOTE: the webhook_id check allows us to still respond to PK users - if new_message.author.bot && new_message.webhook_id.is_none() { + // note: the webhook_id check allows us to still respond to PK users + if (new_message.author.bot && new_message.webhook_id.is_none()) + || new_message.is_own(ctx) + { trace!("Ignoring message {} from bot", new_message.id); return Ok(()); } @@ -57,7 +59,7 @@ pub async fn handle( } FullEvent::ThreadCreate { thread } => { - support_onboard::handle(ctx, thread, framework).await?; + support_onboard::handle(ctx, thread).await?; } _ => {} diff --git a/src/handlers/event/pluralkit.rs b/src/handlers/event/pluralkit.rs index e37b79a..ee8977a 100644 --- a/src/handlers/event/pluralkit.rs +++ b/src/handlers/event/pluralkit.rs @@ -2,18 +2,18 @@ use crate::{api, Data}; use std::time::Duration; use eyre::Result; -use log::trace; +use log::{debug, trace}; use poise::serenity_prelude::{Context, Message}; use tokio::time::sleep; -const PK_DELAY_SEC: Duration = Duration::from_secs(1000); +const PK_DELAY: Duration = Duration::from_secs(1); pub async fn is_message_proxied(message: &Message) -> Result { trace!( "Waiting on PluralKit API for {} seconds", - PK_DELAY_SEC.as_secs() + PK_DELAY.as_secs() ); - sleep(PK_DELAY_SEC).await; + sleep(PK_DELAY).await; let proxied = api::pluralkit::get_sender(message.id).await.is_ok(); @@ -21,21 +21,23 @@ pub async fn is_message_proxied(message: &Message) -> Result { } pub async fn handle(_: &Context, msg: &Message, data: &Data) -> Result<()> { - if msg.webhook_id.is_some() { - trace!( - "Message {} has a webhook ID. Checking if it was sent through PluralKit", - msg.id - ); + if msg.webhook_id.is_none() { + return Ok(()); + } - trace!( - "Waiting on PluralKit API for {} seconds", - PK_DELAY_SEC.as_secs() - ); - sleep(PK_DELAY_SEC).await; + debug!( + "Message {} has a webhook ID. Checking if it was sent through PluralKit", + msg.id + ); - if let Ok(sender) = api::pluralkit::get_sender(msg.id).await { - data.storage.store_user_plurality(sender).await?; - } + trace!( + "Waiting on PluralKit API for {} seconds", + PK_DELAY.as_secs() + ); + sleep(PK_DELAY).await; + + if let Ok(sender) = api::pluralkit::get_sender(msg.id).await { + data.storage.store_user_plurality(sender).await?; } Ok(()) diff --git a/src/handlers/event/support_onboard.rs b/src/handlers/event/support_onboard.rs index f3aa6b1..f2d2d5f 100644 --- a/src/handlers/event/support_onboard.rs +++ b/src/handlers/event/support_onboard.rs @@ -1,36 +1,17 @@ -use crate::Data; - -use eyre::{eyre, Context as _, OptionExt, Report, Result}; +use eyre::{eyre, OptionExt, Result}; use log::{debug, trace}; use poise::serenity_prelude::{ ChannelType, Context, CreateAllowedMentions, CreateMessage, GuildChannel, }; -use poise::FrameworkContext; -pub async fn handle( - ctx: &Context, - thread: &GuildChannel, - framework: FrameworkContext<'_, Data, Report>, -) -> Result<()> { +pub async fn handle(ctx: &Context, thread: &GuildChannel) -> Result<()> { if thread.kind != ChannelType::PublicThread { trace!("Not doing support onboard in non-public thread channel"); return Ok(()); } - // TODO @getchoo: it seems like we can get multiple ThreadCreate events - // should probably figure out a better way to not repeat ourselves here - if thread - .members(ctx) - .wrap_err_with(|| { - format!( - "Couldn't fetch members from thread {}! Not sending a support onboard message.", - thread.id - ) - })? - .iter() - .any(|member| member.user.id == framework.bot_id) - { - debug!("Not sending support onboard message...I think i've been here before :p"); + if thread.last_message_id.is_some() { + debug!("Ignoring duplicate thread creation event"); return Ok(()); } diff --git a/src/main.rs b/src/main.rs index 94b35e6..93d7e5b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,8 +2,7 @@ #![allow(clippy::missing_errors_doc)] #![forbid(unsafe_code)] -use std::sync::Arc; -use std::time::Duration; +use std::{sync::Arc, time::Duration}; use eyre::{eyre, Context as _, Report, Result}; use log::{info, trace, warn}; @@ -111,7 +110,7 @@ async fn main() -> Result<()> { }, prefix_options: PrefixFrameworkOptions { - prefix: Some("r".into()), + prefix: Some(".".into()), edit_tracker: Some(Arc::from(EditTracker::for_timespan(Duration::from_secs( 3600, )))), diff --git a/src/tags.rs b/src/tags.rs index ab77b1e..2d6336d 100644 --- a/src/tags.rs +++ b/src/tags.rs @@ -15,6 +15,6 @@ pub struct TagFrontmatter { #[derive(Clone, Debug, Deserialize, Serialize)] pub struct Tag { pub content: String, - pub file_name: String, + pub id: String, pub frontmatter: TagFrontmatter, } diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 8cfb89f..3df2c9a 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -1,3 +1 @@ -mod resolve_message; - -pub use resolve_message::resolve as resolve_message; +pub mod resolve_message; diff --git a/src/utils/resolve_message.rs b/src/utils/resolve_message.rs index 4e4d9b5..c237eda 100644 --- a/src/utils/resolve_message.rs +++ b/src/utils/resolve_message.rs @@ -4,17 +4,14 @@ use eyre::{eyre, Context as _, Result}; use log::{debug, trace}; use once_cell::sync::Lazy; use poise::serenity_prelude::{ - ChannelId, ChannelType, Colour, Context, CreateEmbed, CreateEmbedAuthor, CreateEmbedFooter, - Message, MessageId, + Cache, CacheHttp, ChannelId, ChannelType, Colour, Context, CreateEmbed, CreateEmbedAuthor, + CreateEmbedFooter, GuildChannel, Member, Message, MessageId, Permissions, }; use regex::Regex; -static MESSAGE_PATTERN: Lazy = Lazy::new(|| { - Regex::new(r"/(https?:\/\/)?(?:canary\.|ptb\.)?discord(?:app)?\.com\/channels\/(?\d+)\/(?\d+)\/(?\d+)/g;").unwrap() -}); - -fn find_first_image(msg: &Message) -> Option { - msg.attachments +fn find_first_image(message: &Message) -> Option { + message + .attachments .iter() .find(|a| { a.content_type @@ -25,86 +22,130 @@ fn find_first_image(msg: &Message) -> Option { .map(|res| res.url.clone()) } -pub async fn resolve(ctx: &Context, msg: &Message) -> Result> { +async fn member_can_view_channel( + ctx: impl CacheHttp + AsRef, + member: &Member, + channel: &GuildChannel, +) -> Result { + static REQUIRED_PERMISSIONS: Lazy = + Lazy::new(|| Permissions::VIEW_CHANNEL | Permissions::READ_MESSAGE_HISTORY); + + let guild = ctx.http().get_guild(channel.guild_id).await?; + + let channel_to_check = match &channel.kind { + ChannelType::PublicThread => { + let parent_id = channel + .parent_id + .ok_or_else(|| eyre!("Couldn't get parent of thread {}", channel.id))?; + parent_id + .to_channel(ctx) + .await? + .guild() + .ok_or_else(|| eyre!("Couldn't get GuildChannel from ChannelID {parent_id}!"))? + } + + ChannelType::Text | ChannelType::News => channel.to_owned(), + + _ => return Ok(false), + }; + + let can_view = guild + .user_permissions_in(&channel_to_check, member) + .contains(*REQUIRED_PERMISSIONS); + Ok(can_view) +} + +pub async fn to_embed( + ctx: impl CacheHttp + AsRef, + message: &Message, +) -> Result { + let author = CreateEmbedAuthor::new(message.author.tag()).icon_url( + message + .author + .avatar_url() + .unwrap_or_else(|| message.author.default_avatar_url()), + ); + + let footer = CreateEmbedFooter::new(format!( + "#{}", + message.channel(ctx).await?.guild().unwrap_or_default().name + )); + + let mut embed = CreateEmbed::new() + .author(author) + .color(Colour::BLITZ_BLUE) + .timestamp(message.timestamp) + .footer(footer) + .description(format!( + "{}\n\n[Jump to original message]({})", + message.content, + message.link() + )); + + if !message.attachments.is_empty() { + embed = embed.fields(message.attachments.iter().map(|a| { + ( + "Attachments".to_string(), + format!("[{}]({})", a.filename, a.url), + false, + ) + })); + + if let Some(image) = find_first_image(message) { + embed = embed.image(image); + } + } + + Ok(embed) +} + +pub async fn from_message(ctx: &Context, msg: &Message) -> Result> { + static MESSAGE_PATTERN: Lazy = Lazy::new(|| { + Regex::new(r"(?:https?:\/\/)?(?:canary\.|ptb\.)?discord(?:app)?\.com\/channels\/(?\d+)\/(?\d+)\/(?\d+)").unwrap() + }); + + let Some(guild_id) = msg.guild_id else { + debug!("Not resolving message in DM"); + return Ok(Vec::new()); + }; + let author = guild_id.member(ctx, msg.author.id).await?; + let matches = MESSAGE_PATTERN .captures_iter(&msg.content) .map(|capture| capture.extract()); let mut embeds: Vec = vec![]; - for (url, [_server_id, channel_id, message_id]) in matches { - trace!("Attempting to resolve message {message_id} from URL {url}"); - - let channel = ChannelId::from_str(channel_id) - .wrap_err_with(|| format!("Couldn't parse channel ID {channel_id}!"))? - .to_channel_cached(ctx.as_ref()) - .ok_or_else(|| eyre!("Couldn't find Guild Channel from {channel_id}!"))? - .to_owned(); - - let author_can_view = if channel.kind == ChannelType::PublicThread - || channel.kind == ChannelType::PrivateThread - { - let thread_members = channel - .id - .get_thread_members(ctx) - .await - .wrap_err("Couldn't get members from thread!")?; - - thread_members - .iter() - .any(|member| member.user_id == msg.author.id) - } else { - channel - .members(ctx) - .wrap_err_with(|| format!("Couldn't get members for channel {channel_id}!"))? - .iter() - .any(|member| member.user.id == msg.author.id) - }; - - if !author_can_view { - debug!("Not resolving message for author who can't see it"); + for (url, [target_guild_id, target_channel_id, target_message_id]) in matches { + if target_guild_id != guild_id.to_string() { + debug!("Not resolving message from other server"); + continue; } + trace!("Attempting to resolve message {target_message_id} from URL {url}"); - let original_message = channel - .message( - ctx, - MessageId::from_str(message_id) - .wrap_err_with(|| format!("Couldn't parse message ID {message_id}!"))?, - ) - .await - .wrap_err_with(|| { - format!("Couldn't get message from ID {message_id} in channel {channel_id}!") + let target_channel = ChannelId::from_str(target_channel_id)? + .to_channel(ctx) + .await? + .guild() + .ok_or_else(|| { + eyre!("Couldn't find GuildChannel from ChannelId {target_channel_id}!") })?; - let author = CreateEmbedAuthor::new(original_message.author.tag()) - .icon_url(original_message.author.default_avatar_url()); - let footer = CreateEmbedFooter::new(format!("#{}", channel.name)); - - let mut embed = CreateEmbed::new() - .author(author) - .color(Colour::BLITZ_BLUE) - .timestamp(original_message.timestamp) - .footer(footer) - .description(format!( - "{}\n\n[Jump to original message]({})", - original_message.content, - original_message.link() - )); - - if !original_message.attachments.is_empty() { - embed = embed.fields(original_message.attachments.iter().map(|a| { - ( - "Attachments".to_string(), - format!("[{}]({})", a.filename, a.url), - false, - ) - })); - - if let Some(image) = find_first_image(msg) { - embed = embed.image(image); - } + if !member_can_view_channel(ctx, &author, &target_channel).await? { + debug!("Not resolving message for author who can't see it"); + continue; } + let target_message_id = MessageId::from_str(target_message_id)?; + let target_message = target_channel + .message(ctx, target_message_id) + .await + .wrap_err_with(|| { + eyre!("Couldn't find channel message from ID {target_message_id}!") + })?; + + let embed = to_embed(ctx, &target_message).await?; + embeds.push(embed); } diff --git a/tags/always-offline.md b/tags/always_offline.md similarity index 100% rename from tags/always-offline.md rename to tags/always_offline.md diff --git a/tags/binary-search.md b/tags/binary_search.md similarity index 100% rename from tags/binary-search.md rename to tags/binary_search.md diff --git a/tags/FTB.md b/tags/ftb.md similarity index 100% rename from tags/FTB.md rename to tags/ftb.md diff --git a/tags/javaforgebugfix.md b/tags/java_forge_bugfix.md similarity index 100% rename from tags/javaforgebugfix.md rename to tags/java_forge_bugfix.md diff --git a/tags/legacyjavafixer.md b/tags/legacy_java_fixer.md similarity index 100% rename from tags/legacyjavafixer.md rename to tags/legacy_java_fixer.md diff --git a/tags/macosarmjava.md b/tags/macos_arm_java.md similarity index 100% rename from tags/macosarmjava.md rename to tags/macos_arm_java.md diff --git a/tags/whyjava8.md b/tags/why_java_8.md similarity index 100% rename from tags/whyjava8.md rename to tags/why_java_8.md From af9938a3c6c34ff9655d345f77fef2cb957ae6c4 Mon Sep 17 00:00:00 2001 From: seth Date: Mon, 18 Mar 2024 08:45:25 -0400 Subject: [PATCH 58/90] handlers::event: improve tracing --- src/handlers/event/analyze_logs/issues.rs | 3 +++ src/handlers/event/analyze_logs/mod.rs | 7 ++++++- src/handlers/event/analyze_logs/providers/0x0.rs | 3 +++ src/handlers/event/analyze_logs/providers/attachment.rs | 3 +++ src/handlers/event/analyze_logs/providers/haste.rs | 3 +++ src/handlers/event/analyze_logs/providers/mclogs.rs | 3 +++ src/handlers/event/analyze_logs/providers/paste_gg.rs | 2 ++ src/handlers/event/analyze_logs/providers/pastebin.rs | 2 ++ src/handlers/event/mod.rs | 9 +++++++++ 9 files changed, 34 insertions(+), 1 deletion(-) diff --git a/src/handlers/event/analyze_logs/issues.rs b/src/handlers/event/analyze_logs/issues.rs index 575be42..d838e88 100644 --- a/src/handlers/event/analyze_logs/issues.rs +++ b/src/handlers/event/analyze_logs/issues.rs @@ -1,12 +1,15 @@ use crate::Data; use eyre::Result; +use log::trace; use once_cell::sync::Lazy; use regex::Regex; pub type Issue = Option<(String, String)>; pub async fn find(log: &str, data: &Data) -> Result> { + trace!("Checking log for issues"); + let issues = [ fabric_internal, flatpak_nvidia, diff --git a/src/handlers/event/analyze_logs/mod.rs b/src/handlers/event/analyze_logs/mod.rs index 1d5481d..5d55694 100644 --- a/src/handlers/event/analyze_logs/mod.rs +++ b/src/handlers/event/analyze_logs/mod.rs @@ -2,7 +2,7 @@ use crate::consts::COLORS; use crate::Data; use eyre::Result; -use log::debug; +use log::{debug, trace}; use poise::serenity_prelude::{ Context, CreateAllowedMentions, CreateEmbed, CreateMessage, Message, }; @@ -13,6 +13,11 @@ mod providers; use providers::find_log; pub async fn handle(ctx: &Context, message: &Message, data: &Data) -> Result<()> { + trace!( + "Checking message {} from {} for logs", + message.id, + message.author.id + ); let channel = message.channel_id; let log = find_log(message).await; diff --git a/src/handlers/event/analyze_logs/providers/0x0.rs b/src/handlers/event/analyze_logs/providers/0x0.rs index fe8c7f0..cfa553a 100644 --- a/src/handlers/event/analyze_logs/providers/0x0.rs +++ b/src/handlers/event/analyze_logs/providers/0x0.rs @@ -1,6 +1,7 @@ use crate::api::REQWEST_CLIENT; use eyre::{eyre, Result}; +use log::trace; use once_cell::sync::Lazy; use regex::Regex; use reqwest::StatusCode; @@ -8,6 +9,8 @@ use reqwest::StatusCode; static REGEX: Lazy = Lazy::new(|| Regex::new(r"https://0x0\.st/\w*\.\w*").unwrap()); pub async fn find(content: &str) -> Result> { + trace!("Checking if {content} is a 0x0 paste"); + let Some(url) = REGEX.find(content).map(|m| &content[m.range()]) else { return Ok(None); }; diff --git a/src/handlers/event/analyze_logs/providers/attachment.rs b/src/handlers/event/analyze_logs/providers/attachment.rs index 892f19b..97e8c3e 100644 --- a/src/handlers/event/analyze_logs/providers/attachment.rs +++ b/src/handlers/event/analyze_logs/providers/attachment.rs @@ -1,7 +1,10 @@ use eyre::Result; +use log::trace; use poise::serenity_prelude::Message; pub async fn find(message: &Message) -> Result> { + trace!("Checking for text attachments in message {}", message.id); + // find first uploaded text file if let Some(attachment) = message.attachments.iter().find(|a| { a.content_type diff --git a/src/handlers/event/analyze_logs/providers/haste.rs b/src/handlers/event/analyze_logs/providers/haste.rs index c72e3b3..194ae79 100644 --- a/src/handlers/event/analyze_logs/providers/haste.rs +++ b/src/handlers/event/analyze_logs/providers/haste.rs @@ -1,6 +1,7 @@ use crate::api::REQWEST_CLIENT; use eyre::{eyre, Result}; +use log::trace; use once_cell::sync::Lazy; use regex::Regex; use reqwest::StatusCode; @@ -9,6 +10,8 @@ static REGEX: Lazy = Lazy::new(|| Regex::new(r"https://hst\.sh(?:/raw)?/(\w+(?:\.\w*)?)").unwrap()); pub async fn find(content: &str) -> Result> { + trace!("Checking if {content} is a haste log"); + let Some(captures) = REGEX.captures(content) else { return Ok(None); }; diff --git a/src/handlers/event/analyze_logs/providers/mclogs.rs b/src/handlers/event/analyze_logs/providers/mclogs.rs index 96a9108..34933de 100644 --- a/src/handlers/event/analyze_logs/providers/mclogs.rs +++ b/src/handlers/event/analyze_logs/providers/mclogs.rs @@ -1,6 +1,7 @@ use crate::api::REQWEST_CLIENT; use eyre::{eyre, Result}; +use log::trace; use once_cell::sync::Lazy; use regex::Regex; use reqwest::StatusCode; @@ -8,6 +9,8 @@ use reqwest::StatusCode; static REGEX: Lazy = Lazy::new(|| Regex::new(r"https://mclo\.gs/(\w+)").unwrap()); pub async fn find(content: &str) -> Result> { + trace!("Checking if {content} is an mclo.gs paste"); + let Some(captures) = REGEX.captures(content) else { return Ok(None); }; diff --git a/src/handlers/event/analyze_logs/providers/paste_gg.rs b/src/handlers/event/analyze_logs/providers/paste_gg.rs index de7cd04..a45ea81 100644 --- a/src/handlers/event/analyze_logs/providers/paste_gg.rs +++ b/src/handlers/event/analyze_logs/providers/paste_gg.rs @@ -1,6 +1,7 @@ use crate::api::REQWEST_CLIENT; use eyre::{eyre, OptionExt, Result}; +use log::trace; use once_cell::sync::Lazy; use regex::Regex; use reqwest::StatusCode; @@ -27,6 +28,7 @@ struct PasteResult { } pub async fn find(content: &str) -> Result> { + trace!("Checking if {content} is a paste.gg log"); let Some(captures) = REGEX.captures(content) else { return Ok(None); }; diff --git a/src/handlers/event/analyze_logs/providers/pastebin.rs b/src/handlers/event/analyze_logs/providers/pastebin.rs index 75b9c89..c42b3de 100644 --- a/src/handlers/event/analyze_logs/providers/pastebin.rs +++ b/src/handlers/event/analyze_logs/providers/pastebin.rs @@ -1,6 +1,7 @@ use crate::api::REQWEST_CLIENT; use eyre::{eyre, Result}; +use log::trace; use once_cell::sync::Lazy; use regex::Regex; use reqwest::StatusCode; @@ -9,6 +10,7 @@ static REGEX: Lazy = Lazy::new(|| Regex::new(r"https://pastebin\.com(?:/raw)?/(\w+)").unwrap()); pub async fn find(content: &str) -> Result> { + trace!("Checking if {content} is a pastebin log"); let Some(captures) = REGEX.captures(content) else { return Ok(None); }; diff --git a/src/handlers/event/mod.rs b/src/handlers/event/mod.rs index 53b2952..cafd16c 100644 --- a/src/handlers/event/mod.rs +++ b/src/handlers/event/mod.rs @@ -30,6 +30,8 @@ pub async fn handle( } FullEvent::Message { new_message } => { + trace!("Recieved message {}", new_message.content); + // ignore new messages from bots // note: the webhook_id check allows us to still respond to PK users if (new_message.author.bot && new_message.webhook_id.is_none()) @@ -55,10 +57,17 @@ pub async fn handle( } FullEvent::ReactionAdd { add_reaction } => { + trace!( + "Recieved reaction {} on message {} from {}", + add_reaction.emoji, + add_reaction.message_id.to_string(), + add_reaction.user_id.unwrap_or_default().to_string() + ); delete_on_reaction::handle(ctx, add_reaction).await?; } FullEvent::ThreadCreate { thread } => { + trace!("Recieved thread {}", thread.id); support_onboard::handle(ctx, thread).await?; } From 6bd5db720870520583297218afc158a8e5935b7c Mon Sep 17 00:00:00 2001 From: seth Date: Mon, 18 Mar 2024 09:48:37 -0400 Subject: [PATCH 59/90] utils::resolve_message: use perms from behind pk proxy --- src/handlers/event/mod.rs | 2 +- src/utils/resolve_message.rs | 22 ++++++++++++++++++++-- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/src/handlers/event/mod.rs b/src/handlers/event/mod.rs index cafd16c..7eff42f 100644 --- a/src/handlers/event/mod.rs +++ b/src/handlers/event/mod.rs @@ -15,7 +15,7 @@ mod support_onboard; pub async fn handle( ctx: &Context, event: &FullEvent, - _framework: FrameworkContext<'_, Data, Report>, + _: FrameworkContext<'_, Data, Report>, data: &Data, ) -> Result<()> { match event { diff --git a/src/utils/resolve_message.rs b/src/utils/resolve_message.rs index c237eda..95b01d5 100644 --- a/src/utils/resolve_message.rs +++ b/src/utils/resolve_message.rs @@ -1,3 +1,4 @@ +use crate::api::pluralkit; use std::str::FromStr; use eyre::{eyre, Context as _, Result}; @@ -5,7 +6,7 @@ use log::{debug, trace}; use once_cell::sync::Lazy; use poise::serenity_prelude::{ Cache, CacheHttp, ChannelId, ChannelType, Colour, Context, CreateEmbed, CreateEmbedAuthor, - CreateEmbedFooter, GuildChannel, Member, Message, MessageId, Permissions, + CreateEmbedFooter, GuildChannel, Member, Message, MessageId, Permissions, UserId, }; use regex::Regex; @@ -22,6 +23,14 @@ fn find_first_image(message: &Message) -> Option { .map(|res| res.url.clone()) } +async fn find_real_author_id(message: &Message) -> UserId { + if let Ok(sender) = pluralkit::get_sender(message.id).await { + sender + } else { + message.author.id + } +} + async fn member_can_view_channel( ctx: impl CacheHttp + AsRef, member: &Member, @@ -108,7 +117,16 @@ pub async fn from_message(ctx: &Context, msg: &Message) -> Result Date: Sun, 24 Mar 2024 15:08:45 -0400 Subject: [PATCH 60/90] nix: `naersk`/`fenix` -> `rustPlatform`/`rust-overlay` --- flake.lock | 145 +++++++++----------------------- flake.nix | 26 +++--- nix/deployment.nix | 92 -------------------- nix/deployment/default.nix | 35 ++++++++ nix/{ => deployment}/module.nix | 0 nix/deployment/static.nix | 41 +++++++++ nix/derivation.nix | 22 +++-- nix/dev.nix | 33 ++++---- nix/packages.nix | 12 +-- 9 files changed, 164 insertions(+), 242 deletions(-) delete mode 100644 nix/deployment.nix create mode 100644 nix/deployment/default.nix rename nix/{ => deployment}/module.nix (100%) create mode 100644 nix/deployment/static.nix diff --git a/flake.lock b/flake.lock index f88b7db..927219c 100644 --- a/flake.lock +++ b/flake.lock @@ -1,42 +1,5 @@ { "nodes": { - "fenix": { - "inputs": { - "nixpkgs": [ - "nixpkgs" - ], - "rust-analyzer-src": "rust-analyzer-src" - }, - "locked": { - "lastModified": 1710656467, - "narHash": "sha256-4Plj0vNP+ckWVNi6EtVojL9YV2dwSH7H4UMFCV40VE8=", - "owner": "nix-community", - "repo": "fenix", - "rev": "c53bb4a32f2fce7acf4e8e160a54779c4460ffdb", - "type": "github" - }, - "original": { - "owner": "nix-community", - "repo": "fenix", - "type": "github" - } - }, - "flake-compat": { - "flake": false, - "locked": { - "lastModified": 1696426674, - "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=", - "owner": "edolstra", - "repo": "flake-compat", - "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33", - "type": "github" - }, - "original": { - "owner": "edolstra", - "repo": "flake-compat", - "type": "github" - } - }, "flake-parts": { "inputs": { "nixpkgs-lib": [ @@ -62,11 +25,11 @@ "systems": "systems" }, "locked": { - "lastModified": 1701680307, - "narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=", + "lastModified": 1710146030, + "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=", "owner": "numtide", "repo": "flake-utils", - "rev": "4022d587cbbfd70fe950c1e2083a02621806a725", + "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a", "type": "github" }, "original": { @@ -83,11 +46,11 @@ ] }, "locked": { - "lastModified": 1703887061, - "narHash": "sha256-gGPa9qWNc6eCXT/+Z5/zMkyYOuRZqeFZBDbopNZQkuY=", + "lastModified": 1709087332, + "narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=", "owner": "hercules-ci", "repo": "gitignore.nix", - "rev": "43e1aa1308018f37118e34d3a9cb4f5e75dc11d5", + "rev": "637db329424fd7e46cf4185293b9cc8c88c95394", "type": "github" }, "original": { @@ -96,33 +59,13 @@ "type": "github" } }, - "naersk": { - "inputs": { - "nixpkgs": [ - "nixpkgs" - ] - }, - "locked": { - "lastModified": 1698420672, - "narHash": "sha256-/TdeHMPRjjdJub7p7+w55vyABrsJlt5QkznPYy55vKA=", - "owner": "nix-community", - "repo": "naersk", - "rev": "aeb58d5e8faead8980a807c840232697982d47b9", - "type": "github" - }, - "original": { - "owner": "nix-community", - "repo": "naersk", - "type": "github" - } - }, "nixpkgs": { "locked": { - "lastModified": 1710701596, - "narHash": "sha256-v4lsAi3vE/sEWg0G8AydMjs3NTHlsNw8K01xw06cKLg=", + "lastModified": 1711231723, + "narHash": "sha256-dARJQ8AJOv6U+sdRePkbcVyVbXJTi1tReCrkkOeusiA=", "owner": "nixos", "repo": "nixpkgs", - "rev": "34ad8c9f29a18b4dd97a9ad40ceb16954f24afe7", + "rev": "e1d501922fd7351da4200e1275dfcf5faaad1220", "type": "github" }, "original": { @@ -132,38 +75,24 @@ "type": "github" } }, - "nixpkgs-stable": { - "locked": { - "lastModified": 1704874635, - "narHash": "sha256-YWuCrtsty5vVZvu+7BchAxmcYzTMfolSPP5io8+WYCg=", - "owner": "NixOS", - "repo": "nixpkgs", - "rev": "3dc440faeee9e889fe2d1b4d25ad0f430d449356", - "type": "github" - }, - "original": { - "owner": "NixOS", - "ref": "nixos-23.11", - "repo": "nixpkgs", - "type": "github" - } - }, "pre-commit-hooks": { "inputs": { - "flake-compat": "flake-compat", + "flake-compat": [], "flake-utils": "flake-utils", "gitignore": "gitignore", "nixpkgs": [ "nixpkgs" ], - "nixpkgs-stable": "nixpkgs-stable" + "nixpkgs-stable": [ + "nixpkgs" + ] }, "locked": { - "lastModified": 1708018599, - "narHash": "sha256-M+Ng6+SePmA8g06CmUZWi1AjG2tFBX9WCXElBHEKnyM=", + "lastModified": 1710923068, + "narHash": "sha256-6hOpUiuxuwpXXc/xfJsBUJeqqgGI+JMJuLo45aG3cKc=", "owner": "cachix", "repo": "pre-commit-hooks.nix", - "rev": "5df5a70ad7575f6601d91f0efec95dd9bc619431", + "rev": "e611897ddfdde3ed3eaac4758635d7177ff78673", "type": "github" }, "original": { @@ -179,11 +108,11 @@ ] }, "locked": { - "lastModified": 1710407041, - "narHash": "sha256-rCHklFHPzrq341KoTgXNdknNZbjOJ+VmalqX5s5YdGM=", + "lastModified": 1711158989, + "narHash": "sha256-exgncIe/lQIswv2L1M0y+RrHAg5dofLFCOxGu4/yJww=", "owner": "getchoo", "repo": "procfile-nix", - "rev": "7a0ab379a4ab71c9deccaca9fb463e9aaea363d8", + "rev": "6388308f9e9c8a8fbfdff54b30adf486fa292cf9", "type": "github" }, "original": { @@ -194,29 +123,35 @@ }, "root": { "inputs": { - "fenix": "fenix", "flake-parts": "flake-parts", - "naersk": "naersk", "nixpkgs": "nixpkgs", "pre-commit-hooks": "pre-commit-hooks", "procfile-nix": "procfile-nix", + "rust-overlay": "rust-overlay", "treefmt-nix": "treefmt-nix" } }, - "rust-analyzer-src": { - "flake": false, + "rust-overlay": { + "inputs": { + "flake-utils": [ + "pre-commit-hooks", + "flake-utils" + ], + "nixpkgs": [ + "nixpkgs" + ] + }, "locked": { - "lastModified": 1710610549, - "narHash": "sha256-xFIGLn5u+msUazlLbdjZ3gQgXrt7Lrlhq+XXUH0XU/0=", - "owner": "rust-lang", - "repo": "rust-analyzer", - "rev": "5ecace48f693afaa6adf8cb23086b651db3aec96", + "lastModified": 1711246447, + "narHash": "sha256-g9TOluObcOEKewFo2fR4cn51Y/jSKhRRo4QZckHLop0=", + "owner": "oxalica", + "repo": "rust-overlay", + "rev": "dcc802a6ec4e9cc6a1c8c393327f0c42666f22e4", "type": "github" }, "original": { - "owner": "rust-lang", - "ref": "nightly", - "repo": "rust-analyzer", + "owner": "oxalica", + "repo": "rust-overlay", "type": "github" } }, @@ -242,11 +177,11 @@ ] }, "locked": { - "lastModified": 1710278050, - "narHash": "sha256-Oc6BP7soXqb8itlHI8UKkdf3V9GeJpa1S39SR5+HJys=", + "lastModified": 1710781103, + "narHash": "sha256-nehQK/XTFxfa6rYKtbi8M1w+IU1v5twYhiyA4dg1vpg=", "owner": "numtide", "repo": "treefmt-nix", - "rev": "35791f76524086ab4b785a33e4abbedfda64bd22", + "rev": "7ee5aaac63c30d3c97a8c56efe89f3b2aa9ae564", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 47a0194..4685dde 100644 --- a/flake.nix +++ b/flake.nix @@ -8,19 +8,13 @@ inputs.nixpkgs-lib.follows = "nixpkgs"; }; - naersk = { - url = "github:nix-community/naersk"; - inputs.nixpkgs.follows = "nixpkgs"; - }; - - fenix = { - url = "github:nix-community/fenix"; - inputs.nixpkgs.follows = "nixpkgs"; - }; - pre-commit-hooks = { url = "github:cachix/pre-commit-hooks.nix"; - inputs.nixpkgs.follows = "nixpkgs"; + inputs = { + nixpkgs.follows = "nixpkgs"; + nixpkgs-stable.follows = "nixpkgs"; + flake-compat.follows = ""; + }; }; procfile-nix = { @@ -28,6 +22,14 @@ inputs.nixpkgs.follows = "nixpkgs"; }; + rust-overlay = { + url = "github:oxalica/rust-overlay"; + inputs = { + nixpkgs.follows = "nixpkgs"; + flake-utils.follows = "pre-commit-hooks/flake-utils"; + }; + }; + treefmt-nix = { url = "github:numtide/treefmt-nix"; inputs.nixpkgs.follows = "nixpkgs"; @@ -39,7 +41,7 @@ imports = [ ./nix/dev.nix ./nix/packages.nix - ./nix/deployment.nix + ./nix/deployment inputs.pre-commit-hooks.flakeModule inputs.procfile-nix.flakeModule diff --git a/nix/deployment.nix b/nix/deployment.nix deleted file mode 100644 index 115318b..0000000 --- a/nix/deployment.nix +++ /dev/null @@ -1,92 +0,0 @@ -{ - inputs, - flake-parts-lib, - withSystem, - ... -}: { - flake.nixosModules.default = flake-parts-lib.importApply ./module.nix { - inherit withSystem; - }; - - perSystem = { - lib, - pkgs, - system, - self', - inputs', - ... - }: let - crossPkgs = - rec { - x86_64-linux = { - x86_64 = pkgs.pkgsStatic; - aarch64 = pkgs.pkgsCross.aarch64-multiplatform.pkgsStatic; - }; - - aarch64-linux = { - x86_64 = pkgs.pkgsCross.musl64; - aarch64 = pkgs.pkgsStatic; - }; - - x86_64-darwin = { - x86_64 = pkgs.pkgsCross.musl64; - aarch64 = pkgs.pkgsCross.aarch64-multiplatform.pkgsStatic; - }; - - aarch64-darwin = x86_64-darwin; - } - .${system}; - - refractionFor = arch: let - inherit (crossPkgs.${arch}.stdenv) cc; - - target = "${arch}-unknown-linux-musl"; - target' = builtins.replaceStrings ["-"] ["_"] target; - targetUpper = lib.toUpper target'; - - toolchain = with inputs'.fenix.packages; - combine [ - minimal.cargo - minimal.rustc - targets.${target}.latest.rust-std - ]; - - naersk' = inputs.naersk.lib.${system}.override { - cargo = toolchain; - rustc = toolchain; - }; - - refraction = self'.packages.refraction.override { - lto = true; - naersk = naersk'; - }; - - newAttrs = { - CARGO_BUILD_TARGET = target; - "CC_${target'}" = "${cc}/bin/${cc.targetPrefix}cc"; - "CARGO_TARGET_${targetUpper}_RUSTFLAGS" = "-C target-feature=+crt-static"; - "CARGO_TARGET_${targetUpper}_LINKER" = newAttrs."CC_${target'}"; - }; - in - refraction.overrideAttrs newAttrs; - - containerFor = arch: - pkgs.dockerTools.buildLayeredImage { - name = "refraction"; - tag = "latest-${arch}"; - contents = [pkgs.dockerTools.caCertificates]; - config.Cmd = [ - (lib.getExe (refractionFor arch)) - ]; - - architecture = crossPkgs.${arch}.go.GOARCH; - }; - - mkPackagesFor = arch: { - "refraction-static-${arch}" = refractionFor arch; - "container-${arch}" = containerFor arch; - }; - in { - legacyPackages = lib.attrsets.mergeAttrsList (map mkPackagesFor ["x86_64" "aarch64"]); - }; -} diff --git a/nix/deployment/default.nix b/nix/deployment/default.nix new file mode 100644 index 0000000..96eb6ee --- /dev/null +++ b/nix/deployment/default.nix @@ -0,0 +1,35 @@ +{ + flake-parts-lib, + withSystem, + ... +}: { + imports = [./static.nix]; + + flake.nixosModules.default = flake-parts-lib.importApply ./module.nix { + inherit withSystem; + }; + + perSystem = { + lib, + pkgs, + self', + ... + }: let + containerFor = arch: + pkgs.dockerTools.buildLayeredImage { + name = "refraction"; + tag = "latest-${arch}"; + contents = [pkgs.dockerTools.caCertificates]; + config.Cmd = [ + (lib.getExe self'.packages."refraction-static-${arch}") + ]; + + architecture = withSystem "${arch}-linux" ({pkgs, ...}: pkgs.pkgsStatic.go.GOARCH); + }; + in { + packages = { + container-x86_64 = containerFor "x86_64"; + container-aarch64 = containerFor "aarch64"; + }; + }; +} diff --git a/nix/module.nix b/nix/deployment/module.nix similarity index 100% rename from nix/module.nix rename to nix/deployment/module.nix diff --git a/nix/deployment/static.nix b/nix/deployment/static.nix new file mode 100644 index 0000000..83d7b2c --- /dev/null +++ b/nix/deployment/static.nix @@ -0,0 +1,41 @@ +{ + perSystem = { + lib, + pkgs, + inputs', + self', + ... + }: let + targets = with pkgs.pkgsCross; { + x86_64 = musl64.pkgsStatic; + aarch64 = aarch64-multiplatform.pkgsStatic; + }; + + toolchain = inputs'.rust-overlay.packages.rust.minimal.override { + extensions = ["rust-std"]; + targets = map (pkgs: pkgs.stdenv.hostPlatform.config) (lib.attrValues targets); + }; + + rustPlatforms = + lib.mapAttrs ( + lib.const (pkgs: + pkgs.makeRustPlatform ( + lib.genAttrs ["cargo" "rustc"] (lib.const toolchain) + )) + ) + targets; + + buildWith = rustPlatform: + self'.packages.refraction.override { + inherit rustPlatform; + lto = true; + }; + in { + packages = + lib.mapAttrs' ( + target: rustPlatform: + lib.nameValuePair "refraction-static-${target}" (buildWith rustPlatform) + ) + rustPlatforms; + }; +} diff --git a/nix/derivation.nix b/nix/derivation.nix index 9e37e05..4736d57 100644 --- a/nix/derivation.nix +++ b/nix/derivation.nix @@ -1,14 +1,18 @@ { lib, stdenv, - naersk, + rustPlatform, darwin, - version, + self, lto ? false, }: -naersk.buildPackage { +rustPlatform.buildRustPackage { pname = "refraction"; - inherit version; + version = + (lib.importTOML ../Cargo.toml).package.version + + "-${self.shortRev or self.dirtyShortRev or "unknown-dirty"}"; + + __structuredAttrs = true; src = lib.fileset.toSource { root = ../.; @@ -21,13 +25,21 @@ naersk.buildPackage { ]; }; + cargoLock = { + lockFile = ../Cargo.lock; + }; + buildInputs = lib.optionals stdenv.hostPlatform.isDarwin (with darwin.apple_sdk.frameworks; [ CoreFoundation Security SystemConfiguration ]); - cargoBuildFlags = lib.optionals lto ["-C" "lto=thin" "-C" "embed-bitcode=yes" "-Zdylib-lto"]; + env = { + CARGO_BUILD_RUSTFLAGS = lib.concatStringsSep " " ( + lib.optionals lto ["-C" "lto=thin" "-C" "embed-bitcode=yes" "-Zdylib-lto"] + ); + }; meta = with lib; { mainProgram = "refraction"; diff --git a/nix/dev.nix b/nix/dev.nix index b9bdf2f..0194f1e 100644 --- a/nix/dev.nix +++ b/nix/dev.nix @@ -5,9 +5,7 @@ config, self', ... - }: let - enableAll = lib.flip lib.genAttrs (lib.const {enable = true;}); - in { + }: { devShells.default = pkgs.mkShell { shellHook = '' ${config.pre-commit.installationScript} @@ -39,12 +37,12 @@ treefmt = { projectRootFile = "flake.nix"; - programs = enableAll [ - "alejandra" - "deadnix" - "prettier" - "rustfmt" - ]; + programs = { + alejandra.enable = true; + deadnix.enable = true; + prettier.enable = true; + rustfmt.enable = true; + }; settings.global = { excludes = [ @@ -55,15 +53,14 @@ }; }; - pre-commit.settings = { - settings.treefmt.package = config.treefmt.build.wrapper; - - hooks = enableAll [ - "actionlint" - "nil" - "statix" - "treefmt" - ]; + pre-commit.settings.hooks = { + actionlint.enable = true; + nil.enable = true; + statix.enable = true; + treefmt = { + enable = true; + package = config.treefmt.build.wrapper; + }; }; procfiles.daemons.processes = { diff --git a/nix/packages.nix b/nix/packages.nix index 19f5730..56c5c82 100644 --- a/nix/packages.nix +++ b/nix/packages.nix @@ -1,19 +1,11 @@ -{ - self, - inputs, - ... -}: { +{self, ...}: { perSystem = { pkgs, - system, self', ... }: { packages = { - refraction = pkgs.callPackage ./derivation.nix { - version = builtins.substring 0 7 self.rev or "dirty"; - naersk = inputs.naersk.lib.${system}; - }; + refraction = pkgs.callPackage ./derivation.nix {inherit self;}; default = self'.packages.refraction; }; From e847ea0ad012cff49025d899be0ad784c0572bb7 Mon Sep 17 00:00:00 2001 From: seth Date: Sat, 23 Mar 2024 11:04:38 -0400 Subject: [PATCH 61/90] api: use `error_for_status_ref()` --- src/api/dadjoke.rs | 22 +++++++++------------- src/api/pluralkit.rs | 32 ++++++++++++-------------------- src/api/prism_meta.rs | 38 +++++++++++++------------------------- src/api/rory.rs | 37 +++++++++++++------------------------ 4 files changed, 47 insertions(+), 82 deletions(-) diff --git a/src/api/dadjoke.rs b/src/api/dadjoke.rs index e1e7962..2675da6 100644 --- a/src/api/dadjoke.rs +++ b/src/api/dadjoke.rs @@ -1,24 +1,20 @@ use crate::api::REQWEST_CLIENT; -use eyre::{eyre, Result}; +use eyre::Result; use log::debug; -use reqwest::StatusCode; const DADJOKE: &str = "https://icanhazdadjoke.com"; pub async fn get_joke() -> Result { - let req = REQWEST_CLIENT + debug!("Making request to {DADJOKE}"); + + let resp = REQWEST_CLIENT .get(DADJOKE) .header("Accept", "text/plain") - .build()?; + .send() + .await?; + resp.error_for_status_ref()?; - debug!("Making request to {}", req.url()); - let resp = REQWEST_CLIENT.execute(req).await?; - let status = resp.status(); - - if let StatusCode::OK = status { - Ok(resp.text().await?) - } else { - Err(eyre!("Couldn't get a joke!")) - } + let joke = resp.text().await?; + Ok(joke) } diff --git a/src/api/pluralkit.rs b/src/api/pluralkit.rs index 4c18a6b..b711be6 100644 --- a/src/api/pluralkit.rs +++ b/src/api/pluralkit.rs @@ -1,39 +1,31 @@ use crate::api::REQWEST_CLIENT; -use eyre::{eyre, Context, Result}; +use eyre::{Context, Result}; use log::debug; use poise::serenity_prelude::{MessageId, UserId}; -use reqwest::StatusCode; use serde::{Deserialize, Serialize}; #[derive(Clone, Debug, Serialize, Deserialize)] -pub struct PluralKitMessage { +pub struct Message { pub sender: String, } const PLURAL_KIT: &str = "https://api.pluralkit.me/v2"; -const MESSAGES_ENDPOINT: &str = "/messages"; +const MESSAGES: &str = "/messages"; pub async fn get_sender(message_id: MessageId) -> Result { - let req = REQWEST_CLIENT - .get(format!("{PLURAL_KIT}{MESSAGES_ENDPOINT}/{message_id}")) - .build()?; + let url = format!("{PLURAL_KIT}{MESSAGES}/{message_id}"); - debug!("Making request to {}", req.url()); - let resp = REQWEST_CLIENT.execute(req).await?; - let status = resp.status(); + debug!("Making request to {url}"); + let resp = REQWEST_CLIENT.get(url).send().await?; + resp.error_for_status_ref()?; - if let StatusCode::OK = status { - let data = resp.json::().await?; - let id: u64 = data.sender.parse().wrap_err_with(|| { + let data: Message = resp.json().await?; + let id: u64 = + data.sender.parse().wrap_err_with(|| { format!("Couldn't parse response from PluralKit as a UserId! Here's the response:\n{data:#?}") })?; - let sender = UserId::from(id); + let sender = UserId::from(id); - Ok(sender) - } else { - Err(eyre!( - "Failed to get PluralKit message information from {PLURAL_KIT}{MESSAGES_ENDPOINT}/{message_id} with {status}", - )) - } + Ok(sender) } diff --git a/src/api/prism_meta.rs b/src/api/prism_meta.rs index f59e126..fbc5878 100644 --- a/src/api/prism_meta.rs +++ b/src/api/prism_meta.rs @@ -1,8 +1,7 @@ use crate::api::REQWEST_CLIENT; -use eyre::{eyre, Context, OptionExt, Result}; +use eyre::{OptionExt, Result}; use log::debug; -use reqwest::StatusCode; use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize)] @@ -14,33 +13,22 @@ pub struct MinecraftPackageJson { pub uid: String, } -const PRISM_META: &str = "https://meta.prismlauncher.org/v1"; -const MINECRAFT_PACKAGEJSON_ENDPOINT: &str = "/net.minecraft/package.json"; +const META: &str = "https://meta.prismlauncher.org/v1"; +const MINECRAFT_PACKAGEJSON: &str = "/net.minecraft/package.json"; pub async fn get_latest_minecraft_version() -> Result { - let req = REQWEST_CLIENT - .get(format!("{PRISM_META}{MINECRAFT_PACKAGEJSON_ENDPOINT}")) - .build()?; + let url = format!("{META}{MINECRAFT_PACKAGEJSON}"); - debug!("Making request to {}", req.url()); - let resp = REQWEST_CLIENT.execute(req).await?; - let status = resp.status(); + debug!("Making request to {url}"); + let resp = REQWEST_CLIENT.get(url).send().await?; + resp.error_for_status_ref()?; - if let StatusCode::OK = status { - let data = resp - .json::() - .await - .wrap_err("Couldn't parse Minecraft versions!")?; + let data: MinecraftPackageJson = resp.json().await?; - let version = data - .recommended - .first() - .ok_or_eyre("Couldn't find latest version of Minecraft!")?; + let version = data + .recommended + .first() + .ok_or_eyre("Couldn't find latest version of Minecraft!")?; - Ok(version.clone()) - } else { - Err(eyre!( - "Failed to get latest Minecraft version from {PRISM_META}{MINECRAFT_PACKAGEJSON_ENDPOINT} with {status}", - )) - } + Ok(version.clone()) } diff --git a/src/api/rory.rs b/src/api/rory.rs index e6ae45d..ccbcada 100644 --- a/src/api/rory.rs +++ b/src/api/rory.rs @@ -1,8 +1,7 @@ use crate::api::REQWEST_CLIENT; -use eyre::{eyre, Context, Result}; +use eyre::{Context, Result}; use log::debug; -use reqwest::StatusCode; use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize)] @@ -13,34 +12,24 @@ pub struct Response { } const RORY: &str = "https://rory.cat"; -const ENDPOINT: &str = "/purr"; +const PURR: &str = "/purr"; pub async fn get(id: Option) -> Result { let target = id.map(|id| id.to_string()).unwrap_or_default(); + let url = format!("{RORY}{PURR}/{target}"); - let req = REQWEST_CLIENT - .get(format!("{RORY}{ENDPOINT}/{target}")) - .build() - .wrap_err("Couldn't build reqwest client!")?; + debug!("Making request to {url}"); - debug!("Making request to {}", req.url()); let resp = REQWEST_CLIENT - .execute(req) + .get(format!("{RORY}{PURR}/{target}")) + .send() + .await?; + resp.error_for_status_ref()?; + + let data: Response = resp + .json() .await - .wrap_err("Couldn't make request for rory!")?; + .wrap_err("Couldn't parse the rory response!")?; - let status = resp.status(); - - if let StatusCode::OK = status { - let data = resp - .json::() - .await - .wrap_err("Couldn't parse the rory response!")?; - - Ok(data) - } else { - Err(eyre!( - "Failed to get rory from {RORY}{ENDPOINT}/{target} with {status}", - )) - } + Ok(data) } From b63ecde6b4bbeaf2d0775fa87369c5c8bd6134b8 Mon Sep 17 00:00:00 2001 From: seth Date: Sat, 23 Mar 2024 11:11:17 -0400 Subject: [PATCH 62/90] commands: tidy up help & joke --- src/commands/general/help.rs | 26 +++++++++++++------------- src/commands/general/joke.rs | 3 +-- src/commands/mod.rs | 5 +++-- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/commands/general/help.rs b/src/commands/general/help.rs index bdf3047..f91d97d 100644 --- a/src/commands/general/help.rs +++ b/src/commands/general/help.rs @@ -1,23 +1,23 @@ -use eyre::Result; -use poise::{builtins, samples::HelpConfiguration}; - use crate::Context; +use eyre::Result; +use log::trace; +use poise::samples::HelpConfiguration; + /// View the help menu #[poise::command(slash_command, prefix_command, track_edits = true)] pub async fn help( ctx: Context<'_>, - #[description = "provide information about a specific command"] command: Option, + #[description = "Provide information about a specific command"] command: Option, ) -> Result<()> { - builtins::help( - ctx, - command.as_deref(), - HelpConfiguration { - extra_text_at_bottom: "Use /help for more information about a specific command!", - ..HelpConfiguration::default() - }, - ) - .await?; + trace!("Running help command"); + + let configuration = HelpConfiguration { + extra_text_at_bottom: "Use /help for more information about a specific command!", + ..Default::default() + }; + + poise::builtins::help(ctx, command.as_deref(), configuration).await?; Ok(()) } diff --git a/src/commands/general/joke.rs b/src/commands/general/joke.rs index fb39d20..fbced97 100644 --- a/src/commands/general/joke.rs +++ b/src/commands/general/joke.rs @@ -10,9 +10,8 @@ pub async fn joke(ctx: Context<'_>) -> Result<()> { trace!("Running joke command"); ctx.defer().await?; - let joke = dadjoke::get_joke().await?; - ctx.say(joke).await?; + Ok(()) } diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 0d44cbe..b8f7418 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -1,11 +1,12 @@ use crate::Data; use eyre::Report; -use poise::Command; mod general; -pub fn get() -> Vec> { +pub type Command = poise::Command; + +pub fn get() -> Vec { vec![ general::joke(), general::members(), From 827b5a4bd78b915ce6f700194252f04397c144ca Mon Sep 17 00:00:00 2001 From: seth Date: Sat, 23 Mar 2024 15:56:54 -0400 Subject: [PATCH 63/90] analyze_logs: introduce LogProvider trait --- Cargo.lock | 14 +++- Cargo.toml | 2 +- src/api/mod.rs | 1 + src/api/paste_gg.rs | 55 +++++++++++++ .../event/analyze_logs/providers/0x0.rs | 32 ++++---- .../analyze_logs/providers/attachment.rs | 36 +++++---- .../event/analyze_logs/providers/haste.rs | 36 ++++----- .../event/analyze_logs/providers/mclogs.rs | 34 ++++---- .../event/analyze_logs/providers/mod.rs | 79 +++++++++++++----- .../event/analyze_logs/providers/paste_gg.rs | 80 +++++-------------- .../event/analyze_logs/providers/pastebin.rs | 37 ++++----- src/utils/mod.rs | 28 +++++++ 12 files changed, 270 insertions(+), 164 deletions(-) create mode 100644 src/api/paste_gg.rs diff --git a/Cargo.lock b/Cargo.lock index 7014914..c74d784 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -465,6 +465,18 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "enum_dispatch" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f33313078bb8d4d05a2733a94ac4c2d8a0df9a2b84424ebf4f33bfc224a890e" +dependencies = [ + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.48", +] + [[package]] name = "env_filter" version = "0.1.0" @@ -1390,9 +1402,9 @@ dependencies = [ name = "refraction" version = "2.0.0" dependencies = [ - "async-trait", "color-eyre", "dotenvy", + "enum_dispatch", "env_logger", "eyre", "gray_matter", diff --git a/Cargo.toml b/Cargo.toml index ea5bc06..abcd751 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,9 +15,9 @@ serde = "1.0.196" serde_json = "1.0.112" [dependencies] -async-trait = "0.1.77" color-eyre = "0.6.2" dotenvy = "0.15.7" +enum_dispatch = "0.3.12" env_logger = "0.11.1" eyre = "0.6.11" log = "0.4.20" diff --git a/src/api/mod.rs b/src/api/mod.rs index 5198953..a32d14b 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -1,6 +1,7 @@ use once_cell::sync::Lazy; pub mod dadjoke; +pub mod paste_gg; pub mod pluralkit; pub mod prism_meta; pub mod rory; diff --git a/src/api/paste_gg.rs b/src/api/paste_gg.rs new file mode 100644 index 0000000..c649429 --- /dev/null +++ b/src/api/paste_gg.rs @@ -0,0 +1,55 @@ +use crate::{api::REQWEST_CLIENT, utils}; + +use eyre::{eyre, OptionExt, Result}; +use log::debug; +use serde::{Deserialize, Serialize}; + +const PASTE_GG: &str = "https://api.paste.gg/v1"; +const PASTES: &str = "/pastes"; + +#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Deserialize, Serialize)] +enum Status { + #[serde(rename = "success")] + Success, + #[serde(rename = "error")] + Error, +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct Response { + status: Status, + pub result: Option>, + error: Option, + message: Option, +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct Files { + pub id: String, + pub name: Option, +} + +pub async fn get_files(id: &str) -> Result> { + let url = format!("{PASTE_GG}{PASTES}/{id}/files"); + debug!("Making request to {url}"); + let resp = REQWEST_CLIENT.get(url).send().await?; + resp.error_for_status_ref()?; + let resp: Response = resp.json().await?; + + if resp.status == Status::Error { + let message = resp + .error + .ok_or_eyre("Paste.gg gave us an error but with no message!")?; + + Err(eyre!(message)) + } else { + Ok(resp) + } +} + +pub async fn get_raw_file(paste_id: &str, file_id: &str) -> eyre::Result { + let url = format!("{PASTE_GG}{PASTES}/{paste_id}/files/{file_id}/raw"); + let text = utils::text_from_url(&url).await?; + + Ok(text) +} diff --git a/src/handlers/event/analyze_logs/providers/0x0.rs b/src/handlers/event/analyze_logs/providers/0x0.rs index cfa553a..9c76a40 100644 --- a/src/handlers/event/analyze_logs/providers/0x0.rs +++ b/src/handlers/event/analyze_logs/providers/0x0.rs @@ -1,27 +1,27 @@ -use crate::api::REQWEST_CLIENT; +use crate::utils; -use eyre::{eyre, Result}; +use eyre::Result; use log::trace; use once_cell::sync::Lazy; +use poise::serenity_prelude::Message; use regex::Regex; -use reqwest::StatusCode; -static REGEX: Lazy = Lazy::new(|| Regex::new(r"https://0x0\.st/\w*\.\w*").unwrap()); +pub struct _0x0; -pub async fn find(content: &str) -> Result> { - trace!("Checking if {content} is a 0x0 paste"); +impl super::LogProvider for _0x0 { + async fn find_match(&self, message: &Message) -> Option { + static REGEX: Lazy = Lazy::new(|| Regex::new(r"https://0x0\.st/\w*\.\w*").unwrap()); - let Some(url) = REGEX.find(content).map(|m| &content[m.range()]) else { - return Ok(None); - }; + trace!("Checking if message {} is a 0x0 paste", message.id); + REGEX + .find_iter(&message.content) + .map(|m| m.as_str().to_string()) + .nth(0) + } - let request = REQWEST_CLIENT.get(url).build()?; - let response = REQWEST_CLIENT.execute(request).await?; - let status = response.status(); + async fn fetch(&self, content: &str) -> Result { + let log = utils::text_from_url(content).await?; - if let StatusCode::OK = status { - Ok(Some(response.text().await?)) - } else { - Err(eyre!("Failed to fetch paste from {url} with {status}",)) + Ok(log) } } diff --git a/src/handlers/event/analyze_logs/providers/attachment.rs b/src/handlers/event/analyze_logs/providers/attachment.rs index 97e8c3e..103c4c2 100644 --- a/src/handlers/event/analyze_logs/providers/attachment.rs +++ b/src/handlers/event/analyze_logs/providers/attachment.rs @@ -2,20 +2,28 @@ use eyre::Result; use log::trace; use poise::serenity_prelude::Message; -pub async fn find(message: &Message) -> Result> { - trace!("Checking for text attachments in message {}", message.id); +use crate::utils; - // find first uploaded text file - if let Some(attachment) = message.attachments.iter().find(|a| { - a.content_type - .as_ref() - .and_then(|ct| ct.starts_with("text/").then_some(true)) - .is_some() - }) { - let bytes = attachment.download().await?; - let res = String::from_utf8(bytes)?; - Ok(Some(res)) - } else { - Ok(None) +pub struct Attachment; + +impl super::LogProvider for Attachment { + async fn find_match(&self, message: &Message) -> Option { + trace!("Checking if message {} has text attachments", message.id); + + message + .attachments + .iter() + .filter_map(|a| { + a.content_type + .as_ref() + .and_then(|ct| ct.starts_with("text/").then_some(a.url.clone())) + }) + .nth(0) + } + + async fn fetch(&self, content: &str) -> Result { + let attachment = utils::bytes_from_url(content).await?; + let log = String::from_utf8(attachment)?; + Ok(log) } } diff --git a/src/handlers/event/analyze_logs/providers/haste.rs b/src/handlers/event/analyze_logs/providers/haste.rs index 194ae79..9909176 100644 --- a/src/handlers/event/analyze_logs/providers/haste.rs +++ b/src/handlers/event/analyze_logs/providers/haste.rs @@ -1,29 +1,29 @@ -use crate::api::REQWEST_CLIENT; +use crate::utils; -use eyre::{eyre, Result}; +use eyre::Result; use log::trace; use once_cell::sync::Lazy; +use poise::serenity_prelude::Message; use regex::Regex; -use reqwest::StatusCode; -static REGEX: Lazy = - Lazy::new(|| Regex::new(r"https://hst\.sh(?:/raw)?/(\w+(?:\.\w*)?)").unwrap()); +const HASTE: &str = "https://hst.sh"; +const RAW: &str = "/raw"; -pub async fn find(content: &str) -> Result> { - trace!("Checking if {content} is a haste log"); +pub struct Haste; - let Some(captures) = REGEX.captures(content) else { - return Ok(None); - }; +impl super::LogProvider for Haste { + async fn find_match(&self, message: &Message) -> Option { + static REGEX: Lazy = + Lazy::new(|| Regex::new(r"https://hst\.sh(?:/raw)?/(\w+(?:\.\w*)?)").unwrap()); - let url = format!("https://hst.sh/raw/{}", &captures[1]); - let request = REQWEST_CLIENT.get(&url).build()?; - let response = REQWEST_CLIENT.execute(request).await?; - let status = response.status(); + trace!("Checking if message {} is a hst.sh paste", message.id); + super::get_first_capture(®EX, &message.content) + } - if let StatusCode::OK = status { - Ok(Some(response.text().await?)) - } else { - Err(eyre!("Failed to fetch paste from {url} with {status}")) + async fn fetch(&self, content: &str) -> Result { + let url = format!("{HASTE}{RAW}/{content}"); + let log = utils::text_from_url(&url).await?; + + Ok(log) } } diff --git a/src/handlers/event/analyze_logs/providers/mclogs.rs b/src/handlers/event/analyze_logs/providers/mclogs.rs index 34933de..7e9265b 100644 --- a/src/handlers/event/analyze_logs/providers/mclogs.rs +++ b/src/handlers/event/analyze_logs/providers/mclogs.rs @@ -1,28 +1,28 @@ -use crate::api::REQWEST_CLIENT; +use crate::utils; -use eyre::{eyre, Result}; +use eyre::Result; use log::trace; use once_cell::sync::Lazy; +use poise::serenity_prelude::Message; use regex::Regex; -use reqwest::StatusCode; -static REGEX: Lazy = Lazy::new(|| Regex::new(r"https://mclo\.gs/(\w+)").unwrap()); +const MCLOGS: &str = "https://api.mclo.gs/1"; +const RAW: &str = "/raw"; -pub async fn find(content: &str) -> Result> { - trace!("Checking if {content} is an mclo.gs paste"); +pub struct MCLogs; - let Some(captures) = REGEX.captures(content) else { - return Ok(None); - }; +impl super::LogProvider for MCLogs { + async fn find_match(&self, message: &Message) -> Option { + static REGEX: Lazy = Lazy::new(|| Regex::new(r"https://mclo\.gs/(\w+)").unwrap()); - let url = format!("https://api.mclo.gs/1/raw/{}", &captures[1]); - let request = REQWEST_CLIENT.get(&url).build()?; - let response = REQWEST_CLIENT.execute(request).await?; - let status = response.status(); + trace!("Checking if message {} is an mclo.gs paste", message.id); + super::get_first_capture(®EX, &message.content) + } - if let StatusCode::OK = status { - Ok(Some(response.text().await?)) - } else { - Err(eyre!("Failed to fetch log from {url} with {status}")) + async fn fetch(&self, content: &str) -> Result { + let url = format!("{MCLOGS}{RAW}/{content}"); + let log = utils::text_from_url(&url).await?; + + Ok(log) } } diff --git a/src/handlers/event/analyze_logs/providers/mod.rs b/src/handlers/event/analyze_logs/providers/mod.rs index 2d13d6d..f8f096c 100644 --- a/src/handlers/event/analyze_logs/providers/mod.rs +++ b/src/handlers/event/analyze_logs/providers/mod.rs @@ -1,5 +1,15 @@ +use std::slice::Iter; + +use enum_dispatch::enum_dispatch; use eyre::Result; +use once_cell::sync::Lazy; use poise::serenity_prelude::Message; +use regex::Regex; + +use self::{ + _0x0::_0x0 as _0x0st, attachment::Attachment, haste::Haste, mclogs::MCLogs, paste_gg::PasteGG, + pastebin::PasteBin, +}; #[path = "0x0.rs"] mod _0x0; @@ -9,25 +19,52 @@ mod mclogs; mod paste_gg; mod pastebin; -pub type LogProvider = Result>; - -pub async fn find_log(message: &Message) -> LogProvider { - macro_rules! provider_impl { - ($provider:ident) => { - if let Some(content) = $provider::find(&message.content).await? { - return Ok(Some(content)); - } - }; - } - provider_impl!(_0x0); - provider_impl!(mclogs); - provider_impl!(haste); - provider_impl!(paste_gg); - provider_impl!(pastebin); - - if let Some(content) = attachment::find(message).await? { - return Ok(Some(content)); - } - - Ok(None) +#[enum_dispatch] +pub trait LogProvider { + async fn find_match(&self, message: &Message) -> Option; + async fn fetch(&self, content: &str) -> Result; +} + +fn get_first_capture(regex: &Lazy, string: &str) -> Option { + regex + .captures_iter(string) + .filter_map(|c| c.get(1).map(|c| c.as_str().to_string())) + .nth(1) +} + +#[enum_dispatch(LogProvider)] +enum Provider { + _0x0st, + Attachment, + Haste, + MCLogs, + PasteGG, + PasteBin, +} + +impl Provider { + pub fn interator() -> Iter<'static, Provider> { + static PROVIDERS: [Provider; 6] = [ + Provider::_0x0st(_0x0st), + Provider::Attachment(Attachment), + Provider::Haste(Haste), + Provider::MCLogs(MCLogs), + Provider::PasteBin(PasteBin), + Provider::PasteGG(PasteGG), + ]; + PROVIDERS.iter() + } +} + +pub async fn find_log(message: &Message) -> Result> { + let providers = Provider::interator(); + + for provider in providers { + if let Some(found) = provider.find_match(message).await { + let log = provider.fetch(&found).await?; + return Ok(Some(log)); + } + } + + todo!() } diff --git a/src/handlers/event/analyze_logs/providers/paste_gg.rs b/src/handlers/event/analyze_logs/providers/paste_gg.rs index a45ea81..5800a68 100644 --- a/src/handlers/event/analyze_logs/providers/paste_gg.rs +++ b/src/handlers/event/analyze_logs/providers/paste_gg.rs @@ -1,72 +1,36 @@ -use crate::api::REQWEST_CLIENT; +use crate::api::paste_gg; -use eyre::{eyre, OptionExt, Result}; +use eyre::{OptionExt, Result}; use log::trace; use once_cell::sync::Lazy; +use poise::serenity_prelude::Message; use regex::Regex; -use reqwest::StatusCode; -use serde::{Deserialize, Serialize}; -const PASTE_GG: &str = "https://api.paste.gg/v1"; -const PASTES_ENDPOINT: &str = "/pastes"; -static REGEX: Lazy = Lazy::new(|| Regex::new(r"https://paste.gg/p/\w+/(\w+)").unwrap()); +pub struct PasteGG; -#[derive(Clone, Debug, Deserialize, Serialize)] -struct PasteResponse { - status: String, - result: Option>, - error: Option, - message: Option, -} +impl super::LogProvider for PasteGG { + async fn find_match(&self, message: &Message) -> Option { + static REGEX: Lazy = + Lazy::new(|| Regex::new(r"https://paste.gg/p/\w+/(\w+)").unwrap()); -#[derive(Clone, Debug, Deserialize, Serialize)] -struct PasteResult { - id: String, - name: Option, - description: Option, - visibility: Option, -} - -pub async fn find(content: &str) -> Result> { - trace!("Checking if {content} is a paste.gg log"); - let Some(captures) = REGEX.captures(content) else { - return Ok(None); - }; - - let paste_id = &captures[1]; - let files_url = format!("{PASTE_GG}{PASTES_ENDPOINT}/{paste_id}/files"); - - let resp = REQWEST_CLIENT - .execute(REQWEST_CLIENT.get(&files_url).build()?) - .await?; - let status = resp.status(); - - if resp.status() != StatusCode::OK { - return Err(eyre!( - "Couldn't get paste {paste_id} from {PASTE_GG} with status {status}!" - )); + trace!("Checking if message {} is a paste.gg paste", message.id); + super::get_first_capture(®EX, &message.content) } - let paste_files: PasteResponse = resp.json().await?; - let file_id = &paste_files - .result - .ok_or_eyre("Couldn't find any files associated with paste {paste_id}!")?[0] - .id; + async fn fetch(&self, content: &str) -> Result { + let files = paste_gg::get_files(content).await?; + let result = files + .result + .ok_or_eyre("Got an empty result from paste.gg!")?; - let raw_url = format!("{PASTE_GG}{PASTES_ENDPOINT}/{paste_id}/files/{file_id}/raw"); + let file_id = result + .iter() + .map(|f| f.id.as_str()) + .nth(0) + .ok_or_eyre("Couldn't get file id from empty paste.gg response!")?; - let resp = REQWEST_CLIENT - .execute(REQWEST_CLIENT.get(&raw_url).build()?) - .await?; - let status = resp.status(); + let log = paste_gg::get_raw_file(content, file_id).await?; - if status != StatusCode::OK { - return Err(eyre!( - "Couldn't get file {file_id} from paste {paste_id} with status {status}!" - )); + Ok(log) } - - let text = resp.text().await?; - - Ok(Some(text)) } diff --git a/src/handlers/event/analyze_logs/providers/pastebin.rs b/src/handlers/event/analyze_logs/providers/pastebin.rs index c42b3de..f124018 100644 --- a/src/handlers/event/analyze_logs/providers/pastebin.rs +++ b/src/handlers/event/analyze_logs/providers/pastebin.rs @@ -1,28 +1,29 @@ -use crate::api::REQWEST_CLIENT; +use crate::utils; -use eyre::{eyre, Result}; +use eyre::Result; use log::trace; use once_cell::sync::Lazy; +use poise::serenity_prelude::Message; use regex::Regex; -use reqwest::StatusCode; -static REGEX: Lazy = - Lazy::new(|| Regex::new(r"https://pastebin\.com(?:/raw)?/(\w+)").unwrap()); +const PASTEBIN: &str = "https://pastebin.com"; +const RAW: &str = "/raw"; -pub async fn find(content: &str) -> Result> { - trace!("Checking if {content} is a pastebin log"); - let Some(captures) = REGEX.captures(content) else { - return Ok(None); - }; +pub struct PasteBin; - let url = format!("https://pastebin.com/raw/{}", &captures[1]); - let request = REQWEST_CLIENT.get(&url).build()?; - let response = REQWEST_CLIENT.execute(request).await?; - let status = response.status(); +impl super::LogProvider for PasteBin { + async fn find_match(&self, message: &Message) -> Option { + static REGEX: Lazy = + Lazy::new(|| Regex::new(r"https://pastebin\.com(?:/raw)?/(\w+)").unwrap()); - if let StatusCode::OK = status { - Ok(Some(response.text().await?)) - } else { - Err(eyre!("Failed to fetch paste from {url} with {status}")) + trace!("Checking if message {} is a pastebin paste", message.id); + super::get_first_capture(®EX, &message.content) + } + + async fn fetch(&self, content: &str) -> Result { + let url = format!("{PASTEBIN}{RAW}/{content}"); + let log = utils::text_from_url(&url).await?; + + Ok(log) } } diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 3df2c9a..64172ca 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -1 +1,29 @@ +use crate::api::REQWEST_CLIENT; + +use eyre::Result; +use log::debug; +use reqwest::Response; + pub mod resolve_message; + +pub async fn get_url(url: &str) -> Result { + debug!("Making request to {url}"); + let resp = REQWEST_CLIENT.get(url).send().await?; + resp.error_for_status_ref()?; + + Ok(resp) +} + +pub async fn text_from_url(url: &str) -> Result { + let resp = get_url(url).await?; + + let text = resp.text().await?; + Ok(text) +} + +pub async fn bytes_from_url(url: &str) -> Result> { + let resp = get_url(url).await?; + + let bytes = resp.bytes().await?; + Ok(bytes.to_vec()) +} From a9a63f36adb05e1b447d7a5a3edd9f120f2f3c26 Mon Sep 17 00:00:00 2001 From: seth Date: Wed, 27 Mar 2024 18:55:31 -0400 Subject: [PATCH 64/90] treewide: allow for running w/o storage --- .env.example | 3 -- .envrc | 9 ++---- src/api/github.rs | 35 ++++++++++++++++++++ src/api/mod.rs | 1 + src/commands/general/say.rs | 12 ++++++- src/commands/general/stars.rs | 25 +++++++-------- src/config/bot.rs | 28 ++++++++++++++++ src/config/discord.rs | 2 +- src/config/mod.rs | 39 +++++++++++++---------- src/handlers/event/analyze_logs/issues.rs | 23 ++++++------- src/handlers/event/mod.rs | 16 ++++++---- src/handlers/event/pluralkit.rs | 6 ++-- src/main.rs | 39 +++++++++++------------ src/storage/mod.rs | 26 +++++++++++++-- 14 files changed, 174 insertions(+), 90 deletions(-) delete mode 100644 .env.example create mode 100644 src/api/github.rs create mode 100644 src/config/bot.rs diff --git a/.env.example b/.env.example deleted file mode 100644 index 9b080a1..0000000 --- a/.env.example +++ /dev/null @@ -1,3 +0,0 @@ -DISCORD_TOKEN= -SAY_LOGS_CHANNEL= -RUST_LOG=refraction=info,warn,error diff --git a/.envrc b/.envrc index 0475d40..1d67506 100644 --- a/.envrc +++ b/.envrc @@ -1,10 +1,5 @@ -# only use flake when `nix` is present -if command -v nix &> /dev/null; then - if ! has nix_direnv_version || ! nix_direnv_version 2.2.1; then - source_url "https://raw.githubusercontent.com/nix-community/nix-direnv/2.2.1/direnvrc" "sha256-zelF0vLbEl5uaqrfIzbgNzJWGmLzCmYAkInj/LNxvKs=" - fi - - use flake +if has nix_direnv_version; then + use flake fi dotenv_if_exists diff --git a/src/api/github.rs b/src/api/github.rs new file mode 100644 index 0000000..18fce69 --- /dev/null +++ b/src/api/github.rs @@ -0,0 +1,35 @@ +use std::sync::Arc; + +use eyre::{Context, OptionExt, Result}; +use log::debug; +use octocrab::Octocrab; +use once_cell::sync::Lazy; + +static OCTOCRAB: Lazy> = Lazy::new(octocrab::instance); + +pub async fn get_latest_prism_version() -> Result { + debug!("Fetching the latest version of Prism Launcher"); + + let version = OCTOCRAB + .repos("PrismLauncher", "PrismLauncher") + .releases() + .get_latest() + .await? + .tag_name; + + Ok(version) +} + +pub async fn get_prism_stargazers_count() -> Result { + debug!("Fetching Prism Launcher's stargazer count"); + + let stargazers_count = OCTOCRAB + .repos("PrismLauncher", "PrismLauncher") + .get() + .await + .wrap_err("Couldn't fetch PrismLauncher/PrismLauncher!")? + .stargazers_count + .ok_or_eyre("Couldn't retrieve stargazers_coutn from GitHub!")?; + + Ok(stargazers_count) +} diff --git a/src/api/mod.rs b/src/api/mod.rs index a32d14b..649be3e 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -1,6 +1,7 @@ use once_cell::sync::Lazy; pub mod dadjoke; +pub mod github; pub mod paste_gg; pub mod pluralkit; pub mod prism_meta; diff --git a/src/commands/general/say.rs b/src/commands/general/say.rs index afeee0d..ebb754c 100644 --- a/src/commands/general/say.rs +++ b/src/commands/general/say.rs @@ -1,6 +1,7 @@ use crate::Context; use eyre::{OptionExt, Result}; +use log::trace; use poise::serenity_prelude::{CreateEmbed, CreateEmbedAuthor, CreateMessage}; /// Say something through the bot @@ -34,7 +35,14 @@ pub async fn say( ctx.say("I said what you said!").await?; } - if let Some(channel_id) = ctx.data().config.discord.channels().say_log_channel_id() { + if let Some(channel_id) = ctx + .data() + .config + .clone() + .discord_config() + .channels() + .say_log_channel_id() + { let log_channel = guild .channels .iter() @@ -54,6 +62,8 @@ pub async fn say( let message = CreateMessage::new().embed(embed); log_channel.1.send_message(ctx, message).await?; + } else { + trace!("Not sending /say log as no channel is set"); } Ok(()) diff --git a/src/commands/general/stars.rs b/src/commands/general/stars.rs index 7cbbd9b..710fa5d 100644 --- a/src/commands/general/stars.rs +++ b/src/commands/general/stars.rs @@ -1,6 +1,6 @@ -use crate::{consts, Context}; +use crate::{api, consts, Context}; -use eyre::{Context as _, Result}; +use eyre::Result; use log::trace; use poise::serenity_prelude::CreateEmbed; use poise::CreateReply; @@ -12,18 +12,17 @@ pub async fn stars(ctx: Context<'_>) -> Result<()> { ctx.defer().await?; - let prismlauncher = ctx - .data() - .octocrab - .repos("PrismLauncher", "PrismLauncher") - .get() - .await - .wrap_err("Couldn't get PrismLauncher/PrismLauncher from GitHub!")?; - - let count = if let Some(count) = prismlauncher.stargazers_count { - count.to_string() + let count = if let Some(storage) = &ctx.data().storage { + if let Ok(count) = storage.get_launcher_stargazer_count().await { + count + } else { + let count = api::github::get_prism_stargazers_count().await?; + storage.cache_launcher_stargazer_count(count).await?; + count + } } else { - "undefined".to_string() + trace!("Not caching launcher stargazer count, as we're running without a storage backend"); + api::github::get_prism_stargazers_count().await? }; let embed = CreateEmbed::new() diff --git a/src/config/bot.rs b/src/config/bot.rs new file mode 100644 index 0000000..7eee8eb --- /dev/null +++ b/src/config/bot.rs @@ -0,0 +1,28 @@ +use log::{info, warn}; + +#[derive(Clone, Debug, Default)] +pub struct Config { + redis_url: Option, +} + +impl Config { + pub fn new(redis_url: Option) -> Self { + Self { redis_url } + } + + pub fn new_from_env() -> Self { + let redis_url = std::env::var("BOT_REDIS_URL").ok(); + + if let Some(url) = &redis_url { + info!("Redis URL is {url}"); + } else { + warn!("BOT_REDIS_URL is empty; features requiring storage will be disabled."); + } + + Self::new(redis_url) + } + + pub fn redis_url(self) -> Option { + self.redis_url + } +} diff --git a/src/config/discord.rs b/src/config/discord.rs index f5f9879..e1051ee 100644 --- a/src/config/discord.rs +++ b/src/config/discord.rs @@ -8,7 +8,7 @@ pub struct RefractionChannels { say_log_channel_id: Option, } -#[derive(Clone, Copy, Debug, Default)] +#[derive(Clone, Debug, Default)] pub struct Config { channels: RefractionChannels, } diff --git a/src/config/mod.rs b/src/config/mod.rs index 11bcf09..416472b 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -1,27 +1,32 @@ +mod bot; mod discord; -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Default)] pub struct Config { - pub discord: discord::Config, - pub redis_url: String, -} - -impl Default for Config { - fn default() -> Self { - Self { - discord: discord::Config::default(), - redis_url: "redis://localhost:6379".to_string(), - } - } + bot: bot::Config, + discord: discord::Config, } impl Config { - pub fn new_from_env() -> Self { - let discord = discord::Config::new_from_env(); - + pub fn new(bot_config: bot::Config, discord_config: discord::Config) -> Self { Self { - discord, - ..Default::default() + bot: bot_config, + discord: discord_config, } } + + pub fn new_from_env() -> Self { + let bot = bot::Config::new_from_env(); + let discord = discord::Config::new_from_env(); + + Self::new(bot, discord) + } + + pub fn bot_config(self) -> bot::Config { + self.bot + } + + pub fn discord_config(self) -> discord::Config { + self.discord + } } diff --git a/src/handlers/event/analyze_logs/issues.rs b/src/handlers/event/analyze_logs/issues.rs index d838e88..50822c1 100644 --- a/src/handlers/event/analyze_logs/issues.rs +++ b/src/handlers/event/analyze_logs/issues.rs @@ -1,4 +1,4 @@ -use crate::Data; +use crate::{api, Data}; use eyre::Result; use log::trace; @@ -189,20 +189,15 @@ async fn outdated_launcher(log: &str, data: &Data) -> Result { let version_from_log = captures[0].replace("Prism Launcher version: ", ""); - let storage = &data.storage; - let latest_version = if let Ok(version) = storage.get_launcher_version().await { - version + let latest_version = if let Some(storage) = &data.storage { + if let Ok(version) = storage.get_launcher_version().await { + version + } else { + api::github::get_latest_prism_version().await? + } } else { - let version = data - .octocrab - .repos("PrismLauncher", "PrismLauncher") - .releases() - .get_latest() - .await? - .tag_name; - - storage.cache_launcher_version(&version).await?; - version + trace!("Not caching launcher version, as we're running without a storage backend"); + api::github::get_latest_prism_version().await? }; if version_from_log < latest_version { diff --git a/src/handlers/event/mod.rs b/src/handlers/event/mod.rs index 7eff42f..33d6193 100644 --- a/src/handlers/event/mod.rs +++ b/src/handlers/event/mod.rs @@ -41,14 +41,16 @@ pub async fn handle( return Ok(()); } - // detect PK users first to make sure we don't respond to unproxied messages - pluralkit::handle(ctx, new_message, data).await?; + if let Some(storage) = &data.storage { + // detect PK users first to make sure we don't respond to unproxied messages + pluralkit::handle(ctx, new_message, storage).await?; - if data.storage.is_user_plural(new_message.author.id).await? - && pluralkit::is_message_proxied(new_message).await? - { - debug!("Not replying to unproxied PluralKit message"); - return Ok(()); + if storage.is_user_plural(new_message.author.id).await? + && pluralkit::is_message_proxied(new_message).await? + { + debug!("Not replying to unproxied PluralKit message"); + return Ok(()); + } } eta::handle(ctx, new_message).await?; diff --git a/src/handlers/event/pluralkit.rs b/src/handlers/event/pluralkit.rs index ee8977a..27bdba1 100644 --- a/src/handlers/event/pluralkit.rs +++ b/src/handlers/event/pluralkit.rs @@ -1,4 +1,4 @@ -use crate::{api, Data}; +use crate::{api, storage::Storage}; use std::time::Duration; use eyre::Result; @@ -20,7 +20,7 @@ pub async fn is_message_proxied(message: &Message) -> Result { Ok(proxied) } -pub async fn handle(_: &Context, msg: &Message, data: &Data) -> Result<()> { +pub async fn handle(_: &Context, msg: &Message, storage: &Storage) -> Result<()> { if msg.webhook_id.is_none() { return Ok(()); } @@ -37,7 +37,7 @@ pub async fn handle(_: &Context, msg: &Message, data: &Data) -> Result<()> { sleep(PK_DELAY).await; if let Ok(sender) = api::pluralkit::get_sender(msg.id).await { - data.storage.store_user_plurality(sender).await?; + storage.store_user_plurality(sender).await?; } Ok(()) diff --git a/src/main.rs b/src/main.rs index 93d7e5b..893250d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,16 +4,14 @@ use std::{sync::Arc, time::Duration}; -use eyre::{eyre, Context as _, Report, Result}; +use eyre::{bail, Context as _, Report, Result}; use log::{info, trace, warn}; -use octocrab::Octocrab; use poise::{ serenity_prelude as serenity, EditTracker, Framework, FrameworkOptions, PrefixFrameworkOptions, }; use owo_colors::OwoColorize; -use redis::ConnectionLike; use tokio::signal::ctrl_c; #[cfg(target_family = "unix")] @@ -38,17 +36,13 @@ type Context<'a> = poise::Context<'a, Data, Report>; #[derive(Clone)] pub struct Data { config: Config, - storage: Storage, - octocrab: Arc, + storage: Option, } impl Data { - pub fn new(config: Config, storage: Storage, octocrab: Arc) -> Result { - Ok(Self { - config, - storage, - octocrab, - }) + #[must_use] + pub fn new(config: Config, storage: Option) -> Self { + Self { config, storage } } } @@ -58,19 +52,22 @@ async fn setup( framework: &Framework, ) -> Result { let config = Config::new_from_env(); - let storage = Storage::from_url(&config.redis_url)?; - let octocrab = octocrab::instance(); - let data = Data::new(config, storage, octocrab)?; - // test redis connection - let mut client = data.storage.client().clone(); + let storage = if let Some(url) = &config.clone().bot_config().redis_url() { + Some(Storage::from_url(url)?) + } else { + None + }; - if !client.check_connection() { - return Err(eyre!( - "Couldn't connect to storage! Is your daemon running?" - )); + if let Some(storage) = storage.as_ref() { + if !storage.clone().has_connection() { + bail!("You specified a storage backend but there's no connection! Is it running?") + } + + trace!("Redis connection looks good!"); } - trace!("Redis connection looks good!"); + + let data = Data::new(config, storage); poise::builtins::register_globally(ctx, &framework.options().commands).await?; info!("Registered global commands!"); diff --git a/src/storage/mod.rs b/src/storage/mod.rs index af2b5f5..4d53855 100644 --- a/src/storage/mod.rs +++ b/src/storage/mod.rs @@ -3,10 +3,11 @@ use std::fmt::Debug; use eyre::Result; use log::debug; use poise::serenity_prelude::UserId; -use redis::{AsyncCommands, Client}; +use redis::{AsyncCommands, Client, ConnectionLike}; const PK_KEY: &str = "pluralkit-v1"; const LAUNCHER_VERSION_KEY: &str = "launcher-version-v1"; +const LAUNCHER_STARGAZER_KEY: &str = "launcher-stargazer-v1"; #[derive(Clone, Debug)] pub struct Storage { @@ -24,8 +25,8 @@ impl Storage { Ok(Self::new(client)) } - pub fn client(&self) -> &Client { - &self.client + pub fn has_connection(mut self) -> bool { + self.client.check_connection() } pub async fn store_user_plurality(&self, sender: UserId) -> Result<()> { @@ -67,4 +68,23 @@ impl Storage { Ok(res) } + + pub async fn cache_launcher_stargazer_count(&self, stargazers: u32) -> Result<()> { + debug!("Caching stargazer count as {stargazers}"); + + let mut con = self.client.get_async_connection().await?; + con.set_ex(LAUNCHER_STARGAZER_KEY, stargazers, 60 * 60) + .await?; + + Ok(()) + } + + pub async fn get_launcher_stargazer_count(&self) -> Result { + debug!("Fetching launcher stargazer count"); + + let mut con = self.client.get_async_connection().await?; + let res: u32 = con.get(LAUNCHER_STARGAZER_KEY).await?; + + Ok(res) + } } From 4e11bd2bd7ec427eb5ac8d1790abfe747de0ed0b Mon Sep 17 00:00:00 2001 From: seth Date: Wed, 27 Mar 2024 18:56:22 -0400 Subject: [PATCH 65/90] analyze_logs: fix 0x0 regex --- src/handlers/event/analyze_logs/providers/0x0.rs | 2 +- src/handlers/event/analyze_logs/providers/mod.rs | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/handlers/event/analyze_logs/providers/0x0.rs b/src/handlers/event/analyze_logs/providers/0x0.rs index 9c76a40..39b715c 100644 --- a/src/handlers/event/analyze_logs/providers/0x0.rs +++ b/src/handlers/event/analyze_logs/providers/0x0.rs @@ -10,7 +10,7 @@ pub struct _0x0; impl super::LogProvider for _0x0 { async fn find_match(&self, message: &Message) -> Option { - static REGEX: Lazy = Lazy::new(|| Regex::new(r"https://0x0\.st/\w*\.\w*").unwrap()); + static REGEX: Lazy = Lazy::new(|| Regex::new(r"https://0x0\.st/\w*.\w*").unwrap()); trace!("Checking if message {} is a 0x0 paste", message.id); REGEX diff --git a/src/handlers/event/analyze_logs/providers/mod.rs b/src/handlers/event/analyze_logs/providers/mod.rs index f8f096c..679f944 100644 --- a/src/handlers/event/analyze_logs/providers/mod.rs +++ b/src/handlers/event/analyze_logs/providers/mod.rs @@ -28,8 +28,7 @@ pub trait LogProvider { fn get_first_capture(regex: &Lazy, string: &str) -> Option { regex .captures_iter(string) - .filter_map(|c| c.get(1).map(|c| c.as_str().to_string())) - .nth(1) + .find_map(|c| c.get(1).map(|c| c.as_str().to_string())) } #[enum_dispatch(LogProvider)] @@ -66,5 +65,5 @@ pub async fn find_log(message: &Message) -> Result> { } } - todo!() + Ok(None) } From 9f8233709be6471173562990d4e823cc1e38278b Mon Sep 17 00:00:00 2001 From: seth Date: Wed, 27 Mar 2024 19:08:43 -0400 Subject: [PATCH 66/90] nix: use correct env var for redis url --- nix/deployment/module.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nix/deployment/module.nix b/nix/deployment/module.nix index eccdbc2..46155d7 100644 --- a/nix/deployment/module.nix +++ b/nix/deployment/module.nix @@ -96,7 +96,7 @@ in { ''; environment = { - REDIS_URL = + BOT_REDIS_URL = if cfg.redisUrl == "local" then "unix:${config.services.redis.servers.refraction.unixSocket}" else cfg.redisUrl; From 239928a22a940bb7026f274d4d2c6e730abd230a Mon Sep 17 00:00:00 2001 From: seth Date: Wed, 27 Mar 2024 19:17:10 -0400 Subject: [PATCH 67/90] crates: bump octocrab, redis, & reqwest --- Cargo.lock | 367 ++++++++++++++++++++++++++++++++++----------- Cargo.toml | 6 +- src/storage/mod.rs | 12 +- 3 files changed, 287 insertions(+), 98 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c74d784..ba57f24 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -142,6 +142,12 @@ version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9" +[[package]] +name = "base64" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9475866fec1451be56a3c2400fd081ff546538961565ccb5b7142cbd22bc7a51" + [[package]] name = "bitflags" version = "1.3.2" @@ -438,12 +444,6 @@ dependencies = [ "crypto-common", ] -[[package]] -name = "doc-comment" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" - [[package]] name = "dotenvy" version = "0.15.7" @@ -722,7 +722,7 @@ dependencies = [ "futures-core", "futures-sink", "futures-util", - "http", + "http 0.2.11", "indexmap", "slab", "tokio", @@ -759,6 +759,17 @@ dependencies = [ "itoa", ] +[[package]] +name = "http" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + [[package]] name = "http-body" version = "0.4.6" @@ -766,15 +777,32 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" dependencies = [ "bytes", - "http", + "http 0.2.11", "pin-project-lite", ] [[package]] -name = "http-range-header" -version = "0.3.1" +name = "http-body" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "add0ab9360ddbd88cfeb3bd9574a1d85cfdfa14db10b3e21d3700dbc4328758f" +checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" +dependencies = [ + "bytes", + "http 1.1.0", +] + +[[package]] +name = "http-body-util" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0475f8b2ac86659c21b64320d5d653f9efe42acd2a4e560073ec61a155a34f1d" +dependencies = [ + "bytes", + "futures-core", + "http 1.1.0", + "http-body 1.0.0", + "pin-project-lite", +] [[package]] name = "httparse" @@ -805,8 +833,8 @@ dependencies = [ "futures-core", "futures-util", "h2", - "http", - "http-body", + "http 0.2.11", + "http-body 0.4.6", "httparse", "httpdate", "itoa", @@ -818,6 +846,25 @@ dependencies = [ "want", ] +[[package]] +name = "hyper" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "186548d73ac615b32a73aafe38fb4f56c0d340e110e5a200bcadbaf2e199263a" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http 1.1.0", + "http-body 1.0.0", + "httparse", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + [[package]] name = "hyper-rustls" version = "0.24.2" @@ -825,25 +872,63 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" dependencies = [ "futures-util", - "http", - "hyper", - "log", - "rustls", - "rustls-native-certs", + "http 0.2.11", + "hyper 0.14.27", + "rustls 0.21.10", "tokio", - "tokio-rustls", + "tokio-rustls 0.24.1", +] + +[[package]] +name = "hyper-rustls" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0bea761b46ae2b24eb4aef630d8d1c398157b6fc29e6350ecf090a0b70c952c" +dependencies = [ + "futures-util", + "http 1.1.0", + "hyper 1.2.0", + "hyper-util", + "log", + "rustls 0.22.3", + "rustls-native-certs", + "rustls-pki-types", + "tokio", + "tokio-rustls 0.25.0", + "tower-service", ] [[package]] name = "hyper-timeout" -version = "0.4.1" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" +checksum = "3203a961e5c83b6f5498933e78b6b263e208c197b63e9c6c53cc82ffd3f63793" dependencies = [ - "hyper", + "hyper 1.2.0", + "hyper-util", "pin-project-lite", "tokio", - "tokio-io-timeout", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca38ef113da30126bbff9cd1705f9273e15d45498615d138b0c20279ac7a76aa" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http 1.1.0", + "http-body 1.0.0", + "hyper 1.2.0", + "pin-project-lite", + "socket2 0.5.5", + "tokio", + "tower", + "tower-service", + "tracing", ] [[package]] @@ -938,7 +1023,7 @@ version = "9.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c7ea04a7c5c055c175f189b6dc6ba036fd62306b58c66c9f6389036c503a3f4" dependencies = [ - "base64", + "base64 0.21.5", "js-sys", "pem", "ring", @@ -1095,24 +1180,26 @@ dependencies = [ [[package]] name = "octocrab" -version = "0.33.3" +version = "0.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "054a8bf47dfa8f89bb0dcf17e485e9f49f2586c05bf0aa67b8ec5a9e7bc8dfd5" +checksum = "dedddd64be5eb5ea9311d1934a09ce32ef9be22fd29990bb2a5552c17712306e" dependencies = [ "arc-swap", "async-trait", - "base64", + "base64 0.22.0", "bytes", "cfg-if", "chrono", "either", "futures", "futures-util", - "http", - "http-body", - "hyper", - "hyper-rustls", + "http 1.1.0", + "http-body 1.0.0", + "http-body-util", + "hyper 1.2.0", + "hyper-rustls 0.26.0", "hyper-timeout", + "hyper-util", "jsonwebtoken", "once_cell", "percent-encoding", @@ -1183,7 +1270,7 @@ version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b8fcc794035347fb64beda2d3b462595dd2753e3f268d89c5aae77e8cf2c310" dependencies = [ - "base64", + "base64 0.21.5", "serde", ] @@ -1342,9 +1429,9 @@ dependencies = [ [[package]] name = "redis" -version = "0.24.0" +version = "0.25.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c580d9cbbe1d1b479e8d67cf9daf6a62c957e6846048408b80b43ac3f6af84cd" +checksum = "71d64e978fd98a0e6b105d066ba4889a7301fca65aeac850a877d8797343feeb" dependencies = [ "async-trait", "bytes", @@ -1353,15 +1440,15 @@ dependencies = [ "itoa", "percent-encoding", "pin-project-lite", - "rustls", + "rustls 0.22.3", "rustls-native-certs", - "rustls-pemfile", - "rustls-webpki", + "rustls-pemfile 2.1.1", + "rustls-pki-types", "ryu", "sha1_smol", - "socket2 0.4.10", + "socket2 0.5.5", "tokio", - "tokio-rustls", + "tokio-rustls 0.25.0", "tokio-util", "url", ] @@ -1414,10 +1501,10 @@ dependencies = [ "owo-colors 4.0.0", "poise", "rand", - "redis 0.24.0", + "redis 0.25.2", "redis-macros", "regex", - "reqwest", + "reqwest 0.12.2", "serde", "serde_json", "tokio", @@ -1459,16 +1546,16 @@ version = "0.11.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37b1ae8d9ac08420c66222fb9096fc5de435c3c48542bc5336c51892cffafb41" dependencies = [ - "base64", + "base64 0.21.5", "bytes", "encoding_rs", "futures-core", "futures-util", "h2", - "http", - "http-body", - "hyper", - "hyper-rustls", + "http 0.2.11", + "http-body 0.4.6", + "hyper 0.14.27", + "hyper-rustls 0.24.2", "ipnet", "js-sys", "log", @@ -1477,14 +1564,14 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", - "rustls", - "rustls-pemfile", + "rustls 0.21.10", + "rustls-pemfile 1.0.4", "serde", "serde_json", "serde_urlencoded", "system-configuration", "tokio", - "tokio-rustls", + "tokio-rustls 0.24.1", "tokio-util", "tower-service", "url", @@ -1492,7 +1579,48 @@ dependencies = [ "wasm-bindgen-futures", "wasm-streams", "web-sys", - "webpki-roots", + "webpki-roots 0.25.3", + "winreg", +] + +[[package]] +name = "reqwest" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d66674f2b6fb864665eea7a3c1ac4e3dfacd2fda83cf6f935a612e01b0e3338" +dependencies = [ + "base64 0.21.5", + "bytes", + "futures-core", + "futures-util", + "http 1.1.0", + "http-body 1.0.0", + "http-body-util", + "hyper 1.2.0", + "hyper-rustls 0.26.0", + "hyper-util", + "ipnet", + "js-sys", + "log", + "mime", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls 0.22.3", + "rustls-pemfile 1.0.4", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-rustls 0.25.0", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "webpki-roots 0.26.1", "winreg", ] @@ -1537,18 +1665,33 @@ checksum = "f9d5a6813c0759e4609cd494e8e725babae6a2ca7b62a5536a13daaec6fcb7ba" dependencies = [ "log", "ring", - "rustls-webpki", + "rustls-webpki 0.101.7", "sct", ] [[package]] -name = "rustls-native-certs" -version = "0.6.3" +name = "rustls" +version = "0.22.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00" +checksum = "99008d7ad0bbbea527ec27bddbc0e432c5b87d8175178cee68d2eec9c4a1813c" +dependencies = [ + "log", + "ring", + "rustls-pki-types", + "rustls-webpki 0.102.2", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1fb85efa936c42c6d5fc28d2629bb51e4b2f4b8a5211e297d599cc5a093792" dependencies = [ "openssl-probe", - "rustls-pemfile", + "rustls-pemfile 2.1.1", + "rustls-pki-types", "schannel", "security-framework", ] @@ -1559,9 +1702,25 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" dependencies = [ - "base64", + "base64 0.21.5", ] +[[package]] +name = "rustls-pemfile" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f48172685e6ff52a556baa527774f61fcaa884f59daf3375c62a3f1cd2549dab" +dependencies = [ + "base64 0.21.5", + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecd36cc4259e3e4514335c4a138c6b43171a8d61d8f5c9348f9fc7529416f247" + [[package]] name = "rustls-webpki" version = "0.101.7" @@ -1572,6 +1731,17 @@ dependencies = [ "untrusted", ] +[[package]] +name = "rustls-webpki" +version = "0.102.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faaa0a62740bedb9b2ef5afa303da42764c012f743917351dc9a237ea1663610" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + [[package]] name = "ryu" version = "1.0.16" @@ -1715,7 +1885,7 @@ checksum = "385647faa24a889929028973650a4f158fb1b4272b2fcf94feb9fcc3c009e813" dependencies = [ "arrayvec", "async-trait", - "base64", + "base64 0.21.5", "bitflags 2.4.1", "bytes", "chrono", @@ -1726,7 +1896,7 @@ dependencies = [ "mime_guess", "parking_lot", "percent-encoding", - "reqwest", + "reqwest 0.11.23", "secrecy", "serde", "serde_json", @@ -1812,31 +1982,29 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.11.2" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "snafu" -version = "0.7.5" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4de37ad025c587a29e8f3f5605c00f70b98715ef90b9061a815b9e59e9042d6" +checksum = "75976f4748ab44f6e5332102be424e7c2dc18daeaf7e725f2040c3ebb133512e" dependencies = [ - "backtrace", - "doc-comment", "snafu-derive", ] [[package]] name = "snafu-derive" -version = "0.7.5" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "990079665f075b699031e9c08fd3ab99be5029b96f3b78dc0709e8f77e4efebf" +checksum = "b4b19911debfb8c2fb1107bc6cb2d61868aaf53a988449213959bb1b5b1ed95f" dependencies = [ "heck", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.48", ] [[package]] @@ -1871,6 +2039,12 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +[[package]] +name = "subtle" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" + [[package]] name = "syn" version = "1.0.109" @@ -1893,6 +2067,12 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + [[package]] name = "system-configuration" version = "0.5.1" @@ -2025,16 +2205,6 @@ dependencies = [ "windows-sys 0.48.0", ] -[[package]] -name = "tokio-io-timeout" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30b74022ada614a1b4834de765f9bb43877f910cc8ce4be40e89042c9223a8bf" -dependencies = [ - "pin-project-lite", - "tokio", -] - [[package]] name = "tokio-macros" version = "2.2.0" @@ -2052,7 +2222,18 @@ version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" dependencies = [ - "rustls", + "rustls 0.21.10", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f" +dependencies = [ + "rustls 0.22.3", + "rustls-pki-types", "tokio", ] @@ -2064,11 +2245,11 @@ checksum = "212d5dcb2a1ce06d81107c3d0ffa3121fe974b73f068c8282cb1c32328113b6c" dependencies = [ "futures-util", "log", - "rustls", + "rustls 0.21.10", "tokio", - "tokio-rustls", + "tokio-rustls 0.24.1", "tungstenite", - "webpki-roots", + "webpki-roots 0.25.3", ] [[package]] @@ -2113,17 +2294,16 @@ dependencies = [ [[package]] name = "tower-http" -version = "0.4.4" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61c5bb1d698276a2443e5ecfabc1008bf15a36c12e6a7176e7bf089ea9131140" +checksum = "1e9cd434a998747dd2c4276bc96ee2e0c7a2eadf3cae88e52be55a05fa9053f5" dependencies = [ "bitflags 2.4.1", "bytes", - "futures-core", "futures-util", - "http", - "http-body", - "http-range-header", + "http 1.1.0", + "http-body 1.0.0", + "http-body-util", "iri-string", "pin-project-lite", "tower", @@ -2219,11 +2399,11 @@ dependencies = [ "byteorder", "bytes", "data-encoding", - "http", + "http 0.2.11", "httparse", "log", "rand", - "rustls", + "rustls 0.21.10", "sha1", "thiserror", "url", @@ -2463,6 +2643,15 @@ version = "0.25.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1778a42e8b3b90bff8d0f5032bf22250792889a5cdc752aa0020c84abe3aaf10" +[[package]] +name = "webpki-roots" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3de34ae270483955a94f4b21bdaaeb83d508bb84a01435f393818edb0012009" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "winapi" version = "0.3.9" diff --git a/Cargo.toml b/Cargo.toml index abcd751..6af8378 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,14 +22,14 @@ env_logger = "0.11.1" eyre = "0.6.11" log = "0.4.20" poise = "0.6.1" -octocrab = "0.33.3" +octocrab = "0.37.0" once_cell = "1.19.0" owo-colors = "4.0.0" rand = "0.8.5" -redis = { version = "0.24.0", features = ["tokio-comp", "tokio-rustls-comp"] } +redis = { version = "0.25.2", features = ["tokio-comp", "tokio-rustls-comp"] } redis-macros = "0.2.1" regex = "1.10.3" -reqwest = { version = "0.11.23", default-features = false, features = [ +reqwest = { version = "0.12.2", default-features = false, features = [ "rustls-tls", "json", ] } diff --git a/src/storage/mod.rs b/src/storage/mod.rs index 4d53855..6166276 100644 --- a/src/storage/mod.rs +++ b/src/storage/mod.rs @@ -33,7 +33,7 @@ impl Storage { debug!("Marking {sender} as a PluralKit user"); let key = format!("{PK_KEY}:{sender}"); - let mut con = self.client.get_async_connection().await?; + let mut con = self.client.get_multiplexed_async_connection().await?; // Just store some value. We only care about the presence of this key con.set_ex(key, 0, 7 * 24 * 60 * 60).await?; // 1 week @@ -44,7 +44,7 @@ impl Storage { debug!("Checking if user {user_id} is plural"); let key = format!("{PK_KEY}:{user_id}"); - let mut con = self.client.get_async_connection().await?; + let mut con = self.client.get_multiplexed_async_connection().await?; let exists = con.exists(key).await?; Ok(exists) @@ -53,7 +53,7 @@ impl Storage { pub async fn cache_launcher_version(&self, version: &str) -> Result<()> { debug!("Caching launcher version as {version}"); - let mut con = self.client.get_async_connection().await?; + let mut con = self.client.get_multiplexed_async_connection().await?; con.set_ex(LAUNCHER_VERSION_KEY, version, 24 * 60 * 60) .await?; // 1 day @@ -63,7 +63,7 @@ impl Storage { pub async fn get_launcher_version(&self) -> Result { debug!("Fetching launcher version"); - let mut con = self.client.get_async_connection().await?; + let mut con = self.client.get_multiplexed_async_connection().await?; let res = con.get(LAUNCHER_VERSION_KEY).await?; Ok(res) @@ -72,7 +72,7 @@ impl Storage { pub async fn cache_launcher_stargazer_count(&self, stargazers: u32) -> Result<()> { debug!("Caching stargazer count as {stargazers}"); - let mut con = self.client.get_async_connection().await?; + let mut con = self.client.get_multiplexed_async_connection().await?; con.set_ex(LAUNCHER_STARGAZER_KEY, stargazers, 60 * 60) .await?; @@ -82,7 +82,7 @@ impl Storage { pub async fn get_launcher_stargazer_count(&self) -> Result { debug!("Fetching launcher stargazer count"); - let mut con = self.client.get_async_connection().await?; + let mut con = self.client.get_multiplexed_async_connection().await?; let res: u32 = con.get(LAUNCHER_STARGAZER_KEY).await?; Ok(res) From 59bf42998be9ac2d122ceaa0f724515b8cf1bf27 Mon Sep 17 00:00:00 2001 From: seth Date: Fri, 29 Mar 2024 17:54:53 -0400 Subject: [PATCH 68/90] feat: add /set_welcome command --- src/commands/general/mod.rs | 25 +-- src/commands/general/say.rs | 21 +-- src/commands/mod.rs | 45 +++++- src/commands/moderation/mod.rs | 1 + src/commands/moderation/set_welcome.rs | 203 +++++++++++++++++++++++++ src/config/discord.rs | 34 +++-- src/utils/mod.rs | 8 + 7 files changed, 286 insertions(+), 51 deletions(-) create mode 100644 src/commands/moderation/mod.rs create mode 100644 src/commands/moderation/set_welcome.rs diff --git a/src/commands/general/mod.rs b/src/commands/general/mod.rs index 050caff..7e2d92f 100644 --- a/src/commands/general/mod.rs +++ b/src/commands/general/mod.rs @@ -1,17 +1,8 @@ -mod help; -mod joke; -mod members; -mod ping; -mod rory; -mod say; -mod stars; -mod tag; - -pub use help::help; -pub use joke::joke; -pub use members::members; -pub use ping::ping; -pub use rory::rory; -pub use say::say; -pub use stars::stars; -pub use tag::tag; +pub mod help; +pub mod joke; +pub mod members; +pub mod ping; +pub mod rory; +pub mod say; +pub mod stars; +pub mod tag; diff --git a/src/commands/general/say.rs b/src/commands/general/say.rs index ebb754c..5979c3c 100644 --- a/src/commands/general/say.rs +++ b/src/commands/general/say.rs @@ -1,8 +1,8 @@ -use crate::Context; +use crate::{utils, Context}; use eyre::{OptionExt, Result}; use log::trace; -use poise::serenity_prelude::{CreateEmbed, CreateEmbedAuthor, CreateMessage}; +use poise::serenity_prelude::{CreateEmbed, CreateMessage}; /// Say something through the bot #[poise::command( @@ -17,7 +17,6 @@ pub async fn say( ctx: Context<'_>, #[description = "the message content"] content: String, ) -> Result<()> { - let guild = ctx.guild().ok_or_eyre("Couldn't get guild!")?.to_owned(); let channel = ctx .guild_channel() .await @@ -41,19 +40,9 @@ pub async fn say( .clone() .discord_config() .channels() - .say_log_channel_id() + .log_channel_id() { - let log_channel = guild - .channels - .iter() - .find(|c| c.0 == &channel_id) - .ok_or_eyre("Couldn't get log channel from guild!")?; - - let author = CreateEmbedAuthor::new(ctx.author().tag()).icon_url( - ctx.author() - .avatar_url() - .unwrap_or_else(|| ctx.author().default_avatar_url()), - ); + let author = utils::embed_author_from_user(ctx.author()); let embed = CreateEmbed::default() .title("Say command used!") @@ -61,7 +50,7 @@ pub async fn say( .author(author); let message = CreateMessage::new().embed(embed); - log_channel.1.send_message(ctx, message).await?; + channel_id.send_message(ctx, message).await?; } else { trace!("Not sending /say log as no channel is set"); } diff --git a/src/commands/mod.rs b/src/commands/mod.rs index b8f7418..33216d3 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -3,18 +3,47 @@ use crate::Data; use eyre::Report; mod general; +mod moderation; + +macro_rules! command { + ($module: ident, $name: ident) => { + $module::$name::$name() + }; + + ($module: ident, $name: ident, $func: ident) => { + $module::$name::$func() + }; +} + +macro_rules! module_macro { + ($module: ident) => { + macro_rules! $module { + ($name: ident) => { + command!($module, $name) + }; + + ($name: ident, $func: ident) => { + command!($module, $name, $func) + }; + } + }; +} + +module_macro!(general); +module_macro!(moderation); pub type Command = poise::Command; pub fn get() -> Vec { vec![ - general::joke(), - general::members(), - general::ping(), - general::rory(), - general::say(), - general::stars(), - general::tag(), - general::help(), + general!(help), + general!(joke), + general!(members), + general!(ping), + general!(rory), + general!(say), + general!(stars), + general!(tag), + moderation!(set_welcome), ] } diff --git a/src/commands/moderation/mod.rs b/src/commands/moderation/mod.rs new file mode 100644 index 0000000..d5578a0 --- /dev/null +++ b/src/commands/moderation/mod.rs @@ -0,0 +1 @@ +pub mod set_welcome; diff --git a/src/commands/moderation/set_welcome.rs b/src/commands/moderation/set_welcome.rs new file mode 100644 index 0000000..c5c7f03 --- /dev/null +++ b/src/commands/moderation/set_welcome.rs @@ -0,0 +1,203 @@ +use std::{fmt::Write, str::FromStr}; + +use crate::{utils, Context}; + +use eyre::{bail, Result}; +use log::trace; +use poise::serenity_prelude::{ + futures::StreamExt, Attachment, CreateActionRow, CreateButton, CreateEmbed, CreateMessage, + Mentionable, ReactionType, +}; +use serde::{Deserialize, Serialize}; +use url::Url; + +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(deny_unknown_fields)] +struct WelcomeEmbed { + title: String, + description: Option, + url: Option, + hex_color: Option, + image: Option, +} + +impl From for CreateMessage { + fn from(val: WelcomeEmbed) -> Self { + let mut embed = CreateEmbed::new(); + + embed = embed.title(val.title); + if let Some(description) = val.description { + embed = embed.description(description); + } + + if let Some(url) = val.url { + embed = embed.url(url); + } + + if let Some(color) = val.hex_color { + let hex = i32::from_str_radix(&color, 16).unwrap(); + embed = embed.color(hex); + } + + if let Some(image) = val.image { + embed = embed.image(image); + } + + Self::new().embed(embed) + } +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(deny_unknown_fields)] +struct WelcomeRole { + title: String, + id: u64, + emoji: Option, +} + +impl From for CreateButton { + fn from(value: WelcomeRole) -> Self { + let mut button = Self::new(value.id.to_string()).label(value.title); + if let Some(emoji) = value.emoji { + button = button.emoji(ReactionType::from_str(&emoji).unwrap()); + } + + button + } +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(deny_unknown_fields)] +struct WelcomeRoleCategory { + title: String, + description: Option, + roles: Vec, +} + +impl From for CreateMessage { + fn from(value: WelcomeRoleCategory) -> Self { + let mut content = format!("**{}**", value.title); + if let Some(description) = value.description { + write!(content, "\n{description}").ok(); + } + + let buttons: Vec = value + .roles + .iter() + .map(|role| CreateButton::from(role.clone())) + .collect(); + + let components = vec![CreateActionRow::Buttons(buttons)]; + Self::new().content(content).components(components) + } +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(deny_unknown_fields)] +struct WelcomeLayout { + embeds: Vec, + messages: Vec, + roles: Vec, +} + +/// Sets your welcome channel info +#[poise::command( + slash_command, + guild_only, + ephemeral, + default_member_permissions = "MANAGE_GUILD", + required_permissions = "MANAGE_GUILD" +)] +pub async fn set_welcome( + ctx: Context<'_>, + #[description = "A file to use"] file: Option, + #[description = "A URL for a file to use"] url: Option, +) -> Result<()> { + trace!("Running set_welcome command!"); + + let configured_channels = ctx.data().config.clone().discord_config().channels(); + let Some(channel_id) = configured_channels.welcome_channel_id() else { + ctx.say("You don't have a welcome channel ID set, so I can't do anything :(") + .await?; + return Ok(()); + }; + + ctx.defer_ephemeral().await?; + + // download attachment from discord or URL + let file = if let Some(attachment) = file { + let Some(content_type) = &attachment.content_type else { + bail!("Welcome channel attachment was sent without a content type!"); + }; + + if !content_type.starts_with("application/json;") { + trace!("Not attempting to read non-json content type {content_type}"); + ctx.say("Invalid file! Please only send json").await?; + return Ok(()); + } + + let downloaded = attachment.download().await?; + String::from_utf8(downloaded)? + } else if let Some(url) = url { + if Url::parse(&url).is_err() { + ctx.say("Invalid url!").await?; + return Ok(()); + } + + utils::text_from_url(&url).await? + } else { + ctx.say("A text file or URL must be provided!").await?; + return Ok(()); + }; + + // parse and create messages from file + let welcome_layout: WelcomeLayout = serde_json::from_str(&file)?; + let embed_messages: Vec = welcome_layout + .embeds + .iter() + .map(|e| CreateMessage::from(e.clone())) + .collect(); + let roles_messages: Vec = welcome_layout + .roles + .iter() + .map(|c| CreateMessage::from(c.clone())) + .collect(); + + // clear previous messages + let mut prev_messages = channel_id.messages_iter(ctx).boxed(); + while let Some(prev_message) = prev_messages.next().await { + if let Ok(message) = prev_message { + message.delete(ctx).await?; + } + } + + // send our new ones + for embed in embed_messages { + channel_id.send_message(ctx, embed).await?; + } + + for message in roles_messages { + channel_id.send_message(ctx, message).await?; + } + + for message in welcome_layout.messages { + channel_id.say(ctx, message).await?; + } + + if let Some(log_channel) = configured_channels.log_channel_id() { + let author = utils::embed_author_from_user(ctx.author()); + let embed = CreateEmbed::new() + .title("set_welcome command used!") + .author(author); + let message = CreateMessage::new().embed(embed); + + log_channel.send_message(ctx, message).await?; + } else { + trace!("Not sending /set_welcome log as no channel is set"); + } + + ctx.reply(format!("Updated {}!", channel_id.mention())) + .await?; + + Ok(()) +} diff --git a/src/config/discord.rs b/src/config/discord.rs index e1051ee..837c860 100644 --- a/src/config/discord.rs +++ b/src/config/discord.rs @@ -5,7 +5,8 @@ use poise::serenity_prelude::ChannelId; #[derive(Clone, Copy, Debug, Default)] pub struct RefractionChannels { - say_log_channel_id: Option, + log_channel_id: Option, + welcome_channel_id: Option, } #[derive(Clone, Debug, Default)] @@ -14,20 +15,29 @@ pub struct Config { } impl RefractionChannels { - pub fn new(say_log_channel_id: Option) -> Self { - Self { say_log_channel_id } + pub fn new(log_channel_id: Option, welcome_channel_id: Option) -> Self { + Self { + log_channel_id, + welcome_channel_id, + } } pub fn new_from_env() -> Self { - let say_log_channel_id = Self::get_channel_from_env("DISCORD_SAY_LOG_CHANNELID"); - - if let Some(channel_id) = say_log_channel_id { + let log_channel_id = Self::get_channel_from_env("DISCORD_LOG_CHANNEL_ID"); + if let Some(channel_id) = log_channel_id { info!("Log channel is {channel_id}"); } else { - warn!("DISCORD_SAY_LOG_CHANNELID is empty; this will disable logging in your server."); + warn!("DISCORD_LOG_CHANNEL_ID is empty; this will disable logging in your server."); } - Self::new(say_log_channel_id) + let welcome_channel_id = Self::get_channel_from_env("DISCORD_WELCOME_CHANNEL_ID"); + if let Some(channel_id) = welcome_channel_id { + info!("Welcome channel is {channel_id}"); + } else { + warn!("DISCORD_WELCOME_CHANNEL_ID is empty; this will disable welcome channel features in your server"); + } + + Self::new(log_channel_id, welcome_channel_id) } fn get_channel_from_env(var: &str) -> Option { @@ -36,8 +46,12 @@ impl RefractionChannels { .and_then(|env_var| ChannelId::from_str(&env_var).ok()) } - pub fn say_log_channel_id(self) -> Option { - self.say_log_channel_id + pub fn log_channel_id(self) -> Option { + self.log_channel_id + } + + pub fn welcome_channel_id(self) -> Option { + self.welcome_channel_id } } diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 64172ca..62aa051 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -2,6 +2,7 @@ use crate::api::REQWEST_CLIENT; use eyre::Result; use log::debug; +use poise::serenity_prelude::{CreateEmbedAuthor, User}; use reqwest::Response; pub mod resolve_message; @@ -27,3 +28,10 @@ pub async fn bytes_from_url(url: &str) -> Result> { let bytes = resp.bytes().await?; Ok(bytes.to_vec()) } + +pub fn embed_author_from_user(user: &User) -> CreateEmbedAuthor { + CreateEmbedAuthor::new(user.tag()).icon_url( + user.avatar_url() + .unwrap_or_else(|| user.default_avatar_url()), + ) +} From a41a84fd2d32f973f2ce4d35b4726a6b2f6e7e60 Mon Sep 17 00:00:00 2001 From: seth Date: Fri, 29 Mar 2024 18:00:30 -0400 Subject: [PATCH 69/90] say: use channel_id, only use prefix command --- src/commands/general/say.rs | 22 ++++------------------ 1 file changed, 4 insertions(+), 18 deletions(-) diff --git a/src/commands/general/say.rs b/src/commands/general/say.rs index 5979c3c..1703fe3 100644 --- a/src/commands/general/say.rs +++ b/src/commands/general/say.rs @@ -1,38 +1,24 @@ use crate::{utils, Context}; -use eyre::{OptionExt, Result}; +use eyre::Result; use log::trace; use poise::serenity_prelude::{CreateEmbed, CreateMessage}; /// Say something through the bot #[poise::command( slash_command, - prefix_command, ephemeral, default_member_permissions = "MODERATE_MEMBERS", required_permissions = "MODERATE_MEMBERS", - guild_only = true + guild_only )] pub async fn say( ctx: Context<'_>, #[description = "the message content"] content: String, ) -> Result<()> { - let channel = ctx - .guild_channel() - .await - .ok_or_eyre("Couldn't get channel!")?; - - if let Context::Prefix(prefix) = ctx { - // ignore error, we might not have perm - let _ = prefix.msg.delete(ctx).await; - } - - ctx.defer_ephemeral().await?; + let channel = ctx.channel_id(); channel.say(ctx, &content).await?; - - if let Context::Application(_) = ctx { - ctx.say("I said what you said!").await?; - } + ctx.say("I said what you said!").await?; if let Some(channel_id) = ctx .data() From 90387c5a3b54ecafcd921536f2565951bc6821e8 Mon Sep 17 00:00:00 2001 From: seth Date: Sat, 30 Mar 2024 03:04:32 -0400 Subject: [PATCH 70/90] once_cell -> std --- Cargo.lock | 1 - Cargo.toml | 1 - src/api/dadjoke.rs | 4 +- src/api/github.rs | 12 +++-- src/api/mod.rs | 47 ++++++++++++++----- src/api/paste_gg.rs | 6 +-- src/api/pluralkit.rs | 4 +- src/api/prism_meta.rs | 4 +- src/api/rory.rs | 7 +-- src/commands/general/members.rs | 2 +- src/commands/general/stars.rs | 2 +- src/commands/general/tag.rs | 16 ++++--- src/commands/moderation/set_welcome.rs | 4 +- src/consts.rs | 26 +++++----- src/handlers/error.rs | 2 +- src/handlers/event/analyze_logs/issues.rs | 36 +++++++------- src/handlers/event/analyze_logs/mod.rs | 7 ++- .../event/analyze_logs/providers/0x0.rs | 12 +++-- .../analyze_logs/providers/attachment.rs | 4 +- .../event/analyze_logs/providers/haste.rs | 14 +++--- .../event/analyze_logs/providers/mclogs.rs | 12 +++-- .../event/analyze_logs/providers/mod.rs | 3 +- .../event/analyze_logs/providers/paste_gg.rs | 9 ++-- .../event/analyze_logs/providers/pastebin.rs | 14 +++--- src/handlers/event/eta.rs | 14 ++++-- src/main.rs | 2 +- src/utils/mod.rs | 27 ----------- src/utils/resolve_message.rs | 18 +++---- 28 files changed, 157 insertions(+), 153 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ba57f24..935c9f0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1497,7 +1497,6 @@ dependencies = [ "gray_matter", "log", "octocrab", - "once_cell", "owo-colors 4.0.0", "poise", "rand", diff --git a/Cargo.toml b/Cargo.toml index 6af8378..7678984 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,6 @@ eyre = "0.6.11" log = "0.4.20" poise = "0.6.1" octocrab = "0.37.0" -once_cell = "1.19.0" owo-colors = "4.0.0" rand = "0.8.5" redis = { version = "0.25.2", features = ["tokio-comp", "tokio-rustls-comp"] } diff --git a/src/api/dadjoke.rs b/src/api/dadjoke.rs index 2675da6..642d656 100644 --- a/src/api/dadjoke.rs +++ b/src/api/dadjoke.rs @@ -1,5 +1,3 @@ -use crate::api::REQWEST_CLIENT; - use eyre::Result; use log::debug; @@ -8,7 +6,7 @@ const DADJOKE: &str = "https://icanhazdadjoke.com"; pub async fn get_joke() -> Result { debug!("Making request to {DADJOKE}"); - let resp = REQWEST_CLIENT + let resp = super::client() .get(DADJOKE) .header("Accept", "text/plain") .send() diff --git a/src/api/github.rs b/src/api/github.rs index 18fce69..42f4a8b 100644 --- a/src/api/github.rs +++ b/src/api/github.rs @@ -1,16 +1,18 @@ -use std::sync::Arc; +use std::sync::{Arc, OnceLock}; use eyre::{Context, OptionExt, Result}; use log::debug; use octocrab::Octocrab; -use once_cell::sync::Lazy; -static OCTOCRAB: Lazy> = Lazy::new(octocrab::instance); +fn octocrab() -> &'static Arc { + static OCTOCRAB: OnceLock> = OnceLock::new(); + OCTOCRAB.get_or_init(octocrab::instance) +} pub async fn get_latest_prism_version() -> Result { debug!("Fetching the latest version of Prism Launcher"); - let version = OCTOCRAB + let version = octocrab() .repos("PrismLauncher", "PrismLauncher") .releases() .get_latest() @@ -23,7 +25,7 @@ pub async fn get_latest_prism_version() -> Result { pub async fn get_prism_stargazers_count() -> Result { debug!("Fetching Prism Launcher's stargazer count"); - let stargazers_count = OCTOCRAB + let stargazers_count = octocrab() .repos("PrismLauncher", "PrismLauncher") .get() .await diff --git a/src/api/mod.rs b/src/api/mod.rs index 649be3e..99d51fb 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -1,4 +1,8 @@ -use once_cell::sync::Lazy; +use std::sync::OnceLock; + +use eyre::Result; +use log::debug; +use reqwest::{Client, Response}; pub mod dadjoke; pub mod github; @@ -7,15 +11,36 @@ pub mod pluralkit; pub mod prism_meta; pub mod rory; -pub static USER_AGENT: Lazy = Lazy::new(|| { - let version = option_env!("CARGO_PKG_VERSION").unwrap_or("development"); +pub fn client() -> &'static reqwest::Client { + static CLIENT: OnceLock = OnceLock::new(); + CLIENT.get_or_init(|| { + let version = option_env!("CARGO_PKG_VERSION").unwrap_or("development"); + let user_agent = format!("refraction/{version}"); + Client::builder() + .user_agent(user_agent) + .build() + .unwrap_or_default() + }) +} - format!("refraction/{version}") -}); +pub async fn get_url(url: &str) -> Result { + debug!("Making request to {url}"); + let resp = client().get(url).send().await?; + resp.error_for_status_ref()?; -pub static REQWEST_CLIENT: Lazy = Lazy::new(|| { - reqwest::Client::builder() - .user_agent(USER_AGENT.to_string()) - .build() - .unwrap_or_default() -}); + Ok(resp) +} + +pub async fn text_from_url(url: &str) -> Result { + let resp = get_url(url).await?; + + let text = resp.text().await?; + Ok(text) +} + +pub async fn bytes_from_url(url: &str) -> Result> { + let resp = get_url(url).await?; + + let bytes = resp.bytes().await?; + Ok(bytes.to_vec()) +} diff --git a/src/api/paste_gg.rs b/src/api/paste_gg.rs index c649429..4fdd59d 100644 --- a/src/api/paste_gg.rs +++ b/src/api/paste_gg.rs @@ -1,5 +1,3 @@ -use crate::{api::REQWEST_CLIENT, utils}; - use eyre::{eyre, OptionExt, Result}; use log::debug; use serde::{Deserialize, Serialize}; @@ -32,7 +30,7 @@ pub struct Files { pub async fn get_files(id: &str) -> Result> { let url = format!("{PASTE_GG}{PASTES}/{id}/files"); debug!("Making request to {url}"); - let resp = REQWEST_CLIENT.get(url).send().await?; + let resp = super::client().get(url).send().await?; resp.error_for_status_ref()?; let resp: Response = resp.json().await?; @@ -49,7 +47,7 @@ pub async fn get_files(id: &str) -> Result> { pub async fn get_raw_file(paste_id: &str, file_id: &str) -> eyre::Result { let url = format!("{PASTE_GG}{PASTES}/{paste_id}/files/{file_id}/raw"); - let text = utils::text_from_url(&url).await?; + let text = super::text_from_url(&url).await?; Ok(text) } diff --git a/src/api/pluralkit.rs b/src/api/pluralkit.rs index b711be6..5e28512 100644 --- a/src/api/pluralkit.rs +++ b/src/api/pluralkit.rs @@ -1,5 +1,3 @@ -use crate::api::REQWEST_CLIENT; - use eyre::{Context, Result}; use log::debug; use poise::serenity_prelude::{MessageId, UserId}; @@ -17,7 +15,7 @@ pub async fn get_sender(message_id: MessageId) -> Result { let url = format!("{PLURAL_KIT}{MESSAGES}/{message_id}"); debug!("Making request to {url}"); - let resp = REQWEST_CLIENT.get(url).send().await?; + let resp = super::client().get(url).send().await?; resp.error_for_status_ref()?; let data: Message = resp.json().await?; diff --git a/src/api/prism_meta.rs b/src/api/prism_meta.rs index fbc5878..9b23fd4 100644 --- a/src/api/prism_meta.rs +++ b/src/api/prism_meta.rs @@ -1,5 +1,3 @@ -use crate::api::REQWEST_CLIENT; - use eyre::{OptionExt, Result}; use log::debug; use serde::{Deserialize, Serialize}; @@ -20,7 +18,7 @@ pub async fn get_latest_minecraft_version() -> Result { let url = format!("{META}{MINECRAFT_PACKAGEJSON}"); debug!("Making request to {url}"); - let resp = REQWEST_CLIENT.get(url).send().await?; + let resp = super::client().get(url).send().await?; resp.error_for_status_ref()?; let data: MinecraftPackageJson = resp.json().await?; diff --git a/src/api/rory.rs b/src/api/rory.rs index ccbcada..bb64a0c 100644 --- a/src/api/rory.rs +++ b/src/api/rory.rs @@ -1,5 +1,3 @@ -use crate::api::REQWEST_CLIENT; - use eyre::{Context, Result}; use log::debug; use serde::{Deserialize, Serialize}; @@ -20,10 +18,7 @@ pub async fn get(id: Option) -> Result { debug!("Making request to {url}"); - let resp = REQWEST_CLIENT - .get(format!("{RORY}{PURR}/{target}")) - .send() - .await?; + let resp = super::client().get(url).send().await?; resp.error_for_status_ref()?; let data: Response = resp diff --git a/src/commands/general/members.rs b/src/commands/general/members.rs index 94204cc..48525be 100644 --- a/src/commands/general/members.rs +++ b/src/commands/general/members.rs @@ -29,7 +29,7 @@ pub async fn members(ctx: Context<'_>) -> Result<()> { let embed = CreateEmbed::new() .title(format!("{member_count} total members!",)) .description(format!("{online_count} online members",)) - .color(consts::COLORS["blue"]); + .color(consts::colors()["blue"]); let reply = CreateReply::default().embed(embed); ctx.send(reply).await?; diff --git a/src/commands/general/stars.rs b/src/commands/general/stars.rs index 710fa5d..4858d87 100644 --- a/src/commands/general/stars.rs +++ b/src/commands/general/stars.rs @@ -27,7 +27,7 @@ pub async fn stars(ctx: Context<'_>) -> Result<()> { let embed = CreateEmbed::new() .title(format!("⭐ {count} total stars!")) - .color(consts::COLORS["yellow"]); + .color(consts::colors()["yellow"]); let reply = CreateReply::default().embed(embed); ctx.send(reply).await?; diff --git a/src/commands/general/tag.rs b/src/commands/general/tag.rs index 43fa7a8..b34a250 100644 --- a/src/commands/general/tag.rs +++ b/src/commands/general/tag.rs @@ -1,16 +1,18 @@ #![allow(non_camel_case_types, clippy::upper_case_acronyms)] -use crate::tags::Tag; -use crate::{consts, Context}; +use crate::{consts, tags::Tag, Context}; use std::env; +use std::sync::OnceLock; use eyre::{eyre, Result}; use log::trace; -use once_cell::sync::Lazy; use poise::serenity_prelude::{Color, CreateEmbed, User}; use poise::CreateReply; include!(concat!(env!("OUT_DIR"), "/generated.rs")); -static TAGS: Lazy> = Lazy::new(|| serde_json::from_str(env!("TAGS")).unwrap()); +fn tags() -> &'static Vec { + static TAGS: OnceLock> = OnceLock::new(); + TAGS.get_or_init(|| serde_json::from_str(env!("TAGS")).unwrap()) +} /// Send a tag #[poise::command( @@ -27,7 +29,7 @@ pub async fn tag( trace!("Running tag command"); let tag_id = name.as_str(); - let tag = TAGS + let tag = tags() .iter() .find(|t| t.id == tag_id) .ok_or_else(|| eyre!("Tried to get non-existent tag: {tag_id}"))?; @@ -38,7 +40,7 @@ pub async fn tag( let mut e = CreateEmbed::new(); if let Some(color) = &frontmatter.color { - let color = *consts::COLORS + let color = *consts::colors() .get(color.as_str()) .unwrap_or(&Color::default()); e = e.color(color); @@ -76,7 +78,7 @@ pub async fn tag( } fn help() -> String { - let tag_list = TAGS + let tag_list = tags() .iter() .map(|tag| format!("`{}`", tag.id)) .collect::>() diff --git a/src/commands/moderation/set_welcome.rs b/src/commands/moderation/set_welcome.rs index c5c7f03..a9438d2 100644 --- a/src/commands/moderation/set_welcome.rs +++ b/src/commands/moderation/set_welcome.rs @@ -1,6 +1,6 @@ use std::{fmt::Write, str::FromStr}; -use crate::{utils, Context}; +use crate::{api, utils, Context}; use eyre::{bail, Result}; use log::trace; @@ -144,7 +144,7 @@ pub async fn set_welcome( return Ok(()); } - utils::text_from_url(&url).await? + api::text_from_url(&url).await? } else { ctx.say("A text file or URL must be provided!").await?; return Ok(()); diff --git a/src/consts.rs b/src/consts.rs index 5a0aa29..f823470 100644 --- a/src/consts.rs +++ b/src/consts.rs @@ -1,15 +1,17 @@ -use std::collections::HashMap; +use std::{collections::HashMap, sync::OnceLock}; -use once_cell::sync::Lazy; use poise::serenity_prelude::Color; -pub static COLORS: Lazy> = Lazy::new(|| { - HashMap::from([ - ("red", Color::from((239, 68, 68))), - ("green", Color::from((34, 197, 94))), - ("blue", Color::from((96, 165, 250))), - ("yellow", Color::from((253, 224, 71))), - ("orange", Color::from((251, 146, 60))), - // TODO purple & pink :D - ]) -}); +pub fn colors() -> &'static HashMap<&'static str, Color> { + static COLORS: OnceLock> = OnceLock::new(); + COLORS.get_or_init(|| { + HashMap::from([ + ("red", Color::from((239, 68, 68))), + ("green", Color::from((34, 197, 94))), + ("blue", Color::from((96, 165, 250))), + ("yellow", Color::from((253, 224, 71))), + ("orange", Color::from((251, 146, 60))), + // TODO purple & pink :D + ]) + }) +} diff --git a/src/handlers/error.rs b/src/handlers/error.rs index b0e8462..019bb3f 100644 --- a/src/handlers/error.rs +++ b/src/handlers/error.rs @@ -34,7 +34,7 @@ pub async fn handle(error: FrameworkError<'_, Data, Report>) { .title("Something went wrong!") .description("oopsie") .timestamp(Timestamp::now()) - .color(consts::COLORS["red"]); + .color(consts::colors()["red"]); let reply = CreateReply::default().embed(embed); diff --git a/src/handlers/event/analyze_logs/issues.rs b/src/handlers/event/analyze_logs/issues.rs index 50822c1..72ee00a 100644 --- a/src/handlers/event/analyze_logs/issues.rs +++ b/src/handlers/event/analyze_logs/issues.rs @@ -1,8 +1,9 @@ use crate::{api, Data}; +use std::sync::OnceLock; + use eyre::Result; use log::trace; -use once_cell::sync::Lazy; use regex::Regex; pub type Issue = Option<(String, String)>; @@ -103,12 +104,15 @@ fn intel_hd(log: &str) -> Issue { } fn java_option(log: &str) -> Issue { - static VM_OPTION_REGEX: Lazy = - Lazy::new(|| Regex::new(r"Unrecognized VM option '(.+)'[\r\n]").unwrap()); - static OPTION_REGEX: Lazy = - Lazy::new(|| Regex::new(r"Unrecognized option: (.+)[\r\n]").unwrap()); + static VM_OPTION_REGEX: OnceLock = OnceLock::new(); + static UNRECOGNIZED_OPTION_REGEX: OnceLock = OnceLock::new(); - if let Some(captures) = VM_OPTION_REGEX.captures(log) { + let vm_option = + VM_OPTION_REGEX.get_or_init(|| Regex::new(r"Unrecognized VM option '(.+)'[\r\n]").unwrap()); + let unrecognized_option = UNRECOGNIZED_OPTION_REGEX + .get_or_init(|| Regex::new(r"Unrecognized option: (.+)[\r\n]").unwrap()); + + if let Some(captures) = vm_option.captures(log) { let title = if &captures[1] == "UseShenandoahGC" { "Wrong Java Arguments" } else { @@ -120,7 +124,7 @@ fn java_option(log: &str) -> Issue { )); } - if let Some(captures) = OPTION_REGEX.captures(log) { + if let Some(captures) = unrecognized_option.captures(log) { return Some(( "Wrong Java Arguments".to_string(), format!("Remove `{}` from your Java arguments", &captures[1]), @@ -180,10 +184,11 @@ fn optinotfine(log: &str) -> Issue { } async fn outdated_launcher(log: &str, data: &Data) -> Result { - static OUTDATED_LAUNCHER_REGEX: Lazy = - Lazy::new(|| Regex::new("Prism Launcher version: [0-9].[0-9].[0-9]").unwrap()); + static OUTDATED_LAUNCHER_REGEX: OnceLock = OnceLock::new(); + let outdated_launcher = OUTDATED_LAUNCHER_REGEX + .get_or_init(|| Regex::new("Prism Launcher version: [0-9].[0-9].[0-9]").unwrap()); - let Some(captures) = OUTDATED_LAUNCHER_REGEX.captures(log) else { + let Some(captures) = outdated_launcher.captures(log) else { return Ok(None); }; @@ -235,13 +240,12 @@ which is why the issue was not present." } fn wrong_java(log: &str) -> Issue { - static SWITCH_VERSION_REGEX: Lazy = Lazy::new(|| { - Regex::new( - r"(?m)Please switch to one of the following Java versions for this instance:[\r\n]+(Java version [\d.]+)", - ).unwrap() - }); + static SWITCH_VERSION_REGEX: OnceLock = OnceLock::new(); + let switch_version = SWITCH_VERSION_REGEX.get_or_init(|| Regex::new( + r"(?m)Please switch to one of the following Java versions for this instance:[\r\n]+(Java version [\d.]+)", +).unwrap()); - if let Some(captures) = SWITCH_VERSION_REGEX.captures(log) { + if let Some(captures) = switch_version.captures(log) { let versions = captures[1].split('\n').collect::>().join(", "); return Some(( "Wrong Java Version".to_string(), diff --git a/src/handlers/event/analyze_logs/mod.rs b/src/handlers/event/analyze_logs/mod.rs index 5d55694..58c38cf 100644 --- a/src/handlers/event/analyze_logs/mod.rs +++ b/src/handlers/event/analyze_logs/mod.rs @@ -1,5 +1,4 @@ -use crate::consts::COLORS; -use crate::Data; +use crate::{consts, Data}; use eyre::Result; use log::{debug, trace}; @@ -49,10 +48,10 @@ pub async fn handle(ctx: &Context, message: &Message, data: &Data) -> Result<()> if issues.is_empty() { e = e - .color(COLORS["green"]) + .color(consts::colors()["green"]) .description("No issues found automatically"); } else { - e = e.color(COLORS["red"]); + e = e.color(consts::colors()["red"]); for (title, description) in issues { e = e.field(title, description, false); diff --git a/src/handlers/event/analyze_logs/providers/0x0.rs b/src/handlers/event/analyze_logs/providers/0x0.rs index 39b715c..14f7feb 100644 --- a/src/handlers/event/analyze_logs/providers/0x0.rs +++ b/src/handlers/event/analyze_logs/providers/0x0.rs @@ -1,8 +1,9 @@ -use crate::utils; +use crate::api; + +use std::sync::OnceLock; use eyre::Result; use log::trace; -use once_cell::sync::Lazy; use poise::serenity_prelude::Message; use regex::Regex; @@ -10,17 +11,18 @@ pub struct _0x0; impl super::LogProvider for _0x0 { async fn find_match(&self, message: &Message) -> Option { - static REGEX: Lazy = Lazy::new(|| Regex::new(r"https://0x0\.st/\w*.\w*").unwrap()); + static REGEX: OnceLock = OnceLock::new(); + let regex = REGEX.get_or_init(|| Regex::new(r"https://0x0\.st/\w*.\w*").unwrap()); trace!("Checking if message {} is a 0x0 paste", message.id); - REGEX + regex .find_iter(&message.content) .map(|m| m.as_str().to_string()) .nth(0) } async fn fetch(&self, content: &str) -> Result { - let log = utils::text_from_url(content).await?; + let log = api::text_from_url(content).await?; Ok(log) } diff --git a/src/handlers/event/analyze_logs/providers/attachment.rs b/src/handlers/event/analyze_logs/providers/attachment.rs index 103c4c2..25ce3cc 100644 --- a/src/handlers/event/analyze_logs/providers/attachment.rs +++ b/src/handlers/event/analyze_logs/providers/attachment.rs @@ -2,7 +2,7 @@ use eyre::Result; use log::trace; use poise::serenity_prelude::Message; -use crate::utils; +use crate::api; pub struct Attachment; @@ -22,7 +22,7 @@ impl super::LogProvider for Attachment { } async fn fetch(&self, content: &str) -> Result { - let attachment = utils::bytes_from_url(content).await?; + let attachment = api::bytes_from_url(content).await?; let log = String::from_utf8(attachment)?; Ok(log) } diff --git a/src/handlers/event/analyze_logs/providers/haste.rs b/src/handlers/event/analyze_logs/providers/haste.rs index 9909176..c4113d5 100644 --- a/src/handlers/event/analyze_logs/providers/haste.rs +++ b/src/handlers/event/analyze_logs/providers/haste.rs @@ -1,8 +1,9 @@ -use crate::utils; +use crate::api; + +use std::sync::OnceLock; use eyre::Result; use log::trace; -use once_cell::sync::Lazy; use poise::serenity_prelude::Message; use regex::Regex; @@ -13,16 +14,17 @@ pub struct Haste; impl super::LogProvider for Haste { async fn find_match(&self, message: &Message) -> Option { - static REGEX: Lazy = - Lazy::new(|| Regex::new(r"https://hst\.sh(?:/raw)?/(\w+(?:\.\w*)?)").unwrap()); + static REGEX: OnceLock = OnceLock::new(); + let regex = + REGEX.get_or_init(|| Regex::new(r"https://hst\.sh(?:/raw)?/(\w+(?:\.\w*)?)").unwrap()); trace!("Checking if message {} is a hst.sh paste", message.id); - super::get_first_capture(®EX, &message.content) + super::get_first_capture(regex, &message.content) } async fn fetch(&self, content: &str) -> Result { let url = format!("{HASTE}{RAW}/{content}"); - let log = utils::text_from_url(&url).await?; + let log = api::text_from_url(&url).await?; Ok(log) } diff --git a/src/handlers/event/analyze_logs/providers/mclogs.rs b/src/handlers/event/analyze_logs/providers/mclogs.rs index 7e9265b..b0f0c35 100644 --- a/src/handlers/event/analyze_logs/providers/mclogs.rs +++ b/src/handlers/event/analyze_logs/providers/mclogs.rs @@ -1,8 +1,9 @@ -use crate::utils; +use crate::api; + +use std::sync::OnceLock; use eyre::Result; use log::trace; -use once_cell::sync::Lazy; use poise::serenity_prelude::Message; use regex::Regex; @@ -13,15 +14,16 @@ pub struct MCLogs; impl super::LogProvider for MCLogs { async fn find_match(&self, message: &Message) -> Option { - static REGEX: Lazy = Lazy::new(|| Regex::new(r"https://mclo\.gs/(\w+)").unwrap()); + static REGEX: OnceLock = OnceLock::new(); + let regex = REGEX.get_or_init(|| Regex::new(r"https://mclo\.gs/(\w+)").unwrap()); trace!("Checking if message {} is an mclo.gs paste", message.id); - super::get_first_capture(®EX, &message.content) + super::get_first_capture(regex, &message.content) } async fn fetch(&self, content: &str) -> Result { let url = format!("{MCLOGS}{RAW}/{content}"); - let log = utils::text_from_url(&url).await?; + let log = api::text_from_url(&url).await?; Ok(log) } diff --git a/src/handlers/event/analyze_logs/providers/mod.rs b/src/handlers/event/analyze_logs/providers/mod.rs index 679f944..e20547f 100644 --- a/src/handlers/event/analyze_logs/providers/mod.rs +++ b/src/handlers/event/analyze_logs/providers/mod.rs @@ -2,7 +2,6 @@ use std::slice::Iter; use enum_dispatch::enum_dispatch; use eyre::Result; -use once_cell::sync::Lazy; use poise::serenity_prelude::Message; use regex::Regex; @@ -25,7 +24,7 @@ pub trait LogProvider { async fn fetch(&self, content: &str) -> Result; } -fn get_first_capture(regex: &Lazy, string: &str) -> Option { +fn get_first_capture(regex: &Regex, string: &str) -> Option { regex .captures_iter(string) .find_map(|c| c.get(1).map(|c| c.as_str().to_string())) diff --git a/src/handlers/event/analyze_logs/providers/paste_gg.rs b/src/handlers/event/analyze_logs/providers/paste_gg.rs index 5800a68..7ebead7 100644 --- a/src/handlers/event/analyze_logs/providers/paste_gg.rs +++ b/src/handlers/event/analyze_logs/providers/paste_gg.rs @@ -1,8 +1,9 @@ use crate::api::paste_gg; +use std::sync::OnceLock; + use eyre::{OptionExt, Result}; use log::trace; -use once_cell::sync::Lazy; use poise::serenity_prelude::Message; use regex::Regex; @@ -10,11 +11,11 @@ pub struct PasteGG; impl super::LogProvider for PasteGG { async fn find_match(&self, message: &Message) -> Option { - static REGEX: Lazy = - Lazy::new(|| Regex::new(r"https://paste.gg/p/\w+/(\w+)").unwrap()); + static REGEX: OnceLock = OnceLock::new(); + let regex = REGEX.get_or_init(|| Regex::new(r"https://paste.gg/p/\w+/(\w+)").unwrap()); trace!("Checking if message {} is a paste.gg paste", message.id); - super::get_first_capture(®EX, &message.content) + super::get_first_capture(regex, &message.content) } async fn fetch(&self, content: &str) -> Result { diff --git a/src/handlers/event/analyze_logs/providers/pastebin.rs b/src/handlers/event/analyze_logs/providers/pastebin.rs index f124018..66feecc 100644 --- a/src/handlers/event/analyze_logs/providers/pastebin.rs +++ b/src/handlers/event/analyze_logs/providers/pastebin.rs @@ -1,8 +1,9 @@ -use crate::utils; +use crate::api; + +use std::sync::OnceLock; use eyre::Result; use log::trace; -use once_cell::sync::Lazy; use poise::serenity_prelude::Message; use regex::Regex; @@ -13,16 +14,17 @@ pub struct PasteBin; impl super::LogProvider for PasteBin { async fn find_match(&self, message: &Message) -> Option { - static REGEX: Lazy = - Lazy::new(|| Regex::new(r"https://pastebin\.com(?:/raw)?/(\w+)").unwrap()); + static REGEX: OnceLock = OnceLock::new(); + let regex = + REGEX.get_or_init(|| Regex::new(r"https://pastebin\.com(?:/raw)?/(\w+)").unwrap()); trace!("Checking if message {} is a pastebin paste", message.id); - super::get_first_capture(®EX, &message.content) + super::get_first_capture(regex, &message.content) } async fn fetch(&self, content: &str) -> Result { let url = format!("{PASTEBIN}{RAW}/{content}"); - let log = utils::text_from_url(&url).await?; + let log = api::text_from_url(&url).await?; Ok(log) } diff --git a/src/handlers/event/eta.rs b/src/handlers/event/eta.rs index f818c2e..f4219f1 100644 --- a/src/handlers/event/eta.rs +++ b/src/handlers/event/eta.rs @@ -1,13 +1,17 @@ +use std::sync::OnceLock; + use eyre::Result; use log::trace; -use once_cell::sync::Lazy; use poise::serenity_prelude::{Context, Message}; use rand::seq::SliceRandom; use regex::Regex; -static ETA_REGEX: Lazy = Lazy::new(|| Regex::new(r"\beta\b").unwrap()); +fn regex() -> &'static Regex { + static REGEX: OnceLock = OnceLock::new(); + REGEX.get_or_init(|| Regex::new(r"\beta\b").unwrap()) +} -const ETA_MESSAGES: [&str; 16] = [ +const MESSAGES: [&str; 16] = [ "Sometime", "Some day", "Not far", @@ -27,7 +31,7 @@ const ETA_MESSAGES: [&str; 16] = [ ]; pub async fn handle(ctx: &Context, message: &Message) -> Result<()> { - if !ETA_REGEX.is_match(&message.content) { + if !regex().is_match(&message.content) { trace!( "The message '{}' (probably) doesn't say ETA", message.content @@ -37,7 +41,7 @@ pub async fn handle(ctx: &Context, message: &Message) -> Result<()> { let response = format!( "{} <:pofat:1031701005559144458>", - ETA_MESSAGES + MESSAGES .choose(&mut rand::thread_rng()) .unwrap_or(&"sometime") ); diff --git a/src/main.rs b/src/main.rs index 893250d..118b109 100644 --- a/src/main.rs +++ b/src/main.rs @@ -33,7 +33,7 @@ use storage::Storage; type Context<'a> = poise::Context<'a, Data, Report>; -#[derive(Clone)] +#[derive(Clone, Debug, Default)] pub struct Data { config: Config, storage: Option, diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 62aa051..d12edb2 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -1,34 +1,7 @@ -use crate::api::REQWEST_CLIENT; - -use eyre::Result; -use log::debug; use poise::serenity_prelude::{CreateEmbedAuthor, User}; -use reqwest::Response; pub mod resolve_message; -pub async fn get_url(url: &str) -> Result { - debug!("Making request to {url}"); - let resp = REQWEST_CLIENT.get(url).send().await?; - resp.error_for_status_ref()?; - - Ok(resp) -} - -pub async fn text_from_url(url: &str) -> Result { - let resp = get_url(url).await?; - - let text = resp.text().await?; - Ok(text) -} - -pub async fn bytes_from_url(url: &str) -> Result> { - let resp = get_url(url).await?; - - let bytes = resp.bytes().await?; - Ok(bytes.to_vec()) -} - pub fn embed_author_from_user(user: &User) -> CreateEmbedAuthor { CreateEmbedAuthor::new(user.tag()).icon_url( user.avatar_url() diff --git a/src/utils/resolve_message.rs b/src/utils/resolve_message.rs index 95b01d5..e6bd2dd 100644 --- a/src/utils/resolve_message.rs +++ b/src/utils/resolve_message.rs @@ -1,9 +1,9 @@ use crate::api::pluralkit; -use std::str::FromStr; + +use std::{str::FromStr, sync::OnceLock}; use eyre::{eyre, Context as _, Result}; use log::{debug, trace}; -use once_cell::sync::Lazy; use poise::serenity_prelude::{ Cache, CacheHttp, ChannelId, ChannelType, Colour, Context, CreateEmbed, CreateEmbedAuthor, CreateEmbedFooter, GuildChannel, Member, Message, MessageId, Permissions, UserId, @@ -36,8 +36,9 @@ async fn member_can_view_channel( member: &Member, channel: &GuildChannel, ) -> Result { - static REQUIRED_PERMISSIONS: Lazy = - Lazy::new(|| Permissions::VIEW_CHANNEL | Permissions::READ_MESSAGE_HISTORY); + static REQUIRED_PERMISSIONS: OnceLock = OnceLock::new(); + let required_permissions = REQUIRED_PERMISSIONS + .get_or_init(|| Permissions::VIEW_CHANNEL | Permissions::READ_MESSAGE_HISTORY); let guild = ctx.http().get_guild(channel.guild_id).await?; @@ -60,7 +61,7 @@ async fn member_can_view_channel( let can_view = guild .user_permissions_in(&channel_to_check, member) - .contains(*REQUIRED_PERMISSIONS); + .contains(*required_permissions); Ok(can_view) } @@ -109,9 +110,8 @@ pub async fn to_embed( } pub async fn from_message(ctx: &Context, msg: &Message) -> Result> { - static MESSAGE_PATTERN: Lazy = Lazy::new(|| { - Regex::new(r"(?:https?:\/\/)?(?:canary\.|ptb\.)?discord(?:app)?\.com\/channels\/(?\d+)\/(?\d+)\/(?\d+)").unwrap() - }); + static MESSAGE_PATTERN: OnceLock = OnceLock::new(); + let message_pattern = MESSAGE_PATTERN.get_or_init(|| Regex::new(r"(?:https?:\/\/)?(?:canary\.|ptb\.)?discord(?:app)?\.com\/channels\/(?\d+)\/(?\d+)\/(?\d+)").unwrap()); let Some(guild_id) = msg.guild_id else { debug!("Not resolving message in DM"); @@ -128,7 +128,7 @@ pub async fn from_message(ctx: &Context, msg: &Message) -> Result Date: Sat, 30 Mar 2024 03:05:53 -0400 Subject: [PATCH 71/90] drop redis-macros this was unused --- Cargo.lock | 41 +---------------------------------------- Cargo.toml | 1 - 2 files changed, 1 insertion(+), 41 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 935c9f0..1285bc2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1412,21 +1412,6 @@ dependencies = [ "getrandom", ] -[[package]] -name = "redis" -version = "0.23.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f49cdc0bb3f412bf8e7d1bd90fe1d9eb10bc5c399ba90973c14662a27b3f8ba" -dependencies = [ - "combine", - "itoa", - "percent-encoding", - "ryu", - "sha1_smol", - "socket2 0.4.10", - "url", -] - [[package]] name = "redis" version = "0.25.2" @@ -1453,29 +1438,6 @@ dependencies = [ "url", ] -[[package]] -name = "redis-macros" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60eb39e2b44d4c0f9c84e7c5fc4fc3adc8dd26ec48f1ac3a160033f7c03b18fd" -dependencies = [ - "redis 0.23.3", - "redis-macros-derive", - "serde", - "serde_json", -] - -[[package]] -name = "redis-macros-derive" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39550b9e94ce430a349c5490ca4efcae90ab8189603320f88c1d69f0326f169e" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "redox_syscall" version = "0.4.1" @@ -1500,8 +1462,7 @@ dependencies = [ "owo-colors 4.0.0", "poise", "rand", - "redis 0.25.2", - "redis-macros", + "redis", "regex", "reqwest 0.12.2", "serde", diff --git a/Cargo.toml b/Cargo.toml index 7678984..b47c5fd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,7 +26,6 @@ octocrab = "0.37.0" owo-colors = "4.0.0" rand = "0.8.5" redis = { version = "0.25.2", features = ["tokio-comp", "tokio-rustls-comp"] } -redis-macros = "0.2.1" regex = "1.10.3" reqwest = { version = "0.12.2", default-features = false, features = [ "rustls-tls", From 29ed728fc131d34f64da5997e017f083d250cf9e Mon Sep 17 00:00:00 2001 From: seth Date: Sat, 30 Mar 2024 03:24:47 -0400 Subject: [PATCH 72/90] drop rand --- Cargo.lock | 1 - Cargo.toml | 1 - src/handlers/event/eta.rs | 16 ++++++++-------- 3 files changed, 8 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1285bc2..6238db1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1461,7 +1461,6 @@ dependencies = [ "octocrab", "owo-colors 4.0.0", "poise", - "rand", "redis", "regex", "reqwest 0.12.2", diff --git a/Cargo.toml b/Cargo.toml index b47c5fd..dd9eed8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,7 +24,6 @@ log = "0.4.20" poise = "0.6.1" octocrab = "0.37.0" owo-colors = "4.0.0" -rand = "0.8.5" redis = { version = "0.25.2", features = ["tokio-comp", "tokio-rustls-comp"] } regex = "1.10.3" reqwest = { version = "0.12.2", default-features = false, features = [ diff --git a/src/handlers/event/eta.rs b/src/handlers/event/eta.rs index f4219f1..02bb24e 100644 --- a/src/handlers/event/eta.rs +++ b/src/handlers/event/eta.rs @@ -1,9 +1,8 @@ -use std::sync::OnceLock; +use std::{sync::OnceLock, time::SystemTime}; use eyre::Result; use log::trace; use poise::serenity_prelude::{Context, Message}; -use rand::seq::SliceRandom; use regex::Regex; fn regex() -> &'static Regex { @@ -39,13 +38,14 @@ pub async fn handle(ctx: &Context, message: &Message) -> Result<()> { return Ok(()); } - let response = format!( - "{} <:pofat:1031701005559144458>", - MESSAGES - .choose(&mut rand::thread_rng()) - .unwrap_or(&"sometime") - ); + // no, this isn't actually random. we don't need it to be, though -getchoo + let current_time = SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH)? + .as_millis(); + let random_pos = (current_time % MESSAGES.len() as u128) as usize; + let response = format!("{} <:pofat:1031701005559144458>", MESSAGES[random_pos]); message.reply(ctx, response).await?; + Ok(()) } From 1ff95de3bf317dc4c2937d9a7aaebfded6e4a755 Mon Sep 17 00:00:00 2001 From: seth Date: Sat, 30 Mar 2024 03:42:53 -0400 Subject: [PATCH 73/90] make sure to borrow in getters --- src/commands/general/say.rs | 1 - src/commands/general/stars.rs | 2 +- src/commands/moderation/set_welcome.rs | 2 +- src/config/bot.rs | 6 +++--- src/config/discord.rs | 14 +++++++------- src/config/mod.rs | 12 ++++++------ src/handlers/event/analyze_logs/issues.rs | 2 +- src/main.rs | 2 +- src/storage/mod.rs | 6 +++--- 9 files changed, 23 insertions(+), 24 deletions(-) diff --git a/src/commands/general/say.rs b/src/commands/general/say.rs index 1703fe3..9aa8098 100644 --- a/src/commands/general/say.rs +++ b/src/commands/general/say.rs @@ -23,7 +23,6 @@ pub async fn say( if let Some(channel_id) = ctx .data() .config - .clone() .discord_config() .channels() .log_channel_id() diff --git a/src/commands/general/stars.rs b/src/commands/general/stars.rs index 4858d87..b669dbe 100644 --- a/src/commands/general/stars.rs +++ b/src/commands/general/stars.rs @@ -13,7 +13,7 @@ pub async fn stars(ctx: Context<'_>) -> Result<()> { ctx.defer().await?; let count = if let Some(storage) = &ctx.data().storage { - if let Ok(count) = storage.get_launcher_stargazer_count().await { + if let Ok(count) = storage.launcher_stargazer_count().await { count } else { let count = api::github::get_prism_stargazers_count().await?; diff --git a/src/commands/moderation/set_welcome.rs b/src/commands/moderation/set_welcome.rs index a9438d2..901aecc 100644 --- a/src/commands/moderation/set_welcome.rs +++ b/src/commands/moderation/set_welcome.rs @@ -115,7 +115,7 @@ pub async fn set_welcome( ) -> Result<()> { trace!("Running set_welcome command!"); - let configured_channels = ctx.data().config.clone().discord_config().channels(); + let configured_channels = ctx.data().config.discord_config().channels(); let Some(channel_id) = configured_channels.welcome_channel_id() else { ctx.say("You don't have a welcome channel ID set, so I can't do anything :(") .await?; diff --git a/src/config/bot.rs b/src/config/bot.rs index 7eee8eb..ebad8fe 100644 --- a/src/config/bot.rs +++ b/src/config/bot.rs @@ -10,7 +10,7 @@ impl Config { Self { redis_url } } - pub fn new_from_env() -> Self { + pub fn from_env() -> Self { let redis_url = std::env::var("BOT_REDIS_URL").ok(); if let Some(url) = &redis_url { @@ -22,7 +22,7 @@ impl Config { Self::new(redis_url) } - pub fn redis_url(self) -> Option { - self.redis_url + pub fn redis_url(&self) -> Option<&str> { + self.redis_url.as_deref() } } diff --git a/src/config/discord.rs b/src/config/discord.rs index 837c860..8ef8082 100644 --- a/src/config/discord.rs +++ b/src/config/discord.rs @@ -46,12 +46,12 @@ impl RefractionChannels { .and_then(|env_var| ChannelId::from_str(&env_var).ok()) } - pub fn log_channel_id(self) -> Option { - self.log_channel_id + pub fn log_channel_id(&self) -> Option<&ChannelId> { + self.log_channel_id.as_ref() } - pub fn welcome_channel_id(self) -> Option { - self.welcome_channel_id + pub fn welcome_channel_id(&self) -> Option<&ChannelId> { + self.welcome_channel_id.as_ref() } } @@ -60,13 +60,13 @@ impl Config { Self { channels } } - pub fn new_from_env() -> Self { + pub fn from_env() -> Self { let channels = RefractionChannels::new_from_env(); Self::new(channels) } - pub fn channels(self) -> RefractionChannels { - self.channels + pub fn channels(&self) -> &RefractionChannels { + &self.channels } } diff --git a/src/config/mod.rs b/src/config/mod.rs index 416472b..568a7e2 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -16,17 +16,17 @@ impl Config { } pub fn new_from_env() -> Self { - let bot = bot::Config::new_from_env(); - let discord = discord::Config::new_from_env(); + let bot = bot::Config::from_env(); + let discord = discord::Config::from_env(); Self::new(bot, discord) } - pub fn bot_config(self) -> bot::Config { - self.bot + pub fn bot_config(&self) -> &bot::Config { + &self.bot } - pub fn discord_config(self) -> discord::Config { - self.discord + pub fn discord_config(&self) -> &discord::Config { + &self.discord } } diff --git a/src/handlers/event/analyze_logs/issues.rs b/src/handlers/event/analyze_logs/issues.rs index 72ee00a..c2254bc 100644 --- a/src/handlers/event/analyze_logs/issues.rs +++ b/src/handlers/event/analyze_logs/issues.rs @@ -195,7 +195,7 @@ async fn outdated_launcher(log: &str, data: &Data) -> Result { let version_from_log = captures[0].replace("Prism Launcher version: ", ""); let latest_version = if let Some(storage) = &data.storage { - if let Ok(version) = storage.get_launcher_version().await { + if let Ok(version) = storage.launcher_version().await { version } else { api::github::get_latest_prism_version().await? diff --git a/src/main.rs b/src/main.rs index 118b109..3e0e101 100644 --- a/src/main.rs +++ b/src/main.rs @@ -53,7 +53,7 @@ async fn setup( ) -> Result { let config = Config::new_from_env(); - let storage = if let Some(url) = &config.clone().bot_config().redis_url() { + let storage = if let Some(url) = config.bot_config().redis_url() { Some(Storage::from_url(url)?) } else { None diff --git a/src/storage/mod.rs b/src/storage/mod.rs index 6166276..e482b4f 100644 --- a/src/storage/mod.rs +++ b/src/storage/mod.rs @@ -25,7 +25,7 @@ impl Storage { Ok(Self::new(client)) } - pub fn has_connection(mut self) -> bool { + pub fn has_connection(&mut self) -> bool { self.client.check_connection() } @@ -60,7 +60,7 @@ impl Storage { Ok(()) } - pub async fn get_launcher_version(&self) -> Result { + pub async fn launcher_version(&self) -> Result { debug!("Fetching launcher version"); let mut con = self.client.get_multiplexed_async_connection().await?; @@ -79,7 +79,7 @@ impl Storage { Ok(()) } - pub async fn get_launcher_stargazer_count(&self) -> Result { + pub async fn launcher_stargazer_count(&self) -> Result { debug!("Fetching launcher stargazer count"); let mut con = self.client.get_multiplexed_async_connection().await?; From a3014f26944e364bacc4ad29a7bebb58f8c80180 Mon Sep 17 00:00:00 2001 From: seth Date: Sat, 30 Mar 2024 04:11:04 -0400 Subject: [PATCH 74/90] consts: store colors as hex codes in struct --- src/commands/general/members.rs | 4 +-- src/commands/general/stars.rs | 4 +-- src/commands/general/tag.rs | 10 +++--- src/consts.rs | 50 ++++++++++++++++++-------- src/handlers/error.rs | 6 ++-- src/handlers/event/analyze_logs/mod.rs | 6 ++-- 6 files changed, 52 insertions(+), 28 deletions(-) diff --git a/src/commands/general/members.rs b/src/commands/general/members.rs index 48525be..483b1a3 100644 --- a/src/commands/general/members.rs +++ b/src/commands/general/members.rs @@ -1,4 +1,4 @@ -use crate::{consts, Context}; +use crate::{consts::Colors, Context}; use eyre::{eyre, Context as _, OptionExt, Result}; use log::trace; @@ -29,7 +29,7 @@ pub async fn members(ctx: Context<'_>) -> Result<()> { let embed = CreateEmbed::new() .title(format!("{member_count} total members!",)) .description(format!("{online_count} online members",)) - .color(consts::colors()["blue"]); + .color(Colors::BLUE); let reply = CreateReply::default().embed(embed); ctx.send(reply).await?; diff --git a/src/commands/general/stars.rs b/src/commands/general/stars.rs index b669dbe..916c03f 100644 --- a/src/commands/general/stars.rs +++ b/src/commands/general/stars.rs @@ -1,4 +1,4 @@ -use crate::{api, consts, Context}; +use crate::{api, consts::Colors, Context}; use eyre::Result; use log::trace; @@ -27,7 +27,7 @@ pub async fn stars(ctx: Context<'_>) -> Result<()> { let embed = CreateEmbed::new() .title(format!("⭐ {count} total stars!")) - .color(consts::colors()["yellow"]); + .color(Colors::YELLOW); let reply = CreateReply::default().embed(embed); ctx.send(reply).await?; diff --git a/src/commands/general/tag.rs b/src/commands/general/tag.rs index b34a250..607ba8e 100644 --- a/src/commands/general/tag.rs +++ b/src/commands/general/tag.rs @@ -1,6 +1,7 @@ #![allow(non_camel_case_types, clippy::upper_case_acronyms)] -use crate::{consts, tags::Tag, Context}; +use crate::{consts::Colors, tags::Tag, Context}; use std::env; +use std::str::FromStr; use std::sync::OnceLock; use eyre::{eyre, Result}; @@ -40,9 +41,10 @@ pub async fn tag( let mut e = CreateEmbed::new(); if let Some(color) = &frontmatter.color { - let color = *consts::colors() - .get(color.as_str()) - .unwrap_or(&Color::default()); + let color = Colors::from_str(color.as_str()) + .map(Color::from) + .unwrap_or_default(); + e = e.color(color); } diff --git a/src/consts.rs b/src/consts.rs index f823470..e53ae36 100644 --- a/src/consts.rs +++ b/src/consts.rs @@ -1,17 +1,39 @@ -use std::{collections::HashMap, sync::OnceLock}; +#![allow(clippy::unreadable_literal)] +use std::str::FromStr; -use poise::serenity_prelude::Color; +use poise::serenity_prelude::Colour; -pub fn colors() -> &'static HashMap<&'static str, Color> { - static COLORS: OnceLock> = OnceLock::new(); - COLORS.get_or_init(|| { - HashMap::from([ - ("red", Color::from((239, 68, 68))), - ("green", Color::from((34, 197, 94))), - ("blue", Color::from((96, 165, 250))), - ("yellow", Color::from((253, 224, 71))), - ("orange", Color::from((251, 146, 60))), - // TODO purple & pink :D - ]) - }) +#[derive(Clone, Copy, Debug, Default)] +pub struct Colors(i32); + +impl Colors { + pub const RED: i32 = 0xEF4444; + pub const GREEN: i32 = 0x22C55E; + pub const BLUE: i32 = 0x60A5FA; + pub const YELLOW: i32 = 0xFDE047; + pub const ORANGE: i32 = 0xFB923C; + + pub fn as_i32(self) -> i32 { + self.0 + } +} + +impl FromStr for Colors { + type Err = (); + fn from_str(s: &str) -> Result { + match s.to_lowercase().as_str() { + "red" => Ok(Colors(Self::RED)), + "green" => Ok(Colors(Self::GREEN)), + "blue" => Ok(Colors(Self::BLUE)), + "yellow" => Ok(Colors(Self::YELLOW)), + "orange" => Ok(Colors(Self::ORANGE)), + _ => Err(()), + } + } +} + +impl From for Colour { + fn from(value: Colors) -> Self { + Self::from(value.as_i32()) + } } diff --git a/src/handlers/error.rs b/src/handlers/error.rs index 019bb3f..16fcda4 100644 --- a/src/handlers/error.rs +++ b/src/handlers/error.rs @@ -1,5 +1,5 @@ -use crate::consts; -use crate::Data; +use crate::{consts::Colors, Data}; + use std::fmt::Write; use eyre::Report; @@ -34,7 +34,7 @@ pub async fn handle(error: FrameworkError<'_, Data, Report>) { .title("Something went wrong!") .description("oopsie") .timestamp(Timestamp::now()) - .color(consts::colors()["red"]); + .color(Colors::RED); let reply = CreateReply::default().embed(embed); diff --git a/src/handlers/event/analyze_logs/mod.rs b/src/handlers/event/analyze_logs/mod.rs index 58c38cf..e490d09 100644 --- a/src/handlers/event/analyze_logs/mod.rs +++ b/src/handlers/event/analyze_logs/mod.rs @@ -1,4 +1,4 @@ -use crate::{consts, Data}; +use crate::{consts::Colors, Data}; use eyre::Result; use log::{debug, trace}; @@ -48,10 +48,10 @@ pub async fn handle(ctx: &Context, message: &Message, data: &Data) -> Result<()> if issues.is_empty() { e = e - .color(consts::colors()["green"]) + .color(Colors::GREEN) .description("No issues found automatically"); } else { - e = e.color(consts::colors()["red"]); + e = e.color(Colors::RED); for (title, description) in issues { e = e.field(title, description, false); From a2106caf226d28a7825d0649909faed948de4334 Mon Sep 17 00:00:00 2001 From: seth Date: Sun, 31 Mar 2024 13:12:13 -0400 Subject: [PATCH 75/90] set_welcome: bulk delete messages --- src/commands/moderation/set_welcome.rs | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/commands/moderation/set_welcome.rs b/src/commands/moderation/set_welcome.rs index 901aecc..b10477a 100644 --- a/src/commands/moderation/set_welcome.rs +++ b/src/commands/moderation/set_welcome.rs @@ -5,8 +5,8 @@ use crate::{api, utils, Context}; use eyre::{bail, Result}; use log::trace; use poise::serenity_prelude::{ - futures::StreamExt, Attachment, CreateActionRow, CreateButton, CreateEmbed, CreateMessage, - Mentionable, ReactionType, + futures::TryStreamExt, Attachment, CreateActionRow, CreateButton, CreateEmbed, CreateMessage, + Mentionable, Message, ReactionType, }; use serde::{Deserialize, Serialize}; use url::Url; @@ -164,12 +164,8 @@ pub async fn set_welcome( .collect(); // clear previous messages - let mut prev_messages = channel_id.messages_iter(ctx).boxed(); - while let Some(prev_message) = prev_messages.next().await { - if let Ok(message) = prev_message { - message.delete(ctx).await?; - } - } + let prev_messages: Vec = channel_id.messages_iter(ctx).try_collect().await?; + channel_id.delete_messages(ctx, prev_messages).await?; // send our new ones for embed in embed_messages { From 9dfc3b21ffdb1ede2abe37212a7a121a6247f95d Mon Sep 17 00:00:00 2001 From: seth Date: Sun, 31 Mar 2024 14:51:21 -0400 Subject: [PATCH 76/90] set_welcome: wire up role interaction --- src/handlers/event/give_role.rs | 45 +++++++++++++++++++++++++++++++++ src/handlers/event/mod.rs | 7 +++++ 2 files changed, 52 insertions(+) create mode 100644 src/handlers/event/give_role.rs diff --git a/src/handlers/event/give_role.rs b/src/handlers/event/give_role.rs new file mode 100644 index 0000000..a063399 --- /dev/null +++ b/src/handlers/event/give_role.rs @@ -0,0 +1,45 @@ +use std::str::FromStr; + +use eyre::Result; +use log::debug; +use poise::serenity_prelude::{ + ComponentInteraction, Context, CreateEmbed, CreateInteractionResponseFollowup, RoleId, +}; + +pub async fn handle(ctx: &Context, component_interaction: &ComponentInteraction) -> Result<()> { + let Some(guild_id) = component_interaction.guild_id else { + debug!("Ignoring component interaction not from guild!"); + return Ok(()); + }; + + let Ok(role_id) = RoleId::from_str(&component_interaction.data.custom_id) else { + debug!("Ignoring component interaction that doesn't contain a role as it's ID"); + return Ok(()); + }; + + component_interaction.defer_ephemeral(ctx).await?; + + let mut followup = CreateInteractionResponseFollowup::new().ephemeral(true); + if let Some(role) = guild_id.roles(ctx).await?.get(&role_id) { + let guild_member = guild_id.member(ctx, component_interaction.user.id).await?; + + let mut embed = CreateEmbed::new(); + if guild_member.roles.contains(&role_id) { + guild_member.remove_role(ctx, role_id).await?; + embed = embed.description(format!("❌ Removed `{}`", role.name)); + } else { + guild_member.add_role(ctx, role_id).await?; + embed = embed.description(format!("✅ Added `{}`", role.name)); + } + + followup = followup.add_embed(embed); + } else { + followup = followup.content(format!( + "Role ID {role_id} doesn't seem to exist. Please let the moderators know!" + )); + } + + component_interaction.create_followup(ctx, followup).await?; + + Ok(()) +} diff --git a/src/handlers/event/mod.rs b/src/handlers/event/mod.rs index 33d6193..89f69d6 100644 --- a/src/handlers/event/mod.rs +++ b/src/handlers/event/mod.rs @@ -9,6 +9,7 @@ mod analyze_logs; mod delete_on_reaction; mod eta; mod expand_link; +mod give_role; pub mod pluralkit; mod support_onboard; @@ -29,6 +30,12 @@ pub async fn handle( ctx.set_presence(Some(activity), OnlineStatus::Online); } + FullEvent::InteractionCreate { interaction } => { + if let Some(component_interaction) = interaction.as_message_component() { + give_role::handle(ctx, component_interaction).await?; + } + } + FullEvent::Message { new_message } => { trace!("Recieved message {}", new_message.content); From 0b0779f8b7ae3fb25e4a942aa63fd479f1f6593a Mon Sep 17 00:00:00 2001 From: seth Date: Sun, 31 Mar 2024 17:26:16 -0400 Subject: [PATCH 77/90] clean up --- Cargo.lock | 12 +---- Cargo.toml | 13 ++++- build.rs | 2 +- src/api/github.rs | 8 +-- src/api/mod.rs | 8 +++ src/api/paste_gg.rs | 15 +++--- src/api/pluralkit.rs | 12 ++--- src/api/prism_meta.rs | 2 +- src/commands/general/help.rs | 5 +- src/commands/general/joke.rs | 5 +- src/commands/general/members.rs | 8 +-- src/commands/general/ping.rs | 5 +- src/commands/general/rory.rs | 6 +-- src/commands/general/say.rs | 13 ++--- src/commands/general/stars.rs | 7 ++- src/commands/general/tag.rs | 6 +-- src/commands/mod.rs | 6 +-- src/commands/moderation/set_welcome.rs | 24 ++++----- src/config/bot.rs | 6 +-- src/config/discord.rs | 18 ++----- src/config/mod.rs | 12 +---- src/consts.rs | 49 +++++++++++-------- src/handlers/error.rs | 7 ++- src/handlers/event/analyze_logs/issues.rs | 4 +- src/handlers/event/analyze_logs/mod.rs | 4 +- .../event/analyze_logs/providers/paste_gg.rs | 2 +- src/handlers/event/expand_link.rs | 2 +- src/handlers/event/mod.rs | 11 ++--- src/handlers/event/pluralkit.rs | 4 +- src/main.rs | 38 +++++--------- src/utils/{resolve_message.rs => messages.rs} | 2 +- src/utils/mod.rs | 2 +- 32 files changed, 137 insertions(+), 181 deletions(-) rename src/utils/{resolve_message.rs => messages.rs} (98%) diff --git a/Cargo.lock b/Cargo.lock index 6238db1..f29d4e0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -263,7 +263,7 @@ dependencies = [ "eyre", "indenter", "once_cell", - "owo-colors 3.5.0", + "owo-colors", "tracing-error", ] @@ -274,7 +274,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd6be1b2a7e382e2b98b43b2adcca6bb0e465af0bdd38123873ae61eb17a72c2" dependencies = [ "once_cell", - "owo-colors 3.5.0", + "owo-colors", "tracing-core", "tracing-error", ] @@ -1235,12 +1235,6 @@ version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" -[[package]] -name = "owo-colors" -version = "4.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "caff54706df99d2a78a5a4e3455ff45448d81ef1bb63c22cd14052ca0e993a3f" - [[package]] name = "parking_lot" version = "0.12.1" @@ -1459,7 +1453,6 @@ dependencies = [ "gray_matter", "log", "octocrab", - "owo-colors 4.0.0", "poise", "redis", "regex", @@ -1467,7 +1460,6 @@ dependencies = [ "serde", "serde_json", "tokio", - "url", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index dd9eed8..388def1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,6 @@ eyre = "0.6.11" log = "0.4.20" poise = "0.6.1" octocrab = "0.37.0" -owo-colors = "4.0.0" redis = { version = "0.25.2", features = ["tokio-comp", "tokio-rustls-comp"] } regex = "1.10.3" reqwest = { version = "0.12.2", default-features = false, features = [ @@ -37,7 +36,17 @@ tokio = { version = "1.35.1", features = [ "rt-multi-thread", "signal", ] } -url = { version = "2.5.0", features = ["serde"] } + +[lints.rust] +unsafe_code = "forbid" + +[lints.clippy] +complexity = "warn" +correctness = "deny" +pedantic = "warn" +perf = "warn" +style = "warn" +suspicious = "deny" [profile.release] codegen-units = 1 diff --git a/build.rs b/build.rs index 9925fb4..5638757 100644 --- a/build.rs +++ b/build.rs @@ -6,7 +6,7 @@ use gray_matter::{engine, Matter}; include!("src/tags.rs"); -/// generate the ChoiceParameter enum and tag data we will use in the `tag` command +/// generate the `ChoiceParameter` enum and tag data we will use in the `tag` command #[allow(dead_code)] fn main() { let out_dir = env::var_os("OUT_DIR").unwrap(); diff --git a/src/api/github.rs b/src/api/github.rs index 42f4a8b..f0a1fa4 100644 --- a/src/api/github.rs +++ b/src/api/github.rs @@ -1,12 +1,12 @@ -use std::sync::{Arc, OnceLock}; +use std::sync::OnceLock; use eyre::{Context, OptionExt, Result}; use log::debug; use octocrab::Octocrab; -fn octocrab() -> &'static Arc { - static OCTOCRAB: OnceLock> = OnceLock::new(); - OCTOCRAB.get_or_init(octocrab::instance) +fn octocrab() -> &'static Octocrab { + static OCTOCRAB: OnceLock = OnceLock::new(); + OCTOCRAB.get_or_init(Octocrab::default) } pub async fn get_latest_prism_version() -> Result { diff --git a/src/api/mod.rs b/src/api/mod.rs index 99d51fb..711334c 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -3,6 +3,7 @@ use std::sync::OnceLock; use eyre::Result; use log::debug; use reqwest::{Client, Response}; +use serde::de::DeserializeOwned; pub mod dadjoke; pub mod github; @@ -44,3 +45,10 @@ pub async fn bytes_from_url(url: &str) -> Result> { let bytes = resp.bytes().await?; Ok(bytes.to_vec()) } + +pub async fn json_from_url(url: &str) -> Result { + let resp = get_url(url).await?; + + let json = resp.json().await?; + Ok(json) +} diff --git a/src/api/paste_gg.rs b/src/api/paste_gg.rs index 4fdd59d..01df669 100644 --- a/src/api/paste_gg.rs +++ b/src/api/paste_gg.rs @@ -6,7 +6,7 @@ const PASTE_GG: &str = "https://api.paste.gg/v1"; const PASTES: &str = "/pastes"; #[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Deserialize, Serialize)] -enum Status { +pub enum Status { #[serde(rename = "success")] Success, #[serde(rename = "error")] @@ -15,10 +15,10 @@ enum Status { #[derive(Clone, Debug, Deserialize, Serialize)] pub struct Response { - status: Status, + pub status: Status, pub result: Option>, - error: Option, - message: Option, + pub error: Option, + pub message: Option, } #[derive(Clone, Debug, Deserialize, Serialize)] @@ -27,12 +27,11 @@ pub struct Files { pub name: Option, } -pub async fn get_files(id: &str) -> Result> { +pub async fn files_from(id: &str) -> Result> { let url = format!("{PASTE_GG}{PASTES}/{id}/files"); debug!("Making request to {url}"); - let resp = super::client().get(url).send().await?; - resp.error_for_status_ref()?; - let resp: Response = resp.json().await?; + + let resp: Response = super::json_from_url(&url).await?; if resp.status == Status::Error { let message = resp diff --git a/src/api/pluralkit.rs b/src/api/pluralkit.rs index 5e28512..2fdb4cb 100644 --- a/src/api/pluralkit.rs +++ b/src/api/pluralkit.rs @@ -11,17 +11,15 @@ pub struct Message { const PLURAL_KIT: &str = "https://api.pluralkit.me/v2"; const MESSAGES: &str = "/messages"; -pub async fn get_sender(message_id: MessageId) -> Result { +pub async fn sender_from(message_id: MessageId) -> Result { let url = format!("{PLURAL_KIT}{MESSAGES}/{message_id}"); - debug!("Making request to {url}"); - let resp = super::client().get(url).send().await?; - resp.error_for_status_ref()?; - let data: Message = resp.json().await?; + let resp: Message = super::json_from_url(&url).await?; + let id: u64 = - data.sender.parse().wrap_err_with(|| { - format!("Couldn't parse response from PluralKit as a UserId! Here's the response:\n{data:#?}") + resp.sender.parse().wrap_err_with(|| { + format!("Couldn't parse response from PluralKit as a UserId! Here's the response:\n{resp:#?}") })?; let sender = UserId::from(id); diff --git a/src/api/prism_meta.rs b/src/api/prism_meta.rs index 9b23fd4..e2114b7 100644 --- a/src/api/prism_meta.rs +++ b/src/api/prism_meta.rs @@ -14,7 +14,7 @@ pub struct MinecraftPackageJson { const META: &str = "https://meta.prismlauncher.org/v1"; const MINECRAFT_PACKAGEJSON: &str = "/net.minecraft/package.json"; -pub async fn get_latest_minecraft_version() -> Result { +pub async fn latest_minecraft_version() -> Result { let url = format!("{META}{MINECRAFT_PACKAGEJSON}"); debug!("Making request to {url}"); diff --git a/src/commands/general/help.rs b/src/commands/general/help.rs index f91d97d..bc6cb76 100644 --- a/src/commands/general/help.rs +++ b/src/commands/general/help.rs @@ -1,6 +1,5 @@ -use crate::Context; +use crate::{Context, Error}; -use eyre::Result; use log::trace; use poise::samples::HelpConfiguration; @@ -9,7 +8,7 @@ use poise::samples::HelpConfiguration; pub async fn help( ctx: Context<'_>, #[description = "Provide information about a specific command"] command: Option, -) -> Result<()> { +) -> Result<(), Error> { trace!("Running help command"); let configuration = HelpConfiguration { diff --git a/src/commands/general/joke.rs b/src/commands/general/joke.rs index fbced97..a064997 100644 --- a/src/commands/general/joke.rs +++ b/src/commands/general/joke.rs @@ -1,12 +1,11 @@ -use crate::api::dadjoke; -use crate::Context; +use crate::{api::dadjoke, Context, Error}; use eyre::Result; use log::trace; /// It's a joke #[poise::command(slash_command, prefix_command, track_edits = true)] -pub async fn joke(ctx: Context<'_>) -> Result<()> { +pub async fn joke(ctx: Context<'_>) -> Result<(), Error> { trace!("Running joke command"); ctx.defer().await?; diff --git a/src/commands/general/members.rs b/src/commands/general/members.rs index 483b1a3..0476972 100644 --- a/src/commands/general/members.rs +++ b/src/commands/general/members.rs @@ -1,13 +1,13 @@ -use crate::{consts::Colors, Context}; +use crate::{consts::Colors, Context, Error}; -use eyre::{eyre, Context as _, OptionExt, Result}; +use eyre::{eyre, Context as _, OptionExt}; use log::trace; use poise::serenity_prelude::CreateEmbed; use poise::CreateReply; /// Returns the number of members in the server #[poise::command(slash_command, prefix_command, guild_only = true, track_edits = true)] -pub async fn members(ctx: Context<'_>) -> Result<()> { +pub async fn members(ctx: Context<'_>) -> Result<(), Error> { trace!("Running members command"); ctx.defer().await?; @@ -29,7 +29,7 @@ pub async fn members(ctx: Context<'_>) -> Result<()> { let embed = CreateEmbed::new() .title(format!("{member_count} total members!",)) .description(format!("{online_count} online members",)) - .color(Colors::BLUE); + .color(Colors::Blue); let reply = CreateReply::default().embed(embed); ctx.send(reply).await?; diff --git a/src/commands/general/ping.rs b/src/commands/general/ping.rs index 90fc53f..09544ee 100644 --- a/src/commands/general/ping.rs +++ b/src/commands/general/ping.rs @@ -1,11 +1,10 @@ -use crate::Context; +use crate::{Context, Error}; -use eyre::Result; use log::trace; /// Replies with pong! #[poise::command(slash_command, prefix_command, track_edits = true, ephemeral)] -pub async fn ping(ctx: Context<'_>) -> Result<()> { +pub async fn ping(ctx: Context<'_>) -> Result<(), Error> { trace!("Running ping command!"); ctx.say("Pong!").await?; Ok(()) diff --git a/src/commands/general/rory.rs b/src/commands/general/rory.rs index 6bdc6f5..bc8cd63 100644 --- a/src/commands/general/rory.rs +++ b/src/commands/general/rory.rs @@ -1,7 +1,5 @@ -use crate::api::rory; -use crate::Context; +use crate::{api::rory, Context, Error}; -use eyre::Result; use log::trace; use poise::serenity_prelude::{CreateEmbed, CreateEmbedFooter}; use poise::CreateReply; @@ -11,7 +9,7 @@ use poise::CreateReply; pub async fn rory( ctx: Context<'_>, #[description = "specify a Rory ID"] id: Option, -) -> Result<()> { +) -> Result<(), Error> { trace!("Running rory command"); ctx.defer().await?; diff --git a/src/commands/general/say.rs b/src/commands/general/say.rs index 9aa8098..b873afd 100644 --- a/src/commands/general/say.rs +++ b/src/commands/general/say.rs @@ -1,6 +1,5 @@ -use crate::{utils, Context}; +use crate::{utils, Context, Error}; -use eyre::Result; use log::trace; use poise::serenity_prelude::{CreateEmbed, CreateMessage}; @@ -15,18 +14,12 @@ use poise::serenity_prelude::{CreateEmbed, CreateMessage}; pub async fn say( ctx: Context<'_>, #[description = "the message content"] content: String, -) -> Result<()> { +) -> Result<(), Error> { let channel = ctx.channel_id(); channel.say(ctx, &content).await?; ctx.say("I said what you said!").await?; - if let Some(channel_id) = ctx - .data() - .config - .discord_config() - .channels() - .log_channel_id() - { + if let Some(channel_id) = ctx.data().config.discord.channels.log_channel_id { let author = utils::embed_author_from_user(ctx.author()); let embed = CreateEmbed::default() diff --git a/src/commands/general/stars.rs b/src/commands/general/stars.rs index 916c03f..703586a 100644 --- a/src/commands/general/stars.rs +++ b/src/commands/general/stars.rs @@ -1,13 +1,12 @@ -use crate::{api, consts::Colors, Context}; +use crate::{api, consts::Colors, Context, Error}; -use eyre::Result; use log::trace; use poise::serenity_prelude::CreateEmbed; use poise::CreateReply; /// Returns GitHub stargazer count #[poise::command(slash_command, prefix_command, track_edits = true)] -pub async fn stars(ctx: Context<'_>) -> Result<()> { +pub async fn stars(ctx: Context<'_>) -> Result<(), Error> { trace!("Running stars command"); ctx.defer().await?; @@ -27,7 +26,7 @@ pub async fn stars(ctx: Context<'_>) -> Result<()> { let embed = CreateEmbed::new() .title(format!("⭐ {count} total stars!")) - .color(Colors::YELLOW); + .color(Colors::Yellow); let reply = CreateReply::default().embed(embed); ctx.send(reply).await?; diff --git a/src/commands/general/tag.rs b/src/commands/general/tag.rs index 607ba8e..3cfe1a1 100644 --- a/src/commands/general/tag.rs +++ b/src/commands/general/tag.rs @@ -1,10 +1,10 @@ #![allow(non_camel_case_types, clippy::upper_case_acronyms)] -use crate::{consts::Colors, tags::Tag, Context}; +use crate::{consts::Colors, tags::Tag, Context, Error}; use std::env; use std::str::FromStr; use std::sync::OnceLock; -use eyre::{eyre, Result}; +use eyre::eyre; use log::trace; use poise::serenity_prelude::{Color, CreateEmbed, User}; use poise::CreateReply; @@ -26,7 +26,7 @@ pub async fn tag( ctx: Context<'_>, #[description = "the tag to send"] name: Choice, #[description = "a user to mention"] user: Option, -) -> Result<()> { +) -> Result<(), Error> { trace!("Running tag command"); let tag_id = name.as_str(); diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 33216d3..a849933 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -1,6 +1,4 @@ -use crate::Data; - -use eyre::Report; +use crate::{Data, Error}; mod general; mod moderation; @@ -32,7 +30,7 @@ macro_rules! module_macro { module_macro!(general); module_macro!(moderation); -pub type Command = poise::Command; +pub type Command = poise::Command; pub fn get() -> Vec { vec![ diff --git a/src/commands/moderation/set_welcome.rs b/src/commands/moderation/set_welcome.rs index b10477a..4882af4 100644 --- a/src/commands/moderation/set_welcome.rs +++ b/src/commands/moderation/set_welcome.rs @@ -1,24 +1,23 @@ use std::{fmt::Write, str::FromStr}; -use crate::{api, utils, Context}; +use crate::{api, utils, Context, Error}; -use eyre::{bail, Result}; +use eyre::Result; use log::trace; use poise::serenity_prelude::{ futures::TryStreamExt, Attachment, CreateActionRow, CreateButton, CreateEmbed, CreateMessage, Mentionable, Message, ReactionType, }; use serde::{Deserialize, Serialize}; -use url::Url; #[derive(Debug, Clone, Deserialize, Serialize)] #[serde(deny_unknown_fields)] struct WelcomeEmbed { title: String, description: Option, - url: Option, + url: Option, hex_color: Option, - image: Option, + image: Option, } impl From for CreateMessage { @@ -112,11 +111,11 @@ pub async fn set_welcome( ctx: Context<'_>, #[description = "A file to use"] file: Option, #[description = "A URL for a file to use"] url: Option, -) -> Result<()> { +) -> Result<(), Error> { trace!("Running set_welcome command!"); - let configured_channels = ctx.data().config.discord_config().channels(); - let Some(channel_id) = configured_channels.welcome_channel_id() else { + let configured_channels = ctx.data().config.discord.channels; + let Some(channel_id) = configured_channels.welcome_channel_id else { ctx.say("You don't have a welcome channel ID set, so I can't do anything :(") .await?; return Ok(()); @@ -127,7 +126,7 @@ pub async fn set_welcome( // download attachment from discord or URL let file = if let Some(attachment) = file { let Some(content_type) = &attachment.content_type else { - bail!("Welcome channel attachment was sent without a content type!"); + return Err("Welcome channel attachment was sent without a content type!".into()); }; if !content_type.starts_with("application/json;") { @@ -139,11 +138,6 @@ pub async fn set_welcome( let downloaded = attachment.download().await?; String::from_utf8(downloaded)? } else if let Some(url) = url { - if Url::parse(&url).is_err() { - ctx.say("Invalid url!").await?; - return Ok(()); - } - api::text_from_url(&url).await? } else { ctx.say("A text file or URL must be provided!").await?; @@ -180,7 +174,7 @@ pub async fn set_welcome( channel_id.say(ctx, message).await?; } - if let Some(log_channel) = configured_channels.log_channel_id() { + if let Some(log_channel) = configured_channels.log_channel_id { let author = utils::embed_author_from_user(ctx.author()); let embed = CreateEmbed::new() .title("set_welcome command used!") diff --git a/src/config/bot.rs b/src/config/bot.rs index ebad8fe..c34c5b0 100644 --- a/src/config/bot.rs +++ b/src/config/bot.rs @@ -2,7 +2,7 @@ use log::{info, warn}; #[derive(Clone, Debug, Default)] pub struct Config { - redis_url: Option, + pub redis_url: Option, } impl Config { @@ -21,8 +21,4 @@ impl Config { Self::new(redis_url) } - - pub fn redis_url(&self) -> Option<&str> { - self.redis_url.as_deref() - } } diff --git a/src/config/discord.rs b/src/config/discord.rs index 8ef8082..27782b1 100644 --- a/src/config/discord.rs +++ b/src/config/discord.rs @@ -5,13 +5,13 @@ use poise::serenity_prelude::ChannelId; #[derive(Clone, Copy, Debug, Default)] pub struct RefractionChannels { - log_channel_id: Option, - welcome_channel_id: Option, + pub log_channel_id: Option, + pub welcome_channel_id: Option, } #[derive(Clone, Debug, Default)] pub struct Config { - channels: RefractionChannels, + pub channels: RefractionChannels, } impl RefractionChannels { @@ -45,14 +45,6 @@ impl RefractionChannels { .ok() .and_then(|env_var| ChannelId::from_str(&env_var).ok()) } - - pub fn log_channel_id(&self) -> Option<&ChannelId> { - self.log_channel_id.as_ref() - } - - pub fn welcome_channel_id(&self) -> Option<&ChannelId> { - self.welcome_channel_id.as_ref() - } } impl Config { @@ -65,8 +57,4 @@ impl Config { Self::new(channels) } - - pub fn channels(&self) -> &RefractionChannels { - &self.channels - } } diff --git a/src/config/mod.rs b/src/config/mod.rs index 568a7e2..3507670 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -3,8 +3,8 @@ mod discord; #[derive(Debug, Clone, Default)] pub struct Config { - bot: bot::Config, - discord: discord::Config, + pub bot: bot::Config, + pub discord: discord::Config, } impl Config { @@ -21,12 +21,4 @@ impl Config { Self::new(bot, discord) } - - pub fn bot_config(&self) -> &bot::Config { - &self.bot - } - - pub fn discord_config(&self) -> &discord::Config { - &self.discord - } } diff --git a/src/consts.rs b/src/consts.rs index e53ae36..3b3f6e4 100644 --- a/src/consts.rs +++ b/src/consts.rs @@ -3,18 +3,31 @@ use std::str::FromStr; use poise::serenity_prelude::Colour; +const BLUE: u32 = 0x60A5FA; +const GREEN: u32 = 0x22C55E; +const ORANGE: u32 = 0xFB923C; +const RED: u32 = 0xEF4444; +const YELLOW: u32 = 0xFDE047; + #[derive(Clone, Copy, Debug, Default)] -pub struct Colors(i32); +pub enum Colors { + Blue, + #[default] + Green, + Orange, + Red, + Yellow, +} -impl Colors { - pub const RED: i32 = 0xEF4444; - pub const GREEN: i32 = 0x22C55E; - pub const BLUE: i32 = 0x60A5FA; - pub const YELLOW: i32 = 0xFDE047; - pub const ORANGE: i32 = 0xFB923C; - - pub fn as_i32(self) -> i32 { - self.0 +impl From for Colour { + fn from(value: Colors) -> Self { + Self::from(match &value { + Colors::Blue => BLUE, + Colors::Green => GREEN, + Colors::Orange => ORANGE, + Colors::Red => RED, + Colors::Yellow => YELLOW, + }) } } @@ -22,18 +35,12 @@ impl FromStr for Colors { type Err = (); fn from_str(s: &str) -> Result { match s.to_lowercase().as_str() { - "red" => Ok(Colors(Self::RED)), - "green" => Ok(Colors(Self::GREEN)), - "blue" => Ok(Colors(Self::BLUE)), - "yellow" => Ok(Colors(Self::YELLOW)), - "orange" => Ok(Colors(Self::ORANGE)), + "blue" => Ok(Self::Blue), + "green" => Ok(Self::Green), + "orange" => Ok(Self::Orange), + "red" => Ok(Self::Red), + "yellow" => Ok(Self::Yellow), _ => Err(()), } } } - -impl From for Colour { - fn from(value: Colors) -> Self { - Self::from(value.as_i32()) - } -} diff --git a/src/handlers/error.rs b/src/handlers/error.rs index 16fcda4..58d7a86 100644 --- a/src/handlers/error.rs +++ b/src/handlers/error.rs @@ -1,8 +1,7 @@ -use crate::{consts::Colors, Data}; +use crate::{consts::Colors, Data, Error}; use std::fmt::Write; -use eyre::Report; use log::error; use poise::serenity_prelude::{CreateEmbed, Timestamp}; use poise::{CreateReply, FrameworkError}; @@ -16,7 +15,7 @@ macro_rules! writelne { } } -pub async fn handle(error: FrameworkError<'_, Data, Report>) { +pub async fn handle(error: FrameworkError<'_, Data, Error>) { match error { FrameworkError::Setup { error, framework, .. @@ -34,7 +33,7 @@ pub async fn handle(error: FrameworkError<'_, Data, Report>) { .title("Something went wrong!") .description("oopsie") .timestamp(Timestamp::now()) - .color(Colors::RED); + .color(Colors::Red); let reply = CreateReply::default().embed(embed); diff --git a/src/handlers/event/analyze_logs/issues.rs b/src/handlers/event/analyze_logs/issues.rs index c2254bc..730c9e9 100644 --- a/src/handlers/event/analyze_logs/issues.rs +++ b/src/handlers/event/analyze_logs/issues.rs @@ -198,7 +198,9 @@ async fn outdated_launcher(log: &str, data: &Data) -> Result { if let Ok(version) = storage.launcher_version().await { version } else { - api::github::get_latest_prism_version().await? + let version = api::github::get_latest_prism_version().await?; + storage.cache_launcher_version(&version).await?; + version } } else { trace!("Not caching launcher version, as we're running without a storage backend"); diff --git a/src/handlers/event/analyze_logs/mod.rs b/src/handlers/event/analyze_logs/mod.rs index e490d09..cb2b0b4 100644 --- a/src/handlers/event/analyze_logs/mod.rs +++ b/src/handlers/event/analyze_logs/mod.rs @@ -48,10 +48,10 @@ pub async fn handle(ctx: &Context, message: &Message, data: &Data) -> Result<()> if issues.is_empty() { e = e - .color(Colors::GREEN) + .color(Colors::Green) .description("No issues found automatically"); } else { - e = e.color(Colors::RED); + e = e.color(Colors::Red); for (title, description) in issues { e = e.field(title, description, false); diff --git a/src/handlers/event/analyze_logs/providers/paste_gg.rs b/src/handlers/event/analyze_logs/providers/paste_gg.rs index 7ebead7..60f3c4b 100644 --- a/src/handlers/event/analyze_logs/providers/paste_gg.rs +++ b/src/handlers/event/analyze_logs/providers/paste_gg.rs @@ -19,7 +19,7 @@ impl super::LogProvider for PasteGG { } async fn fetch(&self, content: &str) -> Result { - let files = paste_gg::get_files(content).await?; + let files = paste_gg::files_from(content).await?; let result = files .result .ok_or_eyre("Got an empty result from paste.gg!")?; diff --git a/src/handlers/event/expand_link.rs b/src/handlers/event/expand_link.rs index ae73b56..8e3517f 100644 --- a/src/handlers/event/expand_link.rs +++ b/src/handlers/event/expand_link.rs @@ -4,7 +4,7 @@ use poise::serenity_prelude::{Context, CreateAllowedMentions, CreateMessage, Mes use crate::utils; pub async fn handle(ctx: &Context, message: &Message) -> Result<()> { - let embeds = utils::resolve_message::from_message(ctx, message).await?; + let embeds = utils::messages::from_message(ctx, message).await?; if !embeds.is_empty() { let allowed_mentions = CreateAllowedMentions::new().replied_user(false); diff --git a/src/handlers/event/mod.rs b/src/handlers/event/mod.rs index 89f69d6..dcbfc4d 100644 --- a/src/handlers/event/mod.rs +++ b/src/handlers/event/mod.rs @@ -1,6 +1,5 @@ -use crate::{api, Data}; +use crate::{api, Data, Error}; -use eyre::{Report, Result}; use log::{debug, info, trace}; use poise::serenity_prelude::{ActivityData, Context, FullEvent, OnlineStatus}; use poise::FrameworkContext; @@ -10,20 +9,20 @@ mod delete_on_reaction; mod eta; mod expand_link; mod give_role; -pub mod pluralkit; +mod pluralkit; mod support_onboard; pub async fn handle( ctx: &Context, event: &FullEvent, - _: FrameworkContext<'_, Data, Report>, + _: FrameworkContext<'_, Data, Error>, data: &Data, -) -> Result<()> { +) -> Result<(), Error> { match event { FullEvent::Ready { data_about_bot } => { info!("Logged in as {}!", data_about_bot.user.name); - let latest_minecraft_version = api::prism_meta::get_latest_minecraft_version().await?; + let latest_minecraft_version = api::prism_meta::latest_minecraft_version().await?; let activity = ActivityData::playing(format!("Minecraft {latest_minecraft_version}")); info!("Setting presence to activity {activity:#?}"); diff --git a/src/handlers/event/pluralkit.rs b/src/handlers/event/pluralkit.rs index 27bdba1..dce3c37 100644 --- a/src/handlers/event/pluralkit.rs +++ b/src/handlers/event/pluralkit.rs @@ -15,7 +15,7 @@ pub async fn is_message_proxied(message: &Message) -> Result { ); sleep(PK_DELAY).await; - let proxied = api::pluralkit::get_sender(message.id).await.is_ok(); + let proxied = api::pluralkit::sender_from(message.id).await.is_ok(); Ok(proxied) } @@ -36,7 +36,7 @@ pub async fn handle(_: &Context, msg: &Message, storage: &Storage) -> Result<()> ); sleep(PK_DELAY).await; - if let Ok(sender) = api::pluralkit::get_sender(msg.id).await { + if let Ok(sender) = api::pluralkit::sender_from(msg.id).await { storage.store_user_plurality(sender).await?; } diff --git a/src/main.rs b/src/main.rs index 3e0e101..09de4a0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,18 +1,10 @@ -#![warn(clippy::all, clippy::pedantic, clippy::perf)] -#![allow(clippy::missing_errors_doc)] -#![forbid(unsafe_code)] - use std::{sync::Arc, time::Duration}; -use eyre::{bail, Context as _, Report, Result}; +use eyre::Context as _; use log::{info, trace, warn}; - use poise::{ serenity_prelude as serenity, EditTracker, Framework, FrameworkOptions, PrefixFrameworkOptions, }; - -use owo_colors::OwoColorize; - use tokio::signal::ctrl_c; #[cfg(target_family = "unix")] use tokio::signal::unix::{signal, SignalKind}; @@ -31,7 +23,8 @@ mod utils; use config::Config; use storage::Storage; -type Context<'a> = poise::Context<'a, Data, Report>; +type Error = Box; +type Context<'a> = poise::Context<'a, Data, Error>; #[derive(Clone, Debug, Default)] pub struct Data { @@ -39,21 +32,14 @@ pub struct Data { storage: Option, } -impl Data { - #[must_use] - pub fn new(config: Config, storage: Option) -> Self { - Self { config, storage } - } -} - async fn setup( ctx: &serenity::Context, _: &serenity::Ready, - framework: &Framework, -) -> Result { + framework: &Framework, +) -> Result { let config = Config::new_from_env(); - let storage = if let Some(url) = config.bot_config().redis_url() { + let storage = if let Some(url) = &config.bot.redis_url { Some(Storage::from_url(url)?) } else { None @@ -61,13 +47,15 @@ async fn setup( if let Some(storage) = storage.as_ref() { if !storage.clone().has_connection() { - bail!("You specified a storage backend but there's no connection! Is it running?") + return Err( + "You specified a storage backend but there's no connection! Is it running?".into(), + ); } trace!("Redis connection looks good!"); } - let data = Data::new(config, storage); + let data = Data { config, storage }; poise::builtins::register_globally(ctx, &framework.options().commands).await?; info!("Registered global commands!"); @@ -78,11 +66,11 @@ async fn setup( async fn handle_shutdown(shard_manager: Arc, reason: &str) { warn!("{reason}! Shutting down bot..."); shard_manager.shutdown_all().await; - println!("{}", "Everything is shutdown. Goodbye!".green()); + println!("Everything is shutdown. Goodbye!"); } #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> eyre::Result<()> { dotenvy::dotenv().ok(); color_eyre::install()?; env_logger::init(); @@ -134,7 +122,7 @@ async fn main() -> Result<()> { let mut sigterm = ctrl_close()?; tokio::select! { - result = client.start() => result.map_err(Report::from), + result = client.start() => result.map_err(eyre::Report::from), _ = sigterm.recv() => { handle_shutdown(shard_manager, "Received SIGTERM").await; std::process::exit(0); diff --git a/src/utils/resolve_message.rs b/src/utils/messages.rs similarity index 98% rename from src/utils/resolve_message.rs rename to src/utils/messages.rs index e6bd2dd..f82073a 100644 --- a/src/utils/resolve_message.rs +++ b/src/utils/messages.rs @@ -24,7 +24,7 @@ fn find_first_image(message: &Message) -> Option { } async fn find_real_author_id(message: &Message) -> UserId { - if let Ok(sender) = pluralkit::get_sender(message.id).await { + if let Ok(sender) = pluralkit::sender_from(message.id).await { sender } else { message.author.id diff --git a/src/utils/mod.rs b/src/utils/mod.rs index d12edb2..8d9dd97 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -1,6 +1,6 @@ use poise::serenity_prelude::{CreateEmbedAuthor, User}; -pub mod resolve_message; +pub mod messages; pub fn embed_author_from_user(user: &User) -> CreateEmbedAuthor { CreateEmbedAuthor::new(user.tag()).icon_url( From 5cfb079e359d2bdfd099093b8aa27d4d56445249 Mon Sep 17 00:00:00 2001 From: seth Date: Sun, 31 Mar 2024 19:56:13 -0400 Subject: [PATCH 78/90] nix: improve RUSTFLAGS + add overlay --- Cargo.toml | 6 ------ nix/deployment/static.nix | 2 +- nix/derivation.nix | 22 +++++++++++++++++----- nix/packages.nix | 5 ++++- 4 files changed, 22 insertions(+), 13 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 388def1..71ddf90 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,9 +47,3 @@ pedantic = "warn" perf = "warn" style = "warn" suspicious = "deny" - -[profile.release] -codegen-units = 1 -opt-level = "s" -panic = "abort" -strip = "symbols" diff --git a/nix/deployment/static.nix b/nix/deployment/static.nix index 83d7b2c..319d974 100644 --- a/nix/deployment/static.nix +++ b/nix/deployment/static.nix @@ -28,7 +28,7 @@ buildWith = rustPlatform: self'.packages.refraction.override { inherit rustPlatform; - lto = true; + optimizeSize = true; }; in { packages = diff --git a/nix/derivation.nix b/nix/derivation.nix index 4736d57..9d95a29 100644 --- a/nix/derivation.nix +++ b/nix/derivation.nix @@ -4,7 +4,8 @@ rustPlatform, darwin, self, - lto ? false, + lto ? true, + optimizeSize ? false, }: rustPlatform.buildRustPackage { pname = "refraction"; @@ -35,11 +36,22 @@ rustPlatform.buildRustPackage { SystemConfiguration ]); - env = { - CARGO_BUILD_RUSTFLAGS = lib.concatStringsSep " " ( - lib.optionals lto ["-C" "lto=thin" "-C" "embed-bitcode=yes" "-Zdylib-lto"] + env = let + toRustFlags = lib.mapAttrs' ( + name: + lib.nameValuePair + "CARGO_PROFILE_RELEASE_${lib.toUpper (builtins.replaceStrings ["-"] ["_"] name)}" ); - }; + in + lib.optionalAttrs lto (toRustFlags { + lto = "thin"; + }) + // lib.optionalAttrs optimizeSize (toRustFlags { + codegen-units = "1"; + opt-level = "s"; + panic = "abort"; + strip = "symbols"; + }); meta = with lib; { mainProgram = "refraction"; diff --git a/nix/packages.nix b/nix/packages.nix index 56c5c82..4d1c9cd 100644 --- a/nix/packages.nix +++ b/nix/packages.nix @@ -6,8 +6,11 @@ }: { packages = { refraction = pkgs.callPackage ./derivation.nix {inherit self;}; - default = self'.packages.refraction; }; }; + + flake.overlays.default = _: prev: { + refraction = prev.callPackage ./derivation.nix {inherit self;}; + }; } From bdce1f445561bb3e3f1bb0cbdf0e8a5d79a39f73 Mon Sep 17 00:00:00 2001 From: seth Date: Sun, 31 Mar 2024 19:56:40 -0400 Subject: [PATCH 79/90] nix: use toolchain from refraction package --- nix/dev.nix | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/nix/dev.nix b/nix/dev.nix index 0194f1e..8826a28 100644 --- a/nix/dev.nix +++ b/nix/dev.nix @@ -18,8 +18,6 @@ config.procfiles.daemons.package # rust - cargo - rustc clippy rustfmt rust-analyzer @@ -31,7 +29,8 @@ statix ]; - RUST_SRC_PATH = "${pkgs.rust.packages.stable.rustPlatform.rustLibSrc}"; + inputsFrom = [self'.packages.refraction]; + RUST_SRC_PATH = "${pkgs.rustPlatform.rustLibSrc}"; }; treefmt = { From da95309ed319c1f3705dd4d33ab6a6f15e0d4835 Mon Sep 17 00:00:00 2001 From: seth Date: Sun, 31 Mar 2024 19:56:59 -0400 Subject: [PATCH 80/90] nix: update flake.lock --- flake.lock | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/flake.lock b/flake.lock index 927219c..be76fcd 100644 --- a/flake.lock +++ b/flake.lock @@ -61,11 +61,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1711231723, - "narHash": "sha256-dARJQ8AJOv6U+sdRePkbcVyVbXJTi1tReCrkkOeusiA=", + "lastModified": 1711715736, + "narHash": "sha256-9slQ609YqT9bT/MNX9+5k5jltL9zgpn36DpFB7TkttM=", "owner": "nixos", "repo": "nixpkgs", - "rev": "e1d501922fd7351da4200e1275dfcf5faaad1220", + "rev": "807c549feabce7eddbf259dbdcec9e0600a0660d", "type": "github" }, "original": { @@ -88,11 +88,11 @@ ] }, "locked": { - "lastModified": 1710923068, - "narHash": "sha256-6hOpUiuxuwpXXc/xfJsBUJeqqgGI+JMJuLo45aG3cKc=", + "lastModified": 1711850184, + "narHash": "sha256-rs5zMkTO+AlVBzgOaskAtY4zix7q3l8PpawfznHotcQ=", "owner": "cachix", "repo": "pre-commit-hooks.nix", - "rev": "e611897ddfdde3ed3eaac4758635d7177ff78673", + "rev": "9fc61b5eb0e50fc42f1d358f5240722907b79726", "type": "github" }, "original": { @@ -142,11 +142,11 @@ ] }, "locked": { - "lastModified": 1711246447, - "narHash": "sha256-g9TOluObcOEKewFo2fR4cn51Y/jSKhRRo4QZckHLop0=", + "lastModified": 1711851236, + "narHash": "sha256-EJ03x3N9ihhonAttkaCrqxb0djDq3URCuDpmVPbNZhA=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "dcc802a6ec4e9cc6a1c8c393327f0c42666f22e4", + "rev": "f258266af947599e8069df1c2e933189270f143a", "type": "github" }, "original": { @@ -177,11 +177,11 @@ ] }, "locked": { - "lastModified": 1710781103, - "narHash": "sha256-nehQK/XTFxfa6rYKtbi8M1w+IU1v5twYhiyA4dg1vpg=", + "lastModified": 1711803027, + "narHash": "sha256-Qic3OvsVLpetchzaIe2hJqgliWXACq2Oee6mBXa/IZQ=", "owner": "numtide", "repo": "treefmt-nix", - "rev": "7ee5aaac63c30d3c97a8c56efe89f3b2aa9ae564", + "rev": "1810d51a015c1730f2fe05a255258649799df416", "type": "github" }, "original": { From dfa4d6665416a731134f5d3b8511e0b59501d72a Mon Sep 17 00:00:00 2001 From: seth Date: Sun, 31 Mar 2024 19:57:27 -0400 Subject: [PATCH 81/90] ci: update actions --- .github/workflows/autobot.yaml | 2 +- .github/workflows/docker.yml | 8 ++++---- .github/workflows/nix.yml | 21 +++++++++++++-------- .github/workflows/update-flake.yml | 8 ++++---- 4 files changed, 22 insertions(+), 17 deletions(-) diff --git a/.github/workflows/autobot.yaml b/.github/workflows/autobot.yaml index 3a5606e..195eabe 100644 --- a/.github/workflows/autobot.yaml +++ b/.github/workflows/autobot.yaml @@ -14,7 +14,7 @@ jobs: if: github.actor == 'dependabot[bot]' steps: - - uses: dependabot/fetch-metadata@v1 + - uses: dependabot/fetch-metadata@v2 id: metadata with: github-token: ${{ github.token }} diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 44776b0..d7afa64 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -19,17 +19,17 @@ jobs: - uses: actions/checkout@v4 - name: Install Nix - uses: DeterminateSystems/nix-installer-action@v9 + uses: DeterminateSystems/nix-installer-action@v10 - name: Setup Nix cache - uses: DeterminateSystems/magic-nix-cache-action@v2 + uses: DeterminateSystems/magic-nix-cache-action@v4 - name: Build Docker image id: build run: | - nix build -L --accept-flake-config .#container-${{ matrix.arch }} + nix build --print-build-logs .#container-${{ matrix.arch }} [ ! -L result ] && exit 1 - echo "path=$(realpath result)" >> "$GITHUB_OUTPUT" + echo "path=$(readlink -f result)" >> "$GITHUB_OUTPUT" - name: Upload image uses: actions/upload-artifact@v4 diff --git a/.github/workflows/nix.yml b/.github/workflows/nix.yml index e44af28..f7fb3e6 100644 --- a/.github/workflows/nix.yml +++ b/.github/workflows/nix.yml @@ -20,27 +20,32 @@ jobs: uses: actions/checkout@v4 - name: Install Nix - uses: DeterminateSystems/nix-installer-action@v9 + uses: DeterminateSystems/nix-installer-action@v10 - name: Setup Nix cache - uses: DeterminateSystems/magic-nix-cache-action@v2 + uses: DeterminateSystems/magic-nix-cache-action@v4 - name: Build refraction - run: nix build -L --fallback + run: nix build --fallback --print-build-logs check: - name: Check - runs-on: ubuntu-latest + name: Check flake + + strategy: + matrix: + os: [ubuntu-latest, macos-latest] + + runs-on: ${{ matrix.os }} steps: - name: Checkout repository uses: actions/checkout@v4 - name: Install Nix - uses: DeterminateSystems/nix-installer-action@v9 + uses: DeterminateSystems/nix-installer-action@v10 - name: Setup Nix cache - uses: DeterminateSystems/magic-nix-cache-action@v2 + uses: DeterminateSystems/magic-nix-cache-action@v4 - name: Run checks - run: nix flake check -L --show-trace + run: nix flake check --print-build-logs --show-trace diff --git a/.github/workflows/update-flake.yml b/.github/workflows/update-flake.yml index fabcaf0..e66c5de 100644 --- a/.github/workflows/update-flake.yml +++ b/.github/workflows/update-flake.yml @@ -18,11 +18,11 @@ jobs: - uses: actions/checkout@v4 - name: Install Nix - uses: nixbuild/nix-quick-install-action@v26 + uses: nixbuild/nix-quick-install-action@v27 - name: Update and create PR - uses: DeterminateSystems/update-flake-lock@v20 + uses: DeterminateSystems/update-flake-lock@v21 with: - commit-msg: 'nix: update inputs' - pr-title: 'nix: update inputs' + commit-msg: 'nix: update flake.lock' + pr-title: 'nix: update flake.lock' token: ${{ github.token }} From a8d6a2b8d7c42dbf49c0f1e607be8555afaee065 Mon Sep 17 00:00:00 2001 From: seth Date: Sun, 31 Mar 2024 20:01:48 -0400 Subject: [PATCH 82/90] nix: use self'.packages as a default in module this allows for the module to always work when only importing it from the flake. if you want to make sure you aren't duplicating nixpkgs inputs, the `nixpkgs` input of this flake should be overriden, or `services.refraction.package` should be set to an overlayed version of refraction (i.e., `pkgs.refraction` after setting `nixpkgs.overlays = [ inputs.refraction.overlays.default ]`) --- nix/deployment/module.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nix/deployment/module.nix b/nix/deployment/module.nix index 46155d7..ec39895 100644 --- a/nix/deployment/module.nix +++ b/nix/deployment/module.nix @@ -23,7 +23,7 @@ in { options.services.refraction = { enable = mkEnableOption "refraction"; package = mkPackageOption ( - withSystem pkgs.stdenv.hostPlatform.system ({pkgs, ...}: pkgs) + withSystem pkgs.stdenv.hostPlatform.system ({self', ...}: self'.packages) ) "refraction" {}; user = mkOption { From 94b12a1069d8eb53317b429fc3997a944f177f74 Mon Sep 17 00:00:00 2001 From: seth Date: Tue, 2 Apr 2024 19:23:15 -0400 Subject: [PATCH 83/90] remove reactions from messages older than n days --- .env.template | 9 +++++++ .gitignore | 4 +-- Cargo.lock | 9 ++++--- Cargo.toml | 1 + src/config/discord.rs | 29 ++++++++++++++------- src/config/mod.rs | 8 +++--- src/handlers/event/block_reaction.rs | 39 ++++++++++++++++++++++++++++ src/handlers/event/mod.rs | 3 +++ src/main.rs | 2 +- 9 files changed, 86 insertions(+), 18 deletions(-) create mode 100644 .env.template create mode 100644 src/handlers/event/block_reaction.rs diff --git a/.env.template b/.env.template new file mode 100644 index 0000000..024ce41 --- /dev/null +++ b/.env.template @@ -0,0 +1,9 @@ +DISCORD_BOT_TOKEN= +DISCORD_DAYS_TO_DELETE_REACTION= +DISCORD_LOG_CHANNEL_ID= +DISCORD_WELCOME_CHANNEL_ID= + +BOT_REDIS_URL="redis://localhost:6379" + +RUST_LOG="refraction=debug" +RUST_BACKTRACE=1 diff --git a/.gitignore b/.gitignore index f07ccc4..b0e79ce 100644 --- a/.gitignore +++ b/.gitignore @@ -17,7 +17,7 @@ Cargo.lock # direnv secrets .env .env.* -!.env.example +!.env.template # Nix .direnv/ @@ -29,4 +29,4 @@ repl-result-out* *.rdb # JetBrains -.idea/ \ No newline at end of file +.idea/ diff --git a/Cargo.lock b/Cargo.lock index f29d4e0..8588b30 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -241,15 +241,17 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.31" +version = "0.4.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" +checksum = "8a0d04d43504c61aa6c7531f1871dd0d418d91130162063b789da00fd7057a5e" dependencies = [ "android-tzdata", "iana-time-zone", + "js-sys", "num-traits", "serde", - "windows-targets 0.48.5", + "wasm-bindgen", + "windows-targets 0.52.0", ] [[package]] @@ -1445,6 +1447,7 @@ dependencies = [ name = "refraction" version = "2.0.0" dependencies = [ + "chrono", "color-eyre", "dotenvy", "enum_dispatch", diff --git a/Cargo.toml b/Cargo.toml index 71ddf90..37c72ee 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,7 @@ serde = "1.0.196" serde_json = "1.0.112" [dependencies] +chrono = "0.4.37" color-eyre = "0.6.2" dotenvy = "0.15.7" enum_dispatch = "0.3.12" diff --git a/src/config/discord.rs b/src/config/discord.rs index 27782b1..e1ef7fd 100644 --- a/src/config/discord.rs +++ b/src/config/discord.rs @@ -1,5 +1,6 @@ use std::str::FromStr; +use eyre::{Context, Result}; use log::{info, warn}; use poise::serenity_prelude::ChannelId; @@ -12,6 +13,7 @@ pub struct RefractionChannels { #[derive(Clone, Debug, Default)] pub struct Config { pub channels: RefractionChannels, + pub days_to_delete_reaction: i64, } impl RefractionChannels { @@ -22,15 +24,15 @@ impl RefractionChannels { } } - pub fn new_from_env() -> Self { - let log_channel_id = Self::get_channel_from_env("DISCORD_LOG_CHANNEL_ID"); + pub fn from_env() -> Self { + let log_channel_id = Self::channel_from_env("DISCORD_LOG_CHANNEL_ID"); if let Some(channel_id) = log_channel_id { info!("Log channel is {channel_id}"); } else { warn!("DISCORD_LOG_CHANNEL_ID is empty; this will disable logging in your server."); } - let welcome_channel_id = Self::get_channel_from_env("DISCORD_WELCOME_CHANNEL_ID"); + let welcome_channel_id = Self::channel_from_env("DISCORD_WELCOME_CHANNEL_ID"); if let Some(channel_id) = welcome_channel_id { info!("Welcome channel is {channel_id}"); } else { @@ -40,7 +42,7 @@ impl RefractionChannels { Self::new(log_channel_id, welcome_channel_id) } - fn get_channel_from_env(var: &str) -> Option { + fn channel_from_env(var: &str) -> Option { std::env::var(var) .ok() .and_then(|env_var| ChannelId::from_str(&env_var).ok()) @@ -48,13 +50,22 @@ impl RefractionChannels { } impl Config { - pub fn new(channels: RefractionChannels) -> Self { - Self { channels } + pub fn new(channels: RefractionChannels, days_to_delete_reaction: i64) -> Self { + Self { + channels, + days_to_delete_reaction, + } } - pub fn from_env() -> Self { - let channels = RefractionChannels::new_from_env(); + pub fn from_env() -> Result { + let channels = RefractionChannels::from_env(); + let days_to_delete_reaction = std::env::var("DISCORD_DAYS_TO_DELETE_REACTION") + .wrap_err("DISCORD_DAYS_TO_DELETE_REACTION is empty! This variable is required.")? + .parse() + .wrap_err("DISCORD_DAYS_TO_DELETE_REACTION is not a number!")?; - Self::new(channels) + info!("Reactions will be deleted on messages older than {days_to_delete_reaction} days"); + + Ok(Self::new(channels, days_to_delete_reaction)) } } diff --git a/src/config/mod.rs b/src/config/mod.rs index 3507670..4ef4071 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -1,3 +1,5 @@ +use eyre::Result; + mod bot; mod discord; @@ -15,10 +17,10 @@ impl Config { } } - pub fn new_from_env() -> Self { + pub fn from_env() -> Result { let bot = bot::Config::from_env(); - let discord = discord::Config::from_env(); + let discord = discord::Config::from_env()?; - Self::new(bot, discord) + Ok(Self::new(bot, discord)) } } diff --git a/src/handlers/event/block_reaction.rs b/src/handlers/event/block_reaction.rs new file mode 100644 index 0000000..888733d --- /dev/null +++ b/src/handlers/event/block_reaction.rs @@ -0,0 +1,39 @@ +use crate::Data; + +use chrono::Duration; +use eyre::{Context as _, Result}; +use log::{debug, trace}; +use poise::serenity_prelude::{Context, Reaction, Timestamp}; + +pub async fn handle(ctx: &Context, reaction: &Reaction, data: &Data) -> Result<()> { + let reaction_type = reaction.emoji.clone(); + let reactor = reaction.user_id; + let message = reaction.message(ctx).await.wrap_err_with(|| { + format!( + "Couldn't get message {} from reaction! We won't be able to check if it's old", + reaction.message_id + ) + })?; + + let time_sent = message.timestamp.to_utc(); + let age = Timestamp::now().signed_duration_since(time_sent); + let max_days = Duration::days(data.config.discord.days_to_delete_reaction); + + if age >= max_days { + // NOTE: if we for some reason **didn't** get the user_id associated with the reaction, + // this will clear **all** reactions of this type. this is intentional as older reactions + // being removed > harmful reactions being kept + debug!( + "Removing reaction {reaction_type} from message {}", + message.id + ); + message.delete_reaction(ctx, reactor, reaction_type).await?; + } else { + trace!( + "Keeping reaction {reaction_type} for message {}", + message.id + ); + } + + Ok(()) +} diff --git a/src/handlers/event/mod.rs b/src/handlers/event/mod.rs index dcbfc4d..bf77748 100644 --- a/src/handlers/event/mod.rs +++ b/src/handlers/event/mod.rs @@ -5,6 +5,7 @@ use poise::serenity_prelude::{ActivityData, Context, FullEvent, OnlineStatus}; use poise::FrameworkContext; mod analyze_logs; +mod block_reaction; mod delete_on_reaction; mod eta; mod expand_link; @@ -71,6 +72,8 @@ pub async fn handle( add_reaction.message_id.to_string(), add_reaction.user_id.unwrap_or_default().to_string() ); + + block_reaction::handle(ctx, add_reaction, data).await?; delete_on_reaction::handle(ctx, add_reaction).await?; } diff --git a/src/main.rs b/src/main.rs index 09de4a0..4034840 100644 --- a/src/main.rs +++ b/src/main.rs @@ -37,7 +37,7 @@ async fn setup( _: &serenity::Ready, framework: &Framework, ) -> Result { - let config = Config::new_from_env(); + let config = Config::from_env()?; let storage = if let Some(url) = &config.bot.redis_url { Some(Storage::from_url(url)?) From 4795e152caa88cd502886dcb6299f6d02ef365e2 Mon Sep 17 00:00:00 2001 From: seth Date: Tue, 2 Apr 2024 19:51:22 -0400 Subject: [PATCH 84/90] block_reaction: log old reactions --- src/handlers/event/block_reaction.rs | 42 ++++++++++++++++++++++++++-- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/src/handlers/event/block_reaction.rs b/src/handlers/event/block_reaction.rs index 888733d..ff9231b 100644 --- a/src/handlers/event/block_reaction.rs +++ b/src/handlers/event/block_reaction.rs @@ -1,9 +1,45 @@ -use crate::Data; +use crate::{config::Config, consts::Colors, Data}; use chrono::Duration; use eyre::{Context as _, Result}; use log::{debug, trace}; -use poise::serenity_prelude::{Context, Reaction, Timestamp}; +use poise::serenity_prelude::{ + Context, CreateEmbed, CreateMessage, Mentionable, Message, Reaction, Timestamp, UserId, +}; + +async fn log_old_react( + ctx: &Context, + config: &Config, + reactor: &Option, + message: &Message, +) -> Result<()> { + let Some(log_channel) = config.discord.channels.log_channel_id else { + debug!("Not logging old reaction; no log channel is set!"); + return Ok(()); + }; + + let mut embed = CreateEmbed::new() + .title("Old message reaction!") + .color(Colors::Red); + + if let Some(reactor) = reactor { + embed = embed.description(format!( + "{} just reacted to {}!", + reactor.mention(), + message.link() + )); + } else { + embed = embed.description(format!( + "Someone (or something...) just reacted to {}!", + message.link() + )); + } + + let message = CreateMessage::new().embed(embed); + log_channel.send_message(ctx, message).await?; + + Ok(()) +} pub async fn handle(ctx: &Context, reaction: &Reaction, data: &Data) -> Result<()> { let reaction_type = reaction.emoji.clone(); @@ -28,6 +64,8 @@ pub async fn handle(ctx: &Context, reaction: &Reaction, data: &Data) -> Result<( message.id ); message.delete_reaction(ctx, reactor, reaction_type).await?; + + log_old_react(ctx, &data.config, &reactor, &message).await?; } else { trace!( "Keeping reaction {reaction_type} for message {}", From 3503dda44dbb3527e66fa0b074a311c936a559cf Mon Sep 17 00:00:00 2001 From: seth Date: Tue, 9 Apr 2024 22:36:07 -0400 Subject: [PATCH 85/90] block_reaction: avoid rate limits --- src/handlers/event/block_reaction.rs | 45 +++++++++++----------------- 1 file changed, 17 insertions(+), 28 deletions(-) diff --git a/src/handlers/event/block_reaction.rs b/src/handlers/event/block_reaction.rs index ff9231b..dac9fcc 100644 --- a/src/handlers/event/block_reaction.rs +++ b/src/handlers/event/block_reaction.rs @@ -1,37 +1,34 @@ use crate::{config::Config, consts::Colors, Data}; use chrono::Duration; -use eyre::{Context as _, Result}; +use eyre::Result; use log::{debug, trace}; use poise::serenity_prelude::{ - Context, CreateEmbed, CreateMessage, Mentionable, Message, Reaction, Timestamp, UserId, + Context, CreateEmbed, CreateMessage, Mentionable, Reaction, Timestamp, }; -async fn log_old_react( - ctx: &Context, - config: &Config, - reactor: &Option, - message: &Message, -) -> Result<()> { +async fn log_old_react(ctx: &Context, config: &Config, reaction: &Reaction) -> Result<()> { let Some(log_channel) = config.discord.channels.log_channel_id else { debug!("Not logging old reaction; no log channel is set!"); return Ok(()); }; + let message_link = reaction + .message_id + .link(reaction.channel_id, reaction.guild_id); + let mut embed = CreateEmbed::new() .title("Old message reaction!") .color(Colors::Red); - if let Some(reactor) = reactor { + if let Some(reactor) = reaction.user_id { embed = embed.description(format!( - "{} just reacted to {}!", + "{} just reacted to {message_link}!", reactor.mention(), - message.link() )); } else { embed = embed.description(format!( - "Someone (or something...) just reacted to {}!", - message.link() + "Someone (or something...) just reacted to {message_link}!" )); } @@ -43,33 +40,25 @@ async fn log_old_react( pub async fn handle(ctx: &Context, reaction: &Reaction, data: &Data) -> Result<()> { let reaction_type = reaction.emoji.clone(); - let reactor = reaction.user_id; - let message = reaction.message(ctx).await.wrap_err_with(|| { - format!( - "Couldn't get message {} from reaction! We won't be able to check if it's old", - reaction.message_id - ) - })?; + let message_id = reaction.message_id; + trace!("Checking if we should block reaction on {message_id}"); - let time_sent = message.timestamp.to_utc(); + let time_sent = message_id.created_at().to_utc(); let age = Timestamp::now().signed_duration_since(time_sent); let max_days = Duration::days(data.config.discord.days_to_delete_reaction); if age >= max_days { - // NOTE: if we for some reason **didn't** get the user_id associated with the reaction, - // this will clear **all** reactions of this type. this is intentional as older reactions - // being removed > harmful reactions being kept debug!( "Removing reaction {reaction_type} from message {}", - message.id + message_id ); - message.delete_reaction(ctx, reactor, reaction_type).await?; - log_old_react(ctx, &data.config, &reactor, &message).await?; + reaction.delete(ctx).await?; + log_old_react(ctx, &data.config, reaction).await?; } else { trace!( "Keeping reaction {reaction_type} for message {}", - message.id + message_id ); } From 921540e2499597385ef8379369e2dafa35cd102c Mon Sep 17 00:00:00 2001 From: seth Date: Wed, 10 Apr 2024 01:03:45 -0400 Subject: [PATCH 86/90] nix: move dev outputs to subflake --- .envrc | 2 +- .github/workflows/docker.yml | 2 +- .github/workflows/nix.yml | 4 +- .github/workflows/update-flake.yml | 60 ++++- flake.lock | 171 +------------- flake.nix | 69 ++---- nix/dev.nix | 69 ------ nix/dev/args.nix | 11 + .../default.nix => dev/docker.nix} | 12 +- nix/dev/flake.lock | 212 ++++++++++++++++++ nix/dev/flake.nix | 65 ++++++ nix/dev/pre-commit.nix | 20 ++ nix/dev/procfiles.nix | 11 + nix/dev/shell.nix | 36 +++ nix/{deployment => dev}/static.nix | 4 +- nix/dev/treefmt.nix | 22 ++ nix/{deployment => }/module.nix | 6 +- nix/packages.nix | 16 -- 18 files changed, 461 insertions(+), 331 deletions(-) delete mode 100644 nix/dev.nix create mode 100644 nix/dev/args.nix rename nix/{deployment/default.nix => dev/docker.nix} (76%) create mode 100644 nix/dev/flake.lock create mode 100644 nix/dev/flake.nix create mode 100644 nix/dev/pre-commit.nix create mode 100644 nix/dev/procfiles.nix create mode 100644 nix/dev/shell.nix rename nix/{deployment => dev}/static.nix (93%) create mode 100644 nix/dev/treefmt.nix rename nix/{deployment => }/module.nix (95%) delete mode 100644 nix/packages.nix diff --git a/.envrc b/.envrc index 1d67506..65f365c 100644 --- a/.envrc +++ b/.envrc @@ -1,5 +1,5 @@ if has nix_direnv_version; then - use flake + use flake ./nix/dev fi dotenv_if_exists diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index d7afa64..2d4748d 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -27,7 +27,7 @@ jobs: - name: Build Docker image id: build run: | - nix build --print-build-logs .#container-${{ matrix.arch }} + nix build --print-build-logs ./nix/dev#container-${{ matrix.arch }} [ ! -L result ] && exit 1 echo "path=$(readlink -f result)" >> "$GITHUB_OUTPUT" diff --git a/.github/workflows/nix.yml b/.github/workflows/nix.yml index f7fb3e6..e038910 100644 --- a/.github/workflows/nix.yml +++ b/.github/workflows/nix.yml @@ -48,4 +48,6 @@ jobs: uses: DeterminateSystems/magic-nix-cache-action@v4 - name: Run checks - run: nix flake check --print-build-logs --show-trace + run: | + cd ./nix/dev + nix flake check --print-build-logs --show-trace diff --git a/.github/workflows/update-flake.yml b/.github/workflows/update-flake.yml index e66c5de..c9931e2 100644 --- a/.github/workflows/update-flake.yml +++ b/.github/workflows/update-flake.yml @@ -8,21 +8,65 @@ on: jobs: update: + name: Run update runs-on: ubuntu-latest permissions: contents: write pull-requests: write + env: + PR_BRANCH: 'update-lockfiles' + steps: - - uses: actions/checkout@v4 + - name: Checkout repository + uses: actions/checkout@v4 - name: Install Nix - uses: nixbuild/nix-quick-install-action@v27 + uses: DeterminateSystems/nix-installer-action@v10 - - name: Update and create PR - uses: DeterminateSystems/update-flake-lock@v21 - with: - commit-msg: 'nix: update flake.lock' - pr-title: 'nix: update flake.lock' - token: ${{ github.token }} + - name: Set Git user info + run: | + git config user.name 'github-actions[bot]' + git config user.email 'github-actions[bot]@users.noreply.github.com' + + - name: Create new branch + id: branch + run: | + git switch -c "$PR_BRANCH" + + - name: Update flake inputs + run: | + pushd nix/dev + nix flake update \ + --commit-lock-file \ + --commit-lockfile-summary "nix: update dev flake.lock" + popd + + nix flake update \ + --commit-lock-file \ + --commit-lockfile-summary "nix: update flake.lock" + + - name: Make PR if needed + env: + GH_TOKEN: ${{ github.token }} + run: | + if ! git diff --color=always --exit-code origin/main; then + git fetch origin "$PR_BRANCH" || true + git push --force-with-lease -u origin "$PR_BRANCH" + + open_prs="$(gh pr list --base main --head "$PR_BRANCH" | wc -l)" + if [ "$open_prs" -eq 0 ]; then + gh pr create \ + --base main \ + --head "$PR_BRANCH" \ + --title "chore: update lockfiles" \ + --fill + fi + fi + + - name: Enable auto-merge + shell: bash + run: gh pr merge --auto --squash + env: + GH_TOKEN: ${{ secrets.MERGE_TOKEN }} diff --git a/flake.lock b/flake.lock index be76fcd..93b865c 100644 --- a/flake.lock +++ b/flake.lock @@ -1,64 +1,5 @@ { "nodes": { - "flake-parts": { - "inputs": { - "nixpkgs-lib": [ - "nixpkgs" - ] - }, - "locked": { - "lastModified": 1709336216, - "narHash": "sha256-Dt/wOWeW6Sqm11Yh+2+t0dfEWxoMxGBvv3JpIocFl9E=", - "owner": "hercules-ci", - "repo": "flake-parts", - "rev": "f7b3c975cf067e56e7cda6cb098ebe3fb4d74ca2", - "type": "github" - }, - "original": { - "owner": "hercules-ci", - "repo": "flake-parts", - "type": "github" - } - }, - "flake-utils": { - "inputs": { - "systems": "systems" - }, - "locked": { - "lastModified": 1710146030, - "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=", - "owner": "numtide", - "repo": "flake-utils", - "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a", - "type": "github" - }, - "original": { - "owner": "numtide", - "repo": "flake-utils", - "type": "github" - } - }, - "gitignore": { - "inputs": { - "nixpkgs": [ - "pre-commit-hooks", - "nixpkgs" - ] - }, - "locked": { - "lastModified": 1709087332, - "narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=", - "owner": "hercules-ci", - "repo": "gitignore.nix", - "rev": "637db329424fd7e46cf4185293b9cc8c88c95394", - "type": "github" - }, - "original": { - "owner": "hercules-ci", - "repo": "gitignore.nix", - "type": "github" - } - }, "nixpkgs": { "locked": { "lastModified": 1711715736, @@ -75,119 +16,9 @@ "type": "github" } }, - "pre-commit-hooks": { - "inputs": { - "flake-compat": [], - "flake-utils": "flake-utils", - "gitignore": "gitignore", - "nixpkgs": [ - "nixpkgs" - ], - "nixpkgs-stable": [ - "nixpkgs" - ] - }, - "locked": { - "lastModified": 1711850184, - "narHash": "sha256-rs5zMkTO+AlVBzgOaskAtY4zix7q3l8PpawfznHotcQ=", - "owner": "cachix", - "repo": "pre-commit-hooks.nix", - "rev": "9fc61b5eb0e50fc42f1d358f5240722907b79726", - "type": "github" - }, - "original": { - "owner": "cachix", - "repo": "pre-commit-hooks.nix", - "type": "github" - } - }, - "procfile-nix": { - "inputs": { - "nixpkgs": [ - "nixpkgs" - ] - }, - "locked": { - "lastModified": 1711158989, - "narHash": "sha256-exgncIe/lQIswv2L1M0y+RrHAg5dofLFCOxGu4/yJww=", - "owner": "getchoo", - "repo": "procfile-nix", - "rev": "6388308f9e9c8a8fbfdff54b30adf486fa292cf9", - "type": "github" - }, - "original": { - "owner": "getchoo", - "repo": "procfile-nix", - "type": "github" - } - }, "root": { "inputs": { - "flake-parts": "flake-parts", - "nixpkgs": "nixpkgs", - "pre-commit-hooks": "pre-commit-hooks", - "procfile-nix": "procfile-nix", - "rust-overlay": "rust-overlay", - "treefmt-nix": "treefmt-nix" - } - }, - "rust-overlay": { - "inputs": { - "flake-utils": [ - "pre-commit-hooks", - "flake-utils" - ], - "nixpkgs": [ - "nixpkgs" - ] - }, - "locked": { - "lastModified": 1711851236, - "narHash": "sha256-EJ03x3N9ihhonAttkaCrqxb0djDq3URCuDpmVPbNZhA=", - "owner": "oxalica", - "repo": "rust-overlay", - "rev": "f258266af947599e8069df1c2e933189270f143a", - "type": "github" - }, - "original": { - "owner": "oxalica", - "repo": "rust-overlay", - "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" - } - }, - "treefmt-nix": { - "inputs": { - "nixpkgs": [ - "nixpkgs" - ] - }, - "locked": { - "lastModified": 1711803027, - "narHash": "sha256-Qic3OvsVLpetchzaIe2hJqgliWXACq2Oee6mBXa/IZQ=", - "owner": "numtide", - "repo": "treefmt-nix", - "rev": "1810d51a015c1730f2fe05a255258649799df416", - "type": "github" - }, - "original": { - "owner": "numtide", - "repo": "treefmt-nix", - "type": "github" + "nixpkgs": "nixpkgs" } } }, diff --git a/flake.nix b/flake.nix index 4685dde..e575b27 100644 --- a/flake.nix +++ b/flake.nix @@ -1,58 +1,31 @@ { description = "Discord bot for Prism Launcher"; - inputs = { - nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable"; - flake-parts = { - url = "github:hercules-ci/flake-parts"; - inputs.nixpkgs-lib.follows = "nixpkgs"; - }; + inputs.nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable"; - pre-commit-hooks = { - url = "github:cachix/pre-commit-hooks.nix"; - inputs = { - nixpkgs.follows = "nixpkgs"; - nixpkgs-stable.follows = "nixpkgs"; - flake-compat.follows = ""; - }; - }; + outputs = { + self, + nixpkgs, + ... + }: let + systems = [ + "x86_64-linux" + "aarch64-linux" + "x86_64-darwin" + "aarch64-darwin" + ]; - procfile-nix = { - url = "github:getchoo/procfile-nix"; - inputs.nixpkgs.follows = "nixpkgs"; - }; + forAllSystems = fn: nixpkgs.lib.genAttrs systems (system: fn nixpkgs.legacyPackages.${system}); + in { + nixosModules.default = import ./nix/module.nix self; - rust-overlay = { - url = "github:oxalica/rust-overlay"; - inputs = { - nixpkgs.follows = "nixpkgs"; - flake-utils.follows = "pre-commit-hooks/flake-utils"; - }; - }; + packages = forAllSystems (pkgs: rec { + refraction = pkgs.callPackage ./nix/derivation.nix {inherit self;}; + default = refraction; + }); - treefmt-nix = { - url = "github:numtide/treefmt-nix"; - inputs.nixpkgs.follows = "nixpkgs"; + overlays.default = _: prev: { + refraction = prev.callPackage ./nix/derivation.nix {inherit self;}; }; }; - - outputs = {flake-parts, ...} @ inputs: - flake-parts.lib.mkFlake {inherit inputs;} { - imports = [ - ./nix/dev.nix - ./nix/packages.nix - ./nix/deployment - - inputs.pre-commit-hooks.flakeModule - inputs.procfile-nix.flakeModule - inputs.treefmt-nix.flakeModule - ]; - - systems = [ - "x86_64-linux" - "aarch64-linux" - "x86_64-darwin" - "aarch64-darwin" - ]; - }; } diff --git a/nix/dev.nix b/nix/dev.nix deleted file mode 100644 index 8826a28..0000000 --- a/nix/dev.nix +++ /dev/null @@ -1,69 +0,0 @@ -{ - perSystem = { - lib, - pkgs, - config, - self', - ... - }: { - devShells.default = pkgs.mkShell { - shellHook = '' - ${config.pre-commit.installationScript} - ''; - - packages = with pkgs; [ - # general - actionlint - nodePackages.prettier - config.procfiles.daemons.package - - # rust - clippy - rustfmt - rust-analyzer - - # nix - self'.formatter - deadnix - nil - statix - ]; - - inputsFrom = [self'.packages.refraction]; - RUST_SRC_PATH = "${pkgs.rustPlatform.rustLibSrc}"; - }; - - treefmt = { - projectRootFile = "flake.nix"; - - programs = { - alejandra.enable = true; - deadnix.enable = true; - prettier.enable = true; - rustfmt.enable = true; - }; - - settings.global = { - excludes = [ - "./target" - "./flake.lock" - "./Cargo.lock" - ]; - }; - }; - - pre-commit.settings.hooks = { - actionlint.enable = true; - nil.enable = true; - statix.enable = true; - treefmt = { - enable = true; - package = config.treefmt.build.wrapper; - }; - }; - - procfiles.daemons.processes = { - redis = lib.getExe' pkgs.redis "redis-server"; - }; - }; -} diff --git a/nix/dev/args.nix b/nix/dev/args.nix new file mode 100644 index 0000000..42b60e2 --- /dev/null +++ b/nix/dev/args.nix @@ -0,0 +1,11 @@ +{inputs, ...}: { + perSystem = { + lib, + system, + ... + }: { + _module.args = { + refraction' = lib.mapAttrs (lib.const (v: v.${system} or v)) (inputs.get-flake ../../.); + }; + }; +} diff --git a/nix/deployment/default.nix b/nix/dev/docker.nix similarity index 76% rename from nix/deployment/default.nix rename to nix/dev/docker.nix index 96eb6ee..f778070 100644 --- a/nix/deployment/default.nix +++ b/nix/dev/docker.nix @@ -1,14 +1,4 @@ -{ - flake-parts-lib, - withSystem, - ... -}: { - imports = [./static.nix]; - - flake.nixosModules.default = flake-parts-lib.importApply ./module.nix { - inherit withSystem; - }; - +{withSystem, ...}: { perSystem = { lib, pkgs, diff --git a/nix/dev/flake.lock b/nix/dev/flake.lock new file mode 100644 index 0000000..8522cb4 --- /dev/null +++ b/nix/dev/flake.lock @@ -0,0 +1,212 @@ +{ + "nodes": { + "flake-parts": { + "inputs": { + "nixpkgs-lib": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1709336216, + "narHash": "sha256-Dt/wOWeW6Sqm11Yh+2+t0dfEWxoMxGBvv3JpIocFl9E=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "f7b3c975cf067e56e7cda6cb098ebe3fb4d74ca2", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "flake-parts", + "type": "github" + } + }, + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1710146030, + "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "get-flake": { + "locked": { + "lastModified": 1694475786, + "narHash": "sha256-s5wDmPooMUNIAAsxxCMMh9g68AueGg63DYk2hVZJbc8=", + "owner": "ursi", + "repo": "get-flake", + "rev": "ac54750e3b95dab6ec0726d77f440efe6045bec1", + "type": "github" + }, + "original": { + "owner": "ursi", + "repo": "get-flake", + "type": "github" + } + }, + "gitignore": { + "inputs": { + "nixpkgs": [ + "pre-commit-hooks", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1709087332, + "narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=", + "owner": "hercules-ci", + "repo": "gitignore.nix", + "rev": "637db329424fd7e46cf4185293b9cc8c88c95394", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "gitignore.nix", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1711715736, + "narHash": "sha256-9slQ609YqT9bT/MNX9+5k5jltL9zgpn36DpFB7TkttM=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "807c549feabce7eddbf259dbdcec9e0600a0660d", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "pre-commit-hooks": { + "inputs": { + "flake-compat": [], + "flake-utils": "flake-utils", + "gitignore": "gitignore", + "nixpkgs": [ + "nixpkgs" + ], + "nixpkgs-stable": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1711850184, + "narHash": "sha256-rs5zMkTO+AlVBzgOaskAtY4zix7q3l8PpawfznHotcQ=", + "owner": "cachix", + "repo": "pre-commit-hooks.nix", + "rev": "9fc61b5eb0e50fc42f1d358f5240722907b79726", + "type": "github" + }, + "original": { + "owner": "cachix", + "repo": "pre-commit-hooks.nix", + "type": "github" + } + }, + "procfile-nix": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1711158989, + "narHash": "sha256-exgncIe/lQIswv2L1M0y+RrHAg5dofLFCOxGu4/yJww=", + "owner": "getchoo", + "repo": "procfile-nix", + "rev": "6388308f9e9c8a8fbfdff54b30adf486fa292cf9", + "type": "github" + }, + "original": { + "owner": "getchoo", + "repo": "procfile-nix", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-parts": "flake-parts", + "get-flake": "get-flake", + "nixpkgs": "nixpkgs", + "pre-commit-hooks": "pre-commit-hooks", + "procfile-nix": "procfile-nix", + "rust-overlay": "rust-overlay", + "treefmt-nix": "treefmt-nix" + } + }, + "rust-overlay": { + "inputs": { + "flake-utils": [ + "pre-commit-hooks", + "flake-utils" + ], + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1711851236, + "narHash": "sha256-EJ03x3N9ihhonAttkaCrqxb0djDq3URCuDpmVPbNZhA=", + "owner": "oxalica", + "repo": "rust-overlay", + "rev": "f258266af947599e8069df1c2e933189270f143a", + "type": "github" + }, + "original": { + "owner": "oxalica", + "repo": "rust-overlay", + "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" + } + }, + "treefmt-nix": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1711803027, + "narHash": "sha256-Qic3OvsVLpetchzaIe2hJqgliWXACq2Oee6mBXa/IZQ=", + "owner": "numtide", + "repo": "treefmt-nix", + "rev": "1810d51a015c1730f2fe05a255258649799df416", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "treefmt-nix", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/nix/dev/flake.nix b/nix/dev/flake.nix new file mode 100644 index 0000000..0f99cb1 --- /dev/null +++ b/nix/dev/flake.nix @@ -0,0 +1,65 @@ +{ + description = "Discord bot for Prism Launcher"; + + inputs = { + nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable"; + flake-parts = { + url = "github:hercules-ci/flake-parts"; + inputs.nixpkgs-lib.follows = "nixpkgs"; + }; + + get-flake.url = "github:ursi/get-flake"; + + pre-commit-hooks = { + url = "github:cachix/pre-commit-hooks.nix"; + inputs = { + nixpkgs.follows = "nixpkgs"; + nixpkgs-stable.follows = "nixpkgs"; + flake-compat.follows = ""; + }; + }; + + procfile-nix = { + url = "github:getchoo/procfile-nix"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + + rust-overlay = { + url = "github:oxalica/rust-overlay"; + inputs = { + nixpkgs.follows = "nixpkgs"; + flake-utils.follows = "pre-commit-hooks/flake-utils"; + }; + }; + + treefmt-nix = { + url = "github:numtide/treefmt-nix"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + }; + + outputs = {flake-parts, ...} @ inputs: + flake-parts.lib.mkFlake {inherit inputs;} { + debug = true; + imports = [ + ./args.nix + ./docker.nix + ./pre-commit.nix + ./procfiles.nix + ./shell.nix + ./static.nix + ./treefmt.nix + + inputs.pre-commit-hooks.flakeModule + inputs.procfile-nix.flakeModule + inputs.treefmt-nix.flakeModule + ]; + + systems = [ + "x86_64-linux" + "aarch64-linux" + "x86_64-darwin" + "aarch64-darwin" + ]; + }; +} diff --git a/nix/dev/pre-commit.nix b/nix/dev/pre-commit.nix new file mode 100644 index 0000000..39d6e9e --- /dev/null +++ b/nix/dev/pre-commit.nix @@ -0,0 +1,20 @@ +{ + perSystem = { + config, + lib, + ... + }: { + pre-commit.settings = { + rootSrc = lib.mkForce ../../.; + hooks = { + actionlint.enable = true; + nil.enable = true; + statix.enable = true; + treefmt = { + enable = true; + package = config.treefmt.build.wrapper; + }; + }; + }; + }; +} diff --git a/nix/dev/procfiles.nix b/nix/dev/procfiles.nix new file mode 100644 index 0000000..c8c8c2e --- /dev/null +++ b/nix/dev/procfiles.nix @@ -0,0 +1,11 @@ +{ + perSystem = { + lib, + pkgs, + ... + }: { + procfiles.daemons.processes = { + redis = lib.getExe' pkgs.redis "redis-server"; + }; + }; +} diff --git a/nix/dev/shell.nix b/nix/dev/shell.nix new file mode 100644 index 0000000..e0adb94 --- /dev/null +++ b/nix/dev/shell.nix @@ -0,0 +1,36 @@ +{ + perSystem = { + pkgs, + config, + self', + refraction', + ... + }: { + devShells.default = pkgs.mkShell { + shellHook = '' + ${config.pre-commit.installationScript} + ''; + + packages = with pkgs; [ + # general + actionlint + nodePackages.prettier + config.procfiles.daemons.package + + # rust + clippy + rustfmt + rust-analyzer + + # nix + self'.formatter + deadnix + nil + statix + ]; + + inputsFrom = [refraction'.packages.refraction]; + RUST_SRC_PATH = "${pkgs.rustPlatform.rustLibSrc}"; + }; + }; +} diff --git a/nix/deployment/static.nix b/nix/dev/static.nix similarity index 93% rename from nix/deployment/static.nix rename to nix/dev/static.nix index 319d974..3a1c470 100644 --- a/nix/deployment/static.nix +++ b/nix/dev/static.nix @@ -3,7 +3,7 @@ lib, pkgs, inputs', - self', + refraction', ... }: let targets = with pkgs.pkgsCross; { @@ -26,7 +26,7 @@ targets; buildWith = rustPlatform: - self'.packages.refraction.override { + refraction'.packages.refraction.override { inherit rustPlatform; optimizeSize = true; }; diff --git a/nix/dev/treefmt.nix b/nix/dev/treefmt.nix new file mode 100644 index 0000000..13a1675 --- /dev/null +++ b/nix/dev/treefmt.nix @@ -0,0 +1,22 @@ +{ + perSystem = { + treefmt = { + projectRootFile = ".git/config"; + + programs = { + alejandra.enable = true; + deadnix.enable = true; + prettier.enable = true; + rustfmt.enable = true; + }; + + settings.global = { + excludes = [ + "./target" + "./flake.lock" + "./Cargo.lock" + ]; + }; + }; + }; +} diff --git a/nix/deployment/module.nix b/nix/module.nix similarity index 95% rename from nix/deployment/module.nix rename to nix/module.nix index ec39895..5572e4b 100644 --- a/nix/deployment/module.nix +++ b/nix/module.nix @@ -1,4 +1,4 @@ -{withSystem, ...}: { +self: { config, lib, pkgs, @@ -22,9 +22,7 @@ in { options.services.refraction = { enable = mkEnableOption "refraction"; - package = mkPackageOption ( - withSystem pkgs.stdenv.hostPlatform.system ({self', ...}: self'.packages) - ) "refraction" {}; + package = mkPackageOption self.packages.${pkgs.stdenv.hostPlatform.system} "refraction" {}; user = mkOption { description = mdDoc '' diff --git a/nix/packages.nix b/nix/packages.nix deleted file mode 100644 index 4d1c9cd..0000000 --- a/nix/packages.nix +++ /dev/null @@ -1,16 +0,0 @@ -{self, ...}: { - perSystem = { - pkgs, - self', - ... - }: { - packages = { - refraction = pkgs.callPackage ./derivation.nix {inherit self;}; - default = self'.packages.refraction; - }; - }; - - flake.overlays.default = _: prev: { - refraction = prev.callPackage ./derivation.nix {inherit self;}; - }; -} From 84a7cfe1517fecc404664cc9badb89c2ce65364b Mon Sep 17 00:00:00 2001 From: seth Date: Fri, 26 Apr 2024 20:53:13 -0400 Subject: [PATCH 87/90] use http client from context --- src/api/dadjoke.rs | 15 ++--- src/api/github.rs | 17 ++--- src/api/mod.rs | 62 +++++++------------ src/api/paste_gg.rs | 17 ++--- src/api/pluralkit.rs | 9 ++- src/api/prism_meta.rs | 12 ++-- src/api/rory.rs | 14 ++--- src/commands/general/joke.rs | 2 +- src/commands/general/rory.rs | 2 +- src/commands/general/stars.rs | 5 +- src/commands/mod.rs | 2 +- src/commands/moderation/set_welcome.rs | 9 ++- src/handlers/event/analyze_logs/issues.rs | 5 +- src/handlers/event/analyze_logs/mod.rs | 2 +- .../event/analyze_logs/providers/0x0.rs | 6 +- .../analyze_logs/providers/attachment.rs | 9 +-- .../event/analyze_logs/providers/haste.rs | 6 +- .../event/analyze_logs/providers/mclogs.rs | 6 +- .../event/analyze_logs/providers/mod.rs | 12 ++-- .../event/analyze_logs/providers/paste_gg.rs | 8 +-- .../event/analyze_logs/providers/pastebin.rs | 6 +- src/handlers/event/expand_link.rs | 8 +-- src/handlers/event/mod.rs | 16 ++--- src/handlers/event/pluralkit.rs | 19 ++++-- src/main.rs | 14 ++++- src/utils/messages.rs | 14 +++-- 26 files changed, 148 insertions(+), 149 deletions(-) diff --git a/src/api/dadjoke.rs b/src/api/dadjoke.rs index 642d656..ac6ca5f 100644 --- a/src/api/dadjoke.rs +++ b/src/api/dadjoke.rs @@ -1,18 +1,11 @@ +use super::{HttpClient, HttpClientExt}; + use eyre::Result; -use log::debug; const DADJOKE: &str = "https://icanhazdadjoke.com"; -pub async fn get_joke() -> Result { - debug!("Making request to {DADJOKE}"); +pub async fn get_joke(http: &HttpClient) -> Result { + let joke = http.get_request(DADJOKE).await?.text().await?; - let resp = super::client() - .get(DADJOKE) - .header("Accept", "text/plain") - .send() - .await?; - resp.error_for_status_ref()?; - - let joke = resp.text().await?; Ok(joke) } diff --git a/src/api/github.rs b/src/api/github.rs index f0a1fa4..18b533a 100644 --- a/src/api/github.rs +++ b/src/api/github.rs @@ -1,18 +1,11 @@ -use std::sync::OnceLock; - -use eyre::{Context, OptionExt, Result}; +use eyre::{OptionExt, Result, WrapErr}; use log::debug; use octocrab::Octocrab; -fn octocrab() -> &'static Octocrab { - static OCTOCRAB: OnceLock = OnceLock::new(); - OCTOCRAB.get_or_init(Octocrab::default) -} - -pub async fn get_latest_prism_version() -> Result { +pub async fn get_latest_prism_version(octocrab: &Octocrab) -> Result { debug!("Fetching the latest version of Prism Launcher"); - let version = octocrab() + let version = octocrab .repos("PrismLauncher", "PrismLauncher") .releases() .get_latest() @@ -22,10 +15,10 @@ pub async fn get_latest_prism_version() -> Result { Ok(version) } -pub async fn get_prism_stargazers_count() -> Result { +pub async fn get_prism_stargazers_count(octocrab: &Octocrab) -> Result { debug!("Fetching Prism Launcher's stargazer count"); - let stargazers_count = octocrab() + let stargazers_count = octocrab .repos("PrismLauncher", "PrismLauncher") .get() .await diff --git a/src/api/mod.rs b/src/api/mod.rs index 711334c..cb1c7bf 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -1,9 +1,5 @@ -use std::sync::OnceLock; - -use eyre::Result; -use log::debug; -use reqwest::{Client, Response}; -use serde::de::DeserializeOwned; +use log::trace; +use reqwest::Response; pub mod dadjoke; pub mod github; @@ -12,43 +8,29 @@ pub mod pluralkit; pub mod prism_meta; pub mod rory; -pub fn client() -> &'static reqwest::Client { - static CLIENT: OnceLock = OnceLock::new(); - CLIENT.get_or_init(|| { +pub type HttpClient = reqwest::Client; + +pub trait HttpClientExt { + // sadly i can't implement the actual Default trait :/ + fn default() -> Self; + async fn get_request(&self, url: &str) -> Result; +} + +impl HttpClientExt for HttpClient { + fn default() -> Self { let version = option_env!("CARGO_PKG_VERSION").unwrap_or("development"); let user_agent = format!("refraction/{version}"); - Client::builder() + reqwest::ClientBuilder::new() .user_agent(user_agent) .build() .unwrap_or_default() - }) -} - -pub async fn get_url(url: &str) -> Result { - debug!("Making request to {url}"); - let resp = client().get(url).send().await?; - resp.error_for_status_ref()?; - - Ok(resp) -} - -pub async fn text_from_url(url: &str) -> Result { - let resp = get_url(url).await?; - - let text = resp.text().await?; - Ok(text) -} - -pub async fn bytes_from_url(url: &str) -> Result> { - let resp = get_url(url).await?; - - let bytes = resp.bytes().await?; - Ok(bytes.to_vec()) -} - -pub async fn json_from_url(url: &str) -> Result { - let resp = get_url(url).await?; - - let json = resp.json().await?; - Ok(json) + } + + async fn get_request(&self, url: &str) -> Result { + trace!("Making request to {url}"); + let resp = self.get(url).send().await?; + resp.error_for_status_ref()?; + + Ok(resp) + } } diff --git a/src/api/paste_gg.rs b/src/api/paste_gg.rs index 01df669..a4004cb 100644 --- a/src/api/paste_gg.rs +++ b/src/api/paste_gg.rs @@ -1,5 +1,6 @@ +use super::{HttpClient, HttpClientExt}; + use eyre::{eyre, OptionExt, Result}; -use log::debug; use serde::{Deserialize, Serialize}; const PASTE_GG: &str = "https://api.paste.gg/v1"; @@ -27,11 +28,9 @@ pub struct Files { pub name: Option, } -pub async fn files_from(id: &str) -> Result> { +pub async fn files_from(http: &HttpClient, id: &str) -> Result> { let url = format!("{PASTE_GG}{PASTES}/{id}/files"); - debug!("Making request to {url}"); - - let resp: Response = super::json_from_url(&url).await?; + let resp: Response = http.get_request(&url).await?.json().await?; if resp.status == Status::Error { let message = resp @@ -44,9 +43,13 @@ pub async fn files_from(id: &str) -> Result> { } } -pub async fn get_raw_file(paste_id: &str, file_id: &str) -> eyre::Result { +pub async fn get_raw_file( + http: &HttpClient, + paste_id: &str, + file_id: &str, +) -> eyre::Result { let url = format!("{PASTE_GG}{PASTES}/{paste_id}/files/{file_id}/raw"); - let text = super::text_from_url(&url).await?; + let text = http.get_request(&url).await?.text().await?; Ok(text) } diff --git a/src/api/pluralkit.rs b/src/api/pluralkit.rs index 2fdb4cb..0b16793 100644 --- a/src/api/pluralkit.rs +++ b/src/api/pluralkit.rs @@ -1,5 +1,6 @@ +use super::{HttpClient, HttpClientExt}; + use eyre::{Context, Result}; -use log::debug; use poise::serenity_prelude::{MessageId, UserId}; use serde::{Deserialize, Serialize}; @@ -11,11 +12,9 @@ pub struct Message { const PLURAL_KIT: &str = "https://api.pluralkit.me/v2"; const MESSAGES: &str = "/messages"; -pub async fn sender_from(message_id: MessageId) -> Result { +pub async fn sender_from(http: &HttpClient, message_id: MessageId) -> Result { let url = format!("{PLURAL_KIT}{MESSAGES}/{message_id}"); - debug!("Making request to {url}"); - - let resp: Message = super::json_from_url(&url).await?; + let resp: Message = http.get_request(&url).await?.json().await?; let id: u64 = resp.sender.parse().wrap_err_with(|| { diff --git a/src/api/prism_meta.rs b/src/api/prism_meta.rs index e2114b7..f7efb0a 100644 --- a/src/api/prism_meta.rs +++ b/src/api/prism_meta.rs @@ -1,5 +1,6 @@ +use super::{HttpClient, HttpClientExt}; + use eyre::{OptionExt, Result}; -use log::debug; use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize)] @@ -14,14 +15,9 @@ pub struct MinecraftPackageJson { const META: &str = "https://meta.prismlauncher.org/v1"; const MINECRAFT_PACKAGEJSON: &str = "/net.minecraft/package.json"; -pub async fn latest_minecraft_version() -> Result { +pub async fn latest_minecraft_version(http: &HttpClient) -> Result { let url = format!("{META}{MINECRAFT_PACKAGEJSON}"); - - debug!("Making request to {url}"); - let resp = super::client().get(url).send().await?; - resp.error_for_status_ref()?; - - let data: MinecraftPackageJson = resp.json().await?; + let data: MinecraftPackageJson = http.get_request(&url).await?.json().await?; let version = data .recommended diff --git a/src/api/rory.rs b/src/api/rory.rs index bb64a0c..3fffbbe 100644 --- a/src/api/rory.rs +++ b/src/api/rory.rs @@ -1,5 +1,6 @@ +use super::{HttpClient, HttpClientExt}; + use eyre::{Context, Result}; -use log::debug; use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize)] @@ -12,16 +13,13 @@ pub struct Response { const RORY: &str = "https://rory.cat"; const PURR: &str = "/purr"; -pub async fn get(id: Option) -> Result { +pub async fn get(http: &HttpClient, id: Option) -> Result { let target = id.map(|id| id.to_string()).unwrap_or_default(); let url = format!("{RORY}{PURR}/{target}"); - debug!("Making request to {url}"); - - let resp = super::client().get(url).send().await?; - resp.error_for_status_ref()?; - - let data: Response = resp + let data: Response = http + .get_request(&url) + .await? .json() .await .wrap_err("Couldn't parse the rory response!")?; diff --git a/src/commands/general/joke.rs b/src/commands/general/joke.rs index a064997..e08282e 100644 --- a/src/commands/general/joke.rs +++ b/src/commands/general/joke.rs @@ -9,7 +9,7 @@ pub async fn joke(ctx: Context<'_>) -> Result<(), Error> { trace!("Running joke command"); ctx.defer().await?; - let joke = dadjoke::get_joke().await?; + let joke = dadjoke::get_joke(&ctx.data().http_client).await?; ctx.say(joke).await?; Ok(()) diff --git a/src/commands/general/rory.rs b/src/commands/general/rory.rs index bc8cd63..f02783d 100644 --- a/src/commands/general/rory.rs +++ b/src/commands/general/rory.rs @@ -14,7 +14,7 @@ pub async fn rory( ctx.defer().await?; - let rory = rory::get(id).await?; + let rory = rory::get(&ctx.data().http_client, id).await?; let embed = { let embed = CreateEmbed::new(); diff --git a/src/commands/general/stars.rs b/src/commands/general/stars.rs index 703586a..603f435 100644 --- a/src/commands/general/stars.rs +++ b/src/commands/general/stars.rs @@ -8,6 +8,7 @@ use poise::CreateReply; #[poise::command(slash_command, prefix_command, track_edits = true)] pub async fn stars(ctx: Context<'_>) -> Result<(), Error> { trace!("Running stars command"); + let octocrab = &ctx.data().octocrab; ctx.defer().await?; @@ -15,13 +16,13 @@ pub async fn stars(ctx: Context<'_>) -> Result<(), Error> { if let Ok(count) = storage.launcher_stargazer_count().await { count } else { - let count = api::github::get_prism_stargazers_count().await?; + let count = api::github::get_prism_stargazers_count(octocrab).await?; storage.cache_launcher_stargazer_count(count).await?; count } } else { trace!("Not caching launcher stargazer count, as we're running without a storage backend"); - api::github::get_prism_stargazers_count().await? + api::github::get_prism_stargazers_count(octocrab).await? }; let embed = CreateEmbed::new() diff --git a/src/commands/mod.rs b/src/commands/mod.rs index a849933..2292afd 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -32,7 +32,7 @@ module_macro!(moderation); pub type Command = poise::Command; -pub fn get() -> Vec { +pub fn all() -> Vec { vec![ general!(help), general!(joke), diff --git a/src/commands/moderation/set_welcome.rs b/src/commands/moderation/set_welcome.rs index 4882af4..be35ac0 100644 --- a/src/commands/moderation/set_welcome.rs +++ b/src/commands/moderation/set_welcome.rs @@ -1,6 +1,6 @@ use std::{fmt::Write, str::FromStr}; -use crate::{api, utils, Context, Error}; +use crate::{api::HttpClientExt, utils, Context, Error}; use eyre::Result; use log::trace; @@ -138,7 +138,12 @@ pub async fn set_welcome( let downloaded = attachment.download().await?; String::from_utf8(downloaded)? } else if let Some(url) = url { - api::text_from_url(&url).await? + ctx.data() + .http_client + .get_request(&url) + .await? + .text() + .await? } else { ctx.say("A text file or URL must be provided!").await?; return Ok(()); diff --git a/src/handlers/event/analyze_logs/issues.rs b/src/handlers/event/analyze_logs/issues.rs index 730c9e9..00b0e5e 100644 --- a/src/handlers/event/analyze_logs/issues.rs +++ b/src/handlers/event/analyze_logs/issues.rs @@ -192,19 +192,20 @@ async fn outdated_launcher(log: &str, data: &Data) -> Result { return Ok(None); }; + let octocrab = &data.octocrab; let version_from_log = captures[0].replace("Prism Launcher version: ", ""); let latest_version = if let Some(storage) = &data.storage { if let Ok(version) = storage.launcher_version().await { version } else { - let version = api::github::get_latest_prism_version().await?; + let version = api::github::get_latest_prism_version(octocrab).await?; storage.cache_launcher_version(&version).await?; version } } else { trace!("Not caching launcher version, as we're running without a storage backend"); - api::github::get_latest_prism_version().await? + api::github::get_latest_prism_version(octocrab).await? }; if version_from_log < latest_version { diff --git a/src/handlers/event/analyze_logs/mod.rs b/src/handlers/event/analyze_logs/mod.rs index cb2b0b4..6b9cf3e 100644 --- a/src/handlers/event/analyze_logs/mod.rs +++ b/src/handlers/event/analyze_logs/mod.rs @@ -19,7 +19,7 @@ pub async fn handle(ctx: &Context, message: &Message, data: &Data) -> Result<()> ); let channel = message.channel_id; - let log = find_log(message).await; + let log = find_log(&data.http_client, message).await; if log.is_err() { let embed = CreateEmbed::new() diff --git a/src/handlers/event/analyze_logs/providers/0x0.rs b/src/handlers/event/analyze_logs/providers/0x0.rs index 14f7feb..300ae0e 100644 --- a/src/handlers/event/analyze_logs/providers/0x0.rs +++ b/src/handlers/event/analyze_logs/providers/0x0.rs @@ -1,4 +1,4 @@ -use crate::api; +use crate::api::{HttpClient, HttpClientExt}; use std::sync::OnceLock; @@ -21,8 +21,8 @@ impl super::LogProvider for _0x0 { .nth(0) } - async fn fetch(&self, content: &str) -> Result { - let log = api::text_from_url(content).await?; + async fn fetch(&self, http: &HttpClient, content: &str) -> Result { + let log = http.get_request(content).await?.text().await?; Ok(log) } diff --git a/src/handlers/event/analyze_logs/providers/attachment.rs b/src/handlers/event/analyze_logs/providers/attachment.rs index 25ce3cc..4e09c67 100644 --- a/src/handlers/event/analyze_logs/providers/attachment.rs +++ b/src/handlers/event/analyze_logs/providers/attachment.rs @@ -1,9 +1,9 @@ +use crate::api::{HttpClient, HttpClientExt}; + use eyre::Result; use log::trace; use poise::serenity_prelude::Message; -use crate::api; - pub struct Attachment; impl super::LogProvider for Attachment { @@ -21,9 +21,10 @@ impl super::LogProvider for Attachment { .nth(0) } - async fn fetch(&self, content: &str) -> Result { - let attachment = api::bytes_from_url(content).await?; + async fn fetch(&self, http: &HttpClient, content: &str) -> Result { + let attachment = http.get_request(content).await?.bytes().await?.to_vec(); let log = String::from_utf8(attachment)?; + Ok(log) } } diff --git a/src/handlers/event/analyze_logs/providers/haste.rs b/src/handlers/event/analyze_logs/providers/haste.rs index c4113d5..3ad1c18 100644 --- a/src/handlers/event/analyze_logs/providers/haste.rs +++ b/src/handlers/event/analyze_logs/providers/haste.rs @@ -1,4 +1,4 @@ -use crate::api; +use crate::api::{HttpClient, HttpClientExt}; use std::sync::OnceLock; @@ -22,9 +22,9 @@ impl super::LogProvider for Haste { super::get_first_capture(regex, &message.content) } - async fn fetch(&self, content: &str) -> Result { + async fn fetch(&self, http: &HttpClient, content: &str) -> Result { let url = format!("{HASTE}{RAW}/{content}"); - let log = api::text_from_url(&url).await?; + let log = http.get_request(&url).await?.text().await?; Ok(log) } diff --git a/src/handlers/event/analyze_logs/providers/mclogs.rs b/src/handlers/event/analyze_logs/providers/mclogs.rs index b0f0c35..e89009a 100644 --- a/src/handlers/event/analyze_logs/providers/mclogs.rs +++ b/src/handlers/event/analyze_logs/providers/mclogs.rs @@ -1,4 +1,4 @@ -use crate::api; +use crate::api::{HttpClient, HttpClientExt}; use std::sync::OnceLock; @@ -21,9 +21,9 @@ impl super::LogProvider for MCLogs { super::get_first_capture(regex, &message.content) } - async fn fetch(&self, content: &str) -> Result { + async fn fetch(&self, http: &HttpClient, content: &str) -> Result { let url = format!("{MCLOGS}{RAW}/{content}"); - let log = api::text_from_url(&url).await?; + let log = http.get_request(&url).await?.text().await?; Ok(log) } diff --git a/src/handlers/event/analyze_logs/providers/mod.rs b/src/handlers/event/analyze_logs/providers/mod.rs index e20547f..bb9aa4e 100644 --- a/src/handlers/event/analyze_logs/providers/mod.rs +++ b/src/handlers/event/analyze_logs/providers/mod.rs @@ -1,3 +1,5 @@ +use crate::api::HttpClient; + use std::slice::Iter; use enum_dispatch::enum_dispatch; @@ -21,7 +23,7 @@ mod pastebin; #[enum_dispatch] pub trait LogProvider { async fn find_match(&self, message: &Message) -> Option; - async fn fetch(&self, content: &str) -> Result; + async fn fetch(&self, http: &HttpClient, content: &str) -> Result; } fn get_first_capture(regex: &Regex, string: &str) -> Option { @@ -41,7 +43,7 @@ enum Provider { } impl Provider { - pub fn interator() -> Iter<'static, Provider> { + pub fn iterator() -> Iter<'static, Provider> { static PROVIDERS: [Provider; 6] = [ Provider::_0x0st(_0x0st), Provider::Attachment(Attachment), @@ -54,12 +56,12 @@ impl Provider { } } -pub async fn find_log(message: &Message) -> Result> { - let providers = Provider::interator(); +pub async fn find_log(http: &HttpClient, message: &Message) -> Result> { + let providers = Provider::iterator(); for provider in providers { if let Some(found) = provider.find_match(message).await { - let log = provider.fetch(&found).await?; + let log = provider.fetch(http, &found).await?; return Ok(Some(log)); } } diff --git a/src/handlers/event/analyze_logs/providers/paste_gg.rs b/src/handlers/event/analyze_logs/providers/paste_gg.rs index 60f3c4b..8e69514 100644 --- a/src/handlers/event/analyze_logs/providers/paste_gg.rs +++ b/src/handlers/event/analyze_logs/providers/paste_gg.rs @@ -1,4 +1,4 @@ -use crate::api::paste_gg; +use crate::api::{paste_gg, HttpClient}; use std::sync::OnceLock; @@ -18,8 +18,8 @@ impl super::LogProvider for PasteGG { super::get_first_capture(regex, &message.content) } - async fn fetch(&self, content: &str) -> Result { - let files = paste_gg::files_from(content).await?; + async fn fetch(&self, http: &HttpClient, content: &str) -> Result { + let files = paste_gg::files_from(http, content).await?; let result = files .result .ok_or_eyre("Got an empty result from paste.gg!")?; @@ -30,7 +30,7 @@ impl super::LogProvider for PasteGG { .nth(0) .ok_or_eyre("Couldn't get file id from empty paste.gg response!")?; - let log = paste_gg::get_raw_file(content, file_id).await?; + let log = paste_gg::get_raw_file(http, content, file_id).await?; Ok(log) } diff --git a/src/handlers/event/analyze_logs/providers/pastebin.rs b/src/handlers/event/analyze_logs/providers/pastebin.rs index 66feecc..4207706 100644 --- a/src/handlers/event/analyze_logs/providers/pastebin.rs +++ b/src/handlers/event/analyze_logs/providers/pastebin.rs @@ -1,4 +1,4 @@ -use crate::api; +use crate::api::{HttpClient, HttpClientExt}; use std::sync::OnceLock; @@ -22,9 +22,9 @@ impl super::LogProvider for PasteBin { super::get_first_capture(regex, &message.content) } - async fn fetch(&self, content: &str) -> Result { + async fn fetch(&self, http: &HttpClient, content: &str) -> Result { let url = format!("{PASTEBIN}{RAW}/{content}"); - let log = api::text_from_url(&url).await?; + let log = http.get_request(&url).await?.text().await?; Ok(log) } diff --git a/src/handlers/event/expand_link.rs b/src/handlers/event/expand_link.rs index 8e3517f..b336616 100644 --- a/src/handlers/event/expand_link.rs +++ b/src/handlers/event/expand_link.rs @@ -1,10 +1,10 @@ +use crate::{api::HttpClient, utils}; + use eyre::Result; use poise::serenity_prelude::{Context, CreateAllowedMentions, CreateMessage, Message}; -use crate::utils; - -pub async fn handle(ctx: &Context, message: &Message) -> Result<()> { - let embeds = utils::messages::from_message(ctx, message).await?; +pub async fn handle(ctx: &Context, http: &HttpClient, message: &Message) -> Result<()> { + let embeds = utils::messages::from_message(ctx, http, message).await?; if !embeds.is_empty() { let allowed_mentions = CreateAllowedMentions::new().replied_user(false); diff --git a/src/handlers/event/mod.rs b/src/handlers/event/mod.rs index bf77748..82e279a 100644 --- a/src/handlers/event/mod.rs +++ b/src/handlers/event/mod.rs @@ -23,7 +23,8 @@ pub async fn handle( FullEvent::Ready { data_about_bot } => { info!("Logged in as {}!", data_about_bot.user.name); - let latest_minecraft_version = api::prism_meta::latest_minecraft_version().await?; + let latest_minecraft_version = + api::prism_meta::latest_minecraft_version(&data.http_client).await?; let activity = ActivityData::playing(format!("Minecraft {latest_minecraft_version}")); info!("Setting presence to activity {activity:#?}"); @@ -37,7 +38,7 @@ pub async fn handle( } FullEvent::Message { new_message } => { - trace!("Recieved message {}", new_message.content); + trace!("Received message {}", new_message.content); // ignore new messages from bots // note: the webhook_id check allows us to still respond to PK users @@ -49,11 +50,12 @@ pub async fn handle( } if let Some(storage) = &data.storage { + let http = &data.http_client; // detect PK users first to make sure we don't respond to unproxied messages - pluralkit::handle(ctx, new_message, storage).await?; + pluralkit::handle(ctx, http, storage, new_message).await?; if storage.is_user_plural(new_message.author.id).await? - && pluralkit::is_message_proxied(new_message).await? + && pluralkit::is_message_proxied(http, new_message).await? { debug!("Not replying to unproxied PluralKit message"); return Ok(()); @@ -61,13 +63,13 @@ pub async fn handle( } eta::handle(ctx, new_message).await?; - expand_link::handle(ctx, new_message).await?; + expand_link::handle(ctx, &data.http_client, new_message).await?; analyze_logs::handle(ctx, new_message, data).await?; } FullEvent::ReactionAdd { add_reaction } => { trace!( - "Recieved reaction {} on message {} from {}", + "Received reaction {} on message {} from {}", add_reaction.emoji, add_reaction.message_id.to_string(), add_reaction.user_id.unwrap_or_default().to_string() @@ -78,7 +80,7 @@ pub async fn handle( } FullEvent::ThreadCreate { thread } => { - trace!("Recieved thread {}", thread.id); + trace!("Received thread {}", thread.id); support_onboard::handle(ctx, thread).await?; } diff --git a/src/handlers/event/pluralkit.rs b/src/handlers/event/pluralkit.rs index dce3c37..a53434c 100644 --- a/src/handlers/event/pluralkit.rs +++ b/src/handlers/event/pluralkit.rs @@ -1,4 +1,8 @@ -use crate::{api, storage::Storage}; +use crate::{ + api::{self, HttpClient}, + storage::Storage, +}; + use std::time::Duration; use eyre::Result; @@ -8,19 +12,24 @@ use tokio::time::sleep; const PK_DELAY: Duration = Duration::from_secs(1); -pub async fn is_message_proxied(message: &Message) -> Result { +pub async fn is_message_proxied(http: &HttpClient, message: &Message) -> Result { trace!( "Waiting on PluralKit API for {} seconds", PK_DELAY.as_secs() ); sleep(PK_DELAY).await; - let proxied = api::pluralkit::sender_from(message.id).await.is_ok(); + let proxied = api::pluralkit::sender_from(http, message.id).await.is_ok(); Ok(proxied) } -pub async fn handle(_: &Context, msg: &Message, storage: &Storage) -> Result<()> { +pub async fn handle( + _: &Context, + http: &HttpClient, + storage: &Storage, + msg: &Message, +) -> Result<()> { if msg.webhook_id.is_none() { return Ok(()); } @@ -36,7 +45,7 @@ pub async fn handle(_: &Context, msg: &Message, storage: &Storage) -> Result<()> ); sleep(PK_DELAY).await; - if let Ok(sender) = api::pluralkit::sender_from(msg.id).await { + if let Ok(sender) = api::pluralkit::sender_from(http, msg.id).await { storage.store_user_plurality(sender).await?; } diff --git a/src/main.rs b/src/main.rs index 4034840..4cefb2e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -30,6 +30,8 @@ type Context<'a> = poise::Context<'a, Data, Error>; pub struct Data { config: Config, storage: Option, + http_client: api::HttpClient, + octocrab: Arc, } async fn setup( @@ -55,7 +57,15 @@ async fn setup( trace!("Redis connection looks good!"); } - let data = Data { config, storage }; + let http_client = api::HttpClient::default(); + let octocrab = octocrab::instance(); + + let data = Data { + config, + storage, + http_client, + octocrab, + }; poise::builtins::register_globally(ctx, &framework.options().commands).await?; info!("Registered global commands!"); @@ -82,7 +92,7 @@ async fn main() -> eyre::Result<()> { serenity::GatewayIntents::non_privileged() | serenity::GatewayIntents::MESSAGE_CONTENT; let options = FrameworkOptions { - commands: commands::get(), + commands: commands::all(), on_error: |error| Box::pin(handlers::handle_error(error)), diff --git a/src/utils/messages.rs b/src/utils/messages.rs index f82073a..830e49f 100644 --- a/src/utils/messages.rs +++ b/src/utils/messages.rs @@ -1,4 +1,4 @@ -use crate::api::pluralkit; +use crate::api::{pluralkit, HttpClient}; use std::{str::FromStr, sync::OnceLock}; @@ -23,8 +23,8 @@ fn find_first_image(message: &Message) -> Option { .map(|res| res.url.clone()) } -async fn find_real_author_id(message: &Message) -> UserId { - if let Ok(sender) = pluralkit::sender_from(message.id).await { +async fn find_real_author_id(http: &HttpClient, message: &Message) -> UserId { + if let Ok(sender) = pluralkit::sender_from(http, message.id).await { sender } else { message.author.id @@ -109,7 +109,11 @@ pub async fn to_embed( Ok(embed) } -pub async fn from_message(ctx: &Context, msg: &Message) -> Result> { +pub async fn from_message( + ctx: &Context, + http: &HttpClient, + msg: &Message, +) -> Result> { static MESSAGE_PATTERN: OnceLock = OnceLock::new(); let message_pattern = MESSAGE_PATTERN.get_or_init(|| Regex::new(r"(?:https?:\/\/)?(?:canary\.|ptb\.)?discord(?:app)?\.com\/channels\/(?\d+)\/(?\d+)\/(?\d+)").unwrap()); @@ -121,7 +125,7 @@ pub async fn from_message(ctx: &Context, msg: &Message) -> Result Date: Tue, 30 Apr 2024 22:06:24 +0200 Subject: [PATCH 88/90] Revert "block_reaction: avoid rate limits" This reverts commit 3503dda44dbb3527e66fa0b074a311c936a559cf. --- src/handlers/event/block_reaction.rs | 45 +++++++++++++++++----------- 1 file changed, 28 insertions(+), 17 deletions(-) diff --git a/src/handlers/event/block_reaction.rs b/src/handlers/event/block_reaction.rs index dac9fcc..ff9231b 100644 --- a/src/handlers/event/block_reaction.rs +++ b/src/handlers/event/block_reaction.rs @@ -1,34 +1,37 @@ use crate::{config::Config, consts::Colors, Data}; use chrono::Duration; -use eyre::Result; +use eyre::{Context as _, Result}; use log::{debug, trace}; use poise::serenity_prelude::{ - Context, CreateEmbed, CreateMessage, Mentionable, Reaction, Timestamp, + Context, CreateEmbed, CreateMessage, Mentionable, Message, Reaction, Timestamp, UserId, }; -async fn log_old_react(ctx: &Context, config: &Config, reaction: &Reaction) -> Result<()> { +async fn log_old_react( + ctx: &Context, + config: &Config, + reactor: &Option, + message: &Message, +) -> Result<()> { let Some(log_channel) = config.discord.channels.log_channel_id else { debug!("Not logging old reaction; no log channel is set!"); return Ok(()); }; - let message_link = reaction - .message_id - .link(reaction.channel_id, reaction.guild_id); - let mut embed = CreateEmbed::new() .title("Old message reaction!") .color(Colors::Red); - if let Some(reactor) = reaction.user_id { + if let Some(reactor) = reactor { embed = embed.description(format!( - "{} just reacted to {message_link}!", + "{} just reacted to {}!", reactor.mention(), + message.link() )); } else { embed = embed.description(format!( - "Someone (or something...) just reacted to {message_link}!" + "Someone (or something...) just reacted to {}!", + message.link() )); } @@ -40,25 +43,33 @@ async fn log_old_react(ctx: &Context, config: &Config, reaction: &Reaction) -> R pub async fn handle(ctx: &Context, reaction: &Reaction, data: &Data) -> Result<()> { let reaction_type = reaction.emoji.clone(); - let message_id = reaction.message_id; - trace!("Checking if we should block reaction on {message_id}"); + let reactor = reaction.user_id; + let message = reaction.message(ctx).await.wrap_err_with(|| { + format!( + "Couldn't get message {} from reaction! We won't be able to check if it's old", + reaction.message_id + ) + })?; - let time_sent = message_id.created_at().to_utc(); + let time_sent = message.timestamp.to_utc(); let age = Timestamp::now().signed_duration_since(time_sent); let max_days = Duration::days(data.config.discord.days_to_delete_reaction); if age >= max_days { + // NOTE: if we for some reason **didn't** get the user_id associated with the reaction, + // this will clear **all** reactions of this type. this is intentional as older reactions + // being removed > harmful reactions being kept debug!( "Removing reaction {reaction_type} from message {}", - message_id + message.id ); + message.delete_reaction(ctx, reactor, reaction_type).await?; - reaction.delete(ctx).await?; - log_old_react(ctx, &data.config, reaction).await?; + log_old_react(ctx, &data.config, &reactor, &message).await?; } else { trace!( "Keeping reaction {reaction_type} for message {}", - message_id + message.id ); } From a51210289b8f88d3e4e4d47a51aa5a4651d9a282 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Tue, 30 Apr 2024 22:06:25 +0200 Subject: [PATCH 89/90] Revert "block_reaction: log old reactions" This reverts commit 4795e152caa88cd502886dcb6299f6d02ef365e2. --- src/handlers/event/block_reaction.rs | 42 ++-------------------------- 1 file changed, 2 insertions(+), 40 deletions(-) diff --git a/src/handlers/event/block_reaction.rs b/src/handlers/event/block_reaction.rs index ff9231b..888733d 100644 --- a/src/handlers/event/block_reaction.rs +++ b/src/handlers/event/block_reaction.rs @@ -1,45 +1,9 @@ -use crate::{config::Config, consts::Colors, Data}; +use crate::Data; use chrono::Duration; use eyre::{Context as _, Result}; use log::{debug, trace}; -use poise::serenity_prelude::{ - Context, CreateEmbed, CreateMessage, Mentionable, Message, Reaction, Timestamp, UserId, -}; - -async fn log_old_react( - ctx: &Context, - config: &Config, - reactor: &Option, - message: &Message, -) -> Result<()> { - let Some(log_channel) = config.discord.channels.log_channel_id else { - debug!("Not logging old reaction; no log channel is set!"); - return Ok(()); - }; - - let mut embed = CreateEmbed::new() - .title("Old message reaction!") - .color(Colors::Red); - - if let Some(reactor) = reactor { - embed = embed.description(format!( - "{} just reacted to {}!", - reactor.mention(), - message.link() - )); - } else { - embed = embed.description(format!( - "Someone (or something...) just reacted to {}!", - message.link() - )); - } - - let message = CreateMessage::new().embed(embed); - log_channel.send_message(ctx, message).await?; - - Ok(()) -} +use poise::serenity_prelude::{Context, Reaction, Timestamp}; pub async fn handle(ctx: &Context, reaction: &Reaction, data: &Data) -> Result<()> { let reaction_type = reaction.emoji.clone(); @@ -64,8 +28,6 @@ pub async fn handle(ctx: &Context, reaction: &Reaction, data: &Data) -> Result<( message.id ); message.delete_reaction(ctx, reactor, reaction_type).await?; - - log_old_react(ctx, &data.config, &reactor, &message).await?; } else { trace!( "Keeping reaction {reaction_type} for message {}", From ca7bacb5ac5dffe92df8ac6797e012b8961e237d Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Tue, 30 Apr 2024 22:08:39 +0200 Subject: [PATCH 90/90] Revert "remove reactions from messages older than n days" This reverts commit 94b12a1069d8eb53317b429fc3997a944f177f74. --- .env.template | 9 ------- .gitignore | 4 +-- Cargo.lock | 9 +++---- Cargo.toml | 1 - src/config/discord.rs | 29 +++++++-------------- src/config/mod.rs | 8 +++--- src/handlers/event/block_reaction.rs | 39 ---------------------------- src/handlers/event/mod.rs | 3 --- src/main.rs | 2 +- 9 files changed, 18 insertions(+), 86 deletions(-) delete mode 100644 .env.template delete mode 100644 src/handlers/event/block_reaction.rs diff --git a/.env.template b/.env.template deleted file mode 100644 index 024ce41..0000000 --- a/.env.template +++ /dev/null @@ -1,9 +0,0 @@ -DISCORD_BOT_TOKEN= -DISCORD_DAYS_TO_DELETE_REACTION= -DISCORD_LOG_CHANNEL_ID= -DISCORD_WELCOME_CHANNEL_ID= - -BOT_REDIS_URL="redis://localhost:6379" - -RUST_LOG="refraction=debug" -RUST_BACKTRACE=1 diff --git a/.gitignore b/.gitignore index b0e79ce..f07ccc4 100644 --- a/.gitignore +++ b/.gitignore @@ -17,7 +17,7 @@ Cargo.lock # direnv secrets .env .env.* -!.env.template +!.env.example # Nix .direnv/ @@ -29,4 +29,4 @@ repl-result-out* *.rdb # JetBrains -.idea/ +.idea/ \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 8588b30..f29d4e0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -241,17 +241,15 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.37" +version = "0.4.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a0d04d43504c61aa6c7531f1871dd0d418d91130162063b789da00fd7057a5e" +checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" dependencies = [ "android-tzdata", "iana-time-zone", - "js-sys", "num-traits", "serde", - "wasm-bindgen", - "windows-targets 0.52.0", + "windows-targets 0.48.5", ] [[package]] @@ -1447,7 +1445,6 @@ dependencies = [ name = "refraction" version = "2.0.0" dependencies = [ - "chrono", "color-eyre", "dotenvy", "enum_dispatch", diff --git a/Cargo.toml b/Cargo.toml index 37c72ee..71ddf90 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,6 @@ serde = "1.0.196" serde_json = "1.0.112" [dependencies] -chrono = "0.4.37" color-eyre = "0.6.2" dotenvy = "0.15.7" enum_dispatch = "0.3.12" diff --git a/src/config/discord.rs b/src/config/discord.rs index e1ef7fd..27782b1 100644 --- a/src/config/discord.rs +++ b/src/config/discord.rs @@ -1,6 +1,5 @@ use std::str::FromStr; -use eyre::{Context, Result}; use log::{info, warn}; use poise::serenity_prelude::ChannelId; @@ -13,7 +12,6 @@ pub struct RefractionChannels { #[derive(Clone, Debug, Default)] pub struct Config { pub channels: RefractionChannels, - pub days_to_delete_reaction: i64, } impl RefractionChannels { @@ -24,15 +22,15 @@ impl RefractionChannels { } } - pub fn from_env() -> Self { - let log_channel_id = Self::channel_from_env("DISCORD_LOG_CHANNEL_ID"); + pub fn new_from_env() -> Self { + let log_channel_id = Self::get_channel_from_env("DISCORD_LOG_CHANNEL_ID"); if let Some(channel_id) = log_channel_id { info!("Log channel is {channel_id}"); } else { warn!("DISCORD_LOG_CHANNEL_ID is empty; this will disable logging in your server."); } - let welcome_channel_id = Self::channel_from_env("DISCORD_WELCOME_CHANNEL_ID"); + let welcome_channel_id = Self::get_channel_from_env("DISCORD_WELCOME_CHANNEL_ID"); if let Some(channel_id) = welcome_channel_id { info!("Welcome channel is {channel_id}"); } else { @@ -42,7 +40,7 @@ impl RefractionChannels { Self::new(log_channel_id, welcome_channel_id) } - fn channel_from_env(var: &str) -> Option { + fn get_channel_from_env(var: &str) -> Option { std::env::var(var) .ok() .and_then(|env_var| ChannelId::from_str(&env_var).ok()) @@ -50,22 +48,13 @@ impl RefractionChannels { } impl Config { - pub fn new(channels: RefractionChannels, days_to_delete_reaction: i64) -> Self { - Self { - channels, - days_to_delete_reaction, - } + pub fn new(channels: RefractionChannels) -> Self { + Self { channels } } - pub fn from_env() -> Result { - let channels = RefractionChannels::from_env(); - let days_to_delete_reaction = std::env::var("DISCORD_DAYS_TO_DELETE_REACTION") - .wrap_err("DISCORD_DAYS_TO_DELETE_REACTION is empty! This variable is required.")? - .parse() - .wrap_err("DISCORD_DAYS_TO_DELETE_REACTION is not a number!")?; + pub fn from_env() -> Self { + let channels = RefractionChannels::new_from_env(); - info!("Reactions will be deleted on messages older than {days_to_delete_reaction} days"); - - Ok(Self::new(channels, days_to_delete_reaction)) + Self::new(channels) } } diff --git a/src/config/mod.rs b/src/config/mod.rs index 4ef4071..3507670 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -1,5 +1,3 @@ -use eyre::Result; - mod bot; mod discord; @@ -17,10 +15,10 @@ impl Config { } } - pub fn from_env() -> Result { + pub fn new_from_env() -> Self { let bot = bot::Config::from_env(); - let discord = discord::Config::from_env()?; + let discord = discord::Config::from_env(); - Ok(Self::new(bot, discord)) + Self::new(bot, discord) } } diff --git a/src/handlers/event/block_reaction.rs b/src/handlers/event/block_reaction.rs deleted file mode 100644 index 888733d..0000000 --- a/src/handlers/event/block_reaction.rs +++ /dev/null @@ -1,39 +0,0 @@ -use crate::Data; - -use chrono::Duration; -use eyre::{Context as _, Result}; -use log::{debug, trace}; -use poise::serenity_prelude::{Context, Reaction, Timestamp}; - -pub async fn handle(ctx: &Context, reaction: &Reaction, data: &Data) -> Result<()> { - let reaction_type = reaction.emoji.clone(); - let reactor = reaction.user_id; - let message = reaction.message(ctx).await.wrap_err_with(|| { - format!( - "Couldn't get message {} from reaction! We won't be able to check if it's old", - reaction.message_id - ) - })?; - - let time_sent = message.timestamp.to_utc(); - let age = Timestamp::now().signed_duration_since(time_sent); - let max_days = Duration::days(data.config.discord.days_to_delete_reaction); - - if age >= max_days { - // NOTE: if we for some reason **didn't** get the user_id associated with the reaction, - // this will clear **all** reactions of this type. this is intentional as older reactions - // being removed > harmful reactions being kept - debug!( - "Removing reaction {reaction_type} from message {}", - message.id - ); - message.delete_reaction(ctx, reactor, reaction_type).await?; - } else { - trace!( - "Keeping reaction {reaction_type} for message {}", - message.id - ); - } - - Ok(()) -} diff --git a/src/handlers/event/mod.rs b/src/handlers/event/mod.rs index 82e279a..528317d 100644 --- a/src/handlers/event/mod.rs +++ b/src/handlers/event/mod.rs @@ -5,7 +5,6 @@ use poise::serenity_prelude::{ActivityData, Context, FullEvent, OnlineStatus}; use poise::FrameworkContext; mod analyze_logs; -mod block_reaction; mod delete_on_reaction; mod eta; mod expand_link; @@ -74,8 +73,6 @@ pub async fn handle( add_reaction.message_id.to_string(), add_reaction.user_id.unwrap_or_default().to_string() ); - - block_reaction::handle(ctx, add_reaction, data).await?; delete_on_reaction::handle(ctx, add_reaction).await?; } diff --git a/src/main.rs b/src/main.rs index 4cefb2e..0d6f1b1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -39,7 +39,7 @@ async fn setup( _: &serenity::Ready, framework: &Framework, ) -> Result { - let config = Config::from_env()?; + let config = Config::new_from_env(); let storage = if let Some(url) = &config.bot.redis_url { Some(Storage::from_url(url)?)