Projects:

Projects I’ve worked on, with notes on how they work and what I learnt.

PROJECT 01 / COMMUNITY INFRASTRUCTURE

GlobalHost Bot

PRIMARY STACK

Python · discord.py · SQLite

RUNTIME

Linux · systemd · uv

QUALITY

pytest

Overview

I built GlobalHost Bot for my Discord server, GlobalHost. It handles moderation checks and saves cases so staff can review them later. I run it on a Linux server as a service, so it can stay online without me starting it manually each time.

Why it’s used + how to use it

I use it in GlobalHost to help with routine moderation. Members use its Discord commands, and staff can check the cases it records. Its features are based on what our server needs, so it isn’t meant to be a one-size-fits-all bot.

SNIPPET / NORMALISING MESSAGE CONTENT

This function converts message text into a more consistent form before moderation checks. It normalises Unicode, replaces common lookalike characters (such as @ with a and 0 with o), then removes whitespace and punctuation matched by the configured regular expression. That helps the bot recognise some attempts to disguise words.

def _normalise(value):
    value = _unicode_text(value)
    value = value.replace("@", "a").replace("$", "s")
    value = value.replace("!", "i").replace("0", "o")
    return SPACE_PUNCT_RE.sub("", value)

What I learned

One tricky part was checking messages that try to disguise words with unusual characters. The bot normalises the text before checking it, which makes some of those attempts easier to catch. I’ve also had to think about two messages arriving almost at once. Per-user locks help stop handlers from acting on the same offence count at the same time, and tests help me check that changes don’t break older behaviour.

PROJECT 02 / API INTERACTION UTILITY

DisHook

PRIMARY STACK

Python · PySide6

INTERACTION

Discord webhooks · HTTP

FORMAT

JSON payloads

Overview

I built DisHook because I wanted a desktop app for making Discord webhook messages. It lets me prepare embeds, check the payload and reuse saved templates instead of putting every request together from scratch.

Why it’s used + how to use it

The usual flow is to add a webhook, build a message or embed, check the payload and then send it or save it as a template. The URL contains a token that can give access to the webhook, so it needs to be kept private.

SNIPPET / VALIDATING A WEBHOOK URL

This function checks whether an input looks like a Discord webhook URL before the app uses it. It requires HTTPS, an approved Discord hostname, the expected API path, a numeric webhook ID and a non-empty token. If parsing fails or the structure is wrong, it returns false. This is a format check, not proof that the webhook exists or that the token is valid.

def valid_webhook(url):
    try:
        parsed = urlparse(url)
        parts = parsed.path.split("/")
        return (
            isinstance(url, str)
            and parsed.scheme == "https"
            and parsed.netloc.lower() in {
                "discord.com", "discordapp.com"
            }
            and len(parts) == 5
            and parts[1:3] == ["api", "webhooks"]
            and parts[3].isdigit()
            and bool(parts[4])
        )
    except (TypeError, ValueError, AttributeError):
        return False

What I learned

I’ve learnt to check data before sending it to another service. DisHook checks the webhook URL and validates parts of the payload, including types and Discord’s length limits. Keeping that logic separate from the UI also means I can test it without opening the app and clicking through every step.

PROJECT 03 / PYTHON SHOWCASE

Stock Market Simulator

PRIMARY STACK

Python

MAIN TOPIC

State, transactions and valuation

PROJECT TYPE

Skills showcase

Overview

I made the Stock Market Simulator as a way to practise Python. It lets a user buy and sell shares in a simulated market, while the program tracks cash, holdings and portfolio value. It’s a small programming project, not a real trading platform.

Why it’s used + how to use it

I wanted to practise keeping the numbers consistent when a user buys or sells shares. Run the script in a terminal and follow the prompts to make transactions or check the portfolio.

SNIPPET / PORTFOLIO VALUATION

This code calculates the current value of the shares held. It multiplies each stock's quantity by its current market price, adds those values together, then prints the portfolio value and total wealth (cash plus shares), rounded to two decimal places.

total_value = 0

for stock in portfolio:
    stock_value = portfolio[stock] * market[stock]
    total_value += stock_value

print("Total portfolio value: $", round(total_value, 2))
print("Total wealth: $", round(player_cash + total_value, 2))

What I learned

This project made me think about how one action affects several values. Before a transaction, the program needs to check the stock and quantity, then make sure there’s enough cash or enough shares to sell. I also calculate the totals from the current data rather than saving another copy that could become wrong.