Skip to content
Merged
3 changes: 2 additions & 1 deletion apps/discord-bot/.gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
.env
/core/apps/discord-bot/.venv*
/core/apps/discord-bot/.venv*
*.log
78 changes: 75 additions & 3 deletions apps/discord-bot/README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,75 @@
### Discord Bot
for venv run source .venv/bin/activate if get error about pip commands not found
(hi alex)
# Discord Bot

## Prerequisites

Before you begin, make sure you have:

* **Python 3.8+** installed and on your PATH
* **Git** (optional if you clone the repo)

*All commands assume you are in the `core/apps/discord-bot/` directory.*

## 1. Create a Virtual Environment

```bash
python -m venv .venv
```

## 2. Activate the Environment

### macOS / Linux

```bash
source .venv/bin/activate
```

### Windows (PowerShell)

```powershell
.\.venv\Scripts\Activate.ps1
```

*(Your prompt should now be prefixed with `(.venv)`.)*

## 3. Install Dependencies

```bash
# Upgrade pip
python -m pip install --upgrade pip

# Install runtime dependencies
pip install -r requirements.txt
```

## 4. Configure Environment Variables

Create a file named `.env` in this directory with the following content:

```
DISCORD_TOKEN=YOUR_DISCORD_BOT_TOKEN_HERE
```

## 5. Run the Bot

With the venv active, start your bot:

```bash
python main.py
```

You should see something like:

```
Bot: SwampHackr is ready to go!
```

---

> If you ever switch to a fresh terminal session, re-activate the venv before running any commands:
>
> ```bash
> source .venv/bin/activate # (macOS/Linux)
> # or
> .\.venv\Scripts\Activate.ps1 # (Windows)
> ```

134 changes: 134 additions & 0 deletions apps/discord-bot/cogs/anti_spam.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import discord
from discord.ext import commands
import re
from typing import List, Pattern, Set
from utils.messaging import send_channel_message, send_dm



# Regular expressions for detecting spam patterns
SPAM_PATTERNS: List[Pattern] = [
# Pattern for detecting ticket sales
re.compile(r"""
(?i)
\bselling\b
.{0,60}?
(tickets?|passes?|spots?|seats?)
(.{0,20}?\bfor\b.{0,20}?\$?\d{2,5})?
""", re.VERBOSE),

# Pattern for detecting subleasing advertisements
re.compile(r"""
(?i)
\b(sublease|subleasing|sublet|lease)\b
(.{0,20}?\bfor\b.{0,20}?\$?\d{2,5})?
""", re.VERBOSE),

# Pattern for detecting "DM if interested" messages
re.compile(r"""
(?i)
dm\s+
(me\s+)?(if\s+)?interested\b
""", re.VERBOSE),
]


class AntiSpam(commands.Cog):
"""A cog that detects and handles spam messages in Discord channels.

This cog monitors all messages in the server and automatically removes
messages that match predefined spam patterns. It also notifies users
when their messages are removed.
"""

def __init__(self, bot: commands.Bot) -> None:
"""Initialize the AntiSpam cog

Args:
bot: Discord bot instance
"""
self.bot: commands.Bot = bot
self.ignore_channels: Set[int] = set()


def is_spam(self, message: discord.Message) -> bool:
"""Check if a message matches any spam patterns

Args:
message: Discord message to check

Returns:
bool: True if the message is determined to be spam, False otherwise
"""
content: str = message.content

for pattern in SPAM_PATTERNS:
if pattern.search(content):
return True

# TODO: Implement additional spam detection methods:
# - Check for repeated messages
# - Detect excessive mentions
# - Check for suspicious links
# - Monitor message frequency

return False

async def handle_spam(self, message: discord.Message) -> None:
"""Handle a detected spam message

This method:
1. Deletes the spam message
2. Sends a notification in the channel by calling send_channel_message
3. Sends a DM to the user explaining why their message was removed by calling send_dm

Args:
message: The spam message to handle

Note:
If the bot lacks permissions to delete messages or send DMs,
the error will be logged but not raised.
"""
try:
await message.delete()
content: str = f"{message.author.mention} Your message has been deleted for potential scam/spam."
await send_channel_message(message.channel, content, delete_after=5)

