- Delete apps/telegram-bot/storage.py (replaced by adapters/storage/json_file.py) - Delete apps/telegram-bot/__init__.py (empty file) - Delete apps/telegram-bot/requirements-dev.txt (dev deps in pyproject.toml) - Update SPEC.md with new hexagonal architecture (core, adapters, apps) - Update SPEC.md command reference: /update -> /edit - Update README.md with new project structure and quick start - Update CONTRIBUTING.md with new architecture and dev setup
5.5 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 user ID of who posted/added it.
Architecture
Stack
- Bot:
python-telegram-bot(pure Python, no C extensions) - Storage: Per-group JSON files via
adapters/storage/json_file.py - Date parsing:
dateparser - Runtime: Python 3.10+
- Deployment: Any $5 VPS with Python 3.10+
Directory Structure
jigaido/
├── core/ # Domain layer
│ ├── models.py # Domain dataclasses (Bounty, Tracking)
│ ├── ports.py # Port interfaces (abstract base classes)
│ └── services.py # Domain services (BountyService, TrackingService)
├── adapters/ # Infrastructure adapters
│ └── storage/
│ └── json_file.py # JSON file storage implementation
├── apps/
│ └── telegram-bot/ # Telegram bot CLI application
│ ├── bot.py # Bot entry point
│ └── commands.py # Command handlers
├── config.py # Configuration management
└── tests/ # Unit tests
Hexagonal Architecture
-
Core (
core/): Pure domain logic, no external dependenciesmodels.py: Domain dataclassesports.py: Abstract interfaces for storageservices.py: Business logic
-
Adapters (
adapters/): Implementations of portsstorage/json_file.py: JSON file-based storage
-
Apps (
apps/): CLI applicationstelegram-bot/: Telegram bot interface
Data Storage
Data is stored at ~/.jigaido/ (home directory), NOT inside the repository.
File: ~/.jigaido/{group_id}/group.json
{
"bounties": [
{
"id": 1,
"created_by_user_id": 123456,
"text": "Fix login bug",
"link": "https://github.com/example/repo/issues/1",
"due_date_ts": 1735689600,
"created_at": 1735603200
}
],
"next_id": 2
}
File: ~/.jigaido/{group_id}/{user_id}.json
{
"tracked": [
{"bounty_id": 1, "created_at": 1735600000}
]
}
File: ~/.jigaido/{user_id}/user.json
{
"bounties": [
{
"id": 1,
"text": "Personal task",
"link": null,
"due_date_ts": 1735689600,
"created_at": 1735603200
}
],
"next_id": 2
}
Key design decisions:
- Hexagonal architecture — Core domain is isolated from infrastructure
- Group-isolated storage — Each group has its own directory. No cross-group access.
- Bounty IDs are sequential per group.json — Not global. Each group's file has its own ID counter.
- Atomic writes — Uses
tempfile+renamefor safe writes.
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] |
anyone | Add a new bounty to the group |
/edit <bounty_id> [text] [link] [due_date] |
creator only | Edit an existing bounty |
/delete <bounty_id> |
creator only | Delete a bounty |
/track <bounty_id> |
anyone | Track a group bounty |
/untrack <bounty_id> |
anyone | Stop tracking a bounty |
In DM (1:1 with bot)
| Command | Description |
|---|---|
/bounty |
List all your personal bounties |
/my |
List all your personal bounties |
/add <text> [link] [due date] |
Add a personal bounty |
/edit <bounty_id> [text] [link] [due_date] |
Edit a personal bounty |
/delete <bounty_id> |
Delete a personal bounty |
/track <bounty_id> |
Track a personal bounty |
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
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
/edit//deleteby non-creator → "⛔ Only the creator can edit/delete this bounty."/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