You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
crap_bot_discord/src/handler.rs

437 lines
14 KiB

use std::collections::BTreeMap;
use serenity::{
async_trait,
client::{Context, EventHandler},
model::{
channel::{ChannelType, Message, Reaction, ReactionType},
id::{ChannelId, GuildId, MessageId, RoleId},
prelude::Ready,
Permissions,
},
};
use tokio::task;
use crate::{
db,
error::{CrapBotError, Result},
helpers::{check_for_admin_else_reply, reply_message, user_is_admin},
};
pub struct Handler {}
impl Handler {
pub fn new() -> Handler {
Handler {}
}
}
#[async_trait]
impl EventHandler for Handler {
async fn message(&self, ctx: Context, msg: Message) {
// most inexpensive check i could imagine
if !msg.content.is_empty() && msg.content.as_bytes()[0] == b'!' {
println!(
"handling command '{}' from user '{}'",
msg.content, msg.author.name
);
let res = match msg.content.split(' ').next().unwrap() {
"!create" => create_channels(&ctx, &msg).await,
"!cleanup" => delete_channels(&ctx, &msg).await,
"!delete" => delete_channels(&ctx, &msg).await,
"!del" => delete_channels(&ctx, &msg).await,
"!info" => get_info(&ctx, &msg).await,
"!add_admin" => add_admin(&ctx, &msg).await,
"!del_admin" => delete_admin(&ctx, &msg).await,
"!add_role" => add_role(&ctx, &msg).await,
"!del_role" => delete_role(&ctx, &msg).await,
"!say" => say(&ctx, &msg).await,
"!get_message" => get_message(&ctx, &msg).await,
_ => Ok(()),
};
if res.is_err() {
match res.unwrap_err() {
CrapBotError::InsufficientPermissions => todo!(),
CrapBotError::NoEntry => todo!(),
CrapBotError::ErrorWithReply(m) => {
eprintln!("{}", m);
msg.reply(&ctx, m).await;
}
CrapBotError::ErrorWithMessage(m) => eprintln!("{}", m),
CrapBotError::SQLiteError(_) => todo!(),
CrapBotError::DiscordError(_) => todo!(),
}
}
}
}
async fn reaction_add(&self, ctx: Context, reaction: Reaction) {
manage_user_role(ctx, reaction, true).await;
}
async fn reaction_remove(&self, ctx: Context, reaction: Reaction) {
manage_user_role(ctx, reaction, false).await;
}
async fn ready(&self, ctx: Context, ready: Ready) {
println!("{} is connected!", ready.user.name);
let permissions = Permissions::READ_MESSAGES
| Permissions::SEND_MESSAGES
| Permissions::MANAGE_CHANNELS
| Permissions::MANAGE_ROLES
| Permissions::MANAGE_MESSAGES
| Permissions::ADD_REACTIONS;
println!(
"to invite this bot instance to your server use the following url:\n{}",
ready.user.invite_url(ctx, permissions).await.unwrap()
);
}
}
#[inline]
async fn manage_user_role(ctx: Context, reaction: Reaction, add: bool) {
let user_name = match reaction.user_id {
Some(user) => user.to_user(&ctx).await.unwrap().name,
None => "None".into(),
};
if add {
println!(
"handling reaction addition of user '{:?}': '{}'",
user_name, reaction.emoji
);
} else {
println!(
"handling reaction removal of user '{:?}': '{}'",
user_name, reaction.emoji
);
}
if reaction.user(&ctx).await.unwrap().bot {
println!("user is a bot");
return;
}
if let Some(guild) = reaction.guild_id {
if let Some(user) = reaction.user_id {
let emoji = match reaction.emoji {
ReactionType::Custom { id, .. } => id.to_string(), // FIXME add to database
ReactionType::Unicode(u) => u,
_ => {
eprintln!("unknown emoji type encountered: {:?}", reaction.emoji);
return;
}
};
let message_id = reaction.message_id;
let role =
match task::block_in_place(|| db::get_role_for_emoji(guild, message_id, emoji)) {
Ok(role) => role,
Err(why) => {
eprintln!("failed getting role: {}", why);
return;
}
};
let mut member = match guild.member(&ctx, user).await {
Ok(m) => m,
Err(why) => {
eprintln!("getting member failed: {:?}", why);
return;
}
};
let res = if add {
member.add_role(&ctx, role).await
} else {
member.remove_role(&ctx, role).await
};
if let Err(why) = res {
eprintln!("modifying member failed: {:?}", why);
}
} else {
eprintln!("no user id");
}
} else {
eprintln!("no guild id");
}
}
async fn create_channels(ctx: &Context, msg: &Message) -> Result<()> {
async fn cleanup_channels(channels: Vec<ChannelId>, ctx: &Context) {
for channel in channels {
if let Err(why) = channel.delete(ctx).await {
eprintln!("deleting channel failed: {:?}", why);
}
}
}
if let Some(guild) = msg.guild_id {
if !user_is_admin(ctx, msg).await {
let created_channels =
task::block_in_place(|| db::get_channels_for_user(guild, msg.author.id))?;
if !created_channels.is_empty() {
return Err(CrapBotError::ErrorWithReply(
"You already created a channel!".into(),
));
}
}
let name = msg.content.strip_prefix("!create ").unwrap();
let mut new_channels = Vec::with_capacity(3);
let category = guild
.create_channel(ctx, |c| {
c.name(&name).kind(ChannelType::Category).position(9001)
})
.await?;
new_channels.push(category.id);
let text_channel = match guild
.create_channel(ctx, |c| {
c.name(&name).kind(ChannelType::Text).category(category.id)
})
.await
{
Ok(c) => c,
Err(why) => {
cleanup_channels(new_channels, ctx).await;
return Err(why.into());
}
};
new_channels.push(text_channel.id);
new_channels.push(
match guild
.create_channel(ctx, |c| {
c.name(&name).kind(ChannelType::Voice).category(category.id)
})
.await
{
Ok(c) => c,
Err(why) => {
cleanup_channels(new_channels, ctx).await;
return Err(why.into());
}
}
.id,
);
task::block_in_place(|| {
db::add_created_channels(guild, text_channel.id, msg.author.id, new_channels)
})?;
} else {
println!("not in a guild");
}
Ok(())
}
async fn delete_channels(ctx: &Context, msg: &Message) -> Result<()> {
if let Some(guild) = msg.guild_id {
let mut user = None;
if !user_is_admin(ctx, msg).await {
user = Some(msg.author.id);
}
let channels =
task::block_in_place(|| db::get_channels_to_delete(guild, msg.channel_id, user))?;
for channel in channels {
if let Err(why) = channel.delete(ctx).await {
println!("Error deleting channel: {:?}", why);
}
}
task::block_in_place(|| db::delete_created_channels(guild, msg.channel_id, user))?;
} else {
println!("not in a guild");
}
Ok(())
}
#[derive(Debug)]
struct Info(
GuildId,
ChannelId,
Option<MessageId>,
BTreeMap<String, RoleId>,
);
async fn get_info(ctx: &Context, msg: &Message) -> Result<()> {
if user_is_admin(ctx, msg).await {
let role_ids = msg
.guild(ctx)
.await
.unwrap()
.roles
.iter()
.map(|(id, r)| (r.name.clone(), *id))
.collect();
let message_id = match &msg.referenced_message {
Some(m) => Some(m.id),
None => None,
};
reply_message(
ctx,
msg,
format!(
"{:#?}",
Info(msg.guild_id.unwrap(), msg.channel_id, message_id, role_ids)
),
)
.await;
}
Ok(())
}
async fn add_admin(ctx: &Context, msg: &Message) -> Result<()> {
if let Some(guild) = msg.guild_id {
if check_for_admin_else_reply(ctx, msg).await? {
if msg.mention_roles.len() == 1 {
task::block_in_place(|| db::add_admin(guild, msg.mention_roles[0]))?;
} else if msg.mention_roles.len() > 1 {
println!("too much roles");
reply_message(ctx, msg, "too much roles").await
} else {
println!("no role");
reply_message(ctx, msg, "mention a role").await
}
}
} else {
println!("not in a guild");
}
Ok(())
}
async fn delete_admin(ctx: &Context, msg: &Message) -> Result<()> {
if let Some(guild) = msg.guild_id {
if check_for_admin_else_reply(ctx, msg).await? {
if msg.mention_roles.len() == 1 {
task::block_in_place(|| db::delete_admin(guild, msg.mention_roles[0]))?;
} else if msg.mention_roles.len() > 1 {
println!("too much roles");
reply_message(ctx, msg, "too much roles").await
} else {
println!("no role");
reply_message(ctx, msg, "mention a role").await
}
}
} else {
println!("not in a guild");
}
Ok(())
}
async fn add_role(ctx: &Context, msg: &Message) -> Result<()> {
if let Some(guild) = msg.guild_id {
if user_is_admin(ctx, msg).await {
if let Some(referenced_message) = &msg.referenced_message {
if msg.mention_roles.len() == 1 {
let reaction = msg.content.split(' ').nth(1).unwrap().to_string();
referenced_message
.react(ctx, ReactionType::Unicode(reaction.clone()))
.await?;
task::block_in_place(|| {
db::add_emoji_role(
guild,
referenced_message.id,
reaction,
msg.mention_roles[0],
)
})?;
msg.delete(ctx).await?;
} else if msg.mention_roles.len() > 1 {
println!("too much roles");
reply_message(ctx, msg, "too much roles").await
} else {
println!("no role");
reply_message(ctx, msg, "mention a role").await
}
} else {
println!("not a reply");
reply_message(ctx, msg, "please reply to a message").await
}
} else {
println!("user is not an admin");
}
} else {
println!("not in a guild");
}
Ok(())
}
async fn delete_role(ctx: &Context, msg: &Message) -> Result<()> {
if let Some(guild) = msg.guild_id {
if user_is_admin(ctx, msg).await {
if let Some(referenced_message) = msg.clone().referenced_message {
let reaction = msg.content.split(' ').nth(1).unwrap().to_string();
task::block_in_place(|| {
db::delete_emoji_role(guild, referenced_message.id, reaction)
})?;
msg.delete(ctx).await?;
} else {
println!("not a reply");
reply_message(ctx, msg, "please reply to a message").await
}
} else {
println!("user is not an admin");
}
} else {
println!("not in a guild");
}
Ok(())
}
async fn say(ctx: &Context, msg: &Message) -> Result<()> {
if let Some(_) = msg.guild_id {
if check_for_admin_else_reply(ctx, msg).await? {
let new_message = msg.content.trim_start_matches("!say ");
match msg.clone().referenced_message {
Some(mut r_msg) => {
r_msg.edit(ctx, |m| m.content(new_message)).await?;
}
None => {
msg.channel_id.say(ctx, new_message).await?;
}
}
msg.delete(ctx).await?;
}
} else {
println!("not in a guild");
}
Ok(())
}
async fn get_message(ctx: &Context, msg: &Message) -> Result<()> {
if let Some(_) = msg.guild_id {
if user_is_admin(ctx, msg).await {
match msg.clone().referenced_message {
Some(r_msg) => {
msg.channel_id
.say(ctx, format!("```\n{}\n```", r_msg.content))
.await?;
}
None => {
println!("not a reply");
msg.reply(ctx, "you need to reply to a message").await?;
}
}
msg.delete(ctx).await?;
} else {
println!("user is not an admin");
msg.react(ctx, ReactionType::Unicode("❌".into())).await;
}
} else {
println!("not in a guild");
}
Ok(())
}