aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNathan Perry <np@nathanperry.dev>2024-08-06 10:45:06 -0400
committerNathan Perry <np@nathanperry.dev>2024-08-06 10:45:06 -0400
commit72d9bbe15220c21909dec8e30fb80729a24cec72 (patch)
tree5025c799e3065553c1e6a91b82cb2eae8e00c43e
parent9319e0b9987114ffef2cc2be2d00f127925ba3a8 (diff)
first pass convert to poise
-rw-r--r--Cargo.lock397
-rw-r--r--Cargo.toml15
-rw-r--r--src/bot.rs169
-rw-r--r--src/commands/game.rs (renamed from src/game.rs)186
-rw-r--r--src/commands/help.rs31
-rw-r--r--src/commands/meme/create.rs104
-rw-r--r--src/commands/meme/delete.rs29
-rw-r--r--src/commands/meme/history.rs151
-rw-r--r--src/commands/meme/invoke.rs149
-rw-r--r--src/commands/meme/mod.rs75
-rw-r--r--src/commands/mod.rs90
-rw-r--r--src/commands/playback.rs153
-rw-r--r--src/commands/roll.rs107
-rw-r--r--src/commands/sound_levels.rs79
-rw-r--r--src/commands/today/mod.rs32
-rw-r--r--src/lib.rs31
-rw-r--r--src/main.rs34
-rw-r--r--src/util/mod.rs (renamed from src/util.rs)76
-rw-r--r--src/util/rest_vec.rs84
19 files changed, 1156 insertions, 836 deletions
diff --git a/Cargo.lock b/Cargo.lock
index a4e7e87..3496cef 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -52,6 +52,55 @@ dependencies = [
]
[[package]]
+name = "anstream"
+version = "0.6.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526"
+dependencies = [
+ "anstyle",
+ "anstyle-parse",
+ "anstyle-query",
+ "anstyle-wincon",
+ "colorchoice",
+ "is_terminal_polyfill",
+ "utf8parse",
+]
+
+[[package]]
+name = "anstyle"
+version = "1.0.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1"
+
+[[package]]
+name = "anstyle-parse"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb"
+dependencies = [
+ "utf8parse",
+]
+
+[[package]]
+name = "anstyle-query"
+version = "1.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a"
+dependencies = [
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "anstyle-wincon"
+version = "3.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8"
+dependencies = [
+ "anstyle",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
name = "anyhow"
version = "1.0.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -140,6 +189,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
[[package]]
+name = "base64"
+version = "0.22.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
+
+[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -167,6 +222,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
[[package]]
+name = "bytecount"
+version = "0.6.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5ce89b21cab1437276d2650d57e971f9d548a2d9037cc231abdc0562b97498ce"
+
+[[package]]
name = "bytemuck"
version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -185,6 +246,37 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9"
[[package]]
+name = "camino"
+version = "1.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e0ec6b951b160caa93cc0c7b209e5a3bff7aae9062213451ac99493cd844c239"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "cargo-platform"
+version = "0.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "24b1f0365a6c6bb4020cd05806fd0d33c44d38046b8bd7f0e40814b9763cabfc"
+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.97"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -206,6 +298,7 @@ dependencies = [
"iana-time-zone",
"js-sys",
"num-traits",
+ "serde",
"wasm-bindgen",
"windows-targets 0.52.5",
]
@@ -222,6 +315,46 @@ dependencies = [
]
[[package]]
+name = "clap"
+version = "4.5.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fbb260a053428790f3de475e304ff84cdbc4face759ea7a3e64c1edd938a7fc"
+dependencies = [
+ "clap_builder",
+ "clap_derive",
+]
+
+[[package]]
+name = "clap_builder"
+version = "4.5.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "64b17d7ea74e9f833c7dbf2cbe4fb12ff26783eda4782a8975b72f895c9b4d99"
+dependencies = [
+ "anstream",
+ "anstyle",
+ "clap_lex",
+ "strsim",
+]
+
+[[package]]
+name = "clap_derive"
+version = "4.5.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "501d359d5f3dcaf6ecdeee48833ae73ec6e42723a1e52419c79abf9507eec0a0"
+dependencies = [
+ "heck",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.61",
+]
+
+[[package]]
+name = "clap_lex"
+version = "0.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97"
+
+[[package]]
name = "cmake"
version = "0.1.50"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -231,6 +364,12 @@ dependencies = [
]
[[package]]
+name = "colorchoice"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0"
+
+[[package]]
name = "colored"
version = "1.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -243,9 +382,9 @@ dependencies = [
[[package]]
name = "command_attr"
-version = "0.5.1"
+version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "32f08c85a02e066b7b4f7dcb60eee6ae0793ef7d6452a3547d1f19665df070a9"
+checksum = "88da8d7e9fe6f30d8e3fcf72d0f84102b49de70fece952633e8439e89bdc7631"
dependencies = [
"proc-macro2",
"quote",
@@ -328,6 +467,41 @@ dependencies = [
]
[[package]]
+name = "darling"
+version = "0.20.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989"
+dependencies = [
+ "darling_core",
+ "darling_macro",
+]
+
+[[package]]
+name = "darling_core"
+version = "0.20.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5"
+dependencies = [
+ "fnv",
+ "ident_case",
+ "proc-macro2",
+ "quote",
+ "strsim",
+ "syn 2.0.61",
+]
+
+[[package]]
+name = "darling_macro"
+version = "0.20.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806"
+dependencies = [
+ "darling_core",
+ "quote",
+ "syn 2.0.61",
+]
+
+[[package]]
name = "dashmap"
version = "5.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -565,6 +739,15 @@ dependencies = [
]
[[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 = "extended"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -792,6 +975,12 @@ 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.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -817,6 +1006,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
[[package]]
+name = "heck"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
+
+[[package]]
name = "hermit-abi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -951,6 +1146,12 @@ dependencies = [
]
[[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"
@@ -997,6 +1198,12 @@ dependencies = [
]
[[package]]
+name = "is_terminal_polyfill"
+version = "1.70.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
+
+[[package]]
name = "isolang"
version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1142,6 +1349,21 @@ dependencies = [
]
[[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.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1540,6 +1762,35 @@ dependencies = [
]
[[package]]
+name = "poise"
+version = "0.6.1"
+source = "git+https://pub.npry.dev/poise#f72f91ad0c11f403e9ceee5b4581a348c38b3bb6"
+dependencies = [
+ "async-trait",
+ "derivative",
+ "futures-util",
+ "indexmap",
+ "parking_lot",
+ "poise_macros",
+ "regex",
+ "serenity",
+ "tokio",
+ "tracing",
+ "trim-in-place",
+]
+
+[[package]]
+name = "poise_macros"
+version = "0.6.1"
+source = "git+https://pub.npry.dev/poise#f72f91ad0c11f403e9ceee5b4581a348c38b3bb6"
+dependencies = [
+ "darling",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.61",
+]
+
+[[package]]
name = "poly1305"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1611,6 +1862,17 @@ dependencies = [
]
[[package]]
+name = "pulldown-cmark"
+version = "0.9.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "57206b407293d2bcd3af849ce869d52068623f19e1b5ff8e8778e3309439682b"
+dependencies = [
+ "bitflags 2.5.0",
+ "memchr",
+ "unicase",
+]
+
+[[package]]
name = "quote"
version = "1.0.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2004,6 +2266,15 @@ dependencies = [
]
[[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"
@@ -2078,6 +2349,15 @@ dependencies = [
]
[[package]]
+name = "semver"
+version = "1.0.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b"
+dependencies = [
+ "serde",
+]
+
+[[package]]
name = "serde"
version = "1.0.201"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2108,6 +2388,15 @@ dependencies = [
]
[[package]]
+name = "serde_cow"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e7bbbec7196bfde255ab54b65e34087c0849629280028238e67ee25d6a4b7da"
+dependencies = [
+ "serde",
+]
+
+[[package]]
name = "serde_derive"
version = "1.0.201"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2154,15 +2443,16 @@ dependencies = [
[[package]]
name = "serenity"
-version = "0.12.1"
+version = "0.12.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c64da29158bb55d70677cacd4f4f8eab1acef005fb830d9c3bea411b090e96a9"
+checksum = "880a04106592d0a8f5bdacb1d935889bfbccb4a14f7074984d9cd857235d34ac"
dependencies = [
"arrayvec",
"async-trait",
- "base64 0.21.7",
+ "base64 0.22.1",
"bitflags 2.5.0",
"bytes",
+ "chrono",
"command_attr",
"dashmap",
"flate2",
@@ -2175,6 +2465,7 @@ dependencies = [
"reqwest",
"secrecy",
"serde",
+ "serde_cow",
"serde_json",
"static_assertions",
"time",
@@ -2182,6 +2473,7 @@ dependencies = [
"tokio-tungstenite 0.21.0",
"tracing",
"typemap_rev",
+ "typesize",
"url",
"uwl",
]
@@ -2259,6 +2551,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d"
[[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"
@@ -2390,6 +2697,12 @@ dependencies = [
]
[[package]]
+name = "strsim"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
+
+[[package]]
name = "subtle"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2640,6 +2953,12 @@ dependencies = [
]
[[package]]
+name = "tagptr"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417"
+
+[[package]]
name = "tap"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2693,6 +3012,7 @@ version = "0.2.0"
dependencies = [
"anyhow",
"chrono",
+ "clap",
"deadpool-postgres",
"diesel",
"diesel-async",
@@ -2708,6 +3028,7 @@ dependencies = [
"log",
"pest",
"pest_derive",
+ "poise",
"rand",
"regex",
"reqwest",
@@ -3022,6 +3343,18 @@ dependencies = [
]
[[package]]
+name = "trim-in-place"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "343e926fc669bc8cde4fa3129ab681c63671bae288b1f1081ceee6d9d37904fc"
+
+[[package]]
+name = "triomphe"
+version = "0.1.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6631e42e10b40c0690bf92f404ebcfe6e1fdb480391d15f17cc8e96eeed5369"
+
+[[package]]
name = "try-lock"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3134,6 +3467,35 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
[[package]]
+name = "typesize"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eb704842c709bc76f63e99e704cb208beeccca2abbabd0d9aec02e48ca1cee0f"
+dependencies = [
+ "chrono",
+ "dashmap",
+ "hashbrown",
+ "mini-moka",
+ "parking_lot",
+ "secrecy",
+ "serde_json",
+ "time",
+ "typesize-derive",
+ "url",
+]
+
+[[package]]
+name = "typesize-derive"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "905e88c2a4cc27686bd57e495121d451f027e441388a67f773be729ad4be1ea8"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.61",
+]
+
+[[package]]
name = "ucd-trie"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3219,6 +3581,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
[[package]]
+name = "utf8parse"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
+
+[[package]]
name = "uuid"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3252,6 +3620,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
+name = "walkdir"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
+dependencies = [
+ "same-file",
+ "winapi-util",
+]
+
+[[package]]
name = "want"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3424,6 +3802,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
+name = "winapi-util"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
+dependencies = [
+ "windows-sys 0.52.0",
+]
+
+[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/Cargo.toml b/Cargo.toml
index fa9aac5..f3f28e6 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -3,6 +3,11 @@ name = "thulani"
version = "0.2.0"
authors = ["Nathan Perry <np@npry.dev>"]
edition = "2021"
+default-run = "thulani"
+
+[[bin]]
+name = "batch_delmeme"
+required-features = ["db"]
[features]
default = ["db", "games"]
@@ -32,6 +37,8 @@ timeago = "0.4"
statrs = "0.16"
fnv = "1.0"
+clap = { version = "4.5", features = ["derive"] }
+
pest = "2.7"
pest_derive = "2.7"
@@ -45,6 +52,8 @@ tokio = { version = "1.37", features = ["full"] }
songbird = { version = "0.4", features = ["builtin-queue"] }
symphonia = { version = "0.5", features = ["all"] }
+poise = { git = "https://pub.npry.dev/poise" }
+
diesel = { version = "2.1", features = ["chrono"], optional = true }
diesel-async = { version = "0.4", optional = true, features = ["deadpool", "postgres"] }
diesel_async_migrations = { version = "0.12", optional = true }
@@ -56,3 +65,9 @@ version = "0.12"
default-features = false
features = ["builder", "cache", "client", "framework", "gateway", "http", "model", "utils", "voice", "standard_framework", "rustls_backend"]
+[profile.release]
+codegen-units = 1
+lto = "fat"
+overflow-checks = false
+debug-assertions = false
+opt-level = 3
diff --git a/src/bot.rs b/src/bot.rs
index db37754..c8b01ab 100644
--- a/src/bot.rs
+++ b/src/bot.rs
@@ -1,9 +1,9 @@
use std::{
+ collections::HashSet,
fs::File,
future::Future,
path::PathBuf,
pin::Pin,
- result::Result as StdResult,
str::FromStr,
sync::Arc,
};
@@ -21,22 +21,16 @@ use log::{
trace,
warn,
};
+use poise::{
+ BoxFuture,
+ FrameworkError,
+};
use serenity::{
all::{
GuildId,
ReactionType,
},
- async_trait,
- framework::{
- standard::{
- BucketBuilder,
- CommandError,
- Configuration,
- },
- StandardFramework,
- },
model::{
- channel::Message,
event::ResumedEvent,
gateway::Ready,
id::{
@@ -56,11 +50,13 @@ use songbird::{
use tokio::sync::Mutex;
use crate::{
- commands::register_commands,
+ commands,
config::CONFIG,
util,
util::OAUTH_URL,
Error,
+ PoiseContext,
+ PoiseData,
Result,
};
@@ -123,7 +119,7 @@ impl EventHandler for Handler {
struct SongbirdHandler(Arc<Mutex<Call>>);
-#[async_trait]
+#[serenity::async_trait]
impl songbird::events::EventHandler for SongbirdHandler {
async fn act(&self, _ctx: &EventContext<'_>) -> Option<Event> {
let mut call = self.0.lock().await;
@@ -161,112 +157,125 @@ lazy_static! {
let result = restrict_ids.unwrap_or_default().into_iter().collect::<FnvHashSet<_>>();
- info!("restricted ids: {:?}", result);
+ info!("restricted ids: {result:?}");
result
};
}
-async fn framework() -> StandardFramework {
- let builder = BucketBuilder::default().delay(1).limit(20).time_span(60);
+fn on_err(err: FrameworkError<PoiseData, anyhow::Error>) -> BoxFuture<()> {
+ Box::pin(async move {
+ error!("error encountered: {err:?}");
- let framework = StandardFramework::new()
- .before(before_handle)
- .after(after_handle)
- .bucket("Standard", builder)
- .await;
+ if let Some(ctx) = err.ctx() {
+ if let Err(e) = util::react(ctx, ReactionType::Unicode("❌".to_owned())).await {
+ error!("reacting to failed message: {e}");
+ }
- let config = Configuration::default()
- .allow_dm(false)
- .with_whitespace(true)
- .prefixes(ALL_PREFIXES.iter().map(|x| x.to_string()))
- .ignore_bots(true)
- .on_mention(None)
- .owners(vec![CONFIG.discord.owner()].into_iter().collect())
- .case_insensitivity(true);
+ if let Err(e) = util::reply(ctx, "BANIC").await {
+ error!("sending BANIC: {e}");
+ }
+ }
+ })
+}
- framework.configure(config);
+async fn framework() -> poise::Framework<PoiseData, anyhow::Error> {
+ let additional_prefixes =
+ ALL_PREFIXES.iter().skip(1).map(|x| poise::Prefix::Literal(x.to_owned())).collect();
- register_commands(framework)
-}
+ let framework = poise::Framework::builder()
+ .options(poise::FrameworkOptions {
+ pre_command: before_handle,
+ post_command: after_handle,
+ on_error: on_err,
+
+ command_check: Some(check),
+
+ prefix_options: poise::PrefixFrameworkOptions {
+ prefix: ALL_PREFIXES.get(0).map(|&x| x.to_owned()),
+ additional_prefixes,
+ case_insensitive_commands: true,
+ mention_as_prefix: false,
+ ignore_bots: true,
+ ..Default::default()
+ },
+
+ commands: commands::commands(),
+ owners: HashSet::from_iter([CONFIG.discord.owner()]),
+ initialize_owners: false,
+ skip_checks_for_owners: true,
-fn before_handle<'fut>(
- ctx: &'fut Context,
- message: &'fut Message,
- cmd: &'fut str,
-) -> Pin<Box<dyn Future<Output = bool> + Send + 'fut>> {
- debug!("got command '{}' from user '{}' ({})", cmd, message.author.name, message.author.id);
+ ..Default::default()
+ })
+ .setup(|_ctx, _ready, _framework| Box::pin(async move { Ok(()) }))
+ .build();
+
+ framework
+}
+fn check(ctx: PoiseContext) -> BoxFuture<anyhow::Result<bool>> {
Box::pin(async move {
- if !message.guild_id.map_or(false, |x| x == CONFIG.discord.guild()) {
- info!("rejecting command '{}' from user '{}': wrong guild", cmd, message.author.name);
- return false;
+ if !ctx.guild_id().map_or(false, |x| x == CONFIG.discord.guild()) {
+ info!(
+ "rejecting command '{}' from user '{}': wrong guild",
+ ctx.command().name,
+ ctx.author().name
+ );
+ return Ok(false);
}
- if message.author.id == CONFIG.discord.owner() {
- return true;
+ if ctx.author().id == CONFIG.discord.owner() {
+ return Ok(true);
}
- let restricted_prefix =
- RESTRICTED_PREFIXES.iter().any(|prefix| message.content.starts_with(prefix));
+ let restricted_prefix = RESTRICTED_PREFIXES.iter().any(|&prefix| ctx.prefix() == prefix);
if !restricted_prefix {
- return true;
+ return Ok(true);
}
const PERMITTED_WEEKDAY: chrono::Weekday = chrono::Weekday::Tue;
- let user_is_restricted = RESTRICT_IDS.contains(&message.author.id.get());
+ let user_is_restricted = RESTRICT_IDS.contains(&ctx.author().id.get());
let restrictions_flipped = chrono::Local::now().weekday() == PERMITTED_WEEKDAY;
if user_is_restricted == restrictions_flipped {
- return true;
+ return Ok(true);
}
let reason = if !restrictions_flipped {
"restricted prefix".to_owned()
} else {
- format!("it is {:?}", PERMITTED_WEEKDAY)
+ format!("it is {PERMITTED_WEEKDAY:?}")
};
- info!("rejecting command '{}' from user '{}': {}", cmd, message.author.name, reason);
+ info!(
+ "rejecting command '{}' from user '{}': {}",
+ ctx.command().name,
+ ctx.author().name,
+ reason
+ );
- match util::send_result(ctx, message.channel_id, "no", message.tts).await {
- Err(e) => error!("sending restricted prefix response: {}", e),
- Ok(msg_id) => {
- let mut mp = MESSAGE_WATCH.lock().await;
- mp.insert(message.id, msg_id);
- },
- }
+ util::reply(ctx, "no").await?;
- false
+ Ok(false)
})
}
-fn after_handle<'fut>(
- ctx: &'fut Context,
- msg: &'fut Message,
- cmd: &'fut str,
- err: StdResult<(), CommandError>,
-) -> Pin<Box<dyn Future<Output = ()> + Send + 'fut>> {
- Box::pin(async move {
- match err {
- Ok(()) => {
- trace!("command '{}' completed successfully", cmd);
- },
-
- Err(e) => {
- if let Err(e) = msg.react(&ctx, ReactionType::Unicode("❌".to_owned())).await {
- error!("reacting to failed message: {}", e);
- }
+fn before_handle<'fut>(ctx: PoiseContext<'fut>) -> Pin<Box<dyn Future<Output = ()> + Send + 'fut>> {
+ debug!(
+ "got command '{}' from user '{}' ({})",
+ ctx.command().name,
+ ctx.author().name,
+ ctx.author().id
+ );
- if let Err(e) = util::send(ctx, msg.channel_id, "BANIC", msg.tts).await {
- error!("sending BANIC: {}", e);
- }
+ Box::pin(async {})
+}
- error!("error encountered handling command '{}': {:?}", cmd, e);
- },
- }
+fn after_handle(ctx: PoiseContext) -> BoxFuture<()> {
+ Box::pin(async move {
+ trace!("command '{}' completed successfully", ctx.command().name);
})
}
diff --git a/src/game.rs b/src/commands/game.rs
index 4011a5d..72633b5 100644
--- a/src/game.rs
+++ b/src/commands/game.rs
@@ -1,5 +1,4 @@
use std::{
- convert::Infallible,
fs,
iter,
path::PathBuf,
@@ -10,10 +9,7 @@ use std::{
},
};
-use anyhow::{
- anyhow,
- Error,
-};
+use anyhow::anyhow;
use fnv::{
FnvHashMap,
FnvHashSet,
@@ -26,24 +22,9 @@ use log::{
info,
};
use serde::Deserialize;
-use serenity::{
- framework::standard::{
- macros::{
- command,
- group,
- },
- ArgError,
- Args,
- CommandError,
- CommandResult,
- },
- futures::TryFutureExt,
- model::{
- channel::Message,
- guild::Guild,
- id::UserId,
- },
- prelude::*,
+use serenity::model::{
+ guild::Guild,
+ id::UserId,
};
use tap::Pipe;
use url::Url;
@@ -51,14 +32,11 @@ use url::Url;
use crate::{
bot::HttpKey,
util,
+ PoiseContext,
Result,
CONFIG,
};
-#[group]
-#[commands(game, installedgame, ownedgame, updategaem)]
-pub struct Game;
-
lazy_static! {
static ref SPREADSHEET_URL: Url = Url::parse(&format!(
"https://sheets.googleapis.com/v4/spreadsheets/{}/values:batchGet",
@@ -142,7 +120,7 @@ enum GameStatus {
}
impl FromStr for GameStatus {
- type Err = Error;
+ type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self> {
use std::char;
@@ -161,16 +139,18 @@ impl FromStr for GameStatus {
}
}
-#[command]
-#[aliases("installedgaem")]
-pub async fn installedgame(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
- _game(ctx, msg, args, GameStatus::Installed).await
+pub fn commands() -> Vec<poise::Command<crate::PoiseData, anyhow::Error>> {
+ vec![installedgame(), ownedgame(), game(), updategaem()]
}
-#[command]
-#[aliases("ownedgaem")]
-pub async fn ownedgame(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
- _game(ctx, msg, args, GameStatus::NotInstalled).await
+#[poise::command(prefix_command, guild_only, category = "gaem", aliases("installedgaem"))]
+pub async fn installedgame(ctx: PoiseContext<'_>, args: util::RestVec) -> anyhow::Result<()> {
+ _game(ctx, args.into_inner(), GameStatus::Installed).await
+}
+
+#[poise::command(prefix_command, guild_only, category = "gaem", aliases("ownedgaem"))]
+pub async fn ownedgame(ctx: PoiseContext<'_>, args: util::RestVec) -> anyhow::Result<()> {
+ _game(ctx, args.into_inner(), GameStatus::NotInstalled).await
}
#[derive(Copy, Clone, Debug, thiserror::Error, PartialEq, Eq, Hash)]
@@ -222,29 +202,25 @@ pub fn get_user_id<S: AsRef<str>>(g: &Guild, s: S) -> StdResult<UserId, UserLook
}
}
-#[command]
-#[aliases("gaem")]
-async fn game(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
- _game(ctx, msg, args, GameStatus::Installed).await
+#[poise::command(prefix_command, guild_only, category = "gaem", aliases("gaem"))]
+async fn game(ctx: PoiseContext<'_>, args: util::RestVec) -> anyhow::Result<()> {
+ _game(ctx, args.into_inner(), GameStatus::Installed).await
}
async fn _game(
- ctx: &Context,
- msg: &Message,
- mut args: Args,
+ ctx: PoiseContext<'_>,
+ user_args: Vec<String>,
min_status: GameStatus,
-) -> CommandResult {
- let users = {
- let guild =
- msg.channel_id.to_channel(&ctx).await?.guild().ok_or(anyhow!("couldn't find guild"))?;
-
- let user_args: Vec<String> = if args.rest().is_empty() {
- Vec::new()
- } else {
- args.quoted().iter::<String>().collect::<StdResult<Vec<_>, ArgError<Infallible>>>()?
- };
+) -> anyhow::Result<()> {
+ use serenity::futures::StreamExt;
- use serenity::futures::StreamExt;
+ let users = {
+ let guild = ctx
+ .channel_id()
+ .to_channel(&ctx)
+ .await?
+ .guild()
+ .ok_or(anyhow!("couldn't find guild"))?;
let mut users = user_args
.into_iter()
@@ -268,25 +244,15 @@ async fn _game(
debug!("parsed userid {:?}", possible);
match possible {
+ Ok(x) => Some(x),
Err(UserLookupError::NotFound) => {
- let _ = util::send(
- ctx,
- msg.channel_id,
- &format!("didn't recognize {}", &u),
- msg.tts,
- )
- .await;
+ let _ = util::reply(ctx, format!("didn't recognize {u}")).await;
+
None
},
- Ok(x) => Some(x),
Err(UserLookupError::Ambiguous(x)) => {
- let _ = util::send(
- ctx,
- msg.channel_id,
- &format!("too many matches ({}) for {}", x, &u),
- msg.tts,
- )
- .await;
+ let _ =
+ util::reply(ctx, format!("too many matches ({x}) for {u}")).await;
None
},
}
@@ -296,7 +262,7 @@ async fn _game(
let res = DISCORD_MAP.get(&uid).map(|s| s.to_lowercase());
if res.is_none() {
- info!("user {} is not recognized", uid);
+ info!("user {uid} is not recognized");
}
res
@@ -314,7 +280,7 @@ async fn _game(
.collect::<FnvHashMap<_, _>>();
let channel =
- pairs.get(&msg.author.id).cloned().unwrap_or(CONFIG.discord.voice_channel());
+ pairs.get(&ctx.author().id).cloned().unwrap_or(CONFIG.discord.voice_channel());
users = pairs
.iter()
@@ -335,12 +301,12 @@ async fn _game(
if inferred && users.len() < 2 || !inferred && users.is_empty() {
info!("too few known users to make game comparison");
- util::send(ctx, msg.channel_id, "yer too lonely", msg.tts).await?;
+ util::reply(ctx, "yer too lonely").await?;
return Ok(());
}
let client = {
- let data = ctx.data.read().await;
+ let data = ctx.serenity_context().data.read().await;
data.get::<HttpKey>().unwrap().clone()
};
let data = load_spreadsheet(&client).await?;
@@ -418,7 +384,7 @@ async fn _game(
games_formatted = "**LITERALLY NOTHING**".to_owned();
}
- util::send(ctx, msg.channel_id, &games_formatted, msg.tts).await?;
+ util::reply(ctx, games_formatted).await?;
Ok(())
}
@@ -451,29 +417,36 @@ async fn load_spreadsheet(client: &reqwest::Client) -> Result<Vec<Vec<String>>>
Ok(resp.value_ranges.into_iter().next().unwrap().values)
}
-#[command]
-#[aliases("updategame")]
-pub async fn updategaem(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
+#[poise::command(
+ slash_command,
+ prefix_command,
+ guild_only,
+ category = "gaem",
+ aliases("updategame")
+)]
+pub async fn updategaem(ctx: PoiseContext<'_>, user: Option<String>) -> anyhow::Result<()> {
use regex::Regex;
+ use std::borrow::Borrow;
let client = {
- let data = ctx.data.read().await;
+ let data = ctx.serenity_context().data.read().await;
data.get::<HttpKey>().unwrap().clone()
};
- let arg_user = args.single_quoted::<String>();
+ let user = match user {
+ None => ctx.author().id,
+ Some(user) => {
+ let guild = ctx
+ .channel_id()
+ .to_channel(&ctx)
+ .await?
+ .guild()
+ .ok_or(anyhow!("couldn't find guild"))?;
- let user = if arg_user.is_err() {
- msg.author.id
- } else {
- use std::borrow::Borrow;
-
- let guild =
- msg.channel_id.to_channel(&ctx).await?.guild().ok_or(anyhow!("couldn't find guild"))?;
+ let guild = guild.guild(&ctx).ok_or(anyhow!("couldn't find guild"))?;
- let guild = guild.guild(&ctx).ok_or(anyhow!("couldn't find guild"))?;
-
- get_user_id(guild.borrow(), arg_user.unwrap()).map_err(Error::from)?
+ get_user_id(guild.borrow(), user).map_err(anyhow::Error::from)?
+ },
};
debug!("parsed userid {:?}", user);
@@ -481,18 +454,16 @@ pub async fn updategaem(ctx: &Context, msg: &Message, mut args: Args) -> Command
let username = match DISCORD_MAP.get(&user) {
Some(s) => s,
None => {
- return util::send(ctx, msg.channel_id, "WHO THE FUCK ARE YE", msg.tts)
- .map_err(CommandError::from)
- .await;
+ util::reply(ctx, "WHO THE FUCK ARE YE").await?;
+ return Ok(());
},
};
let steam_id = match STEAM_MAP.get(&user) {
Some(u) => u,
None => {
- return util::send(ctx, msg.channel_id, "WHO ARE YE ON STEAM", msg.tts)
- .map_err(CommandError::from)
- .await;
+ util::reply(ctx, "WHO ARE YE ON STEAM").await?;
+ return Ok(());
},
};
@@ -504,9 +475,8 @@ pub async fn updategaem(ctx: &Context, msg: &Message, mut args: Args) -> Command
let user_column = match user_column {
Some(c) => &spreadsheet[c][1..],
None => {
- return util::send(ctx, msg.channel_id, "YER NOT IN THE SPREADSHEET", msg.tts)
- .map_err(CommandError::from)
- .await;
+ util::reply(ctx, "YER NOT IN THE SPREADSHEET").await?;
+ return Ok(());
},
};
@@ -520,9 +490,8 @@ pub async fn updategaem(ctx: &Context, msg: &Message, mut args: Args) -> Command
Some(c) => &spreadsheet[c][1..],
None => {
error!("didn't find an appid column in the spreadsheet");
- return util::send(ctx, msg.channel_id, "SPREADSHEET BROKE", msg.tts)
- .map_err(CommandError::from)
- .await;
+ util::reply(ctx, "SPREADSHEET BROKE").await?;
+ return Ok(());
},
};
@@ -560,7 +529,7 @@ pub async fn updategaem(ctx: &Context, msg: &Message, mut args: Args) -> Command
}
let client = {
- let data = ctx.data.read().await;
+ let data = ctx.serenity_context().data.read().await;
data.get::<HttpKey>().unwrap().clone()
};
@@ -587,19 +556,16 @@ pub async fn updategaem(ctx: &Context, msg: &Message, mut args: Args) -> Command
.join("\n");
if !found_games.is_empty() {
- util::send(
+ let n_missing = found_games.chars().filter(|x| *x == '\n').count() + 1;
+ util::reply(
ctx,
- msg.channel_id,
- &format!(
- "{} games owned on steam that are missing from the list:\n{}",
- found_games.chars().filter(|x| *x == '\n').count() + 1,
- found_games
+ format!(
+ "{n_missing} games owned on steam that are missing from the list:\n{found_games}"
),
- msg.tts,
)
.await?;
} else {
- util::send(ctx, msg.channel_id, "up to date", msg.tts).await?;
+ util::reply(ctx, "up to date").await?;
}
Ok(())
diff --git a/src/commands/help.rs b/src/commands/help.rs
deleted file mode 100644
index 0d84b2d..0000000
--- a/src/commands/help.rs
+++ /dev/null
@@ -1,31 +0,0 @@
-use std::collections::HashSet;
-
-use serenity::{
- framework::standard::{
- help_commands,
- macros::help,
- Args,
- CommandGroup,
- CommandResult,
- HelpOptions,
- },
- model::{
- channel::Message,
- id::UserId,
- },
- prelude::*,
-};
-
-#[help]
-pub async fn help(
- ctx: &Context,
- msg: &Message,
- args: Args,
- opts: &'static HelpOptions,
- groups: &[&'static CommandGroup],
- owners: HashSet<UserId>,
-) -> CommandResult {
- help_commands::with_embeds(ctx, msg, args, opts, groups, owners).await?;
-
- Ok(())
-}
diff --git a/src/commands/meme/create.rs b/src/commands/meme/create.rs
index 9aff370..cad9bfc 100644
--- a/src/commands/meme/create.rs
+++ b/src/commands/meme/create.rs
@@ -2,25 +2,12 @@ use std::process::Stdio;
use anyhow::anyhow;
use diesel::result::Error as DieselError;
-use lazy_static::lazy_static;
use log::{
debug,
error,
warn,
};
-use serenity::{
- all::ReactionType,
- framework::standard::{
- macros::command,
- Args,
- CommandError,
- CommandResult,
- Delimiter,
- },
- futures::TryFutureExt,
- model::channel::Message,
- prelude::*,
-};
+use serenity::all::ReactionType;
use tap::Pipe;
use tokio::{
io::AsyncReadExt,
@@ -37,20 +24,16 @@ use crate::{
},
parse_times,
util,
+ PoiseContext,
FFMPEG_COMMAND,
};
-lazy_static! {
- static ref DELIMS: Vec<Delimiter> = vec![' '.into(), '\n'.into(), '\t'.into()];
-}
-
-#[command]
-pub async fn addmeme(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
- let mut args = Args::new(args.rest(), DELIMS.as_ref());
-
- let title = args.single_quoted::<String>()?;
- let text = args.rest().to_owned();
-
+#[poise::command(slash_command, prefix_command, guild_only, category = "memes")]
+pub async fn addmeme(
+ ctx: PoiseContext<'_>,
+ title: String,
+ #[rest] text: String,
+) -> anyhow::Result<()> {
let text = if text.is_empty() {
None
} else {
@@ -59,20 +42,21 @@ pub async fn addmeme(ctx: &Context, msg: &Message, args: Args) -> CommandResult
let mut conn = connection().await?;
- let image = msg.attachments.first();
+ let image = util::msg(ctx).and_then(|msg| msg.attachments.first());
if image.is_none() && text.is_none() {
warn!("tried to create non-audio meme with no image or text");
- return util::send(ctx, msg.channel_id, "hahAA it's empty xdddd", msg.tts)
- .map_err(CommandError::from)
- .await;
+
+ util::reply(ctx, "hahAA it's empty xdddd").await?;
+ return Ok(());
}
let mut image_id = None;
if let Some(att) = image {
let data = att.download().await?;
- image_id = Some(Image::create(&mut conn, &att.filename, data, msg.author.id.get()).await?);
+ image_id =
+ Some(Image::create(&mut conn, &att.filename, data, ctx.author().id.get()).await?);
};
let save_result = NewMeme {
@@ -82,24 +66,25 @@ pub async fn addmeme(ctx: &Context, msg: &Message, args: Args) -> CommandResult
audio_id: None,
metadata_id: 0,
}
- .save(&mut conn, msg.author.id.get())
+ .save(&mut conn, ctx.author().id.get())
.await
.map(|_| {});
use diesel::result::DatabaseErrorKind;
match save_result {
Ok(_) => {
- msg.react(&ctx, ReactionType::Unicode("👌".to_string())).await?;
+ util::react(ctx, ReactionType::Unicode("👌".to_string())).await?;
},
Err(e) => {
if let Some(DieselError::DatabaseError(DatabaseErrorKind::UniqueViolation, _)) =
e.downcast_ref::<DieselError>()
{
error!("tried to create meme that already exists");
- msg.react(&ctx, ReactionType::Unicode("❌".to_owned())).await?;
- return util::send(ctx, msg.channel_id, "that meme already exists", msg.tts)
- .map_err(CommandError::from)
- .await;
+
+ util::react(ctx, ReactionType::Unicode("❌".to_owned())).await?;
+ util::reply(ctx, "that meme already exists").await?;
+
+ return Ok(());
}
return Err(e.into());
@@ -109,19 +94,19 @@ pub async fn addmeme(ctx: &Context, msg: &Message, args: Args) -> CommandResult
Ok(())
}
-#[command]
-pub async fn addaudiomeme(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
+#[poise::command(slash_command, prefix_command, guild_only, category = "memes")]
+pub async fn addaudiomeme(
+ ctx: PoiseContext<'_>,
+ title: String,
+ audio_str: String,
+ #[rest] rest: String,
+) -> anyhow::Result<()> {
debug!("running addaudiomeme");
- let mut args = Args::new(args.rest(), DELIMS.as_ref());
-
- let title = args.single_quoted::<String>()?;
- let audio_str = args.single_quoted::<String>()?;
-
let elems = audio_str.split_whitespace().collect::<Vec<_>>();
if elems.is_empty() {
- util::send(ctx, msg.channel_id, "are you stupid", msg.tts).await?;
+ util::reply(ctx, "are you stupid").await?;
return Err(anyhow!("no audio link was provided").into());
}
@@ -168,23 +153,23 @@ pub async fn addaudiomeme(ctx: &Context, msg: &Message, args: Args) -> CommandRe
let mut audio_reader = ffmpeg_command.stdout.unwrap();
- let text = args.rest().to_owned();
- let text = if text.is_empty() {
+ let text = if rest.is_empty() {
None
} else {
- Some(text)
+ Some(rest)
};
let mut conn = connection().await?;
- let image_att = msg.attachments.first().ok_or(anyhow!("no attachment"));
+ let image_att =
+ util::msg(ctx).and_then(|x| x.attachments.first()).ok_or(anyhow!("no attachment"));
let mut image_id = None;
if let Ok(att) = image_att {
let data = att.download().await?;
image_id =
- Image::create(&mut conn, &att.filename, data, msg.author.id.get()).await?.pipe(Some);
+ Image::create(&mut conn, &att.filename, data, ctx.author().id.get()).await?.pipe(Some);
}
let mut audio_data = Vec::new();
@@ -193,12 +178,12 @@ pub async fn addaudiomeme(ctx: &Context, msg: &Message, args: Args) -> CommandRe
if bytes == 0 {
debug!("read 0 bytes from audio reader");
- return util::send(ctx, msg.channel_id, "🔇🔇🔇🔕🔕🔕🔕🔕🔇🔕🔕🔇🔕🔕📣📢📣📢📣", msg.tts)
- .map_err(CommandError::from)
- .await;
+
+ util::reply(ctx, "🔇🔇🔇🔕🔕🔕🔕🔕🔇🔕🔕🔇🔕🔕📣📢📣📢📣").await?;
+ return Ok(());
}
- let audio_id = Audio::create(&mut conn, audio_data, msg.author.id.get()).await?;
+ let audio_id = Audio::create(&mut conn, audio_data, ctx.author().id.get()).await?;
let save_result = NewMeme {
title,
@@ -207,24 +192,25 @@ pub async fn addaudiomeme(ctx: &Context, msg: &Message, args: Args) -> CommandRe
audio_id: Some(audio_id),
metadata_id: 0,
}
- .save(&mut conn, msg.author.id.get())
+ .save(&mut conn, ctx.author().id.get())
.await
.map(|_| {});
use diesel::result::DatabaseErrorKind;
match save_result {
Ok(_) => {
- msg.react(&ctx, ReactionType::Unicode("👌".to_owned())).await?;
+ util::react(ctx, ReactionType::Unicode("👌".to_owned())).await?;
},
Err(e) => {
if let Some(DieselError::DatabaseError(DatabaseErrorKind::UniqueViolation, _)) =
e.downcast_ref::<DieselError>()
{
error!("tried to create meme that already exists");
- msg.react(&ctx, ReactionType::Unicode("❌".to_owned())).await?;
- return util::send(ctx, msg.channel_id, "that meme already exists", msg.tts)
- .map_err(CommandError::from)
- .await;
+
+ util::react(ctx, ReactionType::Unicode("❌".to_owned())).await?;
+ util::reply(ctx, "that meme already exists").await?;
+
+ return Ok(());
}
return Err(e.into());
diff --git a/src/commands/meme/delete.rs b/src/commands/meme/delete.rs
index 6af1b6b..25ddf0d 100644
--- a/src/commands/meme/delete.rs
+++ b/src/commands/meme/delete.rs
@@ -3,42 +3,33 @@ use diesel::{
NotFound,
};
use log::info;
-use serenity::{
- all::ReactionType,
- framework::standard::{
- macros::command,
- Args,
- CommandResult,
- },
- model::channel::Message,
- prelude::*,
-};
+use serenity::all::ReactionType;
use crate::{
db::{
connection,
delete_meme,
},
+ msg,
util,
+ PoiseContext,
};
-#[command]
-#[aliases("delmem")]
-pub async fn delmeme(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
- let title = args.single_quoted::<String>()?;
-
+#[poise::command(slash_command, prefix_command, guild_only, category = "memes", aliases("delmem"))]
+pub async fn delmeme(ctx: PoiseContext<'_>, title: String) -> anyhow::Result<()> {
let mut conn = connection().await?;
- match delete_meme(&mut conn, &title, msg.author.id.get()).await {
+ match delete_meme(&mut conn, &title, ctx.author().id.get()).await {
Ok(_) => {
- msg.react(ctx, ReactionType::Unicode("💀".to_owned())).await?;
+ util::react(ctx, ReactionType::Unicode("💀".to_owned())).await?;
Ok(())
},
Err(e) => {
if let Some(NotFound) = e.downcast_ref::<DieselError>() {
- msg.react(&ctx, ReactionType::Unicode("❓".to_owned())).await?;
info!("attempted to delete nonexistent meme: '{}'", title);
- util::send(ctx, msg.channel_id, "nice try", msg.tts).await?;
+
+ util::react(ctx, ReactionType::Unicode("❓".to_owned())).await?;
+ util::reply(ctx, "nice try").await?;
return Ok(());
}
diff --git a/src/commands/meme/history.rs b/src/commands/meme/history.rs
index edc75cd..cfd78df 100644
--- a/src/commands/meme/history.rs
+++ b/src/commands/meme/history.rs
@@ -1,4 +1,3 @@
-use anyhow::anyhow;
use diesel::{
result::Error as DieselError,
NotFound,
@@ -11,18 +10,10 @@ use log::{
info,
};
use serenity::{
- framework::standard::{
- macros::command,
- Args,
- CommandError,
- CommandResult,
- },
futures::{
StreamExt,
- TryFutureExt,
TryStreamExt,
},
- model::channel::Message,
prelude::*,
};
use tap::Pipe;
@@ -32,6 +23,7 @@ use timeago::{
};
use crate::{
+ commands::game::get_user_id,
db::{
self,
connection,
@@ -40,6 +32,7 @@ use crate::{
Metadata,
},
util,
+ PoiseContext,
CONFIG,
};
@@ -55,9 +48,14 @@ lazy_static! {
static CLEAN_DATE_FORMAT: &str = "%b %-e %Y";
-#[command]
-#[aliases("what", "hwaet", "hwæt")]
-pub async fn wat(ctx: &Context, msg: &Message, _: Args) -> CommandResult {
+#[poise::command(
+ slash_command,
+ prefix_command,
+ guild_only,
+ category = "memes",
+ aliases("what", "hwaet", "hwæt")
+)]
+pub async fn wat(ctx: PoiseContext<'_>) -> anyhow::Result<()> {
let mut conn = connection().await?;
let record = match InvocationRecord::last(&mut conn).await {
@@ -65,12 +63,12 @@ pub async fn wat(ctx: &Context, msg: &Message, _: Args) -> CommandResult {
Err(e) => {
if let Some(NotFound) = e.downcast_ref::<DieselError>() {
info!("found no memes in history");
- return util::send(ctx, msg.channel_id, "no one has ever memed before", msg.tts)
- .map_err(CommandError::from)
- .await;
+
+ util::reply(ctx, "no one has ever memed before").await?;
+ return Ok(());
}
- util::send(ctx, msg.channel_id, "BAD MEME BAD MEME", msg.tts).await?;
+ util::reply(ctx, "BAD MEME BAD MEME").await?;
return Err(e.into());
},
};
@@ -82,44 +80,41 @@ pub async fn wat(ctx: &Context, msg: &Message, _: Args) -> CommandResult {
let metadata = Metadata::find(&mut conn, meme.metadata_id).await?;
let author = CONFIG.discord.guild().member(&ctx, metadata.created_by as u64).await?;
- util::send(
+ util::reply(
ctx,
- msg.channel_id,
&format!(
"that was \"{}\" by {} ({})",
meme.title,
author.mention(),
metadata.created.date().format(CLEAN_DATE_FORMAT)
),
- msg.tts,
)
- .await?
+ .await?;
},
Err(e) => {
if let Some(NotFound) = e.downcast_ref::<DieselError>() {
info!("last meme not found in database");
- return util::send(ctx, msg.channel_id, "heuueueeeeh?", msg.tts)
- .await
- .map_err(CommandError::from);
+
+ util::reply(ctx, "heuueueeeeh?").await?;
+ return Ok(());
}
- util::send(ctx, msg.channel_id, "do i look like i know what a jpeg is", msg.tts)
- .await?;
+ util::reply(ctx, "do i look like i know what a jpeg is").await?;
return Err(e.into());
},
};
- meme.map(|_| {}).map_err(CommandError::from)
+ let _meme = meme?;
+ Ok(())
}
-#[command]
-#[aliases("hist")]
-pub async fn history(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
- let n = args.single_quoted::<usize>().unwrap_or(CONFIG.default_hist);
-
+#[poise::command(slash_command, prefix_command, guild_only, category = "memes", aliases("hist"))]
+pub async fn history(ctx: PoiseContext<'_>, n: Option<usize>) -> anyhow::Result<()> {
+ let n = n.unwrap_or(CONFIG.default_hist);
+
if n > CONFIG.max_hist {
debug!("user requested more than MAX_HIST ({}) items from history", CONFIG.max_hist);
- util::send(ctx, msg.channel_id, "YER PUSHIN ME OVER THE FUCKIN LINE", true).await?;
+ util::reply(ctx, "YER PUSHIN ME OVER THE FUCKIN LINE").await?;
}
let n = n.min(CONFIG.max_hist);
@@ -131,9 +126,9 @@ pub async fn history(ctx: &Context, msg: &Message, mut args: Args) -> CommandRes
if records.is_empty() {
info!("no memes in history");
- return util::send(ctx, msg.channel_id, "i don't remember anything :(", msg.tts)
- .map_err(CommandError::from)
- .await;
+ util::reply(ctx, "i don't remember anything :(").await?;
+
+ return Ok(());
}
info!("reporting meme history (len {})", n);
@@ -198,19 +193,19 @@ pub async fn history(ctx: &Context, msg: &Message, mut args: Args) -> CommandRes
},
};
- Result::<_, CommandError>::Ok(result)
+ anyhow::Ok(result)
})
.try_collect::<Vec<String>>()
.await?;
let resp = resp.join("\n");
+ util::reply(ctx, resp).await?;
- util::send(ctx, msg.channel_id, &resp, false).await.map_err(CommandError::from)
+ Ok(())
}
-#[command]
-#[aliases("stat")]
-pub async fn stats(ctx: &Context, msg: &Message, _: Args) -> CommandResult {
+#[poise::command(slash_command, prefix_command, guild_only, category = "memes", aliases("stat"))]
+pub async fn stats(ctx: PoiseContext<'_>) -> anyhow::Result<()> {
use db;
use serenity::model::{
id::UserId,
@@ -277,11 +272,12 @@ and *{}* was the most-memed overall ({})"#,
stats.most_popular_meme_overall_count,
);
- util::send(ctx, msg.channel_id, s, msg.tts).map_err(CommandError::from).await
+ util::reply(ctx, s).await?;
+ Ok(())
}
-#[command]
-pub async fn memers(ctx: &Context, msg: &Message, _args: Args) -> CommandResult {
+#[poise::command(slash_command, prefix_command, guild_only, category = "memes")]
+pub async fn memers(ctx: PoiseContext<'_>) -> anyhow::Result<()> {
use serenity::model::id::UserId;
let s = db::memers()
@@ -302,25 +298,25 @@ pub async fn memers(ctx: &Context, msg: &Message, _args: Args) -> CommandResult
info.most_used_meme_count,
);
- Result::<_, CommandError>::Ok(res)
+ anyhow::Ok(res)
})
.try_collect::<Vec<String>>()
.await?
.into_iter()
.join("\n");
- util::send(ctx, msg.channel_id, &s, msg.tts).map_err(CommandError::from).await
+ util::reply(ctx, s).await?;
+
+ Ok(())
}
-#[command]
-pub async fn query(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
+#[poise::command(prefix_command, guild_only, category = "memes")]
+pub async fn query(ctx: PoiseContext<'_>, rest: util::RestVec) -> anyhow::Result<()> {
use regex::Regex;
use serenity::model::id::UserId;
- use std::borrow::Borrow;
use crate::{
db,
- game::get_user_id,
CONFIG,
};
@@ -329,42 +325,31 @@ pub async fn query(ctx: &Context, msg: &Message, mut args: Args) -> CommandResul
static ref AGE_REGEX: Regex = Regex::new(r"(?i)(?:age|order)=(.*)").unwrap();
}
- let creator: Option<u64> = {
- let guild =
- msg.channel_id.to_channel(&ctx).await?.guild().ok_or(anyhow!("couldn't find guild"))?;
+ let mut rest = rest.into_inner();
- let guild = guild.guild(&ctx).ok_or(anyhow!("couldn't find guild"))?;
+ let creator: Option<u64> = try {
+ let fst = rest.first()?;
+ let captures = CREATOR_REGEX.captures(fst)?;
+ let creator = captures.get(1)?.as_str().to_owned();
- let creator = args.quoted().current().map(|s| CREATOR_REGEX.is_match(s)).unwrap_or(false);
- if creator {
- args.single_quoted::<String>()
- .ok()
- .and_then(|s| {
- CREATOR_REGEX.captures(&s).and_then(|c| c.get(1)).map(|x| x.as_str().to_owned())
- })
- .and_then(|s| get_user_id(guild.borrow(), s).ok().map(UserId::get))
- } else {
- None
- }
+ let guild = ctx.guild()?;
+ let user_id = get_user_id(&guild, creator).ok()?.get();
+ rest.pop();
+
+ user_id
};
- let order = {
- let order = args.quoted().current().map(|s| AGE_REGEX.is_match(s)).unwrap_or(false);
+ let order: Option<String> = try {
+ let fst = rest.first()?;
+ let captures = AGE_REGEX.captures(fst)?;
+ let order = captures.get(1)?.as_str().to_owned();
- if order {
- args.single_quoted::<String>()
- .ok()
- .and_then(|s| {
- AGE_REGEX.captures(&s).and_then(|c| c.get(1)).map(|x| x.as_str().to_owned())
- })
- .map(|s: String| s.contains("new"))
- .unwrap_or(true)
- } else {
- true
- }
+ order
};
- let iter = db::query_meme(args.rest(), creator, order).await?.into_iter();
+ let order = order.is_some_and(|o| o.contains("new"));
+
+ let iter = db::query_meme(rest.join(" "), creator, order).await?.into_iter();
let result = iter
.pipe(serenity::futures::stream::iter)
@@ -380,7 +365,7 @@ pub async fn query(ctx: &Context, msg: &Message, mut args: Args) -> CommandResul
meme.content.map_or(0, |s| s.len()),
meme.image_id.map_or("NO", |_s| "YES"),
meme.audio_id.map_or("NO", |_s| "YES"),
- )) as Result<String, CommandError>
+ )) as anyhow::Result<String>
})
.try_collect::<Vec<String>>()
.await;
@@ -401,10 +386,10 @@ pub async fn query(ctx: &Context, msg: &Message, mut args: Args) -> CommandResul
if result.is_empty() {
info!("no memes matched query");
- return util::send(ctx, msg.channel_id, "no match".to_owned(), msg.tts)
- .map_err(CommandError::from)
- .await;
+ util::reply(ctx, "no match").await?;
+ return Ok(());
}
- util::send(ctx, msg.channel_id, &result, msg.tts).map_err(CommandError::from).await
+ util::reply(ctx, result).await?;
+ Ok(())
}
diff --git a/src/commands/meme/invoke.rs b/src/commands/meme/invoke.rs
index 1400452..e399e82 100644
--- a/src/commands/meme/invoke.rs
+++ b/src/commands/meme/invoke.rs
@@ -2,19 +2,7 @@ use diesel::{
result::Error as DieselError,
NotFound,
};
-use itertools::Itertools;
use log::info;
-use serenity::{
- framework::standard::{
- macros::command,
- Args,
- CommandError,
- CommandResult,
- },
- futures::TryFutureExt,
- model::channel::Message,
- prelude::*,
-};
use crate::{
commands::meme::send_meme,
@@ -25,42 +13,49 @@ use crate::{
InvocationRecord,
},
util,
+ PoiseContext,
};
-#[command]
-#[aliases("mem")]
-pub async fn meme(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
- _meme(ctx, msg, args, AudioPlayback::Optional).await
+#[poise::command(slash_command, prefix_command, guild_only, category = "memes", aliases("mem"))]
+pub async fn meme(ctx: PoiseContext<'_>, #[rest] rest: String) -> anyhow::Result<()> {
+ _meme(ctx, rest, AudioPlayback::Optional).await
}
-#[command]
-pub async fn omen(ctx: &Context, msg: &Message, _args: Args) -> CommandResult {
- let args = Args::new("", &[]);
- _meme(ctx, msg, args, AudioPlayback::Optional).await
+#[poise::command(slash_command, prefix_command, guild_only, category = "memes")]
+pub async fn omen(ctx: PoiseContext<'_>) -> anyhow::Result<()> {
+ _meme(ctx, "", AudioPlayback::Optional).await
}
-#[command]
-pub async fn silentomen(ctx: &Context, msg: &Message, _args: Args) -> CommandResult {
- let args = Args::new("", &[]);
- _meme(ctx, msg, args, AudioPlayback::Prohibited).await
+#[poise::command(slash_command, prefix_command, guild_only, category = "memes")]
+pub async fn silentomen(ctx: PoiseContext<'_>) -> anyhow::Result<()> {
+ _meme(ctx, "", AudioPlayback::Prohibited).await
}
-#[command]
-pub async fn audioomen(ctx: &Context, msg: &Message, _args: Args) -> CommandResult {
- let args = Args::new("", &[]);
- _meme(ctx, msg, args, AudioPlayback::Required).await
+#[poise::command(slash_command, prefix_command, guild_only, category = "memes")]
+pub async fn audioomen(ctx: PoiseContext<'_>) -> anyhow::Result<()> {
+ _meme(ctx, "", AudioPlayback::Required).await
}
-#[command]
-#[aliases("audiomeme", "audiomem")]
-pub async fn audio_meme(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
- _meme(ctx, msg, args, AudioPlayback::Required).await
+#[poise::command(
+ slash_command,
+ prefix_command,
+ guild_only,
+ category = "memes",
+ aliases("audiomeme", "audiomem")
+)]
+pub async fn audio_meme(ctx: PoiseContext<'_>, #[rest] rest: String) -> anyhow::Result<()> {
+ _meme(ctx, rest, AudioPlayback::Required).await
}
-#[command]
-#[aliases("silentmeme", "silentmem")]
-pub async fn silent_meme(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
- _meme(ctx, msg, args, AudioPlayback::Prohibited).await
+#[poise::command(
+ slash_command,
+ prefix_command,
+ guild_only,
+ category = "memes",
+ aliases("silentmeme", "silentmem")
+)]
+pub async fn silent_meme(ctx: PoiseContext<'_>, #[rest] rest: String) -> anyhow::Result<()> {
+ _meme(ctx, rest, AudioPlayback::Prohibited).await
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
@@ -71,21 +66,20 @@ enum AudioPlayback {
}
async fn _meme(
- ctx: &Context,
- msg: &Message,
- args: Args,
+ ctx: PoiseContext<'_>,
+ args: impl AsRef<str>,
audio_playback: AudioPlayback,
-) -> CommandResult {
+) -> anyhow::Result<()> {
+ let args = args.as_ref().trim();
+
if args.is_empty() || audio_playback != AudioPlayback::Optional {
- return rand_meme(ctx, msg, audio_playback).await;
+ return rand_meme(ctx, audio_playback).await;
}
- let search = args.raw().join(" ");
-
let mut conn = connection().await?;
- let mem = match find_meme(&mut conn, search).await {
+ let mem = match find_meme(&mut conn, args).await {
Ok(x) => {
- InvocationRecord::create(&mut conn, msg.author.id.get(), msg.id.get(), x.id, false)
+ InvocationRecord::create(&mut conn, ctx.author().id.get(), ctx.id(), x.id, false)
.await?;
x
@@ -93,27 +87,23 @@ async fn _meme(
Err(e) => {
return if let Some(NotFound) = e.downcast_ref::<DieselError>() {
info!("requested meme not found in database");
- util::send(ctx, msg.channel_id, "c'mon baby, guesstimate", msg.tts)
- .await
- .map_err(CommandError::from)
+
+ util::reply(ctx, "c'mon baby, guesstimate").await?;
+ Ok(())
} else {
- util::send(ctx, msg.channel_id, "what in ryan's name", msg.tts).await?;
+ util::reply(ctx, "what in ryan's name").await?;
Err(e.into())
};
},
};
- send_meme(ctx, &mem, &mut conn, msg).await
+ send_meme(ctx, &mem, &mut conn).await
}
-async fn rand_meme(
- ctx: &Context,
- message: &Message,
- audio_playback: AudioPlayback,
-) -> CommandResult {
+async fn rand_meme(ctx: PoiseContext<'_>, audio_playback: AudioPlayback) -> anyhow::Result<()> {
let mut conn = connection().await?;
- let should_audio = util::users_listening(ctx).await?;
+ let should_audio = util::users_listening(ctx.serenity_context()).await?;
let mem = match audio_playback {
AudioPlayback::Required => db::rand_audio_meme(&mut conn).await,
@@ -123,56 +113,53 @@ async fn rand_meme(
match mem {
Ok(mem) => {
- InvocationRecord::create(
- &mut conn,
- message.author.id.get(),
- message.id.get(),
- mem.id,
- true,
- )
- .await?;
- send_meme(ctx, &mem, &mut conn, message).await?;
+ InvocationRecord::create(&mut conn, ctx.author().id.get(), ctx.id(), mem.id, true)
+ .await?;
+ send_meme(ctx, &mem, &mut conn).await?;
Ok(())
},
Err(e) => {
if let Some(NotFound) = e.downcast_ref::<DieselError>() {
info!("random meme not found");
- return util::send(ctx, message.channel_id, "i don't know any :(", message.tts)
- .map_err(CommandError::from)
- .await;
+
+ util::reply(ctx, "i don't know any :(").await?;
+ return Ok(());
}
- util::send(ctx, message.channel_id, "HELP", message.tts).await?;
+ util::reply(ctx, "HELP").await?;
Err(e.into())
},
}
}
-#[command]
-#[aliases("rarememe", "raremem")]
-pub async fn rare_meme(ctx: &Context, msg: &Message, _args: Args) -> CommandResult {
- let should_audio = util::users_listening(ctx).await?;
+#[poise::command(
+ slash_command,
+ prefix_command,
+ guild_only,
+ category = "memes",
+ aliases("raremem", "rarememe")
+)]
+pub async fn rare_meme(ctx: PoiseContext<'_>) -> anyhow::Result<()> {
+ let should_audio = util::users_listening(ctx.serenity_context()).await?;
let mut conn = connection().await?;
let meme = db::rare_meme(&mut conn, should_audio).await;
match meme {
Ok(meme) => {
- InvocationRecord::create(&mut conn, msg.author.id.get(), msg.id.get(), meme.id, true)
+ InvocationRecord::create(&mut conn, ctx.author().id.get(), ctx.id(), meme.id, true)
.await?;
- send_meme(ctx, &meme, &mut conn, msg).await
+ send_meme(ctx, &meme, &mut conn).await
},
Err(e) => {
if let Some(NotFound) = e.downcast_ref::<DieselError>() {
info!("rare meme not found");
- return util::send(ctx, msg.channel_id, "i don't know any :(", msg.tts)
- .map_err(CommandError::from)
- .await;
+ util::reply(ctx, "i don't know any :(").await?;
+
+ return Ok(());
}
- util::send(ctx, msg.channel_id, "THE MEME MARKET IS IN FREEFALL", msg.tts)
- .map_err(CommandError::from)
- .await?;
+ util::reply(ctx, "THE MEME MARKET IS IN FREEFALL").await?;
Err(e.into())
},
diff --git a/src/commands/meme/mod.rs b/src/commands/meme/mod.rs
index cfe02ee..0108219 100644
--- a/src/commands/meme/mod.rs
+++ b/src/commands/meme/mod.rs
@@ -8,12 +8,6 @@ use serenity::{
CreateAttachment,
CreateMessage,
},
- framework::standard::{
- macros::group,
- CommandResult,
- },
- model::channel::Message,
- prelude::*,
};
use songbird::input::{
core::{
@@ -26,53 +20,54 @@ use songbird::input::{
Input,
};
+pub use self::{
+ create::*,
+ delete::*,
+ history::*,
+ invoke::*,
+};
use crate::{
- commands::songbird,
+ commands::playback::songbird,
db::{
Audio,
Meme,
},
+ msg,
+ util,
+ PoiseContext,
CONFIG,
};
-pub use self::{
- create::*,
- delete::*,
- history::*,
- invoke::*,
-};
-
mod create;
mod delete;
mod history;
mod invoke;
-#[group]
-#[commands(
- meme,
- audio_meme,
- silent_meme,
- omen,
- audioomen,
- silentomen,
- addmeme,
- addaudiomeme,
- delmeme,
- wat,
- stats,
- history,
- rare_meme,
- memers,
- query
-)]
-struct Memes;
+pub fn commands() -> Vec<poise::Command<crate::PoiseData, anyhow::Error>> {
+ vec![
+ meme(),
+ silent_meme(),
+ audio_meme(),
+ rare_meme(),
+ omen(),
+ silentomen(),
+ audioomen(),
+ addmeme(),
+ addaudiomeme(),
+ delmeme(),
+ history(),
+ stats(),
+ memers(),
+ wat(),
+ query(),
+ ]
+}
async fn send_meme(
- ctx: &Context,
+ ctx: PoiseContext<'_>,
t: &Meme,
conn: &mut AsyncPgConnection,
- msg: &Message,
-) -> CommandResult {
+) -> anyhow::Result<()> {
let should_tts =
t.content.as_ref().map(|t| !t.is_empty()).unwrap_or(false) && random::<u32>() % 25 == 0;
@@ -95,12 +90,12 @@ async fn send_meme(
let image = image?;
let att = CreateAttachment::bytes(image.data.as_slice(), &image.filename);
- msg.channel_id.send_files(ctx, vec![att], cmsg).await?;
+ ctx.channel_id().send_files(ctx, vec![att], cmsg).await?;
},
None => {
if t.content.is_some() {
- msg.channel_id.send_message(ctx, cmsg).await?;
+ ctx.channel_id().send_message(ctx, cmsg).await?;
}
},
};
@@ -108,7 +103,7 @@ async fn send_meme(
if let Some(audio) = audio {
let audio = audio?;
- let (_sb, call) = songbird(ctx, msg).await?;
+ let (_sb, call) = songbird(ctx).await?;
let mut call = call.lock().await;
if call.current_channel().is_none() {
@@ -117,7 +112,7 @@ async fn send_meme(
call.enqueue_input(Input::Lazy(Box::new(audio))).await;
- msg.react(ctx, ReactionType::Unicode("📣".to_owned())).await?;
+ util::react(ctx, ReactionType::Unicode("📣".to_owned())).await?;
}
Ok(())
diff --git a/src/commands/mod.rs b/src/commands/mod.rs
index 69c9185..ba87adb 100644
--- a/src/commands/mod.rs
+++ b/src/commands/mod.rs
@@ -1,18 +1,14 @@
-use crate::util;
-use log::info;
-use serenity::framework::{
- standard::macros::group,
- StandardFramework,
-};
+use poise::builtins::PrettyHelpConfiguration;
-#[cfg(feature = "db")]
-pub use self::meme::*;
-pub use self::{
- playback::*,
- roll::ROLL_COMMAND,
- today::TODAY_COMMAND,
+use crate::{
+ commands::playback::_play,
+ util,
+ PoiseContext,
};
+#[cfg(feature = "games")]
+pub mod game;
+
#[cfg(feature = "db")]
pub(crate) mod meme;
pub(crate) mod playback;
@@ -20,40 +16,52 @@ pub(crate) mod roll;
pub(crate) mod sound_levels;
pub(crate) mod today;
-mod help;
+#[cfg(feature = "db")]
+pub use self::meme::*;
-#[group]
-#[only_in(guild)]
-#[commands(roll, today)]
-struct General;
+pub fn commands() -> Vec<poise::Command<crate::PoiseData, anyhow::Error>> {
+ let mut commands = vec![
+ playback::play(),
+ playback::pause(),
+ playback::resume(),
+ playback::die(),
+ playback::list(),
+ sound_levels::mute(),
+ sound_levels::unmute(),
+ roll::roll(),
+ help(),
+ ];
-pub fn register_commands(f: StandardFramework) -> StandardFramework {
- let result = f.group(&PLAYBACK_GROUP).group(&GENERAL_GROUP);
+ #[cfg(feature = "games")]
+ commands.extend(game::commands());
#[cfg(feature = "db")]
- let result = result.group(&MEMES_GROUP);
+ commands.extend(meme::commands());
- #[cfg(feature = "games")]
- let result = result.group(&crate::game::GAME_GROUP);
+ commands
+}
+
+#[poise::command(slash_command, prefix_command, aliases("halp"))]
+pub async fn help(ctx: PoiseContext<'_>, command: Option<String>) -> anyhow::Result<()> {
+ poise::builtins::pretty_help(
+ ctx,
+ command.as_ref().map(|x| x.as_str()),
+ PrettyHelpConfiguration {
+ ..Default::default()
+ },
+ )
+ .await?;
+
+ Ok(())
+}
+
+pub async fn unrecognized(ctx: PoiseContext<'_>, u: url::Url) -> anyhow::Result<()> {
+ if !u.scheme().starts_with("http") {
+ util::reply(ctx, "format your commands right. fuck you.").await?;
+ return Ok(());
+ }
- result.help(&help::HELP).unrecognised_command(|ctx, msg, unrec| {
- Box::pin(async move {
- let url = match msg.content.split_whitespace().nth(1) {
- Some(x) if x.starts_with("http") => x,
- _ => {
- info!("bad command formatting: '{}'", unrec);
- let _ = util::send(
- ctx,
- msg.channel_id,
- "format your commands right. fuck you.",
- msg.tts,
- )
- .await;
- return;
- },
- };
+ let _ = _play(ctx, &u).await?;
- let _ = _play(ctx, msg, url).await;
- })
- })
+ Ok(())
}
diff --git a/src/commands/playback.rs b/src/commands/playback.rs
index 1c3ab95..98ae613 100644
--- a/src/commands/playback.rs
+++ b/src/commands/playback.rs
@@ -1,78 +1,46 @@
+use std::sync::Arc;
+
use log::{
debug,
- error,
info,
warn,
};
-use serenity::{
- framework::standard::{
- macros::{
- command,
- group,
- },
- Args,
- CommandError,
- CommandResult,
- },
- model::channel::Message,
- prelude::*,
-};
+use serenity::prelude::*;
use songbird::{
input::YoutubeDl,
Call,
Songbird,
};
-use std::sync::Arc;
-use tap::Conv;
use crate::{
bot::HttpKey,
- commands::sound_levels::*,
util,
+ PoiseContext,
CONFIG,
};
-#[group]
-#[commands(skip, pause, resume, list, die, mute, unmute, play)]
-#[only_in(guild)]
-struct Playback;
-
-pub async fn songbird(
- ctx: &Context,
- msg: &Message,
-) -> Result<(Arc<Songbird>, Arc<Mutex<Call>>), CommandError> {
- let Some(gid) = msg.guild_id else {
+pub async fn songbird(ctx: PoiseContext<'_>) -> anyhow::Result<(Arc<Songbird>, Arc<Mutex<Call>>)> {
+ let Some(gid) = ctx.guild_id() else {
return Err(anyhow::anyhow!("no guild id").into());
};
- let sb = songbird::get(ctx).await.expect("acquiring songbird handle");
+ let sb = songbird::get(ctx.serenity_context()).await.expect("acquiring songbird handle");
let call = sb.get_or_insert(gid);
Ok((sb, call))
}
-pub async fn _play(ctx: &Context, msg: &Message, url: &str) -> CommandResult {
- use url::{
- Host,
- Url,
- };
+pub async fn _play(ctx: PoiseContext<'_>, url: &url::Url) -> anyhow::Result<()> {
+ use url::Host;
debug!("playing '{}'", url);
- if !url.starts_with("http") {
+ if !url.scheme().starts_with("http") {
warn!("got bad url argument to play: {}", url);
- util::send(ctx, msg.channel_id, "bAD LiNk", msg.tts).await?;
+
+ util::reply(ctx, "bAD LiNk").await?;
return Ok(());
}
- let url = match Url::parse(url) {
- Err(e) => {
- error!("bad url: {}", e);
- util::send(ctx, msg.channel_id, "INVALID URL", msg.tts).await?;
- return Ok(());
- },
- Ok(u) => u,
- };
-
let host = url.host().and_then(|u| match u {
Host::Domain(h) => Some(h.to_owned()),
_ => None,
@@ -81,16 +49,16 @@ pub async fn _play(ctx: &Context, msg: &Message, url: &str) -> CommandResult {
if host.map(|h| h.to_lowercase().contains("imgur")).unwrap_or(false) {
info!("detected imgur link");
- if msg.author.id.get() == 106160362109272064 {
- util::send(ctx, msg.channel_id, "fuck you conway", true).await?;
+ if ctx.author().id == 106160362109272064 {
+ util::reply(ctx, "fuck you conway").await?;
} else {
- util::send(ctx, msg.channel_id, "IMGUR IS BAD, YOU TRASH CAN MAN", msg.tts).await?;
+ util::reply(ctx, "IMGUR IS BAD, YOU TRASH CAN MAN").await?;
}
return Ok(());
}
- let (_sb, call) = songbird(ctx, msg).await?;
+ let (_sb, call) = songbird(ctx).await?;
let mut call = call.lock().await;
if call.current_channel().is_none() {
@@ -98,38 +66,31 @@ pub async fn _play(ctx: &Context, msg: &Message, url: &str) -> CommandResult {
}
let client = {
- let data = ctx.data.read().await;
+ let data = ctx.serenity_context().data.read().await;
data.get::<HttpKey>().unwrap().clone()
};
- let input = YoutubeDl::new_ytdl_like("yt-dlp", client.clone(), url.conv::<String>());
+ let input = YoutubeDl::new_ytdl_like("yt-dlp", client.clone(), url.to_string());
call.enqueue_input(input.into()).await;
Ok(())
}
-#[command]
-pub async fn play(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
- if args.is_empty() {
- return _resume(ctx, msg).await;
- }
-
- let url = match args.single::<String>() {
- Ok(url) => url,
- Err(e) => {
- error!("unable to parse url from args: {}", e);
- return util::send(ctx, msg.channel_id, "BAD LINK", msg.tts)
- .await
- .map_err(CommandError::from);
- },
+#[poise::command(slash_command, prefix_command, guild_only, category = "playback")]
+pub async fn play(
+ ctx: PoiseContext<'_>,
+ #[description = "link to play (if absent, resumes playback)"] u: Option<url::Url>,
+) -> anyhow::Result<()> {
+ let Some(u) = u else {
+ return _resume(ctx).await;
};
- _play(ctx, msg, &url).await
+ _play(ctx, &u).await
}
-#[command]
-pub async fn pause(ctx: &Context, msg: &Message, _: Args) -> CommandResult {
- let (_sb, call) = songbird(ctx, msg).await?;
+#[poise::command(slash_command, prefix_command, guild_only, category = "playback")]
+pub async fn pause(ctx: PoiseContext<'_>) -> anyhow::Result<()> {
+ let (_sb, call) = songbird(ctx).await?;
let call = call.lock().await;
call.queue().pause()?;
@@ -137,14 +98,19 @@ pub async fn pause(ctx: &Context, msg: &Message, _: Args) -> CommandResult {
Ok(())
}
-#[command]
-#[aliases("continue")]
-pub async fn resume(ctx: &Context, msg: &Message, _: Args) -> CommandResult {
- _resume(ctx, msg).await
+#[poise::command(
+ slash_command,
+ prefix_command,
+ guild_only,
+ aliases("continue"),
+ category = "playback"
+)]
+pub async fn resume(ctx: PoiseContext<'_>) -> anyhow::Result<()> {
+ _resume(ctx).await
}
-async fn _resume(ctx: &Context, msg: &Message) -> CommandResult {
- let (_sb, call) = songbird(ctx, msg).await?;
+async fn _resume(ctx: PoiseContext<'_>) -> anyhow::Result<()> {
+ let (_sb, call) = songbird(ctx).await?;
let call = call.lock().await;
call.queue().resume()?;
@@ -152,10 +118,9 @@ async fn _resume(ctx: &Context, msg: &Message) -> CommandResult {
Ok(())
}
-#[command]
-#[aliases("next")]
-pub async fn skip(ctx: &Context, msg: &Message, _args: Args) -> CommandResult {
- let (_sb, call) = songbird(ctx, msg).await?;
+#[poise::command(slash_command, prefix_command, guild_only, category = "playback", aliases("next"))]
+pub async fn skip(ctx: PoiseContext<'_>) -> anyhow::Result<()> {
+ let (_sb, call) = songbird(ctx).await?;
let call = call.lock().await;
call.queue().skip()?;
@@ -163,10 +128,15 @@ pub async fn skip(ctx: &Context, msg: &Message, _args: Args) -> CommandResult {
Ok(())
}
-#[command]
-#[aliases("sudoku", "fuckoff", "stop")]
-pub async fn die(ctx: &Context, msg: &Message, _: Args) -> CommandResult {
- let (_sb, call) = songbird(ctx, msg).await?;
+#[poise::command(
+ slash_command,
+ prefix_command,
+ guild_only,
+ category = "playback",
+ aliases("sudoku", "fuckoff", "stop")
+)]
+pub async fn die(ctx: PoiseContext<'_>) -> anyhow::Result<()> {
+ let (_sb, call) = songbird(ctx).await?;
let mut call = call.lock().await;
call.queue().stop();
@@ -176,21 +146,26 @@ pub async fn die(ctx: &Context, msg: &Message, _: Args) -> CommandResult {
Ok(())
}
-#[command]
-#[aliases("queue")]
-pub async fn list(ctx: &Context, msg: &Message, _: Args) -> CommandResult {
- let (_sb, call) = songbird(ctx, msg).await?;
+#[poise::command(
+ slash_command,
+ prefix_command,
+ guild_only,
+ category = "playback",
+ aliases("queue")
+)]
+pub async fn list(ctx: PoiseContext<'_>) -> anyhow::Result<()> {
+ let (_sb, call) = songbird(ctx).await?;
let call = call.lock().await;
let queue = call.queue();
- util::send(ctx, msg.channel_id, "(command fix work-in-progress)", msg.tts).await?;
+ util::reply(ctx, "(command fix work-in-progress)").await?;
for track in queue.current_queue().into_iter() {
let info = track.get_info().await?;
- util::send(ctx, msg.channel_id, format!("track playing for {:?}", info.play_time), msg.tts)
- .await?;
+ let fmt = format!("track playing for {:?}", info.play_time);
+ util::reply(ctx, fmt).await?;
}
Ok(())
diff --git a/src/commands/roll.rs b/src/commands/roll.rs
index 45e3ba8..6cd084c 100644
--- a/src/commands/roll.rs
+++ b/src/commands/roll.rs
@@ -1,31 +1,18 @@
use std::result::Result as StdResult;
+use lazy_static::lazy_static;
use log::{
debug,
error,
};
use rand::prelude::*;
-use serenity::{
- framework::standard::{
- macros::command,
- Args,
- },
- model::channel::Message,
- prelude::*,
-};
use thiserror::Error;
-use lazy_static::lazy_static;
-use serenity::{
- framework::standard::{
- CommandError,
- CommandResult,
- },
- futures::TryFutureExt,
+use crate::{
+ util,
+ PoiseContext,
};
-use crate::util;
-
#[derive(pest_derive::Parser)]
#[grammar = "commands/calc.pest"]
struct Calc;
@@ -49,27 +36,26 @@ impl Calc {
Pair,
Pairs,
},
- prec_climber::PrecClimber,
+ pratt_parser::PrattParser,
Parser,
};
use self::Rule::*;
lazy_static! {
- static ref CLIMBER: PrecClimber<Rule> = {
- use pest::prec_climber::{
+ static ref CLIMBER: PrattParser<Rule> = {
+ use pest::pratt_parser::{
Assoc::*,
- Operator,
+ Op as Operator,
};
- PrecClimber::new(vec![
- Operator::new(add, Left)
- | Operator::new(sub, Left)
- | Operator::new(modulo, Left),
- Operator::new(mul, Left) | Operator::new(div, Left),
- Operator::new(dice, Left),
- Operator::new(pow, Right),
- ])
+ PrattParser::new()
+ .op(Operator::infix(add, Left)
+ | Operator::infix(sub, Left)
+ | Operator::infix(modulo, Left))
+ .op(Operator::infix(mul, Left) | Operator::infix(div, Left))
+ .op(Operator::infix(dice, Left))
+ .op(Operator::infix(pow, Right))
};
}
@@ -160,29 +146,33 @@ impl Calc {
}
fn eval_expr(p: Pairs<Rule>) -> StdResult<f64, CalcError> {
- CLIMBER.climb(p, eval_single_pair, |lhs, op, rhs| {
- let lhs = lhs?;
- let rhs = rhs?;
+ CLIMBER
+ .map_primary(eval_single_pair)
+ .map_infix(|lhs, op, rhs| {
+ let lhs = lhs?;
+ let rhs = rhs?;
- let result = match op.as_rule() {
- add => lhs + rhs,
- sub => lhs - rhs,
- mul => lhs * rhs,
- div => lhs / rhs,
- pow => lhs.powf(rhs),
- dice => {
- let dice_count = lhs as usize;
- let dice_faces = rhs as usize;
+ let result = match op.as_rule() {
+ add => lhs + rhs,
+ sub => lhs - rhs,
+ mul => lhs * rhs,
+ div => lhs / rhs,
+ pow => lhs.powf(rhs),
+ dice => {
+ let dice_count = lhs as usize;
+ let dice_faces = rhs as usize;
- let mut rng = thread_rng();
- (0..dice_count).map(|_| rng.gen_range(1..(dice_faces + 1))).sum::<usize>()
- as f64
- },
- _ => unreachable!(),
- };
+ let mut rng = thread_rng();
+ (0..dice_count)
+ .map(|_| rng.gen_range(1..(dice_faces + 1)))
+ .sum::<usize>() as f64
+ },
+ _ => unreachable!(),
+ };
- Ok(result)
- })
+ Ok(result)
+ })
+ .parse(p)
}
eval_expr(result)
@@ -212,21 +202,18 @@ mod test {
}
}
-#[command]
-#[aliases("calc", "calculate")]
-pub async fn roll(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
- match Calc::eval(args.rest()) {
+#[poise::command(slash_command, prefix_command, guild_only, aliases("calc", "calculate"))]
+pub async fn roll(ctx: PoiseContext<'_>, #[rest] rest: String) -> anyhow::Result<()> {
+ match Calc::eval(&rest) {
Ok(result) => {
debug!("got calc result '{}'", result);
- util::send(ctx, msg.channel_id, &format!("{}", result), msg.tts)
- .map_err(CommandError::from)
- .await
+ util::reply(ctx, result.to_string()).await?;
},
Err(e) => {
- error!("error encountered reading calc '{}': {}", args.rest(), e);
- util::send(ctx, msg.channel_id, "I COULDN'T READ THAT YOU FUCK", msg.tts)
- .map_err(CommandError::from)
- .await
+ error!("error encountered reading calc '{}': {}", rest, e);
+ util::reply(ctx, "I COULDN'T READ THAT YOU FUCK").await?;
},
}
+
+ Ok(())
}
diff --git a/src/commands/sound_levels.rs b/src/commands/sound_levels.rs
index 8c75b37..9a6cfc6 100644
--- a/src/commands/sound_levels.rs
+++ b/src/commands/sound_levels.rs
@@ -1,21 +1,14 @@
-use serenity::{
- framework::standard::{
- macros::command,
- Args,
- CommandResult,
- },
- model::channel::Message,
- prelude::*,
+use crate::{
+ commands::playback::songbird,
+ PoiseContext,
};
-use crate::commands::songbird;
-
pub const DEFAULT_VOLUME: f32 = 0.20;
const MAX_VOLUME: f32 = 5.0;
-#[command]
-pub async fn mute(ctx: &Context, msg: &Message, _: Args) -> CommandResult {
- let (_sb, call) = songbird(ctx, msg).await?;
+#[poise::command(slash_command, prefix_command, guild_only)]
+pub async fn mute(ctx: PoiseContext<'_>) -> anyhow::Result<()> {
+ let (_sb, call) = songbird(ctx).await?;
let mut call = call.lock().await;
call.mute(true).await?;
@@ -23,66 +16,12 @@ pub async fn mute(ctx: &Context, msg: &Message, _: Args) -> CommandResult {
Ok(())
}
-#[command]
-pub async fn unmute(ctx: &Context, msg: &Message, _: Args) -> CommandResult {
- let (_sb, call) = songbird(ctx, msg).await?;
+#[poise::command(slash_command, prefix_command, guild_only)]
+pub async fn unmute(ctx: PoiseContext<'_>) -> anyhow::Result<()> {
+ let (_sb, call) = songbird(ctx).await?;
let mut call = call.lock().await;
call.mute(true).await?;
Ok(())
}
-
-// #[command]
-// pub async fn volume(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
-// if args.len() == 0 {
-// let vol = {
-// let queue_lock = ctx.data.write().await.get::<PlayQueue>().cloned().unwrap();
-// let play_queue = queue_lock.read().unwrap();
-// (play_queue.volume / DEFAULT_VOLUME * 100.0) as usize
-// };
-//
-// trace!("reporting volume {}", vol);
-//
-// return util::send(ctx, msg.channel_id, &format!("volume: {}%", vol), msg.tts)
-// .map_err(CommandError::from)
-// .await;
-// }
-//
-// let vol: usize = match args.single::<f32>() {
-// Ok(vol) if vol.is_nan() => {
-// warn!("reporting NaN volume");
-// return util::send(ctx, msg.channel_id, "you're a fuck", msg.tts)
-// .map_err(CommandError::from)
-// .await;
-// },
-// Ok(vol) => vol as usize,
-// Err(e) => {
-// error!("parsing volume arg: {}", e);
-// return util::send(ctx, msg.channel_id, "???????", msg.tts)
-// .map_err(CommandError::from)
-// .await;
-// },
-// };
-//
-// let mut vol: f32 = (vol as f32) / 100.0; // force aliasing to reasonable values
-// let adjusted_text = if vol > MAX_VOLUME {
-// format!(" ({:.0}% max)", MAX_VOLUME * 100.0)
-// } else {
-// "".to_owned()
-// };
-//
-// vol = vol.clamp(0.0, MAX_VOLUME);
-//
-// let queue_lock = ctx.data.write().await.get::<PlayQueue>().cloned().unwrap();
-//
-// {
-// let mut play_queue = queue_lock.write().unwrap();
-// play_queue.volume = vol * DEFAULT_VOLUME;
-// info!("volume updated to {}", vol);
-// }
-//
-// util::send(ctx, msg.channel_id, format!("volume adjusted{}", adjusted_text), msg.tts).await?;
-//
-// Ok(())
-// }
diff --git a/src/commands/today/mod.rs b/src/commands/today/mod.rs
index 7f1dca7..c1a02d5 100644
--- a/src/commands/today/mod.rs
+++ b/src/commands/today/mod.rs
@@ -5,22 +5,14 @@ use rand::{
seq::SliceRandom,
thread_rng,
};
-use serenity::{
- framework::standard::{
- macros::command,
- Args,
- CommandResult,
- },
- model::channel::Message,
- prelude::*,
-};
use songbird::input::YoutubeDl;
use tap::Conv;
use crate::{
bot::HttpKey,
- commands::songbird,
+ commands::playback::songbird,
util,
+ PoiseContext,
CONFIG,
};
@@ -66,16 +58,16 @@ lazy_static! {
];
}
-#[command]
-pub async fn today(ctx: &Context, msg: &Message, _args: Args) -> CommandResult {
+#[poise::command(slash_command, prefix_command, guild_only)]
+pub async fn today(ctx: PoiseContext<'_>, #[rest] _rest: String) -> anyhow::Result<()> {
let today = {
#[allow(unused_mut)]
let mut result = chrono::Local::now().naive_local();
#[cfg(debug_assertions)]
{
- let dt = _args.parse::<chrono::NaiveDateTime>().or_else(|_| {
- _args.parse::<chrono::NaiveDate>().map(|date| {
+ let dt = _rest.parse::<chrono::NaiveDateTime>().or_else(|_| {
+ _rest.parse::<chrono::NaiveDate>().map(|date| {
let time = chrono::NaiveTime::from_hms_opt(12, 0, 0).unwrap();
date.and_time(time)
})
@@ -83,11 +75,11 @@ pub async fn today(ctx: &Context, msg: &Message, _args: Args) -> CommandResult {
match dt {
Ok(dt) => {
- log::debug!("overriding with datetime: {}", dt);
+ debug!("overriding with datetime: {dt}");
result = dt;
},
Err(e) => {
- log::debug!("parsing datetime: {:?}", e);
+ debug!("parsing datetime: {e:?}");
},
};
}
@@ -102,7 +94,7 @@ pub async fn today(ctx: &Context, msg: &Message, _args: Args) -> CommandResult {
let play_args = options.choose(&mut thread_rng());
if let Some(play_args) = play_args {
- let (_sb, call) = songbird(ctx, msg).await?;
+ let (_sb, call) = songbird(ctx).await?;
let mut call = call.lock().await;
if call.current_channel().is_none() {
@@ -110,7 +102,7 @@ pub async fn today(ctx: &Context, msg: &Message, _args: Args) -> CommandResult {
}
let client = {
- let data = ctx.data.read().await;
+ let data = ctx.serenity_context().data.read().await;
data.get::<HttpKey>().unwrap().clone()
};
@@ -130,8 +122,8 @@ pub async fn today(ctx: &Context, msg: &Message, _args: Args) -> CommandResult {
});
q.resume()?;
} else {
- util::send(ctx, msg.channel_id, "no", false).await?;
- util::send(ctx, msg.channel_id, ":angry:", false).await?;
+ util::reply(ctx, "no").await?;
+ util::reply(ctx, ":angry:").await?;
}
Ok(())
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644
index 0000000..4ed3e44
--- /dev/null
+++ b/src/lib.rs
@@ -0,0 +1,31 @@
+#![feature(try_blocks)]
+
+#[cfg(feature = "db")]
+pub mod db;
+
+#[cfg(not(feature = "games"))]
+pub mod game {
+ use serenity::framework::StandardFramework;
+
+ #[inline]
+ fn register(f: StandardFramework) -> StandardFramework {
+ return f;
+ }
+}
+
+pub mod bot;
+pub mod commands;
+pub mod config;
+pub mod log_setup;
+pub mod util;
+
+pub use crate::{
+ config::*,
+ util::*,
+};
+
+pub type Error = anyhow::Error;
+pub type Result<T> = anyhow::Result<T>;
+
+pub type PoiseData = ();
+pub type PoiseContext<'a> = poise::Context<'a, PoiseData, anyhow::Error>;
diff --git a/src/main.rs b/src/main.rs
index be2fc90..93d4d2b 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -15,36 +15,6 @@ use log::{
info,
};
-pub use self::{
- config::*,
- util::*,
-};
-
-#[cfg(feature = "db")]
-mod db;
-
-#[cfg(feature = "games")]
-mod game;
-
-#[cfg(not(feature = "games"))]
-mod game {
- use serenity::framework::StandardFramework;
-
- #[inline]
- fn register(f: StandardFramework) -> StandardFramework {
- return f;
- }
-}
-
-mod bot;
-mod commands;
-mod config;
-mod log_setup;
-mod util;
-
-pub type Error = anyhow::Error;
-pub type Result<T> = anyhow::Result<T>;
-
const BACKOFF_FACTOR: f64 = 2.0;
const MAX_BACKOFFS: usize = 3;
const BACKOFF_INIT: f64 = 100.0;
@@ -53,7 +23,7 @@ const MIN_RUN_DURATION: Duration = Duration::from_secs(120);
#[tokio::main]
async fn main() {
- log_setup::init(false).expect("initializing logging");
+ thulani::log_setup::init(false).expect("initializing logging");
let mut backoff_count: usize = 0;
@@ -61,7 +31,7 @@ async fn main() {
let start = Instant::now();
info!("starting bot");
- match bot::run().await {
+ match thulani::bot::run().await {
Err(e) => {
error!("error encountered running client: {:?}", e);
},
diff --git a/src/util.rs b/src/util/mod.rs
index e88e7b0..a0105ac 100644
--- a/src/util.rs
+++ b/src/util/mod.rs
@@ -1,5 +1,20 @@
+use std::process::Stdio;
+
use chrono::Duration;
+use lazy_static::lazy_static;
+use log::debug;
+use poise::CreateReply;
+use regex::{
+ Match,
+ Regex,
+};
use serenity::{
+ all::{
+ CreateMessage,
+ Message,
+ Reaction,
+ ReactionType,
+ },
client::Context,
model::{
id::{
@@ -9,28 +24,21 @@ use serenity::{
permissions::Permissions,
},
};
-use std::process::Stdio;
-
-use lazy_static::lazy_static;
-use log::debug;
-use regex::{
- Match,
- Regex,
-};
-use serenity::all::{
- CreateMessage,
- Message,
-};
use url::Url;
use crate::{
- commands::songbird,
+ commands::playback::songbird,
+ PoiseContext,
Result,
CONFIG,
};
-pub async fn currently_playing(ctx: &Context, msg: &Message) -> bool {
- let (_sb, call) = songbird(ctx, msg).await.expect("no songbird");
+mod rest_vec;
+
+pub use rest_vec::RestVec;
+
+pub async fn currently_playing(ctx: PoiseContext<'_>) -> bool {
+ let (_sb, call) = songbird(ctx).await.expect("no songbird");
let call = call.lock().await;
call.queue().current().is_some()
@@ -53,6 +61,27 @@ pub async fn users_listening(ctx: &Context) -> Result<bool> {
}
#[inline]
+pub fn msg(ctx: PoiseContext<'_>) -> Option<&Message> {
+ match ctx {
+ PoiseContext::Prefix(poise::PrefixContext {
+ msg,
+ ..
+ }) => Some(msg),
+ _ => None,
+ }
+}
+
+#[inline]
+pub fn tts(ctx: PoiseContext<'_>) -> Option<bool> {
+ msg(ctx).map(|msg| msg.tts)
+}
+
+#[inline]
+pub fn unwrap_tts(ctx: PoiseContext<'_>) -> bool {
+ tts(ctx).unwrap_or(false)
+}
+
+#[inline]
pub async fn send(
ctx: &Context,
channel: ChannelId,
@@ -62,6 +91,21 @@ pub async fn send(
send_result(ctx, channel, text, tts).await.map(|_| ())
}
+#[inline]
+pub async fn reply(ctx: PoiseContext<'_>, text: impl AsRef<str>) -> Result<poise::ReplyHandle> {
+ let handle =
+ poise::send_reply(ctx, CreateReply::default().tts(unwrap_tts(ctx)).content(text.as_ref()))
+ .await?;
+
+ Ok(handle)
+}
+
+#[inline]
+pub async fn react(ctx: PoiseContext<'_>, react: ReactionType) -> Result<Reaction> {
+ let react = msg(ctx).ok_or_else(|| anyhow::anyhow!("elp"))?.react(ctx, react).await?;
+ Ok(react)
+}
+
pub async fn send_result(
ctx: &Context,
channel: ChannelId,
@@ -105,7 +149,7 @@ pub async fn ytdl_url(uri: &str) -> Result<String> {
lazy_static! {
static ref YTDL_COMMAND: String = {
let result = CONFIG.ytdl.clone().unwrap_or("youtube-dl".to_owned());
- log::debug!("got ytdl: {}", result);
+ debug!("got ytdl: {}", result);
result
};
diff --git a/src/util/rest_vec.rs b/src/util/rest_vec.rs
new file mode 100644
index 0000000..82889cd
--- /dev/null
+++ b/src/util/rest_vec.rs
@@ -0,0 +1,84 @@
+use serenity::all::{
+ Context,
+ Message,
+};
+use std::error::Error;
+
+/// Pop a whitespace-separated word from the front of the arguments. Supports quotes and quote
+/// escaping.
+///
+/// Leading whitespace will be trimmed; trailing whitespace is not consumed.
+// From https://github.com/serenity-rs/poise/blob/current/src/prefix_argument/mod.rs
+fn pop_string(args: &str) -> Result<(&str, String), poise::TooFewArguments> {
+ // TODO: consider changing the behavior to parse quotes literally if they're in the middle
+ // of the string:
+ // - `"hello world"` => `hello world`
+ // - `"hello "world"` => `"hello "world`
+ // - `"hello" world"` => `hello`
+
+ let args = args.trim_start();
+ if args.is_empty() {
+ return Err(poise::TooFewArguments::default());
+ }
+
+ let mut output = String::new();
+ let mut inside_string = false;
+ let mut escaping = false;
+
+ let mut chars = args.chars();
+ // .clone().next() is poor man's .peek(), but we can't do peekable because then we can't
+ // call as_str on the Chars iterator
+ while let Some(c) = chars.clone().next() {
+ if escaping {
+ output.push(c);
+ escaping = false;
+ } else if !inside_string && c.is_whitespace() {
+ break;
+ } else if c == '"' {
+ inside_string = !inside_string;
+ } else if c == '\\' {
+ escaping = true;
+ } else {
+ output.push(c);
+ }
+
+ chars.next();
+ }
+
+ Ok((chars.as_str(), output))
+}
+
+pub struct RestVec(Vec<String>);
+
+impl RestVec {
+ #[inline]
+ pub fn into_inner(self) -> Vec<String> {
+ self.0
+ }
+}
+
+impl From<RestVec> for Vec<String> {
+ #[inline]
+ fn from(value: RestVec) -> Self {
+ value.0
+ }
+}
+
+#[poise::async_trait]
+impl<'a> poise::PopArgument<'a> for RestVec {
+ async fn pop_from(
+ mut args: &'a str,
+ attachment_index: usize,
+ _ctx: &Context,
+ _msg: &Message,
+ ) -> Result<(&'a str, usize, Self), (Box<dyn Error + Send + Sync>, Option<String>)> {
+ let mut v = vec![];
+
+ while let Ok((remaining, s)) = pop_string(args) {
+ args = remaining;
+ v.push(s);
+ }
+
+ Ok(("", attachment_index, Self(v)))
+ }
+}