更换哔哩哔哩直播使用的api,添加直播间标题修改时推送直播间的功能 (#128)

* feat(bililive):调整bililive所使用的api与推送消息内容

* feat(bililive):调整功能的实现细节

* test(bililive): 初步完善相关测试

* docs(bililive): 补充文档相关

* change(bililive): 将使用修改配置项以调整订阅的方式调整为使用category

* test(bililive): 调整测试,扩大测试范围

* change(bililive):调整Bilibili-live内部的代码实现

* test(bililive):修正测试

* delete(bililive):删除用来测试功能性的临时文件

* change(bililive): 调整代码
This commit is contained in:
AzideCupric 2022-10-19 23:49:02 +08:00 committed by GitHub
parent 710eab42f1
commit e14fd5bfd7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 231 additions and 140 deletions

View File

@ -1,13 +1,17 @@
import functools
import json
import re
from copy import deepcopy
from dataclasses import dataclass, field
from datetime import datetime, timedelta
from typing import Any, Callable, Optional
import httpx
from httpx import AsyncClient
from nonebot.log import logger
from typing_extensions import Self
from ..plugin_config import plugin_config
from ..post import Post
from ..types import Category, RawPost, Tag, Target
from ..utils import SchedulerConfig
@ -203,7 +207,7 @@ class Bilibililive(StatusChange):
# Date : 2022-5-18 8:54
# Description : bilibili开播提醒
# E-mail : 1557157806@qq.com
categories = {}
categories = {1: "开播提醒", 2: "标题更新提醒"}
platform_name = "bilibili-live"
enable_tag = False
enabled = True
@ -212,6 +216,37 @@ class Bilibililive(StatusChange):
name = "Bilibili直播"
has_target = True
@dataclass
class Info:
uname: str
live_status: int
room_id: str
title: str
cover_from_user: str
keyframe: str
category: Category = field(default=Category(0))
def __init__(self, raw_info: dict):
self.__dict__.update(raw_info)
def is_live_turn_on(self, old_info: Self) -> bool:
# 使用 & 判断直播开始
# live_status:
# 0:关播
# 1:直播中
# 2:轮播中
if self.live_status == 1 and old_info.live_status != self.live_status:
return True
return False
def is_title_update(self, old_info: Self) -> bool:
# 使用 ^ 判断直播时标题改变
if self.live_status == 1 and old_info.title != self.title:
return True
return False
@classmethod
async def get_target_name(
cls, client: AsyncClient, target: Target
@ -224,40 +259,49 @@ class Bilibililive(StatusChange):
return None
return res_data["data"]["name"]
async def get_status(self, target: Target):
params = {"mid": target}
async def get_status(self, target: Target) -> Info:
params = {"uids[]": target}
# from https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/live/info.md#%E6%89%B9%E9%87%8F%E6%9F%A5%E8%AF%A2%E7%9B%B4%E6%92%AD%E9%97%B4%E7%8A%B6%E6%80%81
res = await self.client.get(
"https://api.bilibili.com/x/space/acc/info",
"http://api.live.bilibili.com/room/v1/Room/get_status_info_by_uids",
params=params,
timeout=4.0,
)
res_dict = json.loads(res.text)
if res_dict["code"] == 0:
info = {}
info["uid"] = res_dict["data"]["mid"]
info["uname"] = res_dict["data"]["name"]
info["live_state"] = res_dict["data"]["live_room"]["liveStatus"]
info["room_id"] = res_dict["data"]["live_room"]["roomid"]
info["title"] = res_dict["data"]["live_room"]["title"]
info["cover"] = res_dict["data"]["live_room"]["cover"]
data = res_dict["data"][target]
info = self.Info(data)
return info
else:
raise self.FetchError()
def compare_status(self, target: Target, old_status, new_status) -> list[RawPost]:
if (
new_status["live_state"] != old_status["live_state"]
and new_status["live_state"] == 1
):
return [new_status]
def compare_status(
self, target: Target, old_status: Info, new_status: Info
) -> list[RawPost]:
if new_status.is_live_turn_on(old_status):
# 判断开播 运算符左右有顺序要求
current_status = deepcopy(new_status)
current_status.category = Category(1)
return [current_status]
elif new_status.is_title_update(old_status):
# 判断直播时直播间标题变更 运算符左右有顺序要求
current_status = deepcopy(new_status)
current_status.category = Category(2)
return [current_status]
else:
return []
async def parse(self, raw_post: RawPost) -> Post:
url = "https://live.bilibili.com/{}".format(raw_post["room_id"])
pic = [raw_post["cover"]]
target_name = raw_post["uname"]
title = raw_post["title"]
def get_category(self, status: Info) -> Category:
assert status.category != Category(0)
return status.category
async def parse(self, raw_info: Info) -> Post:
url = "https://live.bilibili.com/{}".format(raw_info.room_id)
pic = [raw_info.keyframe]
target_name = raw_info.uname
title = raw_info.title
return Post(
self.name,
text=title,

View File

@ -1,3 +1,3 @@
from .post import Post
__all__ = ["Post"]
__all__ = ["Post", "CustomPost"]

View File

@ -1,114 +1,31 @@
{
"code": 0,
"message": "0",
"ttl": 1,
"msg": "success",
"message": "success",
"data": {
"mid": 13164144,
"name": "魔法Zc目录",
"sex": "男",
"face": "http://i0.hdslb.com/bfs/face/a84fa10f90f7060d0336384954ee1cde7a8e9bc6.jpg",
"face_nft": 0,
"sign": "每日18:00~22:00欢乐直播请勿在任何乌有相关内容中刷Zc尊重角色商务合作qq271374252",
"rank": 10000,
"level": 6,
"jointime": 0,
"moral": 0,
"silence": 0,
"coins": 0,
"fans_badge": true,
"fans_medal": {
"show": false,
"wear": false,
"medal": null
},
"official": {
"role": 1,
"title": "bilibili 2021百大UP主、知名游戏UP主、直播高能主播",
"desc": "",
"type": 0
},
"vip": {
"type": 2,
"status": 1,
"due_date": 1702051200000,
"vip_pay_type": 0,
"theme_type": 0,
"label": {
"path": "",
"text": "年度大会员",
"label_theme": "annual_vip",
"text_color": "#FFFFFF",
"bg_style": 1,
"bg_color": "#FB7299",
"border_color": ""
},
"avatar_subscript": 1,
"nickname_color": "#FB7299",
"role": 3,
"avatar_subscript_url": "http://i0.hdslb.com/bfs/vip/icon_Certification_big_member_22_3x.png"
},
"pendant": {
"pid": 3399,
"name": "2233幻星集",
"image": "http://i0.hdslb.com/bfs/garb/item/20c07ded13498a5b12db99660c766ddd92ecfe31.png",
"expire": 0,
"image_enhance": "http://i0.hdslb.com/bfs/garb/item/20c07ded13498a5b12db99660c766ddd92ecfe31.png",
"image_enhance_frame": ""
},
"nameplate": {
"nid": 1,
"name": "黄金殿堂",
"image": "http://i2.hdslb.com/bfs/face/82896ff40fcb4e7c7259cb98056975830cb55695.png",
"image_small": "http://i0.hdslb.com/bfs/face/627e342851dfda6fe7380c2fa0cbd7fae2e61533.png",
"level": "稀有勋章",
"condition": "单个自制视频总播放数\u003e=100万"
},
"user_honour_info": {
"mid": 0,
"colour": null,
"tags": []
},
"is_followed": true,
"top_photo": "http://i2.hdslb.com/bfs/space/853fea2728651588a2cdef0a1e586bcefff8e3d8.png",
"theme": {},
"sys_notice": {},
"live_room": {
"roomStatus": 1,
"liveStatus": 0,
"url": "https://live.bilibili.com/3044248?broadcast_type=0\u0026is_room_feed=1",
"title": "【Zc】早朝危机合约",
"cover": "http://i0.hdslb.com/bfs/live/new_room_cover/cf7d4d3b2f336c6dba299644c3af952c5db82612.jpg",
"roomid": 3044248,
"roundStatus": 1,
"broadcast_type": 0,
"watched_show": {
"switch": true,
"num": 13753,
"text_small": "1.3万",
"text_large": "1.3万人看过",
"icon": "https://i0.hdslb.com/bfs/live/a725a9e61242ef44d764ac911691a7ce07f36c1d.png",
"icon_location": "",
"icon_web": "https://i0.hdslb.com/bfs/live/8d9d0f33ef8bf6f308742752d13dd0df731df19c.png"
}
},
"birthday": "07-21",
"school": {
"name": ""
},
"profession": {
"name": "",
"department": "",
"title": "",
"is_show": 0
},
"tags": [
"评论区UP主",
"目标是星辰大海"
],
"series": {
"user_upgrade_status": 3,
"show_upgrade_window": false
},
"is_senior_member": 1
"13164144": {
"title": "【Zc】从0挑战到15肉鸽目前10难度",
"room_id": 3044248,
"uid": 13164144,
"online": 270881,
"live_time": 0,
"live_status": 2,
"short_id": 0,
"area": 1,
"area_name": "单机联机",
"area_v2_id": 235,
"area_v2_name": "其他单机",
"area_v2_parent_name": "单机游戏",
"area_v2_parent_id": 6,
"uname": "魔法Zc目录",
"face": "https://i0.hdslb.com/bfs/face/a84fa10f90f7060d0336384954ee1cde7a8e9bc6.jpg",
"tag_name": "以撒,minecraft,饥荒,彩虹六号,东方",
"tags": "明日方舟,搞笑,单机游戏,游戏",
"cover_from_user": "https://i0.hdslb.com/bfs/live/new_room_cover/fd357f0f3cbbb48e9acfbcda616b946c2454c56c.jpg",
"keyframe": "https://i0.hdslb.com/bfs/live-key-frame/keyframe10170435000003044248mwowx0.jpg",
"lock_till": "0000-00-00 00:00:00",
"hidden_till": "0000-00-00 00:00:00",
"broadcast_type": 0
}
}
}

View File

@ -13,13 +13,23 @@ def bili_live(app: App):
return platform_manager["bilibili-live"](AsyncClient())
@pytest.fixture
def dummy_only_status_user_subinfo(app: App):
from nonebot_bison.types import User, UserSubInfo
user = User(123, "group")
return UserSubInfo(user=user, categories=[1], tags=[])
@pytest.mark.asyncio
@respx.mock
async def test_fetch_bilibili_live_status(bili_live, dummy_user_subinfo):
async def test_fetch_bililive_only_status_change(
bili_live, dummy_only_status_user_subinfo
):
mock_bili_live_status = get_json("bili_live_status.json")
bili_live_router = respx.get(
"https://api.bilibili.com/x/space/acc/info?mid=13164144"
"http://api.live.bilibili.com/room/v1/Room/get_status_info_by_uids?uids[]=13164144"
)
bili_live_router.mock(return_value=Response(200, json=mock_bili_live_status))
@ -27,18 +37,138 @@ async def test_fetch_bilibili_live_status(bili_live, dummy_user_subinfo):
bilibili_main_page_router.mock(return_value=Response(200))
target = "13164144"
res = await bili_live.fetch_new_post(target, [dummy_user_subinfo])
assert bili_live_router.called
res = await bili_live.fetch_new_post(target, [dummy_only_status_user_subinfo])
assert bili_live_router.call_count == 1
assert len(res) == 0
mock_bili_live_status["data"]["live_room"]["liveStatus"] = 1
# 直播状态更新
mock_bili_live_status["data"][target]["live_status"] = 1
bili_live_router.mock(return_value=Response(200, json=mock_bili_live_status))
res2 = await bili_live.fetch_new_post(target, [dummy_user_subinfo])
res2 = await bili_live.fetch_new_post(target, [dummy_only_status_user_subinfo])
post = res2[0][1][0]
assert post.target_type == "Bilibili直播"
assert post.text == "【Zc】早朝危机合约!"
assert post.text == "【Zc】从0挑战到15肉鸽目前10难度"
assert post.url == "https://live.bilibili.com/3044248"
assert post.target_name == "魔法Zc目录"
assert post.pics == [
"http://i0.hdslb.com/bfs/live/new_room_cover/cf7d4d3b2f336c6dba299644c3af952c5db82612.jpg"
"https://i0.hdslb.com/bfs/live-key-frame/keyframe10170435000003044248mwowx0.jpg"
]
assert post.compress == True
# 标题变更
mock_bili_live_status["data"][target]["title"] = "【Zc】从0挑战到15肉鸽目前11难度"
bili_live_router.mock(return_value=Response(200, json=mock_bili_live_status))
res3 = await bili_live.fetch_new_post(target, [dummy_only_status_user_subinfo])
assert bili_live_router.call_count == 3
assert len(res3[0][1]) == 0
@pytest.fixture
def dummy_only_title_user_subinfo(app: App):
from nonebot_bison.types import User, UserSubInfo
user = User(123, "group")
return UserSubInfo(user=user, categories=[2], tags=[])
@pytest.mark.asyncio
@respx.mock
async def test_fetch_bililive_only_title_change(
bili_live, dummy_only_title_user_subinfo
):
mock_bili_live_status = get_json("bili_live_status.json")
target = "13164144"
bili_live_router = respx.get(
"http://api.live.bilibili.com/room/v1/Room/get_status_info_by_uids?uids[]=13164144"
)
bili_live_router.mock(return_value=Response(200, json=mock_bili_live_status))
bilibili_main_page_router = respx.get("https://www.bilibili.com/")
bilibili_main_page_router.mock(return_value=Response(200))
res = await bili_live.fetch_new_post(target, [dummy_only_title_user_subinfo])
assert bili_live_router.call_count == 1
assert len(res) == 0
# 未开播前标题变更
mock_bili_live_status["data"][target]["title"] = "【Zc】从0挑战到15肉鸽目前11难度"
bili_live_router.mock(return_value=Response(200, json=mock_bili_live_status))
res0 = await bili_live.fetch_new_post(target, [dummy_only_title_user_subinfo])
assert bili_live_router.call_count == 2
assert len(res0) == 0
# 直播状态更新
mock_bili_live_status["data"][target]["live_status"] = 1
bili_live_router.mock(return_value=Response(200, json=mock_bili_live_status))
res2 = await bili_live.fetch_new_post(target, [dummy_only_title_user_subinfo])
assert bili_live_router.call_count == 3
assert len(res2[0][1]) == 0
# 标题变更
mock_bili_live_status["data"][target]["title"] = "【Zc】从0挑战到15肉鸽目前12难度"
bili_live_router.mock(return_value=Response(200, json=mock_bili_live_status))
res3 = await bili_live.fetch_new_post(target, [dummy_only_title_user_subinfo])
post = res3[0][1][0]
assert post.target_type == "Bilibili直播"
assert post.text == "【Zc】从0挑战到15肉鸽目前12难度"
assert post.url == "https://live.bilibili.com/3044248"
assert post.target_name == "魔法Zc目录"
assert post.pics == [
"https://i0.hdslb.com/bfs/live-key-frame/keyframe10170435000003044248mwowx0.jpg"
]
assert post.compress == True
@pytest.fixture
def dummy_bililive_user_subinfo(app: App):
from nonebot_bison.types import User, UserSubInfo
user = User(123, "group")
return UserSubInfo(user=user, categories=[1, 2], tags=[])
@pytest.mark.asyncio
@respx.mock
async def test_fetch_bililive_combo(bili_live, dummy_bililive_user_subinfo):
mock_bili_live_status = get_json("bili_live_status.json")
target = "13164144"
bili_live_router = respx.get(
"http://api.live.bilibili.com/room/v1/Room/get_status_info_by_uids?uids[]=13164144"
)
bili_live_router.mock(return_value=Response(200, json=mock_bili_live_status))
bilibili_main_page_router = respx.get("https://www.bilibili.com/")
bilibili_main_page_router.mock(return_value=Response(200))
res = await bili_live.fetch_new_post(target, [dummy_bililive_user_subinfo])
assert bili_live_router.call_count == 1
assert len(res) == 0
# 未开播前标题变更
mock_bili_live_status["data"][target]["title"] = "【Zc】从0挑战到15肉鸽目前11难度"
bili_live_router.mock(return_value=Response(200, json=mock_bili_live_status))
res0 = await bili_live.fetch_new_post(target, [dummy_bililive_user_subinfo])
assert bili_live_router.call_count == 2
assert len(res0) == 0
# 直播状态更新
mock_bili_live_status["data"][target]["live_status"] = 1
bili_live_router.mock(return_value=Response(200, json=mock_bili_live_status))
res2 = await bili_live.fetch_new_post(target, [dummy_bililive_user_subinfo])
post2 = res2[0][1][0]
assert post2.target_type == "Bilibili直播"
assert post2.text == "【Zc】从0挑战到15肉鸽目前11难度"
assert post2.url == "https://live.bilibili.com/3044248"
assert post2.target_name == "魔法Zc目录"
assert post2.pics == [
"https://i0.hdslb.com/bfs/live-key-frame/keyframe10170435000003044248mwowx0.jpg"
]
assert post2.compress == True
# 标题变更
mock_bili_live_status["data"][target]["title"] = "【Zc】从0挑战到15肉鸽目前12难度"
bili_live_router.mock(return_value=Response(200, json=mock_bili_live_status))
res3 = await bili_live.fetch_new_post(target, [dummy_bililive_user_subinfo])
post3 = res3[0][1][0]
assert post3.target_type == "Bilibili直播"
assert post3.text == "【Zc】从0挑战到15肉鸽目前12难度"
assert post3.url == "https://live.bilibili.com/3044248"
assert post3.target_name == "魔法Zc目录"
assert post3.pics == [
"https://i0.hdslb.com/bfs/live-key-frame/keyframe10170435000003044248mwowx0.jpg"
]
assert post3.compress == True