2024-07-22 04:35:33 +00:00
|
|
|
from collections import OrderedDict
|
|
|
|
|
2024-07-16 12:30:38 +00:00
|
|
|
import ujson
|
2024-03-16 10:30:34 +00:00
|
|
|
from datetime import datetime, timedelta
|
2024-06-26 16:01:49 +00:00
|
|
|
from enum import Enum
|
2023-03-22 05:06:05 +00:00
|
|
|
from io import BytesIO
|
2024-07-18 14:40:42 +00:00
|
|
|
from typing import Any, List, Optional, Dict
|
2022-07-26 10:07:31 +00:00
|
|
|
|
2023-03-14 01:27:22 +00:00
|
|
|
from PIL import Image, UnidentifiedImageError
|
2022-10-08 00:59:08 +00:00
|
|
|
from pydantic import BaseModel, PrivateAttr
|
2022-07-26 10:07:31 +00:00
|
|
|
|
2024-06-26 16:01:49 +00:00
|
|
|
__all__ = (
|
|
|
|
"ArtworkImage",
|
|
|
|
"PostInfo",
|
|
|
|
"LiveInfo",
|
|
|
|
"LiveCode",
|
|
|
|
"LiveCodeHoYo",
|
|
|
|
"PostTypeEnum",
|
|
|
|
"PostRecommend",
|
|
|
|
"HoYoPostMultiLang",
|
|
|
|
)
|
2022-12-10 12:37:43 +00:00
|
|
|
|
2022-07-26 10:07:31 +00:00
|
|
|
|
2022-10-08 00:59:08 +00:00
|
|
|
class ArtworkImage(BaseModel):
|
|
|
|
art_id: int
|
|
|
|
page: int = 0
|
|
|
|
data: bytes = b""
|
2023-04-14 03:53:15 +00:00
|
|
|
file_name: Optional[str] = None
|
|
|
|
file_extension: Optional[str] = None
|
2022-10-08 00:59:08 +00:00
|
|
|
is_error: bool = False
|
2024-07-22 04:35:33 +00:00
|
|
|
url: str = ""
|
2022-07-26 10:07:31 +00:00
|
|
|
|
2022-10-08 00:59:08 +00:00
|
|
|
@property
|
2023-03-14 01:27:22 +00:00
|
|
|
def format(self) -> Optional[str]:
|
|
|
|
if not self.is_error:
|
|
|
|
try:
|
2023-03-22 05:06:05 +00:00
|
|
|
with BytesIO(self.data) as stream, Image.open(stream) as im:
|
2023-03-14 01:27:22 +00:00
|
|
|
return im.format
|
|
|
|
except UnidentifiedImageError:
|
|
|
|
pass
|
|
|
|
return None
|
2023-02-18 07:41:10 +00:00
|
|
|
|
2023-09-20 08:34:02 +00:00
|
|
|
@staticmethod
|
|
|
|
def gen(*args, **kwargs) -> List["ArtworkImage"]:
|
|
|
|
data = [ArtworkImage(*args, **kwargs)]
|
|
|
|
if data[0].file_extension and data[0].file_extension in ["gif", "mp4"]:
|
|
|
|
return data
|
|
|
|
try:
|
|
|
|
with BytesIO(data[0].data) as stream, Image.open(stream) as image:
|
|
|
|
width, height = image.size
|
|
|
|
crop_height = height
|
|
|
|
crop_num = 1
|
|
|
|
max_height = 10000 - width
|
|
|
|
while crop_height > max_height:
|
|
|
|
crop_num += 1
|
|
|
|
crop_height = height / crop_num
|
|
|
|
new_data = []
|
|
|
|
for i in range(crop_num):
|
|
|
|
slice_image = image.crop((0, crop_height * i, width, crop_height * (i + 1)))
|
|
|
|
bio = BytesIO()
|
|
|
|
slice_image.save(bio, "png")
|
|
|
|
kwargs["data"] = bio.getvalue()
|
|
|
|
kwargs["file_extension"] = "png"
|
|
|
|
new_data.append(ArtworkImage(*args, **kwargs))
|
|
|
|
return new_data
|
|
|
|
except UnidentifiedImageError:
|
|
|
|
return data
|
|
|
|
|
2022-10-08 00:59:08 +00:00
|
|
|
|
|
|
|
class PostInfo(BaseModel):
|
|
|
|
_data: dict = PrivateAttr()
|
2024-06-26 16:01:49 +00:00
|
|
|
hoyolab: bool
|
2022-10-08 00:59:08 +00:00
|
|
|
post_id: int
|
|
|
|
user_uid: int
|
|
|
|
subject: str
|
|
|
|
image_urls: List[str]
|
|
|
|
created_at: int
|
2023-03-14 01:27:22 +00:00
|
|
|
video_urls: List[str]
|
2024-07-16 12:30:38 +00:00
|
|
|
content: str
|
2022-10-08 00:59:08 +00:00
|
|
|
|
|
|
|
def __init__(self, _data: dict, **data: Any):
|
|
|
|
super().__init__(**data)
|
|
|
|
self._data = _data
|
|
|
|
|
2024-07-16 12:30:38 +00:00
|
|
|
@staticmethod
|
|
|
|
def parse_structured_content(data: List[Dict]) -> str:
|
|
|
|
content = []
|
|
|
|
for item in data:
|
|
|
|
if not item or item.get("insert") is None:
|
|
|
|
continue
|
|
|
|
insert = item["insert"]
|
|
|
|
if isinstance(insert, str):
|
|
|
|
if attr := item.get("attributes"):
|
|
|
|
if link := attr.get("link"):
|
|
|
|
content.append(f'<p><a href="{link}">{insert}</a></p>')
|
|
|
|
continue
|
|
|
|
content.append(f"<p>{insert}</p>")
|
|
|
|
elif isinstance(insert, dict):
|
|
|
|
if image := insert.get("image"):
|
|
|
|
content.append(f'<img src="{image}" />')
|
|
|
|
return "\n".join(content)
|
|
|
|
|
2022-10-08 00:59:08 +00:00
|
|
|
@classmethod
|
2024-06-26 16:01:49 +00:00
|
|
|
def paste_data(cls, data: dict, hoyolab: bool = False) -> "PostInfo":
|
2022-10-08 00:59:08 +00:00
|
|
|
_data_post = data["post"]
|
|
|
|
post = _data_post["post"]
|
|
|
|
post_id = post["post_id"]
|
|
|
|
subject = post["subject"]
|
2024-07-16 12:30:38 +00:00
|
|
|
image_list = []
|
|
|
|
image_keys = {"cover_list", "image_list"}
|
|
|
|
for key in image_keys:
|
|
|
|
image_list.extend(_data_post.get(key, []))
|
2024-07-22 04:35:33 +00:00
|
|
|
image_urls = list(OrderedDict.fromkeys([image["url"] for image in image_list]))
|
2024-07-16 12:30:38 +00:00
|
|
|
key1, key2 = ("video", "resolution") if hoyolab else ("vod_list", "resolutions")
|
|
|
|
vod_list = _data_post.get(key1, [])
|
|
|
|
if not isinstance(vod_list, list):
|
|
|
|
vod_list = [vod_list]
|
|
|
|
video_urls = [vod[key2][-1]["url"] for vod in vod_list if vod]
|
2022-10-08 00:59:08 +00:00
|
|
|
created_at = post["created_at"]
|
|
|
|
user = _data_post["user"] # 用户数据
|
|
|
|
user_uid = user["uid"] # 用户ID
|
2024-07-16 12:30:38 +00:00
|
|
|
content = post["content"]
|
|
|
|
if hoyolab and ("<" not in content) and (structured_content := post.get("structured_content")):
|
|
|
|
content = PostInfo.parse_structured_content(ujson.loads(structured_content))
|
|
|
|
if hoyolab and post["view_type"] == 5:
|
|
|
|
# video
|
|
|
|
content = ujson.loads(content).get("describe", "")
|
2022-10-08 00:59:08 +00:00
|
|
|
return PostInfo(
|
|
|
|
_data=data,
|
2024-06-26 16:01:49 +00:00
|
|
|
hoyolab=hoyolab,
|
2022-10-08 00:59:08 +00:00
|
|
|
post_id=post_id,
|
|
|
|
user_uid=user_uid,
|
|
|
|
subject=subject,
|
|
|
|
image_urls=image_urls,
|
2023-02-18 07:41:10 +00:00
|
|
|
video_urls=video_urls,
|
2022-10-08 00:59:08 +00:00
|
|
|
created_at=created_at,
|
2024-07-16 12:30:38 +00:00
|
|
|
content=content,
|
2022-10-08 00:59:08 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
def __getitem__(self, item):
|
|
|
|
return self._data[item]
|
2024-03-03 14:05:13 +00:00
|
|
|
|
2024-06-26 16:01:49 +00:00
|
|
|
@property
|
|
|
|
def type_enum(self) -> "PostTypeEnum":
|
|
|
|
return PostTypeEnum.CN if not self.hoyolab else PostTypeEnum.OS
|
|
|
|
|
|
|
|
def get_url(self, short_name: str) -> str:
|
|
|
|
if not self.hoyolab:
|
|
|
|
return f"https://www.miyoushe.com/{short_name}/article/{self.post_id}"
|
|
|
|
return f"https://www.hoyolab.com/article/{self.post_id}"
|
|
|
|
|
|
|
|
def get_fix_url(self, short_name: str) -> str:
|
|
|
|
url = self.get_url(short_name)
|
|
|
|
return url.replace(".com/", ".pp.ua/")
|
|
|
|
|
2024-03-03 14:05:13 +00:00
|
|
|
|
|
|
|
class LiveInfo(BaseModel):
|
|
|
|
act_type: str
|
|
|
|
title: str
|
|
|
|
live_time: str
|
|
|
|
start: datetime
|
|
|
|
end: datetime
|
|
|
|
remain: int
|
|
|
|
now: datetime
|
|
|
|
is_end: bool
|
|
|
|
code_ver: str
|
|
|
|
|
|
|
|
|
|
|
|
class LiveCode(BaseModel):
|
|
|
|
code: str
|
|
|
|
to_get_time: datetime
|
|
|
|
|
|
|
|
@property
|
|
|
|
def text(self) -> str:
|
|
|
|
return self.code if self.code else "XXXXXXXXXXXX"
|
|
|
|
|
|
|
|
|
|
|
|
class LiveCodeHoYo(BaseModel):
|
|
|
|
exchange_code: str
|
|
|
|
offline_at: datetime
|
|
|
|
|
|
|
|
@property
|
|
|
|
def text(self) -> str:
|
|
|
|
return self.exchange_code if self.exchange_code else "XXXXXXXXXXXX"
|
2024-03-16 10:30:34 +00:00
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def guess_offline_at() -> datetime:
|
|
|
|
return datetime.now().replace(hour=23, minute=59, second=59, microsecond=999999) + timedelta(days=1)
|
2024-06-26 16:01:49 +00:00
|
|
|
|
|
|
|
|
|
|
|
class PostTypeEnum(str, Enum):
|
|
|
|
"""社区类型枚举"""
|
|
|
|
|
|
|
|
NULL = "null"
|
|
|
|
CN = "cn"
|
|
|
|
OS = "os"
|
|
|
|
|
|
|
|
|
|
|
|
class HoYoPostMultiLang(BaseModel):
|
|
|
|
lang_subject: dict
|
|
|
|
|
|
|
|
|
|
|
|
class PostRecommend(BaseModel):
|
|
|
|
hoyolab: bool = False
|
|
|
|
post_id: int
|
2024-07-18 14:40:42 +00:00
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def parse(data: Dict, hoyolab: bool = False):
|
|
|
|
_post = data.get("post")
|
|
|
|
post_id = _post.get("post_id")
|
|
|
|
return PostRecommend(hoyolab=hoyolab, post_id=post_id)
|