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:
@@ -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,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user