Compare commits

...

3 Commits

Author SHA1 Message Date
shokollm
7bf08f9b38 feat: implement /show command to display full bounty details
- Add cmd_show function to display bounty details including:
  - ID and full text (not sliced)
  - Link if exists
  - Due date formatted with room timezone
  - Created by username
  - Created at timestamp
- Register show command handler in bot.py
- Add show command to help text and bot command list
- Fixes #44
2026-04-04 07:44:30 +00:00
shokollm
b8f6b98836 Merge pull request #61 from fix/issue-46 2026-04-04 07:40:59 +00:00
shokollm
a06e1327fb feat(/edit): per-argument updates + clear syntax + admin-only
- Add -link and -date flags to /edit command for field clearing
- /edit <id> -link - clear link
- /edit <id> -date - clear date
- /edit <id> -link <url> - set link
- /edit <id> -date <date> - set date
- /edit <id> text -link - update text, clear link
- /edit <id> text <url> - update text and set link
- Parse_args now returns (text, link, due_date_ts, clear_link, clear_date)
- Update usage messages and help text
- Fixes #46
2026-04-04 05:51:56 +00:00
2 changed files with 118 additions and 14 deletions

View File

@@ -13,6 +13,7 @@ from commands import (
cmd_edit,
cmd_help,
cmd_my,
cmd_show,
cmd_start,
cmd_timezone,
cmd_track,
@@ -43,6 +44,7 @@ def build_app() -> Application:
app.add_handler(CommandHandler("track", cmd_track))
app.add_handler(CommandHandler("untrack", cmd_untrack))
app.add_handler(CommandHandler("timezone", cmd_timezone))
app.add_handler(CommandHandler("show", cmd_show))
app.add_handler(MessageHandler(filters.COMMAND, cmd_help))
@@ -59,6 +61,7 @@ async def post_init(app: Application) -> None:
("track", "Track a bounty"),
("untrack", "Stop tracking"),
("timezone", "Get/set room timezone"),
("show", "Show bounty details"),
("help", "Show help"),
]
)

View File

