Spaces:
Runtime error
Runtime error
File size: 4,577 Bytes
0a1b571 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 |
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])
@enum_auto_doc
class QRCodeLevel(str, Enum):
"""ไบ็ปด็ ๅฎน้็"""
LOW = "L"
"""ๆไฝๅฎน้็"""
MEDIUM = "M"
"""ไธญ็ญๅฎน้็"""
QUARTILE = "Q"
"""้ซๅฎน้็"""
HIGH = "H"
"""ๆ้ซๅฎน้็"""
@enum_auto_doc
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"
@classmethod
@validate_arguments
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(),
),
)
@classmethod
@ToAsync
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
|