diff options
Diffstat (limited to 'src/command')
-rw-r--r-- | src/command/mod.rs | 24 | ||||
-rw-r--r-- | src/command/revise.rs | 151 |
2 files changed, 174 insertions, 1 deletions
diff --git a/src/command/mod.rs b/src/command/mod.rs index 165cdb6..73b292a 100644 --- a/src/command/mod.rs +++ b/src/command/mod.rs @@ -1,8 +1,10 @@ mod add_points; mod leaderboard; +mod revise; pub use add_points::add_points; pub use leaderboard::leaderboard; +pub use revise::revise; use std::sync::Arc; @@ -27,6 +29,7 @@ pub enum Commands { About, Leaderboard, Points, + Revise, Permission, Import, } @@ -43,6 +46,7 @@ pub async fn handler( Commands::About => about(brain.clone(), guild), Commands::Leaderboard => leaderboard(brain.clone(), guild, Some(command_data)), Commands::Points => add_points(brain.clone(), guild, create, command_data).await, + Commands::Revise => revise(brain.clone(), guild, create, command_data).await, Commands::Permission => permission(brain.clone(), guild, create, command_data).await, Commands::Import => { import(brain.clone(), guild, create, command_data).await; @@ -82,6 +86,24 @@ pub async fn build() -> Vec<Command> { .unwrap() .build(); + let revise = CommandBuilder::new( + "revise", + "Change history by adding/removing points at a specific point in time", + CommandType::ChatInput, + ) + .option(IntegerBuilder::new("points", "number of points. - or +").required(true)) + .option(StringBuilder::new("users", "mention people to modify their points!!").required(true)) + .option( + StringBuilder::new( + "date", + "Date string in the YYYY-DD-MM format, with optional time component (HH:MM:SS, 24-hour time)", + ) + .required(true), + ) + .validate() + .unwrap() + .build(); + let permission = CommandBuilder::new( "permission", "set who is allowed to change points", @@ -104,7 +126,7 @@ pub async fn build() -> Vec<Command> { ) .build(); - vec![about, leaderboard, points, permission, import] + vec![about, leaderboard, points, revise, permission, import] } pub fn about(_brain: Arc<Brain>, _guild: Id<GuildMarker>) -> InteractionResponseData { diff --git a/src/command/revise.rs b/src/command/revise.rs new file mode 100644 index 0000000..ecb97d8 --- /dev/null +++ b/src/command/revise.rs @@ -0,0 +1,151 @@ +use std::sync::Arc; + +use time::{ + Date, OffsetDateTime, PrimitiveDateTime, Time, format_description, macros::format_description, +}; +use twilight_model::{ + application::interaction::application_command::{CommandData, CommandOptionValue}, + gateway::payload::incoming::InteractionCreate, + http::interaction::InteractionResponseData, + id::{ + Id, + marker::{GuildMarker, UserMarker}, + }, +}; +use twilight_util::builder::{InteractionResponseDataBuilder, embed::EmbedBuilder}; + +use crate::{ + bail, + brain::Brain, + database::{BoardRow, Error as DbError, PermissionSetting}, + success, util, +}; + +pub async fn revise( + brain: Arc<Brain>, + guild: Id<GuildMarker>, + create: &InteractionCreate, + data: &CommandData, +) -> InteractionResponseData { + let mut points = None; + let mut users: Vec<Id<UserMarker>> = vec![]; + let mut date_string = None; + + for opt in &data.options { + match opt.name.as_str() { + "points" => match opt.value { + CommandOptionValue::Integer(num) => points = Some(num), + _ => unreachable!(), + }, + "users" => match &opt.value { + CommandOptionValue::String(raw) => { + let mentions = util::extract_user_mentions(&raw); + users = mentions; + } + _ => unreachable!(), + }, + "date" => match &opt.value { + CommandOptionValue::String(raw) => { + date_string = Some(raw); + } + _ => unreachable!(), + }, + _ => unreachable!(), + } + } + + let date_fmt = format_description::parse("[year]-[month]-[day]").unwrap(); + let datetime_fmt = + format_description::parse("[year]-[month]-[day] [hour]:[minute]:[second]").unwrap(); + + if users.len() == 0 { + bail!("No users mentioned! Who do we add points to?") + } + + if let Some(date) = date_string { + println!("date = \"{date}\""); + } + + let date = match date_string { + None => bail!("What date were these points earned?"), + Some(str) if str.is_empty() => bail!("You must specify a date!"), + Some(date) => match Date::parse(date.trim(), &date_fmt) { + Err(_e) => match PrimitiveDateTime::parse(date.trim(), &datetime_fmt) { + Err(_e) => bail!( + "Cannot parse the given date/time. Did you write it like \"2025-04-13\" or, with time, \"2025-04-13 02:33\"?" + ), + Ok(datetime) => datetime, + }, + Ok(date) => PrimitiveDateTime::new(date, Time::from_hms(12, 0, 0).unwrap()), + }, + }; + + let (points, points_display, points_verb) = match points { + Some(p) if p > 0 => (p, p, "added to"), + Some(p) if p < 0 => (p, -p, "removed from"), + Some(0) => { + return success!("adding 0 points is a no-operation! I won't do anything :)"); + } + Some(_) | None => unreachable!(), + }; + + brain.create_leaderboard_if_not_exists(guild); + + let settings = brain.db.get_leaderboard_settings(guild.get()).unwrap(); + if let PermissionSetting::RoleRequired = settings.permission { + if let Some(role) = settings.role { + let member = create.member.clone().unwrap(); + let found_role = member.roles.iter().find(|vrole| vrole.get() == role); + + if found_role.is_none() { + bail!("You do not have the right permissions to change the score"); + } + } else { + // Seeing as the role is a required input on the subcommand, this + // would only happen with direct database fiddling or like, some + // really weird arcane bullshit? + bail!( + "Permissions set to Role Required, but no role is set. \ + This shouldn't be able to happen. \ + Maybe try to set the permissions again?" + ); + } + } + + for user in &users { + match brain.db.give_user_points(guild.get(), user.get(), points) { + Err(DbError::UserNotExist) => { + let member = brain.guild_member(guild, *user).await; + let row = BoardRow { + user_id: user.get(), + user_handle: member.handle, + user_nickname: member.nick.or(member.global_name), + points, + }; + brain.db.add_user_to_leaderboard(guild.get(), row).unwrap(); + } + _ => (), + } + + //FIXME: gen 2025-08-14: do not assume UTC! + brain + .db + .revise_last_history_date(guild.get(), user.get(), points, date.assume_utc()) + .unwrap(); + } + + let users_string = users + .into_iter() + .map(|u| format!("<@{u}>")) + .collect::<Vec<String>>() + .join(", "); + + let msg = format!( + "{points_display} points {points_verb} {users_string} on date {}", + date.format(&datetime_fmt).unwrap() + ); + let embed = EmbedBuilder::new().description(msg).build(); + InteractionResponseDataBuilder::new() + .embeds([embed]) + .build() +} |