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, guild: Id, create: &InteractionCreate, data: &CommandData, ) -> InteractionResponseData { let mut points = None; let mut users: Vec> = 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::>() .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() }