From 9bc5be5426cd6f64f3b58e029156fbcf3e9bd042 Mon Sep 17 00:00:00 2001 From: Azide Date: Fri, 29 Mar 2024 15:08:06 +0800 Subject: [PATCH] =?UTF-8?q?:recycle:=20=E4=B8=BAB=E7=AB=99=E5=8A=A8?= =?UTF-8?q?=E6=80=81API=E5=BB=BA=E7=AB=8Bmodel=EF=BC=8C=E8=BD=AC=E5=8F=91?= =?UTF-8?q?=E5=8A=A8=E6=80=81=E6=94=BE=E5=85=A5repost=E5=AD=97=E6=AE=B5=20?= =?UTF-8?q?(#507)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * :bug: 替换为pyd兼容性写法 * :recycle: 优化 Bilibili 推送解析 * :bug: 为B站动态API建立Model,动态转发部分放入repost字段 * :fire: 删掉无关紧要的ttl字段 * :bug: 修理转发链接解析 * :technologist: 增加可读性 * :bug: 使用pyd兼容函数 * :bug: 处理 pyd1 model rebuild 时没有递归的情况 * :recycle: 将链接生成进行统一 --- nonebot_bison/platform/bilibili.py | 281 ++++++++++++------ nonebot_bison/platform/weibo.py | 2 +- .../platforms/static/bilibili_bing_list.json | 2 +- .../static/bilibili_fake_dy_list.json | 246 --------------- .../static/bilibili_strange_post-0.json | 2 +- .../static/bilibili_strange_post.json | 2 +- tests/platforms/test_bilibili.py | 191 ++++++------ 7 files changed, 295 insertions(+), 431 deletions(-) delete mode 100644 tests/platforms/static/bilibili_fake_dy_list.json diff --git a/nonebot_bison/platform/bilibili.py b/nonebot_bison/platform/bilibili.py index 7e1e7ab..e893659 100644 --- a/nonebot_bison/platform/bilibili.py +++ b/nonebot_bison/platform/bilibili.py @@ -1,16 +1,16 @@ import re import json from abc import ABC -from typing import Any from copy import deepcopy from enum import Enum, unique from typing_extensions import Self from datetime import datetime, timedelta +from typing import Any, TypeVar, TypeAlias, NamedTuple from httpx import AsyncClient from nonebot.log import logger from pydantic import Field, BaseModel -from nonebot.compat import type_validate_python +from nonebot.compat import PYDANTIC_V2, ConfigDict, type_validate_json, type_validate_python from nonebot_bison.compat import model_rebuild @@ -19,6 +19,90 @@ from ..types import Tag, Target, RawPost, ApiError, Category from ..utils import SchedulerConfig, http_client, text_similarity from .platform import NewMessage, StatusChange, CategoryNotSupport, CategoryNotRecognize +TBaseModel = TypeVar("TBaseModel", bound=type[BaseModel]) + + +# 不能当成装饰器用 +# 当装饰器用时,global namespace 中还没有被装饰的类,会报错 +def model_rebuild_recurse(cls: TBaseModel) -> TBaseModel: + """Recursively rebuild all BaseModel subclasses in the class.""" + if not PYDANTIC_V2: + from inspect import isclass, getmembers + + for _, sub_cls in getmembers(cls, lambda x: isclass(x) and issubclass(x, BaseModel)): + model_rebuild_recurse(sub_cls) + model_rebuild(cls) + return cls + + +class Base(BaseModel): + if PYDANTIC_V2: + model_config = ConfigDict(from_attributes=True) + else: + + class Config: + orm_mode = True + + +class APIBase(Base): + """Bilibili API返回的基础数据""" + + code: int + message: str + + +class UserAPI(APIBase): + class Card(Base): + name: str + + class Data(Base): + card: "UserAPI.Card" + + data: Data | None = None + + +class PostAPI(APIBase): + class Info(Base): + uname: str + + class UserProfile(Base): + info: "PostAPI.Info" + + class Origin(Base): + uid: int + dynamic_id: int + dynamic_id_str: str + timestamp: int + type: int + rid: int + bvid: str | None = None + + class Desc(Base): + dynamic_id: int + dynamic_id_str: str + timestamp: int + type: int + user_profile: "PostAPI.UserProfile" + rid: int + bvid: str | None = None + + origin: "PostAPI.Origin | None" = None + + class Card(Base): + desc: "PostAPI.Desc" + card: str + + class Data(Base): + cards: "list[PostAPI.Card] | None" + + data: Data | None = None + + +DynRawPost: TypeAlias = PostAPI.Card + +model_rebuild_recurse(UserAPI) +model_rebuild_recurse(PostAPI) + class BilibiliClient: _client: AsyncClient @@ -95,10 +179,10 @@ class Bilibili(NewMessage): async def get_target_name(cls, client: AsyncClient, target: Target) -> str | None: res = await client.get("https://api.bilibili.com/x/web-interface/card", params={"mid": target}) res.raise_for_status() - res_data = res.json() - if res_data["code"]: + res_data = type_validate_json(UserAPI, res.content) + if res_data.code != 0: return None - return res_data["data"]["card"]["name"] + return res_data.data.card.name if res_data.data else None @classmethod async def parse_target(cls, target_text: str) -> Target: @@ -109,7 +193,7 @@ class Bilibili(NewMessage): else: raise cls.ParseTargetException() - async def get_sub_list(self, target: Target) -> list[RawPost]: + async def get_sub_list(self, target: Target) -> list[DynRawPost]: params = {"host_uid": target, "offset": 0, "need_top": 0} res = await self.client.get( "https://api.vc.bilibili.com/dynamic_svr/v1/dynamic_svr/space_history", @@ -117,42 +201,45 @@ class Bilibili(NewMessage): timeout=4.0, ) res.raise_for_status() - res_dict = res.json() - if res_dict["code"] == 0: - return res_dict["data"].get("cards", []) - else: - raise ApiError(res.request.url) + res_obj = type_validate_json(PostAPI, res.content) - def get_id(self, post: RawPost) -> Any: - return post["desc"]["dynamic_id"] + if res_obj.code == 0: + if (data := res_obj.data) and (card := data.cards): + return card + return [] + raise ApiError(res.request.url) - def get_date(self, post: RawPost) -> int: - return post["desc"]["timestamp"] + def get_id(self, post: DynRawPost) -> int: + return post.desc.dynamic_id + + def get_date(self, post: DynRawPost) -> int: + return post.desc.timestamp def _do_get_category(self, post_type: int) -> Category: - if post_type == 2: - return Category(1) - elif post_type == 64: - return Category(2) - elif post_type == 8: - return Category(3) - elif post_type == 4: - return Category(4) - elif post_type == 1: - # 转发 - return Category(5) - raise CategoryNotRecognize(post_type) + match post_type: + case 2: + return Category(1) + case 64: + return Category(2) + case 8: + return Category(3) + case 4: + return Category(4) + case 1: + # 转发 + return Category(5) + case unknown_type: + raise CategoryNotRecognize(unknown_type) - def get_category(self, post: RawPost) -> Category: - post_type = post["desc"]["type"] + def get_category(self, post: DynRawPost) -> Category: + post_type = post.desc.type return self._do_get_category(post_type) - def get_tags(self, raw_post: RawPost) -> list[Tag]: - # FIXME: 更深的原因可能是返回格式的变动,需要进一步确认 - if topic_info := raw_post["display"].get("topic_info"): - return [tp["topic_name"] for tp in topic_info["topic_details"]] - - return [] + def get_tags(self, raw_post: DynRawPost) -> list[Tag]: + card_content = json.loads(raw_post.card) + text: str = card_content["item"]["content"] + result: list[str] = re.findall(r"#(.*?)#", text) + return result def _text_process(self, dynamic: str, desc: str, title: str) -> str: similarity = 1.0 if len(dynamic) == 0 or len(desc) == 0 else text_similarity(dynamic, desc) @@ -164,61 +251,85 @@ class Bilibili(NewMessage): text = dynamic + "\n=================\n" + title + "\n\n" + desc return text - def _get_info(self, post_type: Category, card) -> tuple[str, list]: - if post_type == 1: - # 一般动态 - text = card["item"]["description"] - pic = [img["img_src"] for img in card["item"]["pictures"]] - elif post_type == 2: - # 专栏文章 - text = "{} {}".format(card["title"], card["summary"]) - pic = card["image_urls"] - elif post_type == 3: - # 视频 - dynamic = card.get("dynamic", "") - title = card["title"] - desc = card.get("desc", "") - text = self._text_process(dynamic, desc, title) - pic = [card["pic"]] - elif post_type == 4: - # 纯文字 - text = card["item"]["content"] - pic = [] - else: - raise CategoryNotSupport(post_type) - return text, pic + def _raw_post_parse(self, raw_post: DynRawPost, in_repost: bool = False): + class ParsedPost(NamedTuple): + text: str + pics: list[str] + url: str | None + repost_owner: str | None = None + repost: "ParsedPost | None" = None - async def parse(self, raw_post: RawPost) -> Post: - card_content = json.loads(raw_post["card"]) - post_type = self.get_category(raw_post) - target_name = raw_post["desc"]["user_profile"]["info"]["uname"] - if post_type >= 1 and post_type < 5: - url = "" - if post_type == 1: + card_content: dict[str, Any] = json.loads(raw_post.card) + repost_owner: str | None = ou["info"]["uname"] if (ou := card_content.get("origin_user")) else None + + def extract_url_id(url_template: str, name: str) -> str | None: + if in_repost: + if origin := raw_post.desc.origin: + return url_template.format(getattr(origin, name)) + return None + return url_template.format(getattr(raw_post.desc, name)) + + match self._do_get_category(raw_post.desc.type): + case 1: # 一般动态 - url = "https://t.bilibili.com/{}".format(raw_post["desc"]["dynamic_id_str"]) - elif post_type == 2: + url = extract_url_id("https://t.bilibili.com/{}", "dynamic_id_str") + text: str = card_content["item"]["description"] + pic: list[str] = [img["img_src"] for img in card_content["item"]["pictures"]] + return ParsedPost(text, pic, url, repost_owner) + case 2: # 专栏文章 - url = "https://www.bilibili.com/read/cv{}".format(raw_post["desc"]["rid"]) - elif post_type == 3: + url = extract_url_id("https://www.bilibili.com/read/cv{}", "rid") + text = "{} {}".format(card_content["title"], card_content["summary"]) + pic = card_content["image_urls"] + return ParsedPost(text, pic, url, repost_owner) + case 3: # 视频 - url = "https://www.bilibili.com/video/{}".format(raw_post["desc"]["bvid"]) - elif post_type == 4: + url = extract_url_id("https://www.bilibili.com/video/{}", "bvid") + dynamic = card_content.get("dynamic", "") + title = card_content["title"] + desc = card_content.get("desc", "") + text = self._text_process(dynamic, desc, title) + pic = [card_content["pic"]] + return ParsedPost(text, pic, url, repost_owner) + case 4: # 纯文字 - url = "https://t.bilibili.com/{}".format(raw_post["desc"]["dynamic_id_str"]) - text, pic = self._get_info(post_type, card_content) - elif post_type == 5: - # 转发 - url = "https://t.bilibili.com/{}".format(raw_post["desc"]["dynamic_id_str"]) - text = card_content["item"]["content"] - orig_type = card_content["item"]["orig_type"] - orig = json.loads(card_content["origin"]) - orig_text, pic = self._get_info(self._do_get_category(orig_type), orig) - text += "\n--------------\n" - text += orig_text - else: - raise CategoryNotSupport(post_type) - return Post(self, text, url=url, images=pic, nickname=target_name) + url = extract_url_id("https://t.bilibili.com/{}", "dynamic_id_str") + text = card_content["item"]["content"] + pic = [] + return ParsedPost(text, pic, url, repost_owner) + case 5: + # 转发 + url = extract_url_id("https://t.bilibili.com/{}", "dynamic_id_str") + text = card_content["item"]["content"] + orig_type: int = card_content["item"]["orig_type"] + orig_card: str = card_content["origin"] + orig_post = DynRawPost(desc=raw_post.desc, card=orig_card) + orig_post.desc.type = orig_type + + orig_parsed_post = self._raw_post_parse(orig_post, in_repost=True) + return ParsedPost(text, [], url, repost_owner, orig_parsed_post) + case unsupported_type: + raise CategoryNotSupport(unsupported_type) + + async def parse(self, raw_post: DynRawPost) -> Post: + parsed_raw_post = self._raw_post_parse(raw_post) + + post = Post( + self, + parsed_raw_post.text, + url=parsed_raw_post.url, + images=list(parsed_raw_post.pics), + nickname=raw_post.desc.user_profile.info.uname, + ) + if rp := parsed_raw_post.repost: + post.repost = Post( + self, + rp.text, + url=rp.url, + images=list(rp.pics), + nickname=rp.repost_owner, + ) + return post class Bilibililive(StatusChange): diff --git a/nonebot_bison/platform/weibo.py b/nonebot_bison/platform/weibo.py index e1ef5ac..54c2a52 100644 --- a/nonebot_bison/platform/weibo.py +++ b/nonebot_bison/platform/weibo.py @@ -130,7 +130,7 @@ class Weibo(NewMessage): def _get_text(self, raw_text: str) -> str: text = raw_text.replace("
", "\n").replace("
", "\n") - selector = etree.HTML(text) + selector = etree.HTML(text, parser=None) if selector is None: return text url_elems = selector.xpath("//a[@href]/span[@class='surl-text']") diff --git a/tests/platforms/static/bilibili_bing_list.json b/tests/platforms/static/bilibili_bing_list.json index e35c857..4f3f713 100644 --- a/tests/platforms/static/bilibili_bing_list.json +++ b/tests/platforms/static/bilibili_bing_list.json @@ -1,6 +1,6 @@ { "code": 0, - "msg": "", + "ttl": 0, "message": "", "data": { "has_more": 1, diff --git a/tests/platforms/static/bilibili_fake_dy_list.json b/tests/platforms/static/bilibili_fake_dy_list.json deleted file mode 100644 index 6a7fa19..0000000 --- a/tests/platforms/static/bilibili_fake_dy_list.json +++ /dev/null @@ -1,246 +0,0 @@ -{ - "data": { - "cards": [ - { - "desc": { - "type": 2 - }, - "display": { - "topic_info": { - "topic_details": [ - { - "topic_name": "" - } - ] - } - } - }, - { - "desc": { - "type": 1 - }, - "display": { - "topic_info": { - "topic_details": [ - { - "topic_name": "" - } - ] - } - } - }, - { - "desc": { - "type": 8 - }, - "display": { - "topic_info": { - "topic_details": [ - { - "topic_name": "明日方舟" - }, - { - "topic_name": "风笛" - }, - { - "topic_name": "琴柳" - }, - { - "topic_name": "风暴瞭望" - }, - { - "topic_name": "轮换池" - }, - { - "topic_name": "打卡挑战" - } - ] - } - } - }, - { - "desc": { - "type": 2 - }, - "display": { - "topic_info": { - "topic_details": [ - { - "topic_name": "" - } - ] - } - } - }, - { - "desc": { - "type": 1 - }, - "display": { - "topic_info": { - "topic_details": [ - { - "topic_name": "明日方舟" - }, - { - "topic_name": "饼学大厦" - }, - { - "topic_name": "可露希尔的秘密档案" - } - ] - } - } - }, - { - "desc": { - "type": 1 - }, - "display": { - "topic_info": { - "topic_details": [ - { - "topic_name": "罗德岛相簿" - }, - { - "topic_name": "可露希尔的秘密档案" - }, - { - "topic_name": "罗德岛闲逛部" - } - ] - } - } - }, - { - "desc": { - "type": 8 - }, - "display": { - "topic_info": { - "topic_details": [ - { - "topic_name": "明日方舟" - }, - { - "topic_name": "轮换学" - }, - { - "topic_name": "常驻标准寻访" - }, - { - "topic_name": "轮换池" - }, - { - "topic_name": "打卡挑战" - }, - { - "topic_name": "舟游" - } - ] - } - } - }, - { - "desc": { - "type": 2 - }, - "display": { - "topic_info": { - "topic_details": [ - { - "topic_name": "明日方舟" - }, - { - "topic_name": "饼学大厦" - } - ] - } - } - }, - { - "desc": { - "type": 4 - }, - "display": { - "topic_info": { - "topic_details": [ - { - "topic_name": "" - } - ] - } - } - }, - { - "desc": { - "type": 1 - }, - "display": { - "topic_info": { - "topic_details": [ - { - "topic_name": "明日方舟" - }, - { - "topic_name": "饼学大厦" - }, - { - "topic_name": "罗德岛相簿" - }, - { - "topic_name": "可露希尔的秘密档案" - }, - { - "topic_name": "罗德岛闲逛部" - } - ] - } - } - }, - { - "desc": { - "type": 1 - }, - "display": { - "topic_info": { - "topic_details": [ - { - "topic_name": "明日方舟" - }, - { - "topic_name": "饼学大厦" - } - ] - } - } - }, - { - "desc": { - "type": 1 - }, - "display": { - "topic_info": { - "topic_details": [ - { - "topic_name": "明日方舟" - }, - { - "topic_name": "饼学大厦" - }, - { - "topic_name": "罗德岛相簿" - }, - { - "topic_name": "可露希尔的秘密档案" - }, - { - "topic_name": "罗德岛闲逛部" - } - ] - } - } - } - ] - } -} diff --git a/tests/platforms/static/bilibili_strange_post-0.json b/tests/platforms/static/bilibili_strange_post-0.json index d17d9a6..ffc3999 100644 --- a/tests/platforms/static/bilibili_strange_post-0.json +++ b/tests/platforms/static/bilibili_strange_post-0.json @@ -1,6 +1,6 @@ { "code": 0, - "msg": "", + "ttl": 0, "message": "", "data": { "has_more": 1, diff --git a/tests/platforms/static/bilibili_strange_post.json b/tests/platforms/static/bilibili_strange_post.json index 8c87138..53f9445 100644 --- a/tests/platforms/static/bilibili_strange_post.json +++ b/tests/platforms/static/bilibili_strange_post.json @@ -1,6 +1,6 @@ { "code": 0, - "msg": "", + "ttl": 0, "message": "", "data": { "has_more": 1, diff --git a/tests/platforms/test_bilibili.py b/tests/platforms/test_bilibili.py index a5d26e2..c10327e 100644 --- a/tests/platforms/test_bilibili.py +++ b/tests/platforms/test_bilibili.py @@ -5,13 +5,16 @@ import respx import pytest from nonebug.app import App from httpx import Response, AsyncClient +from nonebot.compat import model_dump, type_validate_python from .utils import get_json -@pytest.fixture(scope="module") -def bing_dy_list(): - return get_json("bilibili_bing_list.json")["data"]["cards"] +@pytest.fixture() +def bing_dy_list(app: App): + from nonebot_bison.platform.bilibili import PostAPI + + return type_validate_python(PostAPI, get_json("bilibili_bing_list.json")).data.cards # type: ignore if typing.TYPE_CHECKING: @@ -19,69 +22,73 @@ if typing.TYPE_CHECKING: @pytest.fixture() -def bilibili(app: App): +def bilibili(app: App) -> "Bilibili": from nonebot_bison.utils import ProcessContext from nonebot_bison.platform import platform_manager - return platform_manager["bilibili"](ProcessContext(), AsyncClient()) + return platform_manager["bilibili"](ProcessContext(), AsyncClient()) # type: ignore @pytest.fixture() def without_dynamic(app: App): - return { - "code": 0, - "msg": "", - "message": "", - "data": { - "has_more": 0, - "next_offset": 0, - "_gt_": 0, - }, - } + from nonebot_bison.platform.bilibili import PostAPI + + # 先验证实际的空动态返回能否通过校验,再重新导出 + return model_dump( + type_validate_python( + PostAPI, + { + "code": 0, + "ttl": 1, + "message": "", + "data": { + "cards": None, + "has_more": 0, + "next_offset": 0, + "_gt_": 0, + }, + }, + ) + ) @pytest.mark.asyncio -async def test_get_tag_without_topic_info(bilibili, bing_dy_list): - simple_raw_post = { - "display": { - "topic_info": { - "topic_details": [ - { - "topic_name": "可露希尔的秘密档案", - }, - { - "topic_name": "罗德岛相簿", - }, - ], - }, - }, - } +async def test_get_tag(bilibili: "Bilibili", bing_dy_list): + from nonebot_bison.platform.bilibili import DynRawPost - simple_raw_post_without_topic_info = { - "display": { - "damedane": "dameyo", - }, - } + raw_post_has_tag = type_validate_python(DynRawPost, bing_dy_list[0]) + raw_post_has_tag.card = '{"user":{"uid":111111,"uname":"1111","face":"https://i2.hdslb.com/bfs/face/0b.jpg"},"item":{"rp_id":11111,"uid":31111,"content":"#测试1#\\n测试\\n#测试2#\\n#测\\n测\\n测#","ctrl":"","reply":0}}' - res1 = bilibili.get_tags(simple_raw_post) - assert res1 == ["可露希尔的秘密档案", "罗德岛相簿"] + raw_post_has_no_tag = type_validate_python(DynRawPost, bing_dy_list[1]) + raw_post_has_no_tag.card = '{"user":{"uid":111111,"uname":"1111","face":"https://i2.hdslb.com/bfs/face/0b.jpg"},"item":{"rp_id":11111,"uid":31111,"content":"测试1\\n测试\\n测试2\\n#测\\n测\\n测#","ctrl":"","reply":0}}' - res2 = bilibili.get_tags(simple_raw_post_without_topic_info) + res1 = bilibili.get_tags(raw_post_has_tag) + assert res1 == ["测试1", "测试2"] + + res2 = bilibili.get_tags(raw_post_has_no_tag) assert res2 == [] -@pytest.mark.asyncio async def test_video_forward(bilibili, bing_dy_list): from nonebot_bison.post import Post post: Post = await bilibili.parse(bing_dy_list[1]) - assert ( - post.content - == "答案揭晓:宿舍!来看看投票结果\nhttps://t.bilibili.com/568093580488553786\n--------------\n#可露希尔的秘密档案#" - " \n11:来宿舍休息一下吧 \n档案来源:lambda:\\罗德岛内务\\秘密档案 \n发布时间:9/12 1:00 P.M." - " \n档案类型:可见 \n档案描述:今天请了病假在宿舍休息。很舒适。" - " \n提供者:赫默\n=================\n《可露希尔的秘密档案》11话:来宿舍休息一下吧" + assert post.content == """答案揭晓:宿舍!来看看投票结果\nhttps://t.bilibili.com/568093580488553786""" + assert post.repost is not None + # 注意原文前几行末尾是有空格的 + assert post.repost.content == ( + "#可露希尔的秘密档案# \n" + "11:来宿舍休息一下吧 \n" + "档案来源:lambda:\\罗德岛内务\\秘密档案 \n" + "发布时间:9/12 1:00 P.M. \n" + "档案类型:可见 \n" + "档案描述:今天请了病假在宿舍休息。很舒适。 \n" + "提供者:赫默\n" + "=================\n" + "《可露希尔的秘密档案》11话:来宿舍休息一下吧" ) + assert post.url == "https://t.bilibili.com/569448354910819194" + assert post.repost.url == "https://www.bilibili.com/video/BV1E3411q7nU" assert post.get_priority_themes()[0] == "basic" @@ -99,40 +106,61 @@ async def test_video_forward_without_dynamic(bilibili, bing_dy_list): "包含慕夏对新PV的个人解读,风笛厨力疯狂放出,CP言论输出,9.16轮换池预测视频分析和理智规划杂谈内容。" "\n注意:内含大量个人性质对风笛的厨力观点,与多CP混乱发言,不适者请及时点击退出或跳到下一片段。" ) + assert post.repost is None + assert post.url == "https://www.bilibili.com/video/BV1K44y1h7Xg" assert post.get_priority_themes()[0] == "basic" @pytest.mark.asyncio -async def test_article_forward(bilibili, bing_dy_list): +async def test_article_forward(bilibili: "Bilibili", bing_dy_list): post = await bilibili.parse(bing_dy_list[4]) - assert ( - post.content == "#明日方舟##饼学大厦#\n9.11专栏更新完毕," - "这还塌了实属没跟新运营对上\n后边除了周日发饼和PV没提及的中文语音," - "稳了\n别忘了来参加#可露希尔的秘密档案#的主题投票\nhttps://t.bilibili.com/568093580488553786?tab=2" - + "\n--------------\n" - + "【明日方舟】饼学大厦#12~14(风暴瞭望&玛莉娅·临光&红松林&感谢庆典)9.11更新" - " 更新记录09.11更新:覆盖09.10更新;以及排期更新,猜测周一周五开活动09.10更新:以周五开活动为底," - "PV/公告调整位置,整体结构更新09.08更新:饼学大厦#12更新," - "新增一件六星商店服饰(周日发饼)09.06更新:饼学大厦整栋整栋翻新," - "改为9.16开主线(四日无饼!)09.05凌晨更新:10.13后的排期(两日无饼,鹰角背刺," - "心狠手辣)前言感谢楪筱祈ぺ的动态-哔哩哔哩 (bilibili.com) 对饼学的贡献!后续排期:9.17【风暴瞭望】、" - "10.01【玛莉娅·临光】复刻、10.1" + assert post.content == ( + "#明日方舟##饼学大厦#\n" + "9.11专栏更新完毕,这还塌了实属没跟新运营对上\n" + "后边除了周日发饼和PV没提及的中文语音,稳了\n" + "别忘了来参加#可露希尔的秘密档案#的主题投票\n" + "https://t.bilibili.com/568093580488553786?tab=2" ) + assert post.repost is not None + assert post.repost.content == ( + "【明日方舟】饼学大厦#12~14(风暴瞭望&玛莉娅·临光&红松林&感谢庆典)" + "9.11更新 更新记录09.11更新:覆盖09.10更新;以及排期更新,猜测周一周五开活动" + "09.10更新:以周五开活动为底,PV/公告调整位置,整体结构更新" + "09.08更新:饼学大厦#12更新,新增一件六星商店服饰(周日发饼)" + "09.06更新:饼学大厦整栋整栋翻新,改为9.16开主线(四日无饼!)" + "09.05凌晨更新:10.13后的排期(两日无饼,鹰角背刺,心狠手辣)" + "前言感谢楪筱祈ぺ的动态-哔哩哔哩 (bilibili.com) 对饼学的贡献!" + "后续排期:9.17【风暴瞭望】、10.01【玛莉娅·临光】复刻、10.1" + ) + assert post.url == "https://t.bilibili.com/569189870889648693" + assert post.repost.url == "https://www.bilibili.com/read/cv12993752" @pytest.mark.asyncio async def test_dynamic_forward(bilibili, bing_dy_list): post = await bilibili.parse(bing_dy_list[5]) - assert ( - post.content == "饼组主线饼学预测——9.11版\n①今日结果\n9.11 殿堂上的游禽-星极(x," - "新运营实锤了)\n②后续预测\n9.12 #罗德岛相簿#+#可露希尔的秘密档案#11话\n9.13" - " 六星先锋(执旗手)干员-琴柳\n9.14 宣传策略-空弦+家具\n9.15 轮换池(+中文语音前瞻)\n9.16" - " 停机\n9.17 #罗德岛闲逛部#+新六星EP+EP09·风暴瞭望开启\n9.19 #罗德岛相簿#" - + "\n--------------\n" - + "#明日方舟#\n【新增服饰】\n//殿堂上的游禽 - 星极\n塞壬唱片偶像企划《闪耀阶梯》特供服饰/殿堂上的游禽。" - "星极自费参加了这项企划,尝试着用大众能接受的方式演绎天空之上的故事。\n\n_____________\n谦逊留给观众," - "骄傲发自歌喉,此夜,唯我璀璨。 " + assert post.content == ( + "饼组主线饼学预测——9.11版\n" + "①今日结果\n" + "9.11 殿堂上的游禽-星极(x,新运营实锤了)\n" + "②后续预测\n" + "9.12 #罗德岛相簿#+#可露希尔的秘密档案#11话\n" + "9.13 六星先锋(执旗手)干员-琴柳\n9.14 宣传策略-空弦+家具\n" + "9.15 轮换池(+中文语音前瞻)\n" + "9.16 停机\n" + "9.17 #罗德岛闲逛部#+新六星EP+EP09·风暴瞭望开启\n" + "9.19 #罗德岛相簿#" ) + assert post.repost.content == ( + "#明日方舟#\n" + "【新增服饰】\n" + "//殿堂上的游禽 - 星极\n" + "塞壬唱片偶像企划《闪耀阶梯》特供服饰/殿堂上的游禽。星极自费参加了这项企划,尝试着用大众能接受的方式演绎天空之上的故事。\n\n" + "_____________\n" + "谦逊留给观众,骄傲发自歌喉,此夜,唯我璀璨。 " + ) + assert post.url == "https://t.bilibili.com/569107343093484983" + assert post.repost.url == "https://t.bilibili.com/569105539209306328" @pytest.mark.asyncio @@ -196,32 +224,3 @@ async def test_parse_target(bilibili: "Bilibili"): assert res2 == "161775300" with pytest.raises(Platform.ParseTargetException): await bilibili.parse_target("https://www.bilibili.com/video/BV1qP4y1g738?spm_id_from=333.999.0.0") - - -@pytest.fixture(scope="module") -def post_list(): - return get_json("bilibili_fake_dy_list.json")["data"]["cards"] - - -# 测试新tag机制的平台推送情况 -@pytest.mark.asyncio -async def test_filter_user_custom(bilibili, post_list): - only_banned_tags = ["~可露希尔的秘密档案"] - res0 = await bilibili.filter_user_custom(post_list, [], only_banned_tags) - assert len(res0) == 8 - - only_subscribed_tags = ["可露希尔的秘密档案"] - res1 = await bilibili.filter_user_custom(post_list, [], only_subscribed_tags) - assert len(res1) == 4 - - multi_subs_tags_1 = ["可露希尔的秘密档案", "罗德岛相簿"] - res2 = await bilibili.filter_user_custom(post_list, [], multi_subs_tags_1) - assert len(res2) == 4 - - multi_subs_tags_2 = ["罗德岛相簿", "风暴瞭望"] - res3 = await bilibili.filter_user_custom(post_list, [], multi_subs_tags_2) - assert len(res3) == 4 - - multi_subs_tags_3 = ["明日方舟", "~饼学大厦"] - res4 = await bilibili.filter_user_custom(post_list, [], multi_subs_tags_3) - assert len(res4) == 2