From ccd9a07e37bcd784c084f99715966a41595127e0 Mon Sep 17 00:00:00 2001 From: "oupson1er@gmail.com" Date: Fri, 19 Mar 2021 20:30:44 +0100 Subject: [PATCH] Play with slash commands --- src/commands/interaction.rs | 206 ++++++++++++++++++++++++++++++++++++ src/commands/mod.rs | 22 ++++ src/commands/music.rs | 28 +---- src/data/guilds_options.rs | 1 - src/main.rs | 12 ++- 5 files changed, 242 insertions(+), 27 deletions(-) create mode 100644 src/commands/interaction.rs diff --git a/src/commands/interaction.rs b/src/commands/interaction.rs new file mode 100644 index 0000000..4e06c8b --- /dev/null +++ b/src/commands/interaction.rs @@ -0,0 +1,206 @@ +use serde::Serialize; +use serde_json::Value; +use serenity::{ + client::Context, + framework::standard::CommandResult, + model::{channel::Embed, guild::Member, Permissions}, +}; + +use crate::data::GuildOptionsKey; + +pub(crate) async fn handle_interaction(ctx: &Context, object: Value) -> CommandResult { + let data = object.get("data").ok_or("Failed to get data")?; + + let id = object + .get("id") + .and_then(|i| i.as_str()) + .ok_or("Failed to get ID")?; + + let token = object + .get("token") + .and_then(|t| t.as_str()) + .ok_or("Failed to get token")?; + + let name = data + .get("name") + .and_then(|v| v.as_str()) + .ok_or("Failed to get name")?; + + let guild_id = object + .get("guild_id") + .and_then(|v| v.as_str()) + .and_then(|v| v.parse::().ok()) + .ok_or("Failed to get guild id")?; + + let author = object.get("member").ok_or("Failed to get author")?; + + let author_id = author + .get("user") + .and_then(|v| v.get("id")) + .and_then(|v| v.as_str()) + .and_then(|v| v.parse::().ok()) + .ok_or("Failed to get permission")?; + + let permissions = Permissions::from_bits_truncate( + author + .get("permissions") + .and_then(|v| v.as_str()) + .and_then(|v| v.parse::().ok()) + .ok_or("Failed to get permission")?, + ); + + let author_member = ctx + .cache + .guild(guild_id) + .await + .ok_or("Failed to get guild")? + .member(&ctx.http, author_id) + .await?; + + match name { + "goulag" => { + if permissions.administrator() { + goulag(ctx, data, guild_id, &author_member, id, token).await? + } else { + let _r = reqwest::Client::new() + .post(format!( + "https://discord.com/api/v8/interactions/{}/{}/callback", + id, token + )) + .json(&Response { + response_type: 4, + data: ResponseData { + tts: false, + embeds: Some(vec![Embed::fake(|e| { + super::embed_author(e, Some(&author_member)) + .title("Error") + .description("You don't have the right to do that") + .colour((247, 76, 0)) + })]), + content: None, + flags: 0, + }, + }) + .send() + .await?; + } + } + _ => (), + } + + Ok(()) +} + +async fn goulag( + ctx: &Context, + data: &Value, + guild_id: u64, + author: &Member, + id: &str, + token: &str, +) -> CommandResult { + let ctx_data = ctx.data.read().await; + let ctx_data = ctx_data + .get::() + .expect("Failed to get guild cache"); + + let guild_options = ctx_data.get(&guild_id.into()); + + if let Some(guild_options) = guild_options { + if let Some(mute_role) = guild_options.get_mute_role() { + let options = data + .get("options") + .and_then(|v| v.as_array()) + .ok_or("Failed to get options")?; + + let user = options + .iter() + .find(|f| { + if let Some(name) = f.get("name").map(|n| n.as_str()).and_then(|f| f) { + name == "user" + } else { + false + } + }) + .ok_or("Failed to get user")?; + + let user_id = user + .get("value") + .and_then(|v| v.as_str()) + .and_then(|v| v.parse::().ok()) + .ok_or("Failed to get user id")?; + + let mut member = ctx + .cache + .guild(guild_id) + .await + .ok_or("Failed to get guild")? + .member(&ctx.http, user_id) + .await?; + + member.add_role(&ctx.http, mute_role).await?; + let _r = reqwest::Client::new() + .post(format!( + "https://discord.com/api/v8/interactions/{}/{}/callback", + id, token + )) + .json(&Response { + response_type: 4, + data: ResponseData { + tts: false, + embeds: Some(vec![Embed::fake(|e| { + super::embed_author(e, Some(author)) + .title("Mute") + .description(format!("{} was muted", member.display_name())) + .colour((247, 76, 0)) + })]), + content: None, + flags: 0, + }, + }) + .send() + .await?; + + return Ok(()); + } + } + + let _r = reqwest::Client::new() + .post(format!( + "https://discord.com/api/v8/interactions/{}/{}/callback", + id, token + )) + .json(&Response { + response_type: 4, + data: ResponseData { + tts: false, + embeds: Some(vec![Embed::fake(|e| { + super::embed_author(e, Some(author)) + .title("Error") + .description("Unknown mute role") + .colour((247, 76, 0)) + })]), + content: None, + flags: 0, + }, + }) + .send() + .await?; + + Ok(()) +} + +#[derive(Debug, Serialize)] +struct ResponseData { + tts: bool, + content: Option, + embeds: Option>, + flags: u64, +} + +#[derive(Debug, Serialize)] +struct Response { + #[serde(rename(serialize = "type"))] + response_type: u64, + data: ResponseData, +} diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 4fd61e8..666dee7 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -1,5 +1,8 @@ +use serenity::{builder::CreateEmbed, model::guild::Member}; + pub(crate) mod admin; pub(crate) mod general; +pub(crate) mod interaction; pub(crate) mod owner; pub(crate) mod roulette; pub(crate) mod settings; @@ -8,3 +11,22 @@ pub(crate) mod settings; pub(crate) mod music; pub(crate) type Result = std::result::Result>; + +fn embed_author<'a>(e: &'a mut CreateEmbed, author: Option<&Member>) -> &'a mut CreateEmbed { + if let Some(author) = author { + e.footer(|f| { + f.text(if let Some(nick) = &author.nick { + nick + } else { + &author.user.name + }); + + if let Some(url) = &author.user.avatar_url() { + f.icon_url(url); + } + f + }) + } else { + e + } +} diff --git a/src/commands/music.rs b/src/commands/music.rs index 416e53f..b5a0e87 100644 --- a/src/commands/music.rs +++ b/src/commands/music.rs @@ -5,7 +5,7 @@ use tokio::sync::Mutex as TokioMutex; use crate::data::{GuildOptions, GuildOptionsKey}; use log::error; use serenity::{ - builder::{CreateEmbed, CreateMessage}, + builder::CreateMessage, client::Context, framework::standard::{ macros::{command, group}, @@ -21,6 +21,8 @@ use serenity::{ }; use songbird::{input::Metadata, Call, Event, EventContext, TrackEvent}; +use super::embed_author; + struct TrackStartNotifier { chan_id: ChannelId, http: Arc, @@ -149,28 +151,6 @@ fn embed_response<'a, 'b>( }) } -fn embed_author<'a>(e: &'a mut CreateEmbed, author: Option<&Member>) -> &'a mut CreateEmbed { - if let Some(author) = author { - e.footer(|f| { - f.text(format!( - "{}", - if let Some(nick) = &author.nick { - nick - } else { - &author.user.name - } - )); - - if let Some(url) = &author.user.avatar_url() { - f.icon_url(url); - } - f - }) - } else { - e - } -} - #[group] #[commands(join, leave, play, stop, next, pause, resume)] struct Music; @@ -695,7 +675,7 @@ async fn is_mute( member: &PartialMember, guild_id: GuildId, ) -> tokio::io::Result { - let mut data = ctx.data.write().await; + let mut data = ctx.data.write().await; // TODO READ NOT WRITE let data = data .get_mut::() .expect("Failed to get guild cache"); diff --git a/src/data/guilds_options.rs b/src/data/guilds_options.rs index 019a658..a2e6d23 100644 --- a/src/data/guilds_options.rs +++ b/src/data/guilds_options.rs @@ -83,7 +83,6 @@ impl GuildOptions { Ok(res) } - #[cfg(feature = "music")] pub(crate) fn get_mute_role(&self) -> Option { self.mute_id } diff --git a/src/main.rs b/src/main.rs index 1886af9..4185843 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,6 +6,7 @@ use crate::{ data::{BulletsContainer, GuildOptions, GuildOptionsKey, ShardManagerContainer}, }; use async_trait::async_trait; +use commands::interaction; use log::{debug, error, info}; use serde_json::Value; use serenity::{ @@ -218,8 +219,15 @@ impl EventHandler for Messages { } } - async fn unknown(&self, _ctx: Context, name: String, raw: Value) { - debug!("Unknown event : {}, {:?}", name, raw); + async fn unknown(&self, ctx: Context, name: String, raw: Value) { + match name.as_str() { + "INTERACTION_CREATE" => { + if let Err(e) = interaction::handle_interaction(&ctx, raw).await { + error!("While handling interaction : {}", e); + } + } + _ => debug!("Unknown event : {}, {:?}", name, raw), + }; } }