From b122d840f5b7b0f615a3cb9dc5295a3bf7d30d8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B4=9B=E6=B0=B4=E5=B1=85=E5=AE=A4?= Date: Thu, 13 Oct 2022 15:38:47 +0800 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20=E4=B8=BA=20`post`=20=E6=8F=92?= =?UTF-8?q?=E4=BB=B6=E6=B7=BB=E5=8A=A0=E5=AE=9A=E6=97=B6=E6=8E=A8=E9=80=81?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.example | 24 ++++---- modules/apihelper/hyperion.py | 9 ++- plugins/other/post.py | 108 +++++++++++++++++++++++++++++++++- tests/test_hyperion_bbs.py | 13 ++++ 4 files changed, 138 insertions(+), 16 deletions(-) diff --git a/.env.example b/.env.example index da3b674..f9cf556 100644 --- a/.env.example +++ b/.env.example @@ -16,19 +16,19 @@ REDIS_DB=0 # 联系 https://t.me/BotFather 使用 /newbot 命令创建机器人并获取 token BOT_TOKEN="xxxxxxx" -# 记录错误并发送消息通知开发人员 可选配置项 -ERROR_NOTIFICATION_CHAT_ID=chat_id - -# 文章推送群组 可选配置项 -CHANNELS=[{ "name": "", "chat_id": 1}] - # bot 管理员 ADMINS=[{ "username": "", "user_id": -1 }] -# 群验证功能 可选配置项 -VERIFY_GROUPS=[] +# 记录错误并发送消息通知开发人员 可选配置项 +# ERROR_NOTIFICATION_CHAT_ID=chat_id -# logger 配置 +# 文章推送群组 可选配置项 +# CHANNELS=[{ "name": "", "chat_id": 1}] + +# 群验证功能 可选配置项 +# VERIFY_GROUPS=[] + +# logger 配置 可选配置项 LOGGER_WIDTH=180 LOGGER_LOG_PATH="logs" LOGGER_TIME_FORMAT="[%Y-%m-%d %X]" @@ -36,11 +36,11 @@ LOGGER_TRACEBACK_MAX_FRAMES=20 LOGGER_RENDER_KEYWORDS=["BOT"] # mtp 客户端 可选配置项 -API_ID=12345 -API_HASH="abcdefg" +# API_ID=12345 +# API_HASH="abcdefg" # ENKA_NETWORK_API 可选配置项 -ENKA_NETWORK_API_AGENT="" +# ENKA_NETWORK_API_AGENT="" # Web Server # 目前只用于预览模板,仅开发环境启动 diff --git a/modules/apihelper/hyperion.py b/modules/apihelper/hyperion.py index 740553e..d843906 100644 --- a/modules/apihelper/hyperion.py +++ b/modules/apihelper/hyperion.py @@ -5,8 +5,8 @@ from json import JSONDecodeError from typing import List, Optional, Dict from genshin import Client, InvalidCookies -from genshin.utility.uid import recognize_genshin_server from genshin.utility.ds import generate_dynamic_secret +from genshin.utility.uid import recognize_genshin_server from httpx import AsyncClient from modules.apihelper.base import ArtworkImage, PostInfo @@ -25,6 +25,8 @@ class Hyperion: POST_FULL_URL = "https://bbs-api.mihoyo.com/post/wapi/getPostFull" POST_FULL_IN_COLLECTION_URL = "https://bbs-api.mihoyo.com/post/wapi/getPostFullInCollection" GET_NEW_LIST_URL = "https://bbs-api.mihoyo.com/post/wapi/getNewsList" + GET_OFFICIAL_RECOMMENDED_POSTS_URL = "https://bbs-api.mihoyo.com/post/wapi/getOfficialRecommendedPosts" + 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" @@ -89,6 +91,11 @@ class Hyperion: ) return {"x-oss-process": params} + async def get_official_recommended_posts(self, gids: int) -> JSONDict: + params = {"gids": gids} + response = await self.client.get(url=self.GET_OFFICIAL_RECOMMENDED_POSTS_URL, params=params) + return response + async def get_post_full_in_collection(self, collection_id: int, gids: int = 2, order_type=1) -> JSONDict: params = {"collection_id": collection_id, "gids": gids, "order_type": order_type} response = await self.client.get(url=self.POST_FULL_IN_COLLECTION_URL, params=params) diff --git a/plugins/other/post.py b/plugins/other/post.py index 410c0f6..63cd31d 100644 --- a/plugins/other/post.py +++ b/plugins/other/post.py @@ -1,7 +1,15 @@ -from typing import Optional, List +from typing import Optional, List, Tuple from bs4 import BeautifulSoup -from telegram import Update, ReplyKeyboardMarkup, ReplyKeyboardRemove, InputMediaPhoto +from telegram import ( + Update, + ReplyKeyboardMarkup, + ReplyKeyboardRemove, + InputMediaPhoto, + InlineKeyboardButton, + InlineKeyboardMarkup, + Message, +) from telegram.constants import ParseMode, MessageLimit from telegram.error import BadRequest from telegram.ext import CallbackContext, ConversationHandler, filters @@ -9,8 +17,10 @@ from telegram.helpers import escape_markdown from core.baseplugin import BasePlugin from core.bot import bot +from core.config import config from core.plugin import Plugin, conversation, handler from modules.apihelper.base import ArtworkImage +from modules.apihelper.error import APIHelperException from modules.apihelper.hyperion import Hyperion from utils.decorators.admins import bot_admins_rights_check from utils.decorators.error import error_callable @@ -38,6 +48,95 @@ class Post(Plugin.Conversation, BasePlugin.Conversation): def __init__(self): self.bbs = Hyperion() + self.last_post_id_list: List[int] = [] + if config.channels is not None and len(config.channels) > 0: + logger.success("文章定时推送处理已经开启") + bot.app.job_queue.run_repeating(self.task, 60 * 3) + + async def task(self, context: CallbackContext): + temp_post_id_list: List[int] = [] + + # 请求推荐POST列表并处理 + try: + official_recommended_posts = await self.bbs.get_official_recommended_posts(2) + except APIHelperException as exc: + logger.error(f"获取首页推荐信息失败 {repr(exc)}") + return + + for data_list in official_recommended_posts["list"]: + temp_post_id_list.append(data_list["post_id"]) + + # 判断是否为空 + if len(self.last_post_id_list) == 0: + for temp_list in temp_post_id_list: + self.last_post_id_list.append(temp_list) + return + + # 筛选出新推送的文章 + new_post_id_list = set(temp_post_id_list).difference(set(self.last_post_id_list)) + + if len(new_post_id_list) == 0: + return + + self.last_post_id_list = temp_post_id_list + + for post_id in temp_post_id_list: + try: + post_info = await self.bbs.get_post_info(2, post_id) + except APIHelperException as exc: + logger.error(f"获取文章信息失败 {repr(exc)}") + text = f"获取 post_id[{post_id}] 文章信息失败 {repr(exc)}" + for user in config.admins: + try: + await context.bot.send_message(user.user_id, text) + except BadRequest as _exc: + logger.error(f"发送消息失败 {repr(_exc)}") + return + buttons = [ + [ + InlineKeyboardButton("确认", callback_data=f"post_admin|confirm|{post_info.post_id}"), + InlineKeyboardButton("取消", callback_data=f"post_admin|cancel|{post_info.post_id}"), + ] + ] + url = f"https://bbs.mihoyo.com/ys/article/{post_info.post_id}" + text = f"发现官网推荐文章 {post_info.subject}\n是否开始处理" + for user in config.admins: + try: + await context.bot.send_message(user.user_id, text, reply_markup=InlineKeyboardMarkup(buttons)) + except BadRequest as exc: + logger.error(f"发送消息失败 {repr(exc)}") + + @conversation.entry_point + @handler.callback_query(pattern=r"^post_admin\|", block=False) + @bot_admins_rights_check + @error_callable + async def callback_query_start(self, update: Update, context: CallbackContext) -> int: + post_handler_data: PostHandlerData = context.chat_data.get("post_handler_data") + callback_query = update.callback_query + user = callback_query.from_user + message = callback_query.message + logger.info(f"用户 {user.full_name}[{user.id}] POST命令请求") + + async def get_post_admin_callback(callback_query_data: str) -> Tuple[str, int]: + _data = callback_query_data.split("|") + _result = _data[1] + _post_id = int(_data[2]) + logger.debug(f"callback_query_data函数返回 result[{_result}] post_id[{_post_id}]") + return _result, _post_id + + result, 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) + 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=True) @@ -71,6 +170,9 @@ class Post(Plugin.Conversation, BasePlugin.Conversation): 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) + + async def send_post_info(self, post_handler_data: PostHandlerData, message: Message, post_id: int) -> int: post_info = await self.bbs.get_post_info(2, post_id) post_images = await self.bbs.get_images_by_post_id(2, post_id) post_data = post_info["post"]["post"] @@ -83,7 +185,7 @@ class Post(Plugin.Conversation, BasePlugin.Conversation): post_text += f"[source](https://bbs.mihoyo.com/ys/article/{post_id})" if len(post_text) >= MessageLimit.CAPTION_LENGTH: await message.reply_markdown_v2(post_text) - post_text = post_text[:MessageLimit.CAPTION_LENGTH] + post_text = post_text[: MessageLimit.CAPTION_LENGTH] await message.reply_text(f"警告!图片字符描述已经超过 {MessageLimit.CAPTION_LENGTH} 个字,已经切割并发送原文本") try: if len(post_images) > 1: diff --git a/tests/test_hyperion_bbs.py b/tests/test_hyperion_bbs.py index 966de23..c62ad57 100644 --- a/tests/test_hyperion_bbs.py +++ b/tests/test_hyperion_bbs.py @@ -43,3 +43,16 @@ async def test_get_post_info(hyperion): async def test_get_images_by_post_id(hyperion): post_images = await hyperion.get_images_by_post_id(2, 29023709) assert len(post_images) == 1 + + +# noinspection PyShadowingNames +@pytest.mark.asyncio +@flaky(3, 1) +async def test_official_recommended_posts(hyperion): + official_recommended_posts = await hyperion.get_official_recommended_posts(2) + assert len(official_recommended_posts["list"]) > 0 + for data_list in official_recommended_posts["list"]: + post_info = await hyperion.get_post_info(2, data_list["post_id"]) + assert post_info.post_id + assert post_info.subject + LOGGER.info("official_recommended_posts: post_id[%s] subject[%s]", post_info.post_id, post_info.subject)