18 KiB
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
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
- Google Cloud Console → create/select project
- Enable Google Sheets API and Google Drive API
- Credentials → Create Credentials → Service Account
- Download JSON key → save as
credentials.jsonin the project root - 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.
- Download
pocketbase.exe(Windows) from https://pocketbase.io/docs/ and place it in the project root. - Start PocketBase:
.\pocketbase.exe serve - Open the admin UI at http://127.0.0.1:8090/_/ and create a superuser account.
- Create a collection named
economy_users- seedocs/POCKETBASE_SETUP.mdfor the full schema. - Set
PB_URL,PB_ADMIN_EMAIL,PB_ADMIN_PASSWORDin.env. - 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.exeandpb_data/are gitignored.
5. Environment Variables
# 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
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
Discordusername andUser IDpre-filled. Admin fills in the rest, then runs/check
Matching logic
- Match by User ID (numeric, reliable - IDs never change)
- Fall back to Discord username (case-insensitive) if no ID yet
- 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, 5–40% 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 | 15–75 ⬡ | 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 | 10–40 ⬡ | XL hiirematt reduces cooldown to 3min. Mehhaaniline klaviatuur multiplies earnings ×2. |
/crime |
2h | 200–500 ⬡ | 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) |
|---|---|---|
| 1–2 days | ×1.0 | 150 ⬡ |
| 3–6 days | ×1.5 | 225 ⬡ |
| 7–13 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, 10–99⬡ = +5, 100–999⬡ = +10, 1 000–9 999⬡ = +15, 10 000–99 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 10–25% of target's balance. Fail = fine of 100–250 ⬡ 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 20–55% 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:
- 20–30% 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:
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)