Initial commit

This commit is contained in:
AlacrisDevs
2026-03-20 17:35:35 +02:00
commit e1415fc5ac
27 changed files with 16667 additions and 0 deletions

429
README.md Normal file
View File

@@ -0,0 +1,429 @@
# 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)
```