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
This commit is contained in:
shokollm
2026-04-09 08:02:36 +00:00
parent 7822e65d6c
commit 8494b4621c
6 changed files with 473 additions and 208 deletions

View File

@@ -285,11 +285,19 @@ async def cmd_bounty(update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None:
displayed_bounties = filtered_bounties[:limit]
if not displayed_bounties:
keyboard = [
[InlineKeyboardButton("🗑️ Delete", callback_data=f"del_msg:{user_id}")]
]
reply_markup = InlineKeyboardMarkup(keyboard)
if show_all:
await update.message.reply_text("No bounties yet.")
await update.message.reply_text(
"No bounties yet.",
reply_markup=reply_markup,
)
else:
await update.message.reply_text(
"No active bounties. Use /bounty all to show expired."
"No active bounties. Use /bounty all to show expired.",
reply_markup=reply_markup,
)
return
@@ -371,29 +379,57 @@ async def cmd_add(update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None:
bounty = BOUNTY_SERVICE.add_bounty(
room_id=room_id,
user_id=user_id,
username=username,
text=text,
link=link,
due_date_ts=due_date_ts,
created_by_username=username,
)
except PermissionError as e:
await update.message.reply_text(f"{e}")
keyboard = [
[InlineKeyboardButton("🗑️ Delete", callback_data=f"del_msg:{user_id}")]
]
reply_markup = InlineKeyboardMarkup(keyboard)
await update.message.reply_text(f"{e}", reply_markup=reply_markup)
return
except ValueError as e:
await update.message.reply_text(f"{e}")
keyboard = [
[InlineKeyboardButton("🗑️ Delete", callback_data=f"del_msg:{user_id}")]
]
reply_markup = InlineKeyboardMarkup(keyboard)
await update.message.reply_text(f"{e}", reply_markup=reply_markup)
return
due_str = ""
if due_date_ts:
timezone_str = BOUNTY_SERVICE.get_timezone(room_id)
due_str = f" | Due: {format_due_date(due_date_ts, timezone_str)}"
keyboard = [[InlineKeyboardButton("🗑️ Delete", callback_data=f"del_msg:{user_id}")]]
reply_markup = InlineKeyboardMarkup(keyboard)
await update.message.reply_text(
f"✅ Bounty added (#{bounty.id}){due_str}",
disable_web_page_preview=True,
reply_markup=reply_markup,
)
async def cmd_update(update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None:
user_id = get_user_id(update)
room_id = get_room_id(update)
effective_user = update.effective_user
username = effective_user.username or effective_user.first_name or None
if not BOUNTY_SERVICE.is_admin(room_id, username):
keyboard = [
[InlineKeyboardButton("🗑️ Delete", callback_data=f"del_msg:{user_id}")]
]
reply_markup = InlineKeyboardMarkup(keyboard)
await update.message.reply_text(
"⛔ Only admins can edit bounties.",
reply_markup=reply_markup,
)
return
args = extract_args(update.message.text)
if len(args) < 1:
await update.message.reply_text(
@@ -411,11 +447,16 @@ async def cmd_update(update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None:
try:
bounty_id = int(args[0])
except ValueError:
await update.message.reply_text("Invalid bounty ID.")
keyboard = [
[InlineKeyboardButton("🗑️ Delete", callback_data=f"del_msg:{user_id}")]
]
reply_markup = InlineKeyboardMarkup(keyboard)
await update.message.reply_text(
"Invalid bounty ID.",
reply_markup=reply_markup,
)
return
user_id = get_user_id(update)
room_id = get_room_id(update)
timezone_str = BOUNTY_SERVICE.get_timezone(room_id)
try:
@@ -431,19 +472,33 @@ async def cmd_update(update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None:
and not clear_link
and not clear_date
):
await update.message.reply_text("Nothing to update.")
keyboard = [
[InlineKeyboardButton("🗑️ Delete", callback_data=f"del_msg:{user_id}")]
]
reply_markup = InlineKeyboardMarkup(keyboard)
await update.message.reply_text(
"Nothing to update.",
reply_markup=reply_markup,
)
return
old_bounty = BOUNTY_SERVICE.get_bounty(room_id, bounty_id)
if not old_bounty:
await update.message.reply_text("Bounty not found.")
keyboard = [
[InlineKeyboardButton("🗑️ Delete", callback_data=f"del_msg:{user_id}")]
]
reply_markup = InlineKeyboardMarkup(keyboard)
await update.message.reply_text(
"Bounty not found.",
reply_markup=reply_markup,
)
return
try:
success = BOUNTY_SERVICE.update_bounty(
room_id=room_id,
bounty_id=bounty_id,
user_id=user_id,
username=username,
text=text,
link=link,
due_date_ts=due_date_ts,
@@ -451,10 +506,18 @@ async def cmd_update(update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None:
clear_due=clear_date,
)
except PermissionError as e:
await update.message.reply_text(f"{e}")
keyboard = [
[InlineKeyboardButton("🗑️ Delete", callback_data=f"del_msg:{user_id}")]
]
reply_markup = InlineKeyboardMarkup(keyboard)
await update.message.reply_text(f"{e}", reply_markup=reply_markup)
return
except ValueError as e:
await update.message.reply_text(f"{e}")
keyboard = [
[InlineKeyboardButton("🗑️ Delete", callback_data=f"del_msg:{user_id}")]
]
reply_markup = InlineKeyboardMarkup(keyboard)
await update.message.reply_text(f"{e}", reply_markup=reply_markup)
return
if success:
@@ -485,11 +548,23 @@ async def cmd_update(update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None:
changes.append(f"Date: {old_date} → (cleared)")
if changes:
keyboard = [
[InlineKeyboardButton("🗑️ Delete", callback_data=f"del_msg:{user_id}")]
]
reply_markup = InlineKeyboardMarkup(keyboard)
await update.message.reply_text(
f"✅ Bounty #{bounty_id} updated:\n" + "\n".join(changes)
f"✅ Bounty #{bounty_id} updated:\n" + "\n".join(changes),
reply_markup=reply_markup,
)
else:
await update.message.reply_text(f"✅ Bounty #{bounty_id} updated.")
keyboard = [
[InlineKeyboardButton("🗑️ Delete", callback_data=f"del_msg:{user_id}")]
]
reply_markup = InlineKeyboardMarkup(keyboard)
await update.message.reply_text(
f"✅ Bounty #{bounty_id} updated.",
reply_markup=reply_markup,
)
else:
await update.message.reply_text("Bounty not found.")
@@ -498,6 +573,22 @@ cmd_edit = cmd_update
async def cmd_delete(update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None:
user_id = get_user_id(update)
room_id = get_room_id(update)
effective_user = update.effective_user
username = effective_user.username or effective_user.first_name or None
if not BOUNTY_SERVICE.is_admin(room_id, username):
keyboard = [
[InlineKeyboardButton("🗑️ Delete", callback_data=f"del_msg:{user_id}")]
]
reply_markup = InlineKeyboardMarkup(keyboard)
await update.message.reply_text(
"⛔ Only admins can delete bounties.",
reply_markup=reply_markup,
)
return
args = extract_args(update.message.text)
if not args:
await update.message.reply_text("Usage: /delete <bounty_id> [bounty_id ...]")
@@ -506,20 +597,28 @@ async def cmd_delete(update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None:
try:
bounty_ids = [int(arg) for arg in args]
except ValueError:
await update.message.reply_text("Invalid bounty ID.")
keyboard = [
[InlineKeyboardButton("🗑️ Delete", callback_data=f"del_msg:{user_id}")]
]
reply_markup = InlineKeyboardMarkup(keyboard)
await update.message.reply_text(
"Invalid bounty ID.",
reply_markup=reply_markup,
)
return
user_id = get_user_id(update)
room_id = get_room_id(update)
try:
results = BOUNTY_SERVICE.delete_bounties(
room_id=room_id,
bounty_ids=bounty_ids,
user_id=user_id,
username=username,
)
except PermissionError as e:
await update.message.reply_text(f"{e}")
keyboard = [
[InlineKeyboardButton("🗑️ Delete", callback_data=f"del_msg:{user_id}")]
]
reply_markup = InlineKeyboardMarkup(keyboard)
await update.message.reply_text(f"{e}", reply_markup=reply_markup)
return
response_lines = []
@@ -531,12 +630,24 @@ async def cmd_delete(update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None:
elif result == "permission_denied":
response_lines.append(f"⛔ Bounty #{bounty_id} permission denied.")
await update.message.reply_text("\n".join(response_lines))
keyboard = [[InlineKeyboardButton("🗑️ Delete", callback_data=f"del_msg:{user_id}")]]
reply_markup = InlineKeyboardMarkup(keyboard)
await update.message.reply_text(
"\n".join(response_lines), reply_markup=reply_markup
)
async def cmd_track(update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None:
user_id = get_user_id(update)
if not is_group(update):
await update.message.reply_text("⛔ /track is only available in groups.")
keyboard = [
[InlineKeyboardButton("🗑️ Delete", callback_data=f"del_msg:{user_id}")]
]
reply_markup = InlineKeyboardMarkup(keyboard)
await update.message.reply_text(
"⛔ /track is only available in groups.",
reply_markup=reply_markup,
)
return
args = extract_args(update.message.text)
@@ -555,16 +666,38 @@ async def cmd_track(update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None:
try:
if TRACKING_SERVICE.track_bounty(room_id, user_id, bounty_id):
await update.message.reply_text(f"✅ Now tracking bounty #{bounty_id}.")
keyboard = [
[InlineKeyboardButton("🗑️ Delete", callback_data=f"del_msg:{user_id}")]
]
reply_markup = InlineKeyboardMarkup(keyboard)
await update.message.reply_text(
f"✅ Now tracking bounty #{bounty_id}.",
reply_markup=reply_markup,
)
else:
await update.message.reply_text(f"Already tracking bounty #{bounty_id}.")
keyboard = [
[InlineKeyboardButton("🗑️ Delete", callback_data=f"del_msg:{user_id}")]
]
reply_markup = InlineKeyboardMarkup(keyboard)
await update.message.reply_text(
f"Already tracking bounty #{bounty_id}.",
reply_markup=reply_markup,
)
except ValueError as e:
await update.message.reply_text(str(e))
async def cmd_untrack(update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None:
user_id = get_user_id(update)
if not is_group(update):
await update.message.reply_text("⛔ /untrack is only available in groups.")
keyboard = [
[InlineKeyboardButton("🗑️ Delete", callback_data=f"del_msg:{user_id}")]
]
reply_markup = InlineKeyboardMarkup(keyboard)
await update.message.reply_text(
"⛔ /untrack is only available in groups.",
reply_markup=reply_markup,
)
return
args = extract_args(update.message.text)
@@ -582,22 +715,46 @@ async def cmd_untrack(update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None:
room_id = get_room_id(update)
if TRACKING_SERVICE.untrack_bounty(room_id, user_id, bounty_id):
await update.message.reply_text(f"✅ Untracked bounty #{bounty_id}.")
keyboard = [
[InlineKeyboardButton("🗑️ Delete", callback_data=f"del_msg:{user_id}")]
]
reply_markup = InlineKeyboardMarkup(keyboard)
await update.message.reply_text(
f"✅ Untracked bounty #{bounty_id}.",
reply_markup=reply_markup,
)
else:
await update.message.reply_text("Not tracking bounty #{bounty_id}.")
keyboard = [
[InlineKeyboardButton("🗑️ Delete", callback_data=f"del_msg:{user_id}")]
]
reply_markup = InlineKeyboardMarkup(keyboard)
await update.message.reply_text(
f"Not tracking bounty #{bounty_id}.",
reply_markup=reply_markup,
)
async def cmd_start(update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None:
user_id = get_user_id(update)
room_id = get_room_id(update)
effective_user = update.effective_user
username = effective_user.username or effective_user.first_name or None
if is_group(update):
try:
chat_member = await ctx.bot.get_chat_member(room_id, user_id)
if chat_member.status == "creator" and not BOUNTY_SERVICE.is_admin(
room_id, user_id
room_id, username
):
BOUNTY_SERVICE.add_admin(room_id, user_id, user_id)
BOUNTY_SERVICE.add_admin(room_id, username, username)
keyboard = [
[
InlineKeyboardButton(
"🗑️ Delete", callback_data=f"del_msg:{user_id}"
)
]
]
reply_markup = InlineKeyboardMarkup(keyboard)
await update.message.reply_text(
"👻 JIGAIDO is watching.\n\n"
"This group's bounty list is now active.\n"
@@ -605,34 +762,46 @@ async def cmd_start(update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None:
"/bounty — list bounties\n"
"/add — create a bounty\n"
"/track — track a bounty\n"
"/my — your tracked bounties"
"/my — your tracked bounties",
reply_markup=reply_markup,
)
return
except Exception:
pass
keyboard = [
[InlineKeyboardButton("🗑️ Delete", callback_data=f"del_msg:{user_id}")]
]
reply_markup = InlineKeyboardMarkup(keyboard)
await update.message.reply_text(
"👻 JIGAIDO is watching.\n\n"
"This group's bounty list is now active.\n"
"/bounty — list bounties\n"
"/add — create a bounty\n"
"/track — track a bounty\n"
"/my — your tracked bounties"
"/my — your tracked bounties",
reply_markup=reply_markup,
)
else:
if not BOUNTY_SERVICE.is_admin(room_id, user_id):
BOUNTY_SERVICE.add_admin(room_id, user_id, user_id)
if not BOUNTY_SERVICE.is_admin(room_id, username):
BOUNTY_SERVICE.add_admin(room_id, username, username)
keyboard = [
[InlineKeyboardButton("🗑️ Delete", callback_data=f"del_msg:{user_id}")]
]
reply_markup = InlineKeyboardMarkup(keyboard)
await update.message.reply_text(
"👻 JIGAIDO activated.\n\n"
"Personal bounty list ready.\n"
"/bounty — list your bounties\n"
"/add — create a bounty\n"
"/my — your tracked bounties"
"/my — your tracked bounties",
reply_markup=reply_markup,
)
async def cmd_show(update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None:
user_id = get_user_id(update)
args = extract_args(update.message.text)
if not args:
await update.message.reply_text("Usage: /show <bounty_id>")
@@ -680,19 +849,44 @@ async def cmd_show(update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None:
created_str = dt_created.strftime("%Y-%m-%d %H:%M")
lines.append(f"📌 Created: {created_str}")
keyboard = [[InlineKeyboardButton("🗑️ Delete", callback_data=f"del_msg:{user_id}")]]
reply_markup = InlineKeyboardMarkup(keyboard)
await update.message.reply_text(
"\n".join(lines), disable_web_page_preview=True, parse_mode=ParseMode.HTML
"\n".join(lines),
disable_web_page_preview=True,
parse_mode=ParseMode.HTML,
reply_markup=reply_markup,
)
async def cmd_timezone(update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None:
user_id = get_user_id(update)
args = extract_args(update.message.text)
room_id = get_room_id(update)
user_id = get_user_id(update)
effective_user = update.effective_user
username = effective_user.username or effective_user.first_name or None
if not BOUNTY_SERVICE.is_admin(room_id, username):
keyboard = [
[InlineKeyboardButton("🗑️ Delete", callback_data=f"del_msg:{user_id}")]
]
reply_markup = InlineKeyboardMarkup(keyboard)
await update.message.reply_text(
"⛔ Only admins can change timezone.",
reply_markup=reply_markup,
)
return
if not args:
current_tz = BOUNTY_SERVICE.get_timezone(room_id)
await update.message.reply_text(f"Current timezone: {current_tz}")
keyboard = [
[InlineKeyboardButton("🗑️ Delete", callback_data=f"del_msg:{user_id}")]
]
reply_markup = InlineKeyboardMarkup(keyboard)
await update.message.reply_text(
f"Current timezone: {current_tz}",
reply_markup=reply_markup,
)
return
timezone_str = args[0]
@@ -706,21 +900,39 @@ async def cmd_timezone(update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None:
return
try:
BOUNTY_SERVICE.set_timezone(room_id, timezone_str, user_id)
BOUNTY_SERVICE.set_timezone(room_id, timezone_str, username)
except PermissionError as e:
await update.message.reply_text(f"{e}")
keyboard = [
[InlineKeyboardButton("🗑️ Delete", callback_data=f"del_msg:{user_id}")]
]
reply_markup = InlineKeyboardMarkup(keyboard)
await update.message.reply_text(f"{e}", reply_markup=reply_markup)
return
await update.message.reply_text(f"✅ Timezone set to {timezone_str}.")
keyboard = [[InlineKeyboardButton("🗑️ Delete", callback_data=f"del_msg:{user_id}")]]
reply_markup = InlineKeyboardMarkup(keyboard)
await update.message.reply_text(
f"✅ Timezone set to {timezone_str}.",
reply_markup=reply_markup,
)
async def cmd_recover(update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None:
user_id = get_user_id(update)
args = extract_args(update.message.text)
room_id = get_room_id(update)
user_id = get_user_id(update)
effective_user = update.effective_user
username = effective_user.username or effective_user.first_name or None
if not BOUNTY_SERVICE.is_admin(room_id, user_id):
await update.message.reply_text("⛔ Only admins can perform this action.")
if not BOUNTY_SERVICE.is_admin(room_id, username):
keyboard = [
[InlineKeyboardButton("🗑️ Delete", callback_data=f"del_msg:{user_id}")]
]
reply_markup = InlineKeyboardMarkup(keyboard)
await update.message.reply_text(
"⛔ Only admins can perform this action.",
reply_markup=reply_markup,
)
return
if not args:
@@ -746,7 +958,16 @@ async def cmd_recover(update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None:
)
lines.append(f"[#{b.id}] {text}{link_str} | 🗑️ Deleted {deleted_str}")
await update.message.reply_text("\n".join(lines), disable_web_page_preview=True)
user_id = get_user_id(update)
keyboard = [
[InlineKeyboardButton("🗑️ Delete", callback_data=f"del_msg:{user_id}")]
]
reply_markup = InlineKeyboardMarkup(keyboard)
await update.message.reply_text(
"\n".join(lines),
disable_web_page_preview=True,
reply_markup=reply_markup,
)
return
try:
@@ -755,7 +976,7 @@ async def cmd_recover(update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None:
await update.message.reply_text("Invalid bounty ID.")
return
results = BOUNTY_SERVICE.recover_bounties(room_id, bounty_ids, user_id)
results = BOUNTY_SERVICE.recover_bounties(room_id, bounty_ids, username)
response_lines = []
for bounty_id, result in results.items():
@@ -768,55 +989,54 @@ async def cmd_recover(update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None:
elif result == "permission_denied":
response_lines.append(f"⛔ Bounty #{bounty_id} permission denied.")
await update.message.reply_text("\n".join(response_lines))
async def _find_user_id_by_username(
ctx: ContextTypes.DEFAULT_TYPE, username: str
) -> int | None:
"""Find user_id by username using Telegram API."""
import logging
log = logging.getLogger(__name__)
try:
chat = await ctx.bot.get_chat(f"@{username}")
log.info(f"Found user {username}: {chat.id}")
return chat.id
except Exception as e:
log.error(f"Failed to find user @{username}: {e}")
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
user_id = get_user_id(update)
keyboard = [[InlineKeyboardButton("🗑️ Delete", callback_data=f"del_msg:{user_id}")]]
reply_markup = InlineKeyboardMarkup(keyboard)
await update.message.reply_text(
"\n".join(response_lines), reply_markup=reply_markup
)
async def cmd_admin(update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None:
args = extract_args(update.message.text)
room_id = get_room_id(update)
effective_user = update.effective_user
requesting_username = effective_user.username or effective_user.first_name or None
if not args:
admins = BOUNTY_SERVICE.list_admins(get_room_id(update))
if not admins:
await update.message.reply_text("No admins in this room.")
if not BOUNTY_SERVICE.is_admin(room_id, requesting_username):
user_id = get_user_id(update)
keyboard = [
[InlineKeyboardButton("🗑️ Delete", callback_data=f"del_msg:{user_id}")]
]
reply_markup = InlineKeyboardMarkup(keyboard)
await update.message.reply_text(
"⛔ Only admins can perform this action.",
reply_markup=reply_markup,
)
return
admin_mentions = []
for admin_id in admins:
username = _find_username_by_user_id(get_room_id(update), admin_id)
if username:
admin_mentions.append(
f'<a href="tg://user?id={admin_id}">@{username}</a>'
)
else:
admin_mentions.append(
f'<a href="tg://user?id={admin_id}">{admin_id}</a>'
)
admins = BOUNTY_SERVICE.list_admins(room_id)
if not admins:
user_id = get_user_id(update)
keyboard = [
[InlineKeyboardButton("🗑️ Delete", callback_data=f"del_msg:{user_id}")]
]
reply_markup = InlineKeyboardMarkup(keyboard)
await update.message.reply_text(
"No admins in this room.",
reply_markup=reply_markup,
)
return
user_id = get_user_id(update)
admin_mentions = [f"@{admin}" for admin in admins]
keyboard = [
[InlineKeyboardButton("🗑️ Delete", callback_data=f"del_msg:{user_id}")]
]
reply_markup = InlineKeyboardMarkup(keyboard)
await update.message.reply_text(
f"Room Admins:\n" + "\n".join(f"- {m}" for m in admin_mentions),
parse_mode=ParseMode.HTML,
reply_markup=reply_markup,
)
return
@@ -840,36 +1060,51 @@ async def cmd_admin(update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None:
await update.message.reply_text(f"Usage: /admin {subcommand} @username")
return
username = raw_username[1:]
target_username = raw_username[1:]
user_id = get_user_id(update)
room_id = get_room_id(update)
if not BOUNTY_SERVICE.is_admin(room_id, user_id):
await update.message.reply_text("⛔ Only admins can perform this action.")
return
target_user_id = await _find_user_id_by_username(ctx, username)
if target_user_id is None:
await update.message.reply_text(f"⛔ User @{username} not found.")
if not BOUNTY_SERVICE.is_admin(room_id, requesting_username):
keyboard = [
[InlineKeyboardButton("🗑️ Delete", callback_data=f"del_msg:{user_id}")]
]
reply_markup = InlineKeyboardMarkup(keyboard)
await update.message.reply_text(
"⛔ Only admins can perform this action.",
reply_markup=reply_markup,
)
return
try:
keyboard = [
[InlineKeyboardButton("🗑️ Delete", callback_data=f"del_msg:{user_id}")]
]
reply_markup = InlineKeyboardMarkup(keyboard)
if subcommand == "add":
BOUNTY_SERVICE.add_admin(room_id, target_user_id, user_id)
await update.message.reply_text(f"✅ @{username} is now an admin.")
BOUNTY_SERVICE.add_admin(room_id, target_username, requesting_username)
await update.message.reply_text(
f"✅ @{target_username} is now an admin.",
reply_markup=reply_markup,
)
elif subcommand == "remove":
BOUNTY_SERVICE.remove_admin(room_id, target_user_id, user_id)
await update.message.reply_text(f"✅ @{username} is no longer an admin.")
except PermissionError as e:
await update.message.reply_text(f"{e}")
BOUNTY_SERVICE.remove_admin(room_id, target_username, requesting_username)
await update.message.reply_text(
f"✅ @{target_username} is no longer an admin.",
reply_markup=reply_markup,
)
except (PermissionError, ValueError) as e:
keyboard = [
[InlineKeyboardButton("🗑️ Delete", callback_data=f"del_msg:{user_id}")]
]
reply_markup = InlineKeyboardMarkup(keyboard)
await update.message.reply_text(f"{e}", reply_markup=reply_markup)
async def cmd_help(update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None:
user_id = get_user_id(update)
room_id = get_room_id(update)
is_admin = BOUNTY_SERVICE.is_admin(room_id, user_id)
effective_user = update.effective_user
username = effective_user.username or effective_user.first_name or None
is_admin = BOUNTY_SERVICE.is_admin(room_id, username)
if is_admin:
lines = [
@@ -906,4 +1141,10 @@ async def cmd_help(update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None:
"/help — show this message",
]
await update.message.reply_text("\n".join(lines), disable_web_page_preview=True)
keyboard = [[InlineKeyboardButton("🗑️ Delete", callback_data=f"del_msg:{user_id}")]]
reply_markup = InlineKeyboardMarkup(keyboard)
await update.message.reply_text(
"\n".join(lines),
disable_web_page_preview=True,
reply_markup=reply_markup,
)