# Agent 2: Telegram Bot Fixes (Tasks B, C, E) ## Summary Fixed three issues in `crates/cgcx-bot/src/main.rs`: - **Task B**: Enabled admin commands (including `/get_id`) in channels. - **Task C**: Escaped angle-bracket placeholders in `/help` text to prevent Telegram HTML parse errors. - **Task E**: Restricted `/blacklist_uid` and `/whitelist_uid` to admin groups at the outer dispatch level and removed redundant inner checks. --- ## Task B — /get_id in channels **Problem:** Admin command dispatch was gated behind `msg.chat.is_group() || msg.chat.is_supergroup()`, so channels were excluded. **Change:** Added `|| msg.chat.is_channel()` to the dispatch guard. **oldText:** ```rust // Admin commands in groups if msg.chat.is_group() || msg.chat.is_supergroup() { ``` **newText:** ```rust // Admin commands in groups if msg.chat.is_group() || msg.chat.is_supergroup() || msg.chat.is_channel() { ``` --- ## Task C — /help HTML parse errors **Problem:** The `help_text` raw string contained unescaped placeholders like ``, `<@username>`, ``, ``, ``, ``. Telegram HTML parse mode rejects unsupported tags. **Change:** Replaced every `` placeholder with `[arg]` (e.g., `` → `[ID]`). Existing `<dest>` / `<review>` were left untouched because they were already properly escaped. **oldText:** ```rust let help_text = r#"Admin Commands /reload — Reload moderation lists. /blacklist_uid — Blacklist a user ID. /whitelist_uid — Remove a user from blacklist. /help — Show this message. /get_id — Get current chat ID. /get_id <@username> — Search administrators by username. /get_id — Search members in this chat by display name. /create_submit_forward <dest> <review> [msg] — Create a submission forward. /show_c_forward [page] — List forward links. /add_blacklist — Blacklist a user in all active forwards. /rm_blacklist — Remove a user from blacklist in all active forwards. /sban @user [reason] — Ban for duration /smute @user [reason] — Mute for duration /mute @user [reason] — Mute indefinitely /pban @user [reason] — Permanent ban /kick @user [reason] — Kick from group /rmute @user — Revoke mute /rban @user — Revoke ban"#; ``` **newText:** ```rust let help_text = r#"Admin Commands /reload — Reload moderation lists. /blacklist_uid [ID] — Blacklist a user ID. /whitelist_uid [ID] — Remove a user from blacklist. /help — Show this message. /get_id — Get current chat ID. /get_id [@username] — Search administrators by username. /get_id [displayname] — Search members in this chat by display name. /create_submit_forward <dest> <review> [msg] — Create a submission forward. /show_c_forward [page] — List forward links. /add_blacklist [user_id] — Blacklist a user in all active forwards. /rm_blacklist [user_id] — Remove a user from blacklist in all active forwards. /sban @user [dur] [unit] [reason] — Ban for duration /smute @user [dur] [unit] [reason] — Mute for duration /mute @user [reason] — Mute indefinitely /pban @user [reason] — Permanent ban /kick @user [reason] — Kick from group /rmute @user — Revoke mute /rban @user — Revoke ban"#; ``` --- ## Task E — /blacklist_uid and /whitelist_uid behavior **Problem:** The outer dispatch only checked `is_admin()`, then the inner handler checked `admin_group_ids`. This leaked the command's existence to non-admin groups. **Changes:** 1. At the outer command dispatch, both commands now require: - `ctx.config.groups.admin_group_ids.contains(&chat_id.0)` - `is_admin(&bot, msg.chat.id, user.id).await` 2. Removed the redundant `admin_group_ids` checks from inside `handle_admin_blacklist_uid` and `handle_admin_whitelist_uid`. 3. Missing-parameter usage replies remain intact in the handlers. ### Outer dispatch **oldText:** ```rust "/blacklist_uid" => { tracing::info!("admin command /blacklist_uid chat={} user={}", chat_id, user_id); if is_admin(&bot, msg.chat.id, user.id).await { handle_admin_blacklist_uid(&bot, chat_id, text, &ctx).await?; } return Ok(()); } "/whitelist_uid" => { tracing::info!("admin command /whitelist_uid chat={} user={}", chat_id, user_id); if is_admin(&bot, msg.chat.id, user.id).await { handle_admin_whitelist_uid(&bot, chat_id, text, &ctx).await?; } return Ok(()); } ``` **newText:** ```rust "/blacklist_uid" => { tracing::info!("admin command /blacklist_uid chat={} user={}", chat_id, user_id); if ctx.config.groups.admin_group_ids.contains(&chat_id.0) && is_admin(&bot, msg.chat.id, user.id).await { handle_admin_blacklist_uid(&bot, chat_id, text, &ctx).await?; } return Ok(()); } "/whitelist_uid" => { tracing::info!("admin command /whitelist_uid chat={} user={}", chat_id, user_id); if ctx.config.groups.admin_group_ids.contains(&chat_id.0) && is_admin(&bot, msg.chat.id, user.id).await { handle_admin_whitelist_uid(&bot, chat_id, text, &ctx).await?; } return Ok(()); } ``` ### Inner handler `handle_admin_blacklist_uid` **oldText:** ```rust async fn handle_admin_blacklist_uid( bot: &Bot, chat_id: ChatId, text: &str, ctx: &BotContext, ) -> HandlerResult { if !ctx.config.groups.admin_group_ids.contains(&chat_id.0) { bot.send_message(chat_id, "This command is only available in the admin group.") .await?; return Ok(()); } let uid = text.split_whitespace().nth(1).and_then(|s| s.parse::().ok()); ``` **newText:** ```rust async fn handle_admin_blacklist_uid( bot: &Bot, chat_id: ChatId, text: &str, ctx: &BotContext, ) -> HandlerResult { let uid = text.split_whitespace().nth(1).and_then(|s| s.parse::().ok()); ``` ### Inner handler `handle_admin_whitelist_uid` **oldText:** ```rust async fn handle_admin_whitelist_uid( bot: &Bot, chat_id: ChatId, text: &str, ctx: &BotContext, ) -> HandlerResult { if !ctx.config.groups.admin_group_ids.contains(&chat_id.0) { bot.send_message(chat_id, "This command is only available in the admin group.") .await?; return Ok(()); } let uid = text.split_whitespace().nth(1).and_then(|s| s.parse::().ok()); ``` **newText:** ```rust async fn handle_admin_whitelist_uid( bot: &Bot, chat_id: ChatId, text: &str, ctx: &BotContext, ) -> HandlerResult { let uid = text.split_whitespace().nth(1).and_then(|s| s.parse::().ok()); ``` --- ## Validation ``` $ cargo check -p cgcx-bot Finished `dev` profile [unoptimized + debuginfo] target(s) in 45.10s ``` **Result:** `cargo check -p cgcx-bot` passed with no errors or warnings introduced by these changes. --- ## Files Changed - `crates/cgcx-bot/src/main.rs` - `progress.md` (updated task list) ## Open risks/questions - None identified for these three tasks. ## Recommended next step - Continue with remaining plan tasks (if any) or run `cargo test -p cgcx-bot` if tests exist.