diff --git a/Cargo.lock b/Cargo.lock index 103af10..59b5ff7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1080,7 +1080,7 @@ dependencies = [ [[package]] name = "fren" -version = "1.1.1" +version = "1.2.0" dependencies = [ "axum 0.8.1", "base64 0.22.1", diff --git a/Cargo.toml b/Cargo.toml index c58b0b9..33d1bf4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "fren" -version = "1.1.1" +version = "1.2.0" edition = "2024" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/src/discord/admin.rs b/src/discord/admin.rs index 2dedfe2..9aa6a83 100644 --- a/src/discord/admin.rs +++ b/src/discord/admin.rs @@ -1,4 +1,4 @@ -use crate::discord::Context; +use crate::discord::{Context, get_role}; use crate::error::Error; use crate::inventory::ItemType; use crate::models::api_key::Apikey; @@ -6,12 +6,13 @@ use crate::models::lil_fren::{ AliveState, LilFren, draw_dancing, draw_frankenstein, draw_gone, draw_magic, draw_mining, draw_resonance_cascade, draw_sick, draw_sleep, draw_standing, draw_tax_fraud, }; +use crate::models::managed_roles::ManagedRole; use crate::models::task::Task; use crate::user::User; use json::JsonValue; use log::info; use poise::serenity_prelude::{ - Attachment, CreateAttachment, CreateMessage, EmojiIdentifier, FormattedTimestamp, + Attachment, CreateAttachment, CreateMessage, EditRole, EmojiIdentifier, FormattedTimestamp, FormattedTimestampStyle, MessageBuilder, Timestamp, UserId, }; use std::borrow::Cow; @@ -195,3 +196,70 @@ pub async fn list_tasks(ctx: Context<'_>) -> Result<(), Error> { Ok(()) } + +/// Add a new role that other members can join +#[poise::command(prefix_command, guild_only, category = "Admin", check = "is_admin")] +pub async fn add_role( + ctx: Context<'_>, + #[description = "Role Name"] name: String, + #[description = "Role Description"] + #[rest] + description: String, +) -> Result<(), Error> { + let guild_id = ctx.guild_id().unwrap(); + + if guild_id + .roles(ctx) + .await? + .iter() + .any(|(_, role)| role.name == name) + { + ctx.reply("That role already exists, try something more original") + .await?; + return Ok(()); + } + + let role = guild_id + .create_role(ctx, EditRole::new().name(name).mentionable(true)) + .await?; + + ManagedRole::add_role(&ctx.data().db, role.id, description)?; + + ctx.reply(format!( + "Created `{}`, have fun with your friends dear /s", + role.name + )) + .await?; + + Ok(()) +} + +/// Remove a joinable role +#[poise::command(prefix_command, guild_only, category = "Admin", check = "is_admin")] +pub async fn remove_role( + ctx: Context<'_>, + #[description = "Role Name"] name: String, +) -> Result<(), Error> { + let guild_id = ctx.guild_id().unwrap(); + let role = get_role(ctx.http(), guild_id, &name).await?; + + let role = if let Some(role) = role { + role + } else { + ctx.reply("No role by that name. L + Ratio").await?; + return Ok(()); + }; + + if ManagedRole::get_role(&ctx.data().db, role)?.is_none() { + ctx.reply("No role by that name. L + Ratio").await?; + return Ok(()); + } + + ManagedRole::remove_role(&ctx.data().db, role)?; + guild_id.delete_role(ctx, role).await?; + + ctx.reply(format!("Finally got rid of '{}', thank GOD", name)) + .await?; + + Ok(()) +} diff --git a/src/discord/joke.rs b/src/discord/joke.rs index a16925e..77d50ca 100644 --- a/src/discord/joke.rs +++ b/src/discord/joke.rs @@ -140,7 +140,9 @@ pub async fn random( pub async fn add_random( ctx: Context<'_>, #[description = "Random collection to add to"] random_name: String, - #[description = "Random message"] #[rest] random_response: String, + #[description = "Random message"] + #[rest] + random_response: String, ) -> Result<(), Error> { if let Err(err) = render_random(ctx.author().display_name(), ctx.data(), &random_response).await { diff --git a/src/discord/mod.rs b/src/discord/mod.rs index db860d7..b52675b 100644 --- a/src/discord/mod.rs +++ b/src/discord/mod.rs @@ -8,6 +8,7 @@ mod fren_coin; mod joke; mod little_fren; mod motivate; +mod role; pub(crate) mod shop; pub(crate) mod voices; @@ -271,6 +272,8 @@ pub async fn run_bot(global_data: GlobalData) { admin::debug_buddy(), admin::op_give(), admin::list_tasks(), + admin::add_role(), + admin::remove_role(), album::add_image(), album::list_albums(), birthday::add_birthday(), @@ -301,6 +304,9 @@ pub async fn run_bot(global_data: GlobalData) { little_fren::play(), motivate::motivation(), motivate::motivation_add_album(), + role::list_roles(), + role::join_role(), + role::leave_role(), shop::buy(), shop::inventory(), shop::item_help(), diff --git a/src/discord/role.rs b/src/discord/role.rs new file mode 100644 index 0000000..fc862f3 --- /dev/null +++ b/src/discord/role.rs @@ -0,0 +1,99 @@ +use crate::discord::{Context, get_role}; +use crate::error::Error; +use crate::models::managed_roles::ManagedRole; +use poise::serenity_prelude::MessageBuilder; + +/// List all the roles you can join. It's like joining a club but nerdier +#[poise::command(prefix_command, guild_only, category = "Role")] +pub async fn list_roles(ctx: Context<'_>) -> Result<(), Error> { + let roles = ManagedRole::list_roles(&ctx.data().db)?; + + let mut role_resp = MessageBuilder::new(); + + role_resp.push_line( + "Here are all the roles you can join, use `!join_role to get in on the 'fun':", + ); + + for role in roles { + let discord_role = ctx.guild_id().unwrap().role(ctx, role.role_id).await?; + role_resp.push_line(format!("* `{}`: {}", discord_role.name, role.description)); + } + + ctx.reply(role_resp.build()).await?; + + Ok(()) +} + +/// Join a role. Just hope it's not a #CriticalFail +#[poise::command(prefix_command, guild_only, category = "Role")] +pub async fn join_role( + ctx: Context<'_>, + #[description = "Role to join"] role_name: String, +) -> Result<(), Error> { + let role = get_role(ctx.http(), ctx.guild_id().unwrap(), &role_name).await?; + + let role = if let Some(role) = role { + role + } else { + ctx.reply(format!("I don't know of this '{}' role. If it exists, only god knows. And I have met god. DO NOT MEET GOD", role_name)).await?; + return Ok(()); + }; + + let managed_role = ManagedRole::get_role(&ctx.data().db, role)?; + + if managed_role.is_none() { + ctx.reply("How do you know about that role? Who told you? I can't add you. And we're both DEAD because you mentioned it.").await?; + return Ok(()); + }; + + ctx.author_member() + .await + .unwrap() + .add_role(ctx, role) + .await?; + + ctx.reply(format!( + "You have been added to the ~~cult~~ role '{}'", + role_name + )) + .await?; + + Ok(()) +} + +/// Leave a role, probably pinged too much anyway tbh +#[poise::command(prefix_command, guild_only, category = "Role")] +pub async fn leave_role( + ctx: Context<'_>, + #[description = "Role to leave"] role_name: String, +) -> Result<(), Error> { + let role = get_role(ctx.http(), ctx.guild_id().unwrap(), &role_name).await?; + + let role = if let Some(role) = role { + role + } else { + ctx.reply(format!("I don't know of this '{}' role. If it exists, only god knows. And I have met god. DO NOT MEET GOD", role_name)).await?; + return Ok(()); + }; + + let managed_role = ManagedRole::get_role(&ctx.data().db, role)?; + + if managed_role.is_none() { + ctx.reply("How do you know about that role? Who told you? I can't add you. And we're both DEAD because you mentioned it.").await?; + return Ok(()); + }; + + ctx.author_member() + .await + .unwrap() + .remove_role(ctx, role) + .await?; + + ctx.reply(format!( + "You have left '{}'. Its over. All things come to an end. But did it have to be this way?", + role_name + )) + .await?; + + Ok(()) +} diff --git a/src/models/managed_roles.rs b/src/models/managed_roles.rs new file mode 100644 index 0000000..3fa577a --- /dev/null +++ b/src/models/managed_roles.rs @@ -0,0 +1,60 @@ +use crate::error::Error; +use j_db::database::Database; +use poise::serenity_prelude::RoleId; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ManagedRole { + id: Option, + pub role_id: RoleId, + pub description: String, +} + +impl j_db::model::JdbModel for ManagedRole { + fn id(&self) -> Option { + self.id + } + + fn set_id(&mut self, id: u64) { + self.id = Some(id) + } + + fn tree() -> String { + "ManagedRoles".to_string() + } +} + +impl ManagedRole { + pub fn new(role_id: RoleId, description: String) -> Self { + Self { + id: None, + role_id, + description, + } + } + + pub fn add_role(db: &Database, role_id: RoleId, description: String) -> Result { + Ok(db.insert(Self::new(role_id, description))?) + } + + pub fn get_role(db: &Database, role_id: RoleId) -> Result, Error> { + Ok(db + .filter(|_, role: &Self| role.role_id == role_id)? + .next() + .clone()) + } + + pub fn list_roles(db: &Database) -> Result, Error> { + Ok(db.filter(|_, _role: &Self| true)?.collect()) + } + + pub fn remove_role(db: &Database, role_id: RoleId) -> Result<(), Error> { + let role = Self::get_role(db, role_id)?; + + if let Some(role) = role { + db.remove::(role.id.unwrap())?; + } + + Ok(()) + } +} diff --git a/src/models/mod.rs b/src/models/mod.rs index e501737..9f82883 100644 --- a/src/models/mod.rs +++ b/src/models/mod.rs @@ -2,6 +2,7 @@ pub mod api_key; pub mod birthday; pub mod insult_compliment; pub mod lil_fren; +pub mod managed_roles; pub mod motivation; pub mod race; pub mod random;