@@ -56,26 +56,62 @@ def extract_args(text: str) -> list[str]:
return tokens[1:] if len(tokens) > 1 else []
def parse_args(args: list[str]) -> tuple[Optional[str], Optional[str], Optional[int]]:
def parse_args(
args: list[str],
) -> tuple[Optional[str], Optional[str], Optional[int], bool, bool]:
text = None
link = None
due_date_ts = None
clear_link = False
clear_date = False
remaining = []
for arg in args:
if not link and (arg.startswith("http://") or arg.startswith("https://")):
i = 0
while i < len(args):
arg = args[i]
if arg == "-link":
if i + 1 < len(args) and (
args[i + 1].startswith("http://") or args[i + 1].startswith("https://")
):
link = args[i + 1]
i += 2
else:
clear_link = True
i += 1
elif arg == "-date":
if i + 1 < len(args):
parsed = dateparser.parse(args[i + 1])
if parsed:
due_date_ts = int(parsed.timestamp())
i += 2
else:
clear_date = True
i += 1
else:
clear_date = True
i += 1
elif not link and (arg.startswith("http://") or arg.startswith("https://")):
link = arg
i += 1
elif due_date_ts is None:
parsed = dateparser.parse(arg)
if parsed:
due_date_ts = int(parsed.timestamp())
i += 1
else:
remaining.append(arg)
i += 1
if text is None:
text = arg
else:
remaining.append(arg)
text = text + " " + arg
else:
i += 1
if text is None:
text = arg
else:
text = text + " " + arg
text = " ".join(remaining) if remaining else None
return text, link, due_date_ts
return text, link, due_date_ts, clear_link, clear_date
def format_bounty(b, show_id: bool = True, room_id: int | None = None) -> str:
@@ -172,7 +208,7 @@ async def cmd_add(update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None:
)
return
text, link, due_date_ts = parse_args(args)
text, link, due_date_ts, _, _ = parse_args(args)
if not text and not link:
await update.message.reply_text("A bounty needs at least text or a link.")
return
@@ -203,7 +239,14 @@ async def cmd_update(update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None:
args = extract_args(update.message.text)
if len(args) < 1:
await update.message.reply_text(
"Usage: /update <bounty_id> [text] [link] [due_date]"
"Usage: /update <bounty_id> [text] [link] [due_date]\n"
" /update <bounty_id> -link [<url>] - clear or set link\n"
" /update <bounty_id> -date [<date>] - clear or set date\n"
"Examples:\n"
" /update 1 new text - update text only\n"
" /update 1 -link - clear link\n"
" /update 1 -link https://... - set link\n"
" /update 1 -link -date - clear both link and date"
)
return
@@ -213,8 +256,14 @@ async def cmd_update(update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None:
await update.message.reply_text("Invalid bounty ID.")
return
text, link, due_date_ts = parse_args(args[1:])
if not text and not link and due_date_ts is None:
text, link, due_date_ts, clear_link, clear_date = parse_args(args[1:])
if (
not text
and not link
and due_date_ts is None
and not clear_link
and not clear_date
):
await update.message.reply_text("Nothing to update.")
return
@@ -229,10 +278,15 @@ async def cmd_update(update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None:
text=text,
link=link,
due_date_ts=due_date_ts,
clear_link=clear_link,
clear_due=clear_date,
)
except PermissionError as e:
await update.message.reply_text(f"{e}")
return
except ValueError as e:
await update.message.reply_text(f"{e}")
return
if success:
await update.message.reply_text(f"✅ Bounty #{bounty_id} updated.")
@@ -347,17 +401,64 @@ async def cmd_start(update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None:
)
async def cmd_show(update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None:
args = extract_args(update.message.text)
if not args:
await update.message.reply_text("Usage: /show <bounty_id>")
return
try:
bounty_id = int(args[0])
except ValueError:
await update.message.reply_text("Invalid bounty ID.")
return
room_id = get_room_id(update)
bounty = BOUNTY_SERVICE.get_bounty(room_id, bounty_id)
if not bounty:
await update.message.reply_text("Bounty not found.")
return
timezone = BOUNTY_SERVICE.get_timezone(room_id)
lines = []
title = bounty.text or "(no text)"
lines.append(f"[#{bounty.id}] {title}")
due_parts = []
if bounty.due_date_ts:
due_str = time.strftime("%d %B %Y %H:%M", time.localtime(bounty.due_date_ts))
due_parts.append(f"Due: {due_str} ({timezone})")
username = bounty.created_by_username or f"user#{bounty.created_by_user_id}"
if bounty.link:
due_parts.append(f"{bounty.link} by @{username}")
else:
due_parts.append(f"by @{username}")
if due_parts:
lines.extend(due_parts)
await update.message.reply_text("\n".join(lines), disable_web_page_preview=True)
async def cmd_help(update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None:
await update.message.reply_text(
"👻 JIGAIDO Commands:\n\n"
"/bounty — list all bounties\n"
"/my — bounties you're tracking\n"
"/add <text> [link] [due] — add bounty\n"
"/update <id> [text> [link] [due] — update bounty\n"
"/delete <id> — delete bounty\n"
"/update <id> [text] [link] [due] — update bounty\n"
"/edit <id> [text] [link] [due] — edit bounty (same as update)\n"
" /edit <id> -link [<url>] — clear or set link\n"
" /edit <id> -date [<date>] — clear or set date\n"
"/delete <id> — delete bounty (admin only)\n"
"/track <id> — track a bounty (groups only)\n"
"/untrack <id> — stop tracking (groups only)\n"
"/timezone [tz] — get/set room timezone (admin only)\n"
"/show <id> — show bounty details\n"
"/start — re-initialize\n"
"/help — this message",
disable_web_page_preview=True,