fix🐛: 修正b站订阅

This commit is contained in:
HibiKier 2023-09-06 18:53:15 +08:00
parent fb26be821b
commit 1130569a20
8 changed files with 3372 additions and 3235 deletions

View File

@ -331,6 +331,10 @@ PS: **ARM平台** 请使用全量版 同时 **如果你的机器 RAM < 1G 可能
## 更新 ## 更新
### 2023/9/6
* 修正b站订阅
### 2023/8/28 ### 2023/8/28
* 重构`红包`功能, 允许一个群聊中有多个用户发起的红包,发送`开`等命令会开启群中所有条件允许的红包,新增`红包结算排行`,在红包退回或抢完时统计,在`塞红包`时at可以发送专属红包 * 重构`红包`功能, 允许一个群聊中有多个用户发起的红包,发送`开`等命令会开启群中所有条件允许的红包,新增`红包结算排行`,在红包退回或抢完时统计,在`塞红包`时at可以发送专属红包

View File

@ -70,6 +70,12 @@ __plugin_configs__ = {
"default_value": False, "default_value": False,
"type": bool, "type": bool,
}, },
"DOWNLOAD_DYNAMIC_IMAGE": {
"value": True,
"help": "下载动态中的图片并发送在提醒消息中",
"default_value": True,
"type": bool,
},
} }
add_sub = on_command("添加订阅", priority=5, block=True) add_sub = on_command("添加订阅", priority=5, block=True)
@ -255,11 +261,11 @@ async def _():
if sub: if sub:
try: try:
logger.debug(f"Bilibili订阅开始检测{sub.sub_id}") logger.debug(f"Bilibili订阅开始检测{sub.sub_id}")
rst = await get_sub_status(sub.sub_id, sub.sub_type) rst = await get_sub_status(sub.sub_id, sub.sub_id, sub.sub_type)
await send_sub_msg(rst or "", sub, bot) # type: ignore await send_sub_msg(rst, sub, bot) # type: ignore
if sub.sub_type == "live": if sub.sub_type == "live":
rst = await get_sub_status(sub.sub_id, "up") rst += "\n" + await get_sub_status(sub.uid, sub.sub_id, "up")
await send_sub_msg(rst or "", sub, bot) # type: ignore await send_sub_msg(rst, sub, bot) # type: ignore
except Exception as e: except Exception as e:
logger.error(f"B站订阅推送发生错误 sub_id{sub.sub_id}", e=e) logger.error(f"B站订阅推送发生错误 sub_id{sub.sub_id}", e=e)
@ -298,4 +304,4 @@ async def send_sub_msg(rst: str, sub: BilibiliSub, bot: Bot):
else: else:
await bot.send_private_msg(user_id=int(x), message=Message(rst)) await bot.send_private_msg(user_id=int(x), message=Message(rst))
except Exception as e: except Exception as e:
logger.error(f"B站订阅推送发生错误 sub_id{sub.sub_id}", e=e) logger.error(f"B站订阅推送发生错误 sub_id: {sub.sub_id}", e=e)

View File