content = (
f"Your message was automatically deleted for potential scam/spam content:\n"
f"```{message.content}```\n"
f"If you believe this was a mistake, please contact server staff for review."
)
await send_dm(message.author, content)
except discord.Forbidden:
print("Bot lacks permissions to delete messages")

@commands.Cog.listener()
async def on_message(self, message: discord.Message) -> None:
"""Event listener for new messages

This method is called for every message sent in the server.
1. Ignores messages from bots
2. Ignores messages in ignored channels
3. Calls is_spam function to determine if message is spam

Args:
message: The message that triggered the event
"""
if message.author.bot:
return

if message.channel.id in self.ignore_channels:
return

if self.is_spam(message):
await self.handle_spam(message)


async def setup(bot: commands.Bot) -> None:
"""Add the AntiSpam cog to the bot

Args:
bot: Discord bot instance
"""
await bot.add_cog(AntiSpam(bot))
137 changes: 137 additions & 0 deletions apps/discord-bot/cogs/general.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
from discord.ext import commands

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Everything from the antiSpam.py file comments apply here too.

from discord import app_commands
import discord
from typing import Optional, Literal


class General(commands.Cog):
"""A cog containing general utility commands for the server

This cog includes commands for:
- Basic server interactions
- Role management
- Fun commands
"""

def __init__(self, bot: commands.Bot) -> None:
"""Initialize the General cog

Args:
bot: Discord bot instance
"""
self.bot: commands.Bot = bot

@commands.command()
async def test(self, ctx: commands.Context) -> None:
"""Send a test message

Args:
ctx: The command context
"""
await ctx.send("Testing")

@app_commands.command(
name="role",
description="Assign or remove a role from yourself"
)
@app_commands.describe(
action="Whether to assign or remove the role",
role="The role to assign or remove"
)
async def manage_role(
self,
interaction: discord.Interaction,
action: Literal["assign", "remove"],
role: discord.Role,
member: discord.Member
) -> None:
"""Manage roles for the user who triggered the command

Args:
interaction: The interaction that triggered this command
action: Whether to assign or remove the role
role: The role to assign or remove

Note:
This command will:
1. Check if the user already has/doesn't have the role
2. Assign or remove the role if conditions are met
3. Send appropriate feedback messages
"""
staff_role = discord.utils.get(interaction.guild.roles, name="Staff")

if not staff_role or staff_role not in interaction.user.roles:
await interaction.response.send_message(
"You don't have permission to use this command.",
ephemeral=True
)
return

member = await interaction.guild.fetch_member(member.id)


has_role = role in member.roles

if action == "assign":
if has_role:
await interaction.response.send_message(
f"{member.mention} already has the **{role.name}** role.",
ephemeral=True
)
return

try:
await member.add_roles(role)
await interaction.response.send_message(
f"Assigned **{role.name}** to {member.mention}.",
ephemeral=True
)
# Send a followup message that will be deleted after 5 seconds
await interaction.followup.send(
f"{interaction.user.mention} assigned **{role.name}** role to {member.mention}.",
delete_after=5
)
except discord.Forbidden:
await interaction.response.send_message(
"I don't have permission to assign roles!",
ephemeral=True
)
elif action == "remove":
if not has_role:
await interaction.response.send_message(
f"{member.mention} does not have the **{role.name}** role.",
ephemeral=True
)
return

try:
await member.remove_roles(role)
await interaction.response.send_message(
f"Removed **{role.name}** from {member.mention}.",
ephemeral=True
)
# Send a followup message that will be deleted after 5 seconds
await interaction.followup.send(
f"{interaction.user.mention} removed **{role.name}** role from {member.mention}.",
delete_after=5
)
except discord.Forbidden:
await interaction.response.send_message(
"I don't have permission to remove roles!",
ephemeral=True
)
else:
# This will not be reached but just wanted to show add and remove for commands
await interaction.response.send_message(
"Invalid action. Please use 'assign' or 'remove'.",
ephemeral=True
)


async def setup(bot: commands.Bot) -> None:
"""Add the General cog to the bot

Args:
bot: Discord bot instance
"""
await bot.add_cog(General(bot))
Loading