diff options
Diffstat (limited to 'src/main.rs')
-rw-r--r-- | src/main.rs | 208 |
1 files changed, 172 insertions, 36 deletions
diff --git a/src/main.rs b/src/main.rs index a10cf23..98e7a5f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,6 @@ use std::{env, error::Error, sync::Arc}; -use database::{BoardRow, Database, Error as DbError}; +use database::{BoardRow, Database, Error as DbError, PermissionSetting}; use twilight_cache_inmemory::{DefaultInMemoryCache, ResourceType}; use twilight_gateway::{Event, EventTypeFlags, Intents, Shard, ShardId, StreamExt}; use twilight_http::{Client as HttpClient, client::InteractionClient}; @@ -9,7 +9,7 @@ use twilight_model::{ command::{Command, CommandType}, interaction::{ InteractionData, - application_command::{CommandData, CommandOptionValue}, + application_command::{CommandData, CommandDataOption, CommandOptionValue}, }, }, channel::message::MessageFlags, @@ -22,7 +22,10 @@ use twilight_model::{ }; use twilight_util::builder::{ InteractionResponseDataBuilder, - command::{CommandBuilder, IntegerBuilder, NumberBuilder, StringBuilder, UserBuilder}, + command::{ + CommandBuilder, IntegerBuilder, MentionableBuilder, NumberBuilder, RoleBuilder, + StringBuilder, SubCommandBuilder, SubCommandGroupBuilder, UserBuilder, + }, embed::{EmbedBuilder, EmbedFieldBuilder}, }; @@ -30,6 +33,30 @@ mod database; const APP_ID: u64 = 1363055126264283136; +macro_rules! bail { + ($msg:expr) => { + return InteractionResponseDataBuilder::new() + .content(&format!("❌ {}", $msg)) + .build() + }; +} + +macro_rules! fail { + ($msg:expr) => { + InteractionResponseDataBuilder::new() + .content(&format!("❌ {}", $msg)) + .build() + }; +} + +macro_rules! success { + ($msg:expr) => { + InteractionResponseDataBuilder::new() + .content(&format!("✅ {}", $msg)) + .build() + }; +} + #[tokio::main] async fn main() -> anyhow::Result<()> { dotenv::dotenv()?; @@ -104,7 +131,8 @@ async fn handle_event( let command = match cmd.name.as_str() { "leaderboard" => Commands::Leaderboard, "points" => Commands::Points, - _ => panic!("meow"), + "permission" => Commands::Permission, + _ => panic!("'{}' is not a command", cmd.name), }; command_handler(&db, http, guild, &create, command, cmd).await; @@ -136,7 +164,22 @@ async fn commands() -> Vec<Command> { .unwrap() .build(); - vec![leaderboard, points] + 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(); + + vec![leaderboard, points, permission] } async fn command_handler( @@ -147,47 +190,50 @@ async fn command_handler( command: Commands, command_data: &CommandData, ) { - match command { - Commands::Leaderboard => { - let data = get_leaderboard(&db, guild); - - let response = InteractionResponse { - kind: InteractionResponseType::ChannelMessageWithSource, - data: Some(data), - }; + // Don't allow direct message commanding + if create.is_dm() { + let iclient = http.interaction(Id::new(APP_ID)); + iclient + .create_response( + create.id, + &create.token, + &InteractionResponse { + kind: InteractionResponseType::ChannelMessageWithSource, + data: Some(fail!("leaderboards not supported in DMs")), + }, + ) + .await + .unwrap(); + } - let iclient = http.interaction(Id::new(APP_ID)); - iclient - .create_response(create.id, &create.token, &response) - .await - .unwrap(); - } - Commands::Points => { - let data = add_points(&db, &http, guild, command_data).await; + // Handle the command and create our interaction response data + let data = match command { + Commands::Leaderboard => get_leaderboard(&db, guild), + Commands::Points => add_points(&db, &http, guild, create, command_data).await, + Commands::Permission => permission(&db, &http, guild, create, command_data).await, + }; - let response = InteractionResponse { + // Finally, send back the response + http.interaction(Id::new(APP_ID)) + .create_response( + create.id, + &create.token, + &InteractionResponse { kind: InteractionResponseType::ChannelMessageWithSource, data: Some(data), - }; - - let iclient = http.interaction(Id::new(APP_ID)); - iclient - .create_response(create.id, &create.token, &response) - .await - .unwrap(); - } - } + }, + ) + .await + .unwrap(); } fn get_leaderboard(db: &Database, guild: Id<GuildMarker>) -> InteractionResponseData { let board = db.get_leaderboard(guild.get()); match board { - Err(DbError::TableNotExist) => InteractionResponseDataBuilder::new() - .content( - "❌ No leaderboard exists for this server! Create one by giving someone points.", - ) - .build(), + Err(DbError::TableNotExist) => { + fail!("No leaderboard exists for this server! Create one by giving someone points.") + } Err(DbError::UserNotExist) => unreachable!(), Ok(data) => { let str = data @@ -206,6 +252,7 @@ fn get_leaderboard(db: &Database, guild: Id<GuildMarker>) -> InteractionResponse } } +// A Absolutely Remarkably Terrible Thing macro_rules! aumau { ($meow:expr) => { $meow.await.unwrap().model().await.unwrap() @@ -216,6 +263,7 @@ async fn add_points( db: &Database, http: &HttpClient, guild: Id<GuildMarker>, + create: &InteractionCreate, data: &CommandData, ) -> InteractionResponseData { let mut points = None; @@ -245,10 +293,32 @@ async fn add_points( Some(_) | None => unreachable!(), }; + // Make sure we have a leaderboard to work on. if db.get_leaderboard(guild.get()).is_err() { db.create_leaderboard(guild.get()).unwrap(); } + let settings = 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 db.give_user_points(guild.get(), user.get(), points) { Err(DbError::UserNotExist) => { @@ -278,9 +348,75 @@ async fn add_points( .build() } +async fn permission( + db: &Database, + http: &HttpClient, + guild: Id<GuildMarker>, + 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 command"); + } + + for opt in &data.options { + return match opt.name.as_str() { + "none" => permission_none(db, guild), + "role" => permission_role(db, guild, opt.value.clone()), + _ => panic!(), + }; + } + + fail!("this should be unreachable. report the bug maybe? to gennyble on discord") +} + +fn permission_none(db: &Database, guild: Id<GuildMarker>) -> InteractionResponseData { + if db.get_leaderboard(guild.get()).is_err() { + db.create_leaderboard(guild.get()).unwrap(); + } else { + 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( + db: &Database, + guild: Id<GuildMarker>, + 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!() + }; + + // Make sure we have a leaderboard to work on + if db.get_leaderboard(guild.get()).is_err() { + db.create_leaderboard(guild.get()).unwrap(); + } + + db.set_leaderboard_permission(guild.get(), PermissionSetting::RoleRequired) + .unwrap(); + 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" + )) +} + enum Commands { Leaderboard, Points, + Permission, } fn extract_mentions(mut raw: &str) -> Vec<Id<UserMarker>> { |