Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/developer51709/Noxie/llms.txt

Use this file to discover all available pages before exploring further.

utils/economy.py is Noxie’s single source of truth for all persistent user data. It provides every SQLite CRUD operation the cogs need — balance reads and writes, hunt recording, cooldown checks, inventory management, badge awarding, and donation logging — organized into four logical groups that map directly to the four database tables. All public functions accept a sqlite3.Connection as their first positional argument. That connection is created by helpers.get_db_conn() with row_factory = sqlite3.Row, which means every row returned can be accessed by column name as well as by index.

Schema Initialization

def init_db(conn: sqlite3.Connection) -> None
Creates all four tables using CREATE TABLE IF NOT EXISTS, making the call safe to run on every bot startup. Called once during NoxieBot.setup_hook() before any cog is loaded. Tables created:
TablePrimary KeyDescription
economy(user_id, guild_id)Per-user-per-guild balances, hunt counts, streaks, and last-hunt timestamp
inventoryid (autoincrement)One row per caught creature, with fused flag for future fusion feature
badgesid (autoincrement), unique on (user_id, badge_name)Badge awards with timestamps; the unique constraint prevents duplicates
donationsid (autoincrement)Immutable donation log; each transaction is a new row
def init_db(conn: sqlite3.Connection) -> None:
    conn.executescript("""
        CREATE TABLE IF NOT EXISTS economy (
            user_id     TEXT NOT NULL,
            guild_id    TEXT NOT NULL,
            glow_shards INTEGER NOT NULL DEFAULT 0,
            vibe_coins  INTEGER NOT NULL DEFAULT 0,
            total_hunts INTEGER NOT NULL DEFAULT 0,
            hunt_streak INTEGER NOT NULL DEFAULT 0,
            last_hunt   REAL    NOT NULL DEFAULT 0,
            PRIMARY KEY (user_id, guild_id)
        );

        CREATE TABLE IF NOT EXISTS badges (
            id          INTEGER PRIMARY KEY AUTOINCREMENT,
            user_id     TEXT    NOT NULL,
            badge_name  TEXT    NOT NULL,
            awarded_at  REAL    NOT NULL DEFAULT 0,
            UNIQUE(user_id, badge_name)
        );

        CREATE TABLE IF NOT EXISTS donations (
            id          INTEGER PRIMARY KEY AUTOINCREMENT,
            user_id     TEXT    NOT NULL,
            amount_usd  REAL    NOT NULL,
            currency    TEXT    NOT NULL DEFAULT 'USDT',
            tx_id       TEXT    NOT NULL,
            awarded_at  REAL    NOT NULL DEFAULT 0
        );

        CREATE TABLE IF NOT EXISTS inventory (
            id          INTEGER PRIMARY KEY AUTOINCREMENT,
            user_id     TEXT    NOT NULL,
            guild_id    TEXT    NOT NULL,
            creature_id TEXT    NOT NULL,
            caught_at   REAL    NOT NULL DEFAULT 0,
            fused       INTEGER NOT NULL DEFAULT 0
        );
    """)
    conn.commit()

Balance Functions

These functions manage the economy table — the core currency and hunt-stat store.

get_balance()

def get_balance(conn: sqlite3.Connection, user_id: str, guild_id: str) -> dict
Returns the full economy row for a user in a guild as a dict. Calls _ensure_row() first, so it always returns a valid dict even for first-time users. Return shape:
{
    "glow_shards": int,   # primary currency balance
    "vibe_coins":  int,   # premium currency balance
    "total_hunts": int,   # lifetime hunt count
    "hunt_streak": int,   # current consecutive hunt streak
    "last_hunt":   float, # Unix timestamp of last hunt (0.0 if never)
}

add_currency()

def add_currency(
    conn: sqlite3.Connection,
    user_id: str,
    guild_id: str,
    glow_shards: int = 0,
    vibe_coins: int = 0,
) -> None
Adds the specified amounts to the user’s balance using an UPDATE … SET x = x + ? statement, making it safe for concurrent writes. Either or both currency amounts may be specified; defaults of 0 mean unspecified currencies are unaffected.

spend_currency()

def spend_currency(
    conn: sqlite3.Connection,
    user_id: str,
    guild_id: str,
    glow_shards: int = 0,
    vibe_coins: int = 0,
) -> bool
Attempts to deduct the specified amounts from the user’s balance. Returns False if the user cannot afford the cost (checked individually per currency); returns True and commits the deduction otherwise.
spend_currency() performs a read-then-write in two separate statements. For production use at high concurrency, wrap calls in a transaction or use BEGIN IMMEDIATE to prevent race conditions.

Hunt Functions

record_hunt()

def record_hunt(
    conn: sqlite3.Connection,
    user_id: str,
    guild_id: str,
    glow_earned: int,
    coins_earned: int,
) -> dict
Atomically increments total_hunts by 1, increments hunt_streak by 1, sets last_hunt to time.time(), and credits glow_earned Glow Shards and coins_earned Vibe Coins — all in a single UPDATE statement. Returns the updated balance dict (same shape as get_balance()).
record_hunt() does not reset hunt_streak. Streak resets based on cooldown expiry are intentionally left to future game logic rather than forced in the DB layer.

