From 29f71fb0ec84be686ebc3f9f80687a761f852809 Mon Sep 17 00:00:00 2001 From: omg-xtao <100690902+omg-xtao@users.noreply.github.com> Date: Thu, 27 Jun 2024 00:01:49 +0800 Subject: [PATCH] :sparkles: Support hoyolab post plugin --- .env.example | 4 +- .../apihelper/client/components/hoyolab.py | 68 ++++++++ .../apihelper/client/components/hyperion.py | 158 +++++++++++------- modules/apihelper/models/genshin/hyperion.py | 52 +++++- pdm.lock | 28 ++-- plugins/admin/post.py | 98 +++++++---- pyproject.toml | 2 +- requirements.txt | 16 +- 8 files changed, 306 insertions(+), 120 deletions(-) create mode 100644 modules/apihelper/client/components/hoyolab.py diff --git a/.env.example b/.env.example index 11d845c..99d941f 100644 --- a/.env.example +++ b/.env.example @@ -40,8 +40,10 @@ OWNER=0 # 记录错误并发送消息通知开发人员 可选配置项 # ERROR_NOTIFICATION_CHAT_ID=chat_id -# 文章推送群组 可选配置项 +# 文章推送频道 可选配置项 # CHANNELS=[] +# 文章推送群组 可选配置项 +# POST_CHAT_ID=0 # 消息帮助频道 可选配置项 # CHANNELS_HELPER=0 diff --git a/modules/apihelper/client/components/hoyolab.py b/modules/apihelper/client/components/hoyolab.py new file mode 100644 index 0000000..8e64268 --- /dev/null +++ b/modules/apihelper/client/components/hoyolab.py @@ -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() diff --git a/modules/apihelper/client/components/hyperion.py b/modules/apihelper/client/components/hyperion.py index 7e9515e..6c34395 100644 --- a/modules/apihelper/client/components/hyperion.py +++ b/modules/apihelper/client/components/hyperion.py @@ -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\d+)") - matches = rgx.search(text) + rgx2 = re.compile(r"(?:bbs|www\.)?(?:hoyolab|hoyoverse)\.(.*)/article/(?P\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): """ diff --git a/modules/apihelper/models/genshin/hyperion.py b/modules/apihelper/models/genshin/hyperion.py index 2ffde16..72d93db 100644 --- a/modules/apihelper/models/genshin/hyperion.py +++ b/modules/apihelper/models/genshin/hyperion.py @@ -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 diff --git a/pdm.lock b/pdm.lock index 62f1f6f..83e2296 100644 --- a/pdm.lock +++ b/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]] diff --git a/plugins/admin/post.py b/plugins/admin/post.py index b41686b..6bab368 100644 --- a/plugins/admin/post.py +++ b/plugins/admin/post.py @@ -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"发现官网推荐文章 {post_info.subject}\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"发现官网推荐文章 {post_info.subject}\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} 个字,已经切割") diff --git a/pyproject.toml b/pyproject.toml index 8d0f8df..b5d8982 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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", diff --git a/requirements.txt b/requirements.txt index 6927611..85e6091 100644 --- a/requirements.txt +++ b/requirements.txt @@ -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