diff --git a/src/handler.rs b/src/handler.rs index d746e9e..80670ce 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -1,4 +1,5 @@ use color_eyre::eyre::{eyre, ContextCompat}; +use mongodb::bson::Uuid; use serenity::{ async_trait, builder::CreateApplicationCommands, @@ -11,11 +12,10 @@ use serenity::{ use tracing::{debug, error, info}; use crate::{ - lawsuit::{Lawsuit, LawsuitState}, + lawsuit::{Lawsuit, LawsuitCtx}, model::SnowflakeId, Mongo, WrapErr, }; -use crate::lawsuit::LawsuitCtx; fn slash_commands(commands: &mut CreateApplicationCommands) -> &mut CreateApplicationCommands { commands.create_application_command(|command| { @@ -83,6 +83,25 @@ fn slash_commands(commands: &mut CreateApplicationCommands) -> &mut CreateApplic .required(true) }) }) + .create_option(|option| { + option + .name("close") + .description("Den Prozess abschliessen") + .kind(ApplicationCommandOptionType::SubCommand) + .create_sub_option(|option| { + option + .name("verdict") + .description("Das Urteil") + .kind(ApplicationCommandOptionType::String) + .required(true) + }) + }) + .create_option(|option| { + option + .name("clear") + .description("Alle Rechtsprozessdaten löschen") + .kind(ApplicationCommandOptionType::SubCommand) + }) }) } @@ -93,7 +112,9 @@ pub struct Handler { } pub enum Response { - Simple(String), + EphemeralStr(&'static str), + Ephemeral(String), + NoPermissions, } #[async_trait] @@ -141,7 +162,7 @@ impl Handler { let response = match command.data.name.as_str() { "lawsuit" => lawsuit_command_handler(&command, &ctx, &self.mongo).await, - _ => Ok(Response::Simple("not implemented :(".to_owned())), + _ => Ok(Response::EphemeralStr("not implemented :(")), }; match response { @@ -151,7 +172,7 @@ impl Handler { self.send_response( ctx, command, - Response::Simple("An internal error occurred".to_owned()), + Response::EphemeralStr("An internal error occurred"), ) .await } @@ -166,9 +187,27 @@ impl Handler { ) -> color_eyre::Result<()> { command .create_interaction_response(&ctx.http, |res| match response { - Response::Simple(content) => res + Response::EphemeralStr(content) => res .kind(InteractionResponseType::ChannelMessageWithSource) - .interaction_response_data(|message| message.content(content)), + .interaction_response_data(|message| { + message + .content(content) + .flags(InteractionApplicationCommandCallbackDataFlags::EPHEMERAL) + }), + Response::Ephemeral(content) => res + .kind(InteractionResponseType::ChannelMessageWithSource) + .interaction_response_data(|message| { + message + .content(content) + .flags(InteractionApplicationCommandCallbackDataFlags::EPHEMERAL) + }), + Response::NoPermissions => res + .kind(InteractionResponseType::ChannelMessageWithSource) + .interaction_response_data(|message| { + message + .content("du häsch kei recht für da!") + .flags(InteractionApplicationCommandCallbackDataFlags::EPHEMERAL) + }), }) .await .wrap_err("sending response")?; @@ -187,8 +226,18 @@ async fn lawsuit_command_handler( let options = &subcommand.options; let guild_id = command.guild_id.wrap_err("guild_id not found")?; + let member = command + .member + .as_ref() + .wrap_err("command must be used my member")?; + let permissions = member.permissions.wrap_err("must be in interaction")?; + match subcommand.name.as_str() { "create" => { + if !permissions.contains(Permissions::MANAGE_GUILD) { + return Ok(Response::NoPermissions); + } + let plaintiff = UserOption::get(options.get(0)).wrap_err("plaintiff")?; let accused = UserOption::get(options.get(1)).wrap_err("accused")?; let judge = UserOption::get(options.get(2)).wrap_err("judge")?; @@ -199,13 +248,14 @@ async fn lawsuit_command_handler( UserOption::get_optional(options.get(5)).wrap_err("accused_layer")?; let lawsuit = Lawsuit { + id: Uuid::new(), plaintiff: plaintiff.0.id.into(), accused: accused.0.id.into(), judge: judge.0.id.into(), plaintiff_lawyer: plaintiff_layer.map(|user| user.0.id.into()), accused_lawyer: accused_layer.map(|user| user.0.id.into()), reason: reason.to_owned(), - state: LawsuitState::Initial, + verdict: None, court_room: SnowflakeId(0), }; @@ -213,7 +263,7 @@ async fn lawsuit_command_handler( lawsuit, mongo_client: mongo_client.clone(), http: ctx.http.clone(), - guild_id + guild_id, }; let response = lawsuit_ctx @@ -224,6 +274,10 @@ async fn lawsuit_command_handler( Ok(response) } "set_category" => { + if !permissions.contains(Permissions::MANAGE_GUILD) { + return Ok(Response::NoPermissions); + } + let channel = ChannelOption::get(options.get(0))?; let channel = channel @@ -238,10 +292,79 @@ async fn lawsuit_command_handler( .set_court_category(guild_id.into(), id.into()) .await?; } - None => return Ok(Response::Simple("Das ist keine Kategorie!".to_owned())), + None => return Ok(Response::EphemeralStr("Das ist keine Kategorie!")), } - Ok(Response::Simple("isch gsetzt".to_owned())) + Ok(Response::EphemeralStr("isch gsetzt")) + } + "close" => { + let permission_override = permissions.contains(Permissions::MANAGE_GUILD); + + let verdict = StringOption::get(options.get(0))?; + + let room_id = command.channel_id; + + let state = mongo_client + .find_or_insert_state(guild_id.into()) + .await + .wrap_err("find guild for verdict")?; + + let lawsuit = state + .lawsuits + .iter() + .find(|l| l.court_room == room_id.into() && l.verdict.is_none()); + + let lawsuit = match lawsuit { + Some(lawsuit) => lawsuit.clone(), + None => { + return Ok(Response::EphemeralStr( + "i dem channel lauft kein aktive prozess!", + )) + } + }; + + let room = state + .court_rooms + .iter() + .find(|r| r.channel_id == room_id.into()); + let room = match room { + Some(room) => room.clone(), + None => { + return Ok(Response::EphemeralStr( + "i dem channel lauft kein aktive prozess!", + )) + } + }; + + let mut lawsuit_ctx = LawsuitCtx { + lawsuit, + mongo_client: mongo_client.clone(), + http: ctx.http.clone(), + guild_id, + }; + + let response = lawsuit_ctx + .rule_verdict( + permission_override, + member.user.id, + verdict.to_string(), + room, + ) + .await?; + + if let Err(response) = response { + return Ok(response); + } + + Ok(Response::EphemeralStr("ich han en dir abschlosse")) + } + "clear" => { + if !permissions.contains(Permissions::MANAGE_GUILD) { + return Ok(Response::NoPermissions); + } + + mongo_client.delete_guild(guild_id.into()).await?; + Ok(Response::EphemeralStr("alles weg")) } _ => Err(eyre!("Unknown subcommand")), } diff --git a/src/lawsuit.rs b/src/lawsuit.rs index ab5a60e..9beb0e3 100644 --- a/src/lawsuit.rs +++ b/src/lawsuit.rs @@ -1,9 +1,10 @@ use std::sync::Arc; use color_eyre::Result; -use mongodb::bson::doc; +use mongodb::bson::{doc, Uuid}; use serde::{Deserialize, Serialize}; use serenity::{ + builder::CreateMessage, http::Http, model::{channel::PermissionOverwriteType, prelude::*, Permissions}, }; @@ -15,22 +16,16 @@ use crate::{ Mongo, WrapErr, }; -#[derive(Debug, Clone, Copy, Serialize, Deserialize)] -pub enum LawsuitState { - Initial, - InProgress, - Completed, -} - #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Lawsuit { + pub id: Uuid, pub plaintiff: SnowflakeId, pub accused: SnowflakeId, pub plaintiff_lawyer: Option, pub accused_lawyer: Option, pub judge: SnowflakeId, pub reason: String, - pub state: LawsuitState, + pub verdict: Option, pub court_room: SnowflakeId, } @@ -69,9 +64,8 @@ impl LawsuitCtx { Ok(room) => room, } } - (None, None) => return Ok(Response::Simple( - "Zuerst eine Kategorie für die Gerichtsräume festlegen mit `/lawsuit set_category`" - .to_owned(), + (None, None) => return Ok(Response::EphemeralStr( + "Zuerst eine Kategorie für die Gerichtsräume festlegen mit `/lawsuit set_category`", )), }; @@ -93,7 +87,7 @@ impl LawsuitCtx { } }); - Ok(Response::Simple(format!( + Ok(Response::Ephemeral(format!( "ha eine ufgmacht im channel <#{}>", channel_id ))) @@ -131,27 +125,176 @@ impl LawsuitCtx { Ok(()) } - assign_role(lawsuit.accused, &http, guild_id, room.role_id).await?; + assign_role(lawsuit.accused, http, guild_id, room.role_id).await?; if let Some(accused_lawyer) = lawsuit.accused_lawyer { - assign_role(accused_lawyer, &http, guild_id, room.role_id).await?; + assign_role(accused_lawyer, http, guild_id, room.role_id).await?; } - assign_role(lawsuit.plaintiff, &http, guild_id, room.role_id).await?; + assign_role(lawsuit.plaintiff, http, guild_id, room.role_id).await?; if let Some(plaintiff_lawyer) = lawsuit.plaintiff_lawyer { - assign_role(plaintiff_lawyer, &http, guild_id, room.role_id).await?; + assign_role(plaintiff_lawyer, http, guild_id, room.role_id).await?; } - assign_role(lawsuit.judge, &http, guild_id, room.role_id).await?; + assign_role(lawsuit.judge, http, guild_id, room.role_id).await?; info!(?lawsuit, "Created lawsuit"); Ok(()) } + pub async fn rule_verdict( + &mut self, + permission_override: bool, + user_id: UserId, + verdict: String, + room: CourtRoom, + ) -> Result> { + if self.lawsuit.judge != user_id.into() && !permission_override { + return Ok(Err(Response::NoPermissions)); + } + + self.lawsuit.verdict = Some(verdict); + let lawsuit = &self.lawsuit; + + async fn remove_role( + user: SnowflakeId, + http: &Http, + guild_id: GuildId, + role_id: SnowflakeId, + ) -> Result<()> { + let mut member = guild_id.member(http, user).await.wrap_err("fetch member")?; + member + .remove_role(http, role_id) + .await + .wrap_err("remove role from member")?; + + Ok(()) + } + + let http = &self.http; + let guild_id = self.guild_id; + + tokio::try_join!( + self.mongo_client.set_court_room( + self.guild_id.into(), + lawsuit.court_room, + doc! { "court_rooms.$.ongoing_lawsuit": false }, + ), + self.mongo_client.set_lawsuit( + self.guild_id.into(), + lawsuit.id, + doc! { "lawsuits.$.verdict": &lawsuit.verdict }, + ), + remove_role(lawsuit.accused, http, guild_id, room.role_id), + remove_role(lawsuit.plaintiff, http, guild_id, room.role_id), + remove_role(lawsuit.judge, http, guild_id, room.role_id), + )?; + + if let Some(accused_lawyer) = lawsuit.accused_lawyer { + remove_role(accused_lawyer, http, guild_id, room.role_id).await?; + } + if let Some(plaintiff_lawyer) = lawsuit.plaintiff_lawyer { + remove_role(plaintiff_lawyer, http, guild_id, room.role_id).await?; + } + + let response = self + .send_process_close_message(http, guild_id, &room) + .await?; + + info!(?lawsuit, "Closed lawsuit"); + + if let Err(response) = response { + return Ok(Err(response)); + } + + Ok(Ok(())) + } + async fn send_process_open_message( &self, http: &Http, guild_id: GuildId, room: &CourtRoom, ) -> Result> { + self.send_court_message(http, guild_id, room, |msg| { + msg.embed(|embed| { + let lawsuit = &self.lawsuit; + embed + .title("Prozess") + .field("Grund", &lawsuit.reason, false) + .field("Kläger", format!("<@{}>", lawsuit.plaintiff), true) + .field( + "Anwalt des Klägers", + match &lawsuit.plaintiff_lawyer { + Some(lawyer) => format!("<@{}>", lawyer), + None => "Keinen".to_string(), + }, + true, + ) + .field("Angeklagter", format!("<@{}>", lawsuit.accused), true) + .field( + "Anwalt des Angeklagten", + match &lawsuit.accused_lawyer { + Some(lawyer) => format!("<@{}>", lawyer), + None => "Keinen".to_string(), + }, + true, + ) + .field("Richter", format!("<@{}>", lawsuit.judge), true) + }) + }) + .await + } + + async fn send_process_close_message( + &self, + http: &Http, + guild_id: GuildId, + room: &CourtRoom, + ) -> Result> { + self.send_court_message(http, guild_id, room, |msg| { + msg.embed(|embed| { + let lawsuit = &self.lawsuit; + embed + .title("Prozess abgeschlossen") + .field("Grund", &lawsuit.reason, false) + .field("Kläger", format!("<@{}>", lawsuit.plaintiff), true) + .field( + "Anwalt des Klägers", + match &lawsuit.plaintiff_lawyer { + Some(lawyer) => format!("<@{}>", lawyer), + None => "Keinen".to_string(), + }, + true, + ) + .field("Angeklagter", format!("<@{}>", lawsuit.accused), true) + .field( + "Anwalt des Angeklagten", + match &lawsuit.accused_lawyer { + Some(lawyer) => format!("<@{}>", lawyer), + None => "Keinen".to_string(), + }, + true, + ) + .field("Richter", format!("<@{}>", lawsuit.judge), true) + .field( + "Urteil", + lawsuit.verdict.clone().expect("no verdict found!"), + true, + ) + }) + }) + .await + } + + async fn send_court_message<'a, F>( + &self, + http: &Http, + guild_id: GuildId, + room: &CourtRoom, + embed_builder: F, + ) -> Result> + where + for<'b> F: FnOnce(&'b mut CreateMessage<'a>) -> &'b mut CreateMessage<'a>, + { let channels = guild_id .to_partial_guild(http) .await @@ -165,40 +308,14 @@ impl LawsuitCtx { Some(channel) => { channel .id - .send_message(http, |msg| { - msg.embed(|embed| { - let lawsuit = &self.lawsuit; - embed - .title("Prozess") - .field("Grund", &lawsuit.reason, false) - .field("Kläger", format!("<@{}>", lawsuit.plaintiff), false) - .field( - "Anwalt des Klägers", - match &lawsuit.plaintiff_lawyer { - Some(lawyer) => format!("<@{}>", lawyer), - None => "TBD".to_string(), - }, - false, - ) - .field("Angeklagter", format!("<@{}>", lawsuit.accused), false) - .field( - "Anwalt des Angeklagten", - match &lawsuit.accused_lawyer { - Some(lawyer) => format!("<@{}>", lawyer), - None => "TBD".to_string(), - }, - false, - ) - .field("Richter", format!("<@{}>", lawsuit.judge), false) - }) - }) + .send_message(http, embed_builder) .await .wrap_err("send message")?; } None => { // todo: remove the court room from the db - return Ok(Err(Response::Simple( - "i ha de channel zum de prozess öffne nöd gfunde".to_string(), + return Ok(Err(Response::EphemeralStr( + "i ha de channel für de prozess nöd gfunde", ))); } } @@ -242,7 +359,7 @@ impl LawsuitCtx { let channel_id = match channels.values().find(|c| c.name() == room_name) { Some(channel) => { if channel.parent_id != Some(category_id.into()) { - return Ok(Err(Response::Simple(format!( + return Ok(Err(Response::Ephemeral(format!( "de channel {room_name} isch i de falsche kategorie, man eh" )))); } diff --git a/src/model.rs b/src/model.rs index c07e9ec..84937df 100644 --- a/src/model.rs +++ b/src/model.rs @@ -7,7 +7,7 @@ use std::{ use color_eyre::Result; use mongodb::{ bson, - bson::{doc, Bson}, + bson::{doc, Bson, Uuid}, options::{ClientOptions, Credential}, Client, Collection, Database, }; @@ -227,6 +227,40 @@ impl Mongo { Ok(()) } + pub async fn set_lawsuit( + &self, + guild_id: SnowflakeId, + lawsuit_id: Uuid, + value: impl Into, + ) -> Result<()> { + let _ = self.find_or_insert_state(guild_id).await?; + let coll = self.state_coll(); + + coll.update_one( + doc! { "guild_id": &guild_id, "lawsuit.id": lawsuit_id }, + doc! { "$set": value.into() }, + None, + ) + .await + .wrap_err("set courtroom")?; + Ok(()) + } + + pub async fn delete_guild( + &self, + guild_id: SnowflakeId, + ) -> Result<()> { + let coll = self.state_coll(); + + coll.delete_one( + doc! { "guild_id": &guild_id }, + None, + ) + .await + .wrap_err("delete guild")?; + Ok(()) + } + fn state_coll(&self) -> Collection { self.db.collection("state") }