Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
# Ignore configuration files
/config/*.yml
/config/*.secret
/nginx/user_conf.d/*.conf
/nginx/nginx_secrets/*.ini
*.env
/.venv/

# Ignore logs
*.log
# Python specific
__pycache__
__pycache__

# Ignore media files
/media/*
26 changes: 26 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Use the official Python image from the Docker Hub
FROM python:3.11-slim

# Install ffmpeg
RUN apt-get update && apt-get install -y ffmpeg && rm -rf /var/lib/apt/lists/*

# Set the working directory in the container
WORKDIR /app

# Copy the requirements file into the container
COPY requirements.txt .

# Install the dependencies
RUN pip install --no-cache-dir -r requirements.txt

# Copy the rest of the application code into the container
COPY . .

# Set environment variables
ENV PYTHONUNBUFFERED=1

# Expose the port the app runs on
EXPOSE 8000

# Run the application
CMD ["python", "main.py"]
39 changes: 38 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,41 @@ Mongodb:
```
use sourcebot
db.tiktok_db.createIndex( { "tiktok_id": 1 }, { unique: true } )
```
```

# Local Configuration

Set variables in `config/main.yml`

Required env variables:
```
discord:
token:
role_channel:
logs_channel:
sauce_channels;
money_guilds:

mongodb:
uri:
db:
```

# Docker Instructions

You must configure the following files for Docker:

- `config/main.yml`
- Bot configuration
- See `config/main.yaml.example`
- `user_conf.d/nginx.conf`
- NGINX configuration for GIF/Media conversion hosting
- See, rename & copy `nginx.conf.example` into the `user_conf.d` folder
- Change all instances of `STATIC.EXAMPLE.COM` to your desired hostname
- `nginx-certbot.env`
- Let's Encrypt configuration
- See `nginx-certbot.env.example`
- `nginx/nginx_secrets/cloudflare.ini`
- Optional for Cloudflare DNS Let's Encrypt challenge
- This can be subbed for other DNS API challenges
- Can also be removed if you want to use default web challenge (godspeed)
2 changes: 1 addition & 1 deletion config.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
# Third-party libraries
import yaml

MAIN_CONFIG = "code/config/main.yml"
MAIN_CONFIG = "config/main.yml"

# Load config files
with open(MAIN_CONFIG) as file:
Expand Down
10 changes: 9 additions & 1 deletion config/main.yml.example
Original file line number Diff line number Diff line change
@@ -1,13 +1,21 @@
discord:
token: "discord-bot-token"
role_channel: "role-channel"
logs_channel: 567890123456789012
logs_channel: 123456789012345678
sauce_channels: # Channels ids where source providing should work
- 123456789012345678
- 234567890123456789
money_guilds:
- 345678901234567890
- 456789012345678901
debug:
link_detection: true
handler_detection: true
delete_original_embeds: true

mongodb:
uri: "mongodb://mongodb:27017/" # use mongodb://127.0.0.1/sourcebot for local development, leave alone for docker
db: "sourcebot"

telegram:
token: "-"
Expand Down
54 changes: 54 additions & 0 deletions docker-compose.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
services:
sourcebot:
build: .
container_name: sourcebot
environment:
- PYTHONUNBUFFERED=1
volumes:
- .:/app
- media:/media
ports:
- "8000:8000"
depends_on:
- mongodb
develop:
watch:
- action: rebuild
path: .
ignore:
- "*.pyc"
- "__pycache__/"
- ".git/"
- "media/"
- "*.md"

mongodb:
image: mongo:4.4
container_name: mongodb
ports:
- "27017:27017"
volumes:
- mongo_data:/data/db

nginx:
image: jonasal/nginx-certbot:latest
container_name: nginx
env_file:
- ./nginx-certbot.env
volumes:
- ./media:/media
- nginx_secrets:/etc/letsencrypt # Docker managed volume (see list at the bottom)
- ./nginx/nginx_secrets/cloudflare.ini:/etc/letsencrypt/cloudflare.ini # Optional DNS INI file, remove if not needed
- ./nginx/user_conf.d:/etc/nginx/user_conf.d # or a host mount with a relative or full path.
ports:
- 443:443
depends_on:
- sourcebot


volumes:
mongo_data:
media:
nginx_secrets:
user_conf.d:

2 changes: 1 addition & 1 deletion handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -398,7 +398,7 @@ async def tiktok(**kwargs):
tiktok_id = url.split('/')[-1]

# Prepare mongodb connection
client = MongoClient("mongodb://127.0.0.1/sourcebot")
client = MongoClient(config['mongodb']['uri'])
cached_data = client['sourcebot']['tiktok_db'].find_one({
'tiktok_id': int(tiktok_id)
})
Expand Down
67 changes: 60 additions & 7 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@
# Spoiler regular expression
spoiler_regex = re.compile(r"(\|\|.*?\|\||\<.*?\>|\`.*?\`)", re.DOTALL)

# URL detection regular expression
url_regex = re.compile(r'https?://(?:[-\w.]|(?:%[\da-fA-F]{2}))+[^\s]*')

# Role reactions
async def handle_reaction(payload):
'''
Expand Down Expand Up @@ -60,7 +63,7 @@ async def handle_reaction(payload):
return

# Search for role in mongodb
client = MongoClient('mongodb://127.0.0.1/sourcebot')
client = MongoClient(config['mongodb']['uri'])
result = client['sourcebot']['roles'].find_one({
'guild': payload.guild_id,
'emoji': emoji
Expand Down Expand Up @@ -109,12 +112,22 @@ async def on_message(message: discord.Message):
'''
Events for each message (main functionality of the bot)
'''
# Optional logging - DEBUG USE ONLY, not recommended for production
#print(f"(Diagnostic) Message from {message.author}: {message.content}")

if message.author == bot.user:
return

# Process prefix commands
await bot.process_commands(message)

# Debug message for links in messages
if url_regex.search(message.content) and config['discord'].get('debug', {}).get('link_detection', False):
print(f"[DEBUG] Link detected in message from {message.author} (ID: {message.author.id}) in channel {message.channel.name if hasattr(message.channel, 'name') else 'DM'}")
print(f"[DEBUG] Message content: {message.content}")
print(f"[DEBUG] Links found: {[match.group(0) for match in url_regex.finditer(message.content)]}")
print("-" * 50)

# Ignore text in valid spoiler tag
content = re.sub(spoiler_regex, '', message.content)

Expand Down Expand Up @@ -164,10 +177,30 @@ async def on_message(message: discord.Message):
)

if isinstance(files, list):
# Debug logs
# Debug logs to console
if config['discord'].get('debug', {}).get('handler_detection', False):
print(f"[DEBUG] Handler successfully processed: {parser['function'].__name__}")
print(f"[DEBUG] Match groups: {match.groups()}")
print(f"[DEBUG] From user: {message.author} in channel: {message.channel.name if hasattr(message.channel, 'name') else 'DM'}")
print("-" * 50)

# Debug logs to Discord channel
logs_channel = bot.get_channel(config['discord']['logs_channel'])
await logs_channel.send(f"```\n{message.author=}\n{message.channel=}\n{match.groups()=}\n```")

# Delete original message embeds if configured
if config['discord'].get('delete_original_embeds', False) and not isinstance(message.channel, discord.DMChannel):
try:
await message.edit(suppress=True)
if config['discord'].get('debug', {}).get('handler_detection', False):
print(f"[DEBUG] Deleted embeds from message: {message.id}")
except discord.Forbidden:
if config['discord'].get('debug', {}).get('handler_detection', False):
print(f"[DEBUG] No permission to delete embeds from message: {message.id}")
except Exception as e:
if config['discord'].get('debug', {}).get('handler_detection', False):
print(f"[DEBUG] Error deleting embeds from message: {message.id}, Error: {str(e)}")

for i in range(0, len(files), 10):
await message.channel.send(files=[ discord.File(file) for file in files[i:i+10] ])
await logs_channel.send(files=[ discord.File(file) for file in files[i:i+10] ])
Expand All @@ -180,11 +213,31 @@ async def on_message(message: discord.Message):
)

if isinstance(output, list):
# Debug logs
# Debug logs to console
if config['discord'].get('debug', {}).get('handler_detection', False):
print(f"[DEBUG] Handler successfully processed: {parser['function'].__name__}")
print(f"[DEBUG] Match groups: {match.groups()}")
print(f"[DEBUG] From user: {message.author} in channel: {message.channel.name if hasattr(message.channel, 'name') else 'DM'}")
print("-" * 50)

# Debug logs to Discord channel
logs_channel = bot.get_channel(config['discord']['logs_channel'])
if parser['function'] is not handlers.youtube:
await logs_channel.send(f"```\n{message.author=}\n{message.channel=}\n{match.groups()=}\n```")

# Delete original message embeds if configured
if config['discord'].get('delete_original_embeds', False) and not isinstance(message.channel, discord.DMChannel):
try:
await message.edit(suppress=True)
if config['discord'].get('debug', {}).get('handler_detection', False):
print(f"[DEBUG] Deleted embeds from message: {message.id}")
except discord.Forbidden:
if config['discord'].get('debug', {}).get('handler_detection', False):
print(f"[DEBUG] No permission to delete embeds from message: {message.id}")
except Exception as e:
if config['discord'].get('debug', {}).get('handler_detection', False):
print(f"[DEBUG] Error deleting embeds from message: {message.id}, Error: {str(e)}")

for kwargs in output:
await message.channel.send(**kwargs)
if parser['function'] is not handlers.youtube:
Expand All @@ -210,7 +263,7 @@ async def _tiktok(ctx):
'''
Posts a random tiktok from sourcebot's collection.
'''
client = MongoClient('mongodb://127.0.0.1/sourcebot')
client = MongoClient(config['mongodb']['uri'])
tiktok = client['sourcebot']['tiktok_db'].aggregate([{ "$sample": { "size": 1 } }]).next()
await ctx.respond(f"{config['media']['url']}/tiktok-{tiktok['tiktok_id']}.mp4")

Expand Down Expand Up @@ -267,7 +320,7 @@ async def _list(ctx):
Returns current list of roles configured for sourcebot.
'''
embed = discord.Embed(title="Current settings", colour=discord.Colour(0x8ba089))
client = MongoClient('mongodb://127.0.0.1/sourcebot')
client = MongoClient(config['mongodb']['uri'])
for role in client['sourcebot']['roles'].find({'guild': ctx.guild.id}):
embed.add_field(name=role['emoji'], value=f"<@&{role['role']}>")
await ctx.respond(embed=embed)
Expand All @@ -278,7 +331,7 @@ async def _add(ctx, emoji: str, *, role: discord.Role):
'''
Adds a new role reaction to the sourcebot.
'''
client = MongoClient('mongodb://127.0.0.1/sourcebot')
client = MongoClient(config['mongodb']['uri'])
client['sourcebot']['roles'].insert_one({
'guild': ctx.guild.id,
'emoji': emoji,
Expand All @@ -292,7 +345,7 @@ async def _remove(ctx, emoji: str):
'''
Removes a role reaction from sourcebot list.
'''
client = MongoClient('mongodb://127.0.0.1/sourcebot')
client = MongoClient(config['mongodb']['uri'])
client['sourcebot']['roles'].delete_one({
'guild': ctx.guild.id,
'emoji': emoji
Expand Down
17 changes: 17 additions & 0 deletions nginx-certbot.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Required
CERTBOT_EMAIL=your@email.org

# Optional (Defaults)
DHPARAM_SIZE=2048
ELLIPTIC_CURVE=secp256r1
RENEWAL_INTERVAL=8d
RSA_KEY_SIZE=2048
STAGING=0
USE_ECDSA=1

# Advanced (Defaults)
# Ideally use dns-cloudflare (or other DNS provider) instead of webroot
CERTBOT_AUTHENTICATOR=webroot
CERTBOT_DNS_PROPAGATION_SECONDS=""
DEBUG=0
USE_LOCAL_CA=0
Loading