2026-03-21 11:04:31 +02:00
2026-03-20 17:35:35 +02:00
2026-03-20 17:35:35 +02:00
2026-03-20 17:35:35 +02:00
2026-03-20 17:35:35 +02:00
2026-03-20 17:35:35 +02:00
2026-03-20 17:35:35 +02:00
2026-03-20 17:35:35 +02:00
2026-03-20 17:35:35 +02:00
2026-03-20 17:35:35 +02:00
2026-03-20 17:35:35 +02:00
2026-03-20 17:35:35 +02:00
2026-03-20 17:35:35 +02:00
2026-03-20 17:35:35 +02:00
2026-03-20 17:35:35 +02:00
2026-03-20 17:35:35 +02:00
2026-03-20 17:35:35 +02:00
2026-03-21 11:04:31 +02:00

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
  2. Member Management
  3. Admin Commands
  4. Birthday System
  5. TipiCOIN Economy
  6. 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 → 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 TammMari-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.

  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

# 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 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:

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)
Description
TipiBOT
Readme 371 KiB
Languages
Python 100%