Add integrations + better mentions handling + big red button

This commit is contained in:
oupson 2021-06-20 12:21:21 +02:00
parent d1edef3623
commit 644bd66ec3
8 changed files with 246 additions and 246 deletions

View File

@ -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"

View File

@ -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,19 +355,22 @@ 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| {
m.embed(|e| {
e.field(
"Uptime",
if hours > 0 {
format!("{}h {:02}min {:02}s", hours, min, seconds)
} else {
format!("{}min {:02}s", min, seconds)
},
false,
).colour((247, 76, 0))
msg.channel_id
.send_message(&ctx.http, |m| {
m.embed(|e| {
e.field(
"Uptime",
if hours > 0 {
format!("{}h {:02}min {:02}s", hours, min, seconds)
} else {
format!("{}min {:02}s", min, seconds)
},
false,
)
.colour((247, 76, 0))
})
})
}).await?;
.await?;
Ok(())
}

View File

@ -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| {
embed_author(e, Some(author))
.title("Mute")
.description(format!("{} was muted", member.display_name()))
.colour((247, 76, 0))
})
.send(token, id)
.await?;
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))
})
})
})
.await?;
return Ok(());
}
}
Response::new_with_embed(4, |e| {
embed_author(e, Some(author))
.title("Error")
.description("Unknown mute role")
.colour((247, 76, 0))
})
.send(token, id)
.await?;
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))
})
})
})
.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 embeds = self.embeds.get_or_insert(Default::default());
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")
{
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,
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?;
}
_ => {}
}
}
#[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()
.await?;
Ok(())
}
Ok(())
}

View File

@ -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,

View File

@ -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,
}
*/

View File

@ -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>,
}

View File

@ -19,8 +19,7 @@ impl TypeMapKey for BulletsContainer {
type Value = HashMap<u64, (u8, u8)>;
}
pub(crate) struct Uptime;
impl TypeMapKey for Uptime {
type Value = std::time::Instant;
}
}

View File

@ -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 {
error!("While handling interaction : {}", e);
}
}
_ => debug!("Unknown event : {}, {:?}", name, raw),
};
async fn interaction_create(&self, ctx: Context, interaction: Interaction) {
if let Err(e) = interaction::handle_interaction(&ctx, &interaction).await {
error!("While handling interaction : {}", e);
}
}
}
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| {