Add integrations + better mentions handling + big red button
This commit is contained in:
parent
d1edef3623
commit
644bd66ec3
|
@ -10,7 +10,7 @@ edition = "2018"
|
|||
music = ["serenity/voice", "songbird"]
|
||||
|
||||
[dependencies]
|
||||
serenity = "0.10"
|
||||
serenity = { version = "0.10", features = ["unstable_discord_api"] }
|
||||
toml = "0.5"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
reqwest = "0.11"
|
||||
|
|
|
@ -23,7 +23,8 @@ use serenity::{
|
|||
older,
|
||||
ping,
|
||||
random_mute,
|
||||
uptime
|
||||
uptime,
|
||||
button
|
||||
)]
|
||||
pub struct General;
|
||||
|
||||
|
@ -283,6 +284,24 @@ async fn _image(ctx: &Context, msg: &Message, mut args: Args) -> crate::commands
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[command]
|
||||
async fn button(ctx: &Context, msg: &Message) -> CommandResult {
|
||||
msg.channel_id
|
||||
.send_message(ctx, |msg| {
|
||||
msg.content("An innofensive button").components(|f| {
|
||||
f.create_action_row(|row| {
|
||||
row.create_button(|b| {
|
||||
b.style(ButtonStyle::Danger)
|
||||
.label("Big Red Fucking Button")
|
||||
.custom_id("gulag_button")
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[command]
|
||||
#[required_permissions(ADMINISTRATOR)]
|
||||
async fn random_mute(ctx: &Context, msg: &Message) -> CommandResult {
|
||||
|
@ -336,7 +355,8 @@ async fn uptime(ctx: &Context, msg: &Message) -> CommandResult {
|
|||
let min = (seconds - (hours * 60 * 60)) / 60;
|
||||
let seconds = seconds - (hours * 3600) - (min * 60);
|
||||
|
||||
msg.channel_id.send_message(&ctx.http, |m| {
|
||||
msg.channel_id
|
||||
.send_message(&ctx.http, |m| {
|
||||
m.embed(|e| {
|
||||
e.field(
|
||||
"Uptime",
|
||||
|
@ -346,9 +366,11 @@ async fn uptime(ctx: &Context, msg: &Message) -> CommandResult {
|
|||
format!("{}min {:02}s", min, seconds)
|
||||
},
|
||||
false,
|
||||
).colour((247, 76, 0))
|
||||
)
|
||||
.colour((247, 76, 0))
|
||||
})
|
||||
}).await?;
|
||||
})
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -1,91 +1,85 @@
|
|||
use serde::Serialize;
|
||||
use serde_json::Value;
|
||||
use serenity::{
|
||||
builder::CreateEmbed,
|
||||
client::Context,
|
||||
framework::standard::CommandResult,
|
||||
model::{channel::Embed, guild::Member, Permissions},
|
||||
model::{
|
||||
guild::Member,
|
||||
interactions::{
|
||||
ApplicationCommandInteractionData, Interaction,
|
||||
InteractionApplicationCommandCallbackDataFlags, InteractionData,
|
||||
InteractionResponseType, MessageComponent,
|
||||
},
|
||||
Permissions,
|
||||
},
|
||||
};
|
||||
|
||||
use crate::{data::GuildOptionsKey, utils::message::embed_author};
|
||||
|
||||
pub(crate) async fn handle_interaction(ctx: &Context, object: Value) -> CommandResult {
|
||||
let data = object.get("data").ok_or("Failed to get data")?;
|
||||
pub(crate) async fn handle_interaction(ctx: &Context, interaction: &Interaction) -> CommandResult {
|
||||
let data = interaction.data.as_ref().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 {
|
||||
Response::new_with_embed(4, |e| {
|
||||
embed_author(e, Some(&author_member))
|
||||
.title("Error")
|
||||
.description("You don't have the right to do that")
|
||||
.colour((247, 76, 0))
|
||||
})
|
||||
.send(token, id)
|
||||
.await?;
|
||||
match data {
|
||||
InteractionData::ApplicationCommand(app) => {
|
||||
command(ctx, &interaction, &app).await?;
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
InteractionData::MessageComponent(msg) => message_interact(ctx, interaction, msg).await?,
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn command(
|
||||
ctx: &Context,
|
||||
interaction: &Interaction,
|
||||
data: &ApplicationCommandInteractionData,
|
||||
) -> CommandResult {
|
||||
let name = &data.name;
|
||||
|
||||
let guild_id = interaction.guild_id.ok_or("Failed to get guild id")?;
|
||||
|
||||
let author = interaction
|
||||
.member
|
||||
.as_ref()
|
||||
.ok_or("Failed to get permission")?;
|
||||
|
||||
let permissions = Permissions::from_bits_truncate(
|
||||
author
|
||||
.permissions
|
||||
.map(|v| v.bits)
|
||||
.ok_or("Failed to get permission")?,
|
||||
);
|
||||
|
||||
match name.as_str() {
|
||||
"goulag" => {
|
||||
if permissions.administrator() {
|
||||
goulag(ctx, interaction, data, guild_id.0, &author).await?
|
||||
} else {
|
||||
interaction
|
||||
.create_interaction_response(&ctx.http, |response| {
|
||||
response
|
||||
.kind(InteractionResponseType::ChannelMessageWithSource)
|
||||
.interaction_response_data(|message| {
|
||||
message.create_embed(|e| {
|
||||
embed_author(e, Some(author))
|
||||
.title("Error")
|
||||
.description("You don't have the right to do that")
|
||||
.colour((247, 76, 0))
|
||||
})
|
||||
})
|
||||
})
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn goulag(
|
||||
ctx: &Context,
|
||||
data: &Value,
|
||||
interaction: &Interaction,
|
||||
data: &ApplicationCommandInteractionData,
|
||||
guild_id: u64,
|
||||
author: &Member,
|
||||
id: &str,
|
||||
token: &str,
|
||||
) -> CommandResult {
|
||||
let ctx_data = ctx.data.read().await;
|
||||
let ctx_data = ctx_data
|
||||
|
@ -96,24 +90,16 @@ async fn goulag(
|
|||
|
||||
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 options = &data.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
|
||||
}
|
||||
})
|
||||
.find(|f| f.name == "user")
|
||||
.ok_or("Failed to get user")?;
|
||||
|
||||
let user_id = user
|
||||
.get("value")
|
||||
.value
|
||||
.as_ref()
|
||||
.and_then(|v| v.as_str())
|
||||
.and_then(|v| v.parse::<u64>().ok())
|
||||
.ok_or("Failed to get user id")?;
|
||||
|
@ -128,143 +114,83 @@ async fn goulag(
|
|||
|
||||
member.add_role(&ctx.http, mute_role).await?;
|
||||
|
||||
Response::new_with_embed(4, |e| {
|
||||
interaction
|
||||
.create_interaction_response(&ctx.http, |response| {
|
||||
response
|
||||
.kind(InteractionResponseType::ChannelMessageWithSource)
|
||||
.interaction_response_data(|message| {
|
||||
message.create_embed(|e| {
|
||||
embed_author(e, Some(author))
|
||||
.title("Mute")
|
||||
.description(format!("{} was muted", member.display_name()))
|
||||
.colour((247, 76, 0))
|
||||
})
|
||||
.send(token, id)
|
||||
})
|
||||
})
|
||||
.await?;
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
Response::new_with_embed(4, |e| {
|
||||
interaction
|
||||
.create_interaction_response(&ctx.http, |response| {
|
||||
response
|
||||
.kind(InteractionResponseType::ChannelMessageWithSource)
|
||||
.interaction_response_data(|message| {
|
||||
message.create_embed(|e| {
|
||||
embed_author(e, Some(author))
|
||||
.title("Error")
|
||||
.description("Unknown mute role")
|
||||
.colour((247, 76, 0))
|
||||
})
|
||||
.send(token, id)
|
||||
})
|
||||
})
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub(crate) struct ResponseData {
|
||||
tts: bool,
|
||||
content: Option<String>,
|
||||
embeds: Option<Vec<Value>>,
|
||||
flags: u64,
|
||||
}
|
||||
async fn message_interact(
|
||||
ctx: &Context,
|
||||
interaction: &Interaction,
|
||||
msg_interaction: &MessageComponent,
|
||||
) -> CommandResult {
|
||||
match msg_interaction.custom_id.as_str() {
|
||||
"gulag_button" => {
|
||||
let guild_id = interaction
|
||||
.guild_id
|
||||
.as_ref()
|
||||
.ok_or("Failed to get guild id")?;
|
||||
|
||||
#[allow(dead_code)]
|
||||
impl ResponseData {
|
||||
pub(crate) fn embed<F>(mut self, f: F) -> Self
|
||||
where
|
||||
F: FnOnce(&mut CreateEmbed) -> &mut CreateEmbed,
|
||||
let mut member = interaction.member.clone().ok_or("Failed to get user")?;
|
||||
let role = match ctx
|
||||
.cache
|
||||
.guild(guild_id)
|
||||
.await
|
||||
.unwrap()
|
||||
.role_by_name("mute")
|
||||
{
|
||||
let embeds = self.embeds.get_or_insert(Default::default());
|
||||
Some(role) => Ok(role.id),
|
||||
None => Err(tokio::io::Error::new(
|
||||
tokio::io::ErrorKind::Other,
|
||||
"Unkown role",
|
||||
)),
|
||||
}?;
|
||||
|
||||
embeds.push(Embed::fake(f));
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Get a reference to the response data's tts.
|
||||
pub(crate) fn tts(&self) -> &bool {
|
||||
&self.tts
|
||||
}
|
||||
|
||||
/// Get a reference to the response data's content.
|
||||
pub(crate) fn content(&self) -> &Option<String> {
|
||||
&self.content
|
||||
}
|
||||
|
||||
/// Get a reference to the response data's flags.
|
||||
pub(crate) fn flags(&self) -> &u64 {
|
||||
&self.flags
|
||||
}
|
||||
|
||||
/// Set the response data's content.
|
||||
pub(crate) fn set_content(mut self, content: Option<String>) -> Self {
|
||||
self.content = content;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the response data's tts.
|
||||
pub(crate) fn set_tts(mut self, tts: bool) -> Self {
|
||||
self.tts = tts;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the response data's flags.
|
||||
pub(crate) fn set_flags(mut self, flags: u64) -> Self {
|
||||
self.flags = flags;
|
||||
self
|
||||
}
|
||||
|
||||
/// Get a reference to the response data's embeds.
|
||||
pub(crate) fn embeds(&self) -> &Option<Vec<Value>> {
|
||||
&self.embeds
|
||||
}
|
||||
|
||||
/// Get a mutable reference to the response data's embeds.
|
||||
pub(crate) fn embeds_mut(&mut self) -> &mut Option<Vec<Value>> {
|
||||
&mut self.embeds
|
||||
}
|
||||
|
||||
/// Set the response data's embeds.
|
||||
pub(crate) fn set_embeds(mut self, embeds: Option<Vec<Value>>) -> Self {
|
||||
self.embeds = embeds;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ResponseData {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
tts: false,
|
||||
content: None,
|
||||
embeds: None,
|
||||
flags: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub(crate) struct Response {
|
||||
#[serde(rename(serialize = "type"))]
|
||||
response_type: u64,
|
||||
data: Option<ResponseData>,
|
||||
}
|
||||
|
||||
impl Response {
|
||||
pub(crate) fn new_with_embed<F>(response_type: u64, f: F) -> Self
|
||||
where
|
||||
F: FnOnce(&mut CreateEmbed) -> &mut CreateEmbed,
|
||||
{
|
||||
Response {
|
||||
response_type,
|
||||
data: Some(ResponseData::default().embed(f)),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn send(&self, token: &str, id: &str) -> CommandResult {
|
||||
reqwest::Client::new()
|
||||
.post(
|
||||
format!(
|
||||
"https://discord.com/api/v8/interactions/{}/{}/callback",
|
||||
id, token
|
||||
)
|
||||
.as_str(),
|
||||
)
|
||||
.json(self)
|
||||
.send()
|
||||
member.add_role(ctx, role).await?;
|
||||
interaction
|
||||
.create_interaction_response(&ctx.http, |response| {
|
||||
response
|
||||
.kind(InteractionResponseType::ChannelMessageWithSource)
|
||||
.interaction_response_data(|message| {
|
||||
message
|
||||
.content("<:cheh:780736245675982909>")
|
||||
.flags(InteractionApplicationCommandCallbackDataFlags::EPHEMERAL)
|
||||
})
|
||||
})
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -305,6 +305,7 @@ async fn play(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
|
|||
})
|
||||
.await?;
|
||||
return Ok(());
|
||||
/*
|
||||
msg.channel_id
|
||||
.send_message(&ctx.http, |m| {
|
||||
embed_response(
|
||||
|
@ -361,6 +362,7 @@ async fn play(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
|
|||
};
|
||||
|
||||
handler.enqueue_source(s);
|
||||
|
||||
}
|
||||
|
||||
let track = handler.queue().current();
|
||||
|
@ -385,6 +387,7 @@ async fn play(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
|
|||
.await?;
|
||||
}
|
||||
return Ok(());
|
||||
*/
|
||||
} else {
|
||||
match songbird::ytdl(&url).await {
|
||||
Ok(source) => source,
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
/*
|
||||
use serde::Deserialize;
|
||||
use tokio::{
|
||||
io::{Error, ErrorKind, Result},
|
||||
process::Command,
|
||||
};
|
||||
|
||||
|
||||
pub(crate) async fn get_list_of_urls(url: &str) -> Result<Vec<String>> {
|
||||
let output = Command::new("youtube-dl")
|
||||
.args(&["-j", "--flat-playlist", &url])
|
||||
|
@ -30,3 +33,5 @@ pub(crate) async fn get_list_of_urls(url: &str) -> Result<Vec<String>> {
|
|||
struct YtdlResponse {
|
||||
url: String,
|
||||
}
|
||||
|
||||
*/
|
||||
|
|
|
@ -8,6 +8,7 @@ pub(crate) struct Conf {
|
|||
#[derive(Debug, Deserialize)]
|
||||
pub(crate) struct Bot {
|
||||
pub(crate) token: String,
|
||||
pub(crate) application_id: u64,
|
||||
pub(crate) log_attachments: Option<bool>,
|
||||
pub(crate) invite_url: Option<String>,
|
||||
}
|
||||
|
|
|
@ -19,7 +19,6 @@ impl TypeMapKey for BulletsContainer {
|
|||
type Value = HashMap<u64, (u8, u8)>;
|
||||
}
|
||||
|
||||
|
||||
pub(crate) struct Uptime;
|
||||
impl TypeMapKey for Uptime {
|
||||
type Value = std::time::Instant;
|
||||
|
|
82
src/main.rs
82
src/main.rs
|
@ -8,7 +8,6 @@ use crate::{
|
|||
use async_trait::async_trait;
|
||||
use commands::interaction;
|
||||
use log::{debug, error, info};
|
||||
use serde_json::Value;
|
||||
use serenity::{
|
||||
framework::standard::{
|
||||
help_commands,
|
||||
|
@ -27,7 +26,7 @@ use std::{
|
|||
path::Path,
|
||||
sync::Arc,
|
||||
time::Duration,
|
||||
time::Instant
|
||||
time::Instant,
|
||||
};
|
||||
use tokio::{fs::File, io::AsyncWriteExt};
|
||||
|
||||
|
@ -44,7 +43,7 @@ mod data;
|
|||
mod presence;
|
||||
mod utils;
|
||||
|
||||
const MINIMUM_MENTIONS: usize = 10;
|
||||
const MINIMUM_MENTIONS: usize = 20;
|
||||
|
||||
const PREFIX: &str = "?";
|
||||
static mut LOG_ATTACHMENTS: bool = false;
|
||||
|
@ -140,6 +139,7 @@ async fn main() -> IoResult<()> {
|
|||
}
|
||||
|
||||
let mut client = Client::builder(&token)
|
||||
.application_id(conf.bot.application_id)
|
||||
.event_handler(Messages {})
|
||||
.framework(framework);
|
||||
|
||||
|
@ -224,41 +224,85 @@ impl EventHandler for Messages {
|
|||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
async fn interaction_create(&self, ctx: Context, interaction: Interaction) {
|
||||
if let Err(e) = interaction::handle_interaction(&ctx, &interaction).await {
|
||||
error!("While handling interaction : {}", e);
|
||||
}
|
||||
}
|
||||
_ => debug!("Unknown event : {}, {:?}", name, raw),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async fn log_mentions(ctx: Context, new_message: &Message) -> CommandResult {
|
||||
if !new_message.mention_everyone
|
||||
&& new_message.mention_roles.is_empty()
|
||||
&& new_message.mentions.is_empty()
|
||||
{
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let data = ctx.data.read().await;
|
||||
|
||||
let guilds_options = data
|
||||
.get::<GuildOptionsKey>()
|
||||
.expect("Expected NonKickGuildsContainer in TypeMap.");
|
||||
|
||||
if new_message.mention_everyone {
|
||||
let mute = if new_message.mention_everyone {
|
||||
true
|
||||
} else {
|
||||
let mut user_mentioned: HashSet<u64> = HashSet::with_capacity(MINIMUM_MENTIONS); // TODO IN GUILD OPTIONS
|
||||
|
||||
let guild = new_message.guild(&ctx).await.ok_or("Failed to get guild")?;
|
||||
|
||||
let mut iter_users = new_message.mentions.iter();
|
||||
while user_mentioned.len() < MINIMUM_MENTIONS {
|
||||
if let Some(u) = iter_users.next() {
|
||||
user_mentioned.insert(u.id.0);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let mut iter_roles = new_message.mention_roles.iter();
|
||||
|
||||
let mut continue_getting_members = true;
|
||||
let mut after = None;
|
||||
|
||||
while continue_getting_members {
|
||||
let members = guild.members(&ctx, Some(1000), after).await?;
|
||||
|
||||
while user_mentioned.len() < MINIMUM_MENTIONS {
|
||||
if let Some(r) = iter_roles.next() {
|
||||
for member in members.iter() {
|
||||
if let Some(roles) = member.roles(&ctx).await {
|
||||
if roles.iter().any(|role| role.id.0 == r.0) {
|
||||
log::debug!("{:?}", member);
|
||||
user_mentioned.insert(member.user.id.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if members.len() == 1000 {
|
||||
continue_getting_members = true;
|
||||
after = Some(members.last().unwrap().user.id);
|
||||
} else {
|
||||
continue_getting_members = false;
|
||||
}
|
||||
}
|
||||
|
||||
user_mentioned.len() >= MINIMUM_MENTIONS
|
||||
};
|
||||
|
||||
if mute {
|
||||
if let Some(guild_id) = new_message.guild_id {
|
||||
if let Some(options) = guilds_options.get(&guild_id) {
|
||||
if let Some(role_id) = options.mute_id {
|
||||
let mut member = new_message.member(&ctx).await?;
|
||||
member.add_role(&ctx.http, role_id).await?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if new_message.mention_everyone
|
||||
|| (new_message.mention_roles.len() + new_message.mentions.len()) > MINIMUM_MENTIONS
|
||||
{
|
||||
if let Some(guild_id) = new_message.guild_id {
|
||||
if let Some(options) = guilds_options.get(&guild_id) {
|
||||
if let Some(channel_id) = options.mention_log_channel {
|
||||
channel_id
|
||||
.send_message(&ctx.http, |m| {
|
||||
|
|
Loading…
Reference in New Issue