From d38d47fb796bc72e9449233606265eaf0d844d04 Mon Sep 17 00:00:00 2001 From: shokollm <270575765+shokollm@users.noreply.github.com> Date: Sat, 4 Apr 2026 05:54:56 +0000 Subject: [PATCH] feat(/bounty): add pagination, sorting, and filtering - Default shows 5 bounties per page - /bounty 10 - show 10 bounties - /bounty all - show all active (exclude overdue >24h) - /bounty all 10 - show 10 including expired Filtering: - Overdue >24h filtered out by default - 'all' flag includes overdue Sorting: - Bounties with due date sorted by due_date_ts (earliest first) - Bounties without due date shown last, sorted by created_at Output format updated: - Header shows 'Showing X of Y bounties' - Description sliced to 40 chars when showing pagination info - Date format changed to '4 Apr 2026' style Fixes #48 --- apps/telegram-bot/commands.py | 64 ++++++++++++++++++++++++++++++----- 1 file changed, 56 insertions(+), 8 deletions(-) diff --git a/apps/telegram-bot/commands.py b/apps/telegram-bot/commands.py index b0ae05e..e12bd62 100644 --- a/apps/telegram-bot/commands.py +++ b/apps/telegram-bot/commands.py @@ -48,21 +48,24 @@ def parse_args(args: list[str]) -> tuple[Optional[str], Optional[str], Optional[ return text, link, due_date_ts -def format_bounty(b, show_id: bool = True) -> str: +def format_bounty(b, show_id: bool = True, slice_length: int = 0) -> str: parts = [] if show_id: parts.append(f"[#{b.id}]") if b.text: - parts.append(b.text) + text = b.text + if slice_length > 0 and len(text) > slice_length: + text = text[:slice_length] + "..." + parts.append(text) if b.link: parts.append(f"🔗 {b.link}") if b.due_date_ts: - due_str = time.strftime("%Y-%m-%d", time.localtime(b.due_date_ts)) + due_str = time.strftime("%d %b %Y", time.localtime(b.due_date_ts)) days_left = (b.due_date_ts - int(time.time())) // 86400 if days_left < 0: parts.append(f"⏰ {due_str} (OVERDUE)") elif days_left == 0: - parts.append(f"⏰ {due_str} (TODAY)") + parts.append(f"⏰ Today (OVERDUE)") else: parts.append(f"⏰ {due_str} ({days_left}d)") if b.created_by_user_id: @@ -95,13 +98,58 @@ def get_room_id(update: Update) -> int: async def cmd_bounty(update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None: room_id = get_room_id(update) - bounties = BOUNTY_SERVICE.list_bounties(room_id) + args = extract_args(update.message.text) - if not bounties: - await update.message.reply_text("No bounties yet.") + show_all = "all" in args + args = [a for a in args if a != "all"] + + try: + limit = int(args[0]) if args else 5 + except (ValueError, IndexError): + limit = 5 + + now = int(time.time()) + cutoff_24h = now - 86400 + + all_bounties = BOUNTY_SERVICE.list_bounties(room_id) + + def is_expired(b) -> bool: + return b.due_date_ts is not None and b.due_date_ts < cutoff_24h + + def sort_key(b): + if b.due_date_ts is not None: + return (0, b.due_date_ts) + return (1, b.created_at) + + filtered_bounties = [b for b in all_bounties if not is_expired(b) or show_all] + filtered_bounties.sort(key=sort_key) + + total_count = len(filtered_bounties) + displayed_bounties = filtered_bounties[:limit] + + if not displayed_bounties: + if show_all: + await update.message.reply_text("No bounties yet.") + else: + await update.message.reply_text( + "No active bounties. Use /bounty all to show expired." + ) return - lines = [format_bounty(b, show_id=True) for b in bounties] + lines = [] + if limit < total_count: + lines.append(f"Showing {limit} of {total_count} bounties:") + slice_length = 40 + elif show_all and total_count > limit: + lines.append(f"Showing {limit} of {total_count} bounties (including expired):") + slice_length = 40 + else: + lines.append(f"Showing {total_count} bounties:") + slice_length = 0 + + for b in displayed_bounties: + lines.append(format_bounty(b, show_id=True, slice_length=slice_length)) + await update.message.reply_text("\n".join(lines), disable_web_page_preview=True) -- 2.49.1