- Add storage.py with load_user(), save_user(), next_bounty_id() - Rewrite commands.py to use JSON storage (simplified) - Remove db.py, schema.sql, cron.py, test_db.py - Update SPEC.md to reflect new architecture - Admin model removed (anyone can add, creator only can edit/delete) - No reminders in v1
5.0 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.
- Group mode: Each Telegram group has its own bounty list. Anyone can add bounties. Only creator can edit/delete.
- DM mode: Personal bounty list. Anyone can manage their own bounties.
- Tracking: Users can track any bounty (group or personal) to their tracking list.
- Due dates: Free-form text (
"april 15","in 3 days","tomorrow") parsed at add time, stored as Unix timestamp. If unparseable, stored asNULL. - Links: Optional. If provided, stored with the bounty.
- Informed by: Every bounty stores the Telegram username of who posted/added it.
Architecture
Stack
- Bot:
python-telegram-bot(pure Python, no C extensions) - Storage: Per-user JSON files (zero-setup, no DB server)
- 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
│ ├── storage.py # JSON file storage
│ ├── data/
│ │ └── users/ # Per-user JSON files
│ │ └── {telegram_user_id}.json
│ ├── requirements.txt
│ └── .env.example
├── SPEC.md # This document
├── README.md
└── CONTRIBUTING.md
Storage Design
File structure (data/users/{telegram_user_id}.json):
{
"user_id": 123,
"username": "alice",
"personal_bounties": [
{
"id": 1,
"text": "Fix login bug",
"link": "https://github.com/example/repo/issues/1",
"due_date_ts": 1735689600,
"group_id": null,
"informed_by_username": "alice",
"created_at": 1735603200
}
],
"tracked_bounties": [
{"bounty_id": 5, "group_id": -1001, "created_at": 1735600000},
{"bounty_id": 3, "group_id": null, "created_at": 1735590000}
]
}
Key design decisions:
- Single file per user — Personal bounties live in the creator's file. Group bounties also live in creator's file with
group_idset. - Bounty IDs are sequential integers per file — Not global. Each user's file has its own ID counter.
- Atomic writes — Uses
tempfile+renamefor safe writes. - No reminders in v1 — Dropped for simplicity.
Commands
In Group
| Command | Who | Description |
|---|---|---|
/bounty |
anyone | List all bounties in this group |
/my |
anyone | List bounties tracked by you |
/add <text> [link] [due date] |
anyone | Add a new bounty to the group |
/update <bounty_id> [text] [link] [due_date] |
creator only | Update an existing bounty |
/delete <bounty_id> |
creator 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 |
In DM (1:1 with bot)
| Command | Description |
|---|---|
/bounty |
List all your personal bounties |
/my |
List bounties you're tracking |
/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 |
/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
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.
Stored as Unix timestamp. User-facing display can be localized/converted to any timezone at render time.
Error Handling
- Unknown command → help text with available commands
/update//deleteby non-creator → "⛔ Group creator only."/trackalready tracked → "Already tracking" (idempotent)/untracknot tracked → "Not tracking" (idempotent)- Bounty not found → "Bounty not found"
When to Revert to SQLite
- Multiple concurrent users with write conflicts
- Complex queries across users
- Reminder system with proper dedup
- Scale > 1,000 users
- Need ACID guarantees on concurrent writes