check_hunt_cooldown()

def check_hunt_cooldown(
    conn: sqlite3.Connection,
    user_id: str,
    guild_id: str,
) -> float
Returns the number of seconds remaining on the hunt cooldown as a float. Returns 0.0 if the cooldown has expired or the user has never hunted. The cooldown duration is read from config.json under the hunt_cooldown key (defaults to 30 seconds if not set). Usage pattern in cogs/hunt.py:
remaining = economy.check_hunt_cooldown(bot.db, user_id, guild_id)
if remaining > 0:
    # show cooldown container and return early
    ...

Inventory Functions

The inventory table stores one row per caught creature instance. The same creature can appear multiple times (if caught more than once), and instances can be marked fused=1 to flag them as consumed by a future fusion mechanic.

add_to_inventory()

def add_to_inventory(
    conn: sqlite3.Connection,
    user_id: str,
    guild_id: str,
    creature_id: str,
) -> None
Inserts a new inventory row for the given creature with caught_at = time.time() and fused = 0. Always inserts a new row — duplicates are intentional and represent multiple copies of the same creature.

get_inventory()

def get_inventory(
    conn: sqlite3.Connection,
    user_id: str,
    guild_id: str,
) -> list[dict]
Returns all inventory rows for the user in the guild, ordered by caught_at DESC (most recently caught first). Includes fused entries. Return shape per item:
{
    "creature_id": str,   # matches id field in vibe_creatures/data.json
    "caught_at":   float, # Unix timestamp
    "fused":       int,   # 0 = available, 1 = consumed by fusion
}

count_creature()

def count_creature(
    conn: sqlite3.Connection,
    user_id: str,
    guild_id: str,
    creature_id: str,
) -> int
Returns the count of non-fused copies (fused = 0) of a specific creature in the user’s inventory. Returns 0 if the user has none.

Badge Functions

Badges are global per user (not per guild). The badges table uses a UNIQUE(user_id, badge_name) constraint to guarantee idempotency.

award_badge()

def award_badge(
    conn: sqlite3.Connection,
    user_id: str,
    badge_name: str,
) -> bool
Attempts to insert a new badge row. Returns True if the badge was newly awarded, or False if the user already has it (caught via sqlite3.IntegrityError from the unique constraint). Use the return value to trigger first-time badge announcements. Example usage in cogs/hunt.py:
if rarity == "legendary":
    if economy.award_badge(bot.db, user_id, "legendary"):
        logger.success(f"badge awarded: user={user_id} legendary")

get_badges()

def get_badges(conn: sqlite3.Connection, user_id: str) -> list[str]
Returns a list of badge_name strings for the user, ordered by awarded_at ASC (oldest first). Returns an empty list if the user has no badges. Pass this list to build_mood_pulse_container() along with BADGE_DISPLAY to render the formatted badge line in the profile card.

Donation Functions

log_donation()

def log_donation(
    conn: sqlite3.Connection,
    user_id: str,
    amount_usd: float,
    currency: str,
    tx_id: str,
) -> None
Appends an immutable donation record to the donations table. Called by cogs/donate.py after OxaPay confirms payment, before credits are awarded. tx_id stores the OxaPay trackId for reconciliation.

get_donation_total()

def get_donation_total(conn: sqlite3.Connection, user_id: str) -> float
Returns the sum of all amount_usd values across every donation row for the user, across all guilds. Returns 0.0 if the user has never donated. Uses COALESCE(SUM(...), 0) so the query always returns a number.

BADGE_DISPLAY

BADGE_DISPLAY is a module-level constant that maps internal badge_name keys to their display strings with emoji. Import it directly wherever badge rendering is needed (e.g. cogs/profile.py).
BADGE_DISPLAY: dict[str, str] = {
    "donor":       "💎 Donor",
    "hunter_10":   "🏹 Hunter I",
    "hunter_50":   "🏹 Hunter II",
    "hunter_100":  "🏹 Hunter III",
    "legendary":   "🌟 Legendary Finder",
    "mythic":      "🌌 Mythic Witness",
    "streak_25":   "🔥 Hot Streak",
}
badge_nameDisplayAwarded When
donor💎 DonorFirst confirmed donation
hunter_10🏹 Hunter I10 total hunts
hunter_50🏹 Hunter II50 total hunts
hunter_100🏹 Hunter III100 total hunts
legendary🌟 Legendary FinderCatching any legendary creature
mythic🌌 Mythic WitnessCatching any mythic creature
streak_25🔥 Hot Streak25-hunt streak

Internal Helpers

_ensure_row() is called internally by most economy functions before any read or write. It executes INSERT OR IGNORE INTO economy (user_id, guild_id) VALUES (?, ?), creating a default row with all-zero balances if the user-guild pair doesn’t exist yet. You never need to call it directly — any public function in this module handles first-time users automatically.

Build docs developers (and LLMs) love