From 1a18996496fa04f6024902ed174578e601a0e288 Mon Sep 17 00:00:00 2001 From: Carter Himmel Date: Fri, 25 Aug 2023 12:33:39 -0600 Subject: [PATCH] feat: `/threads add_role` command --- chuckle-gateway/src/lib.rs | 7 +- chuckle-interactions/commands.lock.json | 300 ++++++++++--------- chuckle-interactions/src/commands/mod.rs | 1 + chuckle-interactions/src/commands/threads.rs | 101 +++++++ chuckle-interactions/src/lib.rs | 7 +- 5 files changed, 273 insertions(+), 143 deletions(-) create mode 100644 chuckle-interactions/src/commands/threads.rs diff --git a/chuckle-gateway/src/lib.rs b/chuckle-gateway/src/lib.rs index f9c3609..182cdcb 100644 --- a/chuckle-gateway/src/lib.rs +++ b/chuckle-gateway/src/lib.rs @@ -15,13 +15,14 @@ const BOT_EVENTS: EventTypeFlags = EventTypeFlags::from_bits_truncate( | EventTypeFlags::CHANNEL_UPDATE.bits() | EventTypeFlags::CHANNEL_DELETE.bits() | EventTypeFlags::INTERACTION_CREATE.bits() - | EventTypeFlags::GUILD_VOICE_STATES.bits(), + | EventTypeFlags::GUILD_VOICE_STATES.bits() + | EventTypeFlags::GUILD_MEMBERS.bits(), ); pub async fn create_gateway(state: ChuckleState, framework: ChuckleFramework) -> Result<()> { let config = Config::builder( CONFIG.discord_token.clone(), - Intents::GUILDS | Intents::GUILD_VOICE_STATES, + Intents::GUILDS | Intents::GUILD_VOICE_STATES | Intents::GUILD_MEMBERS, ) .event_types(BOT_EVENTS) .build(); @@ -38,7 +39,7 @@ pub async fn create_gateway(state: ChuckleState, framework: ChuckleFramework) -> let state = state.clone(); let framework = framework.clone(); - tokio::spawn(async move { handle_event(state, framework, event).await }); + tokio::spawn(handle_event(state, framework, event)); } } diff --git a/chuckle-interactions/commands.lock.json b/chuckle-interactions/commands.lock.json index b6aa067..ab7c20c 100644 --- a/chuckle-interactions/commands.lock.json +++ b/chuckle-interactions/commands.lock.json @@ -1,41 +1,15 @@ [ { "default_member_permissions": null, - "description": "", - "name": "Circle Back", - "options": [], - "type": 3, - "version": "1" - }, - { - "default_member_permissions": null, - "description": "Get the comments for a PR.", - "name": "pr-comments", + "description": "Set a custom role color.", + "name": "hexil", "options": [ { "autocomplete": false, "choices": [], - "description": "The PR number to register for.", - "max_value": 32767, - "min_value": -32768, - "name": "pr", + "description": "A hex code to set your role color to.", + "name": "hex", "required": true, - "type": 4 - }, - { - "autocomplete": false, - "choices": [], - "description": "The owner of the repo.", - "name": "owner", - "required": false, - "type": 3 - }, - { - "autocomplete": false, - "choices": [], - "description": "The repo name.", - "name": "repo", - "required": false, "type": 3 } ], @@ -61,88 +35,50 @@ }, { "default_member_permissions": null, - "description": "Set a custom role color.", - "name": "hexil", - "options": [ - { - "autocomplete": false, - "choices": [], - "description": "A hex code to set your role color to.", - "name": "hex", - "required": true, - "type": 3 - } - ], + "description": "Ping the bot.", + "name": "ping", + "options": [], "type": 1, "version": "1" }, { "default_member_permissions": null, - "description": "Ping the bot.", - "name": "ping", + "description": "", + "name": "Circle Back", "options": [], - "type": 1, + "type": 3, "version": "1" }, { "default_member_permissions": null, - "description": "Commands for managing breakout rooms.", - "name": "breakout-rooms", + "description": "Get the comments for a PR.", + "name": "pr-comments", "options": [ { - "description": "Close breakout rooms and bring everyone back.", - "name": "destroy", - "options": [ - { - "channel_types": [], - "description": "Where to bring everyone back to", - "name": "channel", - "required": true, - "type": 7 - } - ], - "type": 1 + "autocomplete": false, + "choices": [], + "description": "The PR number to register for.", + "max_value": 32767, + "min_value": -32768, + "name": "pr", + "required": true, + "type": 4 }, { - "description": "Separate the people in a voice channel into breakout rooms.", - "name": "create", - "options": [ - { - "channel_types": [], - "description": "Which voice channel to select people from", - "name": "channel", - "required": true, - "type": 7 - }, - { - "autocomplete": false, - "choices": [], - "description": "How many people per room", - "max_value": 255, - "min_value": 0, - "name": "size", - "required": true, - "type": 4 - }, - { - "autocomplete": false, - "choices": [ - { - "name": "Overflow", - "value": 1 - }, - { - "name": "Exclude", - "value": 2 - } - ], - "description": "What to do with people who don't fit into a room", - "name": "remainder_strategy", - "required": true, - "type": 4 - } - ], - "type": 1 + "autocomplete": false, + "choices": [], + "description": "The owner of the repo.", + "name": "owner", + "required": false, + "type": 3 + }, + { + "autocomplete": false, + "choices": [], + "description": "The repo name.", + "name": "repo", + "required": false, + "type": 3 } ], "type": 1, @@ -171,17 +107,51 @@ ], "type": 1 }, + { + "description": "List the current Breakout Rooms category", + "name": "list", + "options": [], + "type": 1 + }, { "description": "Unset the current Breakout Rooms category", "name": "unset", "options": [], "type": 1 - }, + } + ], + "type": 2 + }, + { + "description": "Configure the default GitHub repository for PR Comments.", + "name": "default-repo", + "options": [ { - "description": "List the current Breakout Rooms category", + "description": "List the default repository for PR Comments", "name": "list", "options": [], "type": 1 + }, + { + "description": "Set the default repository for PR Comments", + "name": "set", + "options": [ + { + "autocomplete": false, + "choices": [], + "description": "The default repository for PR Comments", + "name": "repository", + "required": true, + "type": 3 + } + ], + "type": 1 + }, + { + "description": "Unset the default GitHub repository for PR Comments", + "name": "unset", + "options": [], + "type": 1 } ], "type": 2 @@ -218,14 +188,14 @@ "type": 1 }, { - "description": "Unset the forum log channel.", - "name": "unset", + "description": "List the forum log channel.", + "name": "list", "options": [], "type": 1 }, { - "description": "List the forum log channel.", - "name": "list", + "description": "Unset the forum log channel.", + "name": "unset", "options": [], "type": 1 } @@ -233,72 +203,124 @@ "type": 2 }, { - "description": "Configure the default GitHub repository for PR Comments.", - "name": "default-repo", + "description": "Configure the default GitHub organization for PR Comments.", + "name": "default-org", "options": [ { - "description": "Unset the default GitHub repository for PR Comments", + "description": "Unset the default GitHub organization for PR Comments", "name": "unset", "options": [], "type": 1 }, { - "description": "Set the default repository for PR Comments", + "description": "List the default organization for PR Comments", + "name": "list", + "options": [], + "type": 1 + }, + { + "description": "Set the default organization for PR Comments", "name": "set", "options": [ { "autocomplete": false, "choices": [], - "description": "The default repository for PR Comments", - "name": "repository", + "description": "The default organization for PR Comments", + "name": "organization", "required": true, "type": 3 } ], "type": 1 - }, - { - "description": "List the default repository for PR Comments", - "name": "list", - "options": [], - "type": 1 } ], "type": 2 - }, + } + ], + "type": 1, + "version": "1" + }, + { + "default_member_permissions": null, + "description": "Commands for managing breakout rooms.", + "name": "breakout-rooms", + "options": [ { - "description": "Configure the default GitHub organization for PR Comments.", - "name": "default-org", + "description": "Separate the people in a voice channel into breakout rooms.", + "name": "create", "options": [ { - "description": "Unset the default GitHub organization for PR Comments", - "name": "unset", - "options": [], - "type": 1 + "channel_types": [], + "description": "Which voice channel to select people from", + "name": "channel", + "required": true, + "type": 7 }, { - "description": "Set the default organization for PR Comments", - "name": "set", - "options": [ + "autocomplete": false, + "choices": [], + "description": "How many people per room", + "max_value": 255, + "min_value": 0, + "name": "size", + "required": true, + "type": 4 + }, + { + "autocomplete": false, + "choices": [ { - "autocomplete": false, - "choices": [], - "description": "The default organization for PR Comments", - "name": "organization", - "required": true, - "type": 3 + "name": "Overflow", + "value": 1 + }, + { + "name": "Exclude", + "value": 2 } ], - "type": 1 - }, + "description": "What to do with people who don't fit into a room", + "name": "remainder_strategy", + "required": true, + "type": 4 + } + ], + "type": 1 + }, + { + "description": "Close breakout rooms and bring everyone back.", + "name": "destroy", + "options": [ { - "description": "List the default organization for PR Comments", - "name": "list", - "options": [], - "type": 1 + "channel_types": [], + "description": "Where to bring everyone back to", + "name": "channel", + "required": true, + "type": 7 } ], - "type": 2 + "type": 1 + } + ], + "type": 1, + "version": "1" + }, + { + "default_member_permissions": null, + "description": "Commands for managing threads.", + "name": "threads", + "options": [ + { + "description": "Add all people from a provided role to this thread.", + "name": "add_role", + "options": [ + { + "description": "Which role to add from", + "name": "role", + "required": true, + "type": 8 + } + ], + "type": 1 } ], "type": 1, diff --git a/chuckle-interactions/src/commands/mod.rs b/chuckle-interactions/src/commands/mod.rs index 0359f05..2592e96 100644 --- a/chuckle-interactions/src/commands/mod.rs +++ b/chuckle-interactions/src/commands/mod.rs @@ -10,6 +10,7 @@ use zephyrus::{framework::DefaultError, prelude::*}; // groups pub mod breakout_rooms; pub mod config; +pub mod threads; mod hexil; mod link_github; diff --git a/chuckle-interactions/src/commands/threads.rs b/chuckle-interactions/src/commands/threads.rs new file mode 100644 index 0000000..0082aea --- /dev/null +++ b/chuckle-interactions/src/commands/threads.rs @@ -0,0 +1,101 @@ +use chuckle_util::{db::get_settings, ChuckleState}; +use twilight_model::http::interaction::{ + InteractionResponse, InteractionResponseData, InteractionResponseType, +}; +use twilight_model::{channel::message::AllowedMentions, id::Id}; +use zephyrus::{prelude::*, twilight_exports::RoleMarker}; + +use super::handle_generic_error; + +// create a function that will take an array of things that implement to_string or debug +// join them all on the provided separator +// and, for the last one, use the word and instead of the separator +// so, for example, if you have a list of 3 things, you'd get "a, b, and c" +// if you have a list of 2 things, you'd get "a and b" +// if you have a list of 1 thing, you'd get "a" +// if you have a list of 0 things, you'd get "" +fn join_with_and(items: &[T], separator: &str) -> String { + match items.len() { + 0 => "".to_string(), + 1 => items[0].to_string(), + 2 => format!("{} and {}", items[0], items[1]), + _ => format!( + "{} and {}", + items[..items.len() - 1] + .iter() + .map(|i| i.to_string()) + .collect::>() + .join(separator), + items[items.len() - 1] + ), + } +} + +#[tracing::instrument(skip(ctx))] +#[command] +#[description = "Add all people from a provided role to this thread."] +#[only_guilds] +#[error_handler(handle_generic_error)] +pub async fn add_role( + ctx: &SlashContext, + #[description = "Which role to add from"] role: Id, +) -> DefaultCommandResult { + let _settings = get_settings(ctx.data, ctx.interaction.guild_id.unwrap()).await?; + + let members = ctx + .http_client() + .guild_members(ctx.interaction.guild_id.unwrap()) + .limit(500) + .unwrap() + .await + .unwrap() + .model() + .await + .unwrap(); + let role_members = members + .into_iter() + .filter(|m| m.roles.contains(&role)) + .collect::>(); + + for member in &role_members { + let res = ctx + .http_client() + .add_thread_member(ctx.interaction.channel.clone().unwrap().id, member.user.id) + .await; + if let Err(e) = res { + tracing::warn!(?e, "error adding thread member"); + } + } + + let content = format!( + "Successfully added {} member{} to the thread: {}", + role_members.len(), + // plurality + if role_members.len() == 1 { "" } else { "s" }, + join_with_and( + &role_members + .iter() + .map(|m| format!("<@{}>", m.user.id)) + .collect::>(), + ", " + ) + ); + + ctx.interaction_client + .create_response( + ctx.interaction.id, + &ctx.interaction.token, + &InteractionResponse { + kind: InteractionResponseType::ChannelMessageWithSource, + data: Some(InteractionResponseData { + content: Some(content), + flags: None, + allowed_mentions: Some(AllowedMentions::default()), + ..Default::default() + }), + }, + ) + .await?; + + Ok(()) +} diff --git a/chuckle-interactions/src/lib.rs b/chuckle-interactions/src/lib.rs index d77510b..a40924c 100644 --- a/chuckle-interactions/src/lib.rs +++ b/chuckle-interactions/src/lib.rs @@ -9,7 +9,7 @@ use twilight_model::id::marker::ApplicationMarker; use twilight_model::id::Id; use zephyrus::prelude::Framework; -use self::commands::{breakout_rooms, config, hexil, link_github, ping, pr_comments}; +use self::commands::{breakout_rooms, config, hexil, link_github, ping, pr_comments, threads}; pub mod commands; pub mod context_menu; @@ -64,6 +64,11 @@ pub fn crate_framework(state: ChuckleState) -> anyhow::Result .command(breakout_rooms::create) .command(breakout_rooms::destroy) }) + .group(|g| { + g.name("threads") + .description("Commands for managing threads.") + .command(threads::add_role) + }) .command(hexil) .command(link_github) .command(ping)