restructured all the reminder creation stuff into builders
This commit is contained in:
		
							
								
								
									
										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) => {
 | 
					                            let user_data = ctx.user_data(&msg.author).await.unwrap();
 | 
				
			||||||
                                        ok_locations.push(scope);
 | 
					 | 
				
			||||||
                                        ok_reminders.push(id);
 | 
					 | 
				
			||||||
                                    }
 | 
					 | 
				
			||||||
                                }
 | 
					 | 
				
			||||||
                            }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
                            let success_part = match ok_locations.len() {
 | 
					                            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}",
 | 
					 | 
				
			||||||
                                        &format!("<t:{}:R>", time_parser.timestamp().unwrap()),
 | 
					 | 
				
			||||||
                                    ),
 | 
					 | 
				
			||||||
                                n => lm
 | 
					 | 
				
			||||||
                                    .get(&language, "remind/success_bulk")
 | 
					 | 
				
			||||||
                                    .replace("{number}", &n.to_string())
 | 
					 | 
				
			||||||
                                    .replace(
 | 
					 | 
				
			||||||
                                        "{location}",
 | 
					 | 
				
			||||||
                                        &ok_locations
 | 
					 | 
				
			||||||
                                        .iter()
 | 
					                                        .iter()
 | 
				
			||||||
                                        .map(|l| l.mention())
 | 
					                                        .map(|l| l.mention())
 | 
				
			||||||
                                        .collect::<Vec<String>>()
 | 
					                                        .collect::<Vec<String>>()
 | 
				
			||||||
                                        .join(", "),
 | 
					                                        .join(", "),
 | 
				
			||||||
                                    )
 | 
					                                    offset = time_parser.timestamp().unwrap()
 | 
				
			||||||
                                    .replace(
 | 
					 | 
				
			||||||
                                        "{offset}",
 | 
					 | 
				
			||||||
                                        &format!("<t:{}:R>", time_parser.timestamp().unwrap()),
 | 
					 | 
				
			||||||
                                ),
 | 
					                                ),
 | 
				
			||||||
                            };
 | 
					                            };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                            let error_part = format!(
 | 
					                            let error_part = match errors.len() {
 | 
				
			||||||
                                "{}\n{}",
 | 
					 | 
				
			||||||
                                match err_locations.len() {
 | 
					 | 
				
			||||||
                                0 => "".to_string(),
 | 
					                                0 => "".to_string(),
 | 
				
			||||||
                                    1 => lm
 | 
					                                n => format!(
 | 
				
			||||||
                                        .get(&language, "remind/issue")
 | 
					                                    "{n} reminder{s} failed to set:\n{errors}",
 | 
				
			||||||
                                        .replace("{location}", &err_locations[0].mention()),
 | 
					                                    s = if n > 1 { "s" } else { "" },
 | 
				
			||||||
                                    n => lm
 | 
					                                    n = n,
 | 
				
			||||||
                                        .get(&language, "remind/issue_bulk")
 | 
					                                    errors = errors
 | 
				
			||||||
                                        .replace("{number}", &n.to_string())
 | 
					 | 
				
			||||||
                                        .replace(
 | 
					 | 
				
			||||||
                                            "{location}",
 | 
					 | 
				
			||||||
                                            &err_locations
 | 
					 | 
				
			||||||
                                        .iter()
 | 
					                                        .iter()
 | 
				
			||||||
                                                .map(|l| l.mention())
 | 
					                                        .map(|e| e.display(false))
 | 
				
			||||||
                                                .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>>()
 | 
					                                        .collect::<Vec<String>>()
 | 
				
			||||||
                                        .join("\n")
 | 
					                                        .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,11 +644,8 @@ 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"),
 | 
					 | 
				
			||||||
                                        )
 | 
					 | 
				
			||||||
                                        .description(lm.get(&language, content_error.to_response()))
 | 
					 | 
				
			||||||
                                            .color(*THEME_COLOR)
 | 
					                                            .color(*THEME_COLOR)
 | 
				
			||||||
                                    })
 | 
					                                    })
 | 
				
			||||||
                                })
 | 
					                                })
 | 
				
			||||||
@@ -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
 | 
					 | 
				
			||||||
                                .get(&user_data.language, "remind/success_bulk")
 | 
					 | 
				
			||||||
                                .replace("{number}", &n.to_string())
 | 
					 | 
				
			||||||
                                .replace(
 | 
					 | 
				
			||||||
                                    "{location}",
 | 
					 | 
				
			||||||
                                    &ok_locations
 | 
					 | 
				
			||||||
                                    .iter()
 | 
					                                    .iter()
 | 
				
			||||||
                                    .map(|l| l.mention())
 | 
					                                    .map(|l| l.mention())
 | 
				
			||||||
                                    .collect::<Vec<String>>()
 | 
					                                    .collect::<Vec<String>>()
 | 
				
			||||||
                                    .join(", "),
 | 
					                                    .join(", "),
 | 
				
			||||||
                                )
 | 
					                                offset = timestamp
 | 
				
			||||||
                                .replace("{offset}", &format!("<t:{}:R>", timestamp)),
 | 
					                            ),
 | 
				
			||||||
                        };
 | 
					                        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                        let error_part = format!(
 | 
					                        let error_part = match errors.len() {
 | 
				
			||||||
                            "{}\n{}",
 | 
					 | 
				
			||||||
                            match err_locations.len() {
 | 
					 | 
				
			||||||
                            0 => "".to_string(),
 | 
					                            0 => "".to_string(),
 | 
				
			||||||
                                1 => lm
 | 
					                            n => format!(
 | 
				
			||||||
                                    .get(&user_data.language, "remind/issue")
 | 
					                                "{n} reminder{s} failed to set:\n{errors}",
 | 
				
			||||||
                                    .replace("{location}", &err_locations[0].mention()),
 | 
					                                s = if n > 1 { "s" } else { "" },
 | 
				
			||||||
                                n => lm
 | 
					                                n = n,
 | 
				
			||||||
                                    .get(&user_data.language, "remind/issue_bulk")
 | 
					                                errors = errors
 | 
				
			||||||
                                    .replace("{number}", &n.to_string())
 | 
					 | 
				
			||||||
                                    .replace(
 | 
					 | 
				
			||||||
                                        "{location}",
 | 
					 | 
				
			||||||
                                        &err_locations
 | 
					 | 
				
			||||||
                                    .iter()
 | 
					                                    .iter()
 | 
				
			||||||
                                            .map(|l| l.mention())
 | 
					                                    .map(|e| e.display(true))
 | 
				
			||||||
                                            .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>>()
 | 
					                                    .collect::<Vec<String>>()
 | 
				
			||||||
                                    .join("\n")
 | 
					                                    .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,13 +817,8 @@ 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"),
 | 
					 | 
				
			||||||
                                    )
 | 
					 | 
				
			||||||
                                    .description(
 | 
					 | 
				
			||||||
                                        lm.get(&user_data.language, content_error.to_response()),
 | 
					 | 
				
			||||||
                                    )
 | 
					 | 
				
			||||||
                                        .color(*THEME_COLOR)
 | 
					                                        .color(*THEME_COLOR)
 | 
				
			||||||
                                })
 | 
					                                })
 | 
				
			||||||
                            })
 | 
					                            })
 | 
				
			||||||
@@ -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;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										67
									
								
								src/main.rs
									
									
									
									
									
								
							
							
						
						
									
										67
									
								
								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,23 +270,23 @@ 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)
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    if data.custom_id.starts_with("timezone:") {
 | 
					 | 
				
			||||||
                        let mut user_data = UserData::from_user(&member.user, &ctx, &pool)
 | 
					 | 
				
			||||||
                        .await
 | 
					                        .await
 | 
				
			||||||
                        .unwrap();
 | 
					                        .unwrap();
 | 
				
			||||||
                        let new_timezone = data.custom_id.replace("timezone:", "").parse::<Tz>();
 | 
					                    let new_timezone = component
 | 
				
			||||||
 | 
					                        .data
 | 
				
			||||||
 | 
					                        .custom_id
 | 
				
			||||||
 | 
					                        .replace("timezone:", "")
 | 
				
			||||||
 | 
					                        .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(
 | 
				
			||||||
@@ -314,17 +316,17 @@ DELETE FROM guilds WHERE guild = ?
 | 
				
			|||||||
                                })
 | 
					                                })
 | 
				
			||||||
                        }).await;
 | 
					                        }).await;
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                    } else if data.custom_id.starts_with("lang:") {
 | 
					                } else if component.data.custom_id.starts_with("lang:") {
 | 
				
			||||||
                        let mut user_data = UserData::from_user(&member.user, &ctx, &pool)
 | 
					                    let mut user_data = UserData::from_user(&component.user, &ctx, &pool)
 | 
				
			||||||
                        .await
 | 
					                        .await
 | 
				
			||||||
                        .unwrap();
 | 
					                        .unwrap();
 | 
				
			||||||
                        let lang_code = data.custom_id.replace("lang:", "");
 | 
					                    let lang_code = component.data.custom_id.replace("lang:", "");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    if let Some(lang) = lm.get_language(&lang_code) {
 | 
					                    if let Some(lang) = lm.get_language(&lang_code) {
 | 
				
			||||||
                        user_data.language = lang.to_string();
 | 
					                        user_data.language = lang.to_string();
 | 
				
			||||||
                        user_data.commit_changes(&pool).await;
 | 
					                        user_data.commit_changes(&pool).await;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                            let _ = interaction
 | 
					                        let _ = component
 | 
				
			||||||
                            .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| {
 | 
				
			||||||
@@ -343,7 +345,12 @@ DELETE FROM guilds WHERE guild = ?
 | 
				
			|||||||
                            .await;
 | 
					                            .await;
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                } else {
 | 
					                } else {
 | 
				
			||||||
                        match Reminder::from_interaction(&ctx, member.user.id, data.custom_id).await
 | 
					                    match Reminder::from_interaction(
 | 
				
			||||||
 | 
					                        &ctx,
 | 
				
			||||||
 | 
					                        component.user.id,
 | 
				
			||||||
 | 
					                        component.data.custom_id.clone(),
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
 | 
					                    .await
 | 
				
			||||||
                    {
 | 
					                    {
 | 
				
			||||||
                        Ok((reminder, action)) => {
 | 
					                        Ok((reminder, action)) => {
 | 
				
			||||||
                            let response = match action {
 | 
					                            let response = match action {
 | 
				
			||||||
@@ -353,7 +360,7 @@ DELETE FROM guilds WHERE guild = ?
 | 
				
			|||||||
                                }
 | 
					                                }
 | 
				
			||||||
                            };
 | 
					                            };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                                let _ = interaction
 | 
					                            let _ = component
 | 
				
			||||||
                                .create_interaction_response(&ctx, |r| {
 | 
					                                .create_interaction_response(&ctx, |r| {
 | 
				
			||||||
                                    r.kind(InteractionResponseType::ChannelMessageWithSource)
 | 
					                                    r.kind(InteractionResponseType::ChannelMessageWithSource)
 | 
				
			||||||
                                        .interaction_response_data(|d| d
 | 
					                                        .interaction_response_data(|d| d
 | 
				
			||||||
@@ -365,7 +372,7 @@ DELETE FROM guilds WHERE guild = ?
 | 
				
			|||||||
                        }
 | 
					                        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                        Err(ie) => {
 | 
					                        Err(ie) => {
 | 
				
			||||||
                                let _ = interaction
 | 
					                            let _ = component
 | 
				
			||||||
                                .create_interaction_response(&ctx, |r| {
 | 
					                                .create_interaction_response(&ctx, |r| {
 | 
				
			||||||
                                    r.kind(InteractionResponseType::ChannelMessageWithSource)
 | 
					                                    r.kind(InteractionResponseType::ChannelMessageWithSource)
 | 
				
			||||||
                                        .interaction_response_data(|d| d
 | 
					                                        .interaction_response_data(|d| d
 | 
				
			||||||
@@ -378,7 +385,6 @@ DELETE FROM guilds WHERE guild = ?
 | 
				
			|||||||
                    }
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            _ => {}
 | 
					            _ => {}
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -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,
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user