Add status chage and test

This commit is contained in:
felinae98 2021-06-29 19:43:06 +08:00
parent d0b29abed7
commit 9e6a9228f6
No known key found for this signature in database
GPG Key ID: 00C8B010587FF610
6 changed files with 238 additions and 17 deletions

View File

@ -20,7 +20,7 @@ class MonsterSiren(NewMessage, NoTargetMixin):
async def get_target_name(_) -> str:
return '塞壬唱片新闻'
async def get_sub_list(self) -> list[RawPost]:
async def get_sub_list(self, _) -> list[RawPost]:
async with httpx.AsyncClient() as client:
raw_data = await client.get('https://monster-siren.hypergryph.com/api/news')
return raw_data.json()['data']['list']

View File

@ -84,7 +84,14 @@ class CategoryMixin(metaclass=RegistryABCMeta, abstract=True):
"Return category of given Rawpost"
raise NotImplementedError()
class MessageProcessMixin(PlaformNameMixin, CategoryMixin, abstract=True):
class ParsePostMixin(metaclass=RegistryABCMeta, abstract=True):
@abstractmethod
async def parse(self, raw_post: RawPost) -> Post:
"parse RawPost into post"
...
class MessageProcessMixin(PlaformNameMixin, CategoryMixin, ParsePostMixin, abstract=True):
"General message process fetch, parse, filter progress"
def __init__(self):
@ -95,10 +102,6 @@ class MessageProcessMixin(PlaformNameMixin, CategoryMixin, abstract=True):
def get_id(self, post: RawPost) -> Any:
"Get post id of given RawPost"
@abstractmethod
async def parse(self, raw_post: RawPost) -> Post:
"parse RawPost into post"
...
async def _parse_with_cache(self, raw_post: RawPost) -> Post:
post_id = self.get_id(raw_post)
@ -168,7 +171,7 @@ class NewMessageProcessMixin(StorageMixinProto, MessageProcessMixin, abstract=Tr
self.set_stored_data(target, store)
return res
class UserCustomFilterMixin(CategoryMixin, abstract=True):
class UserCustomFilterMixin(CategoryMixin, ParsePostMixin, abstract=True):
categories: dict[Category, str]
enable_tag: bool
@ -196,6 +199,21 @@ class UserCustomFilterMixin(CategoryMixin, abstract=True):
res.append(raw_post)
return res
async def dispatch_user_post(self, target: Target, new_posts: list[RawPost], users: list[UserSubInfo]) -> list[tuple[User, list[Post]]]:
res: list[tuple[User, list[Post]]] = []
for user, category_getter, tag_getter in users:
required_tags = tag_getter(target) if self.enable_tag else []
cats = category_getter(target)
user_raw_post = await self.filter_user_custom(new_posts, cats, required_tags)
user_post: list[Post] = []
for raw_post in user_raw_post:
if isinstance(self, MessageProcessMixin):
user_post.append(await self._parse_with_cache(raw_post))
else:
user_post.append(await self.parse(raw_post))
res.append((user, user_post))
return res
class Platform(metaclass=RegistryABCMeta, base=True):
# schedule_interval: int
@ -220,12 +238,12 @@ class NewMessage(
UserCustomFilterMixin,
abstract=True
):
"Fetch a list of messages, filter the new messages, dispatch it to different users"
async def fetch_new_post(self, target: Target, users: list[UserSubInfo]) -> list[tuple[User, list[Post]]]:
try:
post_list = await self.get_sub_list(target)
new_posts = await self.filter_common_with_diff(target, post_list)
res: list[tuple[User, list[Post]]] = []
if not new_posts:
return []
else:
@ -234,17 +252,44 @@ class NewMessage(
self.platform_name,
target if self.has_target else '-',
self.get_id(post)))
for user, category_getter, tag_getter in users:
required_tags = tag_getter(target) if self.enable_tag else []
cats = category_getter(target)
user_raw_post = await self.filter_user_custom(new_posts, cats, required_tags)
user_post: list[Post] = []
for raw_post in user_raw_post:
user_post.append(await self._parse_with_cache(raw_post))
res.append((user, user_post))
res = await self.dispatch_user_post(target, new_posts, users)
self.parse_cache = {}
return res
except httpx.RequestError as err:
logger.warning("network connection error: {}, url: {}".format(type(err), err.request.url))
return []
class StatusChange(
Platform,
StorageMixinProto,
PlaformNameMixin,
UserCustomFilterMixin,
abstract=True
):
"Watch a status, and fire a post when status changes"
@abstractmethod
async def get_status(self, target: Target) -> Any:
...
@abstractmethod
def compare_status(self, target: Target, old_status, new_status) -> Optional[RawPost]:
...
@abstractmethod
async def parse(self, raw_post: RawPost) -> Post:
...
async def fetch_new_post(self, target: Target, users: list[UserSubInfo]) -> list[tuple[User, list[Post]]]:
try:
new_status = await self.get_status(target)
res = []
if old_status := self.get_stored_data(target):
diff = self.compare_status(target, old_status, new_status)
if diff:
res = await self.dispatch_user_post(target, [diff], users)
self.set_stored_data(target, new_status)
return res
except httpx.RequestError as err:
logger.warning("network connection error: {}, url: {}".format(type(err), err.request.url))
return []

