1
0
forked from sass/tipibot
Files
tipibot/docs/DEV_NOTES.md
2026-05-04 20:50:30 +03:00

239 lines
13 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# TipiLAN Bot - Developer Reference
## File Structure
The codebase is split into **`core/`** (domain logic), **`commands/`** (Discord slash command handlers), and a thin **`bot.py`** that wires everything together.
### Top level
| File | Purpose |
|---|---|
| `bot.py` | Discord client, event handlers (`on_ready`, `on_member_join`, ...), background tasks (presence rotation, daily birthday loop), shared helpers (`_award_exp`, `_maybe_remind`, `_parse_amount`, `_PAUSED`), and `register_*_commands(...)` wiring for every command module |
| `strings.py` | **Single source of truth for all user-facing text.** Edit here to change any message. |
| `config.py` | Environment variables (TOKEN, GUILD_ID, PB_URL, etc.) |
### `core/` - domain logic, no Discord coupling
| File | Purpose |
|---|---|
| `core/economy.py` | All economy business logic (`do_daily`, `do_work`, ...), data model, constants (SHOP, COOLDOWNS, LEVEL_ROLES, EXP_REWARDS, JAIL_DURATION, ...) |
| `core/pb_client.py` | Async PocketBase REST client - auth token cache, CRUD on `economy_users` collection |
| `core/sheets.py` | Google Sheets integration (member sync) |
| `core/member_sync.py` | Birthday/member sync helpers |
### `commands/` - one slash-command group per file
Each file exposes a `register_<group>_commands(tree, bot, ...)` function. `bot.py` calls them all once on startup, passing in shared helpers (`coin`, `cd_ts`, `award_exp`, `maybe_remind`, `parse_amount`, ...).
| File | Commands / Responsibility |
|---|---|
| `commands/dev_member_commands.py` | `/check`, `/member` (dev profile only) |
| `commands/dev_member_runtime.py` | `on_member_join` flow + `birthday_daily` task body |
| `commands/economy_income_commands.py` | `/daily`, `/work`, `/beg`, `/crime`, `/rob` |
| `commands/economy_games_commands.py` | `/roulette`, `/slots`, `/blackjack`, `/rps` |
| `commands/economy_extra_commands.py` | `/heist`, `/jailbreak`, `/reminders`, `/request`, ... |
| `commands/economy_fish_commands.py` | `/fish`, `/fishbook`, `/fishsell` |
| `commands/economy_profile_commands.py` | `/balance`, `/rank`, `/stats`, `/cooldowns`, `/leaderboard` |
| `commands/economy_support_commands.py` | `/shop`, `/buy`, `/give`, `/economysetup` |
| `commands/economy_prestige_commands.py` | `/prestige`, `/prestigeshop`, `/prestigebuy` |
| `commands/economy_admin_commands.py` | `/admincoins`, `/adminexp`, `/adminitem`, `/adminjail`, `/adminban`, `/adminreset`, `/adminview` |
| `commands/ops_admin_commands.py` | `/sync`, `/restart`, `/shutdown`, `/pause`, `/send`, `/status` |
| `commands/ops_channel_commands.py` | Channel allowlist commands |
| `commands/info_commands.py` | `/patchnotes` and other lightweight info commands |
### `scripts/`
| File | Purpose |
|---|---|
| `scripts/migrate_to_pb.py` | One-time legacy migration: `data/economy.json` → PocketBase. Only relevant if you still have a pre-PB JSON store. |
| `scripts/add_stats_fields.py` | Schema migration: adds new fields to the `economy_users` collection. Idempotent. |
| `scripts/reset_pb_collections.py` | **Destructive** - deletes and recreates the dev + economy collections. Requires `--confirm`. Use only in dev. |
---
## Adding a New Economy Command
Pick the `commands/economy_*_commands.py` file that matches the new command's category (income, games, profile, ...) and add the handler inside its `register_*_commands` function. If none fit, create a new module and register it from `bot.py`.
Checklist - do all of these, in order:
1. **`core/economy.py`** - add the `do_<cmd>` async function with cooldown check, logic, `_commit`, and `_txn` logging
2. **`core/economy.py`** - add the cooldown to `COOLDOWNS` dict if it has one
3. **`core/economy.py`** - add the EXP reward to `EXP_REWARDS` dict
4. **`strings.py` `CMD`** - add the slash command description
5. **`strings.py` `OPT`** - add any parameter descriptions
6. **`strings.py` `TITLE`** - add embed title(s) for success/fail states
7. **`strings.py` `ERR`** - add any error messages (banned, cooldown uses `CD_MSG`, jailed uses `CD_MSG["jailed"]`)
8. **`strings.py` `CD_MSG`** - add cooldown message if command has a cooldown
9. **`strings.py` `HELP_CATEGORIES["tipibot"]["fields"]`** - add the command to the help embed
10. **`commands/economy_<group>_commands.py`** - inside `register_*_commands`, add `@tree.command(name="<cmd>", ...)` `cmd_<name>`; handle all `res["reason"]` cases
11. **`commands/economy_<group>_commands.py`** - call `maybe_remind(user_id, "<cmd>")` if the command has a cooldown and reminders make sense (the helper is passed in via the `register_*` signature)
12. **`commands/economy_<group>_commands.py`** - call `await award_exp(interaction, economy.EXP_REWARDS["<cmd>"])` on success
13. **`strings.py` `REMINDER_OPTS`** - add a reminder option if the command needs one
14. **`bot.py` `_maybe_remind`** - if the command has an item-modified cooldown, add an `elif` branch (this helper still lives in `bot.py` and is shared across all command modules)
---
## Adding a New Shop Item
Checklist:
1. **`core/economy.py` `SHOP`** - add the item dict `{name, emoji, cost, description: strings.ITEM_DESCRIPTIONS["key"]}`
2. **`core/economy.py` `SHOP_TIERS`** - add the key to the correct tier list (1/2/3)
3. **`core/economy.py` `SHOP_LEVEL_REQ`** - add minimum level if it is T2 (≥10) or T3 (≥20)
4. **`strings.py` `ITEM_DESCRIPTIONS`** - add the item description (Estonian flavour + English effect)
5. **`strings.py` `HELP_CATEGORIES["shop"]["fields"]`** - add display entry (sorted by cost)
6. If the item modifies a cooldown:
- **`core/economy.py`** - add the `if "item" in user["items"]` branch in the relevant `do_<cmd>` function
- **`bot.py` `_maybe_remind`** - add `elif cmd == "<cmd>" and "<item>" in items:` branch with the new delay
- **`commands/economy_profile_commands.py` `cmd_cooldowns`** - add the item annotation to the relevant status line
---
## Adding a New Level Role
1. **`core/economy.py` `LEVEL_ROLES`** - add `(min_level, "RoleName")` in descending level order (highest first)
2. **`bot.py` `_ensure_level_role`** - no changes needed (uses `LEVEL_ROLES` dynamically)
3. Run **`/economysetup`** in the server to create the role and set its position
---
## Adding a New Admin Command
1. **`strings.py` `CMD`** - add `"[Admin] ..."` description
2. **`strings.py` `HELP_CATEGORIES["admin"]["fields"]`** - add the entry
3. **`commands/economy_admin_commands.py`** (or `commands/ops_admin_commands.py` for non-economy ops) - add the handler with `@app_commands.default_permissions(manage_guild=True)` and `@app_commands.guild_only()`
---
## Economy System Design
### Storage
All economy state is stored in **PocketBase** (`economy_users` collection). `core/pb_client.py` owns all reads/writes. Each `do_*` function in `core/economy.py` calls `get_user()` → mutates the local dict → calls `_commit()`. `_commit` does a `PATCH` to PocketBase.
### Currency & Income Sources
| Command | Cooldown | Base Earn | Notes |
|---|---|---|---|
| `/daily` | 20h (18h w/ korvaklapid) | 150⬡ | ×streak multiplier, ×2 w/ lan_pass, +5% interest w/ gaming_laptop |
| `/work` | 1h (40min w/ monitor) | 15-75⬡ | ×1.5 w/ gaming_hiir, ×1.25 w/ reguleeritav_laud, ×3 30% chance w/ energiajook |
| `/beg` | 5min (3min w/ hiirematt) | 10-40⬡ | ×2 w/ klaviatuur |
| `/crime` | 2h | 200-500⬡ win | 60% success (75% w/ cat6), +30% w/ mikrofon; fail = fine + jail |
| `/rob` | 2h | 1025% of target | 45% success (60% w/ jellyfin); fail = fine; house rob: 35% success, 540% jackpot |
| `/slots` | - | varies | jackpot=10× (15× w/ monitor_360), triple=4× (6×), pair=1× |
| `/roulette` | - | 2× red/black, 14× green | 1/37 green chance |
| `/blackjack` | - | 1:1 win, 3:2 BJ, 2:1 double | Dealer stands on 17+; double down on first action only |
### "all" Keyword
Commands that accept a coin amount (`/give`, `/roulette`, `/rps`, `/slots`, `/blackjack`) accept `"all"` to mean the user's full current balance. Parsed by `_parse_amount(value, balance)` in `bot.py`.
### Daily Streak Multipliers
- 1-2 days: ×1.0 (150⬡)
- 3-6 days: ×1.5 (225⬡)
- 7-13 days: ×2.0 (300⬡)
- 14+ days: ×3.0 (450⬡)
- `karikas` item: streak survives missed days
### Jail
- Duration: 30 minutes (`JAIL_DURATION`)
- `gaming_tool`: prevents jail on crime fail
- `/jailbreak`: 3 dice rolls, need doubles to escape free. On fail - bail = 20-30% of balance, min 350⬡. If balance < 350⬡, player stays jailed until timer.
- **Blocked while jailed**: `/work`, `/beg`, `/crime`, `/rob`, `/give` (checked in `do_*` functions via `_is_jailed`)
### EXP Rewards (from `EXP_REWARDS` in `core/economy.py`)
EXP is awarded on every successful command use. Level formula: `level = max(1, floor(sqrt(exp / 6)))` (see `get_level` / `exp_for_level`). Thresholds: Level 5 = 150 EXP, Level 10 = 600, Level 20 = 2 400, Level 30 = 5 400.
Gambling EXP is bet-scaled via `gamble_exp(bet)`; fish EXP is per-species in `FISH` (common 23, uncommon 67, rare 10, epic 1415, legendary 25).
---
## Role Hierarchy (Discord)
Order top to bottom in server roles:
```
[Bot managed role] ← bot's own role, always at top of our stack
ECONOMY ← given to everyone who uses any economy command
TipiLEGEND ← level 30+
TipiCHAD ← level 20+
TipiHUSTLER ← level 10+
TipiGRINDER ← level 5+
TipiNOOB ← level 1+
```
Run `/economysetup` to auto-create all roles and set their positions. The command is idempotent - safe to run multiple times.
Role assignment:
- **ECONOMY** role: given automatically on first EXP award (i.e. first successful economy command)
- **Level roles**: given/swapped automatically on level-up; synced on `/rank`
---
## Shop Tiers & Level Requirements
| Tier | Level Required | Items |
|---|---|---|
| T1 | 0 (any) | gaming_hiir, hiirematt, korvaklapid, lan_pass, anticheat, energiajook, gaming_laptop |
| T2 | 10 | reguleeritav_laud, jellyfin, mikrofon, klaviatuur, monitor, cat6 |
| T3 | 20 | monitor_360, karikas, gaming_tool |
Shop display is sorted by cost (ascending) within each tier.
The `SHOP_LEVEL_REQ` dict in `core/economy.py` controls per-item lock thresholds.
---
## strings.py Organisation
Imported as `import strings as S` everywhere. Dicts are read from `bot.py` and from every `commands/*.py` module.
| Section | Dict | Typical usage |
|---|---|---|
| Flavour text | `WORK_JOBS`, `BEG_LINES`, `CRIME_WIN`, `CRIME_LOSE` | Randomised descriptions |
| Command descriptions | `CMD["key"]` | `@tree.command(description=S.CMD["key"])` |
| Parameter descriptions | `OPT["key"]` | `@app_commands.describe(param=S.OPT["key"])` |
| Help embed | `HELP_CATEGORIES["cat"]` | `cmd_help` (in `bot.py`) |
| Banned message | `MSG_BANNED` | All banned checks |
| Maintenance mode | `MSG_MAINTENANCE` | Shown when `_PAUSED=True` in `bot.py` (toggled by `/pause` in `commands/ops_admin_commands.py`) |
| Reminder options | `REMINDER_OPTS` | `RemindersSelect` dropdown |
| Slots outcomes | `SLOTS_TIERS["tier"]``(title, color)` | `cmd_slots` (in `commands/economy_games_commands.py`) |
| Embed titles | `TITLE["key"]` | `discord.Embed(title=S.TITLE["key"])` |
| Error messages | `ERR["key"]` | `send_message(S.ERR["key"])` - use `.format(**kwargs)` for dynamic parts |
| Cooldown messages | `CD_MSG["cmd"].format(ts=cd_ts(...))` | Cooldown responses (`cd_ts` helper passed in by `bot.py`) |
| Shop UI | `SHOP_UI["key"]` | `_shop_embed` (in `commands/economy_support_commands.py`) |
| Item descriptions | `ITEM_DESCRIPTIONS["item_key"]` | `core/economy.py` `SHOP[key]["description"]` |
| Patch notes UI | `PATCHNOTES_UI["key"]` | `commands/info_commands.py` (`/patchnotes`) |
---
## Constants Location Quick-Reference
| Constant | File | Description |
|---|---|---|
| `SHOP` | `core/economy.py` | All shop items (name, emoji, cost, description) |
| `SHOP_TIERS` | `core/economy.py` | Which items are in T1/T2/T3 |
| `SHOP_LEVEL_REQ` | `core/economy.py` | Min level per item |
| `COOLDOWNS` | `core/economy.py` | Base cooldown per command |
| `JAIL_DURATION` | `core/economy.py` | How long jail lasts |
| `LEVEL_ROLES` | `core/economy.py` | `[(min_level, "RoleName"), ...]` highest first |
| `ECONOMY_ROLE` | `core/economy.py` | Name of the base economy participation role |
| `EXP_REWARDS` | `core/economy.py` | EXP per command |
| `FISH` | `core/economy.py` | Fish species table (rarity, weight, coins, exp) |
| `HOUSE_ID` | `core/economy.py` | Bot's user ID (house account for /rob) |
| `MIN_BAIL` | `core/economy.py` | Minimum bail payment (350⬡) |
| `COIN` | `core/economy.py` | The coin emoji string |
| `_PAUSED` | `bot.py` | In-memory maintenance flag; toggled by `/pause`; blocks all non-admin commands |
---
## Balance Notes (as of current version)
- **Beg** is most efficient for active players (3min cooldown + 2× multiplier w/ `klaviatuur` = high ⬡/hr)
- **Work** is best for passive players (1h cooldown, fire and forget)
- **Crime** is high risk/reward - best with `cat6` + `mikrofon`
- **`lan_pass`** (1200⬡) doubles daily - good long-term investment
- **`gaming_laptop`** (1500⬡) 5% interest, capped 500⬡/day - snowballs with large balance
- `anticheat` is consumable (2 uses) - only item that can be re-bought
- `karikas` (T3) is the only item that preserves a daily streak across missed days
- `reguleeritav_laud` (T2) stacks with `gaming_hiir`: combined ×1.5 × ×1.25 = ×1.875