Files
jigaido/SPEC.md
shokollm 4885be0752 fix: cleanup codebase and sync SPEC with actual permissions
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)
2026-04-09 10:01:02 +00:00

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. 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

{
  "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:

  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