View File

@ -0,0 +1,63 @@
{
"code": 0,
"msg": "",
"data": {
"list": [
{
"cid": "114091",
"title": "#AUS小屋",
"cate": 8,
"date": "2021-06-23"
},
{
"cid": "027726",
"title": "「音律联觉原声EP」正式上架",
"cate": 1,
"date": "2021-06-12"
},
{
"cid": "750459",
"title": "「ManiFesto:」MV正式公开",
"cate": 1,
"date": "2021-06-08"
},
{
"cid": "241304",
"title": "「Real Me」正式上架",
"cate": 1,
"date": "2021-06-01"
},
{
"cid": "578835",
"title": "#D.D.D.PHOTO",
"cate": 8,
"date": "2021-05-24"
},
{
"cid": "489188",
"title": "#AUS小屋",
"cate": 8,
"date": "2021-05-19"
},
{
"cid": "992677",
"title": "「Immutable」正式上架",
"cate": 1,
"date": "2021-05-02"
},
{
"cid": "605962",
"title": "「Voices」正式上架",
"cate": 1,
"date": "2021-05-01"
},
{
"cid": "336213",
"title": "#D.D.D.PHOTO",
"cate": 8,
"date": "2021-04-28"
}
],
"end": false
}
}

View File

@ -0,0 +1 @@
{"code":0,"msg":"","data":{"list":[{"cid":"241303","title":"#D.D.D.PHOTO","cate":8,"date":"2021-06-29"},{"cid":"114091","title":"#AUS小屋","cate":8,"date":"2021-06-23"},{"cid":"027726","title":"「音律联觉原声EP」正式上架","cate":1,"date":"2021-06-12"},{"cid":"750459","title":"「ManiFesto:」MV正式公开","cate":1,"date":"2021-06-08"},{"cid":"241304","title":"「Real Me」正式上架","cate":1,"date":"2021-06-01"},{"cid":"578835","title":"#D.D.D.PHOTO","cate":8,"date":"2021-05-24"},{"cid":"489188","title":"#AUS小屋","cate":8,"date":"2021-05-19"},{"cid":"992677","title":"「Immutable」正式上架","cate":1,"date":"2021-05-02"},{"cid":"605962","title":"「Voices」正式上架","cate":1,"date":"2021-05-01"},{"cid":"336213","title":"#D.D.D.PHOTO","cate":8,"date":"2021-04-28"}],"end":false}}

View File

