#!/usr/bin/env python3 # /// script # dependencies = [ # "qrcode[pil]>=7.4", # "pillow>=10.0.0", # ] # /// """ Haumdaucher QR Code Generator with Centered Logo. Encodes any URL or string into a high-error-correction QR code (ERROR_CORRECT_H), embeds the Haumdaucher logo in the center with a clean white rounded badge, and outputs MEDIA: for direct Telegram inline photo delivery. """ import argparse import hashlib import os import sys from pathlib import Path from PIL import Image, ImageDraw import qrcode from qrcode.constants import ERROR_CORRECT_H DEFAULT_LOGO_PATH = Path(__file__).resolve().parent.parent / "assets" / "haumdaucher_logo.png" def generate_qr_code(text: str, output_path: str = None, logo_path: str = None) -> str: if not text: raise ValueError("Text/URL to encode cannot be empty.") if not output_path: text_hash = hashlib.sha256(text.encode("utf-8")).hexdigest()[:8] output_path = f"/tmp/qr_{text_hash}.png" # Resolve logo path logo_file = Path(logo_path) if logo_path else DEFAULT_LOGO_PATH if not logo_file.exists(): # Fallback to current script dir or assets if relative path differs logo_file = DEFAULT_LOGO_PATH # Initialize QR code generator with High error correction (30% redundancy) qr = qrcode.QRCode( version=None, # Auto-detect best version error_correction=ERROR_CORRECT_H, box_size=12, border=4, ) qr.add_data(text) qr.make(fit=True) # Generate base QR code image (RGBA for clean compositing) qr_img = qr.make_image(fill_color="black", back_color="white").convert("RGBA") qr_width, qr_height = qr_img.size # Overlay logo if available if logo_file.exists(): try: logo = Image.open(logo_file).convert("RGBA") # Logo should occupy roughly 22% of the QR code dimensions to ensure readability max_logo_size = int(qr_width * 0.23) # Maintain aspect ratio logo_w, logo_h = logo.size scale = min(max_logo_size / logo_w, max_logo_size / logo_h) new_w = int(logo_w * scale) new_h = int(logo_h * scale) logo = logo.resize((new_w, new_h), Image.Resampling.LANCZOS) # Create a white rounded badge behind the logo for contrast and module separation badge_pad = 8 badge_w = new_w + badge_pad * 2 badge_h = new_h + badge_pad * 2 badge = Image.new("RGBA", (badge_w, badge_h), (0, 0, 0, 0)) draw = ImageDraw.Draw(badge) radius = 12 draw.rounded_rectangle( [(0, 0), (badge_w - 1, badge_h - 1)], radius=radius, fill=(255, 255, 255, 255), outline=(220, 220, 220, 255), width=1, ) # Paste logo onto the white badge badge.paste(logo, (badge_pad, badge_pad), mask=logo) # Paste badge into the center of the QR code pos_x = (qr_width - badge_w) // 2 pos_y = (qr_height - badge_h) // 2 qr_img.paste(badge, (pos_x, pos_y), mask=badge) except Exception as e: sys.stderr.write(f"Warning: could not overlay logo: {e}\n") # Ensure output directory exists and save out_file = Path(output_path) out_file.parent.mkdir(parents=True, exist_ok=True) qr_img.save(str(out_file), format="PNG") return str(out_file) def main(): parser = argparse.ArgumentParser(description="Generate a Haumdaucher-branded QR code.") parser.add_argument("text", nargs="?", help="Text or URL to encode into the QR code") parser.add_argument("--text", "-t", dest="text_opt", help="Text or URL to encode") parser.add_argument("--output", "-o", help="Output file path (default: /tmp/qr_.png)") parser.add_argument("--logo", "-l", help="Custom path to center logo PNG") parser.add_argument("--quiet", "-q", action="store_true", help="Only output MEDIA tag") args = parser.parse_args() raw_text = args.text or args.text_opt if not raw_text: parser.error("You must provide text or a URL to encode.") out_path = generate_qr_code(raw_text, output_path=args.output, logo_path=args.logo) # Standard OpenClaw / Telegram media dispatch tag print(f"MEDIA:{out_path}") if not args.quiet: sys.stderr.write(f"QR code successfully generated for: {raw_text}\nSaved to: {out_path}\n") if __name__ == "__main__": main()