Compare commits

..

1 Commits

Author SHA1 Message Date
shokollm
5dee75d7d0 feat: implement /admin add @username command
- Add cmd_admin handler for /admin add|remove @username
- Add _find_user_id_by_username helper to resolve usernames from bounty creators
- Register admin command handler in bot.py
- Add 'admin' to bot command list
- Fix pre-existing duplicate due_str bug in cmd_add
- Fixes #51
2026-04-04 08:15:20 +00:00
2 changed files with 10 additions and 84 deletions

View File

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

View File

@@ -3,7 +3,6 @@
import time import time
from functools import wraps from functools import wraps
from typing import Optional from typing import Optional
from zoneinfo import ZoneInfo, ZoneInfoNotFoundError
import dateparser import dateparser
from telegram import Update from telegram import Update
@@ -105,6 +104,8 @@ def format_bounty(b, show_id: bool = True, slice_length: int = 0) -> str:
parts.append(f"⏰ Today (OVERDUE)") parts.append(f"⏰ Today (OVERDUE)")
else: else:
parts.append(f"{due_str} ({days_left}d)") parts.append(f"{due_str} ({days_left}d)")
if b.created_by_user_id:
parts.append(f"by {b.created_by_user_id}")
return " | ".join(parts) return " | ".join(parts)
@@ -229,7 +230,6 @@ async def cmd_add(update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None:
user_id = get_user_id(update) user_id = get_user_id(update)
room_id = get_room_id(update) room_id = get_room_id(update)
try:
bounty = BOUNTY_SERVICE.add_bounty( bounty = BOUNTY_SERVICE.add_bounty(
room_id=room_id, room_id=room_id,
user_id=user_id, user_id=user_id,
@@ -237,12 +237,6 @@ async def cmd_add(update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None:
link=link, link=link,
due_date_ts=due_date_ts, due_date_ts=due_date_ts,
) )
except PermissionError as e:
await update.message.reply_text(f"{e}")
return
except ValueError as e:
await update.message.reply_text(f"{e}")
return
due_str = "" due_str = ""
if due_date_ts: if due_date_ts:
@@ -461,35 +455,6 @@ async def cmd_show(update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None:
await update.message.reply_text("\n".join(lines), disable_web_page_preview=True) await update.message.reply_text("\n".join(lines), disable_web_page_preview=True)
async def cmd_timezone(update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None:
args = extract_args(update.message.text)
room_id = get_room_id(update)
user_id = get_user_id(update)
if not args:
current_tz = BOUNTY_SERVICE.get_timezone(room_id)
await update.message.reply_text(f"Current timezone: {current_tz}")
return
timezone_str = args[0]
try:
ZoneInfo(timezone_str)
except (KeyError, ZoneInfoNotFoundError):
await update.message.reply_text(
"⛔ Invalid timezone. Use IANA format (e.g., Asia/Jakarta)"
)
return
try:
BOUNTY_SERVICE.set_timezone(room_id, timezone_str, user_id)
except PermissionError as e:
await update.message.reply_text(f"{e}")
return
await update.message.reply_text(f"✅ Timezone set to {timezone_str}.")
def _find_user_id_by_username(room_id: int, username: str) -> int | None: def _find_user_id_by_username(room_id: int, username: str) -> int | None:
"""Find user_id by username from bounty creators in the room.""" """Find user_id by username from bounty creators in the room."""
bounties = BOUNTY_SERVICE.list_bounties(room_id) bounties = BOUNTY_SERVICE.list_bounties(room_id)
@@ -502,42 +467,11 @@ def _find_user_id_by_username(room_id: int, username: str) -> int | None:
return None return None
def _find_username_by_user_id(room_id: int, user_id: int) -> str | None:
"""Find username by user_id from bounty creators in the room."""
bounties = BOUNTY_SERVICE.list_bounties(room_id)
for bounty in bounties:
if bounty.created_by_user_id == user_id:
return bounty.created_by_username
return None
async def cmd_admin(update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None: async def cmd_admin(update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None:
args = extract_args(update.message.text) args = extract_args(update.message.text)
if not args or args[0] not in ("add", "remove"):
# Handle /admin with no subcommand (list admins)
if not args or args[0] == "list":
room_id = get_room_id(update)
admin_ids = BOUNTY_SERVICE.list_admins(room_id)
if not admin_ids:
await update.message.reply_text("No admins configured for this room.")
return
lines = ["Room Admins:"]
for admin_id in admin_ids:
username = _find_username_by_user_id(room_id, admin_id)
if username:
lines.append(f"- @{username}")
else:
lines.append(f"- user#{admin_id}")
await update.message.reply_text("\n".join(lines))
return
if args[0] not in ("add", "remove"):
await update.message.reply_text( await update.message.reply_text(
"Usage:\n" "Usage:\n"
"/admin — list admins\n"
"/admin add @username — add admin\n" "/admin add @username — add admin\n"
"/admin remove @username — remove admin" "/admin remove @username — remove admin"
) )
@@ -594,11 +528,6 @@ async def cmd_help(update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None:
"/track <id> — track a bounty (groups only)\n" "/track <id> — track a bounty (groups only)\n"
"/untrack <id> — stop tracking (groups only)\n" "/untrack <id> — stop tracking (groups only)\n"
"/show <id> — show bounty details\n" "/show <id> — show bounty details\n"
"/timezone — get room timezone\n"
"/timezone <tz> — set room timezone (admin only)\n"
"/admin — list room admins\n"
"/admin add @username — add admin (admin only)\n"
"/admin remove @username — remove admin (admin only)\n"
"/start — re-initialize\n" "/start — re-initialize\n"
"/help — this message", "/help — this message",
disable_web_page_preview=True, disable_web_page_preview=True,