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 | |