Wiring Up python-telegram-bot Command Handlers | Toss Auto-Trading Dev Journal #17

🌐 ν•œκ΅­μ–΄λ‘œ 보기

Toss Auto-Trading Dev Journal (Phase 5 / 16 posts so far)

← Previous: #15 Dashboard

πŸ’‘ Phase 5-2 summary

  • Goal: connect python-telegram-bot so /status, /holdings, /pause, /resume actually work from Telegram
  • Key decision: the bot server only handles transport β€” it calls the same pure functions from bot_commands.py / engine_control.py already built
  • Verification: 7 new unit tests with mocked Update objects, full suite at 109/109 passing, then a live /status check in the actual Telegram app

The logic layer (engine_control.py, bot_commands.py) was already done from a prior session. This time: actually wiring it to Telegram.

Testing what you can before you even have a bot token

Before getting a token from BotFather, the handler logic itself is testable without one. So I ran both tracks in parallel.

bot_server.py β€” async handlers on python-telegram-bot v22

Version 22 moved to a fully async API, so handlers get registered like this:

from telegram import Update
from telegram.ext import ApplicationBuilder, CommandHandler, ContextTypes

async def status_handler(update: Update, context: ContextTypes.DEFAULT_TYPE):
    if not is_authorized(update.effective_chat.id):
        await update.message.reply_text("Unauthorized")
        return
    text = build_status_text()
    await update.message.reply_text(text)

app = ApplicationBuilder().token(TELEGRAM_BOT_TOKEN).build()
app.add_handler(CommandHandler("status", status_handler))

A security gap found mid-build β€” access control

Anyone who knows the bot’s username can message it. Fixed immediately by checking the incoming chat ID against TELEGRAM_CHAT_ID in .env.

def is_authorized(chat_id: int) -> bool:
    return str(chat_id) == os.getenv("TELEGRAM_CHAT_ID")

Testing with mocked Update objects

No live Telegram server needed β€” mock Update/Message objects let me test the handlers in isolation: unauthorized rejection, actual state changes from /pause and /resume, auth failure/success paths for /holdings.

Full pytest run: 109/109 passing (102 existing + 7 new).

Real-world check β€” the empty chat ID mystery

Querying Telegram’s getUpdates API for the chat ID first returned "result": []. Turned out I’d never sent a message in that chat β€” hitting “Start” and sending anything fixed it immediately.

With the token and chat ID in .env, running python bot_server.py and sending /status from the actual Telegram app returned the correct response.

FAQ

Q1. What changed in python-telegram-bot v22?

It moved to a fully async (async/await) API β€” handlers must be coroutines.

Q2. Can anyone message my bot if they know the username?

Yes by default β€” which is why a chat-ID whitelist check is required.

Q3. How do I find my chat ID?

Send a message in the chat first, then query getUpdates β€” an empty chat won’t return anything.

Q4. Can I test without a live Telegram server?

Yes β€” mock Update/Message objects isolate the handler logic for testing.

πŸŽ‰ That wraps up Phase 0 through 5. Next up: Phase 6 (operational automation) β€” APScheduler or systemd/cron.

πŸ‘‰ See the full series index

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top