# JIGAIDO — Specification > Named after Nanami Kento's Cursed Technique restriction. Suppresses power during normal hours, exerts it during overtime. A bounty tracker that keeps you honest when the clock runs long. --- ## Overview JIGAIDO is a Telegram bot that lets groups and individuals track bounties — tasks, obligations, and deadlines — with optional due dates and personal tracking/reminders. - **Group mode**: Each Telegram group has its own bounty list. Only group admins can add/update/delete bounties. Any member can track/untrack. - **DM mode**: Personal bounty list. No admin restrictions — anyone can manage their own bounties. - **Tracking**: Users can add any bounty (group or DM) to their personal tracking list. - **Reminders**: Daily cron checks for due dates within 7 days and DMs the user. - **Due dates**: Free-form text (`"april 15"`, `"in 3 days"`, `"tomorrow"`) parsed at add time, stored as Unix timestamp. If unparseable, stored as `NULL` — no reminder. - **Links**: Optional. If provided, deduplicated per group (no two bounties in the same group can share the same link). Multiple links in one bounty: first link only, user can update later. - **Informed by**: Every bounty stores the Telegram username of who posted/added it (not who created the record — the person whose message triggered the add). --- ## Architecture ### Stack - **Bot**: `python-telegram-bot` (pure Python, no C extensions) - **Database**: SQLite (zero-install, single file) - **Date parsing**: `dateparser` - **Runtime**: Python 3.10+ - **Deployment**: Any $5 VPS with Python 3.10+ ### Directory Structure ``` jigaido/ ├── apps/ │ └── telegram-bot/ # Telegram bot app │ ├── bot.py # Entrypoint │ ├── commands.py # Command handlers │ ├── cron.py # Daily reminder job │ ├── db.py # SQLite wrapper │ ├── schema.sql # Database schema │ ├── requirements.txt │ └── .env.example ├── SPEC.md # This document ├── README.md └── CONTRIBUTING.md ``` --- ## Database Schema ```sql CREATE TABLE groups ( id INTEGER PRIMARY KEY AUTOINCREMENT, telegram_chat_id INTEGER UNIQUE NOT NULL, creator_user_id INTEGER NOT NULL, created_at INTEGER NOT NULL DEFAULT (unixepoch()) ); CREATE TABLE group_admins ( group_id INTEGER REFERENCES groups(id) ON DELETE CASCADE, user_id INTEGER NOT NULL, PRIMARY KEY (group_id, user_id) ); CREATE TABLE users ( id INTEGER PRIMARY KEY AUTOINCREMENT, telegram_user_id INTEGER UNIQUE NOT NULL, username TEXT, created_at INTEGER NOT NULL DEFAULT (unixepoch()) ); CREATE TABLE bounties ( id INTEGER PRIMARY KEY AUTOINCREMENT, group_id INTEGER REFERENCES groups(id) ON DELETE CASCADE, created_by_user_id INTEGER REFERENCES users(id), informed_by_username TEXT NOT NULL, text TEXT, link TEXT, due_date_ts INTEGER, created_at INTEGER NOT NULL DEFAULT (unixepoch()), UNIQUE(group_id, link) ); CREATE TABLE user_bounty_tracking ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, bounty_id INTEGER NOT NULL REFERENCES bounties(id) ON DELETE CASCADE, added_at INTEGER NOT NULL DEFAULT (unixepoch()), UNIQUE(user_id, bounty_id) ); CREATE TABLE reminder_log ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, bounty_id INTEGER NOT NULL REFERENCES bounties(id) ON DELETE CASCADE, reminded_at INTEGER NOT NULL DEFAULT (unixepoch()), UNIQUE(user_id, bounty_id) ); ``` ### Notes - `group_id = NULL` means a personal/DM bounty. - `UNIQUE(group_id, link)` — only enforced when `link IS NOT NULL` (SQLite treats NULL as distinct). - `reminder_log` dedup ensures a user only gets one reminder per bounty. --- ## Commands ### In Group | Command | Who | Description | |---|---|---| | `/bounty` | anyone | List all bounties in this group | | `/my` | anyone | List bounties tracked by you in this group | | `/add [link] [due date]` | admin only | Add a new bounty to the group | | `/update [text] [link] [due_date]` | admin only | Update an existing bounty | | `/delete ` | admin only | Delete a bounty | | `/track ` | anyone | Add a group bounty to your tracking | | `/untrack ` | anyone | Remove a bounty from your tracking | | `/admin_add ` | creator only | Promote a user to admin | | `/admin_remove ` | creator only | Demote an admin | ### In DM (1:1 with bot) | Command | Description | |---|---| | `/bounty` | List all your personal bounties | | `/my` | List all your tracked personal bounties | | `/add [link] [due date]` | Add a personal bounty | | `/update [text] [link] [due_date]` | Update a personal bounty | | `/delete ` | Delete a personal bounty (owner only) | | `/track ` | Add a personal bounty to your tracking | ### Add/Update Syntax ``` /add Some task description https://example.com in 3 days /add Just a text reminder /add https://link-only.example.com tomorrow ``` - `link` is optional - `due_date` is optional, free-form - If link already exists in group → rejected with error ### Tracking - `/track ` — works in both group and DM. In group: tracks a group bounty. In DM: tracks a personal bounty. - Users can track any bounty regardless of who created it. - A bounty can be tracked by multiple users. --- ## Informed By When a user triggers `/add`, the bot captures `message.from_user.username` and stores it in `informed_by_username`. This is displayed on bounty listings so the group/DM knows who posted or requested the bounty. --- ## Due Date Parsing Uses `dateparser` library. Examples: - `"april 15"`, `"April 15, 2026"` - `"in 3 days"` - `"tomorrow"` - `"2026-04-15"` - `"next friday"` If parsing fails → `due_date_ts = NULL`. No error is shown to user, reminder just won't fire. Stored as Unix timestamp. User-facing display can be localized/converted to any timezone at render time. --- ## Reminders (Cron) Runs daily (e.g., 09:00 local). For each user: 1. Find tracked bounties where `due_date_ts - now() < 7 days` 2. Exclude any already in `reminder_log` for that user 3. Send DM: `"Bounty '{title}' is due in {N} days."` 4. Insert into `reminder_log` Does not re-remind. If a bounty is 2 days away today, you get one message. Tomorrow you don't get another. --- ## Admin Management - **Creator**: The user who first added the bot to the group. Stored as `creator_user_id` in `groups`. Only the creator can run `/admin_add` and `/admin_remove`. - **Admins**: Added via `/admin_add `. Can add/update/delete any bounty in the group. Regular members can only track/untrack. - First admin assignment is automatic when the bot detects a new group. --- ## Error Handling - Unknown command → help text with available commands - `/add` with duplicate link in same group → rejection message - `/update`/`/delete` by non-admin → "Admin only" message - `/admin_add`/`/admin_remove` by non-creator → "Creator only" message - `/track` already tracked → "Already tracking" (idempotent, no error) - `/untrack` not tracked → "Not tracking" (idempotent, no error) - Bounty not found → "Bounty not found" - User not found → "User not found"