@ -0,0 +1,44 @@
import pytest
import typing
import respx
from httpx import Response
import feedparser
if typing.TYPE_CHECKING:
import sys
sys.path.append('./src/plugins')
import nonebot_hk_reporter
from .utils import get_json, get_file
@pytest.fixture
def monster_siren(plugin_module: 'nonebot_hk_reporter'):
return plugin_module.platform.platform_manager['monster-siren']
@pytest.fixture(scope='module')
def monster_siren_list_0():
return get_json('monster-siren_list_0.json')
@pytest.fixture(scope='module')
def monster_siren_list_1():
return get_json('monster-siren_list_1.json')
@pytest.mark.asyncio
@respx.mock
async def test_fetch_new(monster_siren, dummy_user_subinfo, monster_siren_list_0, monster_siren_list_1):
ak_list_router = respx.get("https://monster-siren.hypergryph.com/api/news")
ak_list_router.mock(return_value=Response(200, json=monster_siren_list_0))
target = ''
res = await monster_siren.fetch_new_post(target, [dummy_user_subinfo])
assert(ak_list_router.called)
assert(len(res) == 0)
mock_data = monster_siren_list_1
ak_list_router.mock(return_value=Response(200, json=mock_data))
res3 = await monster_siren.fetch_new_post(target, [dummy_user_subinfo])
assert(len(res3[0][1]) == 1)
post = res3[0][1][0]
assert(post.target_type == 'monster-siren')
assert(post.text == '#D.D.D.PHOTO')
assert(post.url == 'https://monster-siren.hypergryph.com/info/241303')
assert(post.target_name == '塞壬唱片新闻')
assert(len(post.pics) == 0)

View File

@ -1,6 +1,6 @@
import sys
import typing
from typing import Any
from typing import Any, Optional
import pytest
@ -172,6 +172,52 @@ def mock_platform_no_target(plugin_module: 'nonebot_hk_reporter'):
return MockPlatform()
@pytest.fixture
def mock_status_change(plugin_module: 'nonebot_hk_reporter'):
class MockPlatform(plugin_module.platform.platform.StatusChange,
plugin_module.platform.platform.NoTargetMixin):
platform_name = 'mock_platform'
name = 'Mock Platform'
enabled = True
is_common = True
enable_tag = False
schedule_type = 'interval'
schedule_kw = {'seconds': 10}
categories = {
1: '转发',
2: '视频',
}
def __init__(self):
self.sub_index = 0
super().__init__()
async def get_status(self, _: 'Target'):
if self.sub_index == 0:
self.sub_index += 1
return {'s': False}
elif self.sub_index == 1:
self.sub_index += 1
return {'s': True}
else:
return {'s': False}
def compare_status(self, target, old_status, new_status) -> Optional['RawPost']:
if old_status['s'] == False and new_status['s'] == True:
return {'text': 'on', 'cat': 1}
elif old_status['s'] == True and new_status['s'] == False:
return {'text': 'off', 'cat': 2}
return None
async def parse(self, raw_post) -> 'Post':
return plugin_module.post.Post('mock_status', raw_post['text'], '')
def get_category(self, raw_post):
return raw_post['cat']
return MockPlatform()
@pytest.mark.asyncio
async def test_new_message_target_without_cats_tags(mock_platform_without_cats_tags, user_info_factory):
res1 = await mock_platform_without_cats_tags.fetch_new_post('dummy', [user_info_factory(lambda _: [1,2], lambda _: [])])
@ -230,3 +276,25 @@ async def test_new_message_no_target(mock_platform_no_target, user_info_factory)
assert('p2' in id_set_1 and 'p3' in id_set_1)
assert('p2' in id_set_2)
assert('p2' in id_set_3)
@pytest.mark.asyncio
async def test_status_change(mock_status_change, user_info_factory):
res1 = await mock_status_change.fetch_new_post('dummy', [user_info_factory(lambda _: [1,2], lambda _: [])])
assert(len(res1) == 0)
res2 = await mock_status_change.fetch_new_post('dummy', [
user_info_factory(lambda _: [1,2], lambda _:[])
])
assert(len(res2) == 1)
posts = res2[0][1]
assert(len(posts) == 1)
assert(posts[0].text == 'on')
res3 = await mock_status_change.fetch_new_post('dummy', [
user_info_factory(lambda _: [1,2], lambda _: []),
user_info_factory(lambda _: [1], lambda _: []),
])
assert(len(res3) == 2)
assert(len(res3[0][1]) == 1)
assert(res3[0][1][0].text == 'off')
assert(len(res3[1][1]) == 0)
res4 = await mock_status_change.fetch_new_post('dummy', [user_info_factory(lambda _: [1,2], lambda _: [])])
assert(len(res4) == 0)