improved admin controls over self roles

This commit is contained in:
2025-05-18 16:10:07 -04:00
parent e5ca4473bc
commit e798e82395
7 changed files with 177 additions and 77 deletions

View File

@@ -38,6 +38,7 @@ pub fn commands() -> Vec<Command<Data, Error>> {
gambling::blackjack::blackjack(),
eval::eval(),
self_roles::role(),
self_roles::editrole(),
settings::setting(),
administration::ban::ban(),
administration::ban::unban(),

View File

@@ -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<bool>) -> 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(())
}

View File

@@ -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<HashMap<&'static str, Color>> = 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<Color, Error> {
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<RoleId, Error> {
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(())
}

View File

@@ -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<RoleId, Error>
{
@@ -107,3 +122,16 @@ pub async fn get_user_role(user: UserId, guild: GuildId, db: &mut PgConnection)
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<Option<UserId>, 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::<i64, usize>(0)? as u64))),
Err(sqlx::Error::RowNotFound) => Ok(None),
Err(e) => return Err(Box::new(e)),
}
}

View File

@@ -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<RoleId, Error> {
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(())
}

View File

@@ -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(())
}

View File

@@ -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(())
}