Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
3 changes: 3 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ Changes since 2026.06.04
- Adds a new /duck next command to show admins the next duck spawn time
- Fuzzes duck spawn time to make it less predictable

### Grab
- Message content is now encrypted in the database

### Hangman
- /hangman start is no longer logged in discord

Expand Down
80 changes: 80 additions & 0 deletions core/cryptography.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
"""A few simple functions to handle encrypting data"""

import base64
import hashlib
import hmac
import os

from cryptography.fernet import Fernet


def _get_raw_key() -> bytes:
"""Gets the raw key from the environment.

Raises:
AttributeError: Raised if no key is present.

Returns:
bytes: The raw encryption key.
"""
env_file_key = os.environ.get("DATA_ENCRYPT_KEY")
if not env_file_key:
raise AttributeError("Missing data encryption key, data cannot be secure")

return env_file_key.encode("utf-8")


def _get_key() -> bytes:
"""Processes the raw key into a format that Fernet accepts.

Returns:
bytes: A Fernet-compatible key.
"""
digest = hashlib.sha256(_get_raw_key()).digest()
return base64.urlsafe_b64encode(digest)


def encrypt(text: str) -> str:
"""Encrypts plaintext.

Args:
text (str): The plaintext to encrypt.

Returns:
str: The encrypted text.
"""
f = Fernet(_get_key())
token = f.encrypt(text.encode("utf-8"))
return token.decode("utf-8")


def decrypt(token: str) -> str:
"""Decrypts encrypted text.

Args:
token (str): The encrypted text to decrypt.

Returns:
str: The decrypted plaintext.
"""
f = Fernet(_get_key())
return f.decrypt(token.encode("utf-8")).decode("utf-8")


def hash_text(text: str) -> str:
"""Creates a deterministic hash suitable for equality checks.

This should be stored alongside encrypted data to support
duplicate detection and lookups.

Args:
text (str): The plaintext to hash.

Returns:
str: The hexadecimal HMAC digest.
"""
return hmac.new(
_get_raw_key(),
text.encode("utf-8"),
hashlib.sha256,
).hexdigest()
2 changes: 2 additions & 0 deletions core/databases.py
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ class Grab(bot.db.Model):
channel (str): The channel the message was grabbed from
guild (str): The guild the message was grabbed from
message (str): The string contents of the message
message_hash (str): A hash of the contents of the message
time (datetime.datetime): The time the message was grabbed
nsfw (bool): Whether the message was grabbed in an NSFW channel
"""
Expand All @@ -187,6 +188,7 @@ class Grab(bot.db.Model):
channel: str = bot.db.Column(bot.db.String)
guild: str = bot.db.Column(bot.db.String)
message: str = bot.db.Column(bot.db.String)
message_hash: str = bot.db.Column(bot.db.String)
time: datetime.datetime = bot.db.Column(
bot.db.DateTime, default=datetime.datetime.utcnow
)
Expand Down
3 changes: 2 additions & 1 deletion default.env
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ POSTGRES_DB_NAME=
POSTGRES_DB_PASSWORD=
POSTGRES_DB_USER=
DEBUG=0
CONFIG_YML=./config.yml
CONFIG_YML=./config.yml
DATA_ENCRYPT_KEY=
3 changes: 2 additions & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ services:
image: rtechsupport/techsupport-bot:prod
build: .
container_name: discordBot
env_file:
- .env
environment:
- DEBUG=${DEBUG}
- TZ=UTC
restart: unless-stopped
stop_signal: SIGINT
Expand Down
18 changes: 12 additions & 6 deletions modules/fun/grab.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

import configuration
import ui
from core import auxiliary, cogs
from core import auxiliary, cogs, cryptography

if TYPE_CHECKING:
import bot
Expand Down Expand Up @@ -109,11 +109,13 @@ async def grab_user(
)
return

grab_hash = cryptography.hash_text(grab_message)

grab = (
await self.bot.models.Grab.query.where(
self.bot.models.Grab.author_id == str(user_to_grab.id),
)
.where(self.bot.models.Grab.message == grab_message)
.where(self.bot.models.Grab.message_hash == grab_hash)
.gino.first()
)

Expand All @@ -127,7 +129,8 @@ async def grab_user(
author_id=str(user_to_grab.id),
channel=str(ctx.channel.id),
guild=str(ctx.guild.id),
message=grab_message,
message=cryptography.encrypt(grab_message),
message_hash=grab_hash,
nsfw=ctx.channel.is_nsfw(),
)
await grab.create()
Expand Down Expand Up @@ -206,7 +209,7 @@ async def all_grabs(
else embed
)
embed.add_field(
name=f'"{grab_.message}"',
name=f'"{cryptography.decrypt(grab_.message)}"',
value=grab_.time.date(),
inline=False,
)
Expand Down Expand Up @@ -276,9 +279,10 @@ async def random_grab(

random_index = random.randint(0, len(grabs) - 1)
grab = grabs[random_index]
print(f"Trying: {grab.message}")

@dkay0670 dkay0670 Jun 14, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

this can probably be removed though


embed = discord.Embed(
title=f'"{grab.message}"',
title=f'"{cryptography.decrypt(grab.message)}"',
description=f"{user_to_grab.name}, {grab.time.date()}",
)

Expand Down Expand Up @@ -320,13 +324,15 @@ async def delete_grab(
channel=ctx.channel,
)
return

grab_hash = cryptography.hash_text(message)
# Gets the target grab by the message
grab = (
await self.bot.models.Grab.query.where(
self.bot.models.Grab.author_id == str(target_user.id)
)
.where(self.bot.models.Grab.guild == str(ctx.guild.id))
.where(self.bot.models.Grab.message == message)
.where(self.bot.models.Grab.message_hash == grab_hash)
.gino.all()
)

Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ requires-python = ">=3.13,<3.14"
dependencies = [
"aiocron==2.1",
"bidict==0.23.1",
"cryptography==49.0.0",
"dateparser==1.4.0",
"discord.py==2.7.1",
"emoji==2.15.0",
Expand Down
Loading
Loading