JIGAIDO is now a platform with apps/ as the container. All telegram-bot files moved to apps/telegram-bot/: - bot.py, commands.py, cron.py, db.py, schema.sql - requirements.txt, .env.example, README.md - Root now holds SPEC.md, README.md, CONTRIBUTING.md only. Structure: jigaido/ ├── apps/ │ └── telegram-bot/ └── SPEC.md, README.md, CONTRIBUTING.md
7.4 KiB
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 asNULL— 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
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 = NULLmeans a personal/DM bounty.UNIQUE(group_id, link)— only enforced whenlink IS NOT NULL(SQLite treats NULL as distinct).reminder_logdedup 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 <text> [link] [due date] |
admin only | Add a new bounty to the group |
/update <bounty_id> [text] [link] [due_date] |
admin only | Update an existing bounty |
/delete <bounty_id> |
admin only | Delete a bounty |
/track <bounty_id> |
anyone | Add a group bounty to your tracking |
/untrack <bounty_id> |
anyone | Remove a bounty from your tracking |
/admin_add <user> |
creator only | Promote a user to admin |
/admin_remove <user> |
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 <text> [link] [due date] |
Add a personal bounty |
/update <bounty_id> [text] [link] [due_date] |
Update a personal bounty |
/delete <bounty_id> |
Delete a personal bounty (owner only) |
/track <bounty_id> |
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
linkis optionaldue_dateis optional, free-form- If link already exists in group → rejected with error
Tracking
/track <bounty_id>— 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:
- Find tracked bounties where
due_date_ts - now() < 7 days - Exclude any already in
reminder_logfor that user - Send DM:
"Bounty '{title}' is due in {N} days." - 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_idingroups. Only the creator can run/admin_addand/admin_remove. - Admins: Added via
/admin_add <username>. 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
/addwith duplicate link in same group → rejection message/update//deleteby non-admin → "Admin only" message/admin_add//admin_removeby non-creator → "Creator only" message/trackalready tracked → "Already tracking" (idempotent, no error)/untracknot tracked → "Not tracking" (idempotent, no error)- Bounty not found → "Bounty not found"
- User not found → "User not found"