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 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ requirements.txt | 9 +++- 2 files changed, 150 insertions(+), 1 deletion(-) create mode 100644 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)) diff --git a/requirements.txt b/requirements.txt index e3883e0..e5dc093 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,6 +10,8 @@ certifi==2026.2.25 cffi==2.0.0 charset-normalizer==3.4.6 click==8.1.8 +croniter==6.2.2 +davey==0.1.4 dotenv==0.9.9 frozenlist==1.8.0 gTTS==2.5.4 @@ -19,14 +21,19 @@ httpx==0.28.1 idna==3.11 multidict==6.7.1 mutagen==1.47.0 +pillow==12.2.0 propcache==0.4.1 -py-cord==2.7.1 +py-cord @ file:///home/salmon/Documents/botcode/senator-sperma/pycord pycparser==3.0 PyNaCl==1.6.2 +python-dateutil==2.9.0.post0 python-dotenv==1.2.2 pytube==15.0.0 +pyzbar==0.1.9 +qrcode==8.2 RapidFuzz==3.14.3 requests==2.32.5 +six==1.17.0 soupsieve==2.8.3 typing_extensions==4.15.0 urllib3==2.6.3 -- cgit v1.2.3