Add phishing validation and Phishlist
This commit is contained in:
parent
b7e0381b8a
commit
1a73d6bbf3
2 changed files with 109 additions and 53 deletions
|
@ -1,6 +1,6 @@
|
||||||
"""JARVIS component event mixin."""
|
"""JARVIS component event mixin."""
|
||||||
from jarvis_core.db import q
|
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 import listen
|
||||||
from naff.api.events.internal import Button
|
from naff.api.events.internal import Button
|
||||||
from naff.models.discord.embed import EmbedField
|
from naff.models.discord.embed import EmbedField
|
||||||
|
@ -136,9 +136,40 @@ class ComponentEventMixin:
|
||||||
|
|
||||||
await context.send("Reminder copied!", ephemeral=True)
|
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()
|
@listen()
|
||||||
async def on_button(self, event: Button) -> None:
|
async def on_button(self, event: Button) -> None:
|
||||||
"""Process button events."""
|
"""Process button events."""
|
||||||
await self._handle_modcase_button(event)
|
await self._handle_modcase_button(event)
|
||||||
await self._handle_delete_button(event)
|
await self._handle_delete_button(event)
|
||||||
await self._handle_copy_button(event)
|
await self._handle_copy_button(event)
|
||||||
|
await self._handle_phishlist_button(event)
|
||||||
|
|
|
@ -7,8 +7,8 @@ from jarvis_core.db import q
|
||||||
from jarvis_core.db.models import (
|
from jarvis_core.db.models import (
|
||||||
Autopurge,
|
Autopurge,
|
||||||
Autoreact,
|
Autoreact,
|
||||||
Filter,
|
|
||||||
Mute,
|
Mute,
|
||||||
|
Phishlist,
|
||||||
Roleping,
|
Roleping,
|
||||||
Setting,
|
Setting,
|
||||||
Warning,
|
Warning,
|
||||||
|
@ -18,15 +18,16 @@ from naff import listen
|
||||||
from naff.api.events.discord import MessageCreate, MessageDelete, MessageUpdate
|
from naff.api.events.discord import MessageCreate, MessageDelete, MessageUpdate
|
||||||
from naff.client.utils.misc_utils import find_all
|
from naff.client.utils.misc_utils import find_all
|
||||||
from naff.models.discord.channel import DMChannel, GuildText
|
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.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.message import Message
|
||||||
from naff.models.discord.user import Member
|
from naff.models.discord.user import Member
|
||||||
|
|
||||||
from jarvis.branding import get_command_color
|
from jarvis.branding import get_command_color
|
||||||
|
from jarvis.embeds.admin import warning_embed
|
||||||
from jarvis.tracking import malicious_tracker, warnings_tracker
|
from jarvis.tracking import malicious_tracker, warnings_tracker
|
||||||
from jarvis.utils import build_embed
|
from jarvis.utils import build_embed
|
||||||
from jarvis.utils.embeds import warning_embed
|
|
||||||
|
|
||||||
|
|
||||||
class MessageEventMixin:
|
class MessageEventMixin:
|
||||||
|
@ -83,6 +84,10 @@ class MessageEventMixin:
|
||||||
]
|
]
|
||||||
if (m := match.group(1)) not in allowed and setting.value:
|
if (m := match.group(1)) not in allowed and setting.value:
|
||||||
self.logger.debug(f"Removing non-allowed invite `{m}` from {message.guild.id}")
|
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)
|
expires_at = datetime.now(tz=timezone.utc) + timedelta(hours=24)
|
||||||
await Warning(
|
await Warning(
|
||||||
|
@ -96,49 +101,12 @@ class MessageEventMixin:
|
||||||
).commit()
|
).commit()
|
||||||
tracker = warnings_tracker.labels(guild_id=message.guild.id, guild_name=message.guild.name)
|
tracker = warnings_tracker.labels(guild_id=message.guild.id, guild_name=message.guild.name)
|
||||||
tracker.inc()
|
tracker.inc()
|
||||||
embed = warning_embed(message.author, "Sent an invite link")
|
embed = warning_embed(message.author, "Sent an invite link", self.user)
|
||||||
try:
|
try:
|
||||||
await message.reply(embeds=embed)
|
await message.channel.send(embeds=embed)
|
||||||
except Exception:
|
except Exception:
|
||||||
self.logger.warn("Failed to send warning embed")
|
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:
|
async def massmention(self, message: Message) -> None:
|
||||||
"""Handle massmention events."""
|
"""Handle massmention events."""
|
||||||
massmention = await Setting.find_one(
|
massmention = await Setting.find_one(
|
||||||
|
@ -168,9 +136,9 @@ class MessageEventMixin:
|
||||||
).commit()
|
).commit()
|
||||||
tracker = warnings_tracker.labels(guild_id=message.guild.id, guild_name=message.guild.name)
|
tracker = warnings_tracker.labels(guild_id=message.guild.id, guild_name=message.guild.name)
|
||||||
tracker.inc()
|
tracker.inc()
|
||||||
embed = warning_embed(message.author, "Mass Mention")
|
embed = warning_embed(message.author, "Mass Mention", self.user)
|
||||||
try:
|
try:
|
||||||
await message.reply(embeds=embed)
|
await message.channel.send(embeds=embed)
|
||||||
except Exception:
|
except Exception:
|
||||||
self.logger.warn("Failed to send warning embed")
|
self.logger.warn("Failed to send warning embed")
|
||||||
|
|
||||||
|
@ -232,9 +200,11 @@ class MessageEventMixin:
|
||||||
).commit()
|
).commit()
|
||||||
tracker = warnings_tracker.labels(guild_id=message.guild.id, guild_name=message.guild.name)
|
tracker = warnings_tracker.labels(guild_id=message.guild.id, guild_name=message.guild.name)
|
||||||
tracker.inc()
|
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:
|
try:
|
||||||
await message.reply(embeds=embed)
|
await message.channel.send(embeds=embed)
|
||||||
except Exception:
|
except Exception:
|
||||||
self.logger.warn("Failed to send warning embed")
|
self.logger.warn("Failed to send warning embed")
|
||||||
|
|
||||||
|
@ -242,6 +212,9 @@ class MessageEventMixin:
|
||||||
"""Check if the message contains any known phishing domains."""
|
"""Check if the message contains any known phishing domains."""
|
||||||
for match in url.finditer(message.content):
|
for match in url.finditer(message.content):
|
||||||
if (m := match.group("domain")) in self.phishing_domains:
|
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(
|
self.logger.debug(
|
||||||
f"Phishing url `{m}` detected in {message.guild.id}/{message.channel.id}/{message.id}"
|
f"Phishing url `{m}` detected in {message.guild.id}/{message.channel.id}/{message.id}"
|
||||||
)
|
)
|
||||||
|
@ -257,9 +230,9 @@ class MessageEventMixin:
|
||||||
).commit()
|
).commit()
|
||||||
tracker = warnings_tracker.labels(guild_id=message.guild.id, guild_name=message.guild.name)
|
tracker = warnings_tracker.labels(guild_id=message.guild.id, guild_name=message.guild.name)
|
||||||
tracker.inc()
|
tracker.inc()
|
||||||
embed = warning_embed(message.author, "Phishing URL")
|
embed = warning_embed(message.author, "Phishing URL", self.user)
|
||||||
try:
|
try:
|
||||||
await message.reply(embeds=embed)
|
await message.channel.send(embeds=embed)
|
||||||
except Exception:
|
except Exception:
|
||||||
self.logger.warn("Failed to send warning embed")
|
self.logger.warn("Failed to send warning embed")
|
||||||
try:
|
try:
|
||||||
|
@ -268,12 +241,41 @@ class MessageEventMixin:
|
||||||
self.logger.warn("Failed to delete malicious message")
|
self.logger.warn("Failed to delete malicious message")
|
||||||
tracker = malicious_tracker.labels(guild_id=message.guild.id, guild_name=message.guild.name)
|
tracker = malicious_tracker.labels(guild_id=message.guild.id, guild_name=message.guild.name)
|
||||||
tracker.inc()
|
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 True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
async def malicious_url(self, message: Message) -> None:
|
async def malicious_url(self, message: Message) -> None:
|
||||||
"""Check if the message contains any known phishing domains."""
|
"""Check if the message contains any known phishing domains."""
|
||||||
for match in url.finditer(message.content):
|
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:
|
async with ClientSession() as session:
|
||||||
resp = await session.post(
|
resp = await session.post(
|
||||||
"https://anti-fish.bitflow.dev/check",
|
"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 = warnings_tracker.labels(guild_id=message.guild.id, guild_name=message.guild.name)
|
||||||
tracker.inc()
|
tracker.inc()
|
||||||
reasons = ", ".join(f"{m['source']}: {m['type']}" for m in data["matches"])
|
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:
|
try:
|
||||||
await message.reply(embeds=embed)
|
await message.channel.send(embeds=embed)
|
||||||
except Exception:
|
except Exception:
|
||||||
self.logger.warn("Failed to send warning embed")
|
self.logger.warn("Failed to send warning embed")
|
||||||
try:
|
try:
|
||||||
|
@ -314,6 +316,31 @@ class MessageEventMixin:
|
||||||
self.logger.warn("Failed to delete malicious message")
|
self.logger.warn("Failed to delete malicious message")
|
||||||
tracker = malicious_tracker.labels(guild_id=message.guild.id, guild_name=message.guild.name)
|
tracker = malicious_tracker.labels(guild_id=message.guild.id, guild_name=message.guild.name)
|
||||||
tracker.inc()
|
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 True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@ -362,7 +389,6 @@ class MessageEventMixin:
|
||||||
malicious = await self.malicious_url(message)
|
malicious = await self.malicious_url(message)
|
||||||
if phish or malicious:
|
if phish or malicious:
|
||||||
await self.timeout_user(message.author, message.channel)
|
await self.timeout_user(message.author, message.channel)
|
||||||
await self.filters(message)
|
|
||||||
|
|
||||||
@listen()
|
@listen()
|
||||||
async def on_message_edit(self, event: MessageUpdate) -> None:
|
async def on_message_edit(self, event: MessageUpdate) -> None:
|
||||||
|
@ -418,7 +444,6 @@ class MessageEventMixin:
|
||||||
malicious = await self.malicious_url(after)
|
malicious = await self.malicious_url(after)
|
||||||
if phish or malicious:
|
if phish or malicious:
|
||||||
await self.timeout_user(after.author, after.channel)
|
await self.timeout_user(after.author, after.channel)
|
||||||
await self.filters(after)
|
|
||||||
|
|
||||||
@listen()
|
@listen()
|
||||||
async def on_message_delete(self, event: MessageDelete) -> None:
|
async def on_message_delete(self, event: MessageDelete) -> None:
|
||||||
|
|
Loading…
Add table
Reference in a new issue