diff --git a/.env.dev b/.env.dev index 283bcdfa..d73006a1 100644 --- a/.env.dev +++ b/.env.dev @@ -13,6 +13,7 @@ SESSION_EXPIRE_TIMEOUT=60 # B站相关插件Cookie配置(可选) # 强烈建议您正确配置此项, 否则B站动态及直播间监控很可能被B站风控限制! +BILI_UID= BILI_SESSDATA= BILI_CSRF= diff --git a/README.md b/README.md index be3fca19..4c6f3c34 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ ## 当前适配nonebot2版本 -[Nonebot2 PreRelease v2.0.0a9](https://github.com/nonebot/nonebot2/releases/tag/v2.0.0a9) +[Nonebot2 PreRelease v2.0.0a9.post1](https://github.com/nonebot/nonebot2/releases/tag/v2.0.0a9.post1) ## 配套使用的api diff --git a/omega_miya/plugins/Omega_help/__init__.py b/omega_miya/plugins/Omega_help/__init__.py index c30394ab..0a0eb239 100644 --- a/omega_miya/plugins/Omega_help/__init__.py +++ b/omega_miya/plugins/Omega_help/__init__.py @@ -36,7 +36,7 @@ async def handle_first_receive(bot: Bot, event: GroupMessageEvent, state: T_Stat else: # 如果用户没有发送参数, 则发送功能列表并结束此命令 plugins_list = '\n'.join(p.export.custom_name for p in plugins) - await bot_help.finish(f'我现在支持的插件有: \n\n{plugins_list}\n\n输入"/help [插件]"即可查看对应帮助') + await bot_help.finish(f'我现在支持的插件有: \n\n{plugins_list}\n\n注意: 群组权限等级未达到要求的命令不会被响应\n输入"/help [插件]"即可查看插件详情及帮助') @bot_help.got('plugin_name', prompt='你想查询哪个插件的用法呢?') diff --git a/omega_miya/plugins/bilibili_dynamic_monitor/monitor.py b/omega_miya/plugins/bilibili_dynamic_monitor/monitor.py index 109f1c2d..2ac3c29d 100644 --- a/omega_miya/plugins/bilibili_dynamic_monitor/monitor.py +++ b/omega_miya/plugins/bilibili_dynamic_monitor/monitor.py @@ -2,7 +2,7 @@ from nonebot import logger, require, get_bots from nonebot.adapters.cqhttp import MessageSegment from omega_miya.utils.Omega_Base import DBSubscription, DBDynamic, DBTable -from .utils import get_dynamic_info, get_user_info, get_user_dynamic, pic_2_base64 +from .utils import get_user_dynamic_history, get_user_info, get_user_dynamic, get_dynamic_info, pic_2_base64 # 启用检查动态状态的定时任务 @@ -86,7 +86,7 @@ async def bilibili_dynamic_monitor(): async def check_dynamic(dy_uid): # 获取动态并返回动态类型及内容 try: - _res = await get_dynamic_info(dy_uid=dy_uid) + _res = await get_user_dynamic_history(dy_uid=dy_uid) if not _res.success(): logger.error(f'bilibili_dynamic_monitor: 获取动态失败, uid: {dy_uid}, error: {_res.info}') return @@ -117,27 +117,38 @@ async def check_dynamic(dy_uid): logger.info(f"用户: {dy_uid}/{dynamic_info[num]['name']} 新动态: {dynamic_info[num]['id']}") # 转发的动态 if dynamic_info[num]['type'] == 1: - # 原动态type=2, 带图片 - if dynamic_info[num]['origin']['type'] == 2: - # 处理图片序列 - pic_segs = '' - for pic_url in dynamic_info[num]['origin']['origin_pics']: - _res = await pic_2_base64(pic_url) - pic_b64 = _res.result - pic_segs += f'{MessageSegment.image(pic_b64)}\n' - msg = '{}转发了{}的动态!\n\n“{}”\n{}\n{}\n@{}: {}\n{}'.format( - dynamic_info[num]['name'], dynamic_info[num]['origin']['name'], - dynamic_info[num]['content'], dynamic_info[num]['url'], '=' * 16, - dynamic_info[num]['origin']['name'], dynamic_info[num]['origin']['content'], - pic_segs - ) - # 原动态为其他类型, 无图 - else: + # 获取原动态信息 + origin_dynamic_id = dynamic_info[num]['origin'] + _dy_res = await get_dynamic_info(dynamic_id=origin_dynamic_id) + if not _dy_res.success(): msg = '{}转发了{}的动态!\n\n“{}”\n{}\n{}\n@{}: {}'.format( - dynamic_info[num]['name'], dynamic_info[num]['origin']['name'], + dynamic_info[num]['name'], 'Unknown', dynamic_info[num]['content'], dynamic_info[num]['url'], '=' * 16, - dynamic_info[num]['origin']['name'], dynamic_info[num]['origin']['content'] + 'Unknown', '获取原动态失败' ) + else: + origin_dynamic_info = _dy_res.result + # 原动态type=2, 带图片 + if origin_dynamic_info['type'] == 2: + # 处理图片序列 + pic_segs = '' + for pic_url in origin_dynamic_info['origin_pics']: + _res = await pic_2_base64(pic_url) + pic_b64 = _res.result + pic_segs += f'{MessageSegment.image(pic_b64)}\n' + msg = '{}转发了{}的动态!\n\n“{}”\n{}\n{}\n@{}: {}\n{}'.format( + dynamic_info[num]['name'], origin_dynamic_info['name'], + dynamic_info[num]['content'], dynamic_info[num]['url'], '=' * 16, + origin_dynamic_info['name'], origin_dynamic_info['content'], + pic_segs + ) + # 原动态为其他类型, 无图 + else: + msg = '{}转发了{}的动态!\n\n“{}”\n{}\n{}\n@{}: {}'.format( + dynamic_info[num]['name'], origin_dynamic_info['name'], + dynamic_info[num]['content'], dynamic_info[num]['url'], '=' * 16, + origin_dynamic_info['name'], origin_dynamic_info['content'] + ) for group_id in notice_group: for _bot in bots: try: diff --git a/omega_miya/plugins/bilibili_dynamic_monitor/utils.py b/omega_miya/plugins/bilibili_dynamic_monitor/utils.py index 8a43f183..b2b4b886 100644 --- a/omega_miya/plugins/bilibili_dynamic_monitor/utils.py +++ b/omega_miya/plugins/bilibili_dynamic_monitor/utils.py @@ -13,6 +13,7 @@ global_config = nonebot.get_driver().config BILI_SESSDATA = global_config.bili_sessdata BILI_CSRF = global_config.bili_csrf +BILI_UID = global_config.bili_uid def check_bili_cookies() -> Result: @@ -41,8 +42,8 @@ async def fetch_json(url: str, paras: dict = None) -> Result: 'accept-language:': 'zh-CN,zh;q=0.9', 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) ' 'AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36', - 'origin': 'https://space.bilibili.com', - 'referer': 'https://space.bilibili.com/'} + 'origin': 'https://t.bilibili.com', + 'referer': 'https://t.bilibili.com/'} async with session.get(url=url, params=paras, headers=headers, cookies=cookies, timeout=timeout) as rp: _json = await rp.json() result = Result(error=False, info='Success', result=_json) @@ -68,7 +69,8 @@ async def get_image(pic_url: str): async with aiohttp.ClientSession(timeout=timeout) as session: headers = {'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) ' 'AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36', - 'referer': 'https://www.bilibili.com/'} + 'origin': 'https://t.bilibili.com', + 'referer': 'https://t.bilibili.com/'} async with session.get(url=pic_url, headers=headers, timeout=timeout) as resp: _res = await resp.read() return _res @@ -128,10 +130,14 @@ def get_user_dynamic(user_id: int) -> Result: # 查询动态并返回动态类型及内容 -async def get_dynamic_info(dy_uid) -> Result: +async def get_user_dynamic_history(dy_uid) -> Result: _DYNAMIC_INFO = {} # 这个字典用来放最后的输出结果 url = DYNAMIC_API_URL - payload = {'host_uid': dy_uid} + if BILI_UID: + payload = {'visitor_uid': BILI_UID, 'host_uid': dy_uid, + 'offset_dynamic_id': 0, 'need_top': 0, 'platform': 'web'} + else: + payload = {'host_uid': dy_uid, 'offset_dynamic_id': 0, 'need_top': 0, 'platform': 'web'} result = await fetch_json(url=url, paras=payload) if not result.success(): @@ -168,55 +174,10 @@ async def get_dynamic_info(dy_uid) -> Result: name = cards['desc']['user_profile']['info']['uname'] # 这是转发动态时评论的内容 content = card['item']['content'] - # 这是被转发的原动态信息 - try: - origin_dy_uid = cards['desc']['origin']['dynamic_id'] - __payload = {'dynamic_id': origin_dy_uid} - - result = await fetch_json(url=GET_DYNAMIC_DETAIL_API_URL, paras=__payload) - origin_dynamic = dict(result.result) - origin_card = origin_dynamic['data']['card'] - origin_name = origin_card['desc']['user_profile']['info']['uname'] - origin_pics_list = [] - if origin_card['desc']['type'] == 1: - origin_description = json.loads(origin_card['card'])['item']['content'] - elif origin_card['desc']['type'] == 2: - origin_description = json.loads(origin_card['card'])['item']['description'] - origin_pics = json.loads(origin_card['card'])['item']['pictures'] - for item in origin_pics: - try: - origin_pics_list.append(item['img_src']) - except (KeyError, TypeError): - continue - elif origin_card['desc']['type'] == 4: - origin_description = json.loads(origin_card['card'])['item']['content'] - elif origin_card['desc']['type'] == 8: - origin_description = json.loads(origin_card['card'])['dynamic'] - if not origin_description: - origin_description = json.loads(origin_card['card'])['title'] - elif origin_card['desc']['type'] == 16: - origin_description = json.loads(origin_card['card'])['item']['description'] - elif origin_card['desc']['type'] == 32: - origin_description = json.loads(origin_card['card'])['title'] - elif origin_card['desc']['type'] == 64: - origin_description = json.loads(origin_card['card'])['summary'] - elif origin_card['desc']['type'] == 256: - origin_description = json.loads(origin_card['card'])['intro'] - elif origin_card['desc']['type'] == 512: - origin_description = json.loads(origin_card['card'])['apiSeasonInfo']['title'] - elif origin_card['desc']['type'] == 2048: - origin_description = json.loads(origin_card['card'])['vest']['content'] - else: - origin_description = '' - origin = dict({'id': origin_dy_uid, 'type': origin_card['desc']['type'], 'url': '', - 'name': origin_name, 'content': origin_description, 'origin': '', - 'origin_pics': origin_pics_list}) - except Exception as e: - # 原动态被删除 - origin = dict({'id': -1, 'type': -1, 'url': '', - 'name': 'Unknow', 'content': '原动态被删除', 'origin': repr(e)}) + # 这是被转发的原动态id + origin_dynamic_id = cards['desc']['origin']['dynamic_id'] card_dic = dict({'id': dy_id, 'type': 1, 'url': url, - 'name': name, 'content': content, 'origin': origin}) + 'name': name, 'content': content, 'origin': origin_dynamic_id}) _DYNAMIC_INFO[card_num] = card_dic # type=2, 这是一条原创的动态(有图片) elif cards['desc']['type'] == 2: @@ -352,17 +313,71 @@ async def get_dynamic_info(dy_uid) -> Result: dy_id = cards['desc']['dynamic_id'] # 这是动态的链接 url = DYNAMIC_URL + str(cards['desc']['dynamic_id']) - name = 'Unknow' + name = 'Unknown' card_dic = dict({'id': dy_id, 'type': -1, 'url': url, 'name': name, 'content': '', 'origin': ''}) _DYNAMIC_INFO[card_num] = card_dic return Result(error=False, info='Success', result=_DYNAMIC_INFO) +async def get_dynamic_info(dynamic_id) -> Result: + __payload = {'dynamic_id': dynamic_id} + _res = await fetch_json(url=GET_DYNAMIC_DETAIL_API_URL, paras=__payload) + if not _res.success(): + return _res + else: + try: + origin_dynamic = dict(_res.result) + origin_card = origin_dynamic['data']['card'] + origin_name = origin_card['desc']['user_profile']['info']['uname'] + origin_pics_list = [] + if origin_card['desc']['type'] == 1: + origin_description = json.loads(origin_card['card'])['item']['content'] + elif origin_card['desc']['type'] == 2: + origin_description = json.loads(origin_card['card'])['item']['description'] + origin_pics = json.loads(origin_card['card'])['item']['pictures'] + for item in origin_pics: + try: + origin_pics_list.append(item['img_src']) + except (KeyError, TypeError): + continue + elif origin_card['desc']['type'] == 4: + origin_description = json.loads(origin_card['card'])['item']['content'] + elif origin_card['desc']['type'] == 8: + origin_description = json.loads(origin_card['card'])['dynamic'] + if not origin_description: + origin_description = json.loads(origin_card['card'])['title'] + elif origin_card['desc']['type'] == 16: + origin_description = json.loads(origin_card['card'])['item']['description'] + elif origin_card['desc']['type'] == 32: + origin_description = json.loads(origin_card['card'])['title'] + elif origin_card['desc']['type'] == 64: + origin_description = json.loads(origin_card['card'])['summary'] + elif origin_card['desc']['type'] == 256: + origin_description = json.loads(origin_card['card'])['intro'] + elif origin_card['desc']['type'] == 512: + origin_description = json.loads(origin_card['card'])['apiSeasonInfo']['title'] + elif origin_card['desc']['type'] == 2048: + origin_description = json.loads(origin_card['card'])['vest']['content'] + else: + origin_description = '' + origin = dict({'id': dynamic_id, 'type': origin_card['desc']['type'], 'url': '', + 'name': origin_name, 'content': origin_description, 'origin': '', + 'origin_pics': origin_pics_list}) + result = Result(error=False, info='Success', result=origin) + except Exception as e: + # 原动态被删除 + origin = dict({'id': dynamic_id, 'type': -1, 'url': '', + 'name': 'Unknown', 'content': '原动态被删除', 'origin': repr(e)}) + result = Result(error=True, info='Dynamic not found', result=origin) + return result + + __all__ = [ 'pic_2_base64', 'get_user_info', 'get_user_dynamic', + 'get_user_dynamic_history', 'get_dynamic_info' ] @@ -370,5 +385,5 @@ async def get_dynamic_info(dy_uid) -> Result: import asyncio loop = asyncio.get_event_loop() - res = loop.run_until_complete(get_dynamic_info(dy_uid=846180)) + res = loop.run_until_complete(get_user_dynamic_history(dy_uid=846180)) print(res) diff --git a/omega_miya/plugins/bilibili_live_monitor/monitor.py b/omega_miya/plugins/bilibili_live_monitor/monitor.py index 26c0d405..6269259c 100644 --- a/omega_miya/plugins/bilibili_live_monitor/monitor.py +++ b/omega_miya/plugins/bilibili_live_monitor/monitor.py @@ -1,7 +1,8 @@ import asyncio +import time from nonebot import logger, require, get_driver, get_bots from nonebot.adapters.cqhttp import MessageSegment -from omega_miya.utils.Omega_Base import DBSubscription, DBTable +from omega_miya.utils.Omega_Base import DBSubscription, DBHistory, DBTable from .utils import get_live_info, get_user_info, pic_2_base64, verify_cookies @@ -20,8 +21,9 @@ async def init_live_info(): if _res.success(): logger.opt(colors=True).info(f'Bilibili 已登录! 当前用户: {_res.result}') else: - logger.opt(colors=True).warning(f'Bilibili 未登录! B站动态及直播间监控很可能被B站风控限制! 请在配置中设置cookies以保证插件正常运行!') + logger.opt(colors=True).warning(f'Bilibili 登录状态异常: {_res.info}! 建议在配置中正确设置cookies!') + logger.opt(colors=True).info('init_live_info: 初始化B站直播间监控列表...') t = DBTable(table_name='Subscription') for item in t.list_col_with_condition('sub_id', 'sub_type', 1).result: sub_id = int(item[0]) @@ -50,7 +52,7 @@ async def init_live_info(): except Exception as e: logger.error(f'init_live_info: 获取直播间信息错误, room_id: {sub_id}, error: {repr(e)}') continue - logger.info('init_live_info: 初始化完成') + logger.opt(colors=True).info('init_live_info: B站直播间监控列表初始化完成.') # 初始化任务加入启动序列 @@ -194,6 +196,11 @@ async def check_live(room_id: int): try: # 现在状态为未开播 if live_info['status'] == 0: + live_start_info = f"LiveEnd! Room: {room_id}/{up_name}" + new_event = DBHistory(time=int(time.time()), self_id=-1, post_type='bilibili', detail_type='live') + new_event.add(sub_type='live_end', user_id=room_id, user_name=up_name, + raw_data=repr(live_info), msg_data=live_start_info) + msg = f'{up_name}下播了' # 通知有通知权限且订阅了该直播间的群 for group_id in notice_group: @@ -209,9 +216,12 @@ async def check_live(room_id: int): logger.info(f"直播间: {room_id}/{up_name} 下播了") # 现在状态为直播中 elif live_info['status'] == 1: - # 打一条log记录准确开播信息 - logger.info(f"开播记录: LiveStart! Room: {room_id}/{up_name}, Title: {live_info['title']}, " - f"TrueTime: {live_info['time']}") + # 记录准确开播信息 + live_start_info = f"LiveStart! Room: {room_id}/{up_name}, Title: {live_info['title']}, " \ + f"TrueTime: {live_info['time']}" + new_event = DBHistory(time=int(time.time()), self_id=-1, post_type='bilibili', detail_type='live') + new_event.add(sub_type='live_start', user_id=room_id, user_name=up_name, + raw_data=repr(live_info), msg_data=live_start_info) cover_pic = await pic_2_base64(url=live_info.get('cover_img')) if cover_pic.success(): @@ -232,6 +242,11 @@ async def check_live(room_id: int): logger.info(f"直播间: {room_id}/{up_name} 开播了") # 现在状态为未开播(轮播中) elif live_info['status'] == 2: + live_start_info = f"LiveEnd! Room: {room_id}/{up_name}" + new_event = DBHistory(time=int(time.time()), self_id=-1, post_type='bilibili', detail_type='live') + new_event.add(sub_type='live_end_with_playlist', user_id=room_id, user_name=up_name, + raw_data=repr(live_info), msg_data=live_start_info) + msg = f'{up_name}下播了(轮播中)' for group_id in notice_group: for _bot in bots: diff --git a/omega_miya/plugins/bilibili_live_monitor/utils.py b/omega_miya/plugins/bilibili_live_monitor/utils.py index a6d199fe..db3a8011 100644 --- a/omega_miya/plugins/bilibili_live_monitor/utils.py +++ b/omega_miya/plugins/bilibili_live_monitor/utils.py @@ -11,6 +11,7 @@ global_config = nonebot.get_driver().config BILI_SESSDATA = global_config.bili_sessdata BILI_CSRF = global_config.bili_csrf +BILI_UID = global_config.bili_uid def check_bili_cookies() -> Result: @@ -66,6 +67,7 @@ async def get_image(pic_url: str): async with aiohttp.ClientSession(timeout=timeout) as session: headers = {'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) ' 'AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36', + 'origin': 'https://www.bilibili.com', 'referer': 'https://www.bilibili.com/'} async with session.get(url=pic_url, headers=headers, timeout=timeout) as resp: _res = await resp.read() @@ -144,7 +146,11 @@ async def verify_cookies() -> Result: data = dict(_res.result.get('data')) if code == 0 and data.get('isLogin'): uname = data.get('uname') - result = Result(error=False, info='Success login', result=uname) + mid = data.get('mid') + if mid == BILI_UID: + result = Result(error=False, info='Success login', result=uname) + else: + result = Result(error=True, info='Logged user UID does not match', result=uname) else: result = Result(error=True, info='Not login', result='') return result diff --git a/omega_miya/plugins/miya_button/__init__.py b/omega_miya/plugins/miya_button/__init__.py new file mode 100644 index 00000000..8b78e7a0 --- /dev/null +++ b/omega_miya/plugins/miya_button/__init__.py @@ -0,0 +1,35 @@ +import re +import os +from nonebot import MatcherGroup, logger +from nonebot.typing import T_State +from nonebot.rule import to_me +from nonebot.adapters.cqhttp.bot import Bot +from nonebot.adapters.cqhttp.message import MessageSegment +from nonebot.adapters.cqhttp.event import GroupMessageEvent +from nonebot.adapters.cqhttp.permission import GROUP +from omega_miya.utils.Omega_plugin_utils import has_command_permission, permission_level +from .resources import MiyaVoice + +""" +miya按钮bot实现版本 +测试中 +""" + + +button = MatcherGroup(type='message', rule=to_me() & has_command_permission() & permission_level(level=10), + permission=GROUP, priority=100, block=False) + + +miya_button = button.on_endswith(msg='喵一个') + + +@miya_button.handle() +async def miya_button(bot: Bot, event: GroupMessageEvent, state: T_State): + arg = str(event.get_plaintext()).strip().lower() + voice = re.sub('喵一个', '', arg) + voice_file = MiyaVoice().get_voice_filepath(voice=voice) + if not os.path.exists(voice_file): + await bot.send(event=event, message='喵?') + else: + msg = MessageSegment.record(file=f'file:///{voice_file}') + await bot.send(event=event, message=msg) diff --git a/omega_miya/plugins/miya_button/resources/__init__.py b/omega_miya/plugins/miya_button/resources/__init__.py new file mode 100644 index 00000000..0038a0ed --- /dev/null +++ b/omega_miya/plugins/miya_button/resources/__init__.py @@ -0,0 +1,56 @@ +import os +import random + + +class Voice(object): + def __init__(self): + self.VoicesFiles = None + + def get_voice_filepath(self, voice: str) -> str: + plugin_path = os.path.dirname(os.path.abspath(__file__)) + if voice in self.VoicesFiles.keys(): + return os.path.join(plugin_path, 'voices', self.VoicesFiles[voice]['file']) + else: + voice_list = [] + for name, content in self.VoicesFiles.items(): + if voice == content['tag']: + voice_list.append(content['file']) + if not voice_list: + return '' + else: + return os.path.join(plugin_path, 'voices', random.choice(voice_list)) + + +class MiyaVoice(Voice): + def __init__(self): + """ + 硬编码音频素材文件 + 先暂时这样以后有空再改 + """ + super().__init__() + self.VoicesFiles = { + '表演绝活': { + 'file': '0.mp3', + 'tag': '' + }, + '你才千岁幼猫': { + 'file': '2.mp3', + 'tag': '' + }, + '坏蛋': { + 'file': '3.mp3', + 'tag': '' + }, + '我信了你的鬼话': { + 'file': '4.mp3', + 'tag': '' + }, + '来打我': { + 'file': '5.mp3', + 'tag': '' + }, + '说别人憨的人': { + 'file': '6.mp3', + 'tag': '' + } + } diff --git a/requirements.txt b/requirements.txt index 61a1c34d..206aab2b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,15 +1,15 @@ -nonebot2==2.0.0a9 +nonebot2==2.0.0a9.post1 sqlalchemy~=1.3.23 mysqlclient~=2.0.3 -aiocqhttp +aiocqhttp~=1.3.0 bs4~=0.0.1 -lxml +lxml~=4.6.2 Pillow~=8.1.0 beautifulsoup4~=4.9.3 Jinja2~=2.11.3 aiohttp~=3.7.3 -xlwt +xlwt~=1.3.0 ujson~=4.0.2 -msgpack +msgpack~=1.0.2 pydantic~=1.7.2 APScheduler~=3.6.3 \ No newline at end of file