Skip to main content

ResponseWithQrBot

@responseWithQrBot is an automated Telegram bot built to instantly transform text payloads and web links into clean, high-resolution QR codes inside chat conversations.

Architecture Overview

I structured @responseWithQrBot around an event-driven model that completely decouples Telegram API event processing from image synthesis.

+------------------+         +--------------------------+         +------------------------+
| Telegram Server | -----> | Handler & Auth Layer | -----> | Core QR Engine |
| (Webhook/Update) | | (handlers.py) | | (core.py) |
+------------------+ +--------------------------+ +------------------------+
| |
v v
[Length & Auth Checks] [In-Memory BytesIO PNG]

The system architecture consists of two primary modules:

  1. Core Generation Engine (responsewithqrbot/core.py): Harnesses qrcode and Pillow (PIL) to build low-error-correction matrix symbols and upscale them to uniform 512x512 PNG images in memory.
  2. Telegram Dispatcher (responsewithqrbot/handlers.py): Receives updates via python-telegram-bot, validates payload limits, executes optional access control, and streams the output buffer back to Telegram.

In-Memory QR Code Rendering Pipeline

To maximize throughput and avoid temporary file cleanup overhead on disk, I engineered the generator to output directly to an io.BytesIO buffer.

import qrcode
import io
import logging
from settings import settings
from PIL import Image
from typing import Optional

logger = logging.getLogger(__name__)

def generate_qrcode(message: str) -> Optional[io.BytesIO]:
try:
qr = qrcode.QRCode(
version=1,
error_correction=qrcode.constants.ERROR_CORRECT_L,
box_size=10,
border=4,
)
qr.add_data(message)
qr.make(fit=True)
img = qr.make_image(fill_color="black", back_color="white")

target_size = (int(settings.RESPONSEWITHQRBOT_IMAGE_BOX_SIZE), int(settings.RESPONSEWITHQRBOT_IMAGE_BOX_SIZE))
img = img.resize(target_size, Image.Resampling.BOX)

buffer = io.BytesIO()
img.save(buffer, format="PNG")
buffer.seek(0)

return buffer

except Exception as e:
logger.error(f"Error generating QR code: {e}")
return None

Technical Implementation Choices

  • Low Error Correction (ERROR_CORRECT_L): Keeps the matrix density minimal (~7% error correction capacity) to fit maximum message content within smaller grid dimensions.
  • Dynamic Grid Fitting (qr.make(fit=True)): Automatically increases the QR version grid if the payload exceeds version 1 limits.
  • Pillow Box Resampling (Image.Resampling.BOX): Resizes generated matrix images up to 512x512 pixels using box resampling to preserve sharp pixel boundaries without blurry interpolation artifacts.
  • In-Memory Streaming: Avoids disk I/O bottlenecks by returning an io.BytesIO PNG stream directly to Telegram photo upload handlers.

Message Parsing and Access Control

Before generating QR codes, incoming updates pass through validation logic in responsewithqrbot/handlers.py.

def parse_update_message(update) -> Optional[str]:
if not update.message.text:
return None

elif len(update.message.text) > int(settings.RESPONSEWITHQRBOT_TEXT_LENGTH):
return None

else:
return update.message.text

Engineering Guardrails

  1. Payload Limit Enforcement: The bot enforces a maximum message length of 256 characters (RESPONSEWITHQRBOT_TEXT_LENGTH). This prevents users from sending huge texts that result in overly dense, hard-to-scan QR matrix patterns.
  2. Access Control Filtering: If settings.SERHIIXXX_TELEGRAM_ID is set in the environment, the handler checks update.to_dict()["message"]["from"]["id"] to block unauthorized users.
  3. User State Tracking: Integrates with add_user_on_start and ChatMemberHandler to handle user lifecycle events.

Handler Registration

The bot handlers are attached to the python-telegram-bot dispatcher as follows:

def register_responsewithqrbot_handlers(dispatcher):
dispatcher.add_handler(CommandHandler('start', handle_start_message))
dispatcher.add_handler(MessageHandler(Filters.text, handle_message))
dispatcher.add_handler(ChatMemberHandler(user_left, ChatMemberHandler.MY_CHAT_MEMBER))

Upon receiving a valid message, the bot logs the update, generates the image buffer via generate_qrcode(), and returns the QR image using update.message.reply_photo(buffer, caption=...).