diff --git a/src/commands/dox.rs b/src/commands/dox.rs index 7c9d014..1276edc 100644 --- a/src/commands/dox.rs +++ b/src/commands/dox.rs @@ -3,7 +3,6 @@ use crate::common::{Context, Error}; use poise::serenity_prelude as serenity; use serenity::Colour; - // this code uses Member::permissions, which while it is a deprecated function, it doesn't actually matter // since it is only used to display information to the user. #[allow(deprecated)] diff --git a/src/commands/gambling/daily.rs b/src/commands/gambling/daily.rs index 61c25eb..0343b6b 100644 --- a/src/commands/gambling/daily.rs +++ b/src/commands/gambling/daily.rs @@ -30,8 +30,8 @@ pub async fn daily(ctx: Context<'_>) -> Result<(), Error> { super::add_balance(id, 50, db).await?; ctx.reply("Added **50** credits to your account!").await?; } else { - let until_next_daily = Duration::from_secs(10) - daily.elapsed(); - ctx.reply(format!("Your daily will be available in {:?}.", format_duration(until_next_daily))).await?; + let until_next_daily = Duration::from_secs(24 * 60 * 60) - daily.elapsed(); + ctx.reply(format!("Your next daily will be available in **{}**.", format_duration(until_next_daily))).await?; } }, None => { diff --git a/src/commands/mod.rs b/src/commands/mod.rs index a4b4e46..b62de1c 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -18,5 +18,6 @@ pub fn commands() -> Vec> { gambling::wager::wager(), gambling::daily::daily(), eval::eval(), + self_roles::role(), ] } diff --git a/src/commands/self_roles/color.rs b/src/commands/self_roles/color.rs new file mode 100644 index 0000000..4248e27 --- /dev/null +++ b/src/commands/self_roles/color.rs @@ -0,0 +1,54 @@ + +use crate::common::{Context, Error}; + +use hex_color::HexColor; +use poise::serenity_prelude::{Color, EditRole}; + +/// Change the color of your personal role +#[poise::command(slash_command, prefix_command)] +pub async fn color(ctx: Context<'_>, color: String) -> Result<(), Error> { + let data = ctx.data(); + let mut db = data.database.lock().await; + let db = db.as_mut(); + + let color = match HexColor::parse_rgb(&color) { + Ok(color) => color, + Err(e) => { + ctx.reply(format!("Couldn't parse color: {e}")).await?; + return Ok(()); + } + }; + + if let Some(guild) = ctx.guild_id() { + let role = match super::get_user_role(ctx, ctx.author().id, guild, db).await? { + Some(role) => role, + None => { + let role = guild.create_role(ctx, + EditRole::new() + .name(format!("{}", color.display_rgb())) + .colour(Color::from_rgb(color.r, color.g, color.b))).await?; + + sqlx::query("INSERT INTO selfroles (userid, roleid, guildid) VALUES ($1, $2, $3)") + .bind(ctx.author().id.get() as i64) + .bind(role.id.get() as i64) + .bind(guild.get() as i64) + .execute(db).await?; + + let member = guild.member(ctx, ctx.author().id).await?; + member.add_role(ctx, role.clone()).await?; + + ctx.reply(format!("You have been given the {} role!", role)).await?; + 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 { + ctx.reply("This command must be run within a server.").await?; + Ok(()) + } +} diff --git a/src/commands/self_roles/disown.rs b/src/commands/self_roles/disown.rs new file mode 100644 index 0000000..80b3ac1 --- /dev/null +++ b/src/commands/self_roles/disown.rs @@ -0,0 +1,30 @@ + +use crate::common::{Context, Error}; + +/// Remove and delete your personal role +#[poise::command(slash_command, prefix_command)] +pub async fn disown(ctx: Context<'_>) -> 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() { + if let Some(role) = super::get_user_role(ctx, ctx.author().id, guild, db).await? { + guild.delete_role(ctx, role).await?; + + sqlx::query("DELETE FROM selfroles WHERE roleid = $1") + .bind(role.get() as i64) + .execute(db).await?; + + ctx.reply("Your role has been successfully removed.").await?; + + Ok(()) + } else { + ctx.reply("You do not currently have a personal role to remove.").await?; + Ok(()) + } + } else { + ctx.reply("This command must be called within a server.").await?; + Ok(()) + } +} diff --git a/src/commands/self_roles/mod.rs b/src/commands/self_roles/mod.rs new file mode 100644 index 0000000..b0c5a98 --- /dev/null +++ b/src/commands/self_roles/mod.rs @@ -0,0 +1,37 @@ + +use crate::common::{Context, Error}; +use sqlx::{PgConnection, Row}; +use poise::serenity_prelude::{RoleId, UserId, GuildId}; + +mod register; +mod whois; +mod color; +mod name; +mod disown; + +#[poise::command( + prefix_command, + slash_command, + subcommands( + "register::register", + "whois::whois", + "color::color", + "name::name", + "disown::disown", + ) +)] +pub async fn role(_ctx: Context<'_>) -> Result<(), Error> { + Ok(()) +} + +pub async fn get_user_role(_ctx: Context<'_>, user: UserId, guild: GuildId, db: &mut PgConnection) -> Result, Error> { + match sqlx::query("SELECT roleid FROM selfroles WHERE userid = $1 AND guildid = $2") + .bind(user.get() as i64) + .bind(guild.get() as i64) + .fetch_one(db).await + { + Ok(row) => Ok(Some(RoleId::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 new file mode 100644 index 0000000..6e40b07 --- /dev/null +++ b/src/commands/self_roles/name.rs @@ -0,0 +1,43 @@ + +use crate::common::{Context, Error}; + +use poise::serenity_prelude::EditRole; + +/// Change the name of your personal role +#[poise::command(slash_command, prefix_command)] +pub async fn name(ctx: Context<'_>, name: String) -> 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() { + let role = match super::get_user_role(ctx, ctx.author().id, guild, db).await? { + Some(role) => role, + None => { + let role = guild.create_role(ctx, EditRole::new().name(name)).await?; + + sqlx::query("INSERT INTO selfroles (userid, roleid, guildid) VALUES ($1, $2, $3)") + .bind(ctx.author().id.get() as i64) + .bind(role.id.get() as i64) + .bind(guild.get() as i64) + .execute(db).await?; + + let member = guild.member(ctx, ctx.author().id).await?; + member.add_role(ctx, role.clone()).await?; + + ctx.reply(format!("You've been given the {} role!", role)).await?; + + return Ok(()); + } + }; + + guild.edit_role(ctx, role, EditRole::new().name(name)).await?; + + ctx.reply("Your custom role's name has been updated!").await?; + + Ok(()) + } else { + ctx.reply("This command must be run within a server.").await?; + Ok(()) + } +} diff --git a/src/commands/self_roles/register.rs b/src/commands/self_roles/register.rs new file mode 100644 index 0000000..cb7070b --- /dev/null +++ b/src/commands/self_roles/register.rs @@ -0,0 +1,29 @@ + +use crate::common::{Context, Error}; + +use poise::serenity_prelude as serenity; + +/// Register an existing role as a user's custom role +#[poise::command(slash_command, prefix_command, required_permissions = "MANAGE_ROLES")] +pub async fn register(ctx: Context<'_>, user: Option, role: serenity::Role) -> Result<(), Error> { + let data = ctx.data(); + let mut db = data.database.lock().await; + let db = db.as_mut(); + + let user = user.as_ref().unwrap_or(ctx.author()); + + if let Some(guild) = ctx.guild().map(|g| g.id) { + sqlx::query("INSERT INTO selfroles (userid, roleid, guildid) VALUES ($1, $2, $3) ON CONFLICT (userid) DO UPDATE SET roleid = EXCLUDED.roleid") + .bind(user.id.get() as i64) + .bind(role.id.get() as i64) + .bind(guild.get() as i64) + .execute(db).await?; + + ctx.reply(format!("**{}** now has **{}** set as their personal role.", user.display_name(), role.name)).await?; + + Ok(()) + } else { + ctx.reply("This command can only be run in a guild!").await?; + Ok(()) + } +} diff --git a/src/commands/self_roles/whois.rs b/src/commands/self_roles/whois.rs new file mode 100644 index 0000000..c9762c1 --- /dev/null +++ b/src/commands/self_roles/whois.rs @@ -0,0 +1,40 @@ + +use crate::common::{Context, Error}; + +use poise::serenity_prelude as serenity; +use serenity::UserId; +use sqlx::Row; + +/// Let you know who is the owner of a role. +#[poise::command(slash_command, prefix_command)] +pub async fn whois(ctx: Context<'_>, role: serenity::Role) -> 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() { + let row = match sqlx::query("SELECT userid FROM selfroles WHERE roleid = $1") + .bind(role.id.get() as i64) + .fetch_one(db).await + { + Ok(row) => row, + Err(sqlx::Error::RowNotFound) => { + ctx.reply("This role is not owned by anyone.").await?; + return Ok(()); + } + Err(e) => return Err(Box::new(e)), + }; + + let user: i64 = row.try_get(0)?; + + let user = UserId::new(user as u64); + + let member = guild.member(ctx, user).await?; + + ctx.reply(format!("{} owns this role.", member.display_name())).await?; + } else { + ctx.reply("This command must be used within a server!").await?; + } + + Ok(()) +} diff --git a/src/common.rs b/src/common.rs index 7de08e2..f61938b 100644 --- a/src/common.rs +++ b/src/common.rs @@ -7,6 +7,9 @@ use sqlx::PgConnection; pub struct Data { pub database: Arc>, pub mentions: Arc>>, + + /// last time the user redeemed a daily + pub dailies: Arc>>, } pub type Error = Box; diff --git a/src/main.rs b/src/main.rs index cc752f5..635e11e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -79,11 +79,22 @@ async fn main() -> Result<(), Error> { "#, ).execute(&mut database).await?; + sqlx::query( + r#" + CREATE TABLE IF NOT EXISTS selfroles ( + userid BIGINT PRIMARY KEY, + roleid BIGINT, + guildid BIGINT + ) + "#, + ).execute(&mut database).await?; + println!("Bot is ready!"); Ok(Data { database: Arc::new(Mutex::new(database)), mentions: Arc::new(Mutex::new(HashMap::new())), + dailies: Arc::new(Mutex::new(HashMap::new())), }) }) })