Play with slash commands
This commit is contained in:
parent
f0b3485060
commit
ccd9a07e37
|
@ -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::<u64>().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::<u64>().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::<u64>().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::<GuildOptionsKey>()
|
||||||
|
.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::<u64>().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<String>,
|
||||||
|
embeds: Option<Vec<Value>>,
|
||||||
|
flags: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize)]
|
||||||
|
struct Response {
|
||||||
|
#[serde(rename(serialize = "type"))]
|
||||||
|
response_type: u64,
|
||||||
|
data: ResponseData,
|
||||||
|
}
|
|
@ -1,5 +1,8 @@
|
||||||
|
use serenity::{builder::CreateEmbed, model::guild::Member};
|
||||||
|
|
||||||
pub(crate) mod admin;
|
pub(crate) mod admin;
|
||||||
pub(crate) mod general;
|
pub(crate) mod general;
|
||||||
|
pub(crate) mod interaction;
|
||||||
pub(crate) mod owner;
|
pub(crate) mod owner;
|
||||||
pub(crate) mod roulette;
|
pub(crate) mod roulette;
|
||||||
pub(crate) mod settings;
|
pub(crate) mod settings;
|
||||||
|
@ -8,3 +11,22 @@ pub(crate) mod settings;
|
||||||
pub(crate) mod music;
|
pub(crate) mod music;
|
||||||
|
|
||||||
pub(crate) type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;
|
pub(crate) type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ use tokio::sync::Mutex as TokioMutex;
|
||||||
use crate::data::{GuildOptions, GuildOptionsKey};
|
use crate::data::{GuildOptions, GuildOptionsKey};
|
||||||
use log::error;
|
use log::error;
|
||||||
use serenity::{
|
use serenity::{
|
||||||
builder::{CreateEmbed, CreateMessage},
|
builder::CreateMessage,
|
||||||
client::Context,
|
client::Context,
|
||||||
framework::standard::{
|
framework::standard::{
|
||||||
macros::{command, group},
|
macros::{command, group},
|
||||||
|
@ -21,6 +21,8 @@ use serenity::{
|
||||||
};
|
};
|
||||||
use songbird::{input::Metadata, Call, Event, EventContext, TrackEvent};
|
use songbird::{input::Metadata, Call, Event, EventContext, TrackEvent};
|
||||||
|
|
||||||
|
use super::embed_author;
|
||||||
|
|
||||||
struct TrackStartNotifier {
|
struct TrackStartNotifier {
|
||||||
chan_id: ChannelId,
|
chan_id: ChannelId,
|
||||||
http: Arc<Http>,
|
http: Arc<Http>,
|
||||||
|
@ -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]
|
#[group]
|
||||||
#[commands(join, leave, play, stop, next, pause, resume)]
|
#[commands(join, leave, play, stop, next, pause, resume)]
|
||||||
struct Music;
|
struct Music;
|
||||||
|
@ -695,7 +675,7 @@ async fn is_mute(
|
||||||
member: &PartialMember,
|
member: &PartialMember,
|
||||||
guild_id: GuildId,
|
guild_id: GuildId,
|
||||||
) -> tokio::io::Result<bool> {
|
) -> tokio::io::Result<bool> {
|
||||||
let mut data = ctx.data.write().await;
|
let mut data = ctx.data.write().await; // TODO READ NOT WRITE
|
||||||
let data = data
|
let data = data
|
||||||
.get_mut::<GuildOptionsKey>()
|
.get_mut::<GuildOptionsKey>()
|
||||||
.expect("Failed to get guild cache");
|
.expect("Failed to get guild cache");
|
||||||
|
|
|
@ -83,7 +83,6 @@ impl GuildOptions {
|
||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "music")]
|
|
||||||
pub(crate) fn get_mute_role(&self) -> Option<RoleId> {
|
pub(crate) fn get_mute_role(&self) -> Option<RoleId> {
|
||||||
self.mute_id
|
self.mute_id
|
||||||
}
|
}
|
||||||
|
|
12
src/main.rs
12
src/main.rs
|
@ -6,6 +6,7 @@ use crate::{
|
||||||
data::{BulletsContainer, GuildOptions, GuildOptionsKey, ShardManagerContainer},
|
data::{BulletsContainer, GuildOptions, GuildOptionsKey, ShardManagerContainer},
|
||||||
};
|
};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
|
use commands::interaction;
|
||||||
use log::{debug, error, info};
|
use log::{debug, error, info};
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
use serenity::{
|
use serenity::{
|
||||||
|
@ -218,8 +219,15 @@ impl EventHandler for Messages {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn unknown(&self, _ctx: Context, name: String, raw: Value) {
|
async fn unknown(&self, ctx: Context, name: String, raw: Value) {
|
||||||
debug!("Unknown event : {}, {:?}", name, raw);
|
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),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue