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; use twilight_model::{ application::{ command::{Command, CommandType}, interaction::application_command::{CommandData, CommandOptionValue}, }, gateway::payload::incoming::InteractionCreate, http::interaction::InteractionResponseData, id::{Id, marker::GuildMarker}, }; use twilight_util::builder::{ InteractionResponseDataBuilder, command::{CommandBuilder, IntegerBuilder, RoleBuilder, StringBuilder, SubCommandBuilder}, embed::{EmbedBuilder, EmbedFieldBuilder}, }; use crate::{bail, brain::Brain, database::PermissionSetting, fail, import, success}; pub enum Commands { About, Leaderboard, Points, Revise, Permission, Import, } pub async fn handler( brain: Arc, guild: Id, create: &InteractionCreate, command: Commands, command_data: &CommandData, ) { // Handle the command and create our interaction response data let data = match command { 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; return; } }; // Finally, send back the response brain.interaction_respond(create, data).await; } pub async fn build() -> Vec { let about = CommandBuilder::new( "about", "Get information about the bot", CommandType::ChatInput, ) .build(); let leaderboard = CommandBuilder::new( "leaderboard", "View the server leaderboard", CommandType::ChatInput, ) .option( StringBuilder::new("style", "style of leaderboard to display") .choices([("Ties Equal", "equal"), ("Ties Broken", "broken")]), ) .build(); let points = CommandBuilder::new("points", "Add and remove points!", CommandType::ChatInput) .option(IntegerBuilder::new("points", "number of points. - or +").required(true)) .option( StringBuilder::new("users", "mention people to modify their points!!").required(true), ) .validate() .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", CommandType::ChatInput, ) .option( SubCommandBuilder::new("role", "require a role to change the score") .option(RoleBuilder::new("role", "role required").required(true)), ) .option(SubCommandBuilder::new( "none", "anyone can change the score", )) .build(); let import = CommandBuilder::new( "import", "import this server's emboard leaderboard by adding the points in emboard, to leaberblord's", CommandType::ChatInput, ) .build(); vec![about, leaderboard, points, revise, permission, import] } pub fn about(_brain: Arc, _guild: Id) -> InteractionResponseData { let embed = EmbedBuilder::new() .title("About Leaberblord") .description( "A single-purpose bot for keeping score. Written in Rust using the twilight set of crates.", ).field(EmbedFieldBuilder::new("Source", "The source is available on the author's [cgit instance](https://git.dreamy.place/whimsy/leaberblord/about)")) .field(EmbedFieldBuilder::new("Author", "Written by @gennyble. Her homepage is [dreamy.place](https://dreamy.place)")) .color(0x33aa88).build(); InteractionResponseDataBuilder::new() .embeds([embed]) .build() } pub async fn permission( brain: Arc, guild: Id, create: &InteractionCreate, data: &CommandData, ) -> InteractionResponseData { let member = create.member.clone().unwrap(); let permissions = member.permissions.unwrap(); if !permissions.contains(twilight_model::guild::Permissions::MANAGE_GUILD) { bail!("You must have the Manage Server permission to perform this action"); } for opt in &data.options { return match opt.name.as_str() { "none" => permission_none(brain, guild), "role" => permission_role(brain, guild, opt.value.clone()), _ => { eprintln!("permission command, unknown option '{}'", opt.name); fail!("this should be unreachable. report the bug maybe? to gennyble on discord") } }; } fail!("this should be unreachable. report the bug maybe? to gennyble on discord") } fn permission_none(brain: Arc, guild: Id) -> InteractionResponseData { if brain.create_leaderboard_if_not_exists(guild) { // If it already existed, we might not be on default none. So set it. brain .db .set_leaderboard_permission(guild.get(), PermissionSetting::None) .unwrap(); } success!("Permission for leaderboard changed to none. Anyone may now set the score.") } fn permission_role( brain: Arc, guild: Id, data: CommandOptionValue, ) -> InteractionResponseData { let role = if let CommandOptionValue::SubCommand(data) = data { match data.iter().find(|d| d.name == "role").unwrap().value { CommandOptionValue::Role(role) => role, _ => panic!(), } } else { panic!() }; brain.create_leaderboard_if_not_exists(guild); brain .db .set_leaderboard_permission(guild.get(), PermissionSetting::RoleRequired) .unwrap(); brain .db .set_leaderboard_permission_role(guild.get(), role.get()) .unwrap(); success!(format!( "Permission for leaderboard changed to Role Required. \ Only members of <@&{role}> may now set the score" )) } pub async fn import( brain: Arc, guild: Id, create: &InteractionCreate, _data: &CommandData, ) { let member = create.member.clone().unwrap(); let permissions = member.permissions.unwrap(); if !permissions.contains(twilight_model::guild::Permissions::MANAGE_GUILD) { brain .interaction_respond( create, fail!("You must have the Manage Server permission to perform this action"), ) .await; } else { import::emboard_direct::import(brain, guild, create).await; } }