@ -1,22 +1,25 @@
import random import random
from asyncio.exceptions import TimeoutError from asyncio.exceptions import TimeoutError
from datetime import datetime from datetime import datetime
from typing import Optional, Tuple from typing import Optional, Tuple, Union
# from .utils import get_videos # from .utils import get_videos
from bilireq import dynamic from bilireq import dynamic
from bilireq.exceptions import ResponseCodeError from bilireq.exceptions import ResponseCodeError
from bilireq.grpc.dynamic import grpc_get_user_dynamics
from bilireq.grpc.protos.bilibili.app.dynamic.v2.dynamic_pb2 import DynamicType
from bilireq.live import get_room_info_by_id from bilireq.live import get_room_info_by_id
from bilireq.user import get_videos from bilireq.user import get_videos
from nonebot.adapters.onebot.v11 import MessageSegment from nonebot.adapters.onebot.v11 import Message, MessageSegment
from configs.config import Config
from configs.path_config import IMAGE_PATH, TEMP_PATH from configs.path_config import IMAGE_PATH, TEMP_PATH
from services.log import logger from services.log import logger
from utils.browser import get_browser from utils.browser import get_browser
from utils.http_utils import AsyncHttpx, AsyncPlaywright from utils.http_utils import AsyncHttpx, AsyncPlaywright
from utils.manager import resources_manager from utils.manager import resources_manager
from utils.message_builder import image from utils.message_builder import image
from utils.utils import get_bot from utils.utils import get_bot, get_local_proxy
from .model import BilibiliSub from .model import BilibiliSub
from .utils import get_meta, get_user_card from .utils import get_meta, get_user_card
@ -27,6 +30,17 @@ DYNAMIC_PATH = IMAGE_PATH / "bilibili_sub" / "dynamic"
DYNAMIC_PATH.mkdir(exist_ok=True, parents=True) DYNAMIC_PATH.mkdir(exist_ok=True, parents=True)
TYPE2MSG = {
0: "发布了新动态",
DynamicType.forward: "转发了一条动态",
DynamicType.word: "发布了新文字动态",
DynamicType.draw: "发布了新图文动态",
DynamicType.av: "发布了新投稿",
DynamicType.article: "发布了新专栏",
DynamicType.music: "发布了新音频",
}
resources_manager.add_temp_dir(DYNAMIC_PATH) resources_manager.add_temp_dir(DYNAMIC_PATH)
@ -52,6 +66,15 @@ async def add_live_sub(live_id: str, sub_user: str) -> str:
short_id = live_info["short_id"] short_id = live_info["short_id"]
title = live_info["title"] title = live_info["title"]
live_status = live_info["live_status"] live_status = live_info["live_status"]
try:
user_info = await get_user_card(uid)
except ResponseCodeError:
return f"未找到UpId{uid} 的信息请检查Id是否正确"
uname = user_info["name"]
dynamic_info = await dynamic.get_user_dynamics(int(uid))
dynamic_upload_time = 0
if dynamic_info.get("cards"):
dynamic_upload_time = dynamic_info["cards"][0]["desc"]["dynamic_id"]
if await BilibiliSub.sub_handle( if await BilibiliSub.sub_handle(
room_id, room_id,
"live", "live",
@ -59,11 +82,9 @@ async def add_live_sub(live_id: str, sub_user: str) -> str:
uid=uid, uid=uid,
live_short_id=short_id, live_short_id=short_id,
live_status=live_status, live_status=live_status,
uname=uname,
dynamic_upload_time=dynamic_upload_time,
): ):
try:
await _get_up_status(room_id)
except Exception as e:
logger.error(f"获取主播UP信息失败: {live_id} 错误", e=e)
if data := await BilibiliSub.get_or_none(sub_id=room_id): if data := await BilibiliSub.get_or_none(sub_id=room_id):
uname = data.uname uname = data.uname
return ( return (
@ -102,11 +123,7 @@ async def add_up_sub(uid: str, sub_user: str) -> str:
"""bilibili_api.user库中User类的get_dynamics改为bilireq.dynamic库的get_user_dynamics方法""" """bilibili_api.user库中User类的get_dynamics改为bilireq.dynamic库的get_user_dynamics方法"""
dynamic_info = await dynamic.get_user_dynamics(int(uid)) dynamic_info = await dynamic.get_user_dynamics(int(uid))
if dynamic_info.get("cards"): if dynamic_info.get("cards"):
dynamic_upload_time = dynamic_info["cards"][0]["desc"]["timestamp"] dynamic_upload_time = dynamic_info["cards"][0]["desc"]["dynamic_id"]
"""bilibili_api.user库中User类的get_videos改为bilireq.user库的get_videos方法"""
video_info = await get_videos(int(uid))
if video_info["list"].get("vlist"):
latest_video_created = video_info["list"]["vlist"][0]["created"]
except Exception as e: except Exception as e:
logger.error(f"订阅Up uid: {uid} 错误", e=e) logger.error(f"订阅Up uid: {uid} 错误", e=e)
if await BilibiliSub.sub_handle( if await BilibiliSub.sub_handle(
@ -204,7 +221,7 @@ async def get_media_id(keyword: str) -> Optional[dict]:
return {} return {}
async def get_sub_status(id_: str, sub_type: str) -> Optional[str]: async def get_sub_status(id_: str, sub_id: str, sub_type: str) -> Union[Message, str]:
""" """
获取订阅状态 获取订阅状态
:param id_: 订阅 id :param id_: 订阅 id
@ -214,7 +231,7 @@ async def get_sub_status(id_: str, sub_type: str) -> Optional[str]:
if sub_type == "live": if sub_type == "live":
return await _get_live_status(id_) return await _get_live_status(id_)
elif sub_type == "up": elif sub_type == "up":
return await _get_up_status(id_) return await _get_up_status(id_, sub_id)
elif sub_type == "season": elif sub_type == "season":
return await _get_season_status(id_) return await _get_season_status(id_)
except ResponseCodeError as e: except ResponseCodeError as e:
@ -223,9 +240,10 @@ async def get_sub_status(id_: str, sub_type: str) -> Optional[str]:
except Exception as e: except Exception as e:
logger.error(f"获取订阅状态发生预料之外的错误 Id_{id_}", e=e) logger.error(f"获取订阅状态发生预料之外的错误 Id_{id_}", e=e)
# return "发生了预料之外的错误..请稍后再试或联系管理员....." # return "发生了预料之外的错误..请稍后再试或联系管理员....."
return ""
async def _get_live_status(id_: str) -> Optional[str]: async def _get_live_status(id_: str) -> str:
""" """
获取直播订阅状态 获取直播订阅状态
:param id_: 直播间 id :param id_: 直播间 id
@ -247,56 +265,94 @@ async def _get_live_status(id_: str) -> Optional[str]:
f"标题:{title}\n" f"标题:{title}\n"
f"直链https://live.bilibili.com/{room_id}" f"直链https://live.bilibili.com/{room_id}"
) )
return None return ""
async def _get_up_status(id_: str) -> Optional[str]: async def _get_up_status(
""" id_: str, live_id: Optional[str] = None
获取用户投稿状态 ) -> Union[Message, str]:
:param id_: 订阅 id """获取up动态
:return:
参数:
id_: up的id
live_id: 直播间id当订阅直播间时才有.
返回:
Union[Message, str]: 消息
""" """
rst = "" rst = ""
if _user := await BilibiliSub.get_or_none(sub_id=id_): if _user := await BilibiliSub.get_or_none(sub_id=live_id or id_):
"""bilibili_api.user库中User类的get_user_info改为bilireq.user库的get_user_info方法""" dynamics = None
user_info = await get_user_card(_user.uid) dynamic = None
uname = user_info["name"] uname = ""
"""bilibili_api.user库中User类的get_videos改为bilireq.user库的get_videos方法""" try:
video_info = await get_videos(int(_user.uid)) dynamics = (
latest_video_created = 0 await grpc_get_user_dynamics(int(id_), proxy=get_local_proxy())
video = None ).list
dividing_line = "\n-------------\n" except Exception as e:
logger.error("获取动态失败...", target=id_, e=e)
if dynamics:
uname = dynamics[0].modules[0].module_author.author.name
for dyn in dynamics:
if int(dyn.extend.dyn_id_str) > _user.dynamic_upload_time:
dynamic = dyn
break
if not dynamic:
logger.debug(f"{_user.sub_type}:{id_} 未有任何动态, 已跳过....")
return ""
if _user.uname != uname: if _user.uname != uname:
await BilibiliSub.sub_handle(id_, uname=uname) await BilibiliSub.sub_handle(live_id or id_, uname=uname)
dynamic_img, dynamic_upload_time, link = await get_user_dynamic( dynamic_img, link = await get_user_dynamic(dynamic.extend.dyn_id_str, _user)
_user.uid, _user if not dynamic_img:
logger.debug(f"{id_} 未发布新动态或截图失败, 已跳过....")
return ""
await BilibiliSub.sub_handle(
live_id or id_, dynamic_upload_time=int(dynamic.extend.dyn_id_str)
) )
if video_info["list"].get("vlist"): rst += (
video = video_info["list"]["vlist"][0] f"{uname} {TYPE2MSG.get(dynamic.card_type, TYPE2MSG[0])}\n"
latest_video_created = video["created"] + dynamic_img
if dynamic_img: + f"\n{link}\n"
await BilibiliSub.sub_handle(id_, dynamic_upload_time=dynamic_upload_time) )
rst += f"{uname} 发布了动态!\n" f"{dynamic_img}\n{link}" video_info = ""
if ( if video_list := [
latest_video_created module
and _user.latest_video_created for module in dynamic.modules
and video if str(module.module_dynamic.dyn_archive)
and _user.latest_video_created < latest_video_created ]:
): video = video_list[0].module_dynamic.dyn_archive
rst = rst + dividing_line if rst else rst video_info = (
await BilibiliSub.sub_handle(id_, latest_video_created=latest_video_created) image(video.cover)
rst += ( + f"标题: {video.title}\nBvid: {video.bvid}\n直链: https://www.bilibili.com/video/{video.bvid}"
f'{image(video["pic"])}\n'
f"{uname} 投稿了新视频啦\n"
f'标题:{video["title"]}\n'
f'Bvid{video["bvid"]}\n'
f'直链https://www.bilibili.com/video/{video["bvid"]}'
) )
rst = None if rst == dividing_line else rst rst += video_info + "\n"
download_dynamic_image = Config.get_config(
"bilibili_sub", "DOWNLOAD_DYNAMIC_IMAGE"
)
draw_info = ""
if download_dynamic_image and (
draw_list := [
module.module_dynamic.dyn_draw
for module in dynamic.modules
if str(module.module_dynamic.dyn_draw)
]
):
idx = 0
for draws in draw_list:
for draw in list(draws.items):
path = (
TEMP_PATH
/ f"{_user.uid}_{dynamic.extend.dyn_id_str}_draw_{idx}.jpg"
)
if await AsyncHttpx.download_file(draw.src, path):
draw_info += image(path)
idx += 1
if draw_info:
rst += "动态图片\n" + draw_info + "\n"
return rst return rst
async def _get_season_status(id_: str) -> Optional[str]: async def _get_season_status(id_: str) -> str:
""" """
获取 番剧 更新状态 获取 番剧 更新状态
:param id_: 番剧 id :param id_: 番剧 id
@ -316,36 +372,30 @@ async def _get_season_status(id_: str) -> Optional[str]:
f"[{title}]更新啦\n" f"[{title}]更新啦\n"
f"最新集数:{new_ep}" f"最新集数:{new_ep}"
) )
return None return ""
async def get_user_dynamic( async def get_user_dynamic(
uid: str, local_user: BilibiliSub dynamic_id: str, local_user: BilibiliSub
) -> Tuple[Optional[MessageSegment], int, str]: ) -> Tuple[Optional[MessageSegment], str]:
""" """
获取用户动态 获取用户动态
:param uid: 用户uid :param dynamic_id: 动态id
:param local_user: 数据库存储的用户数据 :param local_user: 数据库存储的用户数据
:return: 最新动态截图与时间 :return: 最新动态截图与时间
""" """
"""bilibili_api.user库中User类的get_dynamics改为bilireq.dynamic库的get_user_dynamics方法""" if local_user.dynamic_upload_time < int(dynamic_id):
dynamic_info = await dynamic.get_user_dynamics(int(uid)) image = await AsyncPlaywright.screenshot(
if dynamic_info.get("cards"): f"https://t.bilibili.com/{dynamic_id}",
dynamic_upload_time = dynamic_info["cards"][0]["desc"]["timestamp"] DYNAMIC_PATH / f"sub_{local_user.sub_id}.png",
dynamic_id = dynamic_info["cards"][0]["desc"]["dynamic_id"] ".bili-dyn-item__main",
if local_user.dynamic_upload_time < dynamic_upload_time: wait_until="networkidle",
image = await AsyncPlaywright.screenshot( )
f"https://t.bilibili.com/{dynamic_id}", return (
DYNAMIC_PATH / f"sub_{local_user.sub_id}.png", image,
".bili-dyn-item__main", f"https://t.bilibili.com/{dynamic_id}",
wait_until="networkidle", )
) return None, ""
return (
image,
dynamic_upload_time,
f"https://t.bilibili.com/{dynamic_id}",
)
return None, 0, ""
class SubManager: class SubManager:

6357
poetry.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -36,7 +36,7 @@ dateparser = "^1.1.0"
cn2an = "^0.5.16" cn2an = "^0.5.16"
python-jose = "^3.3.0" python-jose = "^3.3.0"
python-multipart = "^0.0.5" python-multipart = "^0.0.5"
bilireq = "^0.2.3.post0" bilireq = "^0.2.6"
emoji = "^1.7.0" emoji = "^1.7.0"
wordcloud = "^1.8.1" wordcloud = "^1.8.1"
rich = "^12.4.3" rich = "^12.4.3"

View File

@ -13,7 +13,7 @@ from playwright.async_api import BrowserContext, Page
from retrying import retry from retrying import retry
from services.log import logger from services.log import logger
from utils.user_agent import get_user_agent from utils.user_agent import get_user_agent, get_user_agent_str
from .browser import get_browser from .browser import get_browser
from .message_builder import image from .message_builder import image
@ -218,7 +218,7 @@ class AsyncHttpx:
else: else:
logger.error(f"下载 {url} 下载超时.. Path{path.absolute()}") logger.error(f"下载 {url} 下载超时.. Path{path.absolute()}")
except Exception as e: except Exception as e:
logger.error(f"下载 {url} 未知错误 {type(e)}{e}.. Path{path.absolute()}") logger.error(f"下载 {url} 错误 Path{path.absolute()}", e=e)
return False return False
@classmethod @classmethod
@ -329,6 +329,7 @@ class AsyncPlaywright:
] = "networkidle", ] = "networkidle",
timeout: Optional[float] = None, timeout: Optional[float] = None,
type_: Optional[Literal["jpeg", "png"]] = None, type_: Optional[Literal["jpeg", "png"]] = None,
user_agent: Optional[str] = None,
**kwargs, **kwargs,
) -> Optional[MessageSegment]: ) -> Optional[MessageSegment]:
""" """
@ -353,7 +354,11 @@ class AsyncPlaywright:
element_list = [element] element_list = [element]
else: else:
element_list = element element_list = element
async with cls.new_page(viewport=viewport_size) as page: async with cls.new_page(
viewport=viewport_size,
user_agent=user_agent,
**kwargs,
) as page:
await page.goto(url, timeout=timeout, wait_until=wait_until) await page.goto(url, timeout=timeout, wait_until=wait_until)
card = page card = page
for e in element_list: for e in element_list:

