Spaces:
Runtime error
Runtime error
| from datetime import datetime | |
| from enum import Enum | |
| from io import BytesIO | |
| from os import fdopen | |
| from pathlib import Path | |
| from typing import Literal, Optional, cast | |
| from PIL import Image | |
| from pydantic import AnyHttpUrl, BaseModel, Field, validate_arguments | |
| from pydantic.color import Color | |
| from qrcode import constants | |
| from qrcode.image.pil import PilImage | |
| from qrcode.main import QRCode | |
| from hibiapi.utils.config import APIConfig | |
| from hibiapi.utils.decorators import ToAsync, enum_auto_doc | |
| from hibiapi.utils.exceptions import ClientSideException | |
| from hibiapi.utils.net import BaseNetClient | |
| from hibiapi.utils.routing import BaseHostUrl | |
| from hibiapi.utils.temp import TempFile | |
| Config = APIConfig("qrcode") | |
| class HostUrl(BaseHostUrl): | |
| allowed_hosts = Config["qrcode"]["icon-site"].get(list[str]) | |
| class QRCodeLevel(str, Enum): | |
| """ไบ็ปด็ ๅฎน้็""" | |
| LOW = "L" | |
| """ๆไฝๅฎน้็""" | |
| MEDIUM = "M" | |
| """ไธญ็ญๅฎน้็""" | |
| QUARTILE = "Q" | |
| """้ซๅฎน้็""" | |
| HIGH = "H" | |
| """ๆ้ซๅฎน้็""" | |
| class ReturnEncode(str, Enum): | |
| """ไบ็ปด็ ่ฟๅ็็ผ็ ๆนๅผ""" | |
| raw = "raw" | |
| """็ดๆฅ้ๅฎๅๅฐไบ็ปด็ ๅพ็""" | |
| json = "json" | |
| """่ฟๅJSONๆ ผๅผ็ไบ็ปด็ ไฟกๆฏ""" | |
| js = "js" | |
| jsc = "jsc" | |
| COLOR_WHITE = Color("FFFFFF") | |
| COLOR_BLACK = Color("000000") | |
| class QRInfo(BaseModel): | |
| url: Optional[AnyHttpUrl] = None | |
| path: Path | |
| time: datetime = Field(default_factory=datetime.now) | |
| data: str | |
| logo: Optional[HostUrl] = None | |
| level: QRCodeLevel = QRCodeLevel.MEDIUM | |
| size: int = 200 | |
| code: Literal[0] = 0 | |
| status: Literal["success"] = "success" | |
| async def new( | |
| cls, | |
| text: str, | |
| *, | |
| size: int = Field( | |
| 200, | |
| gt=Config["qrcode"]["min-size"].as_number(), | |
| lt=Config["qrcode"]["max-size"].as_number(), | |
| ), | |
| logo: Optional[HostUrl] = None, | |
| level: QRCodeLevel = QRCodeLevel.MEDIUM, | |
| bgcolor: Color = COLOR_WHITE, | |
| fgcolor: Color = COLOR_BLACK, | |
| ): | |
| icon_stream = None | |
| if logo is not None: | |
| async with BaseNetClient() as client: | |
| response = await client.get( | |
| logo, headers={"user-agent": "HibiAPI@GitHub"}, timeout=6 | |
| ) | |
| response.raise_for_status() | |
| icon_stream = BytesIO(response.content) | |
| return cls( | |
| data=text, | |
| logo=logo, | |
| level=level, | |
| size=size, | |
| path=await cls._generate( | |
| text, | |
| size=size, | |
| level=level, | |
| icon_stream=icon_stream, | |
| bgcolor=bgcolor.as_hex(), | |
| fgcolor=fgcolor.as_hex(), | |
| ), | |
| ) | |
| def _generate( | |
| cls, | |
| text: str, | |
| *, | |
| size: int = 200, | |
| level: QRCodeLevel = QRCodeLevel.MEDIUM, | |
| icon_stream: Optional[BytesIO] = None, | |
| bgcolor: str = "#FFFFFF", | |
| fgcolor: str = "#000000", | |
| ) -> Path: | |
| qr = QRCode( | |
| error_correction={ | |
| QRCodeLevel.LOW: constants.ERROR_CORRECT_L, | |
| QRCodeLevel.MEDIUM: constants.ERROR_CORRECT_M, | |
| QRCodeLevel.QUARTILE: constants.ERROR_CORRECT_Q, | |
| QRCodeLevel.HIGH: constants.ERROR_CORRECT_H, | |
| }[level], | |
| border=2, | |
| box_size=8, | |
| ) | |
| qr.add_data(text) | |
| image = cast( | |
| Image.Image, | |
| qr.make_image( | |
| PilImage, | |
| back_color=bgcolor, | |
| fill_color=fgcolor, | |
| ).get_image(), | |
| ) | |
| image = image.resize((size, size)) | |
| if icon_stream is not None: | |
| try: | |
| icon = Image.open(icon_stream) | |
| except ValueError as e: | |
| raise ClientSideException("Invalid image format.") from e | |
| icon_width, icon_height = icon.size | |
| image.paste( | |
| icon, | |
| box=( | |
| int(size / 2 - icon_width / 2), | |
| int(size / 2 - icon_height / 2), | |
| int(size / 2 + icon_width / 2), | |
| int(size / 2 + icon_height / 2), | |
| ), | |
| mask=icon if icon.mode == "RGBA" else None, | |
| ) | |
| descriptor, path = TempFile.create(".png") | |
| with fdopen(descriptor, "wb") as f: | |
| image.save(f, format="PNG") | |
| return path | |