diff --git a/Cargo.lock b/Cargo.lock index 3c69325..eaf4330 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -171,12 +171,13 @@ checksum = "2554a3155fec064362507487171dcc4edc3df60cb10f3a1fb10ed8094822b120" dependencies = [ "chrono", "parse-zoneinfo", + "serde", ] [[package]] name = "command_attr" version = "0.3.7" -source = "git+https://github.com/serenity-rs/serenity?branch=next#5348e9c56909d7c534717825be20763acb33336d" +source = "git+https://github.com/serenity-rs/serenity?branch=next#723749c43182838925dd89ac90b93dd2a837261d" dependencies = [ "proc-macro2", "quote", @@ -318,9 +319,9 @@ dependencies = [ [[package]] name = "flate2" -version = "1.0.21" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80edafed416a46fb378521624fab1cfa2eb514784fd8921adbe8a8d8321da811" +checksum = "1e6988e897c1c9c485f43b47a529cef42fde0547f9d8d41a7062518f1d8fc53f" dependencies = [ "cfg-if", "crc32fast", @@ -694,6 +695,15 @@ version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68f2d64f2edebec4ce84ad108148e67e1064789bee435edc5b60ad398714a3a9" +[[package]] +name = "itertools" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69ddb889f9d0d08a67338271fa9b62996bc788c7796a5c18cf057420aaed5eaf" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "0.4.8" @@ -702,9 +712,9 @@ checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" [[package]] name = "js-sys" -version = "0.3.54" +version = "0.3.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1866b355d9c878e5e607473cbe3f63282c0b7aad2db1dbebf55076c686918254" +checksum = "7cc9ffccd38c451a86bf13657df244e9c3f37493cce8e5e21e940963777acc84" dependencies = [ "wasm-bindgen", ] @@ -726,9 +736,9 @@ checksum = "db13adb97ab515a3691f56e4dbab09283d0b86cb45abd991d8634a9d6f501760" [[package]] name = "libc" -version = "0.2.101" +version = "0.2.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cb00336871be5ed2c8ed44b60ae9959dc5b9f08539422ed43f09e34ecaeba21" +checksum = "a2a5ac8f984bfcf3a823267e5fde638acc3325f6496633a5da6bb6eb2171e103" [[package]] name = "libm" @@ -754,12 +764,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "maplit" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" - [[package]] name = "matches" version = "0.1.9" @@ -1433,9 +1437,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.67" +version = "1.0.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7f9e390c27c3c0ce8bc5d725f6e4d30a29d26659494aa4b17535f7522c5c950" +checksum = "0f690853975602e1bfe1ccbf50504d67174e3bcf340f23b5ea9992e0587a52d8" dependencies = [ "itoa", "ryu", @@ -1457,7 +1461,7 @@ dependencies = [ [[package]] name = "serenity" version = "0.10.9" -source = "git+https://github.com/serenity-rs/serenity?branch=next#5348e9c56909d7c534717825be20763acb33336d" +source = "git+https://github.com/serenity-rs/serenity?branch=next#723749c43182838925dd89ac90b93dd2a837261d" dependencies = [ "async-trait", "async-tungstenite", @@ -1545,9 +1549,9 @@ checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e" [[package]] name = "socket2" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "765f090f0e423d2b55843402a07915add955e7d60657db13707a159727326cad" +checksum = "5dc90fe6c7be1a323296982db1836d1ea9e47b6839496dde9a541bc496df3516" dependencies = [ "libc", "winapi", @@ -1561,14 +1565,12 @@ checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" [[package]] name = "sqlformat" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "684001e7985ec1a9a66963b77ed151ef22a7876b3fdd7e37a57ec774f54b7d96" +checksum = "b4b7922be017ee70900be125523f38bdd644f4f06a1b16e8fa5a8ee8c34bffd4" dependencies = [ - "lazy_static", - "maplit", + "itertools", "nom", - "regex", "unicode_categories", ] @@ -1765,9 +1767,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.3.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "848a1e1181b9f6753b5e96a092749e29b11d19ede67dfbbd6c7dc7e0f49b5338" +checksum = "5241dd6f21443a3606b432718b166d3cedc962fd4b8bea54a8bc7f514ebda986" dependencies = [ "tinyvec_macros", ] @@ -1863,9 +1865,9 @@ checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6" [[package]] name = "tracing" -version = "0.1.26" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09adeb8c97449311ccd28a427f96fb563e7fd31aabf994189879d9da2394b89d" +checksum = "c2ba9ab62b7d6497a8638dfda5e5c4fb3b2d5a7fca4118f2b96151c8ef1a437e" dependencies = [ "cfg-if", "log", @@ -1876,9 +1878,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.15" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c42e6fa53307c8a17e4ccd4dc81cf5ec38db9209f59b222210375b54ee40d1e2" +checksum = "98863d0dd09fa59a1b79c6750ad80dbda6b75f4e71c437a6a1a8cb91a8bcbd77" dependencies = [ "proc-macro2", "quote", @@ -1887,9 +1889,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.19" +version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ca517f43f0fb96e0c3072ed5c275fe5eece87e8cb52f4a77b69226d3b1c9df8" +checksum = "46125608c26121c81b0c6d693eab5a420e416da7e43c426d2e8f7df8da8a3acf" dependencies = [ "lazy_static", ] @@ -2044,9 +2046,9 @@ checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" [[package]] name = "wasm-bindgen" -version = "0.2.77" +version = "0.2.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e68338db6becec24d3c7977b5bf8a48be992c934b5d07177e3931f5dc9b076c" +checksum = "632f73e236b219150ea279196e54e610f5dbafa5d61786303d4da54f84e47fce" dependencies = [ "cfg-if", "serde", @@ -2056,9 +2058,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.77" +version = "0.2.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f34c405b4f0658583dba0c1c7c9b694f3cac32655db463b56c254a1c75269523" +checksum = "a317bf8f9fba2476b4b2c85ef4c4af8ff39c3c7f0cdfeed4f82c34a880aa837b" dependencies = [ "bumpalo", "lazy_static", @@ -2071,9 +2073,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.27" +version = "0.4.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a87d738d4abc4cf22f6eb142f5b9a81301331ee3c767f2fef2fda4e325492060" +checksum = "8e8d7523cb1f2a4c96c1317ca690031b714a51cc14e05f712446691f413f5d39" dependencies = [ "cfg-if", "js-sys", @@ -2083,9 +2085,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.77" +version = "0.2.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d5a6580be83b19dc570a8f9c324251687ab2184e57086f71625feb57ec77c8" +checksum = "d56146e7c495528bf6587663bea13a8eb588d39b36b679d83972e1a2dbbdacf9" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2093,9 +2095,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.77" +version = "0.2.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3775a030dc6f5a0afd8a84981a21cc92a781eb429acef9ecce476d0c9113e92" +checksum = "7803e0eea25835f8abdc585cd3021b3deb11543c6fe226dcd30b228857c5c5ab" dependencies = [ "proc-macro2", "quote", @@ -2106,15 +2108,15 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.77" +version = "0.2.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c279e376c7a8e8752a8f1eaa35b7b0bee6bb9fb0cdacfa97cc3f1f289c87e2b4" +checksum = "0237232789cf037d5480773fe568aac745bfe2afbc11a863e97901780a6b47cc" [[package]] name = "web-sys" -version = "0.3.54" +version = "0.3.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a84d70d1ec7d2da2d26a5bd78f4bca1b8c3254805363ce743b7a05bc30d195a" +checksum = "38eb105f1c59d9eaa6b5cdc92b859d85b926e82cb2e0945cd0c9259faa6fe9fb" dependencies = [ "js-sys", "wasm-bindgen", diff --git a/Cargo.toml b/Cargo.toml index 9dedada..bda59b8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,7 @@ regex = "1.4" log = "0.4" env_logger = "0.8" chrono = "0.4" -chrono-tz = "0.5" +chrono-tz = { version = "0.5", features = ["serde"] } lazy_static = "1.4" num-integer = "0.1" serde = "1.0" diff --git a/src/commands/reminder_cmds.rs b/src/commands/reminder_cmds.rs index 5a34be2..20599a7 100644 --- a/src/commands/reminder_cmds.rs +++ b/src/commands/reminder_cmds.rs @@ -14,12 +14,14 @@ use serenity::{ model::{ channel::{Channel, Message}, id::ChannelId, + interactions::message_component::ButtonStyle, misc::Mentionable, }, }; use crate::{ check_subscription_on_message, + component_models::{ComponentDataModel, LookPager, PageAction}, consts::{ EMBED_DESCRIPTION_MAX_LENGTH, REGEX_CHANNEL_USER, REGEX_NATURAL_COMMAND_1, REGEX_NATURAL_COMMAND_2, REGEX_REMIND_COMMAND, THEME_COLOR, @@ -296,6 +298,12 @@ async fn look(ctx: &Context, invoke: &(dyn CommandInvoke + Send + Sync), args: C invoke.channel_id() }; + let channel_name = if let Some(Channel::Guild(channel)) = channel_id.to_channel_cached(&ctx) { + Some(channel.name) + } else { + None + }; + let reminders = Reminder::from_channel(ctx, channel_id, &flags).await; if reminders.is_empty() { @@ -325,35 +333,84 @@ async fn look(ctx: &Context, invoke: &(dyn CommandInvoke + Send + Sync), args: C .fold(0, |t, r| t + r.len()) .div_ceil(EMBED_DESCRIPTION_MAX_LENGTH); - let _ = invoke + let page_first = ComponentDataModel::LookPager(LookPager { + flags: flags.clone(), + page: 0, + action: PageAction::First, + timezone, + }); + let page_prev = ComponentDataModel::LookPager(LookPager { + flags: flags.clone(), + page: 0, + action: PageAction::Previous, + timezone, + }); + let page_next = ComponentDataModel::LookPager(LookPager { + flags: flags.clone(), + page: 0, + action: PageAction::Next, + timezone, + }); + let page_last = ComponentDataModel::LookPager(LookPager { + flags: flags.clone(), + page: 0, + action: PageAction::Last, + timezone, + }); + + invoke .respond( ctx.http.clone(), CreateGenericResponse::new() .embed(|e| { - e.title(format!("Reminders on {}", channel_id.mention())) - .description(display) - .footer(|f| f.text(format!("Page {} of {}", 1, pages))) + 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| { comp.create_action_row(|row| { - row.create_button(|b| b.label("⏮️").custom_id(".1")) - .create_button(|b| b.label("◀️").custom_id(".2")) - .create_button(|b| b.label("▶️").custom_id(".3")) - .create_button(|b| b.label("⏭️").custom_id(".4")) + row.create_button(|b| { + b.label("⏮️") + .style(ButtonStyle::Primary) + .custom_id(page_first.to_custom_id()) + .disabled(true) + }) + .create_button(|b| { + b.label("◀️") + .style(ButtonStyle::Secondary) + .custom_id(page_prev.to_custom_id()) + .disabled(true) + }) + .create_button(|b| { + b.label("▶️") + .style(ButtonStyle::Secondary) + .custom_id(page_next.to_custom_id()) + .disabled(pages == 1) + }) + .create_button(|b| { + b.label("⏭️") + .style(ButtonStyle::Primary) + .custom_id(page_last.to_custom_id()) + .disabled(pages == 1) + }) }) }), ) - .await; + .await + .unwrap(); } } /* #[command("del")] +#[description("Delete reminders")] #[permission_level(Managed)] -async fn delete(ctx: &Context, msg: &Message, _args: String) { - let (pool, lm) = get_ctx_data(&ctx).await; - - let user_data = UserData::from_user(&msg.author, &ctx, &pool).await.unwrap(); +async fn delete(ctx: &Context, invoke: &(dyn CommandInvoke + Send + Sync)) { + let pool = ctx.data.read().await.get::().cloned().unwrap(); let _ = msg.channel_id.say(&ctx, lm.get(&user_data.language, "del/listing")).await; @@ -417,19 +474,6 @@ DELETE FROM reminders WHERE FIND_IN_SET(id, ?) .await .unwrap(); - if let Some(guild_id) = msg.guild_id { - let _ = sqlx::query!( - " -INSERT INTO events (event_name, bulk_count, guild_id, user_id) VALUES ('delete', ?, ?, ?) - ", - count_row.count, - guild_id.as_u64(), - user_data.id - ) - .execute(&pool) - .await; - } - let content = lm.get(&user_data.language, "del/count").replacen( "{}", &count_row.count.to_string(), diff --git a/src/component_models/mod.rs b/src/component_models/mod.rs index 20dac0b..2e95c50 100644 --- a/src/component_models/mod.rs +++ b/src/component_models/mod.rs @@ -1,13 +1,28 @@ use std::io::Cursor; +use chrono_tz::Tz; use rmp_serde::Serializer; use serde::{Deserialize, Serialize}; -use serenity::model::{ - id::{ChannelId, RoleId}, - interactions::message_component::MessageComponentInteraction, +use serenity::{ + builder::CreateEmbed, + client::Context, + model::{ + channel::Channel, + id::{ChannelId, RoleId}, + interactions::{ + message_component::{ButtonStyle, MessageComponentInteraction}, + InteractionResponseType, + }, + }, }; -use crate::models::reminder::look_flags::LookFlags; +use crate::{ + consts::{EMBED_DESCRIPTION_MAX_LENGTH, THEME_COLOR}, + models::{ + reminder::{look_flags::LookFlags, Reminder}, + user_data::UserData, + }, +}; #[derive(Deserialize, Serialize)] #[serde(tag = "type")] @@ -29,23 +44,160 @@ impl ComponentDataModel { rmp_serde::from_read(cur).unwrap() } - pub async fn act(&self, component: MessageComponentInteraction) { + pub async fn act(&self, ctx: &Context, component: MessageComponentInteraction) { match self { ComponentDataModel::Restrict(restrict) => { println!("{:?}", component.data.values); } - ComponentDataModel::LookPager(pager) => {} + ComponentDataModel::LookPager(pager) => { + let flags = pager.flags; + + let channel_opt = component.channel_id.to_channel_cached(&ctx); + + 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) + } else { + component.channel_id + } + } else { + component.channel_id + }; + + let reminders = Reminder::from_channel(ctx, channel_id, &flags).await; + + let pages = reminders + .iter() + .map(|reminder| reminder.display(&flags, &pager.timezone)) + .fold(0, |t, r| t + r.len()) + .div_ceil(EMBED_DESCRIPTION_MAX_LENGTH) as u16; + + let channel_name = + if let Some(Channel::Guild(channel)) = channel_id.to_channel_cached(&ctx) { + Some(channel.name) + } else { + None + }; + + let next_page = match pager.action { + PageAction::First => 0, + PageAction::Previous => 0.max(pager.page - 1), + PageAction::Next => (pages - 1).min(pager.page + 1), + PageAction::Last => pages - 1, + }; + + let mut char_count = 0; + let mut skip_char_count = 0; + + let display = reminders + .iter() + .map(|reminder| reminder.display(&flags, &pager.timezone)) + .skip_while(|p| { + skip_char_count += p.len(); + + skip_char_count < EMBED_DESCRIPTION_MAX_LENGTH * next_page as usize + }) + .take_while(|p| { + char_count += p.len(); + + char_count < EMBED_DESCRIPTION_MAX_LENGTH + }) + .collect::>() + .join("\n"); + + let page_first = ComponentDataModel::LookPager(LookPager { + flags: flags.clone(), + page: next_page, + action: PageAction::First, + timezone: pager.timezone, + }); + let page_prev = ComponentDataModel::LookPager(LookPager { + flags: flags.clone(), + page: next_page, + action: PageAction::Previous, + timezone: pager.timezone, + }); + let page_next = ComponentDataModel::LookPager(LookPager { + flags: flags.clone(), + page: next_page, + action: PageAction::Next, + timezone: pager.timezone, + }); + let page_last = ComponentDataModel::LookPager(LookPager { + flags: flags.clone(), + page: next_page, + action: PageAction::Last, + timezone: pager.timezone, + }); + + let mut embed = CreateEmbed::default(); + embed + .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))) + .color(*THEME_COLOR); + + let _ = + component + .create_interaction_response(&ctx, |r| { + r.kind(InteractionResponseType::UpdateMessage) + .interaction_response_data(|response| { + response.embeds(vec![embed]).components(|comp| { + comp.create_action_row(|row| { + row.create_button(|b| { + b.label("⏮️") + .style(ButtonStyle::Primary) + .custom_id(page_first.to_custom_id()) + .disabled(next_page == 0) + }) + .create_button(|b| { + b.label("◀️") + .style(ButtonStyle::Secondary) + .custom_id(page_prev.to_custom_id()) + .disabled(next_page == 0) + }) + .create_button(|b| { + b.label("▶️") + .style(ButtonStyle::Secondary) + .custom_id(page_next.to_custom_id()) + .disabled(next_page + 1 == pages) + }) + .create_button(|b| { + b.label("⏭️") + .style(ButtonStyle::Primary) + .custom_id(page_last.to_custom_id()) + .disabled(next_page + 1 == pages) + }) + }) + }) + }) + }) + .await; + } } } } -#[derive(Deserialize, Serialize)] +#[derive(Serialize, Deserialize)] pub struct Restrict { pub role_id: RoleId, } -#[derive(Deserialize, Serialize)] +#[derive(Serialize, Deserialize, Debug)] +pub enum PageAction { + First = 0, + Previous = 1, + Next = 2, + Last = 3, +} + +#[derive(Serialize, Deserialize, Debug)] pub struct LookPager { pub flags: LookFlags, - pub page_request: u16, + pub page: u16, + pub action: PageAction, + pub timezone: Tz, } diff --git a/src/framework.rs b/src/framework.rs index 938b183..36c37cf 100644 --- a/src/framework.rs +++ b/src/framework.rs @@ -1,3 +1,5 @@ +// todo move framework to its own module, split out permission checks + use std::{ collections::{HashMap, HashSet}, hash::{Hash, Hasher}, diff --git a/src/main.rs b/src/main.rs index a9fe418..1f7ac9a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -258,7 +258,7 @@ DELETE FROM guilds WHERE guild = ? } Interaction::MessageComponent(component) => { let component_model = ComponentDataModel::from_custom_id(&component.data.custom_id); - component_model.act(component).await; + component_model.act(&ctx, component).await; } _ => {} } diff --git a/src/models/reminder/look_flags.rs b/src/models/reminder/look_flags.rs index dde4568..03eb5db 100644 --- a/src/models/reminder/look_flags.rs +++ b/src/models/reminder/look_flags.rs @@ -3,13 +3,13 @@ use serenity::model::id::ChannelId; use crate::consts::REGEX_CHANNEL; -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, Copy, Clone, Debug)] pub enum TimeDisplayType { Absolute = 0, Relative = 1, } -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, Copy, Clone, Debug)] pub struct LookFlags { pub show_disabled: bool, pub channel_id: Option,