View File

@ -7,7 +7,7 @@ import uuid
from io import BytesIO from io import BytesIO
from math import ceil from math import ceil
from pathlib import Path from pathlib import Path
from typing import Awaitable, Callable, List, Literal, Optional, Tuple, Union from typing import Any, Awaitable, Callable, List, Literal, Optional, Tuple, Union
import cv2 import cv2
import imagehash import imagehash
@ -353,16 +353,16 @@ class BuildImage:
:param font_size: 字体大小 :param font_size: 字体大小
""" """
font_ = cls.load_font(font, font_size) font_ = cls.load_font(font, font_size)
return font_.getsize(msg) return font_.getsize(msg) # type: ignore
def getsize(self, msg: str) -> Tuple[int, int]: def getsize(self, msg: Any) -> Tuple[int, int]:
""" """
说明: 说明:
获取文字在该图片 font_size 下所需要的空间 获取文字在该图片 font_size 下所需要的空间
参数: 参数:
:param msg: 文字内容 :param msg: 文字内容
""" """
return self.font.getsize(msg) return self.font.getsize(str(msg)) # type: ignore
async def apoint( async def apoint(
self, pos: Tuple[int, int], fill: Optional[Tuple[int, int, int]] = None self, pos: Tuple[int, int], fill: Optional[Tuple[int, int, int]] = None

View File

@ -1,6 +1,5 @@
import random import random
user_agent = [ user_agent = [
"Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_8; en-us) AppleWebKit/534.50 (KHTML, like Gecko) Version/5.1 Safari/534.50", "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_8; en-us) AppleWebKit/534.50 (KHTML, like Gecko) Version/5.1 Safari/534.50",
"Mozilla/5.0 (Windows; U; Windows NT 6.1; en-us) AppleWebKit/534.50 (KHTML, like Gecko) Version/5.1 Safari/534.50", "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-us) AppleWebKit/534.50 (KHTML, like Gecko) Version/5.1 Safari/534.50",
@ -45,3 +44,7 @@ user_agent = [
def get_user_agent(): def get_user_agent():
return {"User-Agent": random.choice(user_agent)} return {"User-Agent": random.choice(user_agent)}
def get_user_agent_str():
return random.choice(user_agent)