new role management commands, bug fix, table restructuring, better output

This commit is contained in:
2024-12-08 12:15:54 -05:00
parent 8f764a2270
commit 0def108010
10 changed files with 97 additions and 35 deletions

3
Cargo.lock generated
View File

@@ -204,7 +204,6 @@ dependencies = [
name = "bot" name = "bot"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"chrono",
"clap", "clap",
"dotenv", "dotenv",
"hex_color", "hex_color",
@@ -294,10 +293,8 @@ checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401"
dependencies = [ dependencies = [
"android-tzdata", "android-tzdata",
"iana-time-zone", "iana-time-zone",
"js-sys",
"num-traits", "num-traits",
"serde", "serde",
"wasm-bindgen",
"windows-targets 0.52.6", "windows-targets 0.52.6",
] ]

View File

@@ -20,8 +20,15 @@ pub async fn color(ctx: Context<'_>, color: String) -> Result<(), Error> {
}; };
if let Some(guild) = ctx.guild_id() { if let Some(guild) = ctx.guild_id() {
let role = match super::get_user_role(ctx, ctx.author().id, guild, db).await? { match super::get_user_role(ctx.author().id, guild, db).await? {
Some(role) => role, Some(role) => {
guild.edit_role(ctx, role, EditRole::new().colour(Color::from_rgb(color.r, color.g, color.b))).await?;
let role = guild.role(ctx, role).await?;
ctx.reply(format!("{}'s color has been updated!", role)).await?;
Ok(())
},
None => { None => {
let role = guild.create_role(ctx, let role = guild.create_role(ctx,
EditRole::new() EditRole::new()
@@ -40,13 +47,7 @@ pub async fn color(ctx: Context<'_>, color: String) -> Result<(), Error> {
ctx.reply(format!("You have been given the {} role!", role)).await?; ctx.reply(format!("You have been given the {} role!", role)).await?;
return Ok(()); return Ok(());
} }
}; }
guild.edit_role(ctx, role, EditRole::new().colour(Color::from_rgb(color.r, color.g, color.b))).await?;
ctx.reply("Your custom role's color has been updated!").await?;
Ok(())
} else { } else {
ctx.reply("This command must be run within a server.").await?; ctx.reply("This command must be run within a server.").await?;
Ok(()) Ok(())

View File

@@ -9,7 +9,7 @@ pub async fn disown(ctx: Context<'_>) -> Result<(), Error> {
let db = db.as_mut(); let db = db.as_mut();
if let Some(guild) = ctx.guild_id() { if let Some(guild) = ctx.guild_id() {
if let Some(role) = super::get_user_role(ctx, ctx.author().id, guild, db).await? { if let Some(role) = super::get_user_role(ctx.author().id, guild, db).await? {
guild.delete_role(ctx, role).await?; guild.delete_role(ctx, role).await?;
sqlx::query("DELETE FROM selfroles WHERE roleid = $1") sqlx::query("DELETE FROM selfroles WHERE roleid = $1")

View File

@@ -8,6 +8,7 @@ mod whois;
mod color; mod color;
mod name; mod name;
mod disown; mod disown;
mod remove;
#[poise::command( #[poise::command(
prefix_command, prefix_command,
@@ -18,13 +19,14 @@ mod disown;
"color::color", "color::color",
"name::name", "name::name",
"disown::disown", "disown::disown",
"remove::remove",
) )
)] )]
pub async fn role(_ctx: Context<'_>) -> Result<(), Error> { pub async fn role(_ctx: Context<'_>) -> Result<(), Error> {
Ok(()) Ok(())
} }
pub async fn get_user_role(_ctx: Context<'_>, user: UserId, guild: GuildId, db: &mut PgConnection) -> Result<Option<RoleId>, Error> { pub async fn get_user_role(user: UserId, guild: GuildId, db: &mut PgConnection) -> Result<Option<RoleId>, Error> {
match sqlx::query("SELECT roleid FROM selfroles WHERE userid = $1 AND guildid = $2") match sqlx::query("SELECT roleid FROM selfroles WHERE userid = $1 AND guildid = $2")
.bind(user.get() as i64) .bind(user.get() as i64)
.bind(guild.get() as i64) .bind(guild.get() as i64)

View File

@@ -11,7 +11,7 @@ pub async fn name(ctx: Context<'_>, name: String) -> Result<(), Error> {
let db = db.as_mut(); let db = db.as_mut();
if let Some(guild) = ctx.guild_id() { if let Some(guild) = ctx.guild_id() {
let role = match super::get_user_role(ctx, ctx.author().id, guild, db).await? { let role = match super::get_user_role(ctx.author().id, guild, db).await? {
Some(role) => role, Some(role) => role,
None => { None => {
let role = guild.create_role(ctx, EditRole::new().name(name)).await?; let role = guild.create_role(ctx, EditRole::new().name(name)).await?;
@@ -32,8 +32,9 @@ pub async fn name(ctx: Context<'_>, name: String) -> Result<(), Error> {
}; };
guild.edit_role(ctx, role, EditRole::new().name(name)).await?; guild.edit_role(ctx, role, EditRole::new().name(name)).await?;
let role = guild.role(ctx, role).await?;
ctx.reply("Your custom role's name has been updated!").await?; ctx.reply(format!("Your custom role's name has been updated to {}.", role)).await?;
Ok(()) Ok(())
} else { } else {

View File

@@ -1,27 +1,36 @@
use crate::common::{Context, Error}; use crate::common::{self, Context, Error};
use poise::serenity_prelude as serenity; use poise::serenity_prelude as serenity;
/// Register an existing role as a user's custom role /// Register an existing role as a user's custom role
#[poise::command(slash_command, prefix_command, required_permissions = "MANAGE_ROLES")] #[poise::command(slash_command, prefix_command, required_permissions = "MANAGE_ROLES")]
pub async fn register(ctx: Context<'_>, user: Option<serenity::User>, role: serenity::Role) -> Result<(), Error> { pub async fn register(ctx: Context<'_>, user: serenity::User, role: serenity::Role) -> Result<(), Error> {
let data = ctx.data(); let data = ctx.data();
let mut db = data.database.lock().await; let mut db = data.database.lock().await;
let db = db.as_mut(); let db = db.as_mut();
let user = user.as_ref().unwrap_or(ctx.author()); if let Some(guild) = ctx.guild_id() {
match super::get_user_role(user.id, guild, db).await? {
Some(role) => {
common::no_ping_reply(&ctx, format!("{} already has the role {} registered.", user, role)).await?;
Ok(())
},
None => {
sqlx::query("INSERT INTO selfroles (userid, guildid, roleid) VALUES ($1, $2, $3)")
.bind(user.id.get() as i64)
.bind(guild.get() as i64)
.bind(role.id.get() as i64)
.execute(db).await?;
if let Some(guild) = ctx.guild().map(|g| g.id) { let member = guild.member(ctx, user.id).await?;
sqlx::query("INSERT INTO selfroles (userid, roleid, guildid) VALUES ($1, $2, $3) ON CONFLICT (userid) DO UPDATE SET roleid = EXCLUDED.roleid") member.add_role(ctx, role.id).await?;
.bind(user.id.get() as i64)
.bind(role.id.get() as i64) common::no_ping_reply(&ctx, format!("{} now has {} set as their personal role.", user, role)).await?;
.bind(guild.get() as i64)
.execute(db).await?; Ok(())
}
ctx.reply(format!("**{}** now has **{}** set as their personal role.", user.display_name(), role.name)).await?; }
Ok(())
} else { } else {
ctx.reply("This command can only be run in a guild!").await?; ctx.reply("This command can only be run in a guild!").await?;
Ok(()) Ok(())

View File

@@ -0,0 +1,39 @@
use crate::common::{self, Context, Error};
use poise::serenity_prelude as serenity;
/// force remove someone's role (this does not delete the role)
#[poise::command(slash_command, prefix_command, required_permissions = "MANAGE_ROLES")]
pub async fn remove(ctx: Context<'_>, user: serenity::User) -> Result<(), Error> {
let data = ctx.data();
let mut db = data.database.lock().await;
let db = db.as_mut();
if let Some(guild) = ctx.guild_id() {
match super::get_user_role(user.id, guild, db).await? {
Some(role) => {
sqlx::query("DELETE FROM selfroles WHERE userid = $1 AND guildid = $2")
.bind(user.id.get() as i64)
.bind(guild.get() as i64)
.execute(db).await?;
let member = guild.member(ctx, user.id).await?;
member.remove_role(ctx, role).await?;
let role = guild.role(ctx, role).await?;
common::no_ping_reply(&ctx, format!("{} has had {} removed.", user, role)).await?;
Ok(())
},
None => {
common::no_ping_reply(&ctx, format!("{} does not have a personal role to remove.", user)).await?;
Ok(())
}
}
} else {
ctx.reply("This command can only be run in a guild!").await?;
Ok(())
}
}

View File

@@ -1,5 +1,5 @@
use crate::common::{Context, Error}; use crate::common::{self, Context, Error};
use poise::serenity_prelude as serenity; use poise::serenity_prelude as serenity;
use serenity::UserId; use serenity::UserId;
@@ -31,7 +31,7 @@ pub async fn whois(ctx: Context<'_>, role: serenity::Role) -> Result<(), Error>
let member = guild.member(ctx, user).await?; let member = guild.member(ctx, user).await?;
ctx.reply(format!("{} owns this role.", member.display_name())).await?; common::no_ping_reply(&ctx, format!("{} owns this role.", member)).await?;
} else { } else {
ctx.reply("This command must be used within a server!").await?; ctx.reply("This command must be used within a server!").await?;
} }

View File

@@ -1,7 +1,7 @@
use std::sync::Arc; use std::sync::Arc;
use tokio::sync::Mutex; use tokio::sync::Mutex;
use std::collections::HashMap; use std::collections::HashMap;
use poise::serenity_prelude::UserId; use poise::{serenity_prelude::UserId, ReplyHandle};
use sqlx::PgConnection; use sqlx::PgConnection;
pub struct Data { pub struct Data {
@@ -13,4 +13,16 @@ pub struct Data {
} }
pub type Error = Box<dyn std::error::Error + Send + Sync>; pub type Error = Box<dyn std::error::Error + Send + Sync>;
pub type Context<'a> = poise::Context<'a, Data, Error>; pub type Context<'a> = poise::Context<'a, Data, Error>;
use poise::serenity_prelude::builder::CreateAllowedMentions;
use poise::CreateReply;
pub async fn no_ping_reply<'a>(ctx: &'a Context<'_>, text: impl Into<String>) -> Result<ReplyHandle<'a>, Error> {
Ok(ctx.send(
CreateReply::default()
.content(text.into())
.reply(true)
.allowed_mentions(CreateAllowedMentions::new())
).await?)
}

View File

@@ -82,9 +82,10 @@ async fn main() -> Result<(), Error> {
sqlx::query( sqlx::query(
r#" r#"
CREATE TABLE IF NOT EXISTS selfroles ( CREATE TABLE IF NOT EXISTS selfroles (
userid BIGINT, userid BIGINT NOT NULL,
guildid BIGINT NOT NULL,
roleid BIGINT, roleid BIGINT,
guildid BIGINT UNIQUE (userid, guildid)
) )
"#, "#,
).execute(&mut database).await?; ).execute(&mut database).await?;