diff --git a/discord_bot/.dockerignore b/discord_bot/.dockerignore new file mode 100644 index 0000000000..ad742cd46d --- /dev/null +++ b/discord_bot/.dockerignore @@ -0,0 +1,4 @@ +# flyctl launch added from .gitignore +target +**/.env +fly.toml diff --git a/discord_bot/.gitignore b/discord_bot/.gitignore new file mode 100644 index 0000000000..d918126ce6 --- /dev/null +++ b/discord_bot/.gitignore @@ -0,0 +1,2 @@ +/target/ +.env diff --git a/discord_bot/Cargo.lock b/discord_bot/Cargo.lock new file mode 100644 index 0000000000..4a980a8fdc --- /dev/null +++ b/discord_bot/Cargo.lock @@ -0,0 +1,2723 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "actix-codec" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "617a8268e3537fe1d8c9ead925fca49ef6400927ee7bc26750e90ecee14ce4b8" +dependencies = [ + "bitflags 1.3.2", + "bytes", + "futures-core", + "futures-sink", + "memchr", + "pin-project-lite", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "actix-http" +version = "3.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "129d4c88e98860e1758c5de288d1632b07970a16d59bdf7b8d66053d582bb71f" +dependencies = [ + "actix-codec", + "actix-rt", + "actix-service", + "actix-utils", + "ahash", + "base64", + "bitflags 2.4.2", + "brotli", + "bytes", + "bytestring", + "derive_more", + "encoding_rs", + "flate2", + "futures-core", + "h2", + "http", + "httparse", + "httpdate", + "itoa", + "language-tags", + "local-channel", + "mime", + "percent-encoding", + "pin-project-lite", + "rand", + "sha1", + "smallvec", + "tokio", + "tokio-util", + "tracing", + "zstd", +] + +[[package]] +name = "actix-macros" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01ed3140b2f8d422c68afa1ed2e85d996ea619c988ac834d255db32138655cb" +dependencies = [ + "quote", + "syn 2.0.48", +] + +[[package]] +name = "actix-router" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d22475596539443685426b6bdadb926ad0ecaefdfc5fb05e5e3441f15463c511" +dependencies = [ + "bytestring", + "http", + "regex", + "serde", + "tracing", +] + +[[package]] +name = "actix-rt" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28f32d40287d3f402ae0028a9d54bef51af15c8769492826a69d28f81893151d" +dependencies = [ + "futures-core", + "tokio", +] + +[[package]] +name = "actix-server" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eb13e7eef0423ea6eab0e59f6c72e7cb46d33691ad56a726b3cd07ddec2c2d4" +dependencies = [ + "actix-rt", + "actix-service", + "actix-utils", + "futures-core", + "futures-util", + "mio", + "socket2", + "tokio", + "tracing", +] + +[[package]] +name = "actix-service" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b894941f818cfdc7ccc4b9e60fa7e53b5042a2e8567270f9147d5591893373a" +dependencies = [ + "futures-core", + "paste", + "pin-project-lite", +] + +[[package]] +name = "actix-utils" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88a1dcdff1466e3c2488e1cb5c36a71822750ad43839937f85d2f4d9f8b705d8" +dependencies = [ + "local-waker", + "pin-project-lite", +] + +[[package]] +name = "actix-web" +version = "4.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e43428f3bf11dee6d166b00ec2df4e3aa8cc1606aaa0b7433c146852e2f4e03b" +dependencies = [ + "actix-codec", + "actix-http", + "actix-macros", + "actix-router", + "actix-rt", + "actix-server", + "actix-service", + "actix-utils", + "actix-web-codegen", + "ahash", + "bytes", + "bytestring", + "cfg-if", + "cookie", + "derive_more", + "encoding_rs", + "futures-core", + "futures-util", + "itoa", + "language-tags", + "log", + "mime", + "once_cell", + "pin-project-lite", + "regex", + "serde", + "serde_json", + "serde_urlencoded", + "smallvec", + "socket2", + "time", + "url", +] + +[[package]] +name = "actix-web-codegen" +version = "4.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb1f50ebbb30eca122b188319a4398b3f7bb4a8cdf50ecfb73bfc6a3c3ce54f5" +dependencies = [ + "actix-router", + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[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 = "ahash" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77c3a9648d43b9cd48db467b3f87fdd6e146bcc88ab0180006cef2179fe11d01" +dependencies = [ + "cfg-if", + "getrandom", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +dependencies = [ + "memchr", +] + +[[package]] +name = "alloc-no-stdlib" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" + +[[package]] +name = "alloc-stdlib" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" +dependencies = [ + "alloc-no-stdlib", +] + +[[package]] +name = "allocator-api2" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" + +[[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 = "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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bddcadddf5e9015d310179a59bb28c4d4b9920ad0f11e8e14dbadf654890c9a6" + +[[package]] +name = "arrayvec" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" +dependencies = [ + "serde", +] + +[[package]] +name = "async-trait" +version = "0.1.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[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.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[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.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" + +[[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 = "brotli" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "516074a47ef4bce09577a3b379392300159ce5b1ba2e501ff1c819950066100f" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor", +] + +[[package]] +name = "brotli-decompressor" +version = "2.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e2e4afe60d7dd600fdd3de8d0f08c2b7ec039712e3b6137ff98b7004e82de4f" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] + +[[package]] +name = "bumpalo" +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" +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 = "bytestring" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74d80203ea6b29df88012294f62733de21cfeab47f17b41af3a38bc30a03ee72" +dependencies = [ + "bytes", +] + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "jobserver", + "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.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41daef31d7a747c5c847246f36de49ced6f7403b4cdabc807a97b5cc184cda7a" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "num-traits", + "serde", + "windows-targets 0.52.0", +] + +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + +[[package]] +name = "cookie" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb" +dependencies = [ + "percent-encoding", + "time", + "version_check", +] + +[[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.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +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 = "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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "darling" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0209d94da627ab5605dcccf08bb18afa5009cfbef48d8a8b7d7bdbc79be25c5e" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "177e3443818124b357d8e76f53be906d60937f0d3a90773a664fa63fa253e621" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.48", +] + +[[package]] +name = "darling_macro" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.48", +] + +[[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 = "data-encoding" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5" + +[[package]] +name = "deranged" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +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 = "derive_more" +version = "0.99.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "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", + "subtle", +] + +[[package]] +name = "discord_bot" +version = "0.1.0" +dependencies = [ + "actix-web", + "arc-swap", + "async-trait", + "dotenv", + "env_logger", + "hex", + "hex-literal", + "hmac", + "indoc", + "itertools", + "once_cell", + "poise", + "reqwest", + "serde", + "serde_json", + "sha2", + "strum", + "thiserror", + "tokio", + "tokio-util", +] + +[[package]] +name = "dotenv" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" + +[[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_filter" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a009aa4810eb158359dda09d0c87378e4bbb89b5a801f016885a4707ba24f7ea" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "env_logger" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9eeb342678d785662fd2514be38c459bb925f02b68dd2a3e0f21d7ef82d979dd" +dependencies = [ + "anstream", + "anstyle", + "env_filter", + "humantime", + "log", +] + +[[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 = "error-chain" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d2f06b9cac1506ece98fe3231e3cc9c4410ec3d5b1f24ae1c8946f0742cdefc" +dependencies = [ + "version_check", +] + +[[package]] +name = "fastrand" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" + +[[package]] +name = "flate2" +version = "1.0.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 = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[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.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" + +[[package]] +name = "futures-io" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" + +[[package]] +name = "futures-macro" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "futures-sink" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" + +[[package]] +name = "futures-task" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" + +[[package]] +name = "futures-util" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "gimli" +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 = "h2" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb2c4422095b67ee78da96fbb51a4cc413b3b25883c7717ff7ca1ab31022c9c9" +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" +dependencies = [ + "ahash", + "allocator-api2", +] + +[[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 = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hex-literal" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[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.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[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.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "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", + "rustls", + "tokio", + "tokio-rustls", +] + +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes", + "hyper", + "native-tls", + "tokio", + "tokio-native-tls", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.59" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6a67363e2aa4443928ce15e57ebae94fd8949958fd1223c4cfc0cd473ad7539" +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 = "indexmap" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "indoc" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e186cfbae8084e513daff4240b4797e342f988cecda4fb6c939150f96315fd8" + +[[package]] +name = "ipnet" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" + +[[package]] +name = "itertools" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25db6b064527c5d482d0423354fcd07a89a2dfe07b67892e62411946db7f07b0" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" + +[[package]] +name = "jobserver" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c37f63953c4c63420ed5fd3d6d398c719489b9f872b9fa683262f8edd363c7d" +dependencies = [ + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.67" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a1d36f1235bc969acba30b7f5990b864423a6068a10f7c90ae8f0112e3a59d1" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "language-tags" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388" + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.152" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7" + +[[package]] +name = "linux-raw-sys" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" + +[[package]] +name = "local-channel" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6cbc85e69b8df4b8bb8b89ec634e7189099cea8927a276b7384ce5488e53ec8" +dependencies = [ + "futures-core", + "futures-sink", + "local-waker", +] + +[[package]] +name = "local-waker" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d873d7c67ce09b42110d801813efbc9364414e356be9935700d368351657487" + +[[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.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" + +[[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 = "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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +dependencies = [ + "adler", +] + +[[package]] +name = "mio" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys 0.48.0", +] + +[[package]] +name = "native-tls" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" +dependencies = [ + "lazy_static", + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[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.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "openssl" +version = "0.10.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79a4c6c3a2b158f7f8f2a2fc5a969fa3a068df6fc9dbb4a43845436e3af7c800" +dependencies = [ + "bitflags 2.4.2", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-sys" +version = "0.9.96" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3812c071ba60da8b5677cc12bcb1d42989a65553772897a7e0355545a819838f" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[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 = "paste" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[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 = "pkg-config" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" + +[[package]] +name = "poise" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1819d5a45e3590ef33754abce46432570c54a120798bdbf893112b4211fa09a6" +dependencies = [ + "async-trait", + "derivative", + "futures-util", + "parking_lot", + "poise_macros", + "regex", + "serenity", + "tokio", + "tracing", +] + +[[package]] +name = "poise_macros" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fa2c123c961e78315cd3deac7663177f12be4460f5440dbf62a7ed37b1effea" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[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.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "pulldown-cmark" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a1a2f1f0a7ecff9c31abbe177637be0e97a0aef46cf8738ece09327985d998" +dependencies = [ + "bitflags 1.3.2", + "memchr", + "unicase", +] + +[[package]] +name = "quote" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +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 = "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 = "regex" +version = "1.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b7fa1134405e2ec9353fd416b17f8dacd46c473d7d3fd1cf202706a14eb792a" +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.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37b1ae8d9ac08420c66222fb9096fc5de435c3c48542bc5336c51892cffafb41" +dependencies = [ + "base64", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper", + "hyper-rustls", + "hyper-tls", + "ipnet", + "js-sys", + "log", + "mime", + "mime_guess", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls", + "rustls-pemfile", + "serde", + "serde_json", + "serde_urlencoded", + "system-configuration", + "tokio", + "tokio-native-tls", + "tokio-rustls", + "tokio-util", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", + "webpki-roots", + "winreg", +] + +[[package]] +name = "ring" +version = "0.17.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "688c63d65483050968b2a8937f7995f443e27041a0f7700aa59b0822aedebb74" +dependencies = [ + "cc", + "getrandom", + "libc", + "spin", + "untrusted", + "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 = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "0.38.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "322394588aaf33c24007e8bb3238ee3e4c5c09c084ab32bc73890b99ff326bca" +dependencies = [ + "bitflags 2.4.2", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustls" +version = "0.21.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9d5a6813c0759e4609cd494e8e725babae6a2ca7b62a5536a13daaec6fcb7ba" +dependencies = [ + "log", + "ring", + "rustls-webpki", + "sct", +] + +[[package]] +name = "rustls-pemfile" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +dependencies = [ + "base64", +] + +[[package]] +name = "rustls-webpki" +version = "0.101.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" +dependencies = [ + "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.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" +dependencies = [ + "windows-sys 0.52.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", + "untrusted", +] + +[[package]] +name = "secrecy" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bd1c54ea06cfd2f6b63219704de0b9b4f72dcc2b8fdef820be6cd799780e91e" +dependencies = [ + "serde", + "zeroize", +] + +[[package]] +name = "security-framework" +version = "2.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 = "semver" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97ed7a9823b74f99c7742f5336af7be5ecd3eeafcb1507d1fa93347b1d589b0" +dependencies = [ + "serde", +] + +[[package]] +name = "serde" +version = "1.0.195" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63261df402c67811e9ac6def069e4786148c4563f4b50fd4bf30aa370d626b02" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.195" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46fe8f8603d81ba86327b23a2e9cdf49e1255fb94a4c5f297f6ee0547178ea2c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "serde_json" +version = "1.0.111" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "176e46fa42316f18edd598015a5166857fc835ec732f5215eac6b7bdbf0a84f4" +dependencies = [ + "itoa", + "ryu", + "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.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "385647faa24a889929028973650a4f158fb1b4272b2fcf94feb9fcc3c009e813" +dependencies = [ + "arrayvec", + "async-trait", + "base64", + "bitflags 2.4.2", + "bytes", + "chrono", + "dashmap", + "flate2", + "futures", + "fxhash", + "mime_guess", + "parking_lot", + "percent-encoding", + "reqwest", + "secrecy", + "serde", + "serde_json", + "time", + "tokio", + "tokio-tungstenite", + "tracing", + "typemap_rev", + "typesize", + "url", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[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 = "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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" + +[[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.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 = "strum" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.25.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23dc1fa9ac9c169a78ba62f0b841814b7abae11bdd047b9c58f893439e309ea0" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.48", +] + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" +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 = "tagptr" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" + +[[package]] +name = "tempfile" +version = "3.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01ce4141aa927a6d1bd34a041795abd0db1cccba5d5f24b009f694bdf3a1f3fa" +dependencies = [ + "cfg-if", + "fastrand", + "redox_syscall", + "rustix", + "windows-sys 0.52.0", +] + +[[package]] +name = "thiserror" +version = "1.0.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "time" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f657ba42c3f86e7680e53c8cd3af8abbe56b5491790b46e22e19c0d57463583e" +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.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26197e33420244aeb70c3e8c78376ca46571bc4e701e4791c2cd9f57dcb3a43f" +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.35.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c89b4efa943be685f629b149f53829423f8f5531ea21249408e8e2f8671ec104" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "num_cpus", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.48.0", +] + +[[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.48", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" +dependencies = [ + "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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" +dependencies = [ + "bytes", + "futures-core", + "futures-io", + "futures-sink", + "futures-util", + "hashbrown", + "pin-project-lite", + "slab", + "tokio", + "tracing", +] + +[[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.48", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", +] + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "tungstenite" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e3dac10fd62eaf6617d3a904ae222845979aec67c615d1c842b4002c7666fb9" +dependencies = [ + "byteorder", + "bytes", + "data-encoding", + "http", + "httparse", + "log", + "rand", + "rustls", + "sha1", + "thiserror", + "url", + "utf-8", +] + +[[package]] +name = "typemap_rev" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74b08b0c1257381af16a5c3605254d529d3e7e109f3c62befc5d168968192998" + +[[package]] +name = "typenum" +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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" +dependencies = [ + "version_check", +] + +[[package]] +name = "unicode-bidi" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" + +[[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.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 = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version_check" +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" +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.90" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1223296a201415c7fad14792dbefaace9bd52b62d33453ade1c5b5f07555406" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.90" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcdc935b63408d58a32f8cc9738a0bffd8f05cc7c002086c6ef20b7312ad9dcd" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.48", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bde2032aeb86bdfaecc8b261eef3cba735cc426c1f3a3416d1e0791be95fc461" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.90" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e4c238561b2d428924c49815533a8b9121c664599558a5d9ec51f8a1740a999" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.90" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bae1abb6806dc1ad9e560ed242107c0f6c84335f1749dd4e8ddb012ebd5e25a7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.90" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d91413b1c31d7539ba5ef2451af3f0b833a005eb27a631cec32bc0635a8602b" + +[[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.67" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58cd2333b6e0be7a39605f0e255892fd7418a682d8da8fe042fe25128794d2ed" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[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.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets 0.52.0", +] + +[[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 = "zerocopy" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "zeroize" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" + +[[package]] +name = "zstd" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bffb3309596d527cfcba7dfc6ed6052f1d39dfbd7c867aa2e865e4a449c10110" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "7.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43747c7422e2924c11144d5229878b98180ef8b06cca4ab5af37afc8a8d8ea3e" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.9+zstd.1.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e16efa8a874a0481a574084d34cc26fdb3b99627480f785888deb6386506656" +dependencies = [ + "cc", + "pkg-config", +] diff --git a/discord_bot/Cargo.toml b/discord_bot/Cargo.toml new file mode 100644 index 0000000000..394094ef7d --- /dev/null +++ b/discord_bot/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "discord_bot" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +actix-web = "4.4.1" +arc-swap = "1.6.0" +async-trait = "0.1.77" +dotenv = "0.15.0" +env_logger = "0.11.0" +hex = "0.4.3" +hex-literal = "0.4.1" +hmac = "0.12.1" +indoc = "2.0.4" +itertools = "0.12.0" +once_cell = "1.19.0" +poise = "0.6.1" +reqwest = { version = "0.11.23", features = ["json"] } +serde = { version = "1.0.195", features = ["derive"] } +serde_json = "1.0.111" +sha2 = "0.10.8" +strum = { version = "0.25.0", features = ["derive"] } +thiserror = "1.0.56" +tokio = { version = "1.35.1", features = ["rt-multi-thread"] } +tokio-util = { version = "0.7.10", features = ["full"] } diff --git a/discord_bot/Dockerfile b/discord_bot/Dockerfile new file mode 100644 index 0000000000..92c4747bf6 --- /dev/null +++ b/discord_bot/Dockerfile @@ -0,0 +1,15 @@ +FROM rust:1.75 as builder + +WORKDIR /usr/src/app + +COPY . . + +RUN cargo install --path . + +FROM debian:bookworm-slim +RUN apt-get update && \ + apt-get install -y openssl libssl-dev ca-certificates && \ + rm -rf /var/lib/apt/lists/* +RUN /usr/sbin/update-ca-certificates +COPY --from=builder /usr/local/cargo/bin/discord_bot /usr/local/bin/discord_bot +CMD ["discord_bot"] diff --git a/discord_bot/fly.toml b/discord_bot/fly.toml new file mode 100644 index 0000000000..a0145aa0f3 --- /dev/null +++ b/discord_bot/fly.toml @@ -0,0 +1,22 @@ +# fly.toml app configuration file generated for winston-discord-bot on 2024-01-26T09:31:19+01:00 +# +# See https://fly.io/docs/reference/configuration/ for information about how to use this file. +# + +app = 'winston-discord-bot' +primary_region = 'ams' + +[build] + +[http_service] +internal_port = 8080 +force_https = true +auto_stop_machines = false +auto_start_machines = false +min_machines_running = 1 +processes = ['app'] + +[[vm]] +cpu_kind = 'shared' +cpus = 1 +memory_mb = 256 diff --git a/discord_bot/src/clickup/add_tag_to_task.rs b/discord_bot/src/clickup/add_tag_to_task.rs new file mode 100644 index 0000000000..c2620793a5 --- /dev/null +++ b/discord_bot/src/clickup/add_tag_to_task.rs @@ -0,0 +1,20 @@ +use crate::{WinstonError, CLIENT}; + +pub async fn add_tag_to_task(task_id: &str, tag_name: &str) -> Result<(), WinstonError> { + let result = CLIENT + .post(format!( + "https://api.clickup.com/api/v2/task/{}/tag/{}", + task_id, tag_name + )) + .send() + .await?; + + if !result.status().is_success() { + return Err(WinstonError::ClickupApiError( + result.status().as_u16(), + result.text().await?, + )); + } + + Ok(()) +} diff --git a/discord_bot/src/clickup/create_task.rs b/discord_bot/src/clickup/create_task.rs new file mode 100644 index 0000000000..236fb2b5c9 --- /dev/null +++ b/discord_bot/src/clickup/create_task.rs @@ -0,0 +1,78 @@ +use serde::{Deserialize, Serialize}; + +use crate::{WinstonError, CLICKUP_LIST_ID, CLIENT}; + +use super::{ + ClickupIdentifiable, TaskPriority, TaskSize, TaskStatus, TaskTag, + CLICKUP_CUSTOM_DISCORD_FIELD_ID, CLICKUP_CUSTOM_SIZE_FIELD_ID, +}; + +#[derive(Debug, Serialize)] +pub struct CreateTaskRequest { + name: String, + description: String, + tags: Vec, + status: String, + priority: u8, + custom_fields: Vec, +} + +#[derive(Debug, Serialize)] +pub struct CustomField { + id: String, + value: String, +} + +#[derive(Debug, Deserialize)] +pub struct CreateTaskResponse { + id: String, +} + +pub async fn create_task_in_clickup( + title: &str, + description: &str, + priority: &TaskPriority, + size: &TaskSize, + tags: &[TaskTag], + discord_channel_id: String, +) -> Result { + let request = CreateTaskRequest { + name: title.to_string(), + description: description.to_string(), + tags: tags.into_iter().map(|tag| tag.raw_string()).collect(), + status: TaskStatus::Backlog.raw_string(), + priority: priority.into(), + custom_fields: vec![ + CustomField { + id: CLICKUP_CUSTOM_SIZE_FIELD_ID.to_string(), + value: size.clickup_id(), + }, + CustomField { + id: CLICKUP_CUSTOM_DISCORD_FIELD_ID.to_string(), + value: discord_channel_id, + }, + ], + }; + let result = CLIENT + .post(format!( + "https://api.clickup.com/api/v2/list/{}/task", + CLICKUP_LIST_ID + )) + .json(&request) + .send() + .await?; + + if !result.status().is_success() { + return Err(WinstonError::ClickupApiError( + result.status().as_u16(), + result.text().await?, + )); + } + + let response: CreateTaskResponse = result.json().await?; + let url = format!( + "https://sharing.clickup.com/9015308602/v/6-901502296591-2/t/h/{}/a4c4c9d59ec667d", + response.id + ); + Ok(url) +} diff --git a/discord_bot/src/clickup/get_task.rs b/discord_bot/src/clickup/get_task.rs new file mode 100644 index 0000000000..fcd284650d --- /dev/null +++ b/discord_bot/src/clickup/get_task.rs @@ -0,0 +1,162 @@ +use crate::{webhook::TaskTag, WinstonError, CLIENT}; +use serde::{Deserialize, Serialize}; +use serde_json::Value; +use strum::IntoEnumIterator; + +use super::{ClickupIdentifiable, TaskStatus}; + +pub async fn get_task_from_clickup(task_id: &str) -> Result { + let result = CLIENT + .get(format!("https://api.clickup.com/api/v2/task/{}", task_id)) + .send() + .await?; + + if !result.status().is_success() { + return Err(WinstonError::ClickupApiError( + result.status().as_u16(), + result.text().await?, + )); + } + + let text = result.text().await?; + + match serde_json::from_str::(&text) { + Ok(task) => Ok(task), + Err(e) => { + eprintln!("failed to deserialize task: {}", e); + eprintln!("response: {}", text); + Err(WinstonError::ParseJson(e)) + } + } +} + +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct Task { + pub id: String, + // pub custom_id: Value, + // pub custom_item_id: i64, + pub name: String, + pub text_content: String, + pub description: String, + pub status: Status, + // pub date_created: String, + // pub date_updated: String, + // pub date_closed: Value, + // pub date_done: Value, + pub archived: bool, + // pub creator: User, + // pub assignees: Vec, + // pub watchers: Vec, + // pub checklists: Vec, + pub tags: Vec, + // pub parent: Value, + // pub priority: Priority, + // pub due_date: Value, + // pub start_date: Value, + // pub points: Value, + // pub time_estimate: Value, + // pub time_spent: i64, + pub custom_fields: Vec, + // pub dependencies: Vec, + // pub linked_tasks: Vec, + // pub locations: Vec, + // pub team_id: String, + // pub url: String, + // pub sharing: Sharing, + // pub permission_level: String, + // pub list: List, + // pub project: Project, + // pub folder: Folder, + // pub space: Space, + // pub attachments: Vec, +} + +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct Status { + pub id: String, + pub status: String, + pub color: String, + pub orderindex: i64, + #[serde(rename = "type")] + pub type_field: String, +} + +impl From<&Status> for TaskStatus { + fn from(status: &Status) -> Self { + TaskStatus::iter() + .find(|s| s.clickup_id() == status.id) + .expect(format!("Unknown status: {}", status.id).as_str()) + } +} + +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct Priority { + pub color: String, + pub id: String, + pub orderindex: String, + pub priority: String, +} + +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct CustomField { + pub id: String, + pub name: String, + #[serde(rename = "type")] + pub type_field: String, + pub type_config: TypeConfig, + pub date_created: String, + pub hide_from_guests: bool, + pub value: Option, + pub required: bool, +} + +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct TypeConfig { + pub new_drop_down: Option, + pub options: Option>, +} + +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct TaskOption { + pub id: String, + pub name: String, + pub color: String, + pub orderindex: i64, +} + +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct Sharing { + pub public: bool, + pub public_share_expires_on: Value, + pub public_fields: Vec, + pub token: Value, + pub seo_optimized: bool, +} + +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct List { + pub id: String, + pub name: String, + pub access: bool, +} + +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct Project { + pub id: String, + pub name: String, + pub hidden: bool, + pub access: bool, +} + +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct Folder { + pub id: String, + pub name: String, + pub hidden: bool, + pub access: bool, +} + +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct Space { + pub id: String, +} diff --git a/discord_bot/src/clickup/mod.rs b/discord_bot/src/clickup/mod.rs new file mode 100644 index 0000000000..5f8c071a62 --- /dev/null +++ b/discord_bot/src/clickup/mod.rs @@ -0,0 +1,11 @@ +mod add_tag_to_task; +mod create_task; +mod get_task; +mod types; +mod update_task; + +pub use add_tag_to_task::*; +pub use create_task::*; +pub use get_task::*; +pub use types::*; +pub use update_task::*; diff --git a/discord_bot/src/clickup/types.rs b/discord_bot/src/clickup/types.rs new file mode 100644 index 0000000000..21119a36b0 --- /dev/null +++ b/discord_bot/src/clickup/types.rs @@ -0,0 +1,158 @@ +use std::fmt::Display; + +use strum::EnumIter; + +pub trait ClickupIdentifiable { + fn clickup_id(&self) -> String; +} + +#[derive(Debug, poise::ChoiceParameter, EnumIter, Hash, PartialEq, Eq, Clone)] +pub enum TaskStatus { + #[name = "📋 Backlog"] + Backlog, + #[name = "🏗 In progress"] + InProgress, + #[name = "👀 In beta"] + InBeta, + #[name = "🚀 In production"] + InProduction, +} + +impl TaskStatus { + pub fn raw_string(&self) -> String { + match self { + TaskStatus::Backlog => "backlog".to_string(), + TaskStatus::InProgress => "in progress".to_string(), + TaskStatus::InBeta => "in beta".to_string(), + TaskStatus::InProduction => "in production".to_string(), + } + } +} + +impl Display for TaskStatus { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + TaskStatus::Backlog => write!(f, "📋 Backlog"), + TaskStatus::InProgress => write!(f, "🏗 In progress"), + TaskStatus::InBeta => write!(f, "👀 In beta"), + TaskStatus::InProduction => write!(f, "🚀 In production"), + } + } +} + +impl ClickupIdentifiable for TaskStatus { + fn clickup_id(&self) -> String { + match self { + TaskStatus::Backlog => "p90150987376_OOQuHfTS".to_string(), + TaskStatus::InProgress => "p90150987376_2VuuCTca".to_string(), + TaskStatus::InBeta => "p90150987376_rgJ7Kpgr".to_string(), + TaskStatus::InProduction => "p90150987376_gaWBoToF".to_string(), + } + } +} + +#[derive(Debug, poise::ChoiceParameter)] +pub enum TaskTag { + #[name = "🐛 Bug"] + Bug, + #[name = "🎁 Feature"] + Feature, + #[name = "📖 Documentation"] + Documentation, +} + +impl Display for TaskTag { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + TaskTag::Bug => write!(f, "🐛 Bug"), + TaskTag::Feature => write!(f, "🎁 Feature"), + TaskTag::Documentation => write!(f, "📖 Documentation"), + } + } +} + +impl TaskTag { + pub fn raw_string(&self) -> String { + match self { + TaskTag::Bug => "bug".to_string(), + TaskTag::Feature => "feature".to_string(), + TaskTag::Documentation => "documentation".to_string(), + } + } +} + +#[derive(Debug, poise::ChoiceParameter)] +pub enum TaskPriority { + #[name = "🌋 Urgent"] + Urgent, + #[name = "🏔 High"] + High, + #[name = "🏕 Medium"] + Medium, + #[name = "🏝 Low"] + Low, +} + +impl Display for TaskPriority { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + TaskPriority::Urgent => write!(f, "🌋 Urgent"), + TaskPriority::High => write!(f, "🏔 High"), + TaskPriority::Medium => write!(f, "🏕 Medium"), + TaskPriority::Low => write!(f, "🏝 Low"), + } + } +} + +impl From<&TaskPriority> for u8 { + fn from(priority: &TaskPriority) -> Self { + match priority { + TaskPriority::Urgent => 1, + TaskPriority::High => 2, + TaskPriority::Medium => 3, + TaskPriority::Low => 4, + } + } +} + +pub const CLICKUP_CUSTOM_SIZE_FIELD_ID: &str = "36000367-aed7-461a-9e84-7ac155605fb9"; + +#[derive(Debug, poise::ChoiceParameter)] +pub enum TaskSize { + #[name = "🐋 Months"] + Months, + #[name = "🦑 Weeks"] + Weeks, + #[name = "🐂 Days"] + Days, + #[name = "🐇 Hours"] + Hours, + #[name = "🦔 Minutes"] + Minutes, +} + +impl Display for TaskSize { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + TaskSize::Months => write!(f, "🐋 Months"), + TaskSize::Weeks => write!(f, "🦑 Weeks"), + TaskSize::Days => write!(f, "🐂 Days"), + TaskSize::Hours => write!(f, "🐇 Hours"), + TaskSize::Minutes => write!(f, "🦔 Minutes"), + } + } +} + +impl ClickupIdentifiable for TaskSize { + fn clickup_id(&self) -> String { + match self { + TaskSize::Months => "d861267c-0528-457a-9ca6-a437eb0ee1b9".to_string(), + TaskSize::Weeks => "04c1c537-aee6-4a32-9d0c-f68fa1ac00b1".to_string(), + TaskSize::Days => "df457cfa-b018-42ea-acf4-18eee65142ed".to_string(), + TaskSize::Hours => "b776bfcf-bdae-489c-9617-77e338e26f7a".to_string(), + TaskSize::Minutes => "83efdfff-0efa-4084-b814-2ae20cd61277".to_string(), + } + } +} + +pub const CLICKUP_CUSTOM_DISCORD_FIELD_ID: &str = "e6c36282-9f38-438b-a9da-9796affff15a"; diff --git a/discord_bot/src/clickup/update_task.rs b/discord_bot/src/clickup/update_task.rs new file mode 100644 index 0000000000..fc4d0b6a8b --- /dev/null +++ b/discord_bot/src/clickup/update_task.rs @@ -0,0 +1,39 @@ +use serde::Serialize; + +use crate::WinstonError; + +use super::{Task, TaskStatus}; + +#[derive(Debug, Default)] +pub struct UpdateTask { + pub status: Option, +} + +#[derive(Debug, Serialize)] +struct UpdateTaskRequest { + #[serde(skip_serializing_if = "Option::is_none")] + status: Option, +} + +pub async fn update_task(task_id: &str, update: UpdateTask) -> Result { + let request = UpdateTaskRequest { + status: update.status.map(|status| status.raw_string()), + }; + + let result = crate::CLIENT + .put(format!("https://api.clickup.com/api/v2/task/{}", task_id)) + .json(&request) + .send() + .await?; + + if !result.status().is_success() { + return Err(WinstonError::ClickupApiError( + result.status().as_u16(), + result.text().await?, + )); + } + + let task: Task = result.json().await?; + + Ok(task) +} diff --git a/discord_bot/src/commands/create_card.rs b/discord_bot/src/commands/create_card.rs new file mode 100644 index 0000000000..c6f0c759e4 --- /dev/null +++ b/discord_bot/src/commands/create_card.rs @@ -0,0 +1,92 @@ +use crate::{ + check_is_contributor, + clickup::{create_task_in_clickup, TaskPriority, TaskSize, TaskTag}, + Context, WinstonError, +}; +use poise::{ + serenity_prelude::{CreateEmbed, CreateMessage}, + CreateReply, +}; + +#[poise::command(slash_command, ephemeral, check = "check_is_contributor")] +pub async fn create_task( + ctx: Context<'_>, + #[description = "The title of the task"] title: String, + #[description = "The description of the task"] description: String, + #[description = "The priority of this task"] priority: TaskPriority, + #[description = "The time estimate for this task"] size: TaskSize, + #[description = "All the tags this task has"] tags: Vec, +) -> Result<(), WinstonError> { + let channel = ctx.channel_id(); + + let handle = ctx + .send( + CreateReply::default() + .embed( + embed_card_data(&title, &description, &priority, &size, &tags).color(0x1F85DE), + ) + .content("Creating task..."), + ) + .await?; + + let result = create_task_in_clickup( + &title, + &description, + &priority, + &size, + &tags, + channel.get().to_string(), + ) + .await; + + if let Err(e) = result { + handle + .edit( + ctx, + CreateReply::default().content(format!("Failed to create task: {}", e)), + ) + .await?; + return Err(e); + } + + let url = result.unwrap(); + + handle.delete(ctx).await?; + + channel + .send_message( + ctx, + CreateMessage::default() + .content("This ticket has been linked to the following task:") + .embed( + embed_card_data(&title, &description, &priority, &size, &tags) + .color(0x78ee5c) + .url(url), + ), + ) + .await?; + + Ok(()) +} + +fn embed_card_data( + title: &str, + description: &str, + priority: &TaskPriority, + time_estimate: &TaskSize, + tags: &[TaskTag], +) -> CreateEmbed { + CreateEmbed::new() + .title(title) + .description(description) + .field("Priority", priority.to_string(), true) + .field("Time Estimate", time_estimate.to_string(), true) + .field( + "Tags", + tags.iter() + .map(|tag| tag.to_string()) + .collect::>() + .join(", "), + false, + ) +} diff --git a/discord_bot/src/commands/mod.rs b/discord_bot/src/commands/mod.rs new file mode 100644 index 0000000000..01491000a3 --- /dev/null +++ b/discord_bot/src/commands/mod.rs @@ -0,0 +1,5 @@ +mod create_card; +mod task_fixed; + +pub use create_card::*; +pub use task_fixed::*; diff --git a/discord_bot/src/commands/task_fixed.rs b/discord_bot/src/commands/task_fixed.rs new file mode 100644 index 0000000000..8466ac570c --- /dev/null +++ b/discord_bot/src/commands/task_fixed.rs @@ -0,0 +1,205 @@ +use async_trait::async_trait; +use indoc::formatdoc; +use itertools::Itertools; +use poise::serenity_prelude::{ + ComponentInteraction, Context, EditInteractionResponse, EditMessage, EventHandler, Interaction, +}; + +use crate::{ + clickup::{add_tag_to_task, update_task, TaskStatus, UpdateTask}, + WinstonError, CONTRIBUTOR_ROLE_ID, +}; + +pub struct TaskFixedHandler; + +#[async_trait] +impl EventHandler for TaskFixedHandler { + async fn interaction_create(&self, ctx: Context, interaction: Interaction) { + let Some(mut component) = interaction.message_component() else { + return; + }; + + let custom_id = &component.data.custom_id; + if !custom_id.starts_with("task-fixed-") && !custom_id.starts_with("task-broken-") { + return; + } + + if let Err(err) = component.defer_ephemeral(&ctx).await { + eprintln!("Failed to defer ephemeral: {}", err); + return; + } + + let split = custom_id.split('-').collect_vec(); + if split.len() != 3 { + update_response( + &ctx, + &component, + "Something whent wrong with the button. It seems to be outdated.", + ) + .await; + eprintln!("Invalid custom_id: {}", custom_id); + return; + } + let task_status = split[1].to_string(); + let task_id = split[2].to_string(); + + let Ok(channel) = component.channel_id.to_channel(&ctx).await else { + update_response( + &ctx, + &component, + "Something whent wrong with the button. It seems to be outdated.", + ) + .await; + eprintln!("No channel found"); + return; + }; + + let Some(guild_channel) = channel.guild() else { + update_response( + &ctx, + &component, + "Something whent wrong with the button. It seems to be outdated.", + ) + .await; + eprintln!("No guild channel found"); + return; + }; + + let Some(owner_id) = guild_channel.owner_id else { + update_response( + &ctx, + &component, + "Something whent wrong with the button. It seems to be outdated.", + ) + .await; + eprintln!("No owner found for channel: {}", guild_channel.name()); + return; + }; + + // Only allow the owner to change the status + if component.user.id != owner_id { + update_response( + &ctx, + &component, + "Only the original ticket creator can change the status of the task.", + ) + .await; + eprintln!("User is not the owner"); + return; + } + + let result = match task_status.as_str() { + "fixed" => { + update_response(&ctx, &component, "Marking task as fixed...").await; + mark_task_as_fixed(&ctx, &mut component, &task_id).await + } + "broken" => { + update_response(&ctx, &component, "Marking task as broken...").await; + mark_task_as_broken(&ctx, &mut component, &task_id).await + } + _ => { + update_response( + &ctx, + &component, + "Something whent wrong with the button. It seems to be outdated.", + ) + .await; + eprintln!("Invalid task status: {}", task_status); + return; + } + }; + + if let Err(e) = result { + update_response( + &ctx, + &component, + format!("Failed to mark task as {}: {}", task_status, e), + ) + .await; + eprintln!("Failed to mark task as {}: {}", task_status, e); + return; + } + } +} + +async fn mark_task_as_fixed( + ctx: &Context, + interaction: &mut ComponentInteraction, + task_id: &str, +) -> Result<(), WinstonError> { + add_tag_to_task(task_id, "fixed").await?; + + update_response( + ctx, + interaction, + "Thanks for marking the task as fixed! This helps us keep track of the status of the task.", + ) + .await; + + interaction + .message + .edit( + ctx, + EditMessage::default() + .components(Vec::new()) + .content(formatdoc! {" + # In Development (Fixed) + This task has been marked as **In Development**. + This has been verified to be **fixed** and can be downloaded [here]({}). + ", "https://modrinth.com/plugin/typewriter/versions?c=beta"}), + ) + .await?; + Ok(()) +} + +async fn mark_task_as_broken( + ctx: &Context, + interaction: &mut ComponentInteraction, + task_id: &str, +) -> Result<(), WinstonError> { + update_task( + task_id, + UpdateTask { + status: Some(TaskStatus::InProgress), + ..Default::default() + }, + ) + .await?; + + update_response( + ctx, + interaction, + "Thanks for marking the task as broken! This helps us keep know that the task is still broken and needs to be fixed.", + ).await; + + interaction + .message + .edit( + ctx, + EditMessage::default() + .components(Vec::new()) + .content(formatdoc! {" + # Broken <@&{}> + This task has been marked as **Broken**. And has been moved back to the **In Progress**. + __Please indicate what is still broken, and what needs to be fixed.__ + ", CONTRIBUTOR_ROLE_ID + }), + ) + .await?; + + Ok(()) +} + +async fn update_response( + ctx: &Context, + interaction: &ComponentInteraction, + content: impl Into, +) { + let result = interaction + .edit_response(ctx, EditInteractionResponse::default().content(content)) + .await; + + if let Err(e) = result { + eprintln!("Failed to update response: {}", e); + } +} diff --git a/discord_bot/src/main.rs b/discord_bot/src/main.rs new file mode 100644 index 0000000000..50e4b9647a --- /dev/null +++ b/discord_bot/src/main.rs @@ -0,0 +1,195 @@ +use std::sync::Arc; + +use actix_web::{middleware::Logger, App, HttpServer}; +use once_cell::sync::Lazy; +use poise::serenity_prelude as serenity; +use tokio::signal; +use tokio_util::sync::CancellationToken; + +mod clickup; +mod commands; +mod webhook; +mod webhooks; + +use commands::*; +use reqwest::{ + header::{HeaderMap, HeaderValue, AUTHORIZATION}, + Client, +}; +use webhook::clickup_webhook; + +use crate::webhook::clickup_webhook_get; + +pub struct Data {} // User data, which is stored and accessible in all command invocations +pub type Context<'a> = poise::Context<'a, Data, WinstonError>; +pub type ApplicationContext<'a> = poise::ApplicationContext<'a, Data, WinstonError>; + +const GUILD_ID: serenity::GuildId = serenity::GuildId::new(1054708062520360960); +const CONTRIBUTOR_ROLE_ID: serenity::RoleId = serenity::RoleId::new(1054708457535713350); + +const CLICKUP_LIST_ID: &str = "901502296591"; + +static CLIENT: Lazy = Lazy::new(|| { + let mut headers = HeaderMap::new(); + + headers.insert( + reqwest::header::CONTENT_TYPE, + HeaderValue::from_static("application/json"), + ); + + let mut auth_value = HeaderValue::from_str( + std::env::var("CLICKUP_TOKEN") + .expect("missing CLICKUP_TOKEN") + .as_str(), + ) + .expect("failed to create header value"); + auth_value.set_sensitive(true); + headers.insert(AUTHORIZATION, auth_value); + + Client::builder() + .default_headers(headers) + .build() + .expect("failed to build reqwest client") +}); + +static DISCORD_CLIENT: Lazy>> = + Lazy::new(|| arc_swap::ArcSwap::from_pointee(None)); + +#[tokio::main] +async fn main() { + dotenv::dotenv().ok(); + + let token = CancellationToken::new(); + let webhook_token = token.clone(); + let discord_token = token.clone(); + + let webhook_task = tokio::spawn(async move { + tokio::select! { + _ = webhook_token.cancelled() => {} + _ = startup_webhook() => {} + } + }); + + let discord_task = tokio::spawn(async move { + tokio::select! { + _ = discord_token.cancelled() => {} + _ = startup_discord_bot() => {} + } + }); + + match signal::ctrl_c().await { + Ok(()) => { + println!("\nShutting down..."); + token.cancel(); + } + Err(err) => { + eprintln!("Unable to listen for shutdown signal: {}", err); + token.cancel(); + } + } + + tokio::join!(webhook_task, discord_task).0.unwrap(); +} + +async fn startup_webhook() { + // env_logger::init_from_env(env_logger::Env::new().default_filter_or("info")); + println!("Starting webhook server..."); + HttpServer::new(|| { + App::new() + .wrap(Logger::default()) + .service(clickup_webhook_get) + .service(clickup_webhook) + }) + .bind("0.0.0.0:8080") + .expect("failed to bind server") + .run() + .await + .expect("failed to run server"); +} + +async fn startup_discord_bot() { + let discord_token = std::env::var("DISCORD_TOKEN").expect("missing DISCORD_TOKEN"); + let intents = serenity::GatewayIntents::non_privileged(); + + let framework = poise::Framework::builder() + .options(poise::FrameworkOptions { + commands: vec![create_task()], + on_error: |error| Box::pin(on_error(error)), + ..Default::default() + }) + .setup(|ctx, _ready, framework| { + Box::pin(async move { + poise::builtins::register_globally(ctx, &framework.options().commands).await?; + DISCORD_CLIENT.store(Arc::from(Some(ctx.clone()))); + Ok(Data {}) + }) + }) + .build(); + + let client = serenity::ClientBuilder::new(discord_token, intents) + .event_handler(TaskFixedHandler) + .framework(framework) + .await; + + println!("Starting bot..."); + + client + .unwrap() + .start() + .await + .expect("failed to start discord bot"); +} + +async fn on_error(error: poise::FrameworkError<'_, Data, WinstonError>) { + match error { + poise::FrameworkError::Setup { error, .. } => panic!("Failed to start bot: {:?}", error), + poise::FrameworkError::Command { error, ctx, .. } => { + println!("Error in command `{}`: {:?}", ctx.command().name, error,); + } + error => { + if let Err(e) = poise::builtins::on_error(error).await { + println!("Error while handling error: {}", e) + } + } + } +} +pub async fn check_is_contributor(ctx: Context<'_>) -> Result { + Ok(ctx + .author() + .has_role(ctx, GUILD_ID, CONTRIBUTOR_ROLE_ID) + .await?) +} + +pub fn get_discord() -> Result { + match DISCORD_CLIENT.load().as_ref() { + Some(discord) => Ok(discord.clone()), + None => Err(WinstonError::DiscordClientNotInitialized), + } +} + +#[derive(thiserror::Error, Debug)] +pub enum WinstonError { + #[error("Discord error: {0}")] + Discord(#[from] serenity::Error), + + #[error("Reqwest error: {0}")] + Reqwest(#[from] reqwest::Error), + + #[error("Query error: {0}")] + QueryError(String), + + #[error("Clickup API error: {0}: {1}")] + ClickupApiError(u16, String), + + #[error("Discord client not initialized")] + DiscordClientNotInitialized, + + #[error("Not a guild channel")] + NotAGuildChannel, + + #[error("Failed to parse int: {0}")] + ParseInt(#[from] std::num::ParseIntError), + + #[error("Failed to parse json: {0}")] + ParseJson(#[from] serde_json::Error), +} diff --git a/discord_bot/src/webhook.rs b/discord_bot/src/webhook.rs new file mode 100644 index 0000000000..edd4766035 --- /dev/null +++ b/discord_bot/src/webhook.rs @@ -0,0 +1,281 @@ +use actix_web::{web::Bytes, HttpRequest, HttpResponse, Responder}; +use hmac::{Hmac, Mac}; +use serde::{Deserialize, Serialize}; +use sha2::Sha256; + +type HmacSha256 = Hmac; + +#[actix_web::get("/")] +pub async fn clickup_webhook_get() -> impl Responder { + HttpResponse::Ok().body("ok") +} + +#[actix_web::post("/")] +pub async fn clickup_webhook(req: HttpRequest, bytes: Bytes) -> impl Responder { + let headers = req.headers(); + let signature = headers + .get("X-Signature") + .expect("missing X-Signature header") + .to_str() + .expect("failed to convert X-Signature header to str") + .trim(); + + let secret = std::env::var("CLICKUP_WEBHOOK_SECRET").expect("missing CLICKUP_WEBHOOK_SECRET"); + let mut mac = + HmacSha256::new_from_slice(secret.as_bytes()).expect("HMAC can take key of any size"); + + mac.update(&bytes); + + let new_signature = if signature.len() % 2 != 0 { + format!("0{}", signature) + } else { + signature.to_string() + }; + + let Ok(signature) = hex::decode(new_signature.trim()) else { + return HttpResponse::BadRequest() + .body("failed to decode signature") + .into(); + }; + if let Err(_) = mac.verify_slice(&signature) { + return HttpResponse::Unauthorized() + .body("invalid signature") + .into(); + } + + let event: Event = serde_json::from_slice(&bytes).expect("failed to deserialize event"); + + let result = match event { + Event::TaskCreated(e) => crate::webhooks::handle_task_created(e).await, + Event::TaskUpdated(e) => crate::webhooks::handle_task_updated(e).await, + Event::TaskStatusUpdated(e) => crate::webhooks::handle_task_status_updated(e).await, + _ => Ok(()), + }; + + if let Err(e) = result { + eprintln!("failed to handle event: {}", e); + return HttpResponse::InternalServerError() + .body(format!("failed to handle event: {}", e)) + .into(); + } + + HttpResponse::Ok().finish() +} + +#[derive(Debug, Deserialize)] +#[serde(tag = "event", rename_all = "camelCase")] +enum Event { + TaskCreated(TaskCreated), + TaskUpdated(TaskUpdated), + TaskDeleted(TaskDeleted), + TaskPriorityUpdated(TaskPriorityUpdated), + TaskStatusUpdated(TaskStatusUpdated), + TaskAssigneeUpdated(TaskAssigneeUpdated), + TaskDueDateUpdated(TaskDueDateUpdated), + TaskMoved(TaskMoved), + TaskCommentCreated(TaskCommentCreated), + TaskCommentUpdated(TaskCommentUpdated), + TaskTimeEstimateUpdated(TaskTimeEstimateUpdated), + TaskTimeTrackedUpdated(TaskTimeTrackedUpdated), + TaskTagUpdated(TaskTagUpdated), + ListCreated(ListCreated), + ListUpdated(ListUpdated), + ListDeleted(ListDeleted), + FolderCreated(FolderCreated), + FolderUpdated(FolderUpdated), + FolderDeleted(FolderDeleted), + SpaceCreated(SpaceCreated), + SpaceUpdated(SpaceUpdated), + SpaceDeleted(SpaceDeleted), + GoalCreated(GoalCreated), + GoalUpdated(GoalUpdated), + GoalDeleted(GoalDeleted), + KeyResultCreated(KeyResultCreated), + KeyResultUpdated(KeyResultUpdated), + KeyResultDeleted(KeyResultDeleted), +} + +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct User { + id: u32, + username: String, + email: String, + color: String, + initials: Option, + profile_picture: Option, +} + +#[derive(Debug, Deserialize)] +struct HistoryItem { + id: String, + #[serde(rename = "type")] + type_: u32, + date: String, + field: String, + parent_id: String, + user: User, + before: Option, + after: Option, +} + +#[derive(Debug, Deserialize)] +#[serde(untagged)] +enum BeforeAfter { + Empty, + String(String), + TaskStatus { + status: String, + color: String, + #[serde(rename = "type")] + type_: String, + orderindex: i32, + }, + TaskPriority { + id: String, + priority: String, + color: String, + orderindex: String, + }, + TaskAssignee(User), + TaskTag(Vec), + TaskMoved { + id: String, + name: String, + category: Category, + project: Project, + }, +} + +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct TaskTag { + pub name: String, + tag_fg: String, + tag_bg: String, + creator: u32, +} + +#[derive(Debug, Deserialize)] +pub struct Category { + id: String, + name: String, + hidden: bool, +} + +#[derive(Debug, Deserialize)] +pub struct Project { + id: String, + name: String, +} + +#[derive(Debug, Deserialize)] +struct TaskCreatedHistoryItem { + #[serde(flatten)] + item: HistoryItem, + data: StatusTypeData, +} + +#[derive(Debug, Deserialize)] +struct StatusTypeData { + status_type: Option, +} + +#[derive(Debug, Deserialize)] +pub struct TaskCreated { + pub task_id: String, + pub webhook_id: String, +} + +#[derive(Debug, Deserialize)] +pub struct TaskUpdated { + pub task_id: String, + pub webhook_id: String, +} + +#[derive(Debug, Deserialize)] +struct TaskDeleted {} + +#[derive(Debug, Deserialize)] +struct TaskPriorityUpdated {} + +#[derive(Debug, Deserialize)] +struct TaskStatusUpdatedHistoryItem { + #[serde(flatten)] + item: HistoryItem, + data: StatusTypeData, +} + +#[derive(Debug, Deserialize)] +pub struct TaskStatusUpdated { + pub task_id: String, + pub webhook_id: String, +} + +#[derive(Debug, Deserialize)] +struct TaskAssigneeUpdated {} + +#[derive(Debug, Deserialize)] +struct TaskDueDateUpdated {} + +#[derive(Debug, Deserialize)] +struct TaskMoved {} + +#[derive(Debug, Deserialize)] +struct TaskCommentCreated {} + +#[derive(Debug, Deserialize)] +struct TaskCommentUpdated {} + +#[derive(Debug, Deserialize)] +struct TaskTimeEstimateUpdated {} + +#[derive(Debug, Deserialize)] +struct TaskTimeTrackedUpdated {} + +#[derive(Debug, Deserialize)] +struct TaskTagUpdated {} + +#[derive(Debug, Deserialize)] +struct ListCreated {} + +#[derive(Debug, Deserialize)] +struct ListUpdated {} + +#[derive(Debug, Deserialize)] +struct ListDeleted {} + +#[derive(Debug, Deserialize)] +struct FolderCreated {} + +#[derive(Debug, Deserialize)] +struct FolderUpdated {} + +#[derive(Debug, Deserialize)] +struct FolderDeleted {} + +#[derive(Debug, Deserialize)] +struct SpaceCreated {} + +#[derive(Debug, Deserialize)] +struct SpaceUpdated {} + +#[derive(Debug, Deserialize)] +struct SpaceDeleted {} + +#[derive(Debug, Deserialize)] +struct GoalCreated {} + +#[derive(Debug, Deserialize)] +struct GoalUpdated {} + +#[derive(Debug, Deserialize)] +struct GoalDeleted {} + +#[derive(Debug, Deserialize)] +struct KeyResultCreated {} + +#[derive(Debug, Deserialize)] +struct KeyResultUpdated {} + +#[derive(Debug, Deserialize)] +struct KeyResultDeleted {} diff --git a/discord_bot/src/webhooks/mod.rs b/discord_bot/src/webhooks/mod.rs new file mode 100644 index 0000000000..217c728e98 --- /dev/null +++ b/discord_bot/src/webhooks/mod.rs @@ -0,0 +1,3 @@ +mod tasks; + +pub use tasks::*; diff --git a/discord_bot/src/webhooks/tasks.rs b/discord_bot/src/webhooks/tasks.rs new file mode 100644 index 0000000000..405d5593bc --- /dev/null +++ b/discord_bot/src/webhooks/tasks.rs @@ -0,0 +1,163 @@ +use std::collections::HashMap; + +use indoc::formatdoc; +use itertools::Itertools; +use poise::serenity_prelude::{ + ButtonStyle, ChannelId, CreateButton, CreateMessage, EditThread, ForumTag, ForumTagId, + ReactionType, +}; + +use crate::{ + clickup::{get_task_from_clickup, TaskStatus}, + get_discord, + webhook::{TaskCreated, TaskStatusUpdated, TaskUpdated}, + WinstonError, +}; + +pub async fn handle_task_created(event: TaskCreated) -> Result<(), WinstonError> { + update_discord_channel(&event.task_id, false).await +} + +pub async fn handle_task_updated(event: TaskUpdated) -> Result<(), WinstonError> { + update_discord_channel(&event.task_id, false).await +} + +pub async fn handle_task_status_updated(event: TaskStatusUpdated) -> Result<(), WinstonError> { + update_discord_channel(&event.task_id, true).await +} + +async fn update_discord_channel(task_id: &str, moved: bool) -> Result<(), WinstonError> { + let discord = get_discord()?; + + let task = get_task_from_clickup(task_id).await?; + + let Some(channel_id) = task + .custom_fields + .iter() + .find(|field| field.name == "Discord Channel") + .and_then(|field| field.value.as_ref()) + .and_then(|value| value.as_str()) + else { + return Ok(()); + }; + + let status: TaskStatus = (&task.status).into(); + + let channel: ChannelId = channel_id.parse::()?.into(); + + let guild_channel = channel + .to_channel(&discord) + .await? + .guild() + .ok_or(WinstonError::NotAGuildChannel)?; + + let parent_channel = guild_channel + .parent_id + .ok_or(WinstonError::NotAGuildChannel)? + .to_channel(&discord) + .await? + .guild() + .ok_or(WinstonError::NotAGuildChannel)?; + + let available_tags = parent_channel.available_tags; + + let status_tags = get_status_tags(&available_tags); + + let mut new_tags = Vec::new(); + + new_tags.push(status_tags[&status].clone()); + + if TaskStatus::InProduction == status { + if let Some(resolved_tag) = available_tags + .iter() + .find(|tag| tag.name == "Resolved") + .map(|tag| tag.id.clone()) + { + new_tags.push(resolved_tag); + } + } + + new_tags.extend( + task.tags + .iter() + .map(|tag| tag.name.to_string()) + .filter_map(|name| { + available_tags + .iter() + .find(|tag| tag.name.to_lowercase() == name.to_lowercase()) + }) + .map(|tag| tag.id.clone()), + ); + + new_tags = new_tags.into_iter().unique().collect::>(); + + let lock = status == TaskStatus::InProduction; + + channel + .edit_thread( + &discord, + EditThread::default() + .applied_tags(new_tags) + .locked(lock) + .archived(lock), + ) + .await?; + + if !moved { + return Ok(()); + } + + if TaskStatus::InProduction == status { + let _ = channel + .send_message( + &discord, + CreateMessage::default().content(formatdoc! {" + # In Production + This task has been marked as **In Production** and is now locked. + __If you have any additional questions or concerns, please create a new thread.__ + You can download the latest build [here]({}). + ", "https://modrinth.com/plugin/typewriter/versions?c=release"}), + ) + .await?; + } + + if TaskStatus::InBeta == status { + let _ = channel + .send_message( + &discord, + CreateMessage::default().content(formatdoc! {" + # In Development + This task has been marked as **In Development**. + Please test out the latest build [here]({}). + + __Please confirm that this issue has been fixed by clicking the button below.__ + __If the issue persists, indicate that by clicking the button below.__ + ", "https://modrinth.com/plugin/typewriter/versions?c=beta"}) + .button(CreateButton::new(format!("task-fixed-{}", task_id)) + .label("Fixed") + .style(ButtonStyle::Success) + .emoji(ReactionType::Unicode("👍".to_string()))) + .button(CreateButton::new(format!("task-broken-{}", task_id)) + .label("Broken") + .style(ButtonStyle::Danger) + .emoji(ReactionType::Unicode("🚧".to_string()))) + , + ) + .await?; + } + + Ok(()) +} + +fn get_status_tags(available_tags: &[ForumTag]) -> HashMap { + available_tags + .into_iter() + .filter_map(|tag| match tag.name.as_str() { + "Backlog" => Some((TaskStatus::Backlog, tag.id.clone())), + "In Progress" => Some((TaskStatus::InProgress, tag.id.clone())), + "In Development" => Some((TaskStatus::InBeta, tag.id.clone())), + "In Production" => Some((TaskStatus::InProduction, tag.id.clone())), + _ => None, + }) + .collect::>() +}