126 Commits

Author SHA1 Message Date
54a41a7250 Merge pull request 'fix: add sort and limit to /my command (#94)' (#95) from fix/my-sort-limit into main 2026-04-09 16:20:19 +02:00
shokollm
3743dc6a45 fix: add sort and limit to /my command for consistency with /bounty
- Add same sort_key logic as /bounty (due date first, then created_at)
- Add default limit of 5 with "Showing X of Y" message
- Add "all" flag to show expired bounties
- Add delete button for consistency with /bounty

Fixes #94
2026-04-09 14:08:57 +00:00
7e0bc1f8a3 Merge pull request 'test: Category Feature - Tests (#88)' (#93) from feature/category-tests into main 2026-04-09 13:17:01 +02:00
shokollm
961adf103b test: add category feature tests
Tests for category feature (Issue #88):

Category Management Tests (11 tests):
- test_add_category_requires_admin
- test_add_category_duplicate_slug_fails
- test_add_category_invalid_slug_fails_uppercase
- test_add_category_invalid_slug_fails_with_numbers
- test_add_category_invalid_slug_fails_with_symbols
- test_add_category_invalid_slug_fails_empty
- test_add_category_valid
- test_delete_category_soft_deletes
- test_deleted_category_not_listed
- test_list_categories_empty
- test_list_categories_returns_active
- test_get_category_not_found
- test_get_category_deleted_returns_none
- test_add_category_requires_admin_non_existent_room

Category-to-Bounty Tests (10 tests):
- test_add_category_to_bounty
- test_add_duplicate_category_to_bounty_noop
- test_add_category_to_bounty_invalid_bounty
- test_add_category_to_bounty_invalid_category
- test_remove_category_from_bounty
- test_remove_category_not_on_bounty_returns_false
- test_update_bounty_categories_replace_all
- test_update_bounty_categories_clear_all
- test_update_bounty_categories_validates
- test_add_category_to_bounty_requires_admin
- test_remove_category_from_bounty_requires_admin
- test_update_bounty_categories_requires_admin
- test_delete_category_requires_admin

All 123 tests pass (96 original + 27 new)
2026-04-09 11:08:46 +00:00
e9d7ba0c8e Merge pull request 'feat: Category Feature - Commands (#87)' (#92) from feature/category-commands into main 2026-04-09 13:06:36 +02:00
shokollm
e43c36b84f feat: add category command handlers
Commands layer for category feature (Issue #87):

New Commands:
- /category - list all categories
- /category add <slug> <name> - create category (admin)
- /category delete <slug> - soft delete category (admin)

Updated Commands:
- /add - supports -cat <slug>[,<slug>] flag
- /update - supports -cat, -cat -, and -remove-cat flags
- /bounty - supports -c <slug>[,<slug>] filter
- /show - displays categories for bounty
- /help - includes category commands

Syntax Examples:
- /add Fix bug github.com/repo -cat bug,urgent
- /update 1 -cat feature,docs
- /update 1 -cat - (clear categories)
- /update 1 -remove-cat bug
- /bounty -c bug
- /bounty -c bug,feature
2026-04-09 10:50:50 +00:00
44680dcb4c Merge pull request 'feat: Category Feature - Service Layer (#86)' (#91) from feature/category-service into main 2026-04-09 12:44:18 +02:00
shokollm
7c17bff110 feat: add category service layer methods
Service layer for category feature (Issue #86):

Category Management:
- add_category() - Create category (admin only, validates slug format)
- delete_category() - Soft delete category (admin only)
- list_categories() - List active categories
- get_category() - Get category by slug

Category-to-Bounty Association:
- add_category_to_bounty() - Add category to bounty (admin only)
- remove_category_from_bounty() - Remove category from bounty (admin only)
- update_bounty_categories() - Replace all categories on bounty (admin only)

All methods properly validate permissions, slug format, and existence.
Soft delete preserves category data for bounties that reference it.
2026-04-09 10:38:08 +00:00
43d07eae92 Merge pull request 'feat: Category Feature - Models & Storage (#85)' (#90) from feature/category-models-storage into main 2026-04-09 12:35:50 +02:00
shokollm
66d2a9fb86 feat: add Category model and update storage for categories
Models & Storage layer for category feature (Issue #85):

- Add Category dataclass to core/models.py
  - id (slug): lowercase alphabetic only
  - name: display name
  - created_at: Unix timestamp
  - deleted_at: soft delete timestamp (None if active)

- Add category_ids field to Bounty dataclass
  - list[str] for multiple categories per bounty
  - Default empty list for backward compatibility

- Add categories field to RoomData dataclass
  - list[Category] for room-level categories
  - Default empty list

- Update JsonFileRoomStorage to serialize/deserialize:
  - Category fields (id, name, created_at, deleted_at)
  - Bounty.category_ids
  - RoomData.categories

Backward compatible: existing data without categories works fine.
2026-04-09 10:23:33 +00:00
shokollm
235a89653f test: add pytest.ini for easier test running
- Set testpaths = tests
- Set pythonpath = . (no need for PYTHONPATH=. prefix)
- Set asyncio_default_fixture_loop_scope = function
2026-04-09 10:15:27 +00:00
shokollm
2158f71fd0 feat: normalize URLs without scheme to https://
- Add normalize_url() helper function in commands.py
- Automatically prefix URLs without scheme (e.g. github.com → https://github.com)
- Applies to both -link flag and auto-detected URLs
- Add 5 new tests for URL normalization
- Fix existing tests to handle 5-value return from parse_args()

Examples:
  /add Fix bug github.com/user/repo
  → stored as: https://github.com/user/repo
2026-04-09 10:10:06 +00:00
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
shokollm
75122b3ee2 docs: add audit and category spec from feynman 2026-04-09 09:30:42 +00:00
shokollm
8494b4621c feat: switch admin identification from user_id to username
- Replace admin_user_ids (list[int]) with admin_usernames (list[str])
- Update all service methods to use username for permission checks
- Add delete button to bot responses for message cleanup
- Update tests to match new implementation

Note: Breaking change - existing data files need fresh start
2026-04-09 08:02:36 +00:00
shokollm
7822e65d6c debug: add logging to _find_user_id_by_username
To see the actual error when user lookup fails.
0.1.0
2026-04-05 02:34:47 +00:00
shokollm
c57b422b6a fix: use Telegram API to lookup users by username
_find_user_id_by_username now uses ctx.bot.get_chat() to lookup
any user by username, not just bounty creators. This allows
/admin add to work for any user in the group.
2026-04-05 02:31:38 +00:00
shokollm
1db8e48414 feat: use HTML links for admin list
Now /admin shows usernames as clickable tg://user?id= links,
same format as /show command. Uses HTML parse mode.
2026-04-05 02:26:10 +00:00
shokollm
1bde18589c fix: unify date format in add and edit confirmations
Now uses format_due_date() for edit confirmation dates,
matching the add confirmation format: "5 April 2026 13:00 (Asia/Jakarta)"
2026-04-05 02:18:45 +00:00
shokollm
22c7d8f4f5 fix: define tz variable in cmd_update before using it
The cmd_update function was missing the tz = ZoneInfo(timezone_str) conversion.
This caused "tz is not defined" error when confirming edits.
2026-04-05 02:08:01 +00:00
shokollm
b85806f3ad fix: use room timezone in edit confirmation dates
The edit confirmation was using time.localtime() (server timezone).
Now uses datetime.fromtimestamp() with room's timezone.
2026-04-05 02:00:13 +00:00
shokollm
442e5279cc fix: properly detect flags after -link and -date
Previously, /edit 28 -link -date would treat -date as the link value.
Now it checks if the next argument starts with "-" to detect flags.
2026-04-05 01:57:56 +00:00
shokollm
6c99751827 fix: remove catch-all handler for unknown commands
Unknown commands like /asdasda no longer show help.
Only known commands (with explicit handlers) respond.
2026-04-05 01:49:58 +00:00
shokollm
e570acff4f fix: add created_by_username parameter to BountyService.add_bounty
The add_bounty method now accepts created_by_username parameter
to store the creator's username for display purposes.
2026-04-05 01:41:02 +00:00
shokollm
8cc7b09716 fix: only show user in /show if username is stored
Now only shows the creator line if created_by_username exists,
no fallback to "User {id}" which could be confusing.
2026-04-05 01:39:29 +00:00
shokollm
a1946e4c4e feat: store username when creating bounty for display
When adding a bounty, capture effective_user's username or first_name
and store it for display in /show. Now shows actual name instead of
just "User".
2026-04-05 01:36:30 +00:00
shokollm
db1369c004 feat: use HTML parse mode for user link in /show
Now using <a href="tg://user?id=XXX">User</a> with HTML parse mode
for clickable user link without pinging.
2026-04-05 01:28:07 +00:00
shokollm
17b022fc6c fix: show creator as clickable user link without pinging
Changed from @user#1663194938 to using tg://user?id=XXX link format.
This creates a clickable link to the user's DM without sending a ping.
2026-04-05 00:53:25 +00:00
shokollm
cecf71da5d fix: parse dates using group's timezone when adding/updating bounties
The parse_args function now accepts timezone_str parameter and uses it
to localize parsed dates. This ensures dates are interpreted in the
group's timezone, not the server's local timezone.
2026-04-05 00:25:59 +00:00
shokollm
58614fbda2 fix: use group timezone in bounty list and show command
1. format_bounty now accepts timezone_str parameter
2. Calculate hours/days remaining using group's timezone
3. Format dates using group's timezone (not server local time)
4. Updated cmd_bounty, cmd_my, cmd_show to pass timezone
2026-04-05 00:06:59 +00:00
shokollm
ec29e4f15f feat: add error handler to bot for better error logging
This will help catch and log errors that were previously
showing as "No error handlers are registered"
2026-04-04 23:59:07 +00:00
shokollm
858305ebac fix: show time in date display and bounty list
1. format_bounty now shows time (HH:MM) if time is set
2. Bounty list shows hours remaining if < 48 hours:
   - < 1 hour: shows minutes (e.g., 45m)
   - < 48 hours: shows hours (e.g., 6h)
   - >= 48 hours: shows days (e.g., 3d)
3. Update message now shows time in date display
2026-04-04 23:54:34 +00:00
shokollm
a76aab657f fix: properly parse time (HH:MM) after date
The parse_args function now:
1. Recognizes time format (HH:MM) after parsing a date
2. Combines date + time into a single timestamp
3. Only text comes before link or date flags

Now /update 6 -date 2026-04-05 12:00 properly sets date+time
2026-04-04 23:48:48 +00:00
shokollm
cfe5f019f2 fix: single delete button per message, use query.message.delete()
- /bounty now shows only ONE delete button (not per-bounty)
- Callback uses query.message.delete() instead of delete_message by ID
- This is more reliable and simpler
2026-04-04 23:45:19 +00:00
shokollm
7b64da7897 fix: -link with any value sets link regardless of URL format
Previously, /update 2 -link s would clear the link and then parse
's' as a date. Now, any value after -link is used as the link,
regardless of whether it looks like a URL.
2026-04-04 23:42:15 +00:00
shokollm
7c238b44c8 fix: accept domain-only URLs like google.com
The is_url() function now accepts any string with a dot and no spaces,
not just URLs with http:// or containing /. This allows /update 2 -link google.com
to properly set the link instead of treating google.com as text.
2026-04-04 23:35:22 +00:00
shokollm
7a4d938c41 fix: /edit command improvements
1. Accept any URL-like string as link (not just http/https)
   - Now detects URLs by pattern: contains "." and "/" (e.g., github.com/foo)

2. Show old -> new changes in update response
   - Now shows exactly what changed for verification
   - Helps user catch mistakes immediately
2026-04-04 23:29:03 +00:00
shokollm
dfafefe071 feat: add inline delete button to /bounty list
- Add inline keyboard with delete button on bounty list messages
- Only the user who triggered the command can delete the message
- Message is actually removed from the chat
- Uses callback query handler for button clicks
2026-04-04 23:25:20 +00:00
shokollm
d01d147a45 refactor: reorganize help command with admin/non-admin split
Non-admin sees minimal commands: bounty, track, untrack, my, show, start, help
Admin sees organized by category: Bounty Management, Tracking, Room Management
2026-04-04 23:11:41 +00:00
shokollm
a667ba216a refactor: simplify help command
- Show only top-level commands without variations
- Show admin-specific commands only to admins
- Reduces cognitive overhead for normal users
2026-04-04 23:07:58 +00:00
shokollm
badb2e3292 fix: handle admin_user_ids=None case in add_admin
When loading room data with admin_user_ids=null in JSON,
the code now properly initializes admin_user_ids to []
instead of incorrectly creating a new RoomData.

Also removed debug logging added during troubleshooting.
2026-04-04 22:59:37 +00:00
shokollm
6e5006b429 debug: add detailed logging to add_admin for troubleshooting 2026-04-04 16:08:46 +00:00
shokollm
cce71e55c2 debug: add logging to cmd_start for troubleshooting admin promotion
Adding logging to understand why group creator admin promotion
may not be working in production.
2026-04-04 16:01:24 +00:00
shokollm
8ac8cd21ec fix: allow self-promotion to first admin in room
Users can now add themselves as the first admin without existing
admin permission. This enables /start in DMs to work correctly.
2026-04-04 15:34:58 +00:00
shokollm
d75f897043 feat: auto-promote group creator AND DM user to admin
- Groups: group creator auto-promoted to admin via /start
- DMs: user automatically becomes admin of their own DM
2026-04-04 15:30:27 +00:00
shokollm
0260cae40b feat: auto-promote group creator to admin
When /start is called in a group, check if user is the group creator
and automatically add them as admin. DMs don't need admin concept.
2026-04-04 15:24:47 +00:00
shokollm
408318d323 feat: bot reads JIGAIDO_BOT_TOKEN from config file
- config.py: Added _resolve_bot_token() to read from config file
- bot.py: Uses config.config.bot_token instead of env var directly
- test_config.py: Added test for config file token reading
2026-04-04 15:16:55 +00:00
5502de96ad Merge pull request 'feat: implement /recover command and fix /admin list' (#83) from fix/issue-49-50-recover-admin-list into main 2026-04-04 16:42:07 +02:00
shokollm
6a933742cb feat: implement /recover command and fix /admin list
- Add /recover command for listing and recovering soft-deleted bounties
  - /recover - list recoverable bounties (admin only)
  - /recover <id> [<id>...] - recover specific bounties (admin only)
- Fix /admin list to show @username instead of admin_id
- Add recover_bounty and recover_bounties methods to BountyService
- Add get_deleted_bounty method to BountyService
- Clean up duplicate cmd_admin functions
- Add /recover to bot command menu
- Fixes #49 and #50
2026-04-04 14:29:19 +00:00
b554a81979 Merge pull request 'feat: human-readable date format with timezone awareness (#54)' (#80) from fix/issue-54-v2 into main 2026-04-04 15:37:20 +02:00