mirror of
https://github.com/PaiGramTeam/PamGram.git
synced 2024-11-21 05:41:43 +00:00
✨ Support hoyolab post plugin
This commit is contained in:
parent
5f81e8c810
commit
29f71fb0ec
@ -40,8 +40,10 @@ OWNER=0
|
||||
# 记录错误并发送消息通知开发人员 可选配置项
|
||||
# ERROR_NOTIFICATION_CHAT_ID=chat_id
|
||||
|
||||
# 文章推送群组 可选配置项
|
||||
# 文章推送频道 可选配置项
|
||||
# CHANNELS=[]
|
||||
# 文章推送群组 可选配置项
|
||||
# POST_CHAT_ID=0
|
||||
# 消息帮助频道 可选配置项
|
||||
# CHANNELS_HELPER=0
|
||||
|
||||
|
68
modules/apihelper/client/components/hoyolab.py
Normal file
68
modules/apihelper/client/components/hoyolab.py
Normal file
@ -0,0 +1,68 @@
|
||||
from typing import List
|
||||
|
||||
from .hyperion import HyperionBase
|
||||
from ..base.hyperionrequest import HyperionRequest
|
||||
from ...models.genshin.hyperion import PostInfo, ArtworkImage, PostRecommend, HoYoPostMultiLang
|
||||
|
||||
__all__ = ("Hoyolab",)
|
||||
|
||||
|
||||
class Hoyolab(HyperionBase):
|
||||
POST_FULL_URL = "https://bbs-api-os.hoyolab.com/community/post/wapi/getPostFull"
|
||||
NEW_LIST_URL = "https://bbs-api-os.hoyolab.com/community/post/wapi/getNewsList"
|
||||
NEW_BG_URL = "https://bbs-api-os.hoyolab.com/community/painter/wapi/circle/info"
|
||||
LANG = "zh-cn"
|
||||
USER_AGENT = (
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) "
|
||||
"Chrome/90.0.4430.72 Safari/537.36"
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.client = HyperionRequest(headers=self.get_headers(), *args, **kwargs)
|
||||
|
||||
def get_headers(self, lang: str = LANG):
|
||||
return {
|
||||
"User-Agent": self.USER_AGENT,
|
||||
"Referer": "https://www.hoyolab.com/",
|
||||
"X-Rpc-Language": lang,
|
||||
}
|
||||
|
||||
async def get_official_recommended_posts(
|
||||
self, gids: int, page_size: int = 3, type_: int = 1
|
||||
) -> List[PostRecommend]:
|
||||
params = {"gids": gids, "page_size": page_size, "type": type_}
|
||||
response = await self.client.get(url=self.NEW_LIST_URL, params=params)
|
||||
return [
|
||||
PostRecommend(
|
||||
hoyolab=True,
|
||||
post_id=data["post"]["post_id"],
|
||||
subject=data["post"]["subject"],
|
||||
multi_language_info=HoYoPostMultiLang(**data["post"]["multi_language_info"]),
|
||||
)
|
||||
for data in response["list"]
|
||||
]
|
||||
|
||||
async def get_post_info(self, gids: int, post_id: int, read: int = 1, scene: int = 1, lang: str = LANG) -> PostInfo:
|
||||
params = {"post_id": post_id, "read": read, "scene": scene}
|
||||
response = await self.client.get(self.POST_FULL_URL, params=params, headers=self.get_headers(lang=lang))
|
||||
return PostInfo.paste_data(response, hoyolab=True)
|
||||
|
||||
async def get_images_by_post_id(self, gids: int, post_id: int) -> List[ArtworkImage]:
|
||||
post_info = await self.get_post_info(gids, post_id)
|
||||
task_list = [
|
||||
self._download_image(post_info.post_id, post_info.image_urls[page], page)
|
||||
for page in range(len(post_info.image_urls))
|
||||
]
|
||||
return await self.get_images_by_post_id_tasks(task_list)
|
||||
|
||||
async def _download_image(self, art_id: int, url: str, page: int = 0) -> List[ArtworkImage]:
|
||||
return await self.download_image(self.client, art_id, url, page)
|
||||
|
||||
async def close(self):
|
||||
await self.client.shutdown()
|
||||
|
||||
async def __aenter__(self):
|
||||
return self
|
||||
|
||||
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
||||
await self.close()
|
@ -1,40 +1,31 @@
|
||||
import asyncio
|
||||
import os
|
||||
import re
|
||||
from abc import abstractmethod
|
||||
from time import time
|
||||
from typing import List
|
||||
from typing import List, Tuple
|
||||
|
||||
from ..base.hyperionrequest import HyperionRequest
|
||||
from ...models.genshin.hyperion import PostInfo, ArtworkImage, LiveInfo, LiveCode, LiveCodeHoYo
|
||||
from ...models.genshin.hyperion import (
|
||||
PostInfo,
|
||||
ArtworkImage,
|
||||
LiveInfo,
|
||||
LiveCode,
|
||||
LiveCodeHoYo,
|
||||
PostRecommend,
|
||||
PostTypeEnum,
|
||||
)
|
||||
from ...typedefs import JSON_DATA
|
||||
|
||||
__all__ = ("Hyperion",)
|
||||
__all__ = (
|
||||
"HyperionBase",
|
||||
"Hyperion",
|
||||
)
|
||||
|
||||
|
||||
class Hyperion:
|
||||
"""米忽悠bbs相关API请求
|
||||
|
||||
该名称来源于米忽悠的安卓BBS包名结尾,考虑到大部分重要的功能确实是在移动端实现了
|
||||
"""
|
||||
|
||||
POST_FULL_URL = "https://bbs-api.miyoushe.com/post/wapi/getPostFull"
|
||||
POST_FULL_IN_COLLECTION_URL = "https://bbs-api.miyoushe.com/post/wapi/getPostFullInCollection"
|
||||
GET_NEW_LIST_URL = "https://bbs-api.miyoushe.com/post/wapi/getNewsList"
|
||||
GET_OFFICIAL_RECOMMENDED_POSTS_URL = "https://bbs-api.miyoushe.com/post/wapi/getOfficialRecommendedPosts"
|
||||
LIVE_INFO_URL = "https://api-takumi.mihoyo.com/event/miyolive/index"
|
||||
LIVE_CODE_URL = "https://api-takumi-static.mihoyo.com/event/miyolive/refreshCode"
|
||||
LIVE_CODE_HOYO_URL = "https://bbs-api-os.hoyolab.com/community/painter/wapi/circle/channel/guide/material"
|
||||
|
||||
USER_AGENT = (
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) "
|
||||
"Chrome/90.0.4430.72 Safari/537.36"
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.client = HyperionRequest(headers=self.get_headers(), *args, **kwargs)
|
||||
|
||||
class HyperionBase:
|
||||
@staticmethod
|
||||
def extract_post_id(text: str) -> int:
|
||||
def extract_post_id(text: str) -> Tuple[int, PostTypeEnum]:
|
||||
"""
|
||||
:param text:
|
||||
# https://bbs.mihoyo.com/ys/article/8808224
|
||||
@ -44,20 +35,19 @@ class Hyperion:
|
||||
:return: post_id
|
||||
"""
|
||||
rgx = re.compile(r"(?:bbs|www\.)?(?:miyoushe|mihoyo)\.(.*)/[^.]+/article/(?P<article_id>\d+)")
|
||||
matches = rgx.search(text)
|
||||
rgx2 = re.compile(r"(?:bbs|www\.)?(?:hoyolab|hoyoverse)\.(.*)/article/(?P<article_id>\d+)")
|
||||
matches = rgx.search(text) or rgx2.search(text)
|
||||
if matches is None:
|
||||
return -1
|
||||
return -1, PostTypeEnum.NULL
|
||||
entries = matches.groupdict()
|
||||
if entries is None:
|
||||
return -1
|
||||
return -1, PostTypeEnum.NULL
|
||||
try:
|
||||
art_id = int(entries.get("article_id"))
|
||||
post_type = PostTypeEnum.CN if "miyoushe" in text or "mihoyo" in text else PostTypeEnum.OS
|
||||
except (IndexError, ValueError, TypeError):
|
||||
return -1
|
||||
return art_id
|
||||
|
||||
def get_headers(self, referer: str = "https://www.miyoushe.com/ys/"):
|
||||
return {"User-Agent": self.USER_AGENT, "Referer": referer}
|
||||
return -1, PostTypeEnum.NULL
|
||||
return art_id, post_type
|
||||
|
||||
@staticmethod
|
||||
def get_list_url_params(forum_id: int, is_good: bool = False, is_hot: bool = False, page_size: int = 20) -> dict:
|
||||
@ -89,10 +79,79 @@ class Hyperion:
|
||||
)
|
||||
return {"x-oss-process": params}
|
||||
|
||||
async def get_official_recommended_posts(self, gids: int) -> JSON_DATA:
|
||||
@staticmethod
|
||||
async def get_images_by_post_id_tasks(task_list: List) -> List[ArtworkImage]:
|
||||
art_list = []
|
||||
result_lists = await asyncio.gather(*task_list)
|
||||
for result_list in result_lists:
|
||||
for result in result_list:
|
||||
if isinstance(result, ArtworkImage):
|
||||
art_list.append(result)
|
||||
|
||||
def take_page(elem: ArtworkImage):
|
||||
return elem.page
|
||||
|
||||
art_list.sort(key=take_page)
|
||||
return art_list
|
||||
|
||||
@staticmethod
|
||||
async def download_image(client: "HyperionRequest", art_id: int, url: str, page: int = 0) -> List[ArtworkImage]:
|
||||
filename = os.path.basename(url)
|
||||
_, file_extension = os.path.splitext(filename)
|
||||
is_image = bool(file_extension in ".jpg" or file_extension in ".png")
|
||||
response = await client.get(
|
||||
url, params=Hyperion.get_images_params(resize=2000) if is_image else None, de_json=False
|
||||
)
|
||||
return ArtworkImage.gen(
|
||||
art_id=art_id, page=page, file_name=filename, file_extension=url.split(".")[-1], data=response.content
|
||||
)
|
||||
|
||||
@abstractmethod
|
||||
async def get_official_recommended_posts(self, gids: int) -> List[PostRecommend]:
|
||||
"""获取官方推荐帖子"""
|
||||
|
||||
@abstractmethod
|
||||
async def get_post_info(self, gids: int, post_id: int, read: int = 1) -> PostInfo:
|
||||
"""获取帖子信息"""
|
||||
|
||||
@abstractmethod
|
||||
async def get_images_by_post_id(self, gids: int, post_id: int) -> List[ArtworkImage]:
|
||||
"""获取帖子图片"""
|
||||
|
||||
@abstractmethod
|
||||
async def close(self):
|
||||
"""关闭请求会话"""
|
||||
|
||||
|
||||
class Hyperion(HyperionBase):
|
||||
"""米忽悠bbs相关API请求
|
||||
|
||||
该名称来源于米忽悠的安卓BBS包名结尾,考虑到大部分重要的功能确实是在移动端实现了
|
||||
"""
|
||||
|
||||
POST_FULL_URL = "https://bbs-api.miyoushe.com/post/wapi/getPostFull"
|
||||
POST_FULL_IN_COLLECTION_URL = "https://bbs-api.miyoushe.com/post/wapi/getPostFullInCollection"
|
||||
GET_NEW_LIST_URL = "https://bbs-api.miyoushe.com/post/wapi/getNewsList"
|
||||
GET_OFFICIAL_RECOMMENDED_POSTS_URL = "https://bbs-api.miyoushe.com/post/wapi/getOfficialRecommendedPosts"
|
||||
LIVE_INFO_URL = "https://api-takumi.mihoyo.com/event/miyolive/index"
|
||||
LIVE_CODE_URL = "https://api-takumi-static.mihoyo.com/event/miyolive/refreshCode"
|
||||
LIVE_CODE_HOYO_URL = "https://bbs-api-os.hoyolab.com/community/painter/wapi/circle/channel/guide/material"
|
||||
|
||||
USER_AGENT = (
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) "
|
||||
"Chrome/90.0.4430.72 Safari/537.36"
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.client = HyperionRequest(headers=self.get_headers(), *args, **kwargs)
|
||||
|
||||
def get_headers(self, referer: str = "https://www.miyoushe.com/ys/"):
|
||||
return {"User-Agent": self.USER_AGENT, "Referer": referer}
|
||||
|
||||
async def get_official_recommended_posts(self, gids: int) -> List[PostRecommend]:
|
||||
params = {"gids": gids}
|
||||
response = await self.client.get(url=self.GET_OFFICIAL_RECOMMENDED_POSTS_URL, params=params)
|
||||
return response
|
||||
return [PostRecommend(**data) for data in response["list"]]
|
||||
|
||||
async def get_post_full_in_collection(self, collection_id: int, gids: int = 2, order_type=1) -> JSON_DATA:
|
||||
params = {"collection_id": collection_id, "gids": gids, "order_type": order_type}
|
||||
@ -106,33 +165,14 @@ class Hyperion:
|
||||
|
||||
async def get_images_by_post_id(self, gids: int, post_id: int) -> List[ArtworkImage]:
|
||||
post_info = await self.get_post_info(gids, post_id)
|
||||
art_list = []
|
||||
task_list = [
|
||||
self.download_image(post_info.post_id, post_info.image_urls[page], page)
|
||||
self._download_image(post_info.post_id, post_info.image_urls[page], page)
|
||||
for page in range(len(post_info.image_urls))
|
||||
]
|
||||
result_lists = await asyncio.gather(*task_list)
|
||||
for result_list in result_lists:
|
||||
for result in result_list:
|
||||
if isinstance(result, ArtworkImage):
|
||||
art_list.append(result)
|
||||
return await self.get_images_by_post_id_tasks(task_list)
|
||||
|
||||
def take_page(elem: ArtworkImage):
|
||||
return elem.page
|
||||
|
||||
art_list.sort(key=take_page)
|
||||
return art_list
|
||||
|
||||
async def download_image(self, art_id: int, url: str, page: int = 0) -> List[ArtworkImage]:
|
||||
filename = os.path.basename(url)
|
||||
_, file_extension = os.path.splitext(filename)
|
||||
is_image = bool(file_extension in ".jpg" or file_extension in ".png")
|
||||
response = await self.client.get(
|
||||
url, params=self.get_images_params(resize=2000) if is_image else None, de_json=False
|
||||
)
|
||||
return ArtworkImage.gen(
|
||||
art_id=art_id, page=page, file_name=filename, file_extension=url.split(".")[-1], data=response.content
|
||||
)
|
||||
async def _download_image(self, art_id: int, url: str, page: int = 0) -> List[ArtworkImage]:
|
||||
return await self.download_image(self.client, art_id, url, page)
|
||||
|
||||
async def get_new_list(self, gids: int, type_id: int, page_size: int = 20):
|
||||
"""
|
||||
|
@ -1,11 +1,21 @@
|
||||
from datetime import datetime, timedelta
|
||||
from enum import Enum
|
||||
from io import BytesIO
|
||||
from typing import Any, List, Optional
|
||||
|
||||
from PIL import Image, UnidentifiedImageError
|
||||
from pydantic import BaseModel, PrivateAttr
|
||||
|
||||
__all__ = ("ArtworkImage", "PostInfo", "LiveInfo", "LiveCode", "LiveCodeHoYo")
|
||||
__all__ = (
|
||||
"ArtworkImage",
|
||||
"PostInfo",
|
||||
"LiveInfo",
|
||||
"LiveCode",
|
||||
"LiveCodeHoYo",
|
||||
"PostTypeEnum",
|
||||
"PostRecommend",
|
||||
"HoYoPostMultiLang",
|
||||
)
|
||||
|
||||
|
||||
class ArtworkImage(BaseModel):
|
||||
@ -55,6 +65,7 @@ class ArtworkImage(BaseModel):
|
||||
|
||||
class PostInfo(BaseModel):
|
||||
_data: dict = PrivateAttr()
|
||||
hoyolab: bool
|
||||
post_id: int
|
||||
user_uid: int
|
||||
subject: str
|
||||
@ -67,20 +78,21 @@ class PostInfo(BaseModel):
|
||||
self._data = _data
|
||||
|
||||
@classmethod
|
||||
def paste_data(cls, data: dict) -> "PostInfo":
|
||||
def paste_data(cls, data: dict, hoyolab: bool = False) -> "PostInfo":
|
||||
_data_post = data["post"]
|
||||
post = _data_post["post"]
|
||||
post_id = post["post_id"]
|
||||
subject = post["subject"]
|
||||
image_list = _data_post["image_list"]
|
||||
image_urls = [image["url"] for image in image_list]
|
||||
vod_list = _data_post["vod_list"]
|
||||
vod_list = _data_post.get("vod_list", [])
|
||||
video_urls = [vod["resolutions"][-1]["url"] for vod in vod_list]
|
||||
created_at = post["created_at"]
|
||||
user = _data_post["user"] # 用户数据
|
||||
user_uid = user["uid"] # 用户ID
|
||||
return PostInfo(
|
||||
_data=data,
|
||||
hoyolab=hoyolab,
|
||||
post_id=post_id,
|
||||
user_uid=user_uid,
|
||||
subject=subject,
|
||||
@ -92,6 +104,19 @@ class PostInfo(BaseModel):
|
||||
def __getitem__(self, item):
|
||||
return self._data[item]
|
||||
|
||||
@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/")
|
||||
|
||||
|
||||
class LiveInfo(BaseModel):
|
||||
act_type: str
|
||||
@ -125,3 +150,24 @@ class LiveCodeHoYo(BaseModel):
|
||||
@staticmethod
|
||||
def guess_offline_at() -> datetime:
|
||||
return datetime.now().replace(hour=23, minute=59, second=59, microsecond=999999) + timedelta(days=1)
|
||||
|
||||
|
||||
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
|
||||
subject: str
|
||||
banner: Optional[str] = None
|
||||
official_type: Optional[int] = None
|
||||
multi_language_info: Optional[HoYoPostMultiLang] = None
|
||||
|
28
pdm.lock
28
pdm.lock
@ -4,8 +4,8 @@
|
||||
[metadata]
|
||||
groups = ["default", "pyro", "test"]
|
||||
strategy = ["cross_platform", "inherit_metadata"]
|
||||
lock_version = "4.4.1"
|
||||
content_hash = "sha256:7c20ef5aa9a946f2b922539de88fe7d316e2937a79c4e6f7c2a383316dca30c7"
|
||||
lock_version = "4.4.2"
|
||||
content_hash = "sha256:e65015a34b2d9388ab1032dfe348ae78abd4948a975192d3376819bb2979d8bd"
|
||||
|
||||
[[package]]
|
||||
name = "aiocsv"
|
||||
@ -1038,7 +1038,7 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "importlib-metadata"
|
||||
version = "7.2.1"
|
||||
version = "8.0.0"
|
||||
requires_python = ">=3.8"
|
||||
summary = "Read metadata from Python packages"
|
||||
groups = ["default"]
|
||||
@ -1047,8 +1047,8 @@ dependencies = [
|
||||
"zipp>=0.5",
|
||||
]
|
||||
files = [
|
||||
{file = "importlib_metadata-7.2.1-py3-none-any.whl", hash = "sha256:ffef94b0b66046dd8ea2d619b701fe978d9264d38f3998bc4c27ec3b146a87c8"},
|
||||
{file = "importlib_metadata-7.2.1.tar.gz", hash = "sha256:509ecb2ab77071db5137c655e24ceb3eee66e7bbc6574165d0d114d9fc4bbe68"},
|
||||
{file = "importlib_metadata-8.0.0-py3-none-any.whl", hash = "sha256:15584cf2b1bf449d98ff8a6ff1abef57bf20f3ac6454f431736cd3e660921b2f"},
|
||||
{file = "importlib_metadata-8.0.0.tar.gz", hash = "sha256:188bd24e4c346d3f0a933f275c2fec67050326a856b9a359881d7c2a697e8812"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2127,7 +2127,7 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "redis"
|
||||
version = "5.0.6"
|
||||
version = "5.0.7"
|
||||
requires_python = ">=3.7"
|
||||
summary = "Python client for Redis database and key-value store"
|
||||
groups = ["default"]
|
||||
@ -2135,8 +2135,8 @@ dependencies = [
|
||||
"async-timeout>=4.0.3; python_full_version < \"3.11.3\"",
|
||||
]
|
||||
files = [
|
||||
{file = "redis-5.0.6-py3-none-any.whl", hash = "sha256:c0d6d990850c627bbf7be01c5c4cbaadf67b48593e913bb71c9819c30df37eee"},
|
||||
{file = "redis-5.0.6.tar.gz", hash = "sha256:38473cd7c6389ad3e44a91f4c3eaf6bcb8a9f746007f29bf4fb20824ff0b2197"},
|
||||
{file = "redis-5.0.7-py3-none-any.whl", hash = "sha256:0e479e24da960c690be5d9b96d21f7b918a98c0cf49af3b6fafaa0753f93a0db"},
|
||||
{file = "redis-5.0.7.tar.gz", hash = "sha256:8f611490b93c8109b50adc317b31bfd84fff31def3475b92e7e80bf39f48175b"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2157,7 +2157,7 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "sentry-sdk"
|
||||
version = "2.6.0"
|
||||
version = "2.7.0"
|
||||
requires_python = ">=3.6"
|
||||
summary = "Python client for Sentry (https://sentry.io)"
|
||||
groups = ["default"]
|
||||
@ -2166,19 +2166,19 @@ dependencies = [
|
||||
"urllib3>=1.26.11",
|
||||
]
|
||||
files = [
|
||||
{file = "sentry_sdk-2.6.0-py2.py3-none-any.whl", hash = "sha256:422b91cb49378b97e7e8d0e8d5a1069df23689d45262b86f54988a7db264e874"},
|
||||
{file = "sentry_sdk-2.6.0.tar.gz", hash = "sha256:65cc07e9c6995c5e316109f138570b32da3bd7ff8d0d0ee4aaf2628c3dd8127d"},
|
||||
{file = "sentry_sdk-2.7.0-py2.py3-none-any.whl", hash = "sha256:db9594c27a4d21c1ebad09908b1f0dc808ef65c2b89c1c8e7e455143262e37c1"},
|
||||
{file = "sentry_sdk-2.7.0.tar.gz", hash = "sha256:d846a211d4a0378b289ced3c434480945f110d0ede00450ba631fc2852e7a0d4"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "setuptools"
|
||||
version = "70.1.0"
|
||||
version = "70.1.1"
|
||||
requires_python = ">=3.8"
|
||||
summary = "Easily download, build, install, upgrade, and uninstall Python packages"
|
||||
groups = ["default"]
|
||||
files = [
|
||||
{file = "setuptools-70.1.0-py3-none-any.whl", hash = "sha256:d9b8b771455a97c8a9f3ab3448ebe0b29b5e105f1228bba41028be116985a267"},
|
||||
{file = "setuptools-70.1.0.tar.gz", hash = "sha256:01a1e793faa5bd89abc851fa15d0a0db26f160890c7102cd8dce643e886b47f5"},
|
||||
{file = "setuptools-70.1.1-py3-none-any.whl", hash = "sha256:a58a8fde0541dab0419750bcc521fbdf8585f6e5cb41909df3a472ef7b81ca95"},
|
||||
{file = "setuptools-70.1.1.tar.gz", hash = "sha256:937a48c7cdb7a21eb53cd7f9b59e525503aa8abaf3584c730dc5f7a5bec3a650"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1,7 +1,8 @@
|
||||
import math
|
||||
import os
|
||||
from asyncio import create_subprocess_shell, subprocess
|
||||
from typing import List, Optional, Tuple, TYPE_CHECKING, Union
|
||||
from functools import partial
|
||||
from typing import List, Optional, Tuple, TYPE_CHECKING, Union, Dict
|
||||
|
||||
import aiofiles
|
||||
from arkowrapper import ArkoWrapper
|
||||
@ -23,9 +24,11 @@ from telegram.helpers import escape_markdown
|
||||
|
||||
from core.config import config
|
||||
from core.plugin import Plugin, conversation, handler
|
||||
from modules.apihelper.client.components.hyperion import Hyperion
|
||||
from gram_core.basemodel import Settings
|
||||
from modules.apihelper.client.components.hoyolab import Hoyolab
|
||||
from modules.apihelper.client.components.hyperion import Hyperion, HyperionBase
|
||||
from modules.apihelper.error import APIHelperException
|
||||
from modules.apihelper.models.genshin.hyperion import ArtworkImage
|
||||
from modules.apihelper.models.genshin.hyperion import ArtworkImage, PostTypeEnum
|
||||
from utils.helpers import sha1
|
||||
from utils.log import logger
|
||||
|
||||
@ -44,8 +47,18 @@ class PostHandlerData:
|
||||
self.tags: Optional[List[str]] = []
|
||||
|
||||
|
||||
class PostConfig(Settings):
|
||||
"""文章推送配置"""
|
||||
|
||||
chat_id: Optional[int] = 0
|
||||
|
||||
class Config(Settings.Config):
|
||||
env_prefix = "post_"
|
||||
|
||||
|
||||
CHECK_POST, SEND_POST, CHECK_COMMAND, GTE_DELETE_PHOTO = range(10900, 10904)
|
||||
GET_POST_CHANNEL, GET_TAGS, GET_TEXT, GET_VIDEO = range(10904, 10908)
|
||||
post_config = PostConfig()
|
||||
|
||||
|
||||
class Post(Plugin.Conversation):
|
||||
@ -58,13 +71,14 @@ class Post(Plugin.Conversation):
|
||||
def __init__(self):
|
||||
self.gids = 6
|
||||
self.short_name = "sr"
|
||||
self.last_post_id_list: List[int] = []
|
||||
self.last_post_id_list: Dict[PostTypeEnum, List[int]] = {PostTypeEnum.CN: [], PostTypeEnum.OS: []}
|
||||
self.ffmpeg_enable = False
|
||||
self.cache_dir = os.path.join(os.getcwd(), "cache")
|
||||
|
||||
@staticmethod
|
||||
def get_bbs_client() -> Hyperion:
|
||||
return Hyperion(
|
||||
def get_bbs_client(bbs_type: "PostTypeEnum") -> "HyperionBase":
|
||||
class_type = Hyperion if bbs_type == PostTypeEnum.CN else Hoyolab
|
||||
return class_type(
|
||||
timeout=Timeout(
|
||||
connect=config.connect_timeout,
|
||||
read=config.read_timeout,
|
||||
@ -76,7 +90,10 @@ class Post(Plugin.Conversation):
|
||||
async def initialize(self):
|
||||
if config.channels and len(config.channels) > 0:
|
||||
logger.success("文章定时推送处理已经开启")
|
||||
self.application.job_queue.run_repeating(self.task, 60)
|
||||
cn_task = partial(self.task, post_type=PostTypeEnum.CN)
|
||||
os_task = partial(self.task, post_type=PostTypeEnum.OS)
|
||||
self.application.job_queue.run_repeating(cn_task, 30, name="post_cn_task")
|
||||
self.application.job_queue.run_repeating(os_task, 30, name="post_os_task")
|
||||
logger.success("文章定时推送处理已经开启")
|
||||
output, _ = await self.execute("ffmpeg -version")
|
||||
if "ffmpeg version" in output:
|
||||
@ -86,8 +103,8 @@ class Post(Plugin.Conversation):
|
||||
else:
|
||||
logger.warning("ffmpeg 不可用 已经禁用编码转换")
|
||||
|
||||
async def task(self, context: "ContextTypes.DEFAULT_TYPE"):
|
||||
bbs = self.get_bbs_client()
|
||||
async def task(self, context: "ContextTypes.DEFAULT_TYPE", post_type: "PostTypeEnum"):
|
||||
bbs = self.get_bbs_client(post_type)
|
||||
temp_post_id_list: List[int] = []
|
||||
|
||||
# 请求推荐POST列表并处理
|
||||
@ -97,22 +114,25 @@ class Post(Plugin.Conversation):
|
||||
logger.error("获取首页推荐信息失败 %s", str(exc))
|
||||
return
|
||||
|
||||
for data_list in official_recommended_posts["list"]:
|
||||
temp_post_id_list.append(data_list["post_id"])
|
||||
for data_list in official_recommended_posts:
|
||||
temp_post_id_list.append(data_list.post_id)
|
||||
|
||||
last_post_id_list = self.last_post_id_list[post_type]
|
||||
# 判断是否为空
|
||||
if len(self.last_post_id_list) == 0:
|
||||
if len(last_post_id_list) == 0:
|
||||
for temp_list in temp_post_id_list:
|
||||
self.last_post_id_list.append(temp_list)
|
||||
last_post_id_list.append(temp_list)
|
||||
return
|
||||
|
||||
# 筛选出新推送的文章
|
||||
new_post_id_list = set(temp_post_id_list).difference(set(self.last_post_id_list))
|
||||
last_post_id_list = self.last_post_id_list[post_type]
|
||||
new_post_id_list = set(temp_post_id_list).difference(set(last_post_id_list))
|
||||
|
||||
if not new_post_id_list:
|
||||
return
|
||||
|
||||
self.last_post_id_list = temp_post_id_list
|
||||
self.last_post_id_list[post_type] = temp_post_id_list
|
||||
chat_id = post_config.chat_id or config.owner
|
||||
|
||||
for post_id in new_post_id_list:
|
||||
try:
|
||||
@ -121,21 +141,26 @@ class Post(Plugin.Conversation):
|
||||
logger.error("获取文章信息失败 %s", str(exc))
|
||||
text = f"获取 post_id[{post_id}] 文章信息失败 {str(exc)}"
|
||||
try:
|
||||
await context.bot.send_message(config.owner, text)
|
||||
await context.bot.send_message(chat_id, text)
|
||||
except BadRequest as _exc:
|
||||
logger.error("发送消息失败 %s", _exc.message)
|
||||
return
|
||||
type_name = post_info.type_enum.value
|
||||
buttons = [
|
||||
[
|
||||
InlineKeyboardButton("确认", callback_data=f"post_admin|confirm|{post_info.post_id}"),
|
||||
InlineKeyboardButton("取消", callback_data=f"post_admin|cancel|{post_info.post_id}"),
|
||||
InlineKeyboardButton("确认", callback_data=f"post_admin|confirm|{type_name}|{post_info.post_id}"),
|
||||
InlineKeyboardButton("取消", callback_data=f"post_admin|cancel|{type_name}|{post_info.post_id}"),
|
||||
]
|
||||
]
|
||||
url = f"https://www.miyoushe.pp.ua/{self.short_name}/article/{post_info.post_id}"
|
||||
text = f"发现官网推荐文章 <a href='{url}'>{post_info.subject}</a>\n是否开始处理"
|
||||
url = post_info.get_fix_url(self.short_name)
|
||||
tag = f"#{self.short_name} #{post_type.value} #{self.short_name}_{post_type.value}"
|
||||
text = f"发现官网推荐文章 <a href='{url}'>{post_info.subject}</a>\n是否开始处理 {tag}"
|
||||
try:
|
||||
await context.bot.send_message(
|
||||
config.owner, text, parse_mode=ParseMode.HTML, reply_markup=InlineKeyboardMarkup(buttons)
|
||||
chat_id,
|
||||
text,
|
||||
parse_mode=ParseMode.HTML,
|
||||
reply_markup=InlineKeyboardMarkup(buttons),
|
||||
)
|
||||
except BadRequest as exc:
|
||||
logger.error("发送消息失败 %s", exc.message)
|
||||
@ -268,29 +293,31 @@ class Post(Plugin.Conversation):
|
||||
message = callback_query.message
|
||||
logger.info("用户 %s[%s] POST命令请求", user.full_name, user.id)
|
||||
|
||||
async def get_post_admin_callback(callback_query_data: str) -> Tuple[str, int]:
|
||||
async def get_post_admin_callback(callback_query_data: str) -> Tuple[str, PostTypeEnum, int]:
|
||||
_data = callback_query_data.split("|")
|
||||
_result = _data[1]
|
||||
_post_id = int(_data[2])
|
||||
logger.debug("callback_query_data函数返回 result[%s] post_id[%s]", _result, _post_id)
|
||||
return _result, _post_id
|
||||
_post_type = PostTypeEnum(_data[2])
|
||||
_post_id = int(_data[3])
|
||||
logger.debug(
|
||||
"callback_query_data函数返回 result[%s] _post_type[%s] post_id[%s]", _result, _post_type, _post_id
|
||||
)
|
||||
return _result, _post_type, _post_id
|
||||
|
||||
result, post_id = await get_post_admin_callback(callback_query.data)
|
||||
result, post_type, post_id = await get_post_admin_callback(callback_query.data)
|
||||
|
||||
if result == "cancel":
|
||||
await message.reply_text("操作已经取消")
|
||||
await message.delete()
|
||||
elif result == "confirm":
|
||||
reply_text = await message.reply_text("正在处理")
|
||||
status = await self.send_post_info(post_handler_data, message, post_id)
|
||||
status = await self.send_post_info(post_handler_data, message, post_id, post_type)
|
||||
await reply_text.delete()
|
||||
return status
|
||||
|
||||
await message.reply_text("非法参数")
|
||||
return ConversationHandler.END
|
||||
|
||||
@conversation.entry_point
|
||||
@handler.command(command="post", filters=filters.ChatType.PRIVATE, block=False, admin=True)
|
||||
@handler.command(command="post", block=False, admin=True)
|
||||
async def command_start(self, update: "Update", context: "ContextTypes.DEFAULT_TYPE") -> int:
|
||||
user = update.effective_user
|
||||
message = update.effective_message
|
||||
@ -313,14 +340,16 @@ class Post(Plugin.Conversation):
|
||||
await message.reply_text("退出投稿", reply_markup=ReplyKeyboardRemove())
|
||||
return ConversationHandler.END
|
||||
|
||||
post_id = Hyperion.extract_post_id(update.message.text)
|
||||
post_id, post_type = Hyperion.extract_post_id(update.message.text)
|
||||
if post_id == -1:
|
||||
await message.reply_text("获取作品ID错误,请检查连接是否合法", reply_markup=ReplyKeyboardRemove())
|
||||
return ConversationHandler.END
|
||||
return await self.send_post_info(post_handler_data, message, post_id)
|
||||
return await self.send_post_info(post_handler_data, message, post_id, post_type)
|
||||
|
||||
async def send_post_info(self, post_handler_data: PostHandlerData, message: "Message", post_id: int) -> int:
|
||||
bbs = self.get_bbs_client()
|
||||
async def send_post_info(
|
||||
self, post_handler_data: PostHandlerData, message: "Message", post_id: int, post_type: "PostTypeEnum"
|
||||
) -> int:
|
||||
bbs = self.get_bbs_client(post_type)
|
||||
post_info = await bbs.get_post_info(self.gids, post_id)
|
||||
post_images = await bbs.get_images_by_post_id(self.gids, post_id)
|
||||
await bbs.close()
|
||||
@ -329,7 +358,8 @@ class Post(Plugin.Conversation):
|
||||
post_subject = post_data["subject"]
|
||||
post_soup = BeautifulSoup(post_data["content"], features="html.parser")
|
||||
post_text, too_long = self.parse_post_text(post_soup, post_subject)
|
||||
post_text += f"\n[source](https://www.miyoushe.com/{self.short_name}/article/{post_id})"
|
||||
url = post_info.get_url(self.short_name)
|
||||
post_text += f"\n[source]({url})"
|
||||
if too_long or len(post_text) >= MessageLimit.CAPTION_LENGTH:
|
||||
post_text = post_text[: MessageLimit.CAPTION_LENGTH]
|
||||
await message.reply_text(f"警告!图片字符描述已经超过 {MessageLimit.CAPTION_LENGTH} 个字,已经切割")
|
||||
|
@ -34,7 +34,7 @@ dependencies = [
|
||||
"arko-wrapper<1.0.0,>=0.2.8",
|
||||
"fastapi<1.0.0,>=0.111.0",
|
||||
"uvicorn[standard]<1.0.0,>=0.30.1",
|
||||
"sentry-sdk<3.0.0,>=2.6.0",
|
||||
"sentry-sdk<3.0.0,>=2.7.0",
|
||||
"GitPython<4.0.0,>=3.1.30",
|
||||
"openpyxl<4.0.0,>=3.1.1",
|
||||
"async-lru<3.0.0,>=2.0.4",
|
||||
|
@ -6,7 +6,7 @@ aiofiles==24.1.0
|
||||
aiohttp==3.9.5
|
||||
aiolimiter==1.1.0
|
||||
aiosignal==1.3.1
|
||||
aiosqlite==0.20.0
|
||||
aiosqlite[sqlite]==0.20.0
|
||||
alembic==1.13.1
|
||||
anyio==4.4.0
|
||||
apscheduler==3.10.4
|
||||
@ -43,9 +43,9 @@ httpcore==1.0.5
|
||||
httptools==0.6.1
|
||||
httpx==0.27.0
|
||||
idna==3.7
|
||||
importlib-metadata==7.2.1; python_version < "3.9"
|
||||
importlib-metadata==8.0.0; python_version < "3.9"
|
||||
importlib-resources==6.4.0; python_version < "3.9"
|
||||
influxdb-client==1.44.0
|
||||
influxdb-client[async,ciso]==1.44.0
|
||||
iniconfig==2.0.0
|
||||
jinja2==3.1.4
|
||||
lxml==5.2.2
|
||||
@ -77,15 +77,15 @@ pytest-asyncio==0.23.7
|
||||
python-dateutil==2.9.0.post0
|
||||
python-dotenv==1.0.1
|
||||
python-multipart==0.0.9
|
||||
python-telegram-bot==21.3
|
||||
python-telegram-bot[ext,rate-limiter]==21.3
|
||||
pytz==2024.1
|
||||
pyyaml==6.0.1
|
||||
rapidfuzz==3.9.3
|
||||
reactivex==4.0.4
|
||||
redis==5.0.6
|
||||
redis==5.0.7
|
||||
rich==13.7.1
|
||||
sentry-sdk==2.6.0
|
||||
setuptools==70.1.0
|
||||
sentry-sdk==2.7.0
|
||||
setuptools==70.1.1
|
||||
shellingham==1.5.4
|
||||
simnet @ git+https://github.com/PaiGramTeam/SIMNet@277a33321a20909541b46bf4ecf794fd47e19fb1
|
||||
six==1.16.0
|
||||
@ -108,7 +108,7 @@ tzdata==2024.1; platform_system == "Windows"
|
||||
tzlocal==5.2
|
||||
ujson==5.10.0
|
||||
urllib3==2.2.2
|
||||
uvicorn==0.30.1
|
||||
uvicorn[standard]==0.30.1
|
||||
uvloop==0.19.0; (sys_platform != "cygwin" and sys_platform != "win32") and platform_python_implementation != "PyPy"
|
||||
watchfiles==0.22.0
|
||||
websockets==12.0
|
||||
|
Loading…
Reference in New Issue
Block a user