from bilibili_api.exceptions.ResponseCodeException import ResponseCodeException from utils.manager import resources_manager from asyncio.exceptions import TimeoutError from .model 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 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 from utils.http_utils import AsyncHttpx import random bilibili_search_url = "https://api.bilibili.com/x/web-interface/search/all/v2" dynamic_path = IMAGE_PATH / "bilibili_sub" / "dynamic" dynamic_path.mkdir(exist_ok=True, parents=True) resources_manager.add_temp_dir(dynamic_path) 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 "添加订阅失败..." 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 "添加订阅失败..." 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(int(sub_id), sub_user): return f"已成功取消订阅:{sub_id}" else: return f"取消订阅:{sub_id} 失败,请检查是否订阅过该Id...." async def get_media_id(keyword: str) -> dict: """ 获取番剧的 media_id :param keyword: 番剧名称 """ params = {"keyword": keyword} for _ in range(3): try: _season_data = {} response = await AsyncHttpx.get( bilibili_search_url, params=params, timeout=5 ) if response.status_code == 200: data = 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: 订阅类型 """ 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 f"Id:{id_} 获取信息失败...请检查订阅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 ( latest_video_created and _user.latest_video_created and video and _user.latest_video_created < latest_video_created ): 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() try: 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, ) except Exception as e: logger.error(f"B站订阅:获取用户动态 发送错误 {type(e)}:{e}") finally: 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.live_data = [] self.up_data = [] self.season_data = [] self.current_index = -1 async def reload_sub_data(self): """ 重载数据 """ if not self.live_data or not self.up_data or not self.season_data: ( _live_data, _up_data, _season_data, ) = await BilibiliSub.get_all_sub_data() if not self.live_data: self.live_data = _live_data if not self.up_data: self.up_data = _up_data if not self.season_data: self.season_data = _season_data async def random_sub_data(self) -> Optional[BilibiliSub]: """ 随机获取一条数据 :return: """ sub = None if not self.live_data and not self.up_data and not self.season_data: return sub self.current_index += 1 if self.current_index == 0: if self.live_data: sub = random.choice(self.live_data) self.live_data.remove(sub) elif self.current_index == 1: if self.up_data: sub = random.choice(self.up_data) self.up_data.remove(sub) elif self.current_index == 2: if self.season_data: sub = random.choice(self.season_data) self.season_data.remove(sub) else: self.current_index = -1 if sub: return sub await self.reload_sub_data() return await self.random_sub_data()