From 0d1163d5e065c20467351d1e0e332eb40dae1133 Mon Sep 17 00:00:00 2001 From: salmonik Date: Fri, 17 Apr 2026 19:40:48 +0200 Subject: add qr generation and decoding Signed-off-by: salmonik --- cogs/qr.py | 142 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 142 insertions(+) create mode 100644 cogs/qr.py (limited to 'cogs/qr.py') diff --git a/cogs/qr.py b/cogs/qr.py new file mode 100644 index 0000000..058dac8 --- /dev/null +++ b/cogs/qr.py @@ -0,0 +1,142 @@ +# This file is part of Senator Sperma +# Licensed under the GPL-3.0 License +# Copyright (C) 2026 Salmonik @ salmonik@tuta.io +# https://codeberg.org/salmonik/senator-sperma + +import asyncio +import logging +import os +import tempfile + +import aiohttp +import discord +import qrcode +from discord.ext import commands + +logger = logging.getLogger(__name__) + +try: + from PIL import Image + from pyzbar.pyzbar import decode as decode_qr + + DECODE_AVAILABLE = True +except ImportError: + DECODE_AVAILABLE = False + logger.warning("pyzbar or PIL not available, decode command will be disabled") + + +class QR(commands.Cog): + def __init__(self, bot): + self.bot = bot + + async def _download_image(self, url): + """Download image from URL to temporary file""" + async with aiohttp.ClientSession() as session: + async with session.get(url) as response: + if response.status == 200: + content = await response.read() + with tempfile.NamedTemporaryFile(delete=False, suffix=".png") as f: + f.write(content) + return f.name + return None + + @commands.slash_command( + name="qr_generuj", description="Generuje kod QR z podanego tekstu" + ) + async def qr( + self, + ctx, + text: str = discord.Option(str, "Tekst do zakodowania", required=True), + ): + await ctx.defer() + + temp_dir = os.path.join(os.path.dirname(os.path.dirname(__file__)), "temp") + os.makedirs(temp_dir, exist_ok=True) + temp_path = os.path.join( + temp_dir, f"qr_{os.getpid()}_{asyncio.get_event_loop().time()}.png" + ) + + try: + loop = asyncio.get_event_loop() + await loop.run_in_executor(None, self._generate_qr, text, temp_path) + await ctx.respond(file=discord.File(temp_path, filename="qr.png")) + logger.info(f"Generated QR for text: {text}") + except Exception as e: + logger.error(f"QR generation error: {e}") + await ctx.respond("Error generating QR code", ephemeral=True) + finally: + try: + if os.path.exists(temp_path): + os.unlink(temp_path) + except Exception as e: + logger.error(f"Error cleaning up temp file: {e}") + + def _generate_qr(self, text, output_path): + """Generate QR code (runs in executor)""" + qr = qrcode.QRCode( + version=1, + error_correction=qrcode.constants.ERROR_CORRECT_L, + box_size=10, + border=4, + ) + qr.add_data(text) + qr.make(fit=True) + img = qr.make_image(fill_color="black", back_color="white") + img.save(output_path) + + @commands.slash_command(name="qr_dekoduj", description="Dekoduje kod QR z obrazka") + async def decode( + self, + ctx, + image: discord.Attachment = discord.Option( + discord.Attachment, "Obrazek z kodem QR", required=True + ), + ): + if not DECODE_AVAILABLE: + await ctx.respond( + "Dekodowanie nie jest dostępne (brak biblioteki pyzbar)", ephemeral=True + ) + return + + await ctx.defer() + + try: + temp_path = await self._download_image(image.url) + if not temp_path: + await ctx.respond("Nie udało się pobrać obrazka", ephemeral=True) + return + + try: + loop = asyncio.get_event_loop() + decoded = await loop.run_in_executor(None, self._decode_qr, temp_path) + + if decoded: + await ctx.respond(f"```\n{decoded}\n```") + logger.info(f"Decoded QR: {decoded}") + else: + await ctx.respond( + "Nie znaleziono kodu QR na obrazku", ephemeral=True + ) + finally: + if os.path.exists(temp_path): + os.unlink(temp_path) + except Exception as e: + logger.error(f"QR decode error: {e}") + await ctx.respond("Error dekodowania QR", ephemeral=True) + + def _decode_qr(self, image_path): + """Decode QR code from image (runs in executor)""" + try: + from PIL import Image + + img = Image.open(image_path) + decoded_list = decode_qr(img) + if decoded_list: + return decoded_list[0].data.decode("utf-8") + except Exception as e: + logger.error(f"Error decoding QR: {e}") + return None + + +def setup(bot): + bot.add_cog(QR(bot)) -- cgit v1.2.3