diff --git a/jarvis/client/events/components.py b/jarvis/client/events/components.py index 8d33d3b..1eab154 100644 --- a/jarvis/client/events/components.py +++ b/jarvis/client/events/components.py @@ -1,6 +1,6 @@ """JARVIS component event mixin.""" from jarvis_core.db import q -from jarvis_core.db.models import Action, Modlog, Note, Reminder, Star +from jarvis_core.db.models import Action, Modlog, Note, Phishlist, Reminder, Star from naff import listen from naff.api.events.internal import Button from naff.models.discord.embed import EmbedField @@ -136,9 +136,40 @@ class ComponentEventMixin: await context.send("Reminder copied!", ephemeral=True) + async def _handle_phishlist_button(self, event: Button) -> None: + context = event.context + if not context.custom_id.startswith("pl|"): + return + + if not context.deferred and not context.responded: + await context.defer(ephemeral=True) + + _, valid, id_ = context.custom_id.split("|") + valid = valid == "valid" + pl = await Phishlist.find_one(q(_id=id_)) + if not pl: + self.logger.warn(f"Phishlist {id_} does not exist!") + return + + pl.valid = valid + pl.confirmed = True + + await pl.commit() + + for row in context.message.components: + for component in row.components: + component.disabled = True + + embed = context.message.embeds[0] + embed.add_field(name="Valid", value="Yes" if valid else "No") + + await context.message.edit(components=context.message.components, embeds=embed) + await context.send("Confirmed! Thank you for confirming this URL.") + @listen() async def on_button(self, event: Button) -> None: """Process button events.""" await self._handle_modcase_button(event) await self._handle_delete_button(event) await self._handle_copy_button(event) + await self._handle_phishlist_button(event) diff --git a/jarvis/client/events/message.py b/jarvis/client/events/message.py index 289f054..f3a760a 100644 --- a/jarvis/client/events/message.py +++ b/jarvis/client/events/message.py @@ -7,8 +7,8 @@ from jarvis_core.db import q from jarvis_core.db.models import ( Autopurge, Autoreact, - Filter, Mute, + Phishlist, Roleping, Setting, Warning, @@ -18,15 +18,16 @@ from naff import listen from naff.api.events.discord import MessageCreate, MessageDelete, MessageUpdate from naff.client.utils.misc_utils import find_all from naff.models.discord.channel import DMChannel, GuildText +from naff.models.discord.components import ActionRow, Button from naff.models.discord.embed import EmbedField -from naff.models.discord.enums import Permissions +from naff.models.discord.enums import ButtonStyles, Permissions from naff.models.discord.message import Message from naff.models.discord.user import Member from jarvis.branding import get_command_color +from jarvis.embeds.admin import warning_embed from jarvis.tracking import malicious_tracker, warnings_tracker from jarvis.utils import build_embed -from jarvis.utils.embeds import warning_embed class MessageEventMixin: @@ -83,6 +84,10 @@ class MessageEventMixin: ] if (m := match.group(1)) not in allowed and setting.value: self.logger.debug(f"Removing non-allowed invite `{m}` from {message.guild.id}") + try: + await message.delete() + except Exception: + self.logger.debug("Message deleted before action taken") expires_at = datetime.now(tz=timezone.utc) + timedelta(hours=24) await Warning( @@ -96,49 +101,12 @@ class MessageEventMixin: ).commit() tracker = warnings_tracker.labels(guild_id=message.guild.id, guild_name=message.guild.name) tracker.inc() - embed = warning_embed(message.author, "Sent an invite link") + embed = warning_embed(message.author, "Sent an invite link", self.user) try: - await message.reply(embeds=embed) + await message.channel.send(embeds=embed) except Exception: self.logger.warn("Failed to send warning embed") - try: - await message.delete() - except Exception: - self.logger.debug("Message deleted before action taken") - - async def filters(self, message: Message) -> None: - """Handle filter evennts.""" - filters = await Filter.find(q(guild=message.guild.id)).to_list(None) - for item in filters: - for f in item.filters: - if re.search(f, message.content, re.IGNORECASE): - expires_at = datetime.now(tz=timezone.utc) + timedelta(hours=24) - await Warning( - active=True, - admin=self.user.id, - duration=24, - expires_at=expires_at, - guild=message.guild.id, - reason="Sent a message with a filtered word", - user=message.author.id, - ).commit() - tracker = warnings_tracker.labels( - guild_id=message.guild.id, guild_name=message.guild.name - ) - tracker.inc() - embed = warning_embed(message.author, "Sent a message with a filtered word") - try: - await message.reply(embeds=embed) - except Exception: - self.logger.warn("Failed to send warning embed") - - try: - await message.delete() - except Exception: - self.logger.debug("Message deleted before action taken") - return - async def massmention(self, message: Message) -> None: """Handle massmention events.""" massmention = await Setting.find_one( @@ -168,9 +136,9 @@ class MessageEventMixin: ).commit() tracker = warnings_tracker.labels(guild_id=message.guild.id, guild_name=message.guild.name) tracker.inc() - embed = warning_embed(message.author, "Mass Mention") + embed = warning_embed(message.author, "Mass Mention", self.user) try: - await message.reply(embeds=embed) + await message.channel.send(embeds=embed) except Exception: self.logger.warn("Failed to send warning embed") @@ -232,9 +200,11 @@ class MessageEventMixin: ).commit() tracker = warnings_tracker.labels(guild_id=message.guild.id, guild_name=message.guild.name) tracker.inc() - embed = warning_embed(message.author, "Pinged a blocked role/user with a blocked role") + embed = warning_embed( + message.author, "Pinged a blocked role/user with a blocked role", self.user + ) try: - await message.reply(embeds=embed) + await message.channel.send(embeds=embed) except Exception: self.logger.warn("Failed to send warning embed") @@ -242,6 +212,9 @@ class MessageEventMixin: """Check if the message contains any known phishing domains.""" for match in url.finditer(message.content): if (m := match.group("domain")) in self.phishing_domains: + pl = await Phishlist.find_one(q(url=m)) + if pl and pl.confirmed and not pl.valid: + return False self.logger.debug( f"Phishing url `{m}` detected in {message.guild.id}/{message.channel.id}/{message.id}" ) @@ -257,9 +230,9 @@ class MessageEventMixin: ).commit() tracker = warnings_tracker.labels(guild_id=message.guild.id, guild_name=message.guild.name) tracker.inc() - embed = warning_embed(message.author, "Phishing URL") + embed = warning_embed(message.author, "Phishing URL", self.user) try: - await message.reply(embeds=embed) + await message.channel.send(embeds=embed) except Exception: self.logger.warn("Failed to send warning embed") try: @@ -268,12 +241,41 @@ class MessageEventMixin: self.logger.warn("Failed to delete malicious message") tracker = malicious_tracker.labels(guild_id=message.guild.id, guild_name=message.guild.name) tracker.inc() + + if not pl or not pl.confirmed: + if not pl: + pl = Phishlist(url=m) + await pl.commit() + + embed = build_embed( + title="Phishing URL detected", + description="Please confirm that this is valid", + fields=[EmbedField(name="URL", value=m)], + ) + + valid_button = Button( + style=ButtonStyles.GREEN, emoji="✔️", custom_id=f"pl|valid|{pl.id}" + ) + invalid_button = Button( + style=ButtonStyles.RED, emoji="✖️", custom_id=f"pl|invalid|{pl.id}" + ) + + channel = await self.fetch_channel(1026918337554423868) + + components = [ActionRow(invalid_button, valid_button)] + + await channel.send(embeds=embed, components=components) + return True return False async def malicious_url(self, message: Message) -> None: """Check if the message contains any known phishing domains.""" for match in url.finditer(message.content): + m = match.group("domain") + pl = await Phishlist.find_one(q(url=m)) + if pl and pl.confirmed and not pl.valid: + return False async with ClientSession() as session: resp = await session.post( "https://anti-fish.bitflow.dev/check", @@ -303,9 +305,9 @@ class MessageEventMixin: tracker = warnings_tracker.labels(guild_id=message.guild.id, guild_name=message.guild.name) tracker.inc() reasons = ", ".join(f"{m['source']}: {m['type']}" for m in data["matches"]) - embed = warning_embed(message.author, reasons) + embed = warning_embed(message.author, reasons, self.user) try: - await message.reply(embeds=embed) + await message.channel.send(embeds=embed) except Exception: self.logger.warn("Failed to send warning embed") try: @@ -314,6 +316,31 @@ class MessageEventMixin: self.logger.warn("Failed to delete malicious message") tracker = malicious_tracker.labels(guild_id=message.guild.id, guild_name=message.guild.name) tracker.inc() + + if not pl or not pl.confirmed: + if not pl: + pl = Phishlist(url=m) + await pl.commit() + + embed = build_embed( + title="Malicious URL detected", + description="Please confirm that this is valid", + fields=[EmbedField(name="URL", value=m)], + ) + + valid_button = Button( + style=ButtonStyles.GREEN, emoji="✔️", custom_id=f"pl|valid|{pl.id}" + ) + invalid_button = Button( + style=ButtonStyles.RED, emoji="✖️", custom_id=f"pl|invalid|{pl.id}" + ) + + channel = await self.fetch_channel(1026918337554423868) + + components = [ActionRow(invalid_button, valid_button)] + + await channel.send(embeds=embed, components=components) + return True return False @@ -362,7 +389,6 @@ class MessageEventMixin: malicious = await self.malicious_url(message) if phish or malicious: await self.timeout_user(message.author, message.channel) - await self.filters(message) @listen() async def on_message_edit(self, event: MessageUpdate) -> None: @@ -418,7 +444,6 @@ class MessageEventMixin: malicious = await self.malicious_url(after) if phish or malicious: await self.timeout_user(after.author, after.channel) - await self.filters(after) @listen() async def on_message_delete(self, event: MessageDelete) -> None: