Files
tipibot/README.md
2026-03-20 17:35:35 +02:00

430 lines
18 KiB
Markdown
Raw 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
Discord bot for the TipiLAN community. Manages member roles and nicknames via Google Sheets, announces birthdays, and runs the TipiCOIN economy.
---
## Table of Contents
1. [Setup](#setup)
2. [Member Management](#member-management)
3. [Admin Commands](#admin-commands)
4. [Birthday System](#birthday-system)
5. [TipiCOIN Economy](#tipicoin-economy)
6. [Project Structure](#project-structure)
---
## Setup
### 1. Discord Application
- **Bot token** - Settings → Bot → Token → Copy
- **Server Members Intent** must be **ON** - Settings → Bot → Privileged Gateway Intents
- Bot invite scopes: `bot` + `applications.commands`
- Required permissions: Manage Roles, Manage Nicknames, View Channels, Send Messages, Embed Links, Read Message History
> Permissions integer: `402738176`
### 2. Google Service Account
1. [Google Cloud Console](https://console.cloud.google.com/) → create/select project
2. Enable **Google Sheets API** and **Google Drive API**
3. Credentials → Create Credentials → Service Account
4. Download JSON key → save as `credentials.json` in the project root
5. Share your Google Sheet with the service account `client_email` - give **Editor** access
### 3. Google Sheet Format
Row 1 = headers (exact names). Row 2 = formula/stats row (skipped by bot). Data starts row 3.
| Column | What the bot does with it |
|---|---|
| **Nimi** | Sets Discord nickname: first name + last initial (`Mari-Liis Tamm``Mari-Liis T`) |
| **Organisatsioon** | Maps to a Discord role (comma-separated for multiple) |
| **Meil** | Read-only |
| **Discord** | Username used for initial matching |
| **User ID** | Bot writes numeric Discord ID here once matched |
| **Sünnipäev** | Birthday - accepts `DD/MM/YYYY`, `YYYY-MM-DD`, `MM-DD`. Years outside 1920-now ignored |
| **Telefon** | Read-only |
| **Valdkond** | Maps to a Discord role (comma-separated) |
| **Roll** | Maps to a Discord role (comma-separated) |
| **Discordis synced?** | Bot writes `TRUE`/`FALSE` checkbox after each sync |
| **Groupi lisatud?** | Managed externally |
**Empty cell values** - the bot treats blank cells, `"-"`, `"x"`, `"n/a"`, `"none"`, `"ei"` as empty/skipped.
**Role name mapping** - some sheet values map to different Discord role names. Trailing punctuation (`.`, `,`, `;` etc.) is stripped from sheet values before lookup, so `"Messiala."` correctly matches the `Messiala` Discord role:
| Sheet value | Discord role |
|---|---|
| `Juht` | `Tiimijuht` |
| `Admin` | `+` |
**Base roles** - two role IDs in `config.py → BASE_ROLE_IDS` are added to every synced member automatically.
### 4. PocketBase (Economy Database)
The economy system stores all player data in [PocketBase](https://pocketbase.io/).
1. Download `pocketbase.exe` (Windows) from https://pocketbase.io/docs/ and place it in the project root.
2. Start PocketBase: `.\pocketbase.exe serve`
3. Open the admin UI at http://127.0.0.1:8090/_/ and create a superuser account.
4. Create a collection named `economy_users` - see `docs/POCKETBASE_SETUP.md` for the full schema.
5. Set `PB_URL`, `PB_ADMIN_EMAIL`, `PB_ADMIN_PASSWORD` in `.env`.
6. **One-time data migration** (only if you have an existing `data/economy.json`): `python scripts/migrate_to_pb.py`
> PocketBase must be running before the bot starts. `pocketbase.exe` and `pb_data/` are gitignored.
### 5. Environment Variables
```bash
# Windows
copy .env.example .env
# macOS/Linux
cp .env.example .env
```
| Variable | Description |
|---|---|
| `DISCORD_TOKEN` | Bot token from Discord Developer Portal |
| `SHEET_ID` | ID from the Google Sheet URL |
| `GOOGLE_CREDS_PATH` | Path to `credentials.json` (default: `credentials.json`) |
| `GUILD_ID` | Right-click server → Copy Server ID (Developer Mode on) |
| `BIRTHDAY_CHANNEL_ID` | Channel for birthday `@here` pings |
| `BIRTHDAY_WINDOW_DAYS` | Days before birthday that on-join check flags it (default: 7) |
| `PB_URL` | PocketBase base URL (default: `http://127.0.0.1:8090`) |
| `PB_ADMIN_EMAIL` | PocketBase superuser e-mail |
| `PB_ADMIN_PASSWORD` | PocketBase superuser password |
### 6. Install & Run
```bash
python -m venv .venv
.venv\Scripts\activate # Windows
# source .venv/bin/activate # macOS/Linux
pip install -r requirements.txt
# Terminal 1 - keep running
.\pocketbase.exe serve
# Terminal 2
python bot.py
```
---
## Member Management
### On member join
- Bot looks the member up in the sheet by **Discord username**
- If found → sets nickname, assigns roles, writes back User ID, marks synced
- If **not found** → creates a new sheet row with their `Discord` username and `User ID` pre-filled. Admin fills in the rest, then runs `/check`
### Matching logic
1. Match by **User ID** (numeric, reliable - IDs never change)
2. Fall back to **Discord username** (case-insensitive) if no ID yet
3. Once matched by username, the bot writes the ID back so future matches are by ID
### Nickname format
`Nimi` column → first name + last name initial. Hyphenated first names preserved.
| Nimi | Nickname |
|---|---|
| Mari Tamm | Mari T |
| Mari-Liis Tamm | Mari-Liis T |
| Jaan | Jaan |
### Synced status
A member is marked `Discordis synced? = TRUE` when their sync completes with no errors.
Admins (bot lacks permission to modify them) are silently skipped and still marked synced.
---
## Admin Commands
> These commands are hidden from users who don't have the required permission. Override per-role in **Server Settings → Integrations → Bot**.
| Command | Permission | What it does |
|---|---|---|
| `/check` | Manage Roles | Refreshes sheet data, backfills missing User IDs, syncs nicknames + roles for every member, reports stats |
| `/member @user` | Manage Roles | Shows a member's full sheet data + calculated age |
| `/sync` | Manage Guild | Re-registers slash commands with Discord |
| `/restart` | Manage Guild | Gracefully restarts the bot process; posts ✅ in the same channel when back up |
| `/shutdown` | Manage Guild | Shuts the bot down cleanly without restarting |
| `/pause` | Manage Guild | Toggles maintenance mode — blocks all non-admin commands; calling again unpauses |
| `/send #channel message` | Manage Guild | Sends a message to any channel as the bot |
| `/status` | Manage Guild | Bot uptime, RAM, CPU, latency, cache stats, economy user count |
| `/admincoins @user <kogus> <põhjus>` | Manage Guild | Give (positive) or take (negative) TipiCOINi. Balance floored at 0. User gets a DM with reason. |
| `/adminjail @user <minutid> <põhjus>` | Manage Guild | Manually jail a user for N minutes. User gets a DM. |
| `/adminunjail @user` | Manage Guild | Release a user from jail immediately. |
| `/adminban @user <põhjus>` | Manage Guild | Ban a user from all economy commands. User gets a DM. |
| `/adminunban @user` | Manage Guild | Lift an economy ban. |
| `/adminreset @user <põhjus>` | Manage Guild | Wipe a user's balance, items, and streak to zero. User gets a DM. |
| `/adminview @user` | Manage Guild | Inspect a user's full economy profile: balance, streak, items, jail status, ban status. |
### `/check` output example
```
🔑 Täideti 3 puuduvat kasutaja ID-d.
✅ Korras: 54
🔧 Parandatud: 3
❓ Ei leitud: 1
⚠️ Vead: 2
Üksikasjad:
🔧 Mari T: +rollid: TipiSÕBER
⚠️ Rolli 'Messiala' ei leitud serverist
📊 Tabeli statistika - 58 liiget
...
```
---
## Birthday System
### Daily announcement
Every day at **09:00 Tallinn time** the bot checks all sheet rows and pings `@here` in `BIRTHDAY_CHANNEL_ID` for anyone whose birthday is today.
**Duplicate prevention** - announcements are logged to `birthday_sent.json` keyed by date. Bot restart on a birthday day does **not** re-ping. Log is auto-cleaned after 2 days.
### `/birthdays`
Paginated embed with **12 pages** - one per calendar month. Opens on the **current month**. Navigate with ◀/▶. Each entry shows:
- Member mention (if their User ID is known) or name
- Birthday date
- Days until next birthday (or 🎉 if today)
### On member join
If a member joins and their birthday is within `BIRTHDAY_WINDOW_DAYS` days, a birthday announcement is sent.
---
## TipiCOIN Economy
All economy data is stored in **PocketBase** (`economy_users` collection - see `pb_client.py`). The currency is **TipiCOIN** (⬡), displayed as a custom Discord emoji configured in `economy.py → COIN`.
---
### House account
The bot has its own TipiCOIN balance (the "house"). Coins flow **into** the house when players lose:
- `/roulette` - lost bets
- `/slots` - missed bets
- `/blackjack` - lost bets
- `/rps` - lost bets (vs bot or PvP)
- `/crime` - failure fines
- `/rob` - failure fines and anticheat counter-fines
The house is listed at **#0** on the leaderboard. Players can attempt to rob it via `/rob @TipiBOT` with special jackpot odds (35% success, 540% of the house balance).
---
### Earning coins
| Command | Cooldown | Base payout | Notes |
|---|---|---|---|
| `/daily` | 20h | 150 ⬡ | Streak multiplier applied (see below). Kõrvaklapid reduces cooldown to 18h. LAN Pilet doubles the reward. Botikoobas adds 5% interest on your balance (capped at 500 ⬡/day). |
| `/work` | 1h | 1575 ⬡ | Random job flavour text. Mängurihiir +50%, Reguleeritav laud +25% (stacks). Red Bull: 30% chance of ×3. Ultralai monitor reduces cooldown to 40min. |
| `/beg` | 5min | 1040 ⬡ | XL hiirematt reduces cooldown to 3min. Mehhaaniline klaviatuur multiplies earnings ×2. |
| `/crime` | 2h | 200500 ⬡ | 60% success rate (75% with CAT6). +30% earnings with Mikrofon on win. Fail = fine + 30min jail. Mänguritool skips jail on fail. |
### Daily streak
The streak increments each time you claim `/daily` within the cooldown window. Missing a day resets it to 1 **unless** you own the TipiLAN trofee item.
| Streak | Multiplier | Payout (base) |
|---|---|---|
| 12 days | ×1.0 | 150 ⬡ |
| 36 days | ×1.5 | 225 ⬡ |
| 713 days | ×2.0 | 300 ⬡ |
| 14+ days | ×3.0 | 450 ⬡ |
> With LAN Pilet (×2 daily) and a 14-day streak (×3.0) the base payout reaches **900 ⬡**. Add Botikoobas 5% interest on top.
---
### EXP & levels
Every successful economy action awards EXP:
| Action | EXP |
|---|---|
| `/daily` claimed | +50 |
| `/work` completed | +25 |
| `/crime` success | +15 |
| `/rob` success | +15 |
| Gambling win (`/roulette`, `/slots`, `/blackjack`) | Scaled by bet: <10⬡ = 0, 1099⬡ = +5, 100999⬡ = +10, 1 0009 999⬡ = +15, 10 00099 999⬡ = +20, 100 000+⬡ = +25 |
| `/beg` completed | +5 |
**Level formula:** `level = floor(√(total_exp ÷ 10))`
| Level | EXP required | Milestone |
|---|---|---|
| 1 | 10 | TipiNOOB role |
| 5 | 250 | TipiGRINDER role |
| 10 | 1 000 | TipiHUSTLER role · **T2 shop unlocks** |
| 20 | 4 000 | TipiCHAD role · **T3 shop unlocks** |
| 30 | 9 000 | TipiLEGEND role |
Use `/rank` to see your current EXP, level, progress bar to the next level, and leaderboard position.
### Level roles
Roles are assigned automatically on level-up and re-synced when you run `/rank`.
| Role | Min level |
|---|---|
| TipiNOOB | 1 |
| TipiGRINDER | 5 |
| TipiHUSTLER | 10 |
| TipiCHAD | 20 |
| TipiLEGEND | 30 |
The **ECONOMY** role is granted on your first EXP award (i.e. first successful economy command). Run `/economysetup` (admin) once to create all roles and position them correctly below the bot's own role.
---
### Gambling
| Command | Notes |
|---|---|
| `/roulette <panus> <värv>` | Red/black pays ×2 (≈50% each). Green pays ×14 at 1/37 chance. Lost bets go to the house. |
| `/slots <panus>` | 3 reels. **Pair** = +50% of bet. **Triple** = tiered by symbol rarity (×4 heart → ×15 skull, ×1.5 with 360hz monitor). **Jackpot** (3 karikas) = ×25 (×37 with 360hz monitor). Miss = lose bet. |
| `/blackjack <panus>` | Standard rules. Dealer stands on 17+. Natural blackjack pays 3:2. Double down on first action only. Split identical rank cards (one extra bet). Lost bets go to the house. |
| `/rps [panus]` | Rock Paper Scissors vs. the bot with optional bet. Bot picks randomly. |
| `/rps [panus] @vastane` | PvP duel - both players pick privately via DM, result posted to the server. Bet transferred to winner. |
### Social
| Command | Notes |
|---|---|
| `/rob @user` | 45% success (60% with Jellyfin). Target must have ≥100 ⬡. Steal 1025% of target's balance. Fail = fine of 100250 ⬡ to the house. Anticheat blocks the rob and fines the robber (2 charges per purchase). Cannot rob TipiBOT - use `/heist` instead. |
| `/heist` | Start a bank robbery. Solo or group (max 8). 5-minute join window. Success: 35% base + 5% per extra player (cap 65%). Win = split **2055%** of house balance equally. Fail = **1h 30min jail + ~15% balance fine** for all. 4h personal cooldown + 1h global server cooldown after each event. |
| `/give @user <summa>` | Transfer coins directly to another player. **Jailed users cannot use this command.** |
| `/request <summa> <põhjus> [@sihtmärk]` | Post a crowdfunding request. Anyone (or a specific target) can click **Rahasta** to contribute via `/give`. Expires in 5 minutes. |
### Info commands
| Command | Notes |
|---|---|
| `/balance [@user]` | Balance, daily streak, owned items (with Anticheat charges remaining), jail status if jailed. |
| `/rank [@user]` | EXP total, current level, progress bar to next level, leaderboard rank. |
| `/stats [@user]` | Lifetime statistics: economy totals, work/beg counts, gambling records, crime/heist history, social totals, best streak. |
| `/cooldowns` | All cooldowns at a glance with live Discord timestamps. Shows jail timer if jailed. |
| `/leaderboard` | Paginated coin leaderboard (10/page). House pinned at #0. ◀/▶ to browse; 📍 **Mina** jumps to your page. Has a separate EXP/level tab. |
| `/shop` | Browse all items by tier. Shows owned status, Anticheat charges remaining, and level lock for T2/T3. |
| `/buy <item>` | Purchase an item by name (partial match accepted). |
| `/reminders` | Toggle per-command DM notifications. **All reminders are on by default.** Bot DMs you the moment each cooldown expires. |
---
### Jail system
`/crime` fail (without Mänguritool) jails you for **30 minutes**. While jailed, `/work`, `/beg`, `/crime`, `/rob`, and `/give` are blocked.
#### `/jailbreak`
Roll two dice - matching values (doubles) free you instantly. **3 attempts** per jail sentence. If all 3 fail you pay bail:
- **2030% of your current balance** (scales with wealth)
- **Minimum 350 ⬡** - if your balance is below this you stay jailed until the timer runs out
Cooldowns and jail release times display as live Discord relative timestamps.
---
### Shop items
All items are **permanent** once purchased **except Anticheat**, which expires after 2 uses and can be repurchased.
#### Tier 1 - any level
| Item | Cost | Effect |
|---|---|---|
| Mängurihiir | 500 ⬡ | `/work` earns +50% |
| XL hiirematt | 600 ⬡ | `/beg` cooldown 5min → 3min |
| Anticheat | 750 ⬡ | Rob attempts against you fail and fine the robber. **2 uses**, then repurchase. |
| Red Bull | 800 ⬡ | `/work` has 30% chance to earn ×3 |
| Kõrvaklapid | 1 200 ⬡ | `/daily` cooldown 20h → 18h |
| LAN Pilet | 1 200 ⬡ | `/daily` reward ×2 |
| Botikoobas | 1 500 ⬡ | `/daily` adds 5% interest on balance (capped at 500 ⬡/day) |
#### Tier 2 - level 10 required (TipiHUSTLER+)
| Item | Cost | Effect |
|---|---|---|
| Mehhaaniline klaviatuur | 1 800 ⬡ | `/beg` earns ×2 |
| Ultralai monitor | 2 500 ⬡ | `/work` cooldown 1h → 40min |
| Mikrofon | 2 800 ⬡ | `/crime` win earns +30% |
| Reguleeritav laud | 3 500 ⬡ | `/work` earns +25% (stacks with Mängurihiir → ×1.875 combined) |
| CAT6 netikaabel | 3 500 ⬡ | `/crime` success rate 60% → 75% |
| Jellyfin server | 4 000 ⬡ | `/rob` success rate 45% → 60% |
#### Tier 3 - level 20 required (TipiCHAD+)
| Item | Cost | Effect |
|---|---|---|
| TipiLAN trofee | 6 000 ⬡ | Daily streak survives missed days |
| 360hz monitor | 7 500 ⬡ | Slots jackpot 10× → 15×, triple 4× → 6× |
| Mänguritool | 9 000 ⬡ | `/crime` fail never sends you to jail |
---
### Amount shortcuts
Commands that accept a coin amount (`/give`, `/roulette`, `/rps`, `/slots`, `/blackjack`) accept `"all"` as the amount to wager your entire balance.
### Custom emoji
Change `COIN` in `economy.py` to any Discord emoji string:
```python
COIN = "<:tipicoin:YOUR_EMOJI_ID>"
```
---
## Logging
All logs are written to the `logs/` directory (auto-created on startup).
| File | Rotation | Contents |
|---|---|---|
| `logs/bot.log` | 5 MB x 5 backups | All INFO+ events: commands, errors, member sync |
| `logs/transactions.log` | Daily, 30 days | Economy transactions only: every balance change with user, amount, new balance |
The terminal output is **colour-coded** by log level (green = INFO, yellow = WARNING, red = ERROR).
Every slash command invocation is logged with the user ID, display name, and all options passed.
---
## Project Structure
```
├── bot.py # Discord client, all slash commands, event handlers
├── economy.py # TipiCOIN business logic, constants (SHOP, COOLDOWNS, etc.)
├── pb_client.py # Async PocketBase REST client (auth + CRUD for economy_users)
├── strings.py # All user-facing strings, command descriptions, help text
├── member_sync.py # Role/nickname/birthday sync logic
├── sheets.py # Google Sheets read/write + in-memory cache
├── config.py # Environment variable loader
├── requirements.txt # Python dependencies
├── .env.example # Template for secrets
├── .env # Your secrets (gitignored)
├── credentials.json # Google service account key (gitignored)
├── docs/
│ ├── DEV_NOTES.md # Developer reference (architecture, checklists, constants)
│ ├── CHANGELOG.md # Version history
│ └── POCKETBASE_SETUP.md # PocketBase collection schema + setup instructions
├── scripts/
│ ├── migrate_to_pb.py # One-time migration: economy.json → PocketBase
│ └── add_stats_fields.py # Schema migration: add new fields to economy_users collection
├── data/
│ └── birthday_sent.json # Birthday dedup log (auto-created)
├── pb_data/ # PocketBase database files (auto-created, gitignored)
└── logs/
├── bot.log # General rotating log (auto-created)
└── transactions.log # Daily economy transaction log (auto-created)
```