about summary refs log tree commit diff
path: root/src/command
diff options
context:
space:
mode:
authorgennyble <gen@nyble.dev>2025-08-14 23:17:13 -0500
committergennyble <gen@nyble.dev>2025-08-14 23:17:13 -0500
commitd997d8a3d640cf36b0e705db249e1a8865f0b54f (patch)
treee1478fd88f515755fa0fd1db98ff4f7c31d4b815 /src/command
parent2e56bb762c4a521efd57d1c51796e9f096a7cfe0 (diff)
downloadleaberblord-d997d8a3d640cf36b0e705db249e1a8865f0b54f.tar.gz
leaberblord-d997d8a3d640cf36b0e705db249e1a8865f0b54f.zip
slightly scuffed revise command
Diffstat (limited to 'src/command')
-rw-r--r--src/command/mod.rs24
-rw-r--r--src/command/revise.rs151
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()
+}