restructured all the reminder creation stuff into builders
This commit is contained in:
parent
85a8ae625d
commit
40630c0014
115
Cargo.lock
generated
115
Cargo.lock
generated
@ -1,5 +1,7 @@
|
|||||||
# This file is automatically @generated by Cargo.
|
# This file is automatically @generated by Cargo.
|
||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
|
version = 3
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "Inflector"
|
name = "Inflector"
|
||||||
version = "0.11.4"
|
version = "0.11.4"
|
||||||
@ -55,18 +57,18 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "async-tungstenite"
|
name = "async-tungstenite"
|
||||||
version = "0.11.0"
|
version = "0.13.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f7cc5408453d37e2b1c6f01d8078af1da58b6cfa6a80fa2ede3bd2b9a6ada9c4"
|
checksum = "07b30ef0ea5c20caaa54baea49514a206308989c68be7ecd86c7f956e4da6378"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"futures-io",
|
"futures-io",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"log",
|
"log",
|
||||||
"pin-project",
|
"pin-project-lite",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-rustls",
|
"tokio-rustls",
|
||||||
"tungstenite",
|
"tungstenite",
|
||||||
"webpki-roots 0.20.0",
|
"webpki-roots",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -101,12 +103,6 @@ version = "1.0.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
|
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "base64"
|
|
||||||
version = "0.12.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "base64"
|
name = "base64"
|
||||||
version = "0.13.0"
|
version = "0.13.0"
|
||||||
@ -169,12 +165,6 @@ version = "1.4.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
|
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "bytes"
|
|
||||||
version = "0.5.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "0e4cec68f03f32e44924783795810fa50a7035d8c8ebe78580ad7e6c703fba38"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bytes"
|
name = "bytes"
|
||||||
version = "1.0.1"
|
version = "1.0.1"
|
||||||
@ -220,6 +210,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "command_attr"
|
name = "command_attr"
|
||||||
version = "0.3.7"
|
version = "0.3.7"
|
||||||
|
source = "git+https://github.com/serenity-rs/serenity?branch=next#4d431726f4eb2f29a040b83fb4a18a459427c1b2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@ -307,6 +298,7 @@ checksum = "e77a43b28d0668df09411cb0bc9a8c2adc40f9a048afe863e05fd43251e8e39c"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"num_cpus",
|
"num_cpus",
|
||||||
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -533,7 +525,7 @@ version = "0.3.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "825343c4eef0b63f541f8903f395dc5beb362a979b5799a84062527ef1e37726"
|
checksum = "825343c4eef0b63f541f8903f395dc5beb362a979b5799a84062527ef1e37726"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes 1.0.1",
|
"bytes",
|
||||||
"fnv",
|
"fnv",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"futures-sink",
|
"futures-sink",
|
||||||
@ -600,7 +592,7 @@ version = "0.2.4"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "527e8c9ac747e28542699a951517aa9a6945af506cd1f2e1b53a576c17b6cc11"
|
checksum = "527e8c9ac747e28542699a951517aa9a6945af506cd1f2e1b53a576c17b6cc11"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes 1.0.1",
|
"bytes",
|
||||||
"fnv",
|
"fnv",
|
||||||
"itoa",
|
"itoa",
|
||||||
]
|
]
|
||||||
@ -611,7 +603,7 @@ version = "0.4.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "60daa14be0e0786db0f03a9e57cb404c9d756eed2b6c62b9ea98ec5743ec75a9"
|
checksum = "60daa14be0e0786db0f03a9e57cb404c9d756eed2b6c62b9ea98ec5743ec75a9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes 1.0.1",
|
"bytes",
|
||||||
"http",
|
"http",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
]
|
]
|
||||||
@ -640,7 +632,7 @@ version = "0.14.9"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "07d6baa1b441335f3ce5098ac421fb6547c46dda735ca1bc6d0153c838f9dd83"
|
checksum = "07d6baa1b441335f3ce5098ac421fb6547c46dda735ca1bc6d0153c838f9dd83"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes 1.0.1",
|
"bytes",
|
||||||
"futures-channel",
|
"futures-channel",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
@ -679,7 +671,7 @@ version = "0.5.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905"
|
checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes 1.0.1",
|
"bytes",
|
||||||
"hyper",
|
"hyper",
|
||||||
"native-tls",
|
"native-tls",
|
||||||
"tokio",
|
"tokio",
|
||||||
@ -709,11 +701,11 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "input_buffer"
|
name = "input_buffer"
|
||||||
version = "0.3.1"
|
version = "0.4.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "19a8a95243d5a0398cae618ec29477c6e3cb631152be5c19481f80bc71559754"
|
checksum = "f97967975f448f1a7ddb12b0bc41069d09ed6a1c161a92687e057325db35d413"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes 0.5.6",
|
"bytes",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -1076,7 +1068,7 @@ version = "0.8.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fd56cbd21fea48d0c440b41cd69c589faacade08c992d9a54e471b79d0fd13eb"
|
checksum = "fd56cbd21fea48d0c440b41cd69c589faacade08c992d9a54e471b79d0fd13eb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.13.0",
|
"base64",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"regex",
|
"regex",
|
||||||
]
|
]
|
||||||
@ -1087,26 +1079,6 @@ version = "2.1.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
|
checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "pin-project"
|
|
||||||
version = "1.0.7"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "c7509cc106041c40a4518d2af7a61530e1eed0e6285296a3d8c5472806ccc4a4"
|
|
||||||
dependencies = [
|
|
||||||
"pin-project-internal",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "pin-project-internal"
|
|
||||||
version = "1.0.7"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "48c950132583b500556b1efd71d45b319029f2b71518d979fcc208e16b42426f"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pin-project-lite"
|
name = "pin-project-lite"
|
||||||
version = "0.2.7"
|
version = "0.2.7"
|
||||||
@ -1288,7 +1260,7 @@ name = "reminder_rs"
|
|||||||
version = "1.5.1"
|
version = "1.5.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"Inflector",
|
"Inflector",
|
||||||
"base64 0.13.0",
|
"base64",
|
||||||
"chrono",
|
"chrono",
|
||||||
"chrono-tz",
|
"chrono-tz",
|
||||||
"dashmap",
|
"dashmap",
|
||||||
@ -1326,8 +1298,8 @@ version = "0.11.4"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "246e9f61b9bb77df069a947682be06e31ac43ea37862e244a69f177694ea6d22"
|
checksum = "246e9f61b9bb77df069a947682be06e31ac43ea37862e244a69f177694ea6d22"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.13.0",
|
"base64",
|
||||||
"bytes 1.0.1",
|
"bytes",
|
||||||
"encoding_rs",
|
"encoding_rs",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
@ -1356,7 +1328,7 @@ dependencies = [
|
|||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
"wasm-bindgen-futures",
|
"wasm-bindgen-futures",
|
||||||
"web-sys",
|
"web-sys",
|
||||||
"webpki-roots 0.21.1",
|
"webpki-roots",
|
||||||
"winreg",
|
"winreg",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -1401,7 +1373,7 @@ version = "0.19.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "35edb675feee39aec9c99fa5ff985081995a06d594114ae14cbe797ad7b7a6d7"
|
checksum = "35edb675feee39aec9c99fa5ff985081995a06d594114ae14cbe797ad7b7a6d7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.13.0",
|
"base64",
|
||||||
"log",
|
"log",
|
||||||
"ring",
|
"ring",
|
||||||
"sct",
|
"sct",
|
||||||
@ -1509,16 +1481,21 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "serenity"
|
name = "serenity"
|
||||||
version = "0.10.8"
|
version = "0.10.8"
|
||||||
|
source = "git+https://github.com/serenity-rs/serenity?branch=next#4d431726f4eb2f29a040b83fb4a18a459427c1b2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"async-tungstenite",
|
"async-tungstenite",
|
||||||
"base64 0.13.0",
|
"base64",
|
||||||
"bitflags",
|
"bitflags",
|
||||||
"bytes 1.0.1",
|
"bytes",
|
||||||
"chrono",
|
"chrono",
|
||||||
"command_attr",
|
"command_attr",
|
||||||
|
"dashmap",
|
||||||
"flate2",
|
"flate2",
|
||||||
"futures",
|
"futures",
|
||||||
|
"mime",
|
||||||
|
"mime_guess",
|
||||||
|
"parking_lot",
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"serde",
|
"serde",
|
||||||
@ -1637,11 +1614,11 @@ checksum = "7f23af36748ec8ea8d49ef8499839907be41b0b1178a4e82b8cb45d29f531dc9"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"ahash",
|
"ahash",
|
||||||
"atoi",
|
"atoi",
|
||||||
"base64 0.13.0",
|
"base64",
|
||||||
"bigdecimal",
|
"bigdecimal",
|
||||||
"bitflags",
|
"bitflags",
|
||||||
"byteorder",
|
"byteorder",
|
||||||
"bytes 1.0.1",
|
"bytes",
|
||||||
"chrono",
|
"chrono",
|
||||||
"crc",
|
"crc",
|
||||||
"crossbeam-channel",
|
"crossbeam-channel",
|
||||||
@ -1676,7 +1653,7 @@ dependencies = [
|
|||||||
"tokio-stream",
|
"tokio-stream",
|
||||||
"url",
|
"url",
|
||||||
"webpki",
|
"webpki",
|
||||||
"webpki-roots 0.21.1",
|
"webpki-roots",
|
||||||
"whoami",
|
"whoami",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -1837,7 +1814,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "5fb2ed024293bb19f7a5dc54fe83bf86532a44c12a2bb8ba40d64a4509395ca2"
|
checksum = "5fb2ed024293bb19f7a5dc54fe83bf86532a44c12a2bb8ba40d64a4509395ca2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"autocfg 1.0.1",
|
"autocfg 1.0.1",
|
||||||
"bytes 1.0.1",
|
"bytes",
|
||||||
"libc",
|
"libc",
|
||||||
"memchr",
|
"memchr",
|
||||||
"mio",
|
"mio",
|
||||||
@ -1899,7 +1876,7 @@ version = "0.6.7"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1caa0b0c8d94a049db56b5acf8cba99dc0623aab1b26d5b5f5e2d945846b3592"
|
checksum = "1caa0b0c8d94a049db56b5acf8cba99dc0623aab1b26d5b5f5e2d945846b3592"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes 1.0.1",
|
"bytes",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"futures-sink",
|
"futures-sink",
|
||||||
"log",
|
"log",
|
||||||
@ -1954,21 +1931,25 @@ checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tungstenite"
|
name = "tungstenite"
|
||||||
version = "0.11.1"
|
version = "0.13.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f0308d80d86700c5878b9ef6321f020f29b1bb9d5ff3cab25e75e23f3a492a23"
|
checksum = "5fe8dada8c1a3aeca77d6b51a4f1314e0f4b8e438b7b1b71e3ddaca8080e4093"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.12.3",
|
"base64",
|
||||||
"byteorder",
|
"byteorder",
|
||||||
"bytes 0.5.6",
|
"bytes",
|
||||||
"http",
|
"http",
|
||||||
"httparse",
|
"httparse",
|
||||||
"input_buffer",
|
"input_buffer",
|
||||||
"log",
|
"log",
|
||||||
"rand 0.7.3",
|
"rand 0.8.4",
|
||||||
|
"rustls",
|
||||||
"sha-1",
|
"sha-1",
|
||||||
|
"thiserror",
|
||||||
"url",
|
"url",
|
||||||
"utf-8",
|
"utf-8",
|
||||||
|
"webpki",
|
||||||
|
"webpki-roots",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -2044,6 +2025,7 @@ dependencies = [
|
|||||||
"idna",
|
"idna",
|
||||||
"matches",
|
"matches",
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -2180,15 +2162,6 @@ dependencies = [
|
|||||||
"untrusted",
|
"untrusted",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "webpki-roots"
|
|
||||||
version = "0.20.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "0f20dea7535251981a9670857150d571846545088359b28e4951d350bdaf179f"
|
|
||||||
dependencies = [
|
|
||||||
"webpki",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "webpki-roots"
|
name = "webpki-roots"
|
||||||
version = "0.21.1"
|
version = "0.21.1"
|
||||||
|
@ -22,8 +22,7 @@ serde_json = "1.0"
|
|||||||
rand = "0.7"
|
rand = "0.7"
|
||||||
Inflector = "0.11"
|
Inflector = "0.11"
|
||||||
levenshtein = "1.0"
|
levenshtein = "1.0"
|
||||||
# serenity = { version = "0.10", features = ["collector"] }
|
serenity = { git = "https://github.com/serenity-rs/serenity", branch = "next", features = ["collector", "unstable_discord_api"] }
|
||||||
serenity = { path = "/home/jude/serenity", features = ["collector", "unstable_discord_api"] }
|
|
||||||
sqlx = { version = "0.5", features = ["runtime-tokio-rustls", "macros", "mysql", "bigdecimal", "chrono"]}
|
sqlx = { version = "0.5", features = ["runtime-tokio-rustls", "macros", "mysql", "bigdecimal", "chrono"]}
|
||||||
ring = "0.16"
|
ring = "0.16"
|
||||||
base64 = "0.13.0"
|
base64 = "0.13.0"
|
||||||
|
@ -9,7 +9,7 @@ use crate::{
|
|||||||
consts::DEFAULT_PREFIX,
|
consts::DEFAULT_PREFIX,
|
||||||
get_ctx_data,
|
get_ctx_data,
|
||||||
language_manager::LanguageManager,
|
language_manager::LanguageManager,
|
||||||
models::{user_data::UserData, CtxGuildData},
|
models::{user_data::UserData, CtxData},
|
||||||
FrameworkCtx, THEME_COLOR,
|
FrameworkCtx, THEME_COLOR,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -35,7 +35,7 @@ async fn ping(ctx: &Context, msg: &Message, _args: String) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn footer(ctx: &Context) -> impl FnOnce(&mut CreateEmbedFooter) -> &mut CreateEmbedFooter {
|
async fn footer(ctx: &Context) -> impl FnOnce(&mut CreateEmbedFooter) -> &mut CreateEmbedFooter {
|
||||||
let shard_count = ctx.cache.shard_count().await;
|
let shard_count = ctx.cache.shard_count();
|
||||||
let shard = ctx.shard_id;
|
let shard = ctx.shard_id;
|
||||||
|
|
||||||
move |f| {
|
move |f| {
|
||||||
@ -145,7 +145,7 @@ async fn info(ctx: &Context, msg: &Message, _args: String) {
|
|||||||
|
|
||||||
let desc = lm
|
let desc = lm
|
||||||
.get(&language.await, "info")
|
.get(&language.await, "info")
|
||||||
.replacen("{user}", ¤t_user.await.name, 1)
|
.replacen("{user}", ¤t_user.name, 1)
|
||||||
.replace("{default_prefix}", &*DEFAULT_PREFIX)
|
.replace("{default_prefix}", &*DEFAULT_PREFIX)
|
||||||
.replace("{prefix}", &prefix.await);
|
.replace("{prefix}", &prefix.await);
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ use serenity::{
|
|||||||
model::{
|
model::{
|
||||||
channel::Message,
|
channel::Message,
|
||||||
id::{ChannelId, MessageId, RoleId},
|
id::{ChannelId, MessageId, RoleId},
|
||||||
interactions::ButtonStyle,
|
interactions::message_component::ButtonStyle,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -24,7 +24,7 @@ use crate::{
|
|||||||
consts::{REGEX_ALIAS, REGEX_CHANNEL, REGEX_COMMANDS, REGEX_ROLE, THEME_COLOR},
|
consts::{REGEX_ALIAS, REGEX_CHANNEL, REGEX_COMMANDS, REGEX_ROLE, THEME_COLOR},
|
||||||
framework::SendIterator,
|
framework::SendIterator,
|
||||||
get_ctx_data,
|
get_ctx_data,
|
||||||
models::{channel_data::ChannelData, guild_data::GuildData, user_data::UserData, CtxGuildData},
|
models::{channel_data::ChannelData, guild_data::GuildData, user_data::UserData, CtxData},
|
||||||
FrameworkCtx, PopularTimezones,
|
FrameworkCtx, PopularTimezones,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -46,13 +46,11 @@ async fn blacklist(ctx: &Context, msg: &Message, args: String) {
|
|||||||
|
|
||||||
let (channel, local) = match capture_opt {
|
let (channel, local) = match capture_opt {
|
||||||
Some(capture) => (
|
Some(capture) => (
|
||||||
ChannelId(capture.as_str().parse::<u64>().unwrap())
|
ChannelId(capture.as_str().parse::<u64>().unwrap()).to_channel_cached(&ctx),
|
||||||
.to_channel_cached(&ctx)
|
|
||||||
.await,
|
|
||||||
false,
|
false,
|
||||||
),
|
),
|
||||||
|
|
||||||
None => (msg.channel(&ctx).await, true),
|
None => (msg.channel(&ctx).await.ok(), true),
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut channel_data = ChannelData::from_channel(channel.unwrap(), &pool)
|
let mut channel_data = ChannelData::from_channel(channel.unwrap(), &pool)
|
||||||
@ -394,7 +392,7 @@ async fn restrict(ctx: &Context, msg: &Message, args: String) {
|
|||||||
let (pool, lm) = get_ctx_data(&ctx).await;
|
let (pool, lm) = get_ctx_data(&ctx).await;
|
||||||
|
|
||||||
let language = UserData::language_of(&msg.author, &pool).await;
|
let language = UserData::language_of(&msg.author, &pool).await;
|
||||||
let guild_data = GuildData::from_guild(msg.guild(&ctx).await.unwrap(), &pool)
|
let guild_data = GuildData::from_guild(msg.guild(&ctx).unwrap(), &pool)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
@ -411,7 +409,7 @@ async fn restrict(ctx: &Context, msg: &Message, args: String) {
|
|||||||
.unwrap(),
|
.unwrap(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let role_opt = role_id.to_role_cached(&ctx).await;
|
let role_opt = role_id.to_role_cached(&ctx);
|
||||||
|
|
||||||
if let Some(role) = role_opt {
|
if let Some(role) = role_opt {
|
||||||
let _ = sqlx::query!(
|
let _ = sqlx::query!(
|
||||||
@ -624,7 +622,7 @@ SELECT command FROM command_aliases WHERE guild_id = (SELECT id FROM guilds WHER
|
|||||||
.get::<FrameworkCtx>().cloned().expect("Could not get FrameworkCtx from data");
|
.get::<FrameworkCtx>().cloned().expect("Could not get FrameworkCtx from data");
|
||||||
|
|
||||||
let mut new_msg = msg.clone();
|
let mut new_msg = msg.clone();
|
||||||
new_msg.content = format!("<@{}> {}", &ctx.cache.current_user_id().await, row.command);
|
new_msg.content = format!("<@{}> {}", &ctx.cache.current_user_id(), row.command);
|
||||||
new_msg.id = MessageId(0);
|
new_msg.id = MessageId(0);
|
||||||
|
|
||||||
framework.dispatch(ctx.clone(), new_msg).await;
|
framework.dispatch(ctx.clone(), new_msg).await;
|
||||||
|
@ -2,82 +2,39 @@ use regex_command_attr::command;
|
|||||||
|
|
||||||
use serenity::{
|
use serenity::{
|
||||||
client::Context,
|
client::Context,
|
||||||
http::CacheHttp,
|
model::{channel::Channel, channel::Message},
|
||||||
model::{
|
|
||||||
channel::Message,
|
|
||||||
channel::{Channel, GuildChannel},
|
|
||||||
guild::Guild,
|
|
||||||
id::{ChannelId, GuildId, UserId},
|
|
||||||
interactions::ButtonStyle,
|
|
||||||
misc::Mentionable,
|
|
||||||
webhook::Webhook,
|
|
||||||
},
|
|
||||||
Result as SerenityResult,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
check_subscription_on_message, command_help,
|
check_subscription_on_message, command_help,
|
||||||
consts::{
|
consts::{
|
||||||
CHARACTERS, MAX_TIME, MIN_INTERVAL, REGEX_CHANNEL_USER, REGEX_CONTENT_SUBSTITUTION,
|
REGEX_CHANNEL_USER, REGEX_NATURAL_COMMAND_1, REGEX_NATURAL_COMMAND_2, REGEX_REMIND_COMMAND,
|
||||||
REGEX_NATURAL_COMMAND_1, REGEX_NATURAL_COMMAND_2, REGEX_REMIND_COMMAND, THEME_COLOR,
|
THEME_COLOR,
|
||||||
},
|
},
|
||||||
framework::SendIterator,
|
framework::SendIterator,
|
||||||
get_ctx_data,
|
get_ctx_data,
|
||||||
models::{
|
models::{
|
||||||
channel_data::ChannelData,
|
channel_data::ChannelData,
|
||||||
guild_data::GuildData,
|
guild_data::GuildData,
|
||||||
reminder::{LookFlags, Reminder, ReminderAction},
|
reminder::{builder::ReminderScope, content::Content, look_flags::LookFlags, Reminder},
|
||||||
timer::Timer,
|
timer::Timer,
|
||||||
user_data::UserData,
|
user_data::UserData,
|
||||||
CtxGuildData,
|
CtxData,
|
||||||
},
|
},
|
||||||
time_parser::{natural_parser, TimeParser},
|
time_parser::{natural_parser, TimeParser},
|
||||||
};
|
};
|
||||||
|
|
||||||
use chrono::NaiveDateTime;
|
use chrono::NaiveDateTime;
|
||||||
|
|
||||||
use rand::{rngs::OsRng, seq::IteratorRandom};
|
|
||||||
|
|
||||||
use sqlx::MySqlPool;
|
|
||||||
|
|
||||||
use num_integer::Integer;
|
use num_integer::Integer;
|
||||||
|
|
||||||
|
use crate::models::reminder::builder::MultiReminderBuilder;
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashSet,
|
|
||||||
convert::TryInto,
|
|
||||||
default::Default,
|
default::Default,
|
||||||
env,
|
|
||||||
fmt::Display,
|
|
||||||
string::ToString,
|
string::ToString,
|
||||||
time::{SystemTime, UNIX_EPOCH},
|
time::{SystemTime, UNIX_EPOCH},
|
||||||
};
|
};
|
||||||
|
|
||||||
use regex::Captures;
|
|
||||||
|
|
||||||
async fn create_webhook(
|
|
||||||
ctx: impl CacheHttp,
|
|
||||||
channel: GuildChannel,
|
|
||||||
name: impl Display,
|
|
||||||
) -> SerenityResult<Webhook> {
|
|
||||||
channel
|
|
||||||
.create_webhook_with_avatar(
|
|
||||||
ctx.http(),
|
|
||||||
name,
|
|
||||||
(
|
|
||||||
include_bytes!(concat!(
|
|
||||||
env!("CARGO_MANIFEST_DIR"),
|
|
||||||
"/assets/",
|
|
||||||
env!(
|
|
||||||
"WEBHOOK_AVATAR",
|
|
||||||
"WEBHOOK_AVATAR not provided for compilation"
|
|
||||||
)
|
|
||||||
)) as &[u8],
|
|
||||||
env!("WEBHOOK_AVATAR"),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|
||||||
#[command]
|
#[command]
|
||||||
#[supports_dm(false)]
|
#[supports_dm(false)]
|
||||||
#[permission_level(Restricted)]
|
#[permission_level(Restricted)]
|
||||||
@ -153,7 +110,7 @@ async fn offset(ctx: &Context, msg: &Message, args: String) {
|
|||||||
let parser = TimeParser::new(&args, user_data.timezone());
|
let parser = TimeParser::new(&args, user_data.timezone());
|
||||||
|
|
||||||
if let Ok(displacement) = parser.displacement() {
|
if let Ok(displacement) = parser.displacement() {
|
||||||
if let Some(guild) = msg.guild(&ctx).await {
|
if let Some(guild) = msg.guild(&ctx) {
|
||||||
let guild_data = GuildData::from_guild(guild, &pool).await.unwrap();
|
let guild_data = GuildData::from_guild(guild, &pool).await.unwrap();
|
||||||
|
|
||||||
sqlx::query!(
|
sqlx::query!(
|
||||||
@ -263,7 +220,7 @@ async fn look(ctx: &Context, msg: &Message, args: String) {
|
|||||||
|
|
||||||
let flags = LookFlags::from_string(&args);
|
let flags = LookFlags::from_string(&args);
|
||||||
|
|
||||||
let channel_opt = msg.channel_id.to_channel_cached(&ctx).await;
|
let channel_opt = msg.channel_id.to_channel_cached(&ctx);
|
||||||
|
|
||||||
let channel_id = if let Some(Channel::Guild(channel)) = channel_opt {
|
let channel_id = if let Some(Channel::Guild(channel)) = channel_opt {
|
||||||
if Some(channel.guild_id) == msg.guild_id {
|
if Some(channel.guild_id) == msg.guild_id {
|
||||||
@ -287,7 +244,7 @@ async fn look(ctx: &Context, msg: &Message, args: String) {
|
|||||||
|
|
||||||
let display = reminders
|
let display = reminders
|
||||||
.iter()
|
.iter()
|
||||||
.map(|reminder| reminder.display(&flags, &inter));
|
.map(|reminder| reminder.display(&flags, inter));
|
||||||
|
|
||||||
let _ = msg.channel_id.say_lines(&ctx, display).await;
|
let _ = msg.channel_id.say_lines(&ctx, display).await;
|
||||||
}
|
}
|
||||||
@ -545,308 +502,6 @@ enum RemindCommand {
|
|||||||
Interval,
|
Interval,
|
||||||
}
|
}
|
||||||
|
|
||||||
enum ReminderScope {
|
|
||||||
User(u64),
|
|
||||||
Channel(u64),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ReminderScope {
|
|
||||||
fn mention(&self) -> String {
|
|
||||||
match self {
|
|
||||||
Self::User(id) => format!("<@{}>", id),
|
|
||||||
Self::Channel(id) => format!("<#{}>", id),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(PartialEq, Eq, Hash, Debug)]
|
|
||||||
enum ReminderError {
|
|
||||||
LongInterval,
|
|
||||||
PastTime,
|
|
||||||
ShortInterval,
|
|
||||||
InvalidTag,
|
|
||||||
InvalidTime,
|
|
||||||
InvalidExpiration,
|
|
||||||
DiscordError(String),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::fmt::Display for ReminderError {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
write!(f, "{}", self.to_response())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::error::Error for ReminderError {}
|
|
||||||
|
|
||||||
trait ToResponse {
|
|
||||||
fn to_response(&self) -> &'static str;
|
|
||||||
|
|
||||||
fn to_response_natural(&self) -> &'static str;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ToResponse for ReminderError {
|
|
||||||
fn to_response(&self) -> &'static str {
|
|
||||||
match self {
|
|
||||||
Self::LongInterval => "interval/long_interval",
|
|
||||||
Self::PastTime => "remind/past_time",
|
|
||||||
Self::ShortInterval => "interval/short_interval",
|
|
||||||
Self::InvalidTag => "remind/invalid_tag",
|
|
||||||
Self::InvalidTime => "remind/invalid_time",
|
|
||||||
Self::InvalidExpiration => "interval/invalid_expiration",
|
|
||||||
Self::DiscordError(_) => "remind/generic_error",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn to_response_natural(&self) -> &'static str {
|
|
||||||
match self {
|
|
||||||
Self::InvalidTime => "natural/invalid_time",
|
|
||||||
_ => self.to_response(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> ToResponse for Result<T, ReminderError> {
|
|
||||||
fn to_response(&self) -> &'static str {
|
|
||||||
match self {
|
|
||||||
Ok(_) => "remind/success",
|
|
||||||
|
|
||||||
Err(reminder_error) => reminder_error.to_response(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn to_response_natural(&self) -> &'static str {
|
|
||||||
match self {
|
|
||||||
Ok(_) => "remind/success",
|
|
||||||
|
|
||||||
Err(reminder_error) => reminder_error.to_response_natural(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn generate_uid() -> String {
|
|
||||||
let mut generator: OsRng = Default::default();
|
|
||||||
|
|
||||||
(0..64)
|
|
||||||
.map(|_| {
|
|
||||||
CHARACTERS
|
|
||||||
.chars()
|
|
||||||
.choose(&mut generator)
|
|
||||||
.unwrap()
|
|
||||||
.to_owned()
|
|
||||||
.to_string()
|
|
||||||
})
|
|
||||||
.collect::<Vec<String>>()
|
|
||||||
.join("")
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
enum ContentError {
|
|
||||||
TooManyAttachments,
|
|
||||||
AttachmentTooLarge,
|
|
||||||
AttachmentDownloadFailed,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ContentError {
|
|
||||||
fn to_response(&self) -> &'static str {
|
|
||||||
match self {
|
|
||||||
ContentError::TooManyAttachments => "remind/too_many_attachments",
|
|
||||||
ContentError::AttachmentTooLarge => "remind/attachment_too_large",
|
|
||||||
ContentError::AttachmentDownloadFailed => "remind/attachment_download_failed",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::fmt::Display for ContentError {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
write!(f, "{:?}", self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::error::Error for ContentError {}
|
|
||||||
|
|
||||||
struct Content {
|
|
||||||
content: String,
|
|
||||||
tts: bool,
|
|
||||||
attachment: Option<Vec<u8>>,
|
|
||||||
attachment_name: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Content {
|
|
||||||
async fn build<S: ToString>(content: S, message: &Message) -> Result<Self, ContentError> {
|
|
||||||
if message.attachments.len() > 1 {
|
|
||||||
Err(ContentError::TooManyAttachments)
|
|
||||||
} else if let Some(attachment) = message.attachments.get(0) {
|
|
||||||
if attachment.size > 8_000_000 {
|
|
||||||
Err(ContentError::AttachmentTooLarge)
|
|
||||||
} else if let Ok(attachment_bytes) = attachment.download().await {
|
|
||||||
Ok(Self {
|
|
||||||
content: content.to_string(),
|
|
||||||
tts: false,
|
|
||||||
attachment: Some(attachment_bytes),
|
|
||||||
attachment_name: Some(attachment.filename.clone()),
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
Err(ContentError::AttachmentDownloadFailed)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Ok(Self {
|
|
||||||
content: content.to_string(),
|
|
||||||
tts: false,
|
|
||||||
attachment: None,
|
|
||||||
attachment_name: None,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn substitute(&mut self, guild: Guild) {
|
|
||||||
if self.content.starts_with("/tts ") {
|
|
||||||
self.tts = true;
|
|
||||||
self.content = self.content.split_off(5);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.content = REGEX_CONTENT_SUBSTITUTION
|
|
||||||
.replace(&self.content, |caps: &Captures| {
|
|
||||||
if let Some(user) = caps.name("user") {
|
|
||||||
format!("<@{}>", user.as_str())
|
|
||||||
} else if let Some(role_name) = caps.name("role") {
|
|
||||||
if let Some(role) = guild.role_by_name(role_name.as_str()) {
|
|
||||||
role.mention().to_string()
|
|
||||||
} else {
|
|
||||||
format!("<<{}>>", role_name.as_str().to_string())
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
String::new()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.to_string()
|
|
||||||
.replace("<<everyone>>", "@everyone")
|
|
||||||
.replace("<<here>>", "@here");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[command("countdown")]
|
|
||||||
#[permission_level(Managed)]
|
|
||||||
async fn countdown(ctx: &Context, msg: &Message, args: String) {
|
|
||||||
let (pool, lm) = get_ctx_data(&ctx).await;
|
|
||||||
let language = UserData::language_of(&msg.author, &pool).await;
|
|
||||||
|
|
||||||
if check_subscription_on_message(&ctx, &msg).await {
|
|
||||||
let timezone = UserData::timezone_of(&msg.author, &pool).await;
|
|
||||||
|
|
||||||
let split_args = args.splitn(3, ' ').collect::<Vec<&str>>();
|
|
||||||
|
|
||||||
if split_args.len() == 3 {
|
|
||||||
let time = split_args.get(0).unwrap();
|
|
||||||
let interval = split_args.get(1).unwrap();
|
|
||||||
let event_name = split_args.get(2).unwrap();
|
|
||||||
|
|
||||||
let now = SystemTime::now()
|
|
||||||
.duration_since(UNIX_EPOCH)
|
|
||||||
.unwrap()
|
|
||||||
.as_secs();
|
|
||||||
|
|
||||||
let time_parser = TimeParser::new(*time, timezone);
|
|
||||||
let interval_parser = TimeParser::new(*interval, timezone);
|
|
||||||
|
|
||||||
if let Ok(target_ts) = time_parser.timestamp() {
|
|
||||||
if let Ok(interval) = interval_parser.displacement() {
|
|
||||||
let mut first_time = target_ts;
|
|
||||||
|
|
||||||
while first_time - interval > now as i64 {
|
|
||||||
first_time -= interval;
|
|
||||||
}
|
|
||||||
|
|
||||||
let description = format!(
|
|
||||||
"**{}** occurs in **<<timefrom:{}:%d days, %h:%m>>**",
|
|
||||||
event_name, target_ts
|
|
||||||
);
|
|
||||||
|
|
||||||
sqlx::query!(
|
|
||||||
"
|
|
||||||
INSERT INTO reminders (
|
|
||||||
`uid`,
|
|
||||||
`name`,
|
|
||||||
`embed_title`,
|
|
||||||
`embed_description`,
|
|
||||||
`embed_color`,
|
|
||||||
`channel_id`,
|
|
||||||
`utc_time`,
|
|
||||||
`interval`,
|
|
||||||
`set_by`,
|
|
||||||
`expires`
|
|
||||||
) VALUES (
|
|
||||||
?,
|
|
||||||
'Countdown',
|
|
||||||
?,
|
|
||||||
?,
|
|
||||||
?,
|
|
||||||
?,
|
|
||||||
?,
|
|
||||||
?,
|
|
||||||
(SELECT id FROM users WHERE user = ?),
|
|
||||||
FROM_UNIXTIME(?)
|
|
||||||
)
|
|
||||||
",
|
|
||||||
generate_uid(),
|
|
||||||
event_name,
|
|
||||||
description,
|
|
||||||
*THEME_COLOR,
|
|
||||||
msg.channel_id.as_u64(),
|
|
||||||
first_time,
|
|
||||||
interval,
|
|
||||||
msg.author.id.as_u64(),
|
|
||||||
target_ts
|
|
||||||
)
|
|
||||||
.execute(&pool)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let _ = msg.channel_id.send_message(&ctx, |m| {
|
|
||||||
m.embed(|e| {
|
|
||||||
e.title(lm.get(&language, "remind/success")).description(
|
|
||||||
"A new countdown reminder has been created on this channel",
|
|
||||||
)
|
|
||||||
})
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
let _ = msg.channel_id.send_message(&ctx, |m| {
|
|
||||||
m.embed(|e| {
|
|
||||||
e.title(lm.get(&language, "remind/issue"))
|
|
||||||
.description(lm.get(&language, "interval/invalid_interval"))
|
|
||||||
})
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let _ = msg.channel_id.send_message(&ctx, |m| {
|
|
||||||
m.embed(|e| {
|
|
||||||
e.title(lm.get(&language, "remind/issue"))
|
|
||||||
.description(lm.get(&language, "remind/invalid_time"))
|
|
||||||
})
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
command_help(
|
|
||||||
ctx,
|
|
||||||
msg,
|
|
||||||
lm,
|
|
||||||
&ctx.prefix(msg.guild_id).await,
|
|
||||||
&language,
|
|
||||||
"countdown",
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let _ = msg
|
|
||||||
.channel_id
|
|
||||||
.say(
|
|
||||||
&ctx,
|
|
||||||
lm.get(&language, "interval/donor")
|
|
||||||
.replace("{prefix}", &ctx.prefix(msg.guild_id).await),
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[command("remind")]
|
#[command("remind")]
|
||||||
#[permission_level(Managed)]
|
#[permission_level(Managed)]
|
||||||
async fn remind(ctx: &Context, msg: &Message, args: String) {
|
async fn remind(ctx: &Context, msg: &Message, args: String) {
|
||||||
@ -923,127 +578,63 @@ async fn remind_command(ctx: &Context, msg: &Message, args: String, command: Rem
|
|||||||
|
|
||||||
match content_res {
|
match content_res {
|
||||||
Ok(mut content) => {
|
Ok(mut content) => {
|
||||||
let mut ok_locations = vec![];
|
if let Some(guild) = msg.guild(&ctx) {
|
||||||
let mut ok_reminders = vec![];
|
content.substitute(guild);
|
||||||
let mut err_locations = vec![];
|
|
||||||
let mut err_types = HashSet::new();
|
|
||||||
|
|
||||||
for scope in scopes {
|
|
||||||
let res = create_reminder(
|
|
||||||
&ctx,
|
|
||||||
&pool,
|
|
||||||
msg.author.id,
|
|
||||||
msg.guild_id,
|
|
||||||
&scope,
|
|
||||||
&time_parser,
|
|
||||||
expires_parser.as_ref(),
|
|
||||||
interval,
|
|
||||||
&mut content,
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
match res {
|
|
||||||
Err(e) => {
|
|
||||||
err_locations.push(scope);
|
|
||||||
err_types.insert(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(id) => {
|
|
||||||
ok_locations.push(scope);
|
|
||||||
ok_reminders.push(id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let success_part = match ok_locations.len() {
|
let user_data = ctx.user_data(&msg.author).await.unwrap();
|
||||||
|
|
||||||
|
let mut builder = MultiReminderBuilder::new(ctx, msg.guild_id)
|
||||||
|
.author(user_data)
|
||||||
|
.content(content)
|
||||||
|
.interval(interval)
|
||||||
|
.expires_parser(expires_parser)
|
||||||
|
.time_parser(time_parser.clone());
|
||||||
|
|
||||||
|
builder.set_scopes(scopes);
|
||||||
|
|
||||||
|
let (errors, successes) = builder.build().await;
|
||||||
|
|
||||||
|
let success_part = match successes.len() {
|
||||||
0 => "".to_string(),
|
0 => "".to_string(),
|
||||||
1 => lm
|
n => format!(
|
||||||
.get(&language, "remind/success")
|
"Reminder{s} for {locations} set for <t:{offset}:R>",
|
||||||
.replace("{location}", &ok_locations[0].mention())
|
s = if n > 1 { "s" } else { "" },
|
||||||
.replace(
|
locations = successes
|
||||||
"{offset}",
|
.iter()
|
||||||
&format!("<t:{}:R>", time_parser.timestamp().unwrap()),
|
.map(|l| l.mention())
|
||||||
),
|
.collect::<Vec<String>>()
|
||||||
n => lm
|
.join(", "),
|
||||||
.get(&language, "remind/success_bulk")
|
offset = time_parser.timestamp().unwrap()
|
||||||
.replace("{number}", &n.to_string())
|
),
|
||||||
.replace(
|
|
||||||
"{location}",
|
|
||||||
&ok_locations
|
|
||||||
.iter()
|
|
||||||
.map(|l| l.mention())
|
|
||||||
.collect::<Vec<String>>()
|
|
||||||
.join(", "),
|
|
||||||
)
|
|
||||||
.replace(
|
|
||||||
"{offset}",
|
|
||||||
&format!("<t:{}:R>", time_parser.timestamp().unwrap()),
|
|
||||||
),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let error_part = format!(
|
let error_part = match errors.len() {
|
||||||
"{}\n{}",
|
0 => "".to_string(),
|
||||||
match err_locations.len() {
|
n => format!(
|
||||||
0 => "".to_string(),
|
"{n} reminder{s} failed to set:\n{errors}",
|
||||||
1 => lm
|
s = if n > 1 { "s" } else { "" },
|
||||||
.get(&language, "remind/issue")
|
n = n,
|
||||||
.replace("{location}", &err_locations[0].mention()),
|
errors = errors
|
||||||
n => lm
|
.iter()
|
||||||
.get(&language, "remind/issue_bulk")
|
.map(|e| e.display(false))
|
||||||
.replace("{number}", &n.to_string())
|
.collect::<Vec<String>>()
|
||||||
.replace(
|
.join("\n")
|
||||||
"{location}",
|
),
|
||||||
&err_locations
|
};
|
||||||
.iter()
|
|
||||||
.map(|l| l.mention())
|
|
||||||
.collect::<Vec<String>>()
|
|
||||||
.join(", "),
|
|
||||||
),
|
|
||||||
},
|
|
||||||
err_types
|
|
||||||
.iter()
|
|
||||||
.map(|err| match err {
|
|
||||||
ReminderError::DiscordError(s) => lm
|
|
||||||
.get(&language, err.to_response())
|
|
||||||
.replace("{error}", &s),
|
|
||||||
|
|
||||||
_ => lm
|
|
||||||
.get(&language, err.to_response())
|
|
||||||
.replace("{min_interval}", &*MIN_INTERVAL.to_string()),
|
|
||||||
})
|
|
||||||
.collect::<Vec<String>>()
|
|
||||||
.join("\n")
|
|
||||||
);
|
|
||||||
|
|
||||||
let _ = msg
|
let _ = msg
|
||||||
.channel_id
|
.channel_id
|
||||||
.send_message(&ctx, |m| {
|
.send_message(&ctx, |m| {
|
||||||
m.embed(|e| {
|
m.embed(|e| {
|
||||||
e.title(
|
e.title(format!(
|
||||||
lm.get(&language, "remind/title").replace(
|
"{n} Reminder{s} Set",
|
||||||
"{number}",
|
n = successes.len(),
|
||||||
&ok_locations.len().to_string(),
|
s = if successes.len() > 1 { "s" } else { "" }
|
||||||
),
|
))
|
||||||
)
|
|
||||||
.description(format!("{}\n\n{}", success_part, error_part))
|
.description(format!("{}\n\n{}", success_part, error_part))
|
||||||
.color(*THEME_COLOR)
|
.color(*THEME_COLOR)
|
||||||
})
|
})
|
||||||
.components(|c| {
|
|
||||||
if ok_locations.len() == 1 {
|
|
||||||
c.create_action_row(|r| {
|
|
||||||
r.create_button(|b| {
|
|
||||||
b.style(ButtonStyle::Danger)
|
|
||||||
.label("Delete")
|
|
||||||
.custom_id(ok_reminders[0].signed_action(
|
|
||||||
msg.author.id,
|
|
||||||
ReminderAction::Delete,
|
|
||||||
))
|
|
||||||
})
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
c
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
@ -1053,12 +644,9 @@ async fn remind_command(ctx: &Context, msg: &Message, args: String, command: Rem
|
|||||||
.channel_id
|
.channel_id
|
||||||
.send_message(ctx, |m| {
|
.send_message(ctx, |m| {
|
||||||
m.embed(move |e| {
|
m.embed(move |e| {
|
||||||
e.title(
|
e.title("0 Reminders Set")
|
||||||
lm.get(&language, "remind/title")
|
.description(content_error.to_string())
|
||||||
.replace("{number}", "0"),
|
.color(*THEME_COLOR)
|
||||||
)
|
|
||||||
.description(lm.get(&language, content_error.to_response()))
|
|
||||||
.color(*THEME_COLOR)
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
@ -1163,94 +751,60 @@ async fn natural(ctx: &Context, msg: &Message, args: String) {
|
|||||||
|
|
||||||
match content_res {
|
match content_res {
|
||||||
Ok(mut content) => {
|
Ok(mut content) => {
|
||||||
let mut ok_locations = vec![];
|
if let Some(guild) = msg.guild(&ctx) {
|
||||||
let mut err_locations = vec![];
|
content.substitute(guild);
|
||||||
let mut err_types = HashSet::new();
|
|
||||||
|
|
||||||
for scope in location_ids {
|
|
||||||
let res = create_reminder(
|
|
||||||
&ctx,
|
|
||||||
&pool,
|
|
||||||
msg.author.id,
|
|
||||||
msg.guild_id,
|
|
||||||
&scope,
|
|
||||||
timestamp,
|
|
||||||
expires,
|
|
||||||
interval,
|
|
||||||
&mut content,
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
if let Err(e) = res {
|
|
||||||
err_locations.push(scope);
|
|
||||||
err_types.insert(e);
|
|
||||||
} else {
|
|
||||||
ok_locations.push(scope);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let success_part = match ok_locations.len() {
|
let user_data = ctx.user_data(&msg.author).await.unwrap();
|
||||||
|
|
||||||
|
let mut builder = MultiReminderBuilder::new(ctx, msg.guild_id)
|
||||||
|
.author(user_data)
|
||||||
|
.content(content)
|
||||||
|
.interval(interval)
|
||||||
|
.expires(expires)
|
||||||
|
.time(timestamp);
|
||||||
|
|
||||||
|
builder.set_scopes(location_ids);
|
||||||
|
|
||||||
|
let (errors, successes) = builder.build().await;
|
||||||
|
|
||||||
|
let success_part = match successes.len() {
|
||||||
0 => "".to_string(),
|
0 => "".to_string(),
|
||||||
1 => lm
|
n => format!(
|
||||||
.get(&user_data.language, "remind/success")
|
"Reminder{s} for {locations} set for <t:{offset}:R>",
|
||||||
.replace("{location}", &ok_locations[0].mention())
|
s = if n > 1 { "s" } else { "" },
|
||||||
.replace("{offset}", &format!("<t:{}:R>", timestamp)),
|
locations = successes
|
||||||
n => lm
|
.iter()
|
||||||
.get(&user_data.language, "remind/success_bulk")
|
.map(|l| l.mention())
|
||||||
.replace("{number}", &n.to_string())
|
.collect::<Vec<String>>()
|
||||||
.replace(
|
.join(", "),
|
||||||
"{location}",
|
offset = timestamp
|
||||||
&ok_locations
|
),
|
||||||
.iter()
|
|
||||||
.map(|l| l.mention())
|
|
||||||
.collect::<Vec<String>>()
|
|
||||||
.join(", "),
|
|
||||||
)
|
|
||||||
.replace("{offset}", &format!("<t:{}:R>", timestamp)),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let error_part = format!(
|
let error_part = match errors.len() {
|
||||||
"{}\n{}",
|
0 => "".to_string(),
|
||||||
match err_locations.len() {
|
n => format!(
|
||||||
0 => "".to_string(),
|
"{n} reminder{s} failed to set:\n{errors}",
|
||||||
1 => lm
|
s = if n > 1 { "s" } else { "" },
|
||||||
.get(&user_data.language, "remind/issue")
|
n = n,
|
||||||
.replace("{location}", &err_locations[0].mention()),
|
errors = errors
|
||||||
n => lm
|
.iter()
|
||||||
.get(&user_data.language, "remind/issue_bulk")
|
.map(|e| e.display(true))
|
||||||
.replace("{number}", &n.to_string())
|
.collect::<Vec<String>>()
|
||||||
.replace(
|
.join("\n")
|
||||||
"{location}",
|
),
|
||||||
&err_locations
|
};
|
||||||
.iter()
|
|
||||||
.map(|l| l.mention())
|
|
||||||
.collect::<Vec<String>>()
|
|
||||||
.join(", "),
|
|
||||||
),
|
|
||||||
},
|
|
||||||
err_types
|
|
||||||
.iter()
|
|
||||||
.map(|err| match err {
|
|
||||||
ReminderError::DiscordError(s) => lm
|
|
||||||
.get(&user_data.language, err.to_response_natural())
|
|
||||||
.replace("{error}", &s),
|
|
||||||
|
|
||||||
_ => lm
|
|
||||||
.get(&user_data.language, err.to_response_natural())
|
|
||||||
.to_string(),
|
|
||||||
})
|
|
||||||
.collect::<Vec<String>>()
|
|
||||||
.join("\n")
|
|
||||||
);
|
|
||||||
|
|
||||||
let _ = msg
|
let _ = msg
|
||||||
.channel_id
|
.channel_id
|
||||||
.send_message(&ctx, |m| {
|
.send_message(&ctx, |m| {
|
||||||
m.embed(|e| {
|
m.embed(|e| {
|
||||||
e.title(
|
e.title(format!(
|
||||||
lm.get(&user_data.language, "remind/title")
|
"{n} Reminder{s} Set",
|
||||||
.replace("{number}", &ok_locations.len().to_string()),
|
n = successes.len(),
|
||||||
)
|
s = if successes.len() > 1 { "s" } else { "" }
|
||||||
|
))
|
||||||
.description(format!("{}\n\n{}", success_part, error_part))
|
.description(format!("{}\n\n{}", success_part, error_part))
|
||||||
.color(*THEME_COLOR)
|
.color(*THEME_COLOR)
|
||||||
})
|
})
|
||||||
@ -1263,14 +817,9 @@ async fn natural(ctx: &Context, msg: &Message, args: String) {
|
|||||||
.channel_id
|
.channel_id
|
||||||
.send_message(ctx, |m| {
|
.send_message(ctx, |m| {
|
||||||
m.embed(move |e| {
|
m.embed(move |e| {
|
||||||
e.title(
|
e.title("0 Reminders Set")
|
||||||
lm.get(&user_data.language, "remind/title")
|
.description(content_error.to_string())
|
||||||
.replace("{number}", "0"),
|
.color(*THEME_COLOR)
|
||||||
)
|
|
||||||
.description(
|
|
||||||
lm.get(&user_data.language, content_error.to_response()),
|
|
||||||
)
|
|
||||||
.color(*THEME_COLOR)
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
@ -1306,156 +855,3 @@ async fn natural(ctx: &Context, msg: &Message, args: String) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn create_reminder<'a, U: Into<u64>, T: TryInto<i64>>(
|
|
||||||
ctx: &Context,
|
|
||||||
pool: &MySqlPool,
|
|
||||||
user_id: U,
|
|
||||||
guild_id: Option<GuildId>,
|
|
||||||
scope_id: &ReminderScope,
|
|
||||||
time_parser: T,
|
|
||||||
expires_parser: Option<T>,
|
|
||||||
interval: Option<i64>,
|
|
||||||
content: &mut Content,
|
|
||||||
) -> Result<Reminder, ReminderError> {
|
|
||||||
let user_id = user_id.into();
|
|
||||||
|
|
||||||
if let Some(g_id) = guild_id {
|
|
||||||
if let Some(guild) = g_id.to_guild_cached(&ctx).await {
|
|
||||||
content.substitute(guild);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut nudge = 0;
|
|
||||||
|
|
||||||
let db_channel_id = match scope_id {
|
|
||||||
ReminderScope::User(user_id) => {
|
|
||||||
if let Ok(user) = UserId(*user_id).to_user(&ctx).await {
|
|
||||||
let user_data = UserData::from_user(&user, &ctx, &pool).await.unwrap();
|
|
||||||
|
|
||||||
if let Some(guild_id) = guild_id {
|
|
||||||
if guild_id.member(&ctx, user).await.is_err() {
|
|
||||||
return Err(ReminderError::InvalidTag);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
user_data.dm_channel
|
|
||||||
} else {
|
|
||||||
return Err(ReminderError::InvalidTag);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ReminderScope::Channel(channel_id) => {
|
|
||||||
let channel = ChannelId(*channel_id).to_channel(&ctx).await.unwrap();
|
|
||||||
|
|
||||||
if channel.clone().guild().map(|gc| gc.guild_id) != guild_id {
|
|
||||||
return Err(ReminderError::InvalidTag);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut channel_data = ChannelData::from_channel(channel.clone(), &pool)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
nudge = channel_data.nudge;
|
|
||||||
|
|
||||||
if let Some(guild_channel) = channel.guild() {
|
|
||||||
if channel_data.webhook_token.is_none() || channel_data.webhook_id.is_none() {
|
|
||||||
match create_webhook(&ctx, guild_channel, "Reminder").await {
|
|
||||||
Ok(webhook) => {
|
|
||||||
channel_data.webhook_id = Some(webhook.id.as_u64().to_owned());
|
|
||||||
channel_data.webhook_token = webhook.token;
|
|
||||||
|
|
||||||
channel_data.commit_changes(&pool).await;
|
|
||||||
}
|
|
||||||
|
|
||||||
Err(e) => {
|
|
||||||
return Err(ReminderError::DiscordError(e.to_string()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
channel_data.id
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// validate time, channel
|
|
||||||
if interval.map_or(false, |inner| inner < *MIN_INTERVAL) {
|
|
||||||
Err(ReminderError::ShortInterval)
|
|
||||||
} else if interval.map_or(false, |inner| inner > *MAX_TIME) {
|
|
||||||
Err(ReminderError::LongInterval)
|
|
||||||
} else {
|
|
||||||
match time_parser.try_into() {
|
|
||||||
Ok(time_pre) => {
|
|
||||||
match expires_parser.map(|t| t.try_into()).transpose() {
|
|
||||||
Ok(expires) => {
|
|
||||||
let time = time_pre + nudge as i64;
|
|
||||||
|
|
||||||
let unix_time = SystemTime::now()
|
|
||||||
.duration_since(UNIX_EPOCH)
|
|
||||||
.unwrap()
|
|
||||||
.as_secs() as i64;
|
|
||||||
|
|
||||||
if time >= unix_time - 10 {
|
|
||||||
let uid = generate_uid();
|
|
||||||
|
|
||||||
sqlx::query!(
|
|
||||||
"
|
|
||||||
INSERT INTO reminders (
|
|
||||||
uid,
|
|
||||||
content,
|
|
||||||
tts,
|
|
||||||
attachment,
|
|
||||||
attachment_name,
|
|
||||||
channel_id,
|
|
||||||
`utc_time`,
|
|
||||||
expires,
|
|
||||||
`interval`,
|
|
||||||
set_by
|
|
||||||
) VALUES (
|
|
||||||
?,
|
|
||||||
?,
|
|
||||||
?,
|
|
||||||
?,
|
|
||||||
?,
|
|
||||||
?,
|
|
||||||
DATE_ADD(FROM_UNIXTIME(0), INTERVAL ? SECOND),
|
|
||||||
DATE_ADD(FROM_UNIXTIME(0), INTERVAL ? SECOND),
|
|
||||||
?,
|
|
||||||
(SELECT id FROM users WHERE user = ? LIMIT 1)
|
|
||||||
)
|
|
||||||
",
|
|
||||||
uid,
|
|
||||||
content.content,
|
|
||||||
content.tts,
|
|
||||||
content.attachment,
|
|
||||||
content.attachment_name,
|
|
||||||
db_channel_id,
|
|
||||||
time,
|
|
||||||
expires,
|
|
||||||
interval,
|
|
||||||
user_id
|
|
||||||
)
|
|
||||||
.execute(pool)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let reminder = Reminder::from_uid(ctx, uid).await.unwrap();
|
|
||||||
|
|
||||||
Ok(reminder)
|
|
||||||
} else if time < 0 {
|
|
||||||
// case required for if python returns -1
|
|
||||||
Err(ReminderError::InvalidTime)
|
|
||||||
} else {
|
|
||||||
Err(ReminderError::PastTime)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Err(_) => Err(ReminderError::InvalidExpiration),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Err(_) => Err(ReminderError::InvalidTime),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -14,7 +14,7 @@ use std::fmt;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
command_help, get_ctx_data,
|
command_help, get_ctx_data,
|
||||||
models::{user_data::UserData, CtxGuildData},
|
models::{user_data::UserData, CtxData},
|
||||||
};
|
};
|
||||||
use sqlx::MySqlPool;
|
use sqlx::MySqlPool;
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
@ -362,7 +362,7 @@ impl Execute for Result<SubCommand, ()> {
|
|||||||
if let Ok(subcommand) = self {
|
if let Ok(subcommand) = self {
|
||||||
target.execute(ctx, msg, subcommand, extra).await;
|
target.execute(ctx, msg, subcommand, extra).await;
|
||||||
} else {
|
} else {
|
||||||
show_help(&ctx, msg, Some(target)).await;
|
show_help(ctx, msg, Some(target)).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,7 @@ use std::{collections::HashMap, fmt};
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
language_manager::LanguageManager,
|
language_manager::LanguageManager,
|
||||||
models::{channel_data::ChannelData, guild_data::GuildData, user_data::UserData, CtxGuildData},
|
models::{channel_data::ChannelData, guild_data::GuildData, user_data::UserData, CtxData},
|
||||||
LimitExecutors, SQLPool,
|
LimitExecutors, SQLPool,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -312,10 +312,10 @@ impl Framework for RegexFramework {
|
|||||||
guild: &Guild,
|
guild: &Guild,
|
||||||
channel: &GuildChannel,
|
channel: &GuildChannel,
|
||||||
) -> SerenityResult<PermissionCheck> {
|
) -> SerenityResult<PermissionCheck> {
|
||||||
let user_id = ctx.cache.current_user_id().await;
|
let user_id = ctx.cache.current_user_id();
|
||||||
|
|
||||||
let guild_perms = guild.member_permissions(&ctx, user_id).await?;
|
let guild_perms = guild.member_permissions(&ctx, user_id).await?;
|
||||||
let channel_perms = channel.permissions_for_user(ctx, user_id).await?;
|
let channel_perms = channel.permissions_for_user(ctx, user_id)?;
|
||||||
|
|
||||||
let basic_perms = channel_perms.send_messages();
|
let basic_perms = channel_perms.send_messages();
|
||||||
|
|
||||||
@ -347,8 +347,8 @@ impl Framework for RegexFramework {
|
|||||||
if (msg.author.bot && self.ignore_bots) || msg.content.is_empty() {
|
if (msg.author.bot && self.ignore_bots) || msg.content.is_empty() {
|
||||||
} else {
|
} else {
|
||||||
// Guild Command
|
// Guild Command
|
||||||
if let (Some(guild), Some(Channel::Guild(channel))) =
|
if let (Some(guild), Ok(Channel::Guild(channel))) =
|
||||||
(msg.guild(&ctx).await, msg.channel(&ctx).await)
|
(msg.guild(&ctx), msg.channel(&ctx).await)
|
||||||
{
|
{
|
||||||
let data = ctx.data.read().await;
|
let data = ctx.data.read().await;
|
||||||
|
|
||||||
|
211
src/main.rs
211
src/main.rs
@ -19,7 +19,9 @@ use serenity::{
|
|||||||
channel::Message,
|
channel::Message,
|
||||||
guild::{Guild, GuildUnavailable},
|
guild::{Guild, GuildUnavailable},
|
||||||
id::{GuildId, UserId},
|
id::{GuildId, UserId},
|
||||||
interactions::{Interaction, InteractionData, InteractionType},
|
interactions::{
|
||||||
|
Interaction, InteractionApplicationCommandCallbackDataFlags, InteractionResponseType,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
prelude::{Context, EventHandler, TypeMapKey},
|
prelude::{Context, EventHandler, TypeMapKey},
|
||||||
utils::shard_id,
|
utils::shard_id,
|
||||||
@ -36,7 +38,11 @@ use crate::{
|
|||||||
consts::{CNC_GUILD, DEFAULT_PREFIX, SUBSCRIPTION_ROLES, THEME_COLOR},
|
consts::{CNC_GUILD, DEFAULT_PREFIX, SUBSCRIPTION_ROLES, THEME_COLOR},
|
||||||
framework::RegexFramework,
|
framework::RegexFramework,
|
||||||
language_manager::LanguageManager,
|
language_manager::LanguageManager,
|
||||||
models::{guild_data::GuildData, user_data::UserData},
|
models::{
|
||||||
|
guild_data::GuildData,
|
||||||
|
reminder::{Reminder, ReminderAction},
|
||||||
|
user_data::UserData,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
use inflector::Inflector;
|
use inflector::Inflector;
|
||||||
@ -46,12 +52,9 @@ use dashmap::DashMap;
|
|||||||
|
|
||||||
use tokio::sync::RwLock;
|
use tokio::sync::RwLock;
|
||||||
|
|
||||||
use crate::models::reminder::{Reminder, ReminderAction};
|
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
|
|
||||||
use chrono_tz::Tz;
|
use chrono_tz::Tz;
|
||||||
use serenity::model::prelude::{
|
|
||||||
InteractionApplicationCommandCallbackDataFlags, InteractionResponseType,
|
|
||||||
};
|
|
||||||
|
|
||||||
struct GuildDataCache;
|
struct GuildDataCache;
|
||||||
|
|
||||||
@ -187,13 +190,12 @@ DELETE FROM channels WHERE channel = ?
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Ok(token) = env::var("DISCORDBOTS_TOKEN") {
|
if let Ok(token) = env::var("DISCORDBOTS_TOKEN") {
|
||||||
let shard_count = ctx.cache.shard_count().await;
|
let shard_count = ctx.cache.shard_count();
|
||||||
let current_shard_id = shard_id(guild_id, shard_count);
|
let current_shard_id = shard_id(guild_id, shard_count);
|
||||||
|
|
||||||
let guild_count = ctx
|
let guild_count = ctx
|
||||||
.cache
|
.cache
|
||||||
.guilds()
|
.guilds()
|
||||||
.await
|
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|g| shard_id(g.as_u64().to_owned(), shard_count) == current_shard_id)
|
.filter(|g| shard_id(g.as_u64().to_owned(), shard_count) == current_shard_id)
|
||||||
.count() as u64;
|
.count() as u64;
|
||||||
@ -215,7 +217,7 @@ DELETE FROM channels WHERE channel = ?
|
|||||||
.post(
|
.post(
|
||||||
format!(
|
format!(
|
||||||
"https://top.gg/api/bots/{}/stats",
|
"https://top.gg/api/bots/{}/stats",
|
||||||
ctx.cache.current_user_id().await.as_u64()
|
ctx.cache.current_user_id().as_u64()
|
||||||
)
|
)
|
||||||
.as_str(),
|
.as_str(),
|
||||||
)
|
)
|
||||||
@ -268,113 +270,117 @@ DELETE FROM guilds WHERE guild = ?
|
|||||||
async fn interaction_create(&self, ctx: Context, interaction: Interaction) {
|
async fn interaction_create(&self, ctx: Context, interaction: Interaction) {
|
||||||
let (pool, lm) = get_ctx_data(&&ctx).await;
|
let (pool, lm) = get_ctx_data(&&ctx).await;
|
||||||
|
|
||||||
match interaction.kind {
|
match interaction {
|
||||||
InteractionType::ApplicationCommand => {}
|
Interaction::MessageComponent(component) => {
|
||||||
InteractionType::MessageComponent => {
|
if component.data.custom_id.starts_with("timezone:") {
|
||||||
if let (Some(InteractionData::MessageComponent(data)), Some(member)) =
|
let mut user_data = UserData::from_user(&component.user, &ctx, &pool)
|
||||||
(interaction.clone().data, interaction.clone().member)
|
.await
|
||||||
{
|
.unwrap();
|
||||||
if data.custom_id.starts_with("timezone:") {
|
let new_timezone = component
|
||||||
let mut user_data = UserData::from_user(&member.user, &ctx, &pool)
|
.data
|
||||||
.await
|
.custom_id
|
||||||
.unwrap();
|
.replace("timezone:", "")
|
||||||
let new_timezone = data.custom_id.replace("timezone:", "").parse::<Tz>();
|
.parse::<Tz>();
|
||||||
|
|
||||||
if let Ok(timezone) = new_timezone {
|
if let Ok(timezone) = new_timezone {
|
||||||
user_data.timezone = timezone.to_string();
|
user_data.timezone = timezone.to_string();
|
||||||
user_data.commit_changes(&pool).await;
|
user_data.commit_changes(&pool).await;
|
||||||
|
|
||||||
let _ = interaction.create_interaction_response(&ctx, |r| {
|
let _ = component.create_interaction_response(&ctx, |r| {
|
||||||
r.kind(InteractionResponseType::ChannelMessageWithSource)
|
r.kind(InteractionResponseType::ChannelMessageWithSource)
|
||||||
.interaction_response_data(|d| {
|
.interaction_response_data(|d| {
|
||||||
let footer_text = lm.get(&user_data.language, "timezone/footer").replacen(
|
let footer_text = lm.get(&user_data.language, "timezone/footer").replacen(
|
||||||
"{timezone}",
|
"{timezone}",
|
||||||
&user_data.timezone,
|
&user_data.timezone,
|
||||||
|
1,
|
||||||
|
);
|
||||||
|
|
||||||
|
let now = Utc::now().with_timezone(&user_data.timezone());
|
||||||
|
|
||||||
|
let content = lm
|
||||||
|
.get(&user_data.language, "timezone/set_p")
|
||||||
|
.replacen("{timezone}", &user_data.timezone, 1)
|
||||||
|
.replacen(
|
||||||
|
"{time}",
|
||||||
|
&now.format("%H:%M").to_string(),
|
||||||
1,
|
1,
|
||||||
);
|
);
|
||||||
|
|
||||||
let now = Utc::now().with_timezone(&user_data.timezone());
|
d.create_embed(|e| e.title(lm.get(&user_data.language, "timezone/set_p_title"))
|
||||||
|
.color(*THEME_COLOR)
|
||||||
|
.description(content)
|
||||||
|
.footer(|f| f.text(footer_text)))
|
||||||
|
.flags(InteractionApplicationCommandCallbackDataFlags::EPHEMERAL);
|
||||||
|
|
||||||
let content = lm
|
d
|
||||||
.get(&user_data.language, "timezone/set_p")
|
})
|
||||||
.replacen("{timezone}", &user_data.timezone, 1)
|
}).await;
|
||||||
.replacen(
|
}
|
||||||
"{time}",
|
} else if component.data.custom_id.starts_with("lang:") {
|
||||||
&now.format("%H:%M").to_string(),
|
let mut user_data = UserData::from_user(&component.user, &ctx, &pool)
|
||||||
1,
|
.await
|
||||||
);
|
.unwrap();
|
||||||
|
let lang_code = component.data.custom_id.replace("lang:", "");
|
||||||
|
|
||||||
d.create_embed(|e| e.title(lm.get(&user_data.language, "timezone/set_p_title"))
|
if let Some(lang) = lm.get_language(&lang_code) {
|
||||||
|
user_data.language = lang.to_string();
|
||||||
|
user_data.commit_changes(&pool).await;
|
||||||
|
|
||||||
|
let _ = component
|
||||||
|
.create_interaction_response(&ctx, |r| {
|
||||||
|
r.kind(InteractionResponseType::ChannelMessageWithSource)
|
||||||
|
.interaction_response_data(|d| {
|
||||||
|
d.create_embed(|e| {
|
||||||
|
e.title(
|
||||||
|
lm.get(&user_data.language, "lang/set_p_title"),
|
||||||
|
)
|
||||||
.color(*THEME_COLOR)
|
.color(*THEME_COLOR)
|
||||||
.description(content)
|
.description(
|
||||||
.footer(|f| f.text(footer_text)))
|
lm.get(&user_data.language, "lang/set_p"),
|
||||||
.flags(InteractionApplicationCommandCallbackDataFlags::EPHEMERAL);
|
)
|
||||||
|
})
|
||||||
d
|
.flags(InteractionApplicationCommandCallbackDataFlags::EPHEMERAL)
|
||||||
})
|
})
|
||||||
}).await;
|
})
|
||||||
}
|
.await;
|
||||||
} else if data.custom_id.starts_with("lang:") {
|
}
|
||||||
let mut user_data = UserData::from_user(&member.user, &ctx, &pool)
|
} else {
|
||||||
.await
|
match Reminder::from_interaction(
|
||||||
.unwrap();
|
&ctx,
|
||||||
let lang_code = data.custom_id.replace("lang:", "");
|
component.user.id,
|
||||||
|
component.data.custom_id.clone(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok((reminder, action)) => {
|
||||||
|
let response = match action {
|
||||||
|
ReminderAction::Delete => {
|
||||||
|
reminder.delete(&ctx).await;
|
||||||
|
"Reminder has been deleted"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
if let Some(lang) = lm.get_language(&lang_code) {
|
let _ = component
|
||||||
user_data.language = lang.to_string();
|
|
||||||
user_data.commit_changes(&pool).await;
|
|
||||||
|
|
||||||
let _ = interaction
|
|
||||||
.create_interaction_response(&ctx, |r| {
|
.create_interaction_response(&ctx, |r| {
|
||||||
r.kind(InteractionResponseType::ChannelMessageWithSource)
|
r.kind(InteractionResponseType::ChannelMessageWithSource)
|
||||||
.interaction_response_data(|d| {
|
.interaction_response_data(|d| d
|
||||||
d.create_embed(|e| {
|
.content(response)
|
||||||
e.title(
|
|
||||||
lm.get(&user_data.language, "lang/set_p_title"),
|
|
||||||
)
|
|
||||||
.color(*THEME_COLOR)
|
|
||||||
.description(
|
|
||||||
lm.get(&user_data.language, "lang/set_p"),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.flags(InteractionApplicationCommandCallbackDataFlags::EPHEMERAL)
|
.flags(InteractionApplicationCommandCallbackDataFlags::EPHEMERAL)
|
||||||
})
|
)
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
match Reminder::from_interaction(&ctx, member.user.id, data.custom_id).await
|
|
||||||
{
|
|
||||||
Ok((reminder, action)) => {
|
|
||||||
let response = match action {
|
|
||||||
ReminderAction::Delete => {
|
|
||||||
reminder.delete(&ctx).await;
|
|
||||||
"Reminder has been deleted"
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let _ = interaction
|
Err(ie) => {
|
||||||
.create_interaction_response(&ctx, |r| {
|
let _ = component
|
||||||
r.kind(InteractionResponseType::ChannelMessageWithSource)
|
.create_interaction_response(&ctx, |r| {
|
||||||
.interaction_response_data(|d| d
|
r.kind(InteractionResponseType::ChannelMessageWithSource)
|
||||||
.content(response)
|
.interaction_response_data(|d| d
|
||||||
.flags(InteractionApplicationCommandCallbackDataFlags::EPHEMERAL)
|
.content(ie.to_string())
|
||||||
)
|
.flags(InteractionApplicationCommandCallbackDataFlags::EPHEMERAL)
|
||||||
})
|
)
|
||||||
.await;
|
})
|
||||||
}
|
.await;
|
||||||
|
|
||||||
Err(ie) => {
|
|
||||||
let _ = interaction
|
|
||||||
.create_interaction_response(&ctx, |r| {
|
|
||||||
r.kind(InteractionResponseType::ChannelMessageWithSource)
|
|
||||||
.interaction_response_data(|d| d
|
|
||||||
.content(ie.to_string())
|
|
||||||
.flags(InteractionApplicationCommandCallbackDataFlags::EPHEMERAL)
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.await;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -424,7 +430,6 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
|||||||
.add_command("natural", &reminder_cmds::NATURAL_COMMAND)
|
.add_command("natural", &reminder_cmds::NATURAL_COMMAND)
|
||||||
.add_command("n", &reminder_cmds::NATURAL_COMMAND)
|
.add_command("n", &reminder_cmds::NATURAL_COMMAND)
|
||||||
.add_command("", &reminder_cmds::NATURAL_COMMAND)
|
.add_command("", &reminder_cmds::NATURAL_COMMAND)
|
||||||
.add_command("countdown", &reminder_cmds::COUNTDOWN_COMMAND)
|
|
||||||
// management commands
|
// management commands
|
||||||
.add_command("look", &reminder_cmds::LOOK_COMMAND)
|
.add_command("look", &reminder_cmds::LOOK_COMMAND)
|
||||||
.add_command("del", &reminder_cmds::DELETE_COMMAND)
|
.add_command("del", &reminder_cmds::DELETE_COMMAND)
|
||||||
@ -574,7 +579,7 @@ pub async fn check_subscription_on_message(
|
|||||||
msg: &Message,
|
msg: &Message,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
check_subscription(&cache_http, &msg.author).await
|
check_subscription(&cache_http, &msg.author).await
|
||||||
|| if let Some(guild) = msg.guild(&cache_http).await {
|
|| if let Some(guild) = msg.guild(&cache_http) {
|
||||||
check_subscription(&cache_http, guild.owner_id).await
|
check_subscription(&cache_http, guild.owner_id).await
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
@ -616,8 +621,8 @@ async fn command_help(
|
|||||||
m.embed(move |e| {
|
m.embed(move |e| {
|
||||||
e.title(format!("{} Help", command_name.to_title_case()))
|
e.title(format!("{} Help", command_name.to_title_case()))
|
||||||
.description(
|
.description(
|
||||||
lm.get(&language, &format!("help/{}", command_name))
|
lm.get(language, &format!("help/{}", command_name))
|
||||||
.replace("{prefix}", &prefix),
|
.replace("{prefix}", prefix),
|
||||||
)
|
)
|
||||||
.footer(|f| {
|
.footer(|f| {
|
||||||
f.text(concat!(
|
f.text(concat!(
|
||||||
|
@ -4,34 +4,46 @@ pub mod reminder;
|
|||||||
pub mod timer;
|
pub mod timer;
|
||||||
pub mod user_data;
|
pub mod user_data;
|
||||||
|
|
||||||
use serenity::{async_trait, model::id::GuildId, prelude::Context};
|
use serenity::{
|
||||||
|
async_trait,
|
||||||
|
model::id::{GuildId, UserId},
|
||||||
|
prelude::Context,
|
||||||
|
};
|
||||||
|
|
||||||
use crate::{consts::DEFAULT_PREFIX, GuildDataCache, SQLPool};
|
use crate::{consts::DEFAULT_PREFIX, GuildDataCache, SQLPool};
|
||||||
|
|
||||||
use guild_data::GuildData;
|
use guild_data::GuildData;
|
||||||
|
|
||||||
|
use crate::models::user_data::UserData;
|
||||||
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use tokio::sync::RwLock;
|
use tokio::sync::RwLock;
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
pub trait CtxGuildData {
|
pub trait CtxData {
|
||||||
async fn guild_data<G: Into<GuildId> + Send + Sync>(
|
async fn guild_data<G: Into<GuildId> + Send + Sync>(
|
||||||
&self,
|
&self,
|
||||||
guild_id: G,
|
guild_id: G,
|
||||||
) -> Result<Arc<RwLock<GuildData>>, sqlx::Error>;
|
) -> Result<Arc<RwLock<GuildData>>, sqlx::Error>;
|
||||||
|
|
||||||
|
async fn user_data<U: Into<UserId> + Send + Sync>(
|
||||||
|
&self,
|
||||||
|
user_id: U,
|
||||||
|
) -> Result<UserData, Box<dyn std::error::Error + Sync + Send>>;
|
||||||
|
|
||||||
async fn prefix<G: Into<GuildId> + Send + Sync>(&self, guild_id: Option<G>) -> String;
|
async fn prefix<G: Into<GuildId> + Send + Sync>(&self, guild_id: Option<G>) -> String;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl CtxGuildData for Context {
|
impl CtxData for Context {
|
||||||
async fn guild_data<G: Into<GuildId> + Send + Sync>(
|
async fn guild_data<G: Into<GuildId> + Send + Sync>(
|
||||||
&self,
|
&self,
|
||||||
guild_id: G,
|
guild_id: G,
|
||||||
) -> Result<Arc<RwLock<GuildData>>, sqlx::Error> {
|
) -> Result<Arc<RwLock<GuildData>>, sqlx::Error> {
|
||||||
let guild_id = guild_id.into();
|
let guild_id = guild_id.into();
|
||||||
|
|
||||||
let guild = guild_id.to_guild_cached(&self.cache).await.unwrap();
|
let guild = guild_id.to_guild_cached(&self.cache).unwrap();
|
||||||
|
|
||||||
let guild_cache = self
|
let guild_cache = self
|
||||||
.data
|
.data
|
||||||
@ -62,6 +74,18 @@ impl CtxGuildData for Context {
|
|||||||
x
|
x
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn user_data<U: Into<UserId> + Send + Sync>(
|
||||||
|
&self,
|
||||||
|
user_id: U,
|
||||||
|
) -> Result<UserData, Box<dyn std::error::Error + Sync + Send>> {
|
||||||
|
let user_id = user_id.into();
|
||||||
|
let pool = self.data.read().await.get::<SQLPool>().cloned().unwrap();
|
||||||
|
|
||||||
|
let user = user_id.to_user(self).await.unwrap();
|
||||||
|
|
||||||
|
UserData::from_user(&user, &self, &pool).await
|
||||||
|
}
|
||||||
|
|
||||||
async fn prefix<G: Into<GuildId> + Send + Sync>(&self, guild_id: Option<G>) -> String {
|
async fn prefix<G: Into<GuildId> + Send + Sync>(&self, guild_id: Option<G>) -> String {
|
||||||
if let Some(guild_id) = guild_id {
|
if let Some(guild_id) = guild_id {
|
||||||
self.guild_data(guild_id)
|
self.guild_data(guild_id)
|
||||||
|
365
src/models/reminder/builder.rs
Normal file
365
src/models/reminder/builder.rs
Normal file
@ -0,0 +1,365 @@
|
|||||||
|
use serenity::{
|
||||||
|
client::Context,
|
||||||
|
http::CacheHttp,
|
||||||
|
model::{
|
||||||
|
channel::GuildChannel,
|
||||||
|
id::{ChannelId, GuildId, UserId},
|
||||||
|
webhook::Webhook,
|
||||||
|
},
|
||||||
|
Result as SerenityResult,
|
||||||
|
};
|
||||||
|
|
||||||
|
use chrono::{Duration, NaiveDateTime, Utc};
|
||||||
|
use chrono_tz::Tz;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
consts::{MAX_TIME, MIN_INTERVAL},
|
||||||
|
models::{
|
||||||
|
channel_data::ChannelData,
|
||||||
|
reminder::{content::Content, errors::ReminderError, helper::generate_uid, Reminder},
|
||||||
|
user_data::UserData,
|
||||||
|
},
|
||||||
|
time_parser::TimeParser,
|
||||||
|
SQLPool,
|
||||||
|
};
|
||||||
|
|
||||||
|
use sqlx::MySqlPool;
|
||||||
|
|
||||||
|
use std::{collections::HashSet, fmt::Display};
|
||||||
|
|
||||||
|
async fn create_webhook(
|
||||||
|
ctx: impl CacheHttp,
|
||||||
|
channel: GuildChannel,
|
||||||
|
name: impl Display,
|
||||||
|
) -> SerenityResult<Webhook> {
|
||||||
|
channel
|
||||||
|
.create_webhook_with_avatar(
|
||||||
|
ctx.http(),
|
||||||
|
name,
|
||||||
|
(
|
||||||
|
include_bytes!(concat!(
|
||||||
|
env!("CARGO_MANIFEST_DIR"),
|
||||||
|
"/assets/",
|
||||||
|
env!(
|
||||||
|
"WEBHOOK_AVATAR",
|
||||||
|
"WEBHOOK_AVATAR not provided for compilation"
|
||||||
|
)
|
||||||
|
)) as &[u8],
|
||||||
|
env!("WEBHOOK_AVATAR"),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Hash, PartialEq, Eq)]
|
||||||
|
pub enum ReminderScope {
|
||||||
|
User(u64),
|
||||||
|
Channel(u64),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ReminderScope {
|
||||||
|
pub fn mention(&self) -> String {
|
||||||
|
match self {
|
||||||
|
Self::User(id) => format!("<@{}>", id),
|
||||||
|
Self::Channel(id) => format!("<#{}>", id),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ReminderBuilder {
|
||||||
|
pool: MySqlPool,
|
||||||
|
uid: String,
|
||||||
|
channel: u32,
|
||||||
|
utc_time: NaiveDateTime,
|
||||||
|
timezone: String,
|
||||||
|
interval: Option<i64>,
|
||||||
|
expires: Option<NaiveDateTime>,
|
||||||
|
content: String,
|
||||||
|
tts: bool,
|
||||||
|
attachment_name: Option<String>,
|
||||||
|
attachment: Option<Vec<u8>>,
|
||||||
|
set_by: Option<u32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ReminderBuilder {
|
||||||
|
pub async fn build(self) -> Result<Reminder, ReminderError> {
|
||||||
|
let queried_time = sqlx::query!(
|
||||||
|
"SELECT DATE_ADD(?, INTERVAL (SELECT nudge FROM channels WHERE id = ?) SECOND) AS `utc_time`",
|
||||||
|
self.utc_time,
|
||||||
|
self.channel,
|
||||||
|
)
|
||||||
|
.fetch_one(&self.pool)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
match queried_time.utc_time {
|
||||||
|
Some(utc_time) => {
|
||||||
|
if utc_time < (Utc::now() + Duration::seconds(60)).naive_local() {
|
||||||
|
Err(ReminderError::PastTime)
|
||||||
|
} else {
|
||||||
|
sqlx::query!(
|
||||||
|
"
|
||||||
|
INSERT INTO reminders (
|
||||||
|
`uid`,
|
||||||
|
`channel_id`,
|
||||||
|
`utc_time`,
|
||||||
|
`timezone`,
|
||||||
|
`interval`,
|
||||||
|
`expires`,
|
||||||
|
`content`,
|
||||||
|
`tts`,
|
||||||
|
`attachment_name`,
|
||||||
|
`attachment`,
|
||||||
|
`set_by`
|
||||||
|
) VALUES (
|
||||||
|
?,
|
||||||
|
?,
|
||||||
|
?,
|
||||||
|
?,
|
||||||
|
?,
|
||||||
|
?,
|
||||||
|
?,
|
||||||
|
?,
|
||||||
|
?,
|
||||||
|
?,
|
||||||
|
?
|
||||||
|
)
|
||||||
|
",
|
||||||
|
self.uid,
|
||||||
|
self.channel,
|
||||||
|
utc_time,
|
||||||
|
self.timezone,
|
||||||
|
self.interval,
|
||||||
|
self.expires,
|
||||||
|
self.content,
|
||||||
|
self.tts,
|
||||||
|
self.attachment_name,
|
||||||
|
self.attachment,
|
||||||
|
self.set_by
|
||||||
|
)
|
||||||
|
.execute(&self.pool)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
Ok(Reminder::from_uid(&self.pool, self.uid).await.unwrap())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None => Err(ReminderError::LongTime),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct MultiReminderBuilder<'a> {
|
||||||
|
scopes: Vec<ReminderScope>,
|
||||||
|
utc_time: NaiveDateTime,
|
||||||
|
utc_time_parser: Option<TimeParser>,
|
||||||
|
timezone: Tz,
|
||||||
|
interval: Option<i64>,
|
||||||
|
expires: Option<NaiveDateTime>,
|
||||||
|
expires_parser: Option<TimeParser>,
|
||||||
|
content: Content,
|
||||||
|
set_by: Option<u32>,
|
||||||
|
ctx: &'a Context,
|
||||||
|
guild_id: Option<GuildId>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> MultiReminderBuilder<'a> {
|
||||||
|
pub fn new(ctx: &'a Context, guild_id: Option<GuildId>) -> Self {
|
||||||
|
MultiReminderBuilder {
|
||||||
|
scopes: vec![],
|
||||||
|
utc_time: Utc::now().naive_utc(),
|
||||||
|
utc_time_parser: None,
|
||||||
|
timezone: Tz::UTC,
|
||||||
|
interval: None,
|
||||||
|
expires: None,
|
||||||
|
expires_parser: None,
|
||||||
|
content: Content::new(),
|
||||||
|
set_by: None,
|
||||||
|
ctx,
|
||||||
|
guild_id,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn content(mut self, content: Content) -> Self {
|
||||||
|
self.content = content;
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn time<T: Into<i64>>(mut self, time: T) -> Self {
|
||||||
|
self.utc_time = NaiveDateTime::from_timestamp(time.into(), 0);
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn time_parser(mut self, parser: TimeParser) -> Self {
|
||||||
|
self.utc_time_parser = Some(parser);
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn expires<T: Into<i64>>(mut self, time: Option<T>) -> Self {
|
||||||
|
if let Some(t) = time {
|
||||||
|
self.expires = Some(NaiveDateTime::from_timestamp(t.into(), 0));
|
||||||
|
} else {
|
||||||
|
self.expires = None;
|
||||||
|
}
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn expires_parser(mut self, parser: Option<TimeParser>) -> Self {
|
||||||
|
self.expires_parser = parser;
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn author(mut self, user: UserData) -> Self {
|
||||||
|
self.set_by = Some(user.id);
|
||||||
|
self.timezone = user.timezone();
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn interval(mut self, interval: Option<i64>) -> Self {
|
||||||
|
self.interval = interval;
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_scopes(&mut self, scopes: Vec<ReminderScope>) {
|
||||||
|
self.scopes = scopes;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn build(mut self) -> (HashSet<ReminderError>, HashSet<ReminderScope>) {
|
||||||
|
let pool = self
|
||||||
|
.ctx
|
||||||
|
.data
|
||||||
|
.read()
|
||||||
|
.await
|
||||||
|
.get::<SQLPool>()
|
||||||
|
.cloned()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let mut errors = HashSet::new();
|
||||||
|
|
||||||
|
let mut ok_locs = HashSet::new();
|
||||||
|
|
||||||
|
if let Some(expire_parser) = self.expires_parser {
|
||||||
|
if let Ok(expires) = expire_parser.timestamp() {
|
||||||
|
self.expires = Some(NaiveDateTime::from_timestamp(expires, 0));
|
||||||
|
} else {
|
||||||
|
errors.insert(ReminderError::InvalidExpiration);
|
||||||
|
|
||||||
|
return (errors, ok_locs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(time_parser) = self.utc_time_parser {
|
||||||
|
if let Ok(time) = time_parser.timestamp() {
|
||||||
|
self.utc_time = NaiveDateTime::from_timestamp(time, 0);
|
||||||
|
} else {
|
||||||
|
errors.insert(ReminderError::InvalidTime);
|
||||||
|
|
||||||
|
return (errors, ok_locs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.interval.map_or(false, |i| (i as i64) < *MIN_INTERVAL) {
|
||||||
|
errors.insert(ReminderError::ShortInterval);
|
||||||
|
} else if self.interval.map_or(false, |i| (i as i64) > *MAX_TIME) {
|
||||||
|
errors.insert(ReminderError::LongInterval);
|
||||||
|
} else {
|
||||||
|
for scope in self.scopes {
|
||||||
|
let db_channel_id = match scope {
|
||||||
|
ReminderScope::User(user_id) => {
|
||||||
|
if let Ok(user) = UserId(user_id).to_user(&self.ctx).await {
|
||||||
|
let user_data =
|
||||||
|
UserData::from_user(&user, &self.ctx, &pool).await.unwrap();
|
||||||
|
|
||||||
|
if let Some(guild_id) = self.guild_id {
|
||||||
|
if guild_id.member(&self.ctx, user).await.is_err() {
|
||||||
|
Err(ReminderError::InvalidTag)
|
||||||
|
} else {
|
||||||
|
Ok(user_data.dm_channel)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Ok(user_data.dm_channel)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Err(ReminderError::InvalidTag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ReminderScope::Channel(channel_id) => {
|
||||||
|
let channel = ChannelId(channel_id).to_channel(&self.ctx).await.unwrap();
|
||||||
|
|
||||||
|
if let Some(guild_channel) = channel.clone().guild() {
|
||||||
|
if Some(guild_channel.guild_id) != self.guild_id {
|
||||||
|
Err(ReminderError::InvalidTag)
|
||||||
|
} else {
|
||||||
|
let mut channel_data =
|
||||||
|
ChannelData::from_channel(channel, &pool).await.unwrap();
|
||||||
|
|
||||||
|
if channel_data.webhook_id.is_none()
|
||||||
|
|| channel_data.webhook_token.is_none()
|
||||||
|
{
|
||||||
|
match create_webhook(&self.ctx, guild_channel, "Reminder").await
|
||||||
|
{
|
||||||
|
Ok(webhook) => {
|
||||||
|
channel_data.webhook_id =
|
||||||
|
Some(webhook.id.as_u64().to_owned());
|
||||||
|
channel_data.webhook_token = webhook.token;
|
||||||
|
|
||||||
|
channel_data.commit_changes(&pool).await;
|
||||||
|
|
||||||
|
Ok(channel_data.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(e) => Err(ReminderError::DiscordError(e.to_string())),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Ok(channel_data.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Err(ReminderError::InvalidTag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
match db_channel_id {
|
||||||
|
Ok(c) => {
|
||||||
|
let builder = ReminderBuilder {
|
||||||
|
pool: pool.clone(),
|
||||||
|
uid: generate_uid(),
|
||||||
|
channel: c,
|
||||||
|
utc_time: self.utc_time,
|
||||||
|
timezone: self.timezone.to_string(),
|
||||||
|
interval: self.interval,
|
||||||
|
expires: self.expires,
|
||||||
|
content: self.content.content.clone(),
|
||||||
|
tts: self.content.tts,
|
||||||
|
attachment_name: self.content.attachment_name.clone(),
|
||||||
|
attachment: self.content.attachment.clone(),
|
||||||
|
set_by: self.set_by,
|
||||||
|
};
|
||||||
|
|
||||||
|
match builder.build().await {
|
||||||
|
Ok(_) => {
|
||||||
|
ok_locs.insert(scope);
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
errors.insert(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
errors.insert(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
(errors, ok_locs)
|
||||||
|
}
|
||||||
|
}
|
74
src/models/reminder/content.rs
Normal file
74
src/models/reminder/content.rs
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
use serenity::model::{channel::Message, guild::Guild, misc::Mentionable};
|
||||||
|
|
||||||
|
use regex::Captures;
|
||||||
|
|
||||||
|
use crate::{consts::REGEX_CONTENT_SUBSTITUTION, models::reminder::errors::ContentError};
|
||||||
|
|
||||||
|
pub struct Content {
|
||||||
|
pub content: String,
|
||||||
|
pub tts: bool,
|
||||||
|
pub attachment: Option<Vec<u8>>,
|
||||||
|
pub attachment_name: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Content {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
content: "".to_string(),
|
||||||
|
tts: false,
|
||||||
|
attachment: None,
|
||||||
|
attachment_name: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn build<S: ToString>(content: S, message: &Message) -> Result<Self, ContentError> {
|
||||||
|
if message.attachments.len() > 1 {
|
||||||
|
Err(ContentError::TooManyAttachments)
|
||||||
|
} else if let Some(attachment) = message.attachments.get(0) {
|
||||||
|
if attachment.size > 8_000_000 {
|
||||||
|
Err(ContentError::AttachmentTooLarge)
|
||||||
|
} else if let Ok(attachment_bytes) = attachment.download().await {
|
||||||
|
Ok(Self {
|
||||||
|
content: content.to_string(),
|
||||||
|
tts: false,
|
||||||
|
attachment: Some(attachment_bytes),
|
||||||
|
attachment_name: Some(attachment.filename.clone()),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Err(ContentError::AttachmentDownloadFailed)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Ok(Self {
|
||||||
|
content: content.to_string(),
|
||||||
|
tts: false,
|
||||||
|
attachment: None,
|
||||||
|
attachment_name: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn substitute(&mut self, guild: Guild) {
|
||||||
|
if self.content.starts_with("/tts ") {
|
||||||
|
self.tts = true;
|
||||||
|
self.content = self.content.split_off(5);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.content = REGEX_CONTENT_SUBSTITUTION
|
||||||
|
.replace(&self.content, |caps: &Captures| {
|
||||||
|
if let Some(user) = caps.name("user") {
|
||||||
|
format!("<@{}>", user.as_str())
|
||||||
|
} else if let Some(role_name) = caps.name("role") {
|
||||||
|
if let Some(role) = guild.role_by_name(role_name.as_str()) {
|
||||||
|
role.mention().to_string()
|
||||||
|
} else {
|
||||||
|
format!("<<{}>>", role_name.as_str().to_string())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
String::new()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.to_string()
|
||||||
|
.replace("<<everyone>>", "@everyone")
|
||||||
|
.replace("<<here>>", "@here");
|
||||||
|
}
|
||||||
|
}
|
81
src/models/reminder/errors.rs
Normal file
81
src/models/reminder/errors.rs
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
use crate::consts::{MAX_TIME, MIN_INTERVAL};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum InteractionError {
|
||||||
|
InvalidFormat,
|
||||||
|
InvalidBase64,
|
||||||
|
InvalidSize,
|
||||||
|
NoReminder,
|
||||||
|
SignatureMismatch,
|
||||||
|
InvalidAction,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToString for InteractionError {
|
||||||
|
fn to_string(&self) -> String {
|
||||||
|
match self {
|
||||||
|
InteractionError::InvalidFormat => {
|
||||||
|
String::from("The interaction data was improperly formatted")
|
||||||
|
}
|
||||||
|
InteractionError::InvalidBase64 => String::from("The interaction data was invalid"),
|
||||||
|
InteractionError::InvalidSize => String::from("The interaction data was invalid"),
|
||||||
|
InteractionError::NoReminder => String::from("Reminder could not be found"),
|
||||||
|
InteractionError::SignatureMismatch => {
|
||||||
|
String::from("Only the user who did the command can use interactions")
|
||||||
|
}
|
||||||
|
InteractionError::InvalidAction => String::from("The action was invalid"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Eq, Hash, Debug)]
|
||||||
|
pub enum ReminderError {
|
||||||
|
LongTime,
|
||||||
|
LongInterval,
|
||||||
|
PastTime,
|
||||||
|
ShortInterval,
|
||||||
|
InvalidTag,
|
||||||
|
InvalidTime,
|
||||||
|
InvalidExpiration,
|
||||||
|
DiscordError(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ReminderError {
|
||||||
|
pub fn display(&self, is_natural: bool) -> String {
|
||||||
|
match self {
|
||||||
|
ReminderError::LongTime => "That time is too far in the future. Please specify a shorter time.".to_string(),
|
||||||
|
ReminderError::LongInterval => format!("Please ensure the interval specified is less than {max_time} days", max_time = *MAX_TIME / 86_400),
|
||||||
|
ReminderError::PastTime => "Please ensure the time provided is in the future. If the time should be in the future, please be more specific with the definition.".to_string(),
|
||||||
|
ReminderError::ShortInterval => format!("Please ensure the interval provided is longer than {min_interval} seconds", min_interval = *MIN_INTERVAL),
|
||||||
|
ReminderError::InvalidTag => "Couldn't find a location by your tag. Your tag must be either a channel or a user (not a role)".to_string(),
|
||||||
|
ReminderError::InvalidTime => if is_natural {
|
||||||
|
"Your time failed to process. Please make it as clear as possible, for example `\"16th of july\"` or `\"in 20 minutes\"`".to_string()
|
||||||
|
} else {
|
||||||
|
"Make sure the time you have provided is in the format of [num][s/m/h/d][num][s/m/h/d] etc. or `day/month/year-hour:minute:second`".to_string()
|
||||||
|
},
|
||||||
|
ReminderError::InvalidExpiration => if is_natural {
|
||||||
|
"Your expiration time failed to process. Please make it as clear as possible, for example `\"16th of july\"` or `\"in 20 minutes\"`".to_string()
|
||||||
|
} else {
|
||||||
|
"Make sure the expiration time you have provided is in the format of [num][s/m/h/d][num][s/m/h/d] etc. or `day/month/year-hour:minute:second`".to_string()
|
||||||
|
},
|
||||||
|
ReminderError::DiscordError(s) => format!("A Discord error occurred: **{}**", s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum ContentError {
|
||||||
|
TooManyAttachments,
|
||||||
|
AttachmentTooLarge,
|
||||||
|
AttachmentDownloadFailed,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToString for ContentError {
|
||||||
|
fn to_string(&self) -> String {
|
||||||
|
match self {
|
||||||
|
ContentError::TooManyAttachments => "remind/too_many_attachments",
|
||||||
|
ContentError::AttachmentTooLarge => "remind/attachment_too_large",
|
||||||
|
ContentError::AttachmentDownloadFailed => "remind/attachment_download_failed",
|
||||||
|
}
|
||||||
|
.to_string()
|
||||||
|
}
|
||||||
|
}
|
40
src/models/reminder/helper.rs
Normal file
40
src/models/reminder/helper.rs
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
use crate::consts::{CHARACTERS, DAY, HOUR, MINUTE};
|
||||||
|
|
||||||
|
use num_integer::Integer;
|
||||||
|
|
||||||
|
use rand::{rngs::OsRng, seq::IteratorRandom};
|
||||||
|
|
||||||
|
pub fn longhand_displacement(seconds: u64) -> String {
|
||||||
|
let (days, seconds) = seconds.div_rem(&DAY);
|
||||||
|
let (hours, seconds) = seconds.div_rem(&HOUR);
|
||||||
|
let (minutes, seconds) = seconds.div_rem(&MINUTE);
|
||||||
|
|
||||||
|
let mut sections = vec![];
|
||||||
|
|
||||||
|
for (var, name) in [days, hours, minutes, seconds]
|
||||||
|
.iter()
|
||||||
|
.zip(["days", "hours", "minutes", "seconds"].iter())
|
||||||
|
{
|
||||||
|
if *var > 0 {
|
||||||
|
sections.push(format!("{} {}", var, name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sections.join(", ")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn generate_uid() -> String {
|
||||||
|
let mut generator: OsRng = Default::default();
|
||||||
|
|
||||||
|
(0..64)
|
||||||
|
.map(|_| {
|
||||||
|
CHARACTERS
|
||||||
|
.chars()
|
||||||
|
.choose(&mut generator)
|
||||||
|
.unwrap()
|
||||||
|
.to_owned()
|
||||||
|
.to_string()
|
||||||
|
})
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
.join("")
|
||||||
|
}
|
59
src/models/reminder/look_flags.rs
Normal file
59
src/models/reminder/look_flags.rs
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
use serenity::model::id::ChannelId;
|
||||||
|
|
||||||
|
use crate::consts::REGEX_CHANNEL;
|
||||||
|
|
||||||
|
pub enum TimeDisplayType {
|
||||||
|
Absolute,
|
||||||
|
Relative,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct LookFlags {
|
||||||
|
pub limit: u16,
|
||||||
|
pub show_disabled: bool,
|
||||||
|
pub channel_id: Option<ChannelId>,
|
||||||
|
pub time_display: TimeDisplayType,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for LookFlags {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
limit: u16::MAX,
|
||||||
|
show_disabled: true,
|
||||||
|
channel_id: None,
|
||||||
|
time_display: TimeDisplayType::Relative,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LookFlags {
|
||||||
|
pub fn from_string(args: &str) -> Self {
|
||||||
|
let mut new_flags: Self = Default::default();
|
||||||
|
|
||||||
|
for arg in args.split(' ') {
|
||||||
|
match arg {
|
||||||
|
"enabled" => {
|
||||||
|
new_flags.show_disabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
"time" => {
|
||||||
|
new_flags.time_display = TimeDisplayType::Absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
param => {
|
||||||
|
if let Ok(val) = param.parse::<u16>() {
|
||||||
|
new_flags.limit = val;
|
||||||
|
} else if let Some(channel) = REGEX_CHANNEL
|
||||||
|
.captures(arg)
|
||||||
|
.map(|cap| cap.get(1))
|
||||||
|
.flatten()
|
||||||
|
.map(|c| c.as_str().parse::<u64>().unwrap())
|
||||||
|
{
|
||||||
|
new_flags.channel_id = Some(ChannelId(channel));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
new_flags
|
||||||
|
}
|
||||||
|
}
|
@ -1,3 +1,9 @@
|
|||||||
|
pub mod builder;
|
||||||
|
pub mod content;
|
||||||
|
pub mod errors;
|
||||||
|
mod helper;
|
||||||
|
pub mod look_flags;
|
||||||
|
|
||||||
use serenity::{
|
use serenity::{
|
||||||
client::Context,
|
client::Context,
|
||||||
model::id::{ChannelId, GuildId, UserId},
|
model::id::{ChannelId, GuildId, UserId},
|
||||||
@ -6,32 +12,45 @@ use serenity::{
|
|||||||
use chrono::NaiveDateTime;
|
use chrono::NaiveDateTime;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
consts::{DAY, HOUR, MINUTE, REGEX_CHANNEL},
|
models::reminder::{
|
||||||
|
errors::InteractionError,
|
||||||
|
helper::longhand_displacement,
|
||||||
|
look_flags::{LookFlags, TimeDisplayType},
|
||||||
|
},
|
||||||
SQLPool,
|
SQLPool,
|
||||||
};
|
};
|
||||||
|
|
||||||
use num_integer::Integer;
|
|
||||||
use ring::hmac;
|
use ring::hmac;
|
||||||
use std::convert::{TryFrom, TryInto};
|
|
||||||
use std::env;
|
|
||||||
|
|
||||||
fn longhand_displacement(seconds: u64) -> String {
|
use sqlx::MySqlPool;
|
||||||
let (days, seconds) = seconds.div_rem(&DAY);
|
use std::{
|
||||||
let (hours, seconds) = seconds.div_rem(&HOUR);
|
convert::{TryFrom, TryInto},
|
||||||
let (minutes, seconds) = seconds.div_rem(&MINUTE);
|
env,
|
||||||
|
};
|
||||||
|
|
||||||
let mut sections = vec![];
|
#[derive(Clone, Copy)]
|
||||||
|
pub enum ReminderAction {
|
||||||
|
Delete,
|
||||||
|
}
|
||||||
|
|
||||||
for (var, name) in [days, hours, minutes, seconds]
|
impl ToString for ReminderAction {
|
||||||
.iter()
|
fn to_string(&self) -> String {
|
||||||
.zip(["days", "hours", "minutes", "seconds"].iter())
|
match self {
|
||||||
{
|
Self::Delete => String::from("del"),
|
||||||
if *var > 0 {
|
|
||||||
sections.push(format!("{} {}", var, name));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
sections.join(", ")
|
impl TryFrom<&str> for ReminderAction {
|
||||||
|
type Error = ();
|
||||||
|
|
||||||
|
fn try_from(value: &str) -> Result<Self, Self::Error> {
|
||||||
|
match value {
|
||||||
|
"del" => Ok(Self::Delete),
|
||||||
|
|
||||||
|
_ => Err(()),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@ -49,9 +68,7 @@ pub struct Reminder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Reminder {
|
impl Reminder {
|
||||||
pub async fn from_uid(ctx: &Context, uid: String) -> Option<Self> {
|
pub async fn from_uid(pool: &MySqlPool, uid: String) -> Option<Self> {
|
||||||
let pool = ctx.data.read().await.get::<SQLPool>().cloned().unwrap();
|
|
||||||
|
|
||||||
sqlx::query_as_unchecked!(
|
sqlx::query_as_unchecked!(
|
||||||
Self,
|
Self,
|
||||||
"
|
"
|
||||||
@ -81,7 +98,7 @@ WHERE
|
|||||||
",
|
",
|
||||||
uid
|
uid
|
||||||
)
|
)
|
||||||
.fetch_one(&pool)
|
.fetch_one(pool)
|
||||||
.await
|
.await
|
||||||
.ok()
|
.ok()
|
||||||
}
|
}
|
||||||
@ -178,7 +195,7 @@ LIMIT
|
|||||||
let pool = ctx.data.read().await.get::<SQLPool>().cloned().unwrap();
|
let pool = ctx.data.read().await.get::<SQLPool>().cloned().unwrap();
|
||||||
|
|
||||||
if let Some(guild_id) = guild_id {
|
if let Some(guild_id) = guild_id {
|
||||||
let guild_opt = guild_id.to_guild_cached(&ctx).await;
|
let guild_opt = guild_id.to_guild_cached(&ctx);
|
||||||
|
|
||||||
if let Some(guild) = guild_opt {
|
if let Some(guild) = guild_opt {
|
||||||
let channels = guild
|
let channels = guild
|
||||||
@ -333,7 +350,7 @@ WHERE
|
|||||||
member_id: U,
|
member_id: U,
|
||||||
payload: String,
|
payload: String,
|
||||||
) -> Result<(Self, ReminderAction), InteractionError> {
|
) -> Result<(Self, ReminderAction), InteractionError> {
|
||||||
let sections = payload.split(".").collect::<Vec<&str>>();
|
let sections = payload.split('.').collect::<Vec<&str>>();
|
||||||
|
|
||||||
if sections.len() != 3 {
|
if sections.len() != 3 {
|
||||||
Err(InteractionError::InvalidFormat)
|
Err(InteractionError::InvalidFormat)
|
||||||
@ -397,111 +414,3 @@ DELETE FROM reminders WHERE id = ?
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum InteractionError {
|
|
||||||
InvalidFormat,
|
|
||||||
InvalidBase64,
|
|
||||||
InvalidSize,
|
|
||||||
NoReminder,
|
|
||||||
SignatureMismatch,
|
|
||||||
InvalidAction,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ToString for InteractionError {
|
|
||||||
fn to_string(&self) -> String {
|
|
||||||
match self {
|
|
||||||
InteractionError::InvalidFormat => {
|
|
||||||
String::from("The interaction data was improperly formatted")
|
|
||||||
}
|
|
||||||
InteractionError::InvalidBase64 => String::from("The interaction data was invalid"),
|
|
||||||
InteractionError::InvalidSize => String::from("The interaction data was invalid"),
|
|
||||||
InteractionError::NoReminder => String::from("Reminder could not be found"),
|
|
||||||
InteractionError::SignatureMismatch => {
|
|
||||||
String::from("Only the user who did the command can use interactions")
|
|
||||||
}
|
|
||||||
InteractionError::InvalidAction => String::from("The action was invalid"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
|
||||||
pub enum ReminderAction {
|
|
||||||
Delete,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ToString for ReminderAction {
|
|
||||||
fn to_string(&self) -> String {
|
|
||||||
match self {
|
|
||||||
Self::Delete => String::from("del"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TryFrom<&str> for ReminderAction {
|
|
||||||
type Error = ();
|
|
||||||
|
|
||||||
fn try_from(value: &str) -> Result<Self, Self::Error> {
|
|
||||||
match value {
|
|
||||||
"del" => Ok(Self::Delete),
|
|
||||||
|
|
||||||
_ => Err(()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enum TimeDisplayType {
|
|
||||||
Absolute,
|
|
||||||
Relative,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct LookFlags {
|
|
||||||
pub limit: u16,
|
|
||||||
pub show_disabled: bool,
|
|
||||||
pub channel_id: Option<ChannelId>,
|
|
||||||
time_display: TimeDisplayType,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for LookFlags {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
limit: u16::MAX,
|
|
||||||
show_disabled: true,
|
|
||||||
channel_id: None,
|
|
||||||
time_display: TimeDisplayType::Relative,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl LookFlags {
|
|
||||||
pub fn from_string(args: &str) -> Self {
|
|
||||||
let mut new_flags: Self = Default::default();
|
|
||||||
|
|
||||||
for arg in args.split(' ') {
|
|
||||||
match arg {
|
|
||||||
"enabled" => {
|
|
||||||
new_flags.show_disabled = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
"time" => {
|
|
||||||
new_flags.time_display = TimeDisplayType::Absolute;
|
|
||||||
}
|
|
||||||
|
|
||||||
param => {
|
|
||||||
if let Ok(val) = param.parse::<u16>() {
|
|
||||||
new_flags.limit = val;
|
|
||||||
} else if let Some(channel) = REGEX_CHANNEL
|
|
||||||
.captures(&arg)
|
|
||||||
.map(|cap| cap.get(1))
|
|
||||||
.flatten()
|
|
||||||
.map(|c| c.as_str().parse::<u64>().unwrap())
|
|
||||||
{
|
|
||||||
new_flags.channel_id = Some(ChannelId(channel));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
new_flags
|
|
||||||
}
|
|
||||||
}
|
|
@ -26,11 +26,13 @@ impl Display for InvalidTime {
|
|||||||
|
|
||||||
impl std::error::Error for InvalidTime {}
|
impl std::error::Error for InvalidTime {}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
enum ParseType {
|
enum ParseType {
|
||||||
Explicit,
|
Explicit,
|
||||||
Displacement,
|
Displacement,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct TimeParser {
|
pub struct TimeParser {
|
||||||
timezone: Tz,
|
timezone: Tz,
|
||||||
inverted: bool,
|
inverted: bool,
|
||||||
|
Loading…
Reference in New Issue
Block a user