# 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__) _pil_image = None _decode_func = None _decode_available = False def _ensure_qr(): global _pil_image, _decode_func, _decode_available if _pil_image is not None: return _decode_available try: from PIL import Image from pyzbar.pyzbar import decode as _decode_func _pil_image = Image _decode_available = True except ImportError: _decode_available = False logger.warning("pyzbar or PIL not available, decode command will be disabled") return _decode_available 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() from cogs.utils.temp import get_temp_dir temp_path = os.path.join( get_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, # type: ignore[attr-defined] 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 _ensure_qr(): 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)""" if not _ensure_qr(): return None try: img = _pil_image.open(image_path) decoded_list = _decode_func(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))