Phase 1: Ruff lint fixes - Remove unused imports across all files - Remove unused variables (now_utc, tz, ctx) - Fix f-string without placeholders - Fix E402 import order with noqa comments Phase 2: Remove confusing hard delete from storage - Removed delete_bounty() from RoomStorage Protocol (never used by app) - Removed delete_bounty() from JsonFileRoomStorage (was hard delete) - Removed corresponding tests (hard delete was never used) Phase 3: Sync SPEC.md with actual code behavior - Updated overview: admins can add/edit/delete (not 'anyone' + 'creator') - Updated command table: /add, /edit, /delete are admin only - Updated error handling messages Test results: 96 passed (2 hard delete tests removed)
186 lines
5.5 KiB
Markdown
186 lines
5.5 KiB
Markdown
# 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. Admins can add/edit/delete bounties. Anyone can track.
|
|
- **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 as `NULL`.
|
|
- **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 dependencies
|
|
- `models.py`: Domain dataclasses
|
|
- `ports.py`: Abstract interfaces for storage
|
|
- `services.py`: Business logic
|
|
|
|
- **Adapters** (`adapters/`): Implementations of ports
|
|
- `storage/json_file.py`: JSON file-based storage
|
|
|
|
- **Apps** (`apps/`): CLI applications
|
|
- `telegram-bot/`: Telegram bot interface
|
|
|
|
### Data Storage
|
|
|
|
Data is stored at `~/.jigaido/` (home directory), NOT inside the repository.
|
|
|
|
**File: `~/.jigaido/{group_id}/group.json`**
|
|
|
|
```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`**
|
|
|
|
```json
|
|
{
|
|
"tracked": [
|
|
{"bounty_id": 1, "created_at": 1735600000}
|
|
]
|
|
}
|
|
```
|
|
|
|
**File: `~/.jigaido/{user_id}/user.json`**
|
|
|
|
```json
|
|
{
|
|
"bounties": [
|
|
{
|
|
"id": 1,
|
|
"text": "Personal task",
|
|
"link": null,
|
|
"due_date_ts": 1735689600,
|
|
"created_at": 1735603200
|
|
}
|
|
],
|
|
"next_id": 2
|
|
}
|
|
```
|
|
|
|
**Key design decisions:**
|
|
|
|
1. **Hexagonal architecture** — Core domain is isolated from infrastructure
|
|
2. **Group-isolated storage** — Each group has its own directory. No cross-group access.
|
|
3. **Bounty IDs are sequential per group.json** — Not global. Each group's file has its own ID counter.
|
|
4. **Atomic writes** — Uses `tempfile` + `rename` for 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]` | admin only | Add a new bounty to the group |
|
|
| `/edit <bounty_id> [text] [link] [due_date]` | admin only | Edit an existing bounty |
|
|
| `/delete <bounty_id>` | admin 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
|
|
```
|
|
|
|
- `link` is optional
|
|
- `due_date` is 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
|
|
- `/add`/`/edit`/`/delete` by non-admin → "⛔ Only admins can add/edit/delete bounties."
|
|
- `/track` already tracked → "Already tracking" (idempotent)
|
|
- `/untrack` not 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 |