Skip to content

Commit

Permalink
Merge pull request #23 from CrawKatt/antispam
Browse files Browse the repository at this point in the history
✨ feat: Adding a new anti-spam system and refactoring
  • Loading branch information
SergioRibera authored Apr 27, 2024
2 parents 65fe6d6 + 5c72ce8 commit c7b3607
Show file tree
Hide file tree
Showing 6 changed files with 215 additions and 10 deletions.
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ symphonia = { version = "0.5.3", default-features = false, features = [
"wav",
] }
axum = "0.7.5"
regex = "1.10.2"
once_cell = "1.18.0"

gen_welcome = { version = "0.1.0", path = "crates/gen_welcome" }

Expand Down
17 changes: 17 additions & 0 deletions src/config/slash_command_loader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use serenity::{
model::prelude::{GuildId, Interaction, Member, Ready},
prelude::{Context, EventHandler},
};
use serenity::all::Message;
use tracing::{error, log::info};

use crate::slash_commands::ping;
Expand All @@ -19,6 +20,7 @@ use slash_commands::id::run as id_run;
use slash_commands::invite::run as invite_run;
use slash_commands::ping::run as ping_run;
use slash_commands::sugerencia;
use crate::events::anti_spam::{extract_link, spam_checker};

pub struct Handler {
guild_id: u64,
Expand All @@ -36,6 +38,21 @@ impl EventHandler for Handler {
guild_member_addition(&ctx, &member.guild_id, &member).await;
}

async fn message(&self, ctx: Context, new_message: Message) {
if extract_link(&new_message.content).is_some() {
let message_content = Arc::new(new_message.content.to_string());
spam_checker(
message_content,
new_message.channel_id,
&ctx,
604800,
&new_message,
new_message.guild_id.unwrap(),
).await.unwrap_or_default();
}

}

async fn interaction_create(&self, ctx: Context, interaction: Interaction) {
if let Interaction::Command(command) = interaction {
// info!("Received command interaction: {:#?}", command);
Expand Down
190 changes: 190 additions & 0 deletions src/events/anti_spam.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
use std::sync::Arc;
use regex::Regex;
use tokio::sync::Mutex;
use serenity::all::{Channel, ChannelId, Context, CreateEmbed, CreateEmbedAuthor, CreateMessage, GetMessages, GuildId, Member, Message, Timestamp, UserId};
use std::time::Instant;
use once_cell::sync::Lazy;
use serenity::all::standard::CommandResult;

#[derive(Debug)]
pub struct MessageTracker {
author_id: UserId,
message_content: Arc<String>,
channel_ids: Vec<ChannelId>,
last_message_time: Instant,
}

impl MessageTracker {
pub fn builder() -> MessageTrackerBuilder {
MessageTrackerBuilder::default()
}
}

#[derive(Default)]
pub struct MessageTrackerBuilder {
author_id: Option<UserId>,
message_content: Option<Arc<String>>,
channel_ids: Option<Vec<ChannelId>>,
}

impl MessageTrackerBuilder {
pub fn author_id(mut self, author_id: UserId) -> Self {
self.author_id = Some(author_id);
self
}

pub fn message_content(mut self, message_content: Arc<String>) -> Self {
self.message_content = Some(message_content);
self
}

pub fn channel_ids(mut self, channel_ids: Vec<ChannelId>) -> Self {
self.channel_ids = Some(channel_ids);
self
}

pub fn build(self) -> Result<MessageTracker, &'static str> {
Ok(MessageTracker {
author_id: self.author_id.ok_or("Author id is missing")?,
message_content: self.message_content.ok_or("Message content is missing")?,
channel_ids: self.channel_ids.ok_or("Channel ids are missing")?,
last_message_time: Instant::now(),
})
}
}

static MESSAGE_TRACKER: Lazy<Mutex<Vec<MessageTracker>>> = Lazy::new(|| {
Mutex::new(Vec::new())
});

pub fn extract_link(text: &str) -> Option<String> {
Regex::new(r"(https?://\S+)").map_or(None, |url_re| url_re.find(text).map(|m| m.as_str().to_string()))
}

pub async fn spam_checker(
message_content: Arc<String>,
channel_id: ChannelId,
ctx: &Context,
time: i64,
new_message: &Message,
guild_id: GuildId
) -> CommandResult {
let author_id = new_message.author.id;
let mut member = guild_id.member(&ctx.http, new_message.author.id).await?;
let mut message_tracker = MESSAGE_TRACKER.lock().await;

if let Some(last_message) = message_tracker
.iter()
.last()
{
if last_message.author_id == author_id && last_message.message_content != message_content {
message_tracker.clear();
}
}

let message = if let Some(message) = message_tracker
.iter_mut()
.find(|m| m.author_id == author_id && m.message_content == message_content)
{
// Inicializa el tiempo del último mensaje
message.last_message_time = Instant::now();

// Si el mensaje existe y el canal no está en la lista de canales, añade el canal a la lista de canales
if message.channel_ids.contains(&channel_id) {
// Si el mensaje se repite en el mismo canal, borra el vector
// Debug: println!("Message repeated in the same channel, clearing the vector");
message_tracker.clear();

return Ok(());
}
message.channel_ids.push(channel_id);

message
} else {
// Si el mensaje no existe, crea un nuevo rastreador de mensajes y añádelo a la lista
let message = MessageTracker::builder()
.author_id(author_id)
.message_content(message_content.clone())
.channel_ids(vec![channel_id])
.build()?;

message_tracker.push(message);
message_tracker.last_mut().ok_or("Failed to get the last message tracker")?
};

if message.channel_ids.len() >= 3 {
apply_timeout(&mut member, ctx, time, new_message).await?;
delete_spam_messages(message, ctx, author_id, message_content).await?;

// Limpia completamente el rastreador de mensajes para reiniciar el rastreo de mensajes
message_tracker.retain(|m| m.author_id != author_id);
}
// Debug: println!("Tracker: {message_tracker:#?}");

drop(message_tracker);

Ok(())
}

async fn delete_spam_messages(
message: &MessageTracker,
ctx: &Context,
author_id: UserId,
message_content: Arc<String>,
) -> CommandResult {
// Borra cada mensaje individualmente
for channel_id in &message.channel_ids {
let channel = channel_id.to_channel(ctx).await?;
let Channel::Guild(channel) = channel else {
return Ok(())
};

let messages = channel.messages(&ctx.http, GetMessages::new()).await?;
for message in messages {
if message.author.id == author_id && &*message.content == &*message_content {
message.delete(&ctx.http).await?;
}
}
}

Ok(())
}

async fn create_embed(
ctx: &Context,
guild_id: GuildId,
author_id: UserId,
message_content: &str,
) -> CommandResult {
let log_channel = ChannelId::new(1230718736206532628);
let author_user = author_id.to_user(&ctx.http).await?;
let author_member = guild_id.member(&ctx.http, author_id).await?;
let username = author_member.distinct();
let embed = CreateEmbed::default()
.title("Spam detectado")
.author(CreateEmbedAuthor::new(username)
.icon_url(author_user.face()))
.description(format!("El usuario <@{author_id}> Es sospechoso de enviar spam en el servidor.\nMensaje: {message_content}"))
.color(0x00ff_0000);
let builder = CreateMessage::default().embed(embed);

log_channel.send_message(&ctx.http, builder).await?;

Ok(())
}

/// Silencia al autor del mensaje y elimina el mensaje
pub async fn apply_timeout(
member: &mut Member,
ctx: &Context,
time_out_timer: i64,
message: &Message,
) -> CommandResult {

let time = Timestamp::now().unix_timestamp() + time_out_timer;
let time = Timestamp::from_unix_timestamp(time)?;
member.disable_communication_until_datetime(&ctx.http, time).await?;
message.delete(&ctx.http).await?;

Ok(())
}
13 changes: 3 additions & 10 deletions src/events/join.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,14 +42,11 @@ async fn _guild_member_addition(ctx: &Context, guild_id: &GuildId, member: &Memb

let join_msg_replaced = join_msg
.replace("<mention>", &member.user.mention().to_string())
.replace("<username>", &member.user.name)
.replace("<username>", &member.distinct())
.replace("<server>", &guild_id.name(ctx).unwrap_or_else(|| "".into()));

// Download the user's avatar and create a welcome image
let avatar_url = member
.user
.avatar_url()
.unwrap_or_else(|| member.user.default_avatar_url());
let avatar_url = member.face();
let response = reqwest::get(avatar_url).await?;
let avatar = response.bytes().await?;

Expand All @@ -58,11 +55,7 @@ async fn _guild_member_addition(ctx: &Context, guild_id: &GuildId, member: &Memb
gen_welcome::generate(
"./static/welcome_background.png",
&avatar,
&member
.user
.global_name
.clone()
.unwrap_or(member.user.name.clone()),
&member.distinct(),
guild_id
.to_guild_cached(ctx)
.map(|g| g.member_count as usize),
Expand Down
1 change: 1 addition & 0 deletions src/events/mod.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
pub mod join;
pub mod daily_challenge;
pub mod anti_spam;
2 changes: 2 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#![feature(lazy_cell)]

use axum::{Router, ServiceExt};
use events::daily_challenge::run_daily_challenge;
use serenity::http::Http;
Expand Down

0 comments on commit c7b3607

Please sign in to comment.