From e4e9af2bb4414041b55717c28626aabe84e335d9 Mon Sep 17 00:00:00 2001 From: jude Date: Sat, 6 Jan 2024 19:48:17 +0000 Subject: [PATCH] Wip commit --- Cargo.lock | 202 +-------- Cargo.toml | 1 + postman/Cargo.toml | 2 +- postman/src/sender.rs | 165 +++----- src/commands/autocomplete.rs | 2 +- src/commands/command_macro/delete.rs | 2 +- src/commands/command_macro/list.rs | 45 +- src/commands/command_macro/migrate.rs | 8 +- src/commands/command_macro/record.rs | 71 ++-- src/commands/command_macro/run.rs | 17 +- src/commands/info_cmds.rs | 88 ++-- src/commands/moderation_cmds.rs | 95 ++--- src/commands/reminder_cmds.rs | 260 +++++------- src/commands/todo_cmds.rs | 137 +++--- src/component_models/mod.rs | 361 ++++++++-------- src/consts.rs | 7 +- src/event_handlers.rs | 91 ++-- src/hooks.rs | 85 ++-- src/main.rs | 24 +- src/models/channel_data.rs | 4 +- src/models/command_macro.rs | 6 +- src/models/guild_data.rs | 6 +- src/models/mod.rs | 19 +- src/models/reminder/builder.rs | 19 +- src/models/reminder/mod.rs | 392 +++++++++--------- src/models/user_data.rs | 12 +- src/utils.rs | 81 +--- web/Cargo.toml | 5 +- web/src/consts.rs | 7 +- web/src/lib.rs | 52 +-- .../routes/dashboard/api/guild/channels.rs | 4 +- web/src/routes/dashboard/api/guild/mod.rs | 15 +- .../routes/dashboard/api/guild/reminders.rs | 20 +- web/src/routes/dashboard/api/user/mod.rs | 4 +- web/src/routes/dashboard/export.rs | 12 +- web/src/routes/dashboard/mod.rs | 91 ++-- web/src/routes/login.rs | 5 +- 37 files changed, 1051 insertions(+), 1366 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 65ed4b3..fa147b7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -137,22 +137,6 @@ dependencies = [ "syn 2.0.42", ] -[[package]] -name = "async-tungstenite" -version = "0.17.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1b71b31561643aa8e7df3effe284fa83ab1a840e52294c5f4bd7bfd8b2becbb" -dependencies = [ - "futures-io", - "futures-util", - "log", - "pin-project-lite", - "tokio", - "tokio-rustls 0.23.4", - "tungstenite 0.17.3", - "webpki-roots 0.22.6", -] - [[package]] name = "atoi" version = "2.0.0" @@ -1877,15 +1861,6 @@ dependencies = [ "vcpkg", ] -[[package]] -name = "ordered-float" -version = "2.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68f19d67e5a2795c94e73e0bb1cc1a7edeb2e28efd39e2e1c9b7a40c1108b11c" -dependencies = [ - "num-traits", -] - [[package]] name = "overload" version = "0.1.1" @@ -2102,7 +2077,7 @@ dependencies = [ "parking_lot", "poise_macros", "regex", - "serenity 0.12.0", + "serenity", "tokio", "tracing", ] @@ -2142,7 +2117,7 @@ dependencies = [ "num-integer", "regex", "serde", - "serenity 0.11.7", + "serenity", "sqlx", "tokio", ] @@ -2346,6 +2321,7 @@ dependencies = [ "reminder_web", "reqwest", "rmp-serde", + "secrecy", "serde", "serde_json", "serde_repr", @@ -2369,8 +2345,9 @@ dependencies = [ "reqwest", "rocket", "rocket_dyn_templates", + "secrecy", "serde", - "serenity 0.11.7", + "serenity", "sqlx", ] @@ -2416,25 +2393,10 @@ dependencies = [ "wasm-bindgen-futures", "wasm-streams", "web-sys", - "webpki-roots 0.25.3", + "webpki-roots", "winreg", ] -[[package]] -name = "ring" -version = "0.16.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" -dependencies = [ - "cc", - "libc", - "once_cell", - "spin 0.5.2", - "untrusted 0.7.1", - "web-sys", - "winapi", -] - [[package]] name = "ring" version = "0.17.7" @@ -2445,7 +2407,7 @@ dependencies = [ "getrandom", "libc", "spin 0.9.8", - "untrusted 0.9.0", + "untrusted", "windows-sys 0.48.0", ] @@ -2604,18 +2566,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "rustls" -version = "0.20.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b80e3dec595989ea8510028f30c408a4630db12c9cbb8de34203b89d6577e99" -dependencies = [ - "log", - "ring 0.16.20", - "sct", - "webpki", -] - [[package]] name = "rustls" version = "0.21.10" @@ -2623,7 +2573,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9d5a6813c0759e4609cd494e8e725babae6a2ca7b62a5536a13daaec6fcb7ba" dependencies = [ "log", - "ring 0.17.7", + "ring", "rustls-webpki 0.101.7", "sct", ] @@ -2635,7 +2585,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fe6b63262c9fcac8659abfaa96cac103d28166d3ff3eaf8f412e19f3ae9e5a48" dependencies = [ "log", - "ring 0.17.7", + "ring", "rustls-pki-types", "rustls-webpki 0.102.0", "subtle", @@ -2673,8 +2623,8 @@ version = "0.101.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" dependencies = [ - "ring 0.17.7", - "untrusted 0.9.0", + "ring", + "untrusted", ] [[package]] @@ -2683,9 +2633,9 @@ version = "0.102.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de2635c8bc2b88d367767c5de8ea1d8db9af3f6219eba28442242d9ab81d1b89" dependencies = [ - "ring 0.17.7", + "ring", "rustls-pki-types", - "untrusted 0.9.0", + "untrusted", ] [[package]] @@ -2736,8 +2686,8 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" dependencies = [ - "ring 0.17.7", - "untrusted 0.9.0", + "ring", + "untrusted", ] [[package]] @@ -2791,16 +2741,6 @@ dependencies = [ "serde_derive", ] -[[package]] -name = "serde-value" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c" -dependencies = [ - "ordered-float", - "serde", -] - [[package]] name = "serde_derive" version = "1.0.193" @@ -2865,36 +2805,6 @@ dependencies = [ "serde", ] -[[package]] -name = "serenity" -version = "0.11.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a7a89cef23483fc9d4caf2df41e6d3928e18aada84c56abd237439d929622c6" -dependencies = [ - "async-trait", - "async-tungstenite", - "base64 0.21.5", - "bitflags 1.3.2", - "bytes", - "cfg-if", - "dashmap", - "flate2", - "futures", - "mime", - "mime_guess", - "parking_lot", - "percent-encoding", - "reqwest", - "serde", - "serde-value", - "serde_json", - "time", - "tokio", - "tracing", - "typemap_rev 0.1.5", - "url", -] - [[package]] name = "serenity" version = "0.12.0" @@ -2922,22 +2832,11 @@ dependencies = [ "tokio", "tokio-tungstenite", "tracing", - "typemap_rev 0.3.0", + "typemap_rev", "typesize", "url", ] -[[package]] -name = "sha-1" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5058ada175748e33390e40e872bd0fe59a19f265d0158daa551c5a88a76009c" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", -] - [[package]] name = "sha1" version = "0.10.6" @@ -3135,7 +3034,7 @@ dependencies = [ "tokio-stream", "tracing", "url", - "webpki-roots 0.25.3", + "webpki-roots", ] [[package]] @@ -3536,17 +3435,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "tokio-rustls" -version = "0.23.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" -dependencies = [ - "rustls 0.20.9", - "tokio", - "webpki", -] - [[package]] name = "tokio-rustls" version = "0.24.1" @@ -3590,8 +3478,8 @@ dependencies = [ "rustls 0.21.10", "tokio", "tokio-rustls 0.24.1", - "tungstenite 0.20.1", - "webpki-roots 0.25.3", + "tungstenite", + "webpki-roots", ] [[package]] @@ -3722,27 +3610,6 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" -[[package]] -name = "tungstenite" -version = "0.17.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e27992fd6a8c29ee7eef28fc78349aa244134e10ad447ce3b9f0ac0ed0fa4ce0" -dependencies = [ - "base64 0.13.1", - "byteorder", - "bytes", - "http 0.2.11", - "httparse", - "log", - "rand", - "rustls 0.20.9", - "sha-1", - "thiserror", - "url", - "utf-8", - "webpki", -] - [[package]] name = "tungstenite" version = "0.20.1" @@ -3763,12 +3630,6 @@ dependencies = [ "utf-8", ] -[[package]] -name = "typemap_rev" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed5b74f0a24b5454580a79abb6994393b09adf0ab8070f15827cb666255de155" - [[package]] name = "typemap_rev" version = "0.3.0" @@ -3943,12 +3804,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "untrusted" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" - [[package]] name = "untrusted" version = "0.9.0" @@ -4111,25 +3966,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "webpki" -version = "0.22.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed63aea5ce73d0ff405984102c42de94fc55a6b75765d621c65262469b3c9b53" -dependencies = [ - "ring 0.17.7", - "untrusted 0.9.0", -] - -[[package]] -name = "webpki-roots" -version = "0.22.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c71e40d7d2c34a5106301fb632274ca37242cd0c9d3e64dbece371a40a2d87" -dependencies = [ - "webpki", -] - [[package]] name = "webpki-roots" version = "0.25.3" diff --git a/Cargo.toml b/Cargo.toml index 382c986..4e13ab8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,6 +27,7 @@ rand = "0.8" levenshtein = "1.0" sqlx = { version = "0.7", features = ["runtime-tokio-rustls", "macros", "mysql", "bigdecimal", "chrono", "migrate"]} base64 = "0.21.0" +secrecy = "0.8.0" [dependencies.postman] path = "postman" diff --git a/postman/Cargo.toml b/postman/Cargo.toml index 9748985..344a21c 100644 --- a/postman/Cargo.toml +++ b/postman/Cargo.toml @@ -13,4 +13,4 @@ lazy_static = "1.4" num-integer = "0.1" serde = "1.0" sqlx = { version = "0.7", features = ["runtime-tokio-rustls", "macros", "mysql", "bigdecimal", "chrono", "json"]} -serenity = { version = "0.11", default-features = false, features = ["builder", "cache", "client", "gateway", "http", "model", "utils", "rustls_backend"] } +serenity = { version = "0.12", default-features = false, features = ["builder", "cache", "client", "gateway", "http", "model", "utils", "rustls_backend"] } diff --git a/postman/src/sender.rs b/postman/src/sender.rs index 200e528..2cdad51 100644 --- a/postman/src/sender.rs +++ b/postman/src/sender.rs @@ -8,11 +8,12 @@ use num_integer::Integer; use regex::{Captures, Regex}; use serde::Deserialize; use serenity::{ - builder::CreateEmbed, + all::{CreateAttachment, CreateEmbedFooter}, + builder::{CreateEmbed, CreateEmbedAuthor, CreateMessage, ExecuteWebhook}, http::{CacheHttp, Http, HttpError}, model::{ - channel::{Channel, Embed as SerenityEmbed}, - id::ChannelId, + channel::Channel, + id::{ChannelId, MessageId}, webhook::Webhook, }, Error, Result, @@ -194,43 +195,36 @@ impl Embed { impl Into for Embed { fn into(self) -> CreateEmbed { - let mut c = CreateEmbed::default(); + let mut author = CreateEmbedAuthor::new(&self.author); + if let Some(author_icon) = &self.author_url { + author = author.icon_url(author_icon); + } - c.title(&self.title) + let mut footer = CreateEmbedFooter::new(&self.footer); + if let Some(footer_icon) = &self.footer_url { + footer = footer.icon_url(footer_icon); + } + + let mut embed = CreateEmbed::default() + .title(&self.title) .description(&self.description) .color(self.color) - .author(|a| { - a.name(&self.author); - - if let Some(author_icon) = &self.author_url { - a.icon_url(author_icon); - } - - a - }) - .footer(|f| { - f.text(&self.footer); - - if let Some(footer_icon) = &self.footer_url { - f.icon_url(footer_icon); - } - - f - }); + .author(author) + .footer(footer); for field in &self.fields.0 { - c.field(&field.title, &field.value, field.inline); + embed = embed.field(&field.title, &field.value, field.inline); } if let Some(image_url) = &self.image_url { - c.image(image_url); + embed = embed.image(image_url); } if let Some(thumbnail_url) = &self.thumbnail_url { - c.thumbnail(thumbnail_url); + embed = embed.thumbnail(thumbnail_url); } - c + embed } } @@ -465,8 +459,8 @@ WHERE .expect(&format!("Could not delete Reminder {}", self.id)); } - async fn pin_message>(&self, message_id: M, http: impl AsRef) { - let _ = http.as_ref().pin_message(self.channel_id, message_id.into(), None).await; + async fn pin_message>(&self, message_id: M, http: impl AsRef) { + let _ = http.as_ref().pin_message(self.channel_id.into(), message_id.into(), None).await; } pub async fn send( @@ -479,28 +473,24 @@ WHERE reminder: &Reminder, embed: Option, ) -> Result<()> { - let channel = ChannelId(reminder.channel_id).to_channel(&cache_http).await; + let channel = ChannelId::new(reminder.channel_id).to_channel(&cache_http).await; + + let mut message = CreateMessage::new().content(&reminder.content).tts(reminder.tts); + + if let (Some(attachment), Some(name)) = + (&reminder.attachment, &reminder.attachment_name) + { + message = + message.add_file(CreateAttachment::bytes(attachment as &[u8], name.as_str())); + } + + if let Some(embed) = embed { + message = message.embed(embed); + } match channel { Ok(Channel::Guild(channel)) => { - match channel - .send_message(&cache_http, |m| { - m.content(&reminder.content).tts(reminder.tts); - - if let (Some(attachment), Some(name)) = - (&reminder.attachment, &reminder.attachment_name) - { - m.add_file((attachment as &[u8], name.as_str())); - } - - if let Some(embed) = embed { - m.set_embed(embed); - } - - m - }) - .await - { + match channel.send_message(&cache_http, message).await { Ok(m) => { if reminder.pin { reminder.pin_message(m.id, cache_http.http()).await; @@ -512,24 +502,7 @@ WHERE } } Ok(Channel::Private(channel)) => { - match channel - .send_message(&cache_http.http(), |m| { - m.content(&reminder.content).tts(reminder.tts); - - if let (Some(attachment), Some(name)) = - (&reminder.attachment, &reminder.attachment_name) - { - m.add_file((attachment as &[u8], name.as_str())); - } - - if let Some(embed) = embed { - m.set_embed(embed); - } - - m - }) - .await - { + match channel.send_message(&cache_http.http(), message).await { Ok(m) => { if reminder.pin { reminder.pin_message(m.id, cache_http.http()).await; @@ -551,35 +524,31 @@ WHERE webhook: Webhook, embed: Option, ) -> Result<()> { + let mut builder = ExecuteWebhook::new().content(&reminder.content).tts(reminder.tts); + + if let Some(username) = &reminder.username { + if !username.is_empty() { + builder = builder.username(username); + } + } + + if let Some(avatar) = &reminder.avatar { + builder = builder.avatar_url(avatar); + } + + if let (Some(attachment), Some(name)) = + (&reminder.attachment, &reminder.attachment_name) + { + builder = + builder.add_file(CreateAttachment::bytes(attachment as &[u8], name.as_str())); + } + + if let Some(embed) = embed { + builder = builder.embeds(vec![embed]); + } + match webhook - .execute(&cache_http.http(), reminder.pin || reminder.restartable, |w| { - w.content(&reminder.content).tts(reminder.tts); - - if let Some(username) = &reminder.username { - if !username.is_empty() { - w.username(username); - } - } - - if let Some(avatar) = &reminder.avatar { - w.avatar_url(avatar); - } - - if let (Some(attachment), Some(name)) = - (&reminder.attachment, &reminder.attachment_name) - { - w.add_file((attachment as &[u8], name.as_str())); - } - - if let Some(embed) = embed { - w.embeds(vec![SerenityEmbed::fake(|c| { - *c = embed; - c - })]); - } - - w - }) + .execute(&cache_http.http(), reminder.pin || reminder.restartable, builder) .await { Ok(m) => { @@ -613,8 +582,10 @@ WHERE let result = if let (Some(webhook_id), Some(webhook_token)) = (self.webhook_id, &self.webhook_token) { - let webhook_res = - cache_http.http().get_webhook_with_token(webhook_id, webhook_token).await; + let webhook_res = cache_http + .http() + .get_webhook_with_token(webhook_id.into(), webhook_token) + .await; if let Ok(webhook) = webhook_res { send_to_webhook(cache_http, &self, webhook, embed).await @@ -630,7 +601,7 @@ WHERE if let Err(e) = result { if let Error::Http(error) = e { - if let HttpError::UnsuccessfulRequest(http_error) = *error { + if let HttpError::UnsuccessfulRequest(http_error) = error { match http_error.error.code { 10003 => { self.log_error( diff --git a/src/commands/autocomplete.rs b/src/commands/autocomplete.rs index 9d9698f..721d781 100644 --- a/src/commands/autocomplete.rs +++ b/src/commands/autocomplete.rs @@ -26,7 +26,7 @@ FROM macro WHERE guild_id = (SELECT id FROM guilds WHERE guild = ?) AND name LIKE CONCAT(?, '%')", - ctx.guild_id().unwrap().0, + ctx.guild_id().unwrap().get(), partial, ) .fetch_all(&ctx.data().database) diff --git a/src/commands/command_macro/delete.rs b/src/commands/command_macro/delete.rs index ebc9acc..d54bf2a 100644 --- a/src/commands/command_macro/delete.rs +++ b/src/commands/command_macro/delete.rs @@ -18,7 +18,7 @@ pub async fn delete_macro( match sqlx::query!( " SELECT id FROM macro WHERE guild_id = (SELECT id FROM guilds WHERE guild = ?) AND name = ?", - ctx.guild_id().unwrap().0, + ctx.guild_id().unwrap().get(), name ) .fetch_one(&ctx.data().database) diff --git a/src/commands/command_macro/list.rs b/src/commands/command_macro/list.rs index beb7b43..97c5dfb 100644 --- a/src/commands/command_macro/list.rs +++ b/src/commands/command_macro/list.rs @@ -1,4 +1,7 @@ -use poise::CreateReply; +use poise::{ + serenity_prelude::{CreateEmbed, CreateEmbedFooter}, + CreateReply, +}; use crate::{ component_models::pager::{MacroPager, Pager}, @@ -20,11 +23,7 @@ pub async fn list_macro(ctx: Context<'_>) -> Result<(), Error> { let resp = show_macro_page(¯os, 0); - ctx.send(|m| { - *m = resp; - m - }) - .await?; + ctx.send(resp).await?; Ok(()) } @@ -37,15 +36,12 @@ pub fn show_macro_page(macros: &[CommandMacro], page: usize) -> Crea let pager = MacroPager::new(page); if macros.is_empty() { - let mut reply = CreateReply::default(); - - reply.embed(|e| { - e.title("Macros") + return CreateReply::default().embed( + CreateEmbed::new() + .title("Macros") .description("No Macros Set Up. Use `/macro record` to get started.") - .color(*THEME_COLOR) - }); - - return reply; + .color(*THEME_COLOR), + ); } let pages = max_macro_page(macros); @@ -70,20 +66,13 @@ pub fn show_macro_page(macros: &[CommandMacro], page: usize) -> Crea } }); - let mut reply = CreateReply::default(); - - reply - .embed(|e| { - e.title("Macros") + CreateReply::default() + .embed( + CreateEmbed::new() + .title("Macros") .fields(fields) - .footer(|f| f.text(format!("Page {} of {}", page + 1, pages))) .color(*THEME_COLOR) - }) - .components(|comp| { - pager.create_button_row(pages, comp); - - comp - }); - - reply + .footer(CreateEmbedFooter::new(format!("Page {} of {}", page + 1, pages))), + ) + .components(vec![pager.create_button_row(pages)]) } diff --git a/src/commands/command_macro/migrate.rs b/src/commands/command_macro/migrate.rs index 984a1bf..5001551 100644 --- a/src/commands/command_macro/migrate.rs +++ b/src/commands/command_macro/migrate.rs @@ -1,5 +1,5 @@ use lazy_regex::regex; -use poise::serenity_prelude::CommandOptionType; +use poise::{serenity_prelude::CommandOptionType, CreateReply}; use regex::Captures; use serde_json::{json, Value}; @@ -25,7 +25,7 @@ pub async fn migrate_macro(ctx: Context<'_>) -> Result<(), Error> { let aliases = sqlx::query_as!( Alias, "SELECT name, command FROM command_aliases WHERE guild_id = (SELECT id FROM guilds WHERE guild = ?)", - guild_id.0 + guild_id.get() ) .fetch_all(&mut *transaction) .await?; @@ -37,7 +37,7 @@ pub async fn migrate_macro(ctx: Context<'_>) -> Result<(), Error> { Some(cmd_macro) => { sqlx::query!( "INSERT INTO macro (guild_id, name, description, commands) VALUES ((SELECT id FROM guilds WHERE guild = ?), ?, ?, ?)", - cmd_macro.guild_id.0, + cmd_macro.guild_id.get(), cmd_macro.name, cmd_macro.description, cmd_macro.commands @@ -54,7 +54,7 @@ pub async fn migrate_macro(ctx: Context<'_>) -> Result<(), Error> { transaction.commit().await?; - ctx.send(|b| b.content(format!("Added {} macros.", added_aliases))).await?; + ctx.send(CreateReply::default().content(format!("Added {} macros.", added_aliases))).await?; Ok(()) } diff --git a/src/commands/command_macro/record.rs b/src/commands/command_macro/record.rs index 4ff5f3f..e2463f5 100644 --- a/src/commands/command_macro/record.rs +++ b/src/commands/command_macro/record.rs @@ -1,5 +1,7 @@ use std::collections::hash_map::Entry; +use poise::{serenity_prelude::CreateEmbed, CreateReply}; + use crate::{consts::THEME_COLOR, models::command_macro::CommandMacro, Context, Error}; /// Start recording up to 5 commands to replay @@ -32,23 +34,24 @@ pub async fn record_macro( let row = sqlx::query!( " SELECT 1 as _e FROM macro WHERE guild_id = (SELECT id FROM guilds WHERE guild = ?) AND name = ?", - guild_id.0, + guild_id.get(), name ) .fetch_one(&ctx.data().database) .await; if row.is_ok() { - ctx.send(|m| { - m.ephemeral(true).embed(|e| { - e.title("Unique Name Required") + ctx.send( + CreateReply::default().ephemeral(true).embed( + CreateEmbed::new() + .title("Unique Name Required") .description( "A macro already exists under this name. Please select a unique name for your macro.", ) - .color(*THEME_COLOR) - }) - }) + .color(*THEME_COLOR), + ), + ) .await?; } else { let okay = { @@ -63,28 +66,30 @@ Please select a unique name for your macro.", }; if okay { - ctx.send(|m| { - m.ephemeral(true).embed(|e| { - e.title("Macro Recording Started") + ctx.send( + CreateReply::default().ephemeral(true).embed( + CreateEmbed::new() + .title("Macro Recording Started") .description( "Run up to 5 commands, or type `/macro finish` to stop at any point. Any commands ran as part of recording will be inconsequential", ) - .color(*THEME_COLOR) - }) - }) + .color(*THEME_COLOR), + ), + ) .await?; } else { - ctx.send(|m| { - m.ephemeral(true).embed(|e| { - e.title("Macro Already Recording") + ctx.send( + CreateReply::default().ephemeral(true).embed( + CreateEmbed::new() + .title("Macro Already Recording") .description( "You are already recording a macro in this server. Please use `/macro finish` to end this recording before starting another.", ) - .color(*THEME_COLOR) - }) - }) + .color(*THEME_COLOR), + ), + ) .await?; } } @@ -108,13 +113,14 @@ pub async fn finish_macro(ctx: Context<'_>) -> Result<(), Error> { let contained = lock.get(&key); if contained.map_or(true, |cmacro| cmacro.commands.is_empty()) { - ctx.send(|m| { - m.embed(|e| { - e.title("No Macro Recorded") + ctx.send( + CreateReply::default().embed( + CreateEmbed::new() + .title("No Macro Recorded") .description("Use `/macro record` to start recording a macro") - .color(*THEME_COLOR) - }) - }) + .color(*THEME_COLOR), + ), + ) .await?; } else { let command_macro = contained.unwrap(); @@ -122,7 +128,7 @@ pub async fn finish_macro(ctx: Context<'_>) -> Result<(), Error> { sqlx::query!( "INSERT INTO macro (guild_id, name, description, commands) VALUES ((SELECT id FROM guilds WHERE guild = ?), ?, ?, ?)", - command_macro.guild_id.0, + command_macro.guild_id.get(), command_macro.name, command_macro.description, json @@ -131,13 +137,14 @@ pub async fn finish_macro(ctx: Context<'_>) -> Result<(), Error> { .await .unwrap(); - ctx.send(|m| { - m.embed(|e| { - e.title("Macro Recorded") + ctx.send( + CreateReply::default().embed( + CreateEmbed::new() + .title("Macro Recorded") .description("Use `/macro run` to execute the macro") - .color(*THEME_COLOR) - }) - }) + .color(*THEME_COLOR), + ), + ) .await?; } } diff --git a/src/commands/command_macro/run.rs b/src/commands/command_macro/run.rs index e212f63..7b6d8ff 100644 --- a/src/commands/command_macro/run.rs +++ b/src/commands/command_macro/run.rs @@ -1,3 +1,8 @@ +use poise::{ + serenity_prelude::{CommandOption, CreateEmbed}, + CreateReply, +}; + use super::super::autocomplete::macro_name_autocomplete; use crate::{models::command_macro::guild_command_macro, Context, Data, Error, THEME_COLOR}; @@ -18,15 +23,15 @@ pub async fn run_macro( match guild_command_macro(&Context::Application(ctx), &name).await { Some(command_macro) => { Context::Application(ctx) - .send(|b| { - b.embed(|e| { - e.title("Running Macro").color(*THEME_COLOR).description(format!( + .send(CreateReply::default().embed( + CreateEmbed::new().title("Running Macro").color(*THEME_COLOR).description( + format!( "Running macro {} ({} commands)", command_macro.name, command_macro.commands.len() - )) - }) - }) + ), + ), + )) .await?; for command in command_macro.commands { diff --git a/src/commands/info_cmds.rs b/src/commands/info_cmds.rs index 05a55be..b5bb369 100644 --- a/src/commands/info_cmds.rs +++ b/src/commands/info_cmds.rs @@ -1,22 +1,22 @@ use chrono::offset::Utc; -use poise::{serenity_prelude as serenity, serenity_prelude::Mentionable}; +use poise::{ + serenity_prelude as serenity, + serenity_prelude::{CreateEmbed, CreateEmbedFooter, Mentionable}, + CreateReply, +}; use crate::{models::CtxData, Context, Error, THEME_COLOR}; -fn footer( - ctx: Context<'_>, -) -> impl FnOnce(&mut serenity::CreateEmbedFooter) -> &mut serenity::CreateEmbedFooter { +fn footer(ctx: Context<'_>) -> CreateEmbedFooter { let shard_count = ctx.serenity_context().cache.shard_count(); let shard = ctx.serenity_context().shard_id; - move |f| { - f.text(format!( - "{}\nshard {} of {}", - concat!(env!("CARGO_PKG_NAME"), " ver ", env!("CARGO_PKG_VERSION")), - shard, - shard_count, - )) - } + CreateEmbedFooter::new(format!( + "{}\nshard {} of {}", + concat!(env!("CARGO_PKG_NAME"), " ver ", env!("CARGO_PKG_VERSION")), + shard, + shard_count, + )) } /// Get an overview of bot commands @@ -24,9 +24,10 @@ fn footer( pub async fn help(ctx: Context<'_>) -> Result<(), Error> { let footer = footer(ctx); - ctx.send(|m| { - m.ephemeral(true).embed(|e| { - e.title("Help") + ctx.send( + CreateReply::default().ephemeral(true).embed( + CreateEmbed::new() + .title("Help") .color(*THEME_COLOR) .description( "__Info Commands__ @@ -55,9 +56,9 @@ __Advanced Commands__ `/macro` - Record and replay command sequences ", ) - .footer(footer) - }) - }) + .footer(footer), + ), + ) .await?; Ok(()) @@ -69,9 +70,10 @@ pub async fn info(ctx: Context<'_>) -> Result<(), Error> { let footer = footer(ctx); let _ = ctx - .send(|m| { - m.ephemeral(true).embed(|e| { - e.title("Info") + .send( + CreateReply::default().ephemeral(true).embed( + CreateEmbed::new() + .title("Info") .description( "Help: `/help` @@ -84,9 +86,9 @@ Invite the bot: https://invite.reminder-bot.com/ Use our dashboard: https://reminder-bot.com/", ) .footer(footer) - .color(*THEME_COLOR) - }) - }) + .color(*THEME_COLOR), + ), + ) .await; Ok(()) @@ -97,8 +99,7 @@ Use our dashboard: https://reminder-bot.com/", pub async fn donate(ctx: Context<'_>) -> Result<(), Error> { let footer = footer(ctx); - ctx.send(|m| m.embed(|e| { - e.title("Donate") + ctx.send(CreateReply::default().embed(CreateEmbed::new().title("Donate") .description("Thinking of adding a monthly contribution? Click below for my Patreon and official bot server :) @@ -117,7 +118,7 @@ Just $2 USD/month! *Please note, you must be in the JellyWX Discord server to receive Patreon features*") .footer(footer) .color(*THEME_COLOR) - }), + ), ) .await?; @@ -129,14 +130,15 @@ Just $2 USD/month! pub async fn dashboard(ctx: Context<'_>) -> Result<(), Error> { let footer = footer(ctx); - ctx.send(|m| { - m.ephemeral(true).embed(|e| { - e.title("Dashboard") + ctx.send( + CreateReply::default().ephemeral(true).embed( + CreateEmbed::new() + .title("Dashboard") .description("**https://reminder-bot.com/dashboard**") .footer(footer) - .color(*THEME_COLOR) - }) - }) + .color(*THEME_COLOR), + ), + ) .await?; Ok(()) @@ -150,9 +152,11 @@ pub async fn clock(ctx: Context<'_>) -> Result<(), Error> { let tz = ctx.timezone().await; let now = Utc::now().with_timezone(&tz); - ctx.send(|m| { - m.ephemeral(true).content(format!("Time in **{}**: `{}`", tz, now.format("%H:%M"))) - }) + ctx.send(CreateReply::default().ephemeral(true).content(format!( + "Time in **{}**: `{}`", + tz, + now.format("%H:%M") + ))) .await?; Ok(()) @@ -168,13 +172,11 @@ pub async fn clock_context_menu(ctx: Context<'_>, user: serenity::User) -> Resul let now = Utc::now().with_timezone(&tz); - ctx.send(|m| { - m.ephemeral(true).content(format!( - "Time in {}'s timezone: `{}`", - user.mention(), - now.format("%H:%M") - )) - }) + ctx.send(CreateReply::default().ephemeral(true).content(format!( + "Time in {}'s timezone: `{}`", + user.mention(), + now.format("%H:%M") + ))) .await?; Ok(()) diff --git a/src/commands/moderation_cmds.rs b/src/commands/moderation_cmds.rs index 5f0f69c..9c9a521 100644 --- a/src/commands/moderation_cmds.rs +++ b/src/commands/moderation_cmds.rs @@ -2,6 +2,10 @@ use chrono::offset::Utc; use chrono_tz::{Tz, TZ_VARIANTS}; use levenshtein::levenshtein; use log::warn; +use poise::{ + serenity_prelude::{CreateEmbed, CreateEmbedFooter}, + CreateReply, +}; use super::autocomplete::timezone_autocomplete; use crate::{consts::THEME_COLOR, models::CtxData, Context, Error}; @@ -26,17 +30,18 @@ pub async fn timezone( let now = Utc::now().with_timezone(&tz); - ctx.send(|m| { - m.embed(|e| { - e.title("Timezone Set") + ctx.send( + CreateReply::default().embed( + CreateEmbed::new() + .title("Timezone Set") .description(format!( "Timezone has been set to **{}**. Your current time should be `{}`", timezone, now.format("%H:%M") )) - .color(*THEME_COLOR) - }) - }) + .color(*THEME_COLOR), + ), + ) .await?; } @@ -60,16 +65,15 @@ pub async fn timezone( ) }); - ctx.send(|m| { - m.embed(|e| { - e.title("Timezone Not Recognized") + ctx.send(CreateReply::default().embed(CreateEmbed::new() + .title("Timezone Not Recognized") .description("Possibly you meant one of the following timezones, otherwise click [here](https://gist.github.com/JellyWX/913dfc8b63d45192ad6cb54c829324ee):") .color(*THEME_COLOR) .fields(fields) - .footer(|f| f.text(footer_text)) + .footer(CreateEmbedFooter::new(footer_text)) .url("https://gist.github.com/JellyWX/913dfc8b63d45192ad6cb54c829324ee") - }) - }) + ) + ) .await?; } } @@ -78,9 +82,10 @@ pub async fn timezone( (t.to_string(), format!("🕗 `{}`", Utc::now().with_timezone(t).format("%H:%M")), true) }); - ctx.send(|m| { - m.embed(|e| { - e.title("Timezone Usage") + ctx.send( + CreateReply::default().embed( + CreateEmbed::new() + .title("Timezone Usage") .description( "**Usage:** `/timezone Name` @@ -92,10 +97,10 @@ You may want to use one of the popular timezones below, otherwise click [here](h ) .color(*THEME_COLOR) .fields(popular_timezones_iter) - .footer(|f| f.text(footer_text)) - .url("https://gist.github.com/JellyWX/913dfc8b63d45192ad6cb54c829324ee") - }) - }) + .footer(CreateEmbedFooter::new(footer_text)) + .url("https://gist.github.com/JellyWX/913dfc8b63d45192ad6cb54c829324ee"), + ), + ) .await?; } @@ -136,13 +141,11 @@ pub async fn set_ephemeral_confirmations(ctx: Context<'_>) -> Result<(), Error> guild_data.ephemeral_confirmations = true; guild_data.commit_changes(&ctx.data().database).await; - ctx.send(|r| { - r.ephemeral(true).embed(|e| { - e.title("Confirmations ephemeral") + ctx.send(CreateReply::default().ephemeral(true).embed(CreateEmbed::new().title("Confirmations ephemeral") .description("Reminder confirmations will be sent privately, and removed when your client restarts.") .color(*THEME_COLOR) - }) - }) + ) + ) .await?; Ok(()) @@ -160,15 +163,13 @@ pub async fn unset_ephemeral_confirmations(ctx: Context<'_>) -> Result<(), Error guild_data.ephemeral_confirmations = false; guild_data.commit_changes(&ctx.data().database).await; - ctx.send(|r| { - r.ephemeral(true).embed(|e| { - e.title("Confirmations public") + ctx.send(CreateReply::default().ephemeral(true).embed(CreateEmbed::new().title("Confirmations public") .description( "Reminder confirmations will be sent as regular messages, and won't be removed automatically.", ) .color(*THEME_COLOR) - }) - }) + ) + ) .await?; Ok(()) @@ -187,13 +188,14 @@ pub async fn set_allowed_dm(ctx: Context<'_>) -> Result<(), Error> { user_data.allowed_dm = true; user_data.commit_changes(&ctx.data().database).await; - ctx.send(|r| { - r.ephemeral(true).embed(|e| { - e.title("DMs permitted") + ctx.send( + CreateReply::default().ephemeral(true).embed( + CreateEmbed::new() + .title("DMs permitted") .description("You will receive a message if a user sets a DM reminder for you.") - .color(*THEME_COLOR) - }) - }) + .color(*THEME_COLOR), + ), + ) .await?; Ok(()) @@ -206,15 +208,16 @@ pub async fn unset_allowed_dm(ctx: Context<'_>) -> Result<(), Error> { user_data.allowed_dm = false; user_data.commit_changes(&ctx.data().database).await; - ctx.send(|r| { - r.ephemeral(true).embed(|e| { - e.title("DMs blocked") + ctx.send( + CreateReply::default().ephemeral(true).embed( + CreateEmbed::new() + .title("DMs blocked") .description( "You can still set DM reminders for yourself or for users with DMs enabled.", ) - .color(*THEME_COLOR) - }) - }) + .color(*THEME_COLOR), + ), + ) .await?; Ok(()) @@ -230,15 +233,13 @@ pub async fn webhook(ctx: Context<'_>) -> Result<(), Error> { match ctx.channel_data().await { Ok(data) => { if let (Some(id), Some(token)) = (data.webhook_id, data.webhook_token) { - ctx.send(|b| { - b.ephemeral(true).content(format!( - "**Warning!** + ctx.send(CreateReply::default().ephemeral(true).content(format!( + "**Warning!** This link can be used by users to anonymously send messages, with or without permissions. Do not share it! || https://discord.com/api/webhooks/{}/{} ||", - id, token, - )) - }) + id, token, + ))) .await?; } else { ctx.say("No webhook configured on this channel.").await?; diff --git a/src/commands/reminder_cmds.rs b/src/commands/reminder_cmds.rs index a432fda..c8ffbc2 100644 --- a/src/commands/reminder_cmds.rs +++ b/src/commands/reminder_cmds.rs @@ -5,7 +5,11 @@ use chrono_tz::Tz; use log::warn; use num_integer::Integer; use poise::{ - serenity_prelude::{builder::CreateEmbed, model::channel::Channel, ButtonStyle, ReactionType}, + serenity_prelude::{ + builder::CreateEmbed, model::channel::Channel, ButtonStyle, CreateActionRow, CreateButton, + CreateEmbedFooter, CreateSelectMenu, CreateSelectMenuKind, CreateSelectMenuOption, + ReactionType, + }, CreateReply, Modal, }; @@ -125,21 +129,19 @@ pub async fn offset( let channels = guild .channels .iter() - .filter(|(_, channel)| match channel { - Channel::Guild(guild_channel) => guild_channel.is_text_based(), - _ => false, - }) - .map(|(id, _)| id.0.to_string()) + .filter(|(_, channel)| channel.is_text_based()) + .map(|(id, _)| id.get().to_string()) .collect::>() .join(","); sqlx::query!( " -UPDATE reminders -INNER JOIN - `channels` ON `channels`.id = reminders.channel_id -SET reminders.`utc_time` = DATE_ADD(reminders.`utc_time`, INTERVAL ? SECOND) -WHERE FIND_IN_SET(channels.`channel`, ?)", + UPDATE reminders + INNER JOIN `channels` + ON `channels`.id = reminders.channel_id + SET reminders.`utc_time` = DATE_ADD(reminders.`utc_time`, INTERVAL ? SECOND) + WHERE FIND_IN_SET(channels.`channel`, ?) + ", combined_time as i64, channels ) @@ -148,9 +150,15 @@ WHERE FIND_IN_SET(channels.`channel`, ?)", .unwrap(); } else { sqlx::query!( - "UPDATE reminders INNER JOIN `channels` ON `channels`.id = reminders.channel_id SET reminders.`utc_time` = reminders.`utc_time` + ? WHERE channels.`channel` = ?", + " + UPDATE reminders + INNER JOIN `channels` + ON `channels`.id = reminders.channel_id + SET reminders.`utc_time` = reminders.`utc_time` + ? + WHERE channels.`channel` = ? + ", combined_time as i64, - ctx.channel_id().0 + ctx.channel_id().get() ) .execute(&ctx.data().database) .await @@ -216,9 +224,9 @@ pub async fn look( }), }; - let channel_opt = ctx.channel_id().to_channel_cached(&ctx); + let channel_opt = ctx.channel_id().to_channel_cached(&ctx.cache()); - let channel_id = if let Some(Channel::Guild(channel)) = channel_opt { + let channel_id = if let Some(channel) = channel_opt { if Some(channel.guild_id) == ctx.guild_id() { flags.channel_id.unwrap_or_else(|| ctx.channel_id()) } else { @@ -228,11 +236,8 @@ pub async fn look( ctx.channel_id() }; - let channel_name = if let Some(Channel::Guild(channel)) = channel_id.to_channel_cached(&ctx) { - Some(channel.name) - } else { - None - }; + let channel_name = + channel_id.to_channel_cached(&ctx.cache()).map(|channel| channel.name.clone()); let reminders = Reminder::from_channel(&ctx.data().database, channel_id, &flags).await; @@ -260,23 +265,21 @@ pub async fn look( let pager = LookPager::new(flags, timezone); - ctx.send(|r| { - r.ephemeral(true) - .embed(|e| { - e.title(format!( - "Reminders{}", - channel_name.map_or(String::new(), |n| format!(" on #{}", n)) - )) - .description(display) - .footer(|f| f.text(format!("Page {} of {}", 1, pages))) - .color(*THEME_COLOR) - }) - .components(|comp| { - pager.create_button_row(pages, comp); - - comp - }) - }) + ctx.send( + CreateReply::default() + .ephemeral(true) + .embed( + CreateEmbed::new() + .title(format!( + "Reminders{}", + channel_name.map_or(String::new(), |n| format!(" on #{}", n)) + )) + .description(display) + .footer(CreateEmbedFooter::new(format!("Page {} of {}", 1, pages))) + .color(*THEME_COLOR), + ) + .components(vec![pager.create_button_row(pages)]), + ) .await?; } @@ -298,11 +301,7 @@ pub async fn delete(ctx: Context<'_>) -> Result<(), Error> { let resp = show_delete_page(&reminders, 0, timezone); - ctx.send(|r| { - *r = resp; - r - }) - .await?; + ctx.send(resp).await?; Ok(()) } @@ -333,16 +332,12 @@ pub fn show_delete_page(reminders: &[Reminder], page: usize, timezone: Tz) -> Cr let pager = DelPager::new(page, timezone); if reminders.is_empty() { - let mut reply = CreateReply::default(); + let embed = CreateEmbed::new() + .title("Delete Reminders") + .description("No Reminders") + .color(*THEME_COLOR); - reply - .embed(|e| e.title("Delete Reminders").description("No Reminders").color(*THEME_COLOR)) - .components(|comp| { - pager.create_button_row(0, comp); - comp - }); - - return reply; + return CreateReply::default().embed(embed).components(vec![pager.create_button_row(0)]); } let pages = max_delete_page(reminders, &timezone); @@ -391,49 +386,42 @@ pub fn show_delete_page(reminders: &[Reminder], page: usize, timezone: Tz) -> Cr let del_selector = ComponentDataModel::DelSelector(DelSelector { page, timezone }); - let mut reply = CreateReply::default(); + let embed = CreateEmbed::new() + .title("Delete Reminders") + .description(display) + .footer(CreateEmbedFooter::new(format!("Page {} of {}", page + 1, pages))) + .color(*THEME_COLOR); - reply - .embed(|e| { - e.title("Delete Reminders") - .description(display) - .footer(|f| f.text(format!("Page {} of {}", page + 1, pages))) - .color(*THEME_COLOR) - }) - .components(|comp| { - pager.create_button_row(pages, comp); + let select_menu = CreateSelectMenu::new( + del_selector.to_custom_id(), + CreateSelectMenuKind::String { + options: shown_reminders + .iter() + .enumerate() + .map(|(count, reminder)| { + let c = reminder.display_content(); + let description = if c.len() > 100 { + format!( + "{}...", + reminder.display_content().chars().take(97).collect::() + ) + } else { + c.to_string() + }; - comp.create_action_row(|row| { - row.create_select_menu(|menu| { - menu.custom_id(del_selector.to_custom_id()).options(|opt| { - for (count, reminder) in shown_reminders.iter().enumerate() { - opt.create_option(|o| { - o.label(count + first_num).value(reminder.id).description({ - let c = reminder.display_content(); - - if c.len() > 100 { - format!( - "{}...", - reminder - .display_content() - .chars() - .take(97) - .collect::() - ) - } else { - c.to_string() - } - }) - }); - } - - opt - }) + CreateSelectMenuOption::new( + (count + first_num).to_string(), + reminder.id.to_string(), + ) + .description(description) }) - }) - }); + .collect(), + }, + ); - reply + CreateReply::default() + .embed(embed) + .components(vec![pager.create_button_row(pages), CreateActionRow::SelectMenu(select_menu)]) } fn time_difference(start_time: DateTime) -> String { @@ -465,19 +453,20 @@ pub async fn timer_base(_ctx: Context<'_>) -> Result<(), Error> { default_member_permissions = "MANAGE_GUILD" )] pub async fn list_timer(ctx: Context<'_>) -> Result<(), Error> { - let owner = ctx.guild_id().map(|g| g.0).unwrap_or_else(|| ctx.author().id.0); + let owner = ctx.guild_id().map(|g| g.get()).unwrap_or_else(|| ctx.author().id.get()); let timers = Timer::from_owner(owner, &ctx.data().database).await; if !timers.is_empty() { - ctx.send(|m| { - m.embed(|e| { - e.fields(timers.iter().map(|timer| { - (&timer.name, format!("⌚ `{}`", time_difference(timer.start_time)), false) - })) - .color(*THEME_COLOR) - }) - }) + ctx.send( + CreateReply::default().embed( + CreateEmbed::new() + .fields(timers.iter().map(|timer| { + (&timer.name, format!("⌚ `{}`", time_difference(timer.start_time)), false) + })) + .color(*THEME_COLOR), + ), + ) .await?; } else { ctx.say("No timers currently. Use `/timer start` to create a new timer").await?; @@ -497,7 +486,7 @@ pub async fn start_timer( ctx: Context<'_>, #[description = "Name for the new timer"] name: String, ) -> Result<(), Error> { - let owner = ctx.guild_id().map(|g| g.0).unwrap_or_else(|| ctx.author().id.0); + let owner = ctx.guild_id().map(|g| g.get()).unwrap_or_else(|| ctx.author().id.get()); let count = Timer::count_from_owner(owner, &ctx.data().database).await; @@ -530,7 +519,7 @@ pub async fn delete_timer( ctx: Context<'_>, #[description = "Name of timer to delete"] name: String, ) -> Result<(), Error> { - let owner = ctx.guild_id().map(|g| g.0).unwrap_or_else(|| ctx.author().id.0); + let owner = ctx.guild_id().map(|g| g.get()).unwrap_or_else(|| ctx.author().id.get()); let exists = sqlx::query!("SELECT 1 as _r FROM timers WHERE owner = ? AND name = ?", owner, name) @@ -604,7 +593,7 @@ pub async fn multiline( None => { warn!("Unexpected None encountered in /multiline"); Ok(Context::Application(ctx) - .send(|m| m.content("Unexpected error.").ephemeral(true)) + .send(CreateReply::default().content("Unexpected error.").ephemeral(true)) .await .map(|_| ())?) } @@ -682,9 +671,9 @@ async fn create_reminder( if list.is_empty() { if ctx.guild_id().is_some() { - vec![ReminderScope::Channel(ctx.channel_id().0)] + vec![ReminderScope::Channel(ctx.channel_id().get())] } else { - vec![ReminderScope::User(ctx.author().id.0)] + vec![ReminderScope::User(ctx.author().id.get())] } } else { list @@ -709,10 +698,9 @@ async fn create_reminder( }, ) } else { - ctx.send(|b| { - b.content( - "`repeat` is only available to Patreon subscribers or self-hosted users") - }) + ctx.send(CreateReply::default().content( + "`repeat` is only available to Patreon subscribers or self-hosted users", + )) .await?; return Ok(()); @@ -722,17 +710,16 @@ async fn create_reminder( }; if processed_interval.is_none() && interval.is_some() { - ctx.send(|b| { - b.content( - "Repeat interval could not be processed. Try similar to `1 hour` or `4 days`") - }) + ctx.send(CreateReply::default().content( + "Repeat interval could not be processed. Try similar to `1 hour` or `4 days`", + )) .await?; } else if processed_expires.is_none() && expires.is_some() { - ctx.send(|b| { - b.ephemeral(true).content( + ctx.send( + CreateReply::default().ephemeral(true).content( "Expiry time failed to process. Please make it as clear as possible", - ) - }) + ), + ) .await?; } else { let mut builder = MultiReminderBuilder::new(&ctx, ctx.guild_id()) @@ -756,37 +743,20 @@ async fn create_reminder( reminder_id: reminder, }); - ctx.send(|m| { - m.embed(|c| { - *c = embed; - c - }) - .components(|c| { - c.create_action_row(|r| { - r.create_button(|b| { - b.emoji(ReactionType::Unicode("🔕".to_string())) - .label("Cancel") - .style(ButtonStyle::Danger) - .custom_id(undo_button.to_custom_id()) - }) - .create_button(|b| { - b.emoji(ReactionType::Unicode("📝".to_string())) - .label("Edit") - .style(ButtonStyle::Link) - .url("https://beta.reminder-bot.com/dashboard") - }) - }) - }) - }) + ctx.send(CreateReply::default().embed(embed).components(vec![ + CreateActionRow::Buttons(vec![ + CreateButton::new(undo_button.to_custom_id()) + .emoji(ReactionType::Unicode("🔕".to_string())) + .label("Cancel") + .style(ButtonStyle::Danger), + CreateButton::new_link("https://beta.reminder-bot.com/dashboard") + .emoji(ReactionType::Unicode("📝".to_string())) + .label("Edit"), + ]), + ])) .await?; } else { - ctx.send(|m| { - m.embed(|c| { - *c = embed; - c - }) - }) - .await?; + ctx.send(CreateReply::default().embed(embed)).await?; } } } diff --git a/src/commands/todo_cmds.rs b/src/commands/todo_cmds.rs index f1a1f88..a98dd02 100644 --- a/src/commands/todo_cmds.rs +++ b/src/commands/todo_cmds.rs @@ -1,4 +1,10 @@ -use poise::CreateReply; +use poise::{ + serenity_prelude::{ + CreateActionRow, CreateEmbed, CreateEmbedFooter, CreateSelectMenu, CreateSelectMenuKind, + CreateSelectMenuOption, + }, + CreateReply, +}; use crate::{ component_models::{ @@ -48,7 +54,7 @@ pub async fn todo_guild_add( sqlx::query!( "INSERT INTO todos (guild_id, value) VALUES ((SELECT id FROM guilds WHERE guild = ?), ?)", - ctx.guild_id().unwrap().0, + ctx.guild_id().unwrap().get(), task ) .execute(&ctx.data().database) @@ -73,7 +79,7 @@ pub async fn todo_guild_view(ctx: Context<'_>) -> Result<(), Error> { "SELECT todos.id, value FROM todos INNER JOIN guilds ON todos.guild_id = guilds.id WHERE guilds.guild = ?", - ctx.guild_id().unwrap().0, + ctx.guild_id().unwrap().get(), ) .fetch_all(&ctx.data().database) .await @@ -82,13 +88,9 @@ WHERE guilds.guild = ?", .map(|row| (row.id as usize, row.value.clone())) .collect::>(); - let resp = show_todo_page(&values, 0, None, None, ctx.guild_id().map(|g| g.0)); + let resp = show_todo_page(&values, 0, None, None, ctx.guild_id().map(|g| g.get())); - ctx.send(|r| { - *r = resp; - r - }) - .await?; + ctx.send(resp).await?; Ok(()) } @@ -123,8 +125,8 @@ pub async fn todo_channel_add( sqlx::query!( "INSERT INTO todos (guild_id, channel_id, value) VALUES ((SELECT id FROM guilds WHERE guild = ?), (SELECT id FROM channels WHERE channel = ?), ?)", - ctx.guild_id().unwrap().0, - ctx.channel_id().0, + ctx.guild_id().unwrap().get(), + ctx.channel_id().get(), task ) .execute(&ctx.data().database) @@ -149,7 +151,7 @@ pub async fn todo_channel_view(ctx: Context<'_>) -> Result<(), Error> { "SELECT todos.id, value FROM todos INNER JOIN channels ON todos.channel_id = channels.id WHERE channels.channel = ?", - ctx.channel_id().0, + ctx.channel_id().get(), ) .fetch_all(&ctx.data().database) .await @@ -158,14 +160,15 @@ WHERE channels.channel = ?", .map(|row| (row.id as usize, row.value.clone())) .collect::>(); - let resp = - show_todo_page(&values, 0, None, Some(ctx.channel_id().0), ctx.guild_id().map(|g| g.0)); + let resp = show_todo_page( + &values, + 0, + None, + Some(ctx.channel_id().get()), + ctx.guild_id().map(|g| g.get()), + ); - ctx.send(|r| { - *r = resp; - r - }) - .await?; + ctx.send(resp).await?; Ok(()) } @@ -185,7 +188,7 @@ pub async fn todo_user_add( sqlx::query!( "INSERT INTO todos (user_id, value) VALUES ((SELECT id FROM users WHERE user = ?), ?)", - ctx.author().id.0, + ctx.author().id.get(), task ) .execute(&ctx.data().database) @@ -204,7 +207,7 @@ pub async fn todo_user_view(ctx: Context<'_>) -> Result<(), Error> { "SELECT todos.id, value FROM todos INNER JOIN users ON todos.user_id = users.id WHERE users.user = ?", - ctx.author().id.0, + ctx.author().id.get(), ) .fetch_all(&ctx.data().database) .await @@ -213,13 +216,9 @@ WHERE users.user = ?", .map(|row| (row.id as usize, row.value.clone())) .collect::>(); - let resp = show_todo_page(&values, 0, Some(ctx.author().id.0), None, None); + let resp = show_todo_page(&values, 0, Some(ctx.author().id.get()), None, None); - ctx.send(|r| { - *r = resp; - r - }) - .await?; + ctx.send(resp).await?; Ok(()) } @@ -306,61 +305,51 @@ pub fn show_todo_page( }; if todo_ids.is_empty() { - let mut reply = CreateReply::default(); - - reply.embed(|e| { - e.title(format!("{} Todo List", title)) + CreateReply::default().embed( + CreateEmbed::new() + .title(format!("{} Todo List", title)) .description("Todo List Empty!") - .footer(|f| f.text(format!("Page {} of {}", page + 1, pages))) .color(*THEME_COLOR) - }); - - reply + .footer(CreateEmbedFooter::new(format!("Page {} of {}", page + 1, pages))), + ) } else { let todo_selector = ComponentDataModel::TodoSelector(TodoSelector { page, user_id, channel_id, guild_id }); - let mut reply = CreateReply::default(); - - reply - .embed(|e| { - e.title(format!("{} Todo List", title)) + CreateReply::default() + .embed( + CreateEmbed::new() + .title(format!("{} Todo List", title)) .description(display) - .footer(|f| f.text(format!("Page {} of {}", page + 1, pages))) .color(*THEME_COLOR) - }) - .components(|comp| { - pager.create_button_row(pages, comp); + .footer(CreateEmbedFooter::new(format!("Page {} of {}", page + 1, pages))), + ) + .components(vec![ + pager.create_button_row(pages), + CreateActionRow::SelectMenu(CreateSelectMenu::new( + todo_selector.to_custom_id(), + CreateSelectMenuKind::String { + options: todo_ids + .iter() + .zip(&display_vec) + .enumerate() + .map(|(count, (id, disp))| { + let c = disp.split_once(' ').unwrap_or(("", "")).1; + let description = if c.len() > 100 { + format!("{}...", c.chars().take(97).collect::()) + } else { + c.to_string() + }; - comp.create_action_row(|row| { - row.create_select_menu(|menu| { - menu.custom_id(todo_selector.to_custom_id()).options(|opt| { - for (count, (id, disp)) in todo_ids.iter().zip(&display_vec).enumerate() - { - opt.create_option(|o| { - o.label(format!("Mark {} complete", count + first_num)) - .value(id) - .description({ - let c = disp.split_once(' ').unwrap_or(("", "")).1; - - if c.len() > 100 { - format!( - "{}...", - c.chars().take(97).collect::() - ) - } else { - c.to_string() - } - }) - }); - } - - opt - }) - }) - }) - }); - - reply + CreateSelectMenuOption::new( + format!("Mark {} complete", count + first_num), + id.to_string(), + ) + .description(description) + }) + .collect(), + }, + )), + ]) } } diff --git a/src/component_models/mod.rs b/src/component_models/mod.rs index 9251796..52dab53 100644 --- a/src/component_models/mod.rs +++ b/src/component_models/mod.rs @@ -8,15 +8,8 @@ use log::warn; use poise::{ serenity_prelude as serenity, serenity_prelude::{ - builder::CreateEmbed, - model::{ - application::interaction::{ - message_component::MessageComponentInteraction, InteractionResponseType, - MessageFlags, - }, - channel::Channel, - }, - Context, + builder::CreateEmbed, ComponentInteraction, ComponentInteractionDataKind, Context, + CreateEmbedFooter, CreateInteractionResponse, CreateInteractionResponseMessage, }, }; use rmp_serde::Serializer; @@ -31,7 +24,7 @@ use crate::{ component_models::pager::{DelPager, LookPager, MacroPager, Pager, TodoPager}, consts::{EMBED_DESCRIPTION_MAX_LENGTH, THEME_COLOR}, models::reminder::Reminder, - utils::send_as_initial_response, + utils::reply_to_interaction_response_message, Data, }; @@ -64,21 +57,23 @@ impl ComponentDataModel { rmp_serde::from_read(cur).unwrap() } - pub async fn act(&self, ctx: &Context, data: &Data, component: &MessageComponentInteraction) { + pub async fn act(&self, ctx: &Context, data: &Data, component: &ComponentInteraction) { match self { ComponentDataModel::LookPager(pager) => { let flags = pager.flags; - let channel_opt = component.channel_id.to_channel_cached(&ctx); + let channel_id = { + let channel_opt = component.channel_id.to_channel_cached(&ctx.cache); - let channel_id = if let Some(Channel::Guild(channel)) = channel_opt { - if Some(channel.guild_id) == component.guild_id { - flags.channel_id.unwrap_or(component.channel_id) + if let Some(channel) = channel_opt { + if Some(channel.guild_id) == component.guild_id { + flags.channel_id.unwrap_or(component.channel_id) + } else { + component.channel_id + } } else { component.channel_id } - } else { - component.channel_id }; let reminders = Reminder::from_channel(&data.database, channel_id, &flags).await; @@ -90,11 +85,7 @@ impl ComponentDataModel { .div_ceil(EMBED_DESCRIPTION_MAX_LENGTH); let channel_name = - if let Some(Channel::Guild(channel)) = channel_id.to_channel_cached(&ctx) { - Some(channel.name) - } else { - None - }; + channel_id.to_channel_cached(&ctx.cache).map(|channel| channel.name.clone()); let next_page = pager.next_page(pages); @@ -107,7 +98,7 @@ impl ComponentDataModel { .skip_while(|p| { skip_char_count += p.len(); - skip_char_count < EMBED_DESCRIPTION_MAX_LENGTH * next_page as usize + skip_char_count < EMBED_DESCRIPTION_MAX_LENGTH * next_page }) .take_while(|p| { char_count += p.len(); @@ -117,28 +108,24 @@ impl ComponentDataModel { .collect::>() .join(""); - let mut embed = CreateEmbed::default(); - embed + let embed = CreateEmbed::default() .title(format!( "Reminders{}", channel_name.map_or(String::new(), |n| format!(" on #{}", n)) )) .description(display) - .footer(|f| f.text(format!("Page {} of {}", next_page + 1, pages))) + .footer(CreateEmbedFooter::new(format!("Page {} of {}", next_page + 1, pages))) .color(*THEME_COLOR); let _ = component - .create_interaction_response(&ctx, |r| { - r.kind(InteractionResponseType::UpdateMessage).interaction_response_data( - |response| { - response.set_embeds(vec![embed]).components(|comp| { - pager.create_button_row(pages, comp); - - comp - }) - }, - ) - }) + .create_response( + &ctx, + CreateInteractionResponse::UpdateMessage( + CreateInteractionResponseMessage::new() + .embed(embed) + .components(vec![pager.create_button_row(pages)]), + ), + ) .await; } ComponentDataModel::DelPager(pager) => { @@ -155,55 +142,58 @@ impl ComponentDataModel { let resp = show_delete_page(&reminders, pager.next_page(max_pages), pager.timezone); let _ = component - .create_interaction_response(&ctx, |f| { - f.kind(InteractionResponseType::UpdateMessage).interaction_response_data( - |d| { - send_as_initial_response(resp, d); - d - }, - ) - }) + .create_response( + &ctx, + CreateInteractionResponse::UpdateMessage( + reply_to_interaction_response_message(resp), + ), + ) .await; } ComponentDataModel::DelSelector(selector) => { - let selected_id = component.data.values.join(","); + if let ComponentInteractionDataKind::StringSelect { values } = &component.data.kind + { + let selected_id = values.join(","); - sqlx::query!( - "UPDATE reminders SET `status` = 'deleted' WHERE FIND_IN_SET(id, ?)", - selected_id - ) - .execute(&data.database) - .await - .unwrap(); + sqlx::query!( + " + UPDATE reminders SET `status` = 'deleted' WHERE FIND_IN_SET(id, ?) + ", + selected_id + ) + .execute(&data.database) + .await + .unwrap(); - let reminders = Reminder::from_guild( - &ctx, - &data.database, - component.guild_id, - component.user.id, - ) - .await; - - let resp = show_delete_page(&reminders, selector.page, selector.timezone); - - let _ = component - .create_interaction_response(&ctx, |f| { - f.kind(InteractionResponseType::UpdateMessage).interaction_response_data( - |d| { - send_as_initial_response(resp, d); - d - }, - ) - }) + let reminders = Reminder::from_guild( + &ctx, + &data.database, + component.guild_id, + component.user.id, + ) .await; + + let resp = show_delete_page(&reminders, selector.page, selector.timezone); + + let _ = component + .create_response( + &ctx, + CreateInteractionResponse::UpdateMessage( + reply_to_interaction_response_message(resp), + ), + ) + .await; + } } ComponentDataModel::TodoPager(pager) => { - if Some(component.user.id.0) == pager.user_id || pager.user_id.is_none() { + if Some(component.user.id.get()) == pager.user_id || pager.user_id.is_none() { let values = if let Some(uid) = pager.user_id { sqlx::query!( - "SELECT todos.id, value FROM todos -INNER JOIN users ON todos.user_id = users.id -WHERE users.user = ?", + " + SELECT todos.id, value FROM todos + INNER JOIN users ON todos.user_id = users.id + WHERE users.user = ? + ", uid, ) .fetch_all(&data.database) @@ -214,9 +204,11 @@ WHERE users.user = ?", .collect::>() } else if let Some(cid) = pager.channel_id { sqlx::query!( - "SELECT todos.id, value FROM todos -INNER JOIN channels ON todos.channel_id = channels.id -WHERE channels.channel = ?", + " + SELECT todos.id, value FROM todos + INNER JOIN channels ON todos.channel_id = channels.id + WHERE channels.channel = ? + ", cid, ) .fetch_all(&data.database) @@ -227,9 +219,11 @@ WHERE channels.channel = ?", .collect::>() } else { sqlx::query!( - "SELECT todos.id, value FROM todos -INNER JOIN guilds ON todos.guild_id = guilds.id -WHERE guilds.guild = ?", + " + SELECT todos.id, value FROM todos + INNER JOIN guilds ON todos.guild_id = guilds.id + WHERE guilds.guild = ? + ", pager.guild_id, ) .fetch_all(&data.database) @@ -251,79 +245,86 @@ WHERE guilds.guild = ?", ); let _ = component - .create_interaction_response(&ctx, |f| { - f.kind(InteractionResponseType::UpdateMessage) - .interaction_response_data(|d| { - send_as_initial_response(resp, d); - d - }) - }) + .create_response( + &ctx, + CreateInteractionResponse::UpdateMessage( + reply_to_interaction_response_message(resp), + ), + ) .await; } else { let _ = component - .create_interaction_response(&ctx, |r| { - r.kind(InteractionResponseType::ChannelMessageWithSource) - .interaction_response_data(|d| { - d.flags( - MessageFlags::EPHEMERAL, - ) + .create_response( + &ctx, + CreateInteractionResponse::Message( + CreateInteractionResponseMessage::new() + .ephemeral(true) .content("Only the user who performed the command can use these components") - }) - }) + ) + ) .await; } } ComponentDataModel::TodoSelector(selector) => { - if Some(component.user.id.0) == selector.user_id || selector.user_id.is_none() { - let selected_id = component.data.values.join(","); + if Some(component.user.id.get()) == selector.user_id || selector.user_id.is_none() { + if let ComponentInteractionDataKind::StringSelect { values } = + &component.data.kind + { + let selected_id = values.join(","); - sqlx::query!("DELETE FROM todos WHERE FIND_IN_SET(id, ?)", selected_id) + sqlx::query!( + " + DELETE FROM todos WHERE FIND_IN_SET(id, ?) + ", + selected_id + ) .execute(&data.database) .await .unwrap(); - let values = sqlx::query!( - // fucking braindead mysql use <=> instead of = for null comparison - "SELECT id, value FROM todos WHERE user_id <=> ? AND channel_id <=> ? AND guild_id <=> ?", - selector.user_id, - selector.channel_id, - selector.guild_id, - ) - .fetch_all(&data.database) - .await - .unwrap() - .iter() - .map(|row| (row.id as usize, row.value.clone())) - .collect::>(); + let values = sqlx::query!( + // fucking braindead mysql use <=> instead of = for null comparison + " + SELECT id, value FROM todos WHERE user_id <=> ? AND channel_id <=> ? AND guild_id <=> ? + ", + selector.user_id, + selector.channel_id, + selector.guild_id, + ) + .fetch_all(&data.database) + .await + .unwrap() + .iter() + .map(|row| (row.id as usize, row.value.clone())) + .collect::>(); - let resp = show_todo_page( - &values, - selector.page, - selector.user_id, - selector.channel_id, - selector.guild_id, - ); + let resp = show_todo_page( + &values, + selector.page, + selector.user_id, + selector.channel_id, + selector.guild_id, + ); - let _ = component - .create_interaction_response(&ctx, |f| { - f.kind(InteractionResponseType::UpdateMessage) - .interaction_response_data(|d| { - send_as_initial_response(resp, d); - d - }) - }) - .await; + let _ = component + .create_response( + &ctx, + CreateInteractionResponse::UpdateMessage( + reply_to_interaction_response_message(resp), + ), + ) + .await; + } } else { let _ = component - .create_interaction_response(&ctx, |r| { - r.kind(InteractionResponseType::ChannelMessageWithSource) - .interaction_response_data(|d| { - d.flags( - MessageFlags::EPHEMERAL, - ) + .create_response( + &ctx, + CreateInteractionResponse::Message( + CreateInteractionResponseMessage::new() + .ephemeral(true) .content("Only the user who performed the command can use these components") - }) - }) + ) + ) .await; } } @@ -336,14 +337,12 @@ WHERE guilds.guild = ?", let resp = show_macro_page(¯os, page); let _ = component - .create_interaction_response(&ctx, |f| { - f.kind(InteractionResponseType::UpdateMessage).interaction_response_data( - |d| { - send_as_initial_response(resp, d); - d - }, - ) - }) + .create_response( + &ctx, + CreateInteractionResponse::UpdateMessage( + reply_to_interaction_response_message(resp), + ), + ) .await; } ComponentDataModel::UndoReminder(undo_reminder) => { @@ -355,58 +354,56 @@ WHERE guilds.guild = ?", match reminder.delete(&data.database).await { Ok(()) => { let _ = component - .create_interaction_response(&ctx, |f| { - f.kind(InteractionResponseType::UpdateMessage) - .interaction_response_data(|d| { - d.embed(|e| { - e.title("Reminder Canceled") - .description( - "This reminder has been canceled.", - ) - .color(*THEME_COLOR) - }) - .components(|c| c) - }) - }) + .create_response( + &ctx, + CreateInteractionResponse::UpdateMessage( + CreateInteractionResponseMessage::new().embed( + CreateEmbed::new() + .title("Reminder Canceled") + .description("This reminder has been canceled.") + .color(*THEME_COLOR), + ), + ), + ) .await; } Err(e) => { warn!("Error canceling reminder: {:?}", e); let _ = component - .create_interaction_response(&ctx, |f| { - f.kind(InteractionResponseType::ChannelMessageWithSource) - .interaction_response_data(|d| { - d.content( - "The reminder could not be canceled: it may have already been deleted. Check `/del`!") - .ephemeral(true) - }) - }) + .create_response( + &ctx, + CreateInteractionResponse::Message( + CreateInteractionResponseMessage::new().content( + "An error occurred trying to cancel this reminder.", + ).ephemeral(true), + ), + ) .await; } } } else { let _ = component - .create_interaction_response(&ctx, |f| { - f.kind(InteractionResponseType::ChannelMessageWithSource) - .interaction_response_data(|d| { - d.content( - "The reminder could not be canceled: it may have already been deleted. Check `/del`!") - .ephemeral(true) - }) - }) + .create_response( + &ctx, + CreateInteractionResponse::Message( + CreateInteractionResponseMessage::new().content( + "The reminder could not be canceled. It may have already been deleted.", + ).ephemeral(true), + ), + ) .await; } } else { let _ = component - .create_interaction_response(&ctx, |f| { - f.kind(InteractionResponseType::ChannelMessageWithSource) - .interaction_response_data(|d| { - d.content( - "Only the user who performed the command can use this button.") - .ephemeral(true) - }) - }) + .create_response( + &ctx, + CreateInteractionResponse::Message( + CreateInteractionResponseMessage::new() + .content("Only the user who performed the command can use these components") + .ephemeral(true), + ), + ) .await; } } diff --git a/src/consts.rs b/src/consts.rs index 2cfa0b3..72baf0d 100644 --- a/src/consts.rs +++ b/src/consts.rs @@ -10,17 +10,16 @@ pub const CHARACTERS: &str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWX const THEME_COLOR_FALLBACK: u32 = 0x8fb677; pub const MACRO_MAX_COMMANDS: usize = 5; +use poise::serenity_prelude::CreateAttachment; use std::{collections::HashSet, env, iter::FromIterator}; -use poise::serenity_prelude::model::prelude::AttachmentType; use regex::Regex; lazy_static! { - pub static ref DEFAULT_AVATAR: AttachmentType<'static> = ( + pub static ref DEFAULT_AVATAR: CreateAttachment = CreateAttachment::bytes( include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/assets/webhook.jpg")) as &[u8], "webhook.jpg", - ) - .into(); + ); pub static ref REGEX_CHANNEL_USER: Regex = Regex::new(r#"\s*<(#|@)(?:!)?(\d+)>\s*"#).unwrap(); pub static ref SUBSCRIPTION_ROLES: HashSet = HashSet::from_iter( env::var("PATREON_ROLE_ID") diff --git a/src/event_handlers.rs b/src/event_handlers.rs index 698a34e..ab46441 100644 --- a/src/event_handlers.rs +++ b/src/event_handlers.rs @@ -1,48 +1,44 @@ -use std::{collections::HashMap, env}; - -use log::error; +use poise::serenity_prelude::ActivityData; use poise::{ serenity_prelude as serenity, - serenity_prelude::{model::application::interaction::Interaction, utils::shard_id}, + serenity_prelude::{CreateEmbed, CreateMessage, FullEvent}, }; use crate::{component_models::ComponentDataModel, Data, Error, THEME_COLOR}; pub async fn listener( ctx: &serenity::Context, - event: &poise::Event<'_>, + event: &FullEvent, data: &Data, ) -> Result<(), Error> { match event { - poise::Event::Ready { .. } => { - ctx.set_activity(serenity::Activity::watching("for /remind")).await; + FullEvent::Ready { .. } => { + ctx.set_activity(Some(ActivityData::watching("for /remind"))); } - poise::Event::ChannelDelete { channel } => { - sqlx::query!("DELETE FROM channels WHERE channel = ?", channel.id.as_u64()) + FullEvent::ChannelDelete { channel, .. } => { + sqlx::query!("DELETE FROM channels WHERE channel = ?", channel.id.get()) .execute(&data.database) .await .unwrap(); } - poise::Event::GuildCreate { guild, is_new } => { - if *is_new { - let guild_id = guild.id.as_u64().to_owned(); + FullEvent::GuildCreate { guild, is_new } => { + if is_new.unwrap_or(false) { + let guild_id = guild.id.get().to_owned(); sqlx::query!("INSERT IGNORE INTO guilds (guild) VALUES (?)", guild_id) .execute(&data.database) .await?; - if let Err(e) = post_guild_count(ctx, &data.http, guild_id).await { - error!("DiscordBotList: {:?}", e); - } - let default_channel = guild.default_channel_guaranteed(); if let Some(default_channel) = default_channel { - default_channel - .send_message(&ctx, |m| { - m.embed(|e| { - e.title("Thank you for adding Reminder Bot!").description( - "To get started: + default_channel.send_message( + &ctx, + CreateMessage::new() + .embed( + CreateEmbed::new() + .title("Thank you for adding Reminder Bot!") + .description("To get started: • Set your timezone with `/timezone` • Set up permissions in Server Settings 🠚 Integrations 🠚 Reminder Bot (desktop only) • Create your first reminder with `/remind` @@ -52,24 +48,24 @@ If you need any support, please come and ask us! Join our [Discord](https://disc __Updates__ To stay up to date on the latest features and fixes, join our [Discord](https://discord.jellywx.com). -", - ).color(*THEME_COLOR) - }) - }) +") + .color(*THEME_COLOR) + ) + ) .await?; } } } - poise::Event::GuildDelete { incomplete, .. } => { - let _ = sqlx::query!("DELETE FROM guilds WHERE guild = ?", incomplete.id.0) + FullEvent::GuildDelete { incomplete, .. } => { + let _ = sqlx::query!("DELETE FROM guilds WHERE guild = ?", incomplete.id.get()) .execute(&data.database) .await; } - poise::Event::InteractionCreate { interaction } => { - if let Interaction::MessageComponent(component) = interaction { + FullEvent::InteractionCreate { interaction } => { + if let Some(component) = interaction.clone().message_component() { let component_model = ComponentDataModel::from_custom_id(&component.data.custom_id); - component_model.act(ctx, data, component).await; + component_model.act(ctx, data, &component).await; } } _ => {} @@ -77,38 +73,3 @@ To stay up to date on the latest features and fixes, join our [Discord](https:// Ok(()) } - -async fn post_guild_count( - ctx: &serenity::Context, - http: &reqwest::Client, - guild_id: u64, -) -> Result<(), reqwest::Error> { - if let Ok(token) = env::var("DISCORDBOTS_TOKEN") { - let shard_count = ctx.cache.shard_count(); - let current_shard_id = shard_id(guild_id, shard_count); - - let guild_count = ctx - .cache - .guilds() - .iter() - .filter(|g| shard_id(g.as_u64().to_owned(), shard_count) == current_shard_id) - .count() as u64; - - let mut hm = HashMap::new(); - hm.insert("server_count", guild_count); - hm.insert("shard_id", current_shard_id); - hm.insert("shard_count", shard_count); - - http.post( - format!("https://top.gg/api/bots/{}/stats", ctx.cache.current_user_id().as_u64()) - .as_str(), - ) - .header("Authorization", token) - .json(&hm) - .send() - .await - .map(|_| ()) - } else { - Ok(()) - } -} diff --git a/src/hooks.rs b/src/hooks.rs index 1462236..658f271 100644 --- a/src/hooks.rs +++ b/src/hooks.rs @@ -1,42 +1,41 @@ -use poise::{ - serenity_prelude::model::channel::Channel, ApplicationCommandOrAutocompleteInteraction, -}; +use poise::{serenity_prelude::model::channel::Channel, CreateReply}; use crate::{consts::MACRO_MAX_COMMANDS, models::command_macro::RecordedCommand, Context, Error}; async fn macro_check(ctx: Context<'_>) -> bool { if let Context::Application(app_ctx) = ctx { - if let ApplicationCommandOrAutocompleteInteraction::ApplicationCommand(_) = - app_ctx.interaction - { - if let Some(guild_id) = ctx.guild_id() { - if ctx.command().identifying_name != "finish_macro" { - let mut lock = ctx.data().recording_macros.write().await; + if let Some(guild_id) = ctx.guild_id() { + if ctx.command().identifying_name != "finish_macro" { + let mut lock = ctx.data().recording_macros.write().await; - if let Some(command_macro) = lock.get_mut(&(guild_id, ctx.author().id)) { - if command_macro.commands.len() >= MACRO_MAX_COMMANDS { - let _ = ctx.send(|m| { - m.ephemeral(true).content( - format!("{} commands already recorded. Please use `/macro finish` to end recording.", MACRO_MAX_COMMANDS), - ) - }) - .await; - } else { - let recorded = RecordedCommand { - action: None, - command_name: ctx.command().identifying_name.clone(), - options: Vec::from(app_ctx.args), - }; - - command_macro.commands.push(recorded); - - let _ = ctx - .send(|m| m.ephemeral(true).content("Command recorded to macro")) + if let Some(command_macro) = lock.get_mut(&(guild_id, ctx.author().id)) { + if command_macro.commands.len() >= MACRO_MAX_COMMANDS { + let _ = ctx + .send( + CreateReply::default() + .ephemeral(true) + .content(format!("{} commands already recorded. Please use `/macro finish` to end recording.", MACRO_MAX_COMMANDS)) + ) .await; - } + } else { + let recorded = RecordedCommand { + action: None, + command_name: ctx.command().identifying_name.clone(), + options: app_ctx.interaction.data.options.clone(), + }; - return false; + command_macro.commands.push(recorded); + + let _ = ctx + .send( + CreateReply::default() + .ephemeral(true) + .content("Command recorded to macro"), + ) + .await; } + + return false; } } } @@ -46,11 +45,13 @@ async fn macro_check(ctx: Context<'_>) -> bool { } async fn check_self_permissions(ctx: Context<'_>) -> bool { - if let Some(guild) = ctx.guild() { - let user_id = ctx.serenity_context().cache.current_user_id(); + if let Some(guild_id) = ctx.guild_id() { + let user_id = ctx.serenity_context().cache.current_user().id; - let manage_webhooks = - guild.member_permissions(&ctx, user_id).await.map_or(false, |p| p.manage_webhooks()); + let manage_webhooks = guild_id + .current_user_member(&ctx) + .await + .map_or(false, |m| m.permissions(&ctx).map_or(false, |p| p.manage_webhooks())); let (view_channel, send_messages, embed_links) = ctx .channel_id() @@ -72,20 +73,18 @@ async fn check_self_permissions(ctx: Context<'_>) -> bool { true } else { let _ = ctx - .send(|m| { - m.content(format!( - "Please ensure the bot has the correct permissions: + .send(CreateReply::default().content(format!( + "Please ensure the bot has the correct permissions: {} **View Channel** {} **Send Message** {} **Embed Links** {} **Manage Webhooks**", - if view_channel { "✅" } else { "❌" }, - if send_messages { "✅" } else { "❌" }, - if embed_links { "✅" } else { "❌" }, - if manage_webhooks { "✅" } else { "❌" }, - )) - }) + if view_channel { "✅" } else { "❌" }, + if send_messages { "✅" } else { "❌" }, + if embed_links { "✅" } else { "❌" }, + if manage_webhooks { "✅" } else { "❌" }, + ))) .await; false diff --git a/src/main.rs b/src/main.rs index aaed99a..da9961f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -23,9 +23,12 @@ use std::{ use chrono_tz::Tz; use log::{error, warn}; -use poise::serenity_prelude::model::{ - gateway::GatewayIntents, - id::{GuildId, UserId}, +use poise::serenity_prelude::{ + model::{ + gateway::GatewayIntents, + id::{GuildId, UserId}, + }, + ClientBuilder, }; use sqlx::{MySql, Pool}; use tokio::sync::{broadcast, broadcast::Sender, RwLock}; @@ -36,7 +39,6 @@ use crate::{ event_handlers::listener, hooks::all_checks, models::command_macro::CommandMacro, - utils::register_application_commands, }; type Database = MySql; @@ -213,11 +215,10 @@ async fn _main(tx: Sender<()>) -> Result<(), Box> { .map(|t| t.timezone.parse::().unwrap()) .collect::>(); - poise::Framework::builder() - .token(discord_token) + let framework = poise::Framework::builder() .setup(move |ctx, _bot, framework| { Box::pin(async move { - register_application_commands(ctx, framework, None).await.unwrap(); + poise::builtins::register_globally(ctx, &framework.options().commands).await?; let kill_tx = tx.clone(); let kill_recv = tx.subscribe(); @@ -261,9 +262,12 @@ async fn _main(tx: Sender<()>) -> Result<(), Box> { }) }) .options(options) - .intents(GatewayIntents::GUILDS) - .run_autosharded() - .await?; + .build(); + + let mut client = + ClientBuilder::new(&discord_token, GatewayIntents::GUILDS).framework(framework).await?; + + client.start_autosharded().await?; Ok(()) } diff --git a/src/models/channel_data.rs b/src/models/channel_data.rs index 9983056..3373ca3 100644 --- a/src/models/channel_data.rs +++ b/src/models/channel_data.rs @@ -18,7 +18,7 @@ impl ChannelData { channel: &Channel, pool: &MySqlPool, ) -> Result> { - let channel_id = channel.id().as_u64().to_owned(); + let channel_id = channel.id().get().to_owned(); if let Ok(c) = sqlx::query_as_unchecked!( Self, @@ -30,7 +30,7 @@ impl ChannelData { { Ok(c) } else { - let props = channel.to_owned().guild().map(|g| (g.guild_id.as_u64().to_owned(), g.name)); + let props = channel.to_owned().guild().map(|g| (g.guild_id.get().to_owned(), g.name)); let (guild_id, channel_name) = if let Some((a, b)) = props { (Some(a), Some(b)) } else { (None, None) }; diff --git a/src/models/command_macro.rs b/src/models/command_macro.rs index f7b89ad..f63e93a 100644 --- a/src/models/command_macro.rs +++ b/src/models/command_macro.rs @@ -1,6 +1,4 @@ -use poise::serenity_prelude::model::{ - application::interaction::application_command::CommandDataOption, id::GuildId, -}; +use poise::serenity_prelude::{model::id::GuildId, CommandDataOption}; use serde::{Deserialize, Serialize}; use serde_json::Value; @@ -45,7 +43,7 @@ pub async fn guild_command_macro( " SELECT * FROM macro WHERE guild_id = (SELECT id FROM guilds WHERE guild = ?) AND name = ? ", - ctx.guild_id().unwrap().0, + ctx.guild_id().unwrap().get(), name ) .fetch_one(&ctx.data().database) diff --git a/src/models/guild_data.rs b/src/models/guild_data.rs index 21153c4..b0ce526 100644 --- a/src/models/guild_data.rs +++ b/src/models/guild_data.rs @@ -14,21 +14,21 @@ impl GuildData { if let Ok(c) = sqlx::query_as_unchecked!( Self, "SELECT id, ephemeral_confirmations FROM guilds WHERE guild = ?", - guild_id.0 + guild_id.get() ) .fetch_one(pool) .await { Ok(c) } else { - sqlx::query!("INSERT IGNORE INTO guilds (guild) VALUES (?)", guild_id.0) + sqlx::query!("INSERT IGNORE INTO guilds (guild) VALUES (?)", guild_id.get()) .execute(&pool.clone()) .await?; Ok(sqlx::query_as_unchecked!( Self, "SELECT id, ephemeral_confirmations FROM guilds WHERE guild = ?", - guild_id.0 + guild_id.get() ) .fetch_one(pool) .await?) diff --git a/src/models/mod.rs b/src/models/mod.rs index 52408c7..a5dff20 100644 --- a/src/models/mod.rs +++ b/src/models/mod.rs @@ -6,7 +6,7 @@ pub mod timer; pub mod user_data; use chrono_tz::Tz; -use poise::serenity_prelude::{async_trait, model::id::UserId, ChannelType}; +use poise::serenity_prelude::{async_trait, model::id::UserId}; use crate::{ models::{channel_data::ChannelData, guild_data::GuildData, user_data::UserData}, @@ -53,20 +53,7 @@ impl CtxData for Context<'_> { async fn channel_data(&self) -> Result> { // If we're in a thread, get the parent channel. - let recv_channel = self.channel_id().to_channel(&self).await?; - - let channel = match recv_channel.guild() { - Some(guild_channel) => { - if guild_channel.kind == ChannelType::PublicThread { - guild_channel.parent_id.unwrap().to_channel_cached(&self).unwrap() - } else { - self.channel_id().to_channel_cached(&self).unwrap() - } - } - - None => self.channel_id().to_channel_cached(&self).unwrap(), - }; - + let channel = self.channel_id().to_channel(&self).await?; ChannelData::from_channel(&channel, &self.data().database).await } @@ -82,7 +69,7 @@ impl Data { ) -> Result>, Error> { let rows = sqlx::query!( "SELECT name, description, commands FROM macro WHERE guild_id = (SELECT id FROM guilds WHERE guild = ?)", - guild_id.0 + guild_id.get() ) .fetch_all(&self.database) .await?.iter().map(|row| CommandMacro { diff --git a/src/models/reminder/builder.rs b/src/models/reminder/builder.rs index 0d2e6d8..4baef20 100644 --- a/src/models/reminder/builder.rs +++ b/src/models/reminder/builder.rs @@ -1,4 +1,4 @@ -use std::{collections::HashSet, fmt::Display}; +use std::collections::HashSet; use chrono::{Duration, NaiveDateTime, Utc}; use chrono_tz::Tz; @@ -9,8 +9,9 @@ use poise::serenity_prelude::{ id::{ChannelId, GuildId, UserId}, webhook::Webhook, }, - ChannelType, Result as SerenityResult, + ChannelType, CreateWebhook, Result as SerenityResult, }; +use secrecy::ExposeSecret; use sqlx::MySqlPool; use crate::{ @@ -27,9 +28,9 @@ use crate::{ async fn create_webhook( ctx: impl CacheHttp, channel: GuildChannel, - name: impl Display, + name: impl Into, ) -> SerenityResult { - channel.create_webhook_with_avatar(ctx.http(), name, DEFAULT_AVATAR.clone()).await + channel.create_webhook(ctx.http(), CreateWebhook::new(name).avatar(&*DEFAULT_AVATAR)).await } #[derive(Hash, PartialEq, Eq)] @@ -230,7 +231,7 @@ impl<'a> MultiReminderBuilder<'a> { let thread_id = None; let db_channel_id = match scope { ReminderScope::User(user_id) => { - if let Ok(user) = UserId(user_id).to_user(&self.ctx).await { + if let Ok(user) = UserId::new(user_id).to_user(&self.ctx).await { let user_data = UserData::from_user( &user, &self.ctx.serenity_context(), @@ -257,7 +258,8 @@ impl<'a> MultiReminderBuilder<'a> { } } ReminderScope::Channel(channel_id) => { - let channel = ChannelId(channel_id).to_channel(&self.ctx).await.unwrap(); + let channel = + ChannelId::new(channel_id).to_channel(&self.ctx).await.unwrap(); if let Some(mut guild_channel) = channel.clone().guild() { if Some(guild_channel.guild_id) != self.guild_id { @@ -290,8 +292,9 @@ impl<'a> MultiReminderBuilder<'a> { { Ok(webhook) => { channel_data.webhook_id = - Some(webhook.id.as_u64().to_owned()); - channel_data.webhook_token = webhook.token; + Some(webhook.id.get().to_owned()); + channel_data.webhook_token = + webhook.token.map(|s| s.expose_secret().clone()); channel_data .commit_changes(&self.ctx.data().database) diff --git a/src/models/reminder/mod.rs b/src/models/reminder/mod.rs index 5e69971..a822e53 100644 --- a/src/models/reminder/mod.rs +++ b/src/models/reminder/mod.rs @@ -54,31 +54,31 @@ impl Reminder { sqlx::query_as_unchecked!( Self, " -SELECT - reminders.id, - reminders.uid, - channels.channel, - reminders.utc_time, - reminders.interval_seconds, - reminders.interval_days, - reminders.interval_months, - reminders.expires, - reminders.enabled, - reminders.content, - reminders.embed_description, - users.user AS set_by -FROM - reminders -INNER JOIN - channels -ON - reminders.channel_id = channels.id -LEFT JOIN - users -ON - reminders.set_by = users.id -WHERE - reminders.uid = ? + SELECT + reminders.id, + reminders.uid, + channels.channel, + reminders.utc_time, + reminders.interval_seconds, + reminders.interval_days, + reminders.interval_months, + reminders.expires, + reminders.enabled, + reminders.content, + reminders.embed_description, + users.user AS set_by + FROM + reminders + INNER JOIN + channels + ON + reminders.channel_id = channels.id + LEFT JOIN + users + ON + reminders.set_by = users.id + WHERE + reminders.uid = ? ", uid ) @@ -91,31 +91,31 @@ WHERE sqlx::query_as_unchecked!( Self, " -SELECT - reminders.id, - reminders.uid, - channels.channel, - reminders.utc_time, - reminders.interval_seconds, - reminders.interval_days, - reminders.interval_months, - reminders.expires, - reminders.enabled, - reminders.content, - reminders.embed_description, - users.user AS set_by -FROM - reminders -INNER JOIN - channels -ON - reminders.channel_id = channels.id -LEFT JOIN - users -ON - reminders.set_by = users.id -WHERE - reminders.id = ? + SELECT + reminders.id, + reminders.uid, + channels.channel, + reminders.utc_time, + reminders.interval_seconds, + reminders.interval_days, + reminders.interval_months, + reminders.expires, + reminders.enabled, + reminders.content, + reminders.embed_description, + users.user AS set_by + FROM + reminders + INNER JOIN + channels + ON + reminders.channel_id = channels.id + LEFT JOIN + users + ON + reminders.set_by = users.id + WHERE + reminders.id = ? ", id ) @@ -135,37 +135,37 @@ WHERE sqlx::query_as_unchecked!( Self, " -SELECT - reminders.id, - reminders.uid, - channels.channel, - reminders.utc_time, - reminders.interval_seconds, - reminders.interval_days, - reminders.interval_months, - reminders.expires, - reminders.enabled, - reminders.content, - reminders.embed_description, - users.user AS set_by -FROM - reminders -INNER JOIN - channels -ON - reminders.channel_id = channels.id -LEFT JOIN - users -ON - reminders.set_by = users.id -WHERE - `status` = 'pending' AND - channels.channel = ? AND - FIND_IN_SET(reminders.enabled, ?) -ORDER BY - reminders.utc_time + SELECT + reminders.id, + reminders.uid, + channels.channel, + reminders.utc_time, + reminders.interval_seconds, + reminders.interval_days, + reminders.interval_months, + reminders.expires, + reminders.enabled, + reminders.content, + reminders.embed_description, + users.user AS set_by + FROM + reminders + INNER JOIN + channels + ON + reminders.channel_id = channels.id + LEFT JOIN + users + ON + reminders.set_by = users.id + WHERE + `status` = 'pending' AND + channels.channel = ? AND + FIND_IN_SET(reminders.enabled, ?) + ORDER BY + reminders.utc_time ", - channel_id.as_u64(), + channel_id.get(), enabled, ) .fetch_all(pool) @@ -180,119 +180,126 @@ ORDER BY user: UserId, ) -> Vec { if let Some(guild_id) = guild_id { - let guild_opt = guild_id.to_guild_cached(cache); - - if let Some(guild) = guild_opt { - let channels = guild - .channels - .keys() - .into_iter() - .map(|k| k.as_u64().to_string()) - .collect::>() - .join(","); - - sqlx::query_as_unchecked!( - Self, - " -SELECT - reminders.id, - reminders.uid, - channels.channel, - reminders.utc_time, - reminders.interval_seconds, - reminders.interval_days, - reminders.interval_months, - reminders.expires, - reminders.enabled, - reminders.content, - reminders.embed_description, - users.user AS set_by -FROM - reminders -LEFT JOIN - channels -ON - channels.id = reminders.channel_id -LEFT JOIN - users -ON - reminders.set_by = users.id -WHERE - `status` = 'pending' AND - FIND_IN_SET(channels.channel, ?) - ", - channels + let channel_query = if let Some(guild) = guild_id.to_guild_cached(&cache) { + Some( + guild + .channels + .keys() + .into_iter() + .map(|k| k.get().to_string()) + .collect::>() + .join(","), ) - .fetch_all(pool) - .await } else { - sqlx::query_as_unchecked!( - Self, - " -SELECT - reminders.id, - reminders.uid, - channels.channel, - reminders.utc_time, - reminders.interval_seconds, - reminders.interval_days, - reminders.interval_months, - reminders.expires, - reminders.enabled, - reminders.content, - reminders.embed_description, - users.user AS set_by -FROM - reminders -LEFT JOIN - channels -ON - channels.id = reminders.channel_id -LEFT JOIN - users -ON - reminders.set_by = users.id -WHERE - `status` = 'pending' AND - channels.guild_id = (SELECT id FROM guilds WHERE guild = ?) - ", - guild_id.as_u64() - ) - .fetch_all(pool) - .await + None + }; + + match channel_query { + Some(channel_query) => { + sqlx::query_as_unchecked!( + Self, + " + SELECT + reminders.id, + reminders.uid, + channels.channel, + reminders.utc_time, + reminders.interval_seconds, + reminders.interval_days, + reminders.interval_months, + reminders.expires, + reminders.enabled, + reminders.content, + reminders.embed_description, + users.user AS set_by + FROM + reminders + LEFT JOIN + channels + ON + channels.id = reminders.channel_id + LEFT JOIN + users + ON + reminders.set_by = users.id + WHERE + `status` = 'pending' AND + FIND_IN_SET(channels.channel, ?) + ", + channel_query + ) + .fetch_all(pool) + .await + } + None => { + sqlx::query_as_unchecked!( + Self, + " + SELECT + reminders.id, + reminders.uid, + channels.channel, + reminders.utc_time, + reminders.interval_seconds, + reminders.interval_days, + reminders.interval_months, + reminders.expires, + reminders.enabled, + reminders.content, + reminders.embed_description, + users.user AS set_by + FROM + reminders + LEFT JOIN + channels + ON + channels.id = reminders.channel_id + LEFT JOIN + users + ON + reminders.set_by = users.id + WHERE + `status` = 'pending' AND + channels.guild_id = (SELECT id FROM guilds WHERE guild = ?) + ", + guild_id.get() + ) + .fetch_all(pool) + .await + } } } else { sqlx::query_as_unchecked!( Self, " -SELECT - reminders.id, - reminders.uid, - channels.channel, - reminders.utc_time, - reminders.interval_seconds, - reminders.interval_days, - reminders.interval_months, - reminders.expires, - reminders.enabled, - reminders.content, - reminders.embed_description, - users.user AS set_by -FROM - reminders -INNER JOIN - channels -ON - channels.id = reminders.channel_id -LEFT JOIN - users -ON - reminders.set_by = users.id -WHERE - `status` = 'pending' AND - channels.id = (SELECT dm_channel FROM users WHERE user = ?) - ", - user.as_u64() + SELECT + reminders.id, + reminders.uid, + channels.channel, + reminders.utc_time, + reminders.interval_seconds, + reminders.interval_days, + reminders.interval_months, + reminders.expires, + reminders.enabled, + reminders.content, + reminders.embed_description, + users.user AS set_by + FROM + reminders + INNER JOIN + channels + ON + channels.id = reminders.channel_id + LEFT JOIN + users + ON + reminders.set_by = users.id + WHERE + `status` = 'pending' AND + channels.id = (SELECT dm_channel FROM users WHERE user = ?) + ", + user.get() ) .fetch_all(pool) .await @@ -304,10 +311,15 @@ WHERE &self, db: impl Executor<'_, Database = Database>, ) -> Result<(), sqlx::Error> { - sqlx::query!("UPDATE reminders SET `status` = 'deleted' WHERE uid = ?", self.uid) - .execute(db) - .await - .map(|_| ()) + sqlx::query!( + " + UPDATE reminders SET `status` = 'deleted' WHERE uid = ? + ", + self.uid + ) + .execute(db) + .await + .map(|_| ()) } pub fn display_content(&self) -> &str { diff --git a/src/models/user_data.rs b/src/models/user_data.rs index 647d95e..9e080ae 100644 --- a/src/models/user_data.rs +++ b/src/models/user_data.rs @@ -18,7 +18,7 @@ impl UserData { where U: Into, { - let user_id = user.into().as_u64().to_owned(); + let user_id = user.into().get().to_owned(); match sqlx::query!( " @@ -50,7 +50,7 @@ SELECT IFNULL(timezone, 'UTC') AS timezone FROM users WHERE user = ? SELECT id, user, dm_channel, IF(timezone IS NULL, ?, timezone) AS timezone, allowed_dm FROM users WHERE user = ? ", *LOCAL_TIMEZONE, - user_id.0 + user_id.get() ) .fetch_one(pool) .await @@ -65,7 +65,7 @@ SELECT id, user, dm_channel, IF(timezone IS NULL, ?, timezone) AS timezone, allo " INSERT IGNORE INTO channels (channel) VALUES (?) ", - dm_channel.id.0 + dm_channel.id.get() ) .execute(&pool_c) .await?; @@ -74,8 +74,8 @@ INSERT IGNORE INTO channels (channel) VALUES (?) " INSERT INTO users (name, user, dm_channel, timezone) VALUES ('', ?, (SELECT id FROM channels WHERE channel = ?), ?) ", - user_id.0, - dm_channel.id.0, + user_id.get(), + dm_channel.id.get(), *LOCAL_TIMEZONE ) .execute(&pool_c) @@ -86,7 +86,7 @@ INSERT INTO users (name, user, dm_channel, timezone) VALUES ('', ?, (SELECT id F " SELECT id, user, dm_channel, timezone, allowed_dm FROM users WHERE user = ? ", - user_id.0 + user_id.get() ) .fetch_one(pool) .await?) diff --git a/src/utils.rs b/src/utils.rs index 7d882c5..3ebc3e7 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,51 +1,21 @@ use poise::{ - serenity_prelude as serenity, serenity_prelude::{ - builder::CreateApplicationCommands, http::CacheHttp, - interaction::MessageFlags, model::id::{GuildId, UserId}, + CreateInteractionResponseMessage, }, + CreateReply, }; -use crate::{ - consts::{CNC_GUILD, SUBSCRIPTION_ROLES}, - Data, Error, -}; - -pub async fn register_application_commands( - ctx: &serenity::Context, - framework: &poise::Framework, - guild_id: Option, -) -> Result<(), serenity::Error> { - let mut commands_builder = CreateApplicationCommands::default(); - let commands = &framework.options().commands; - for command in commands { - if let Some(slash_command) = command.create_as_slash_command() { - commands_builder.add_application_command(slash_command); - } - if let Some(context_menu_command) = command.create_as_context_menu_command() { - commands_builder.add_application_command(context_menu_command); - } - } - let commands_builder = poise::serenity_prelude::json::Value::Array(commands_builder.0); - - if let Some(guild_id) = guild_id { - ctx.http.create_guild_application_commands(guild_id.0, &commands_builder).await?; - } else { - ctx.http.create_global_application_commands(&commands_builder).await?; - } - - Ok(()) -} +use crate::consts::{CNC_GUILD, SUBSCRIPTION_ROLES}; pub async fn check_subscription(cache_http: impl CacheHttp, user_id: impl Into) -> bool { if let Some(subscription_guild) = *CNC_GUILD { - let guild_member = GuildId(subscription_guild).member(cache_http, user_id).await; + let guild_member = GuildId::new(subscription_guild).member(cache_http, user_id).await; if let Ok(member) = guild_member { for role in member.roles { - if SUBSCRIPTION_ROLES.contains(role.as_u64()) { + if SUBSCRIPTION_ROLES.contains(&role.get()) { return true; } } @@ -70,39 +40,14 @@ pub async fn check_guild_subscription( } } -/// Sends the message, specified via [`crate::CreateReply`], to the interaction initial response -/// endpoint -pub fn send_as_initial_response( - data: poise::CreateReply<'_>, - f: &mut serenity::CreateInteractionResponseData, -) { - let poise::CreateReply { - content, - embeds, - attachments: _, // serenity doesn't support attachments in initial response yet - components, - ephemeral, - allowed_mentions, - reply: _, - } = data; +pub fn reply_to_interaction_response_message( + reply: CreateReply, +) -> CreateInteractionResponseMessage { + let mut builder = CreateInteractionResponseMessage::new().embeds(reply.embeds); - if let Some(content) = content { - f.content(content); - } - f.set_embeds(embeds); - if let Some(allowed_mentions) = allowed_mentions { - f.allowed_mentions(|f| { - *f = allowed_mentions.clone(); - f - }); - } - if let Some(components) = components { - f.components(|f| { - f.0 = components.0; - f - }); - } - if ephemeral { - f.flags(MessageFlags::EPHEMERAL); + if let Some(components) = reply.components { + builder = builder.components(components) } + + builder } diff --git a/web/Cargo.toml b/web/Cargo.toml index e10e8ef..b09e665 100644 --- a/web/Cargo.toml +++ b/web/Cargo.toml @@ -7,10 +7,10 @@ edition = "2018" [dependencies] rocket = { git = "https://github.com/SergioBenitez/Rocket", branch = "master", features = ["tls", "secrets", "json"] } rocket_dyn_templates = { git = "https://github.com/SergioBenitez/Rocket", branch = "master", features = ["tera"] } -serenity = { version = "0.11", default-features = false, features = ["builder", "cache", "client", "gateway", "http", "model", "utils", "rustls_backend"] } +serenity = { version = "0.12", default-features = false, features = ["builder", "cache", "client", "gateway", "http", "model", "utils", "rustls_backend"] } oauth2 = "4" log = "0.4" -reqwest = "0.11" +reqwest = { version = "0.11", features = ["json"] } serde = { version = "1.0", features = ["derive"] } sqlx = { version = "0.7", features = ["runtime-tokio-rustls", "macros", "mysql", "chrono", "json"] } chrono = "0.4" @@ -20,3 +20,4 @@ rand = "0.8" base64 = "0.13" csv = "1.2" prometheus = "0.13.3" +secrecy = "0.8.0" diff --git a/web/src/consts.rs b/web/src/consts.rs index 957eaf4..f2828cb 100644 --- a/web/src/consts.rs +++ b/web/src/consts.rs @@ -23,14 +23,13 @@ pub const CHARACTERS: &str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWX use std::{collections::HashSet, env, iter::FromIterator}; use lazy_static::lazy_static; -use serenity::model::prelude::AttachmentType; +use serenity::builder::CreateAttachment; lazy_static! { - pub static ref DEFAULT_AVATAR: AttachmentType<'static> = ( + pub static ref DEFAULT_AVATAR: CreateAttachment = CreateAttachment::bytes( include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/../assets/webhook.jpg")) as &[u8], "webhook.jpg", - ) - .into(); + ); pub static ref SUBSCRIPTION_ROLES: HashSet = HashSet::from_iter( env::var("PATREON_ROLE_ID") .map(|var| var diff --git a/web/src/lib.rs b/web/src/lib.rs index b340464..fba7e4c 100644 --- a/web/src/lib.rs +++ b/web/src/lib.rs @@ -35,8 +35,8 @@ type Database = MySql; #[derive(Debug)] enum Error { - SQLx(sqlx::Error), - Serenity(serenity::Error), + SQLx, + Serenity, } pub async fn initialize( @@ -169,11 +169,11 @@ pub async fn check_subscription(cache_http: impl CacheHttp, user_id: impl Into bool { offline!(true); - if let Some(guild) = cache_http.cache().unwrap().guild(guild_id) { - let owner = guild.owner_id; - + if let Some(owner) = cache_http.cache().unwrap().guild(guild_id).map(|guild| guild.owner_id) { check_subscription(&cache_http, owner).await } else { false @@ -203,7 +201,7 @@ pub async fn check_guild_subscription( pub async fn check_authorization( cookies: &CookieJar<'_>, ctx: &Context, - guild: u64, + guild_id: u64, ) -> Result<(), JsonValue> { let user_id = cookies.get_private("userid").map(|c| c.value().parse::().ok()).flatten(); @@ -217,38 +215,30 @@ pub async fn check_authorization( return Ok(()); } - match GuildId(guild).to_guild_cached(ctx) { - Some(guild) => { - let member_res = guild.member(ctx, UserId(user_id)).await; + let guild_id = GuildId::new(guild_id); - match member_res { + match guild_id.member(ctx, UserId::new(user_id)).await { + Ok(member) => { + let permissions_res = member.permissions(ctx); + + match permissions_res { Err(_) => { - return Err(json!({"error": "User not in guild"})); + return Err(json!({"error": "Couldn't fetch permissions"})); } - Ok(member) => { - let permissions_res = member.permissions(ctx); - - match permissions_res { - Err(_) => { - return Err(json!({"error": "Couldn't fetch permissions"})); - } - - Ok(permissions) => { - if !(permissions.manage_messages() - || permissions.manage_guild() - || permissions.administrator()) - { - return Err(json!({"error": "Incorrect permissions"})); - } - } + Ok(permissions) => { + if !(permissions.manage_messages() + || permissions.manage_guild() + || permissions.administrator()) + { + return Err(json!({"error": "Incorrect permissions"})); } } } } - None => { - return Err(json!({"error": "Bot not in guild"})); + Err(_) => { + return Err(json!({"error": "User not in guild"})); } } } diff --git a/web/src/routes/dashboard/api/guild/channels.rs b/web/src/routes/dashboard/api/guild/channels.rs index bc07dc3..2be4085 100644 --- a/web/src/routes/dashboard/api/guild/channels.rs +++ b/web/src/routes/dashboard/api/guild/channels.rs @@ -32,13 +32,13 @@ pub async fn get_guild_channels( }]))); check_authorization(cookies, ctx.inner(), id).await?; - match GuildId(id).to_guild_cached(ctx.inner()) { + match GuildId::new(id).to_guild_cached(ctx.inner()) { Some(guild) => { let mut channels = guild .channels .iter() - .filter_map(|(id, channel)| channel.to_owned().guild().map(|c| (id.to_owned(), c))) .filter(|(_, channel)| channel.is_text_based()) + .map(|(id, channel)| (id.to_owned(), channel.to_owned())) .collect::>(); channels.sort_by(|(_, c1), (_, c2)| c1.position.cmp(&c2.position)); diff --git a/web/src/routes/dashboard/api/guild/mod.rs b/web/src/routes/dashboard/api/guild/mod.rs index 0798150..1ac553a 100644 --- a/web/src/routes/dashboard/api/guild/mod.rs +++ b/web/src/routes/dashboard/api/guild/mod.rs @@ -22,19 +22,22 @@ pub async fn get_guild_info(id: u64, cookies: &CookieJar<'_>, ctx: &State { - let member_res = GuildId(env::var("PATREON_GUILD_ID").unwrap().parse().unwrap()) - .member(&ctx.inner(), guild.owner_id) + match GuildId::new(id) + .to_guild_cached(ctx.inner()) + .map(|guild| (guild.owner_id, guild.name.clone())) + { + Some((owner_id, name)) => { + let member_res = GuildId::new(env::var("PATREON_GUILD_ID").unwrap().parse().unwrap()) + .member(&ctx.inner(), owner_id) .await; let patreon = member_res.map_or(false, |member| { member .roles - .contains(&RoleId(env::var("PATREON_ROLE_ID").unwrap().parse().unwrap())) + .contains(&RoleId::new(env::var("PATREON_ROLE_ID").unwrap().parse().unwrap())) }); - Ok(json!({ "patreon": patreon, "name": guild.name })) + Ok(json!({ "patreon": patreon, "name": name })) } None => json_err!("Bot not in guild"), diff --git a/web/src/routes/dashboard/api/guild/reminders.rs b/web/src/routes/dashboard/api/guild/reminders.rs index 9b7c6e2..0c9c839 100644 --- a/web/src/routes/dashboard/api/guild/reminders.rs +++ b/web/src/routes/dashboard/api/guild/reminders.rs @@ -38,8 +38,8 @@ pub async fn create_guild_reminder( match create_reminder( ctx.inner(), &mut transaction, - GuildId(id), - UserId(user_id), + GuildId::new(id), + UserId::new(user_id), reminder.into_inner(), ) .await @@ -65,14 +65,14 @@ pub async fn get_reminders( ) -> JsonResult { check_authorization(cookies, ctx.inner(), id).await?; - let channels_res = GuildId(id).channels(&ctx.inner()).await; + let channels_res = GuildId::new(id).channels(&ctx.inner()).await; match channels_res { Ok(channels) => { let channels = channels .keys() .into_iter() - .map(|k| k.as_u64().to_string()) + .map(|k| k.get().to_string()) .collect::>() .join(","); @@ -253,10 +253,12 @@ pub async fn edit_reminder( } if reminder.channel > 0 { - let channel = ChannelId(reminder.channel).to_channel_cached(&ctx.inner()); - match channel { - Some(channel) => { - let channel_matches_guild = channel.guild().map_or(false, |c| c.guild_id.0 == id); + let channel_guild = ChannelId::new(reminder.channel) + .to_channel_cached(&ctx.cache) + .map(|channel| channel.guild_id); + match channel_guild { + Some(channel_guild) => { + let channel_matches_guild = channel_guild.get() == id; if !channel_matches_guild { warn!( @@ -269,7 +271,7 @@ pub async fn edit_reminder( let channel = create_database_channel( ctx.inner(), - ChannelId(reminder.channel), + ChannelId::new(reminder.channel), &mut transaction, ) .await; diff --git a/web/src/routes/dashboard/api/user/mod.rs b/web/src/routes/dashboard/api/user/mod.rs index 7e3079d..1e0a588 100644 --- a/web/src/routes/dashboard/api/user/mod.rs +++ b/web/src/routes/dashboard/api/user/mod.rs @@ -39,7 +39,7 @@ pub async fn get_user_info( if let Some(user_id) = cookies.get_private("userid").map(|u| u.value().parse::().ok()).flatten() { - let member_res = GuildId(env::var("PATREON_GUILD_ID").unwrap().parse().unwrap()) + let member_res = GuildId::new(env::var("PATREON_GUILD_ID").unwrap().parse().unwrap()) .member(&ctx.inner(), user_id) .await; @@ -58,7 +58,7 @@ pub async fn get_user_info( patreon: member_res.map_or(false, |member| { member .roles - .contains(&RoleId(env::var("PATREON_ROLE_ID").unwrap().parse().unwrap())) + .contains(&RoleId::new(env::var("PATREON_ROLE_ID").unwrap().parse().unwrap())) }), timezone, }; diff --git a/web/src/routes/dashboard/export.rs b/web/src/routes/dashboard/export.rs index 3a9a7cf..f00f9d6 100644 --- a/web/src/routes/dashboard/export.rs +++ b/web/src/routes/dashboard/export.rs @@ -33,14 +33,14 @@ pub async fn export_reminders( let mut csv_writer = WriterBuilder::new().quote_style(QuoteStyle::Always).from_writer(vec![]); - let channels_res = GuildId(id).channels(&ctx.inner()).await; + let channels_res = GuildId::new(id).channels(&ctx.inner()).await; match channels_res { Ok(channels) => { let channels = channels .keys() .into_iter() - .map(|k| k.as_u64().to_string()) + .map(|k| k.get().to_string()) .collect::>() .join(","); @@ -181,8 +181,8 @@ pub(crate) async fn import_reminders( create_reminder( ctx.inner(), &mut transaction, - GuildId(id), - UserId(user_id), + GuildId::new(id), + UserId::new(user_id), reminder, ) .await?; @@ -289,7 +289,7 @@ pub async fn import_todos( ) -> JsonResult { check_authorization(cookies, ctx.inner(), id).await?; - let channels_res = GuildId(id).channels(&ctx.inner()).await; + let channels_res = GuildId::new(id).channels(&ctx.inner()).await; match channels_res { Ok(channels) => match base64::decode(&body.body) { @@ -307,7 +307,7 @@ pub async fn import_todos( match channel_id.parse::() { Ok(channel_id) => { - if channels.contains_key(&ChannelId(channel_id)) { + if channels.contains_key(&ChannelId::new(channel_id)) { query_params.push((record.value, Some(channel_id), id)); } else { return json_err!(format!( diff --git a/web/src/routes/dashboard/mod.rs b/web/src/routes/dashboard/mod.rs index 7bc9243..40e474a 100644 --- a/web/src/routes/dashboard/mod.rs +++ b/web/src/routes/dashboard/mod.rs @@ -8,10 +8,12 @@ use rocket::{ response::Redirect, serde::json::json, }; +use secrecy::ExposeSecret; use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; use serenity::{ + all::CacheHttp, + builder::CreateWebhook, client::Context, - http::Http, model::id::{ChannelId, GuildId, UserId}, }; use sqlx::types::Json; @@ -363,12 +365,12 @@ pub(crate) async fn create_reminder( reminder: Reminder, ) -> JsonResult { // check guild in db - match sqlx::query!("SELECT 1 as A FROM guilds WHERE guild = ?", guild_id.0) + match sqlx::query!("SELECT 1 as A FROM guilds WHERE guild = ?", guild_id.get()) .fetch_one(transaction.executor()) .await { Err(sqlx::Error::RowNotFound) => { - if sqlx::query!("INSERT INTO guilds (guild) VALUES (?)", guild_id.0) + if sqlx::query!("INSERT INTO guilds (guild) VALUES (?)", guild_id.get()) .execute(transaction.executor()) .await .is_err() @@ -379,23 +381,26 @@ pub(crate) async fn create_reminder( _ => {} } - // validate channel - let channel = ChannelId(reminder.channel).to_channel_cached(&ctx); - let channel_exists = channel.is_some(); + { + // validate channel + let channel = ChannelId::new(reminder.channel).to_channel_cached(&ctx.cache); + let channel_exists = channel.is_some(); - let channel_matches_guild = - channel.map_or(false, |c| c.guild().map_or(false, |c| c.guild_id == guild_id)); + let channel_matches_guild = + channel.map_or(false, |c| c.guild(&ctx.cache).map_or(false, |c| c.id == guild_id)); - if !channel_matches_guild || !channel_exists { - warn!( - "Error in `create_reminder`: channel {} not found for guild {} (channel exists: {})", - reminder.channel, guild_id, channel_exists - ); + if !channel_matches_guild || !channel_exists { + warn!( + "Error in `create_reminder`: channel {} not found for guild {} (channel exists: {})", + reminder.channel, guild_id, channel_exists + ); - return Err(json!({"error": "Channel not found"})); + return Err(json!({"error": "Channel not found"})); + } } - let channel = create_database_channel(&ctx, ChannelId(reminder.channel), transaction).await; + let channel = + create_database_channel(&ctx, ChannelId::new(reminder.channel), transaction).await; if let Err(e) = channel { warn!("`create_database_channel` returned an error code: {:?}", e); @@ -590,32 +595,38 @@ pub(crate) async fn create_reminder( } async fn create_database_channel( - ctx: impl AsRef, + ctx: impl CacheHttp, channel: ChannelId, transaction: &mut Transaction<'_>, ) -> Result { - let row = - sqlx::query!("SELECT webhook_token, webhook_id FROM channels WHERE channel = ?", channel.0) - .fetch_one(transaction.executor()) - .await; + let row = sqlx::query!( + "SELECT webhook_token, webhook_id FROM channels WHERE channel = ?", + channel.get() + ) + .fetch_one(transaction.executor()) + .await; match row { Ok(row) => { if row.webhook_token.is_none() || row.webhook_id.is_none() { let webhook = channel - .create_webhook_with_avatar(&ctx, "Reminder", DEFAULT_AVATAR.clone()) + .create_webhook(&ctx, CreateWebhook::new("Reminder").avatar(&*DEFAULT_AVATAR)) .await - .map_err(|e| Error::Serenity(e))?; + .map_err(|_| Error::Serenity)?; + + let token = webhook.token.unwrap(); sqlx::query!( - "UPDATE channels SET webhook_id = ?, webhook_token = ? WHERE channel = ?", - webhook.id.0, - webhook.token, - channel.0 + " + UPDATE channels SET webhook_id = ?, webhook_token = ? WHERE channel = ? + ", + webhook.id.get(), + token.expose_secret(), + channel.get() ) .execute(transaction.executor()) .await - .map_err(|e| Error::SQLx(e))?; + .map_err(|_| Error::SQLx)?; } Ok(()) @@ -624,35 +635,39 @@ async fn create_database_channel( Err(sqlx::Error::RowNotFound) => { // create webhook let webhook = channel - .create_webhook_with_avatar(&ctx, "Reminder", DEFAULT_AVATAR.clone()) + .create_webhook(&ctx, CreateWebhook::new("Reminder").avatar(&*DEFAULT_AVATAR)) .await - .map_err(|e| Error::Serenity(e))?; + .map_err(|_| Error::Serenity)?; + + let token = webhook.token.unwrap(); // create database entry sqlx::query!( - "INSERT INTO channels ( + " + INSERT INTO channels ( webhook_id, webhook_token, channel - ) VALUES (?, ?, ?)", - webhook.id.0, - webhook.token, - channel.0 + ) VALUES (?, ?, ?) + ", + webhook.id.get(), + token.expose_secret(), + channel.get() ) .execute(transaction.executor()) .await - .map_err(|e| Error::SQLx(e))?; + .map_err(|_| Error::SQLx)?; Ok(()) } - Err(e) => Err(Error::SQLx(e)), + Err(_) => Err(Error::SQLx), }?; - let row = sqlx::query!("SELECT id FROM channels WHERE channel = ?", channel.0) + let row = sqlx::query!("SELECT id FROM channels WHERE channel = ?", channel.get()) .fetch_one(transaction.executor()) .await - .map_err(|e| Error::SQLx(e))?; + .map_err(|_| Error::SQLx)?; Ok(row.id) } diff --git a/web/src/routes/login.rs b/web/src/routes/login.rs index 5e8b9fe..1c903d9 100644 --- a/web/src/routes/login.rs +++ b/web/src/routes/login.rs @@ -102,10 +102,9 @@ pub async fn discord_callback( match user_res { Ok(user) => { - let user_name = format!("{}#{}", user.name, user.discriminator); - let user_id = user.id.as_u64().to_string(); + let user_id = user.id.get().to_string(); - cookies.add_private(Cookie::new("username", user_name)); + cookies.add_private(Cookie::new("username", user.name)); cookies.add_private(Cookie::new("userid", user_id)); Ok(Redirect::to(uri!(super::return_to_same_site("dashboard"))))