Files
jigaido/SPEC.md
shokollm 8bb964fdd0 feat: Replace SQLite with per-user JSON storage (fixes #2)
- 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
2026-04-01 10:02:51 +00:00

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 as NULL.
  • 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:

  1. Single file per user — Personal bounties live in the creator's file. Group bounties also live in creator's file with group_id set.
  2. Bounty IDs are sequential integers per file — Not global. Each user's file has its own ID counter.
  3. Atomic writes — Uses tempfile + rename for safe writes.
  4. 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
  • link is optional
  • due_date is 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//delete by non-creator → " Group creator only."
  • /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