From b1b15b63a14d6d95a3435f5dc764fc48398c64a9 Mon Sep 17 00:00:00 2001 From: HibiKier <45528451+HibiKier@users.noreply.github.com> Date: Mon, 6 Sep 2021 18:03:21 +0800 Subject: [PATCH] Update data_source.py --- plugins/bilibili_sub/data_source.py | 545 ++++++++++------------------ 1 file changed, 183 insertions(+), 362 deletions(-) diff --git a/plugins/bilibili_sub/data_source.py b/plugins/bilibili_sub/data_source.py index d4032287..4e8e7f94 100644 --- a/plugins/bilibili_sub/data_source.py +++ b/plugins/bilibili_sub/data_source.py @@ -1,379 +1,200 @@ -from bilibili_api.exceptions.ResponseCodeException import ResponseCodeException -from asyncio.exceptions import TimeoutError +from nonebot import on_command +from nonebot.typing import T_State +from nonebot.adapters.cqhttp import Bot, MessageEvent, GroupMessageEvent, Message +from .data_source import ( + add_live_sub, + delete_sub, + add_up_sub, + add_season_sub, + get_media_id, + get_sub_status, + SubManager, +) +from models.level_user import LevelUser +from configs.config import GROUP_BILIBILI_SUB_LEVEL +from utils.utils import get_message_text, is_number, scheduler, get_bot from models.bilibili_sub import BilibiliSub -from bilibili_api.live import LiveRoom -from bilibili_api import bangumi -from utils.message_builder import image -from bilibili_api.user import User -from bilibili_api import user from typing import Optional -from pathlib import Path -from configs.path_config import IMAGE_PATH -from datetime import datetime -from utils.browser import get_browser -from services.db_context import db from services.log import logger -import aiohttp -import random +from nonebot import Driver +import nonebot + +__plugin_name__ = "B站订阅" + +__plugin_usage__ = """B站订阅帮助: + 添加订阅 [主播/UP/番剧] [id/链接/番名] + 删除订阅 [id] + 查看订阅""" + +add_sub = on_command("添加订阅", priority=5, block=True) +del_sub = on_command("删除订阅", priority=5, block=True) +show_sub_info = on_command('查看订阅', priority=5, block=True) + +driver: Driver = nonebot.get_driver() -bilibili_search_url = "https://api.bilibili.com/x/web-interface/search/all/v2" - -dynamic_path = Path(IMAGE_PATH) / "bilibili_sub" / "dynamic" -dynamic_path.mkdir(exist_ok=True, parents=True) +sub_manager: Optional[SubManager] = None -async def add_live_sub(live_id: int, sub_user: str) -> str: - """ - 添加直播订阅 - :param live_id: 直播房间号 - :param sub_user: 订阅用户 id # 7384933:private or 7384933:2342344(group) - :return: - """ - try: - async with db.transaction(): - try: - live = LiveRoom(live_id) - live_info = (await live.get_room_info())["room_info"] - except ResponseCodeException: - return f"未找到房间号Id:{live_id} 的信息,请检查Id是否正确" - uid = live_info["uid"] - room_id = live_info["room_id"] - short_id = live_info["short_id"] - title = live_info["title"] - live_status = live_info["live_status"] - if await BilibiliSub.add_bilibili_sub( - room_id, - "live", - sub_user, - uid=uid, - live_short_id=short_id, - live_status=live_status, - ): - await _get_up_status(live_id) - uname = (await BilibiliSub.get_sub(live_id)).uname - return ( - "已成功订阅主播:\n" - f"\ttitle:{title}\n" - f"\tname: {uname}\n" - f"\tlive_id:{live_id}\n" - f"\tuid:{uid}" - ) - else: - return "添加订阅失败..." - except Exception as e: - logger.error(f"订阅主播live_id:{live_id} 发生了错误 {type(e)}:{e}") - return "添加订阅失败..." +@driver.on_startup +async def _(): + global sub_manager + sub_manager = SubManager() -async def add_up_sub(uid: int, sub_user: str) -> str: - """ - 添加订阅 UP - :param uid: UP uid - :param sub_user: 订阅用户 - """ - try: - async with db.transaction(): - try: - u = user.User(uid) - user_info = await u.get_user_info() - except ResponseCodeException: - return f"未找到UpId:{uid} 的信息,请检查Id是否正确" - uname = user_info["name"] - dynamic_info = await u.get_dynamics(0) - dynamic_upload_time = 0 - if dynamic_info.get("cards"): - dynamic_upload_time = dynamic_info["cards"][0]["desc"]["timestamp"] - video_info = await u.get_videos() - latest_video_created = 0 - if video_info["list"].get("vlist"): - latest_video_created = video_info["list"]["vlist"][0]["created"] - if await BilibiliSub.add_bilibili_sub( - uid, - "up", - sub_user, - uid=uid, - uname=uname, - dynamic_upload_time=dynamic_upload_time, - latest_video_created=latest_video_created, - ): - return "已成功订阅UP:\n" f"\tname: {uname}\n" f"\tuid:{uid}" - else: - return "添加订阅失败..." - except Exception as e: - logger.error(f"订阅Up uid:{uid} 发生了错误 {type(e)}:{e}") - return "添加订阅失败..." +@add_sub.args_parser +async def _(bot: Bot, event: MessageEvent, state: T_State): + season_data = state["season_data"] + msg = get_message_text(event.json()) + if not is_number(msg) or int(msg) < 1 or int(msg) > len(season_data): + await add_sub.reject("Id必须为数字且在范围内!请重新输入...") + state["id"] = season_data[int(msg) - 1]["media_id"] -async def add_season_sub(media_id: int, sub_user: str) -> str: - """ - 添加订阅 UP - :param media_id: 番剧 media_id - :param sub_user: 订阅用户 - """ - try: - async with db.transaction(): - try: - season_info = await bangumi.get_meta(media_id) - except ResponseCodeException: - return f"未找到media_id:{media_id} 的信息,请检查Id是否正确" - season_id = season_info["media"]["season_id"] - season_current_episode = season_info["media"]["new_ep"]["index"] - season_name = season_info["media"]["title"] - if await BilibiliSub.add_bilibili_sub( - media_id, - "season", - sub_user, - season_name=season_name, - season_id=season_id, - season_current_episode=season_current_episode, - ): - return ( - "已成功订阅番剧:\n" - f"\ttitle: {season_name}\n" - f"\tcurrent_episode: {season_current_episode}" - ) - else: - return "添加订阅失败..." - except Exception as e: - logger.error(f"订阅番剧 media_id:{media_id} 发生了错误 {type(e)}:{e}") - return "添加订阅失败..." - - -async def delete_sub(sub_id: str, sub_user: str) -> str: - """ - 删除订阅 - :param sub_id: 订阅 id - :param sub_user: 订阅用户 id # 7384933:private or 7384933:2342344(group) - """ - if await BilibiliSub.delete_bilibili_sub(sub_id, sub_user): - return f"已成功取消订阅:{sub_id}" +@add_sub.handle() +async def _(bot: Bot, event: MessageEvent, state: T_State): + msg = get_message_text(event.json()).split() + if len(msg) < 2: + await add_sub.finish("参数不完全,请查看订阅帮助...") + sub_type = msg[0] + id_ = "" + if isinstance(event, GroupMessageEvent): + if not await LevelUser.check_level( + event.user_id, event.group_id, GROUP_BILIBILI_SUB_LEVEL + ): + await add_sub.finish( + f"您的权限不足,群内订阅的需要 {GROUP_BILIBILI_SUB_LEVEL} 级权限..", at_sender=True + ) + sub_user = f"{event.user_id}:{event.group_id}" else: - return f"取消订阅:{sub_id} 失败,请检查是否订阅过该Id...." + sub_user = f"{event.user_id}" + state["sub_type"] = sub_type + state["sub_user"] = sub_user + if len(msg) > 1: + if "http" in msg[1]: + msg[1] = msg[1].split("?")[0] + msg[1] = msg[1][:-1] if msg[1][-1] == "/" else msg[1] + msg[1] = msg[1].split("/")[-1] + id_ = msg[1][2:] if msg[1].startswith("md") else msg[1] + if not is_number(id_): + if sub_type in ["season", "动漫", "番剧"]: + rst = "*以为您找到以下番剧,请输入Id选择:*\n" + state["season_data"] = await get_media_id(id_) + print(state["season_data"]) + if len(state["season_data"]) == 0: + await add_sub.finish(f"未找到番剧:{msg}") + for i, x in enumerate(state["season_data"]): + rst += f'{i + 1}.{state["season_data"][x]["title"]}\n----------\n' + await add_sub.send("\n".join(rst.split("\n")[:-1])) + else: + await add_sub.finish("Id 必须为全数字!") + else: + state["id"] = int(id_) -async def get_media_id(keyword: str) -> dict: +@add_sub.got("id") +async def _(bot: Bot, event: MessageEvent, state: T_State): + sub_type = state["sub_type"] + sub_user = state["sub_user"] + id_ = state["id"] + if sub_type in ["主播", "直播"]: + await add_sub.send(await add_live_sub(id_, sub_user)) + elif sub_type.lower() in ["up", "用户"]: + await add_sub.send(await add_up_sub(id_, sub_user)) + elif sub_type in ["season", "动漫", "番剧"]: + await add_sub.send(await add_season_sub(id_, sub_user)) + else: + await add_sub.finish("参数错误,第一参数必须为:主播/up/番剧!") + sub_manager.reload_flag = True + logger.info( + f"(USER {event.user_id}, GROUP " + f"{event.group_id if isinstance(event, GroupMessageEvent) else 'private'})" + f" 添加订阅:{sub_type} -> {sub_user} -> {id_}" + ) + + +@del_sub.handle() +async def _(bot: Bot, event: MessageEvent, state: T_State): + msg = get_message_text(event.json()) + if not is_number(msg): + await del_sub.finish('Id必须为数字!', at_sender=True) + id_ = f'{event.user_id}:{event.group_id}' if isinstance(event, GroupMessageEvent) else f'{event.user_id}' + if await BilibiliSub.delete_bilibili_sub(int(msg), id_): + await del_sub.send(f'删除订阅id:{msg} 成功...') + logger.info( + f"(USER {event.user_id}, GROUP " + f"{event.group_id if isinstance(event, GroupMessageEvent) else 'private'})" + f" 删除订阅 {id_}" + ) + else: + await del_sub.send(f'删除订阅id:{msg} 失败...') + + +@show_sub_info.handle() +async def _(bot: Bot, event: MessageEvent, state: T_State): + id_ = f'{event.user_id}:{event.group_id}' if isinstance(event, GroupMessageEvent) else f'{event.user_id}' + data = await BilibiliSub.get_sub_data(id_) + live_rst = '' + up_rst = '' + season_rst = '' + for x in data: + if x.sub_type == 'live': + live_rst += f'\t直播间id:{x.sub_id}\n' \ + f'\t名称:{x.uname}\n' \ + f'------------------\n' + if x.sub_type == 'up': + up_rst += f'\tUP:{x.uname}\n' \ + f'\tuid:{x.uid}\n' \ + f'------------------\n' + if x.sub_type == 'season': + season_rst += f'\t番名:{x.season_name}\n' \ + f'\t当前集数:{x.season_current_episode}\n' \ + f'------------------\n' + live_rst = '当前订阅的直播:\n' + live_rst if live_rst else live_rst + up_rst = '当前订阅的UP:\n' + up_rst if up_rst else up_rst + season_rst = '当前订阅的番剧:\n' + season_rst if season_rst else season_rst + if not live_rst and not up_rst and not season_rst: + live_rst = '您目前没有任何订阅...' + await show_sub_info.send(live_rst + up_rst + season_rst) + + +# 推送 +@scheduler.scheduled_job( + "interval", + seconds=30, +) +async def _(): + bot = get_bot() + sub = None + if bot: + try: + await sub_manager.reload_sub_data() + sub = await sub_manager.random_sub_data() + if sub: + rst = await get_sub_status(sub.sub_id, sub.sub_type) + await send_sub_msg(rst, sub, bot) + if sub.sub_type == "live": + rst = await get_sub_status(sub.sub_id, "up") + await send_sub_msg(rst, sub, bot) + except Exception as e: + logger.error(f"B站订阅推送发生错误 sub_id:{sub.sub_id if sub else 0} {type(e)}:{e}") + + +async def send_sub_msg(rst: str, sub: BilibiliSub, bot: Bot): """ - 获取番剧的 media_id - :param keyword: 番剧名称 + 推送信息 + :param rst: 回复 + :param sub: BilibiliSub + :param bot: Bot """ - params = {"keyword": keyword} - async with aiohttp.ClientSession() as session: - for _ in range(3): + if rst: + for x in sub.sub_users.split(",")[:-1]: try: - _season_data = {} - async with session.get( - bilibili_search_url, timeout=5, params=params - ) as response: - if response.status == 200: - data = await response.json() - if data.get("data"): - for item in data["data"]["result"]: - if item["result_type"] == "media_bangumi": - idx = 0 - for x in item["data"]: - _season_data[idx] = { - "media_id": x["media_id"], - "title": x["title"] - .replace('', "") - .replace("", ""), - } - idx += 1 - return _season_data - except TimeoutError: - pass - return {} - - -async def get_sub_status(id_: int, sub_type: str) -> Optional[str]: - """ - 获取订阅状态 - :param id_: 订阅 id - :param sub_type: 订阅类型 - :return: - """ - try: - if sub_type == "live": - return await _get_live_status(id_) - elif sub_type == "up": - return await _get_up_status(id_) - elif sub_type == "season": - return await _get_season_status(id_) - except ResponseCodeException: - return "获取信息失败...请检查订阅Id是否存在或稍后再试..." - # except Exception as e: - # logger.error(f"获取订阅状态发生预料之外的错误 id_:{id_} {type(e)}:{e}") - # return "发生了预料之外的错误..请稍后再试或联系管理员....." - - -async def _get_live_status(id_: int) -> Optional[str]: - """ - 获取直播订阅状态 - :param id_: 直播间 id - """ - live = LiveRoom(id_) - live_info = (await live.get_room_info())["room_info"] - title = live_info["title"] - room_id = live_info["room_id"] - live_status = live_info["live_status"] - cover = live_info["cover"] - sub = await BilibiliSub.get_sub(id_) - if sub.live_status != live_status: - await BilibiliSub.update_sub_info(id_, live_status=live_status) - if sub.live_status == 0 and live_status == 1: - return ( - f"{image(cover)}\n" - f"{sub.uname} 开播啦!\n" - f"标题:{title}\n" - f"直链:https://live.bilibili.com/{room_id}" - ) - return None - - -async def _get_up_status(id_: int) -> Optional[str]: - """ - 获取用户投稿状态 - :param id_: 用户 id - :return: - """ - _user = await BilibiliSub.get_sub(id_) - u = user.User(_user.uid) - user_info = await u.get_user_info() - uname = user_info["name"] - video_info = await u.get_videos() - latest_video_created = 0 - video = None - if _user.uname != uname: - await BilibiliSub.update_sub_info(id_, uname=uname) - dynamic_img, dynamic_upload_time = await get_user_dynamic(u, _user) - if video_info["list"].get("vlist"): - video = video_info["list"]["vlist"][0] - latest_video_created = video["created"] - rst = "" - if dynamic_img: - await BilibiliSub.update_sub_info(id_, dynamic_upload_time=dynamic_upload_time) - rst += f"{uname} 发布了动态!\n" f"{dynamic_img}\n" - if _user.latest_video_created != latest_video_created and video: - rst = rst + "-------------\n" if rst else rst - await BilibiliSub.update_sub_info( - id_, latest_video_created=latest_video_created - ) - rst += ( - 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 == "-------------\n" else rst - return rst - - -async def _get_season_status(id_) -> Optional[str]: - """ - 获取 番剧 更新状态 - :param id_: 番剧 id - """ - season_info = await bangumi.get_meta(id_) - title = season_info["media"]["title"] - _idx = (await BilibiliSub.get_sub(id_)).season_current_episode - new_ep = season_info["media"]["new_ep"]["index"] - if new_ep != _idx: - await BilibiliSub.update_sub_info(id_, season_current_episode=new_ep, season_update_time=datetime.now()) - return ( - f'{image(season_info["media"]["cover"])}\n' f"[{title}]更新啦\n" f"最新集数:{new_ep}" - ) - return None - - -async def get_user_dynamic( - u: User, local_user: BilibiliSub -) -> "Optional[MessageSegment], int": - """ - 获取用户动态 - :param u: 用户类 - :param local_user: 数据库存储的用户数据 - :return: 最新动态截图与时间 - """ - dynamic_info = await u.get_dynamics(0) - browser = await get_browser() - if dynamic_info.get("cards") and browser: - dynamic_upload_time = dynamic_info["cards"][0]["desc"]["timestamp"] - if local_user.dynamic_upload_time != dynamic_upload_time: - page = await browser.new_page() - await page.goto( - f"https://space.bilibili.com/{local_user.uid}/dynamic", - wait_until="networkidle", - timeout=10000, - ) - await page.set_viewport_size({"width": 2560, "height": 1080}) - # 删除置顶 - await page.evaluate( - """ - xs = document.getElementsByClassName('first-card-with-title'); - for (x of xs) { - x.remove(); - } - """ - ) - card = await page.query_selector(".card") - # 截图并保存 - await card.screenshot( - path=dynamic_path / f"{local_user.sub_id}_{dynamic_upload_time}.jpg", - timeout=100000, - ) - await page.close() - return ( - image( - f"{local_user.sub_id}_{dynamic_upload_time}.jpg", - "bilibili_sub/dynamic", - ), - dynamic_upload_time, - ) - return None, None - - -class SubManager: - def __init__(self): - self.reload_flag = True - self.live_data = [] - self.up_data = [] - self.season_data = [] - self.sub_list = [] - - async def reload_sub_data(self): - """ - 重载数据 - """ - if self.reload_flag or not self.sub_list: - ( - self.live_data, - self.up_data, - self.season_data, - ) = await BilibiliSub.get_all_sub_data() - for x, i, j in zip(self.live_data, self.up_data, self.season_data): - self.sub_list.append(x) - self.sub_list.append(i) - self.sub_list.append(j) - self.reload_flag = False - - def append(self, data: BilibiliSub): - """ - 增加新数据 - :param data: 数据 - """ - self.sub_list.append(data) - - async def random_sub_data(self) -> Optional[BilibiliSub]: - """ - 随机获取一条数据 - :return: - """ - if not self.sub_list: - await self.reload_sub_data() - if self.sub_list: - sub = random.choice(self.sub_list) - self.sub_list.remove(sub) - return sub - return None - + if ":" in x: + await bot.send_group_msg( + group_id=int(x.split(":")[1]), message=Message(rst) + ) + else: + await bot.send_private_msg(user_id=int(x), message=Message(rst)) + except Exception as e: + logger.error(f"B站订阅推送发生错误 sub_id:{sub.sub_id} {type(e)}:{e}")