diff --git a/src/commands/mod.rs b/src/commands/mod.rs index fd77fff..2914398 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -38,6 +38,7 @@ pub fn commands() -> Vec> { gambling::blackjack::blackjack(), eval::eval(), self_roles::role(), + self_roles::editrole(), settings::setting(), administration::ban::ban(), administration::ban::unban(), diff --git a/src/commands/self_roles/admin.rs b/src/commands/self_roles/admin.rs new file mode 100644 index 0000000..2e41b53 --- /dev/null +++ b/src/commands/self_roles/admin.rs @@ -0,0 +1,105 @@ + +use crate::common::{self, Context, Error, BigBirbError}; + +use poise::serenity_prelude::{User, Role}; + +/// Change the name of a user's personal role +#[poise::command(slash_command, prefix_command, required_permissions = "MANAGE_ROLES")] +pub async fn name(ctx: Context<'_>, user: User, #[rest] name: String) -> Result<(), Error> { + let guild = ctx.guild_id().ok_or(BigBirbError::GuildOnly)?; + + let role = guild.role(ctx, super::name::change_user_role_name(ctx, &user, guild, name).await?).await?; + common::no_ping_reply(&ctx, format!("{role} has been updated.")).await?; + + Ok(()) +} + +/// Change the name of a user's personal role +#[poise::command(slash_command, prefix_command, required_permissions = "MANAGE_ROLES")] +pub async fn color(ctx: Context<'_>, user: User, color: String) -> Result<(), Error> { + let guild = ctx.guild_id().ok_or(BigBirbError::GuildOnly)?; + let color = super::color::parse_color(&color)?; + + let role = guild.role(ctx, super::color::change_user_role_color(ctx, &user, guild, color).await?).await?; + common::no_ping_reply(&ctx, format!("{role}'s color has been updated.")).await?; + + Ok(()) +} + +/// Change a user's role name and color at once +#[poise::command(slash_command, prefix_command, required_permissions = "MANAGE_ROLES")] +pub async fn set(ctx: Context<'_>, user: User, color: String, #[rest] name: String) -> Result<(), Error> { + let guild = ctx.guild_id().ok_or(BigBirbError::GuildOnly)?; + let color = super::color::parse_color(&color)?; + + super::color::change_user_role_color(ctx, &user, guild, color).await?; + let role = guild.role(ctx, super::name::change_user_role_name(ctx, &user, guild, name).await?).await?; + + common::no_ping_reply(&ctx, format!("{role} has been updated.")).await?; + + Ok(()) +} + +/// Remove and delete user's self role +#[poise::command(slash_command, prefix_command, aliases("disown"), required_permissions = "MANAGE_ROLES")] +pub async fn remove(ctx: Context<'_>, user: User) -> Result<(), Error> { + let guild = ctx.guild_id().ok_or(BigBirbError::GuildOnly)?; + let mut tx = ctx.data().database.begin().await?; + + if let Some(role) = super::get_user_role(user.id, guild, &mut *tx).await? { + guild.delete_role(ctx, role).await?; + super::remove_role(role, guild, &mut *tx).await?; + tx.commit().await?; + common::no_ping_reply(&ctx, format!("{}'s self role has been deleted.", user)).await?; + } else { + common::no_ping_reply(&ctx, format!("{} has no self role.", user)).await?; + } + + Ok(()) +} + +/// Give a user an existing role as their self role +#[poise::command(slash_command, prefix_command, required_permissions = "MANAGE_ROLES")] +pub async fn give(ctx: Context<'_>, user: User, role: Role, force: Option) -> Result<(), Error> { + let guild = ctx.guild_id().ok_or(BigBirbError::GuildOnly)?; + let force = force.unwrap_or(false); + let member = guild.member(ctx, user).await?; + + let mut tx = ctx.data().database.begin().await?; + + if force { + // delete existing self role for user + if let Some(original) = super::get_user_role(member.user.id, guild, &mut *tx).await? { + guild.delete_role(ctx, original).await?; + super::remove_role(role.id, guild, &mut *tx).await?; + } + + // remove role from another user if it is already registered as their self role + if let Some(user) = super::get_user_by_role(role.id, guild, &mut *tx).await? { + let m = guild.member(ctx, user).await?; + m.remove_role(ctx, role.id).await?; + super::remove_role(role.id, guild, &mut *tx).await?; + } + + super::update_user_role(member.user.id, guild, role.id, &mut *tx).await?; + member.add_role(ctx, role.id).await?; + } else { + if let Some(original) = super::get_user_role(member.user.id, guild, &mut *tx).await? { + common::no_ping_reply(&ctx, format!("{original} is already set as this user's self role, enable force to overwrite.")).await?; + return Ok(()); + } + + if let Some(owner) = super::get_user_by_role(role.id, guild, &mut *tx).await? { + common::no_ping_reply(&ctx, format!("{role} is already owned by {owner}, enable force to overwrite.")).await?; + return Ok(()); + } + + super::update_user_role(member.user.id, guild, role.id, &mut *tx).await?; + } + + tx.commit().await?; + + common::no_ping_reply(&ctx, format!("{member} has been given the self role {role}.")).await?; + + Ok(()) +} \ No newline at end of file diff --git a/src/commands/self_roles/color.rs b/src/commands/self_roles/color.rs index 4b0996b..fb24a94 100644 --- a/src/commands/self_roles/color.rs +++ b/src/commands/self_roles/color.rs @@ -5,7 +5,7 @@ use once_cell::sync::Lazy; use std::collections::HashMap; use hex_color::HexColor; -use poise::serenity_prelude::{colours, Color, EditRole}; +use poise::serenity_prelude::{colours, Color, User, GuildId, RoleId, EditRole}; static COLORS: Lazy> = Lazy::new(|| { HashMap::from([ @@ -59,6 +59,25 @@ async fn autocomplete_colors<'a>( COLORS.clone().into_keys().filter(move |x| x.split_whitespace().any(|x| x.starts_with(partial))) } +pub fn parse_color(s: &str) -> Result { + let color = if let Some(named) = COLORS.get(s) { + named.clone() + } else { + let rgb = HexColor::parse_rgb(&s)?; + Color::from_rgb(rgb.r, rgb.g, rgb.b) + }; + + Ok(color) +} + +pub async fn change_user_role_color(ctx: Context<'_>, user: &User, guild: GuildId, color: Color) -> Result { + let mut tx = ctx.data().database.begin().await?; + let role = super::edit_role(ctx, user.id, guild, EditRole::new().colour(color), &mut *tx).await?; + tx.commit().await?; + + Ok(role) +} + /// Change the color of your personal role #[poise::command(slash_command, prefix_command)] pub async fn color(ctx: Context<'_>, @@ -66,22 +85,12 @@ pub async fn color(ctx: Context<'_>, #[rest] color: String) -> Result<(), Error> { - let color = if let Some(named) = COLORS.get(color.as_str()) { - named.clone() - } else { - let rgb = HexColor::parse_rgb(&color)?; - Color::from_rgb(rgb.r, rgb.g, rgb.b) - }; - let guild = ctx.guild_id().ok_or(BigBirbError::GuildOnly)?; - let user = ctx.author(); + let color = parse_color(&color)?; - let mut tx = ctx.data().database.begin().await?; - let role = super::edit_role(ctx, user.id, guild, EditRole::new().colour(color), &mut *tx).await?; - tx.commit().await?; - - common::no_ping_reply(&ctx, format!("{}'s color has been updated.", guild.role(ctx, role).await?)).await?; + let role = guild.role(ctx, change_user_role_color(ctx, &user, guild, color).await?).await?; + common::no_ping_reply(&ctx, format!("{role}'s color has been updated.")).await?; Ok(()) } diff --git a/src/commands/self_roles/mod.rs b/src/commands/self_roles/mod.rs index b723e35..a9c5436 100644 --- a/src/commands/self_roles/mod.rs +++ b/src/commands/self_roles/mod.rs @@ -3,29 +3,44 @@ use crate::common::{Context, Error}; use sqlx::{PgConnection, Row}; use poise::serenity_prelude::{EditRole, GuildId, Permissions, RoleId, UserId}; -mod register; mod whois; -mod color; -mod name; +pub mod color; +pub mod name; mod disown; -mod remove; + +mod admin; #[poise::command( prefix_command, slash_command, subcommands( - "register::register", - "whois::whois", - "color::color", "name::name", + "color::color", "disown::disown", - "remove::remove", + "whois::whois", ) )] pub async fn role(_ctx: Context<'_>) -> Result<(), Error> { Ok(()) } +#[poise::command( + prefix_command, + slash_command, + subcommands( + "admin::name", + "admin::color", + "admin::remove", + "admin::set", + "admin::give", + "whois::whois", + ), + required_permissions = "MANAGE_ROLES", +)] +pub async fn editrole(_ctx: Context<'_>) -> Result<(), Error> { + Ok(()) +} + /// Edit a user's personal role, creates it with some default values if it doesn't exist. pub async fn edit_role(ctx: Context<'_>, user: UserId, guild: GuildId, edit: EditRole<'_>, db: &mut PgConnection) -> Result { @@ -106,4 +121,17 @@ pub async fn get_user_role(user: UserId, guild: GuildId, db: &mut PgConnection) Err(sqlx::Error::RowNotFound) => Ok(None), Err(e) => return Err(Box::new(e)), } +} + +/// Get a user from the role id +pub async fn get_user_by_role(role: RoleId, guild: GuildId, db: &mut PgConnection) -> Result, Error> { + match sqlx::query("SELECT userid FROM selfroles WHERE roleid = $1 AND guildid = $2") + .bind(role.get() as i64) + .bind(guild.get() as i64) + .fetch_one(db).await + { + Ok(row) => Ok(Some(UserId::new(row.try_get::(0)? as u64))), + Err(sqlx::Error::RowNotFound) => Ok(None), + Err(e) => return Err(Box::new(e)), + } } \ No newline at end of file diff --git a/src/commands/self_roles/name.rs b/src/commands/self_roles/name.rs index 68ee214..5e96c0c 100644 --- a/src/commands/self_roles/name.rs +++ b/src/commands/self_roles/name.rs @@ -1,20 +1,23 @@ use crate::common::{self, Context, Error, BigBirbError}; +use poise::serenity_prelude::{EditRole, User, GuildId, RoleId}; -use poise::serenity_prelude::EditRole; - -/// Change the name of your personal role -#[poise::command(slash_command, prefix_command)] -pub async fn name(ctx: Context<'_>, #[rest] name: String) -> Result<(), Error> { - let guild = ctx.guild_id().ok_or(BigBirbError::GuildOnly)?; - let user = ctx.author(); - +pub async fn change_user_role_name(ctx: Context<'_>, user: &User, guild: GuildId, name: String) -> Result { let mut tx = ctx.data().database.begin().await?; let role = super::edit_role(ctx, user.id, guild, EditRole::new().name(name), &mut *tx).await?; tx.commit().await?; - common::no_ping_reply(&ctx, format!("{} has been updated.", guild.role(ctx, role).await?)).await?; + Ok(role) +} +/// Change the name of your personal role +#[poise::command(slash_command, prefix_command, )] +pub async fn name(ctx: Context<'_>, #[rest] name: String) -> Result<(), Error> { + let guild = ctx.guild_id().ok_or(BigBirbError::GuildOnly)?; + let user = ctx.author(); + + let role = guild.role(ctx, change_user_role_name(ctx, user, guild, name).await?).await?; + common::no_ping_reply(&ctx, format!("{} has been updated.", role)).await?; Ok(()) } diff --git a/src/commands/self_roles/register.rs b/src/commands/self_roles/register.rs deleted file mode 100644 index 359e4f7..0000000 --- a/src/commands/self_roles/register.rs +++ /dev/null @@ -1,25 +0,0 @@ - -use crate::common::{self, Context, Error, BigBirbError}; - -use poise::serenity_prelude as serenity; - -/// Register an existing role as a user's custom role. This deletes their current self role. -#[poise::command(slash_command, prefix_command, required_permissions = "MANAGE_ROLES")] -pub async fn register(ctx: Context<'_>, user: serenity::User, role: serenity::Role) -> Result<(), Error> { - let guild = ctx.guild_id().ok_or(BigBirbError::GuildOnly)?; - let mut tx = ctx.data().database.begin().await?; - - if let Some(role) = super::get_user_role(user.id, guild, &mut *tx).await? { - guild.delete_role(ctx, role).await?; - } - - let member = guild.member(ctx, user).await?; - member.add_role(ctx, role.id).await?; - - super::update_user_role(member.user.id, guild, role.id, &mut *tx).await?; - tx.commit().await?; - - common::no_ping_reply(&ctx, format!("{} has been set as {}'s self role.", role, member.user)).await?; - - Ok(()) -} diff --git a/src/commands/self_roles/remove.rs b/src/commands/self_roles/remove.rs deleted file mode 100644 index b0cbef3..0000000 --- a/src/commands/self_roles/remove.rs +++ /dev/null @@ -1,21 +0,0 @@ - -use crate::common::{self, Context, Error, BigBirbError}; - -use poise::serenity_prelude as serenity; - -#[poise::command(slash_command, prefix_command, required_permissions = "MANAGE_ROLES")] -pub async fn remove(ctx: Context<'_>, user: serenity::User) -> Result<(), Error> { - let guild = ctx.guild_id().ok_or(BigBirbError::GuildOnly)?; - let mut tx = ctx.data().database.begin().await?; - - if let Some(role) = super::get_user_role(user.id, guild, &mut *tx).await? { - guild.delete_role(ctx, role).await?; - super::remove_role(role, guild, &mut *tx).await?; - tx.commit().await?; - common::no_ping_reply(&ctx, format!("{}'s self role has been deleted.", user)).await?; - } else { - common::no_ping_reply(&ctx, format!("{} has no self role.", user)).await?; - } - - Ok(()) -} \ No newline at end of file