diff --git a/src/bub_skills/telegram/SKILL.md b/src/bub_skills/telegram/SKILL.md index e620389f..bf0b17a4 100644 --- a/src/bub_skills/telegram/SKILL.md +++ b/src/bub_skills/telegram/SKILL.md @@ -28,7 +28,7 @@ Collect these before execution: ## Execution Policy 1. If handling a direct user message in Telegram and `message_id` is known, send a reply message (`--reply-to`). -2. If source metadata says sender is a bot (`sender_is_bot=true`), do not use reply mode, but send a normal message and prefix content with `@` (or the provided source username). +2. If source metadata says sender is a bot (`sender_is_bot=true`), do not use reply mode, but send a normal message and prefix content with `@` (or the provided source username). If the user doesn't have a username, use `--source-user-id` to mention via `tg://user?id=` link. 3. For long-running tasks, optionally send one progress message, then edit that same message for final status. 4. For multi-line text, pass the content via heredoc command substitution instead of embedding raw line breaks in quoted strings. 5. Avoid emitting HTML tags in message content; use Markdown for formatting instead. @@ -91,13 +91,21 @@ uv run ./scripts/telegram_send.py \ --message "" \ --reply-to -# Source message sender is bot: no direct reply, use @user_id style +# Source message sender is bot: no direct reply, use @username style uv run ./scripts/telegram_send.py \ --chat-id \ --message "" \ --source-is-bot \ --source-username +# Source message sender is bot without username: use tg://user?id= link +uv run ./scripts/telegram_send.py \ + --chat-id \ + --message "" \ + --source-is-bot \ + --source-user-id \ + --source-display-name "Display Name" + # Edit existing message uv run ./scripts/telegram_edit.py \ --chat-id \ @@ -115,8 +123,10 @@ For other actions that not covered by these scripts, use `curl` to call Telegram - `--message`, `-m`: required - `--reply-to`, `-r`: optional - `--token`, `-t`: optional (normally not needed) -- `--source-is-bot`: optional flag, disables reply mode and switches to `@user_id` style -- `--source-user-id`: optional, required when `--source-is-bot` is set +- `--source-is-bot`: optional flag, disables reply mode and adds mention prefix +- `--source-username`: optional, uses `@username` style mention when set +- `--source-user-id`: optional, uses `tg://user?id=` link mention when username is not available +- `--source-display-name`: optional, display name for user ID mention (defaults to "User") ### `telegram_edit.py` diff --git a/src/bub_skills/telegram/scripts/telegram_send.py b/src/bub_skills/telegram/scripts/telegram_send.py index 94690689..73b99565 100755 --- a/src/bub_skills/telegram/scripts/telegram_send.py +++ b/src/bub_skills/telegram/scripts/telegram_send.py @@ -118,6 +118,14 @@ def send_message( return response.json() +def escape_markdown_v2(text: str) -> str: + """ + Escape special characters for Telegram MarkdownV2 format. + """ + escape_chars = r'_*[]()~`>#+-=|{}.!' + return ''.join('\\' + char if char in escape_chars else char for char in text) + + def main(): parser = argparse.ArgumentParser(description="Send messages via Telegram Bot API (auto-converts to MarkdownV2)") parser.add_argument("--chat-id", "-c", required=True, help="Target chat ID") @@ -138,6 +146,14 @@ def main(): "--source-username", help="Source username for @username prefix when --source-is-bot is enabled", ) + parser.add_argument( + "--source-user-id", + help="Source user ID for mention when username is not available (uses tg://user?id= link)", + ) + parser.add_argument( + "--source-display-name", + help="Display name for user ID mention (defaults to 'User')", + ) args = parser.parse_args() @@ -150,10 +166,25 @@ def main(): # Parse chat IDs chat_id = args.chat_id.strip() reply_to = args.reply_to + message = args.message + + # Handle source-is-bot mode: prefix message with mention + if args.source_is_bot: + if args.source_user_id: + # Use tg://user?id= link for mention (works without username) + display_name = args.source_display_name or "User" + # Escape the display name for MarkdownV2 + escaped_name = escape_markdown_v2(display_name) + mention = f"[{escaped_name}](tg://user?id={args.source_user_id})" + message = f"{mention}\n\n{message}" + elif args.source_username: + # Fall back to @username mention + message = f"@{args.source_username}\n\n{message}" + # If neither is provided, just send without mention prefix # Send messages try: - send_message(bot_token, chat_id, args.message, reply_to) + send_message(bot_token, chat_id, message, reply_to) print(f"✅ Message sent successfully to {chat_id} (MarkdownV2)") except requests.HTTPError as e: print(f"❌ HTTP Error: {e}")