Files
jigaido/SPEC.md
shokollm 9f0ad2d404 Refactor to apps/ structure
JIGAIDO is now a platform with apps/ as the container.
All telegram-bot files moved to apps/telegram-bot/:
- bot.py, commands.py, cron.py, db.py, schema.sql
- requirements.txt, .env.example, README.md
- Root now holds SPEC.md, README.md, CONTRIBUTING.md only.

Structure:
jigaido/
├── apps/
│   └── telegram-bot/
└── SPEC.md, README.md, CONTRIBUTING.md
2026-04-01 08:05:10 +00:00

7.4 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/reminders.

  • Group mode: Each Telegram group has its own bounty list. Only group admins can add/update/delete bounties. Any member can track/untrack.
  • DM mode: Personal bounty list. No admin restrictions — anyone can manage their own bounties.
  • Tracking: Users can add any bounty (group or DM) to their personal tracking list.
  • Reminders: Daily cron checks for due dates within 7 days and DMs the user.
  • Due dates: Free-form text ("april 15", "in 3 days", "tomorrow") parsed at add time, stored as Unix timestamp. If unparseable, stored as NULL — no reminder.
  • Links: Optional. If provided, deduplicated per group (no two bounties in the same group can share the same link). Multiple links in one bounty: first link only, user can update later.
  • Informed by: Every bounty stores the Telegram username of who posted/added it (not who created the record — the person whose message triggered the add).

Architecture

Stack

  • Bot: python-telegram-bot (pure Python, no C extensions)
  • Database: SQLite (zero-install, single file)
  • 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
│       ├── cron.py      # Daily reminder job
│       ├── db.py        # SQLite wrapper
│       ├── schema.sql   # Database schema
│       ├── requirements.txt
│       └── .env.example
├── SPEC.md          # This document
├── README.md
└── CONTRIBUTING.md

Database Schema

CREATE TABLE groups (
    id              INTEGER PRIMARY KEY AUTOINCREMENT,
    telegram_chat_id INTEGER UNIQUE NOT NULL,
    creator_user_id  INTEGER NOT NULL,
    created_at       INTEGER NOT NULL DEFAULT (unixepoch())
);

CREATE TABLE group_admins (
    group_id  INTEGER REFERENCES groups(id) ON DELETE CASCADE,
    user_id   INTEGER NOT NULL,
    PRIMARY KEY (group_id, user_id)
);

CREATE TABLE users (
    id               INTEGER PRIMARY KEY AUTOINCREMENT,
    telegram_user_id  INTEGER UNIQUE NOT NULL,
    username          TEXT,
    created_at        INTEGER NOT NULL DEFAULT (unixepoch())
);

CREATE TABLE bounties (
    id                   INTEGER PRIMARY KEY AUTOINCREMENT,
    group_id              INTEGER REFERENCES groups(id) ON DELETE CASCADE,
    created_by_user_id    INTEGER REFERENCES users(id),
    informed_by_username  TEXT NOT NULL,
    text                  TEXT,
    link                  TEXT,
    due_date_ts           INTEGER,
    created_at            INTEGER NOT NULL DEFAULT (unixepoch()),
    UNIQUE(group_id, link)
);

CREATE TABLE user_bounty_tracking (
    id         INTEGER PRIMARY KEY AUTOINCREMENT,
    user_id    INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
    bounty_id  INTEGER NOT NULL REFERENCES bounties(id) ON DELETE CASCADE,
    added_at   INTEGER NOT NULL DEFAULT (unixepoch()),
    UNIQUE(user_id, bounty_id)
);

CREATE TABLE reminder_log (
    id          INTEGER PRIMARY KEY AUTOINCREMENT,
    user_id     INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
    bounty_id   INTEGER NOT NULL REFERENCES bounties(id) ON DELETE CASCADE,
    reminded_at INTEGER NOT NULL DEFAULT (unixepoch()),
    UNIQUE(user_id, bounty_id)
);

Notes

  • group_id = NULL means a personal/DM bounty.
  • UNIQUE(group_id, link) — only enforced when link IS NOT NULL (SQLite treats NULL as distinct).
  • reminder_log dedup ensures a user only gets one reminder per bounty.

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
/update <bounty_id> [text] [link] [due_date] admin only Update an existing bounty
/delete <bounty_id> admin 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
/admin_add <user> creator only Promote a user to admin
/admin_remove <user> creator only Demote an admin

In DM (1:1 with bot)

Command Description
/bounty List all your personal bounties
/my List all your tracked personal bounties
/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 (owner only)
/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
  • If link already exists in group → rejected with error

Tracking

  • /track <bounty_id> — works in both group and DM. In group: tracks a group bounty. In DM: tracks a personal bounty.
  • Users can track any bounty regardless of who created it.
  • A bounty can be tracked by multiple users.

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, reminder just won't fire.

Stored as Unix timestamp. User-facing display can be localized/converted to any timezone at render time.


Reminders (Cron)

Runs daily (e.g., 09:00 local). For each user:

  1. Find tracked bounties where due_date_ts - now() < 7 days
  2. Exclude any already in reminder_log for that user
  3. Send DM: "Bounty '{title}' is due in {N} days."
  4. Insert into reminder_log

Does not re-remind. If a bounty is 2 days away today, you get one message. Tomorrow you don't get another.


Admin Management

  • Creator: The user who first added the bot to the group. Stored as creator_user_id in groups. Only the creator can run /admin_add and /admin_remove.
  • Admins: Added via /admin_add <username>. Can add/update/delete any bounty in the group. Regular members can only track/untrack.
  • First admin assignment is automatic when the bot detects a new group.

Error Handling

  • Unknown command → help text with available commands
  • /add with duplicate link in same group → rejection message
  • /update//delete by non-admin → "Admin only" message
  • /admin_add//admin_remove by non-creator → "Creator only" message
  • /track already tracked → "Already tracking" (idempotent, no error)
  • /untrack not tracked → "Not tracking" (idempotent, no error)
  • Bounty not found → "Bounty not found"
  • User not found → "User not found"