6 |
7 | # nonebot-plugin-bilibilibot
8 |
9 | 👾 _NoneBot bilibili通知插件_ 👾
10 |
version: 2.3.4
11 |
12 |
13 |
14 | # 简介
15 | 基于[Nonebot2](https://github.com/nonebot/nonebot2)的bilibili通知插件,可将up主投稿视频,主播开播,番剧的更新以及用户更新动态推送到QQ
16 |
17 | **已支持v2.0.0-beta.5**
18 | ## 依赖
19 | - 适配器: onebot v11
20 | - [go-cqhttp](https://github.com/Mrs4s/go-cqhttp)
21 | - 插件:
22 | - [nonebot_plugin_apscheduler](https://pypi.org/project/nonebot-plugin-apscheduler/)
23 |
24 | # 特色功能
25 | - 可通过在B站客户端分享用户主页进行关注up主(私聊)
26 |
27 | - 可通过在B站客户端分享直播间进行关注主播(私聊)
28 |
29 | - 可通过分享番剧播放页面进行关注番剧(私聊)
30 |
31 | - 超级用户可对普通用户进行广播
32 |
33 | - 将机器人加入群组,向群友及时同时直播或更新消息
34 |
35 | # 安装方式
36 | - ` nb plugin install nonebot-plugin-bilibilibot`
37 |
38 | # 配置项
39 | 配置方式: 请在nonebot的全局配置文件中添加如下配置项。
40 | ## SUPERUSERS
41 | - 类型: List[str]
42 | - 说明: 超级用户的列表
43 | > SUPERSUSERS = ["your qq id"]
44 |
45 | # 注意事项
46 | - 将机器人加入群组后,只有**管理员\群主\超级管理员**才能对机器人进行操作
47 | - 未避免误触发,群组中不能使用分享链接来关注的功能
48 | - 由于需要同时处理群消息和私聊消息,建议在非调试环境中使用,否则日志将会出现很多的ignore消息
49 | - 如果需要修改公告内容,请修改file/source/announcement.json文件
50 |
51 | # 示例
52 | ## 获取帮助
53 | 
54 | ## 视频更新推送
55 | 
56 | ## 番剧更新推送
57 | 
58 | ## 直播开播推送
59 | 
60 | ## 动态更新推送
61 | 
62 |
63 | ## 通过B站客户端分享进行关注
64 | 
65 | ## 查询关注列表
66 | 
67 |
68 |
69 | # 更新日志
70 | [完整日志](https://github.com/TDK1969/nonebot_plugin_bilibilibot/blob/main/file/source/ChangeLog.md)
71 |
72 | - **ver 2.3.4**
73 | ```
74 | 1. 修复了番剧集数错误以及更新不推送的问题
75 | 2. 增加了主播的下播提醒
76 | ```
77 |
78 | - **ver 2.3.3**
79 | ```
80 | 1. 修复了由于b站接口改变导致-352和-401的错误
81 | 2. 优化了网络IO
82 | ```
83 |
84 | - **ver 2.3.2**
85 | ```
86 | 1. 修复了由于b站接口改变导致关注up主命令失败的问题
87 | 2. 为了避免命令冲突,将命令`/help`改为`/bilihelp`
88 | ```
89 |
90 | - **ver 2.3.1**
91 | ```
92 | 1. 修复了setuptools打包错误导致的import失败问题
93 | 2. 修复了由于路径改变导致使用`/help`命令失败的问题
94 | 3. 已经完结的番剧不会再进行更新检查了
95 | ```
96 |
97 | - **ver 2.2.0**
98 | ```
99 | 1. 修改某些B站的接口,减少被接口风控的风险
100 | 2. 可以使用ep_id, season_id和media_id对番剧进行关注,需要携带前两个字符
101 | 3. 本次更新需要重置数据库,因此会丢失旧版本的关注数据,需要重新关注
102 | ```
103 | ep_id: https://www.bilibili.com/bangumi/play/ep433947, 中的**ep433947**
104 |
105 | season_id: https://www.bilibili.com/bangumi/play/ss39431, 中的**ss39431**
106 |
107 | media_id: https://www.bilibili.com/bangumi/media/md28235123, 中**md28235123**
108 |
109 |
110 | # 特别鸣谢
111 | - 感谢[@0w0w0](https://github.com/a0w0w0)帮助测试
112 |
--------------------------------------------------------------------------------
/__init__.py:
--------------------------------------------------------------------------------
1 | from nonebot import get_driver, on_command, on_message, require, permission, get_bot
2 | from nonebot.log import logger
3 | from nonebot.rule import regex
4 |
5 | from nonebot.adapters.onebot.v11 import Message, MessageSegment, MessageEvent
6 | from nonebot.adapters.onebot.v11.permission import GROUP_ADMIN, GROUP_OWNER, PRIVATE_FRIEND, GROUP_MEMBER
7 | from nonebot.adapters.onebot.v11.event import GroupMessageEvent, PrivateMessageEvent
8 | from nonebot.params import CommandArg
9 | from nonebot.permission import SUPERUSER
10 | from .config import Config
11 | import sys
12 | sys.path.append("..")
13 | from .bili_src.biliStream import *
14 | from .bili_src.biliVideo import *
15 | from .bili_src.biliTelegram import *
16 | from .bili_src.basicFunc import *
17 | from .bili_src.bili_dynamic import follow_dynamic_list, unfollow_dynamic_list
18 | from .bili_src.rule import groupMessageRule, privateMessageRule
19 | from .bili_src.bili_task import BiliTaskManager
20 | from .bili_src.bili_dynamic import check_dynamic_update
21 |
22 | import os
23 | import json
24 | import sys
25 | import re
26 | from typing import Union
27 |
28 | global_config = get_driver().config
29 | config = Config.parse_obj(global_config)
30 |
31 | __PLUGIN_NAME = "[bilibilibot]"
32 |
33 | ALL_PERMISSION = GROUP_ADMIN | GROUP_OWNER | PRIVATE_FRIEND | SUPERUSER
34 | PACKAGEPATH = dirname(abspath(__file__))
35 |
36 | follow_liver_command = on_command("关注主播", permission=ALL_PERMISSION)
37 | @follow_liver_command.handle()
38 | async def follow_liver_command_handler(event: Union[PrivateMessageEvent, GroupMessageEvent], args: Message = CommandArg()):
39 | uid_list = args.extract_plain_text().split()
40 | await create_user(event)
41 | if isinstance(event, PrivateMessageEvent):
42 | success_list, fail_list = await follow_liver_list(event.sender.user_id, uid_list, 0)
43 | if isinstance(event, GroupMessageEvent):
44 | success_list, fail_list = await follow_liver_list(event.group_id, uid_list, 1)
45 | await follow_liver_command.finish(f"关注成功:\n{success_list}\n关注失败:\n{fail_list}")
46 |
47 | unfollow_liver_command = on_command("取关主播", aliases={"切割主播"}, permission=ALL_PERMISSION)
48 | @unfollow_liver_command.handle()
49 | async def unfollow_liver_command_handler(event: Union[PrivateMessageEvent, GroupMessageEvent], args: Message = CommandArg()):
50 | uid_list = args.extract_plain_text().split()
51 | await create_user(event)
52 | if isinstance(event, PrivateMessageEvent):
53 | success_list, fail_list = await unfollow_liver_list(event.sender.user_id, uid_list, 0)
54 | if isinstance(event, GroupMessageEvent):
55 | success_list, fail_list = await unfollow_liver_list(event.group_id, uid_list, 1)
56 | await unfollow_liver_command.finish(f"取关成功:\n{success_list}\n取关失败:\n{fail_list}")
57 |
58 | listFollowingCommand = on_command("查询关注", aliases={"查询成分"}, permission=ALL_PERMISSION)
59 | @listFollowingCommand.handle()
60 | async def listFollowingCommandHandler(event: Union[PrivateMessageEvent, GroupMessageEvent], args: Message = CommandArg()):
61 | await create_user(event)
62 | inputArgs = args.extract_plain_text().split()
63 | defaultArgs = ['直播', 'up主', '番剧', '动态']
64 |
65 | if isinstance(event, PrivateMessageEvent):
66 | user_id = event.sender.user_id
67 | user_type = 0
68 | elif isinstance(event, GroupMessageEvent):
69 | user_id = event.group_id
70 | user_type = 1
71 |
72 | exceptArgs = set(inputArgs) - set(defaultArgs)
73 |
74 | if len(exceptArgs) != 0:
75 | logger.info(f'{__PLUGIN_NAME}查询失败,存在错误参数: {exceptArgs}')
76 | await listFollowingCommand.finish(f"查询失败,存在错误参数:{exceptArgs}\n请正确输入命令,例如: '查询成分 直播' 或 '查询成分 直播 up主 番剧'")
77 |
78 | if not inputArgs:
79 | inputArgs = defaultArgs
80 |
81 | logger.debug(f'{__PLUGIN_NAME} user_id type: {type(user_id)}')
82 |
83 |
84 | try:
85 | res = bili_database.query_info(user_type, user_id)
86 | logger.debug(f'{__PLUGIN_NAME}res = {res}')
87 |
88 | if res:
89 | if user_type == 0:
90 | followed_up_list = bili_database.query_user_relation(1, user_id)
91 | followed_liver_list = bili_database.query_user_relation(3, user_id)
92 | followed_telegram_list = bili_database.query_user_relation(5, user_id)
93 | logger.debug(f'{__PLUGIN_NAME}followed_telegram_list = {followed_telegram_list}')
94 |
95 | followed_dynamic_list = bili_database.query_user_relation(7, user_id)
96 | else:
97 | followed_up_list = bili_database.query_group_relation(1, user_id)
98 | followed_liver_list = bili_database.query_group_relation(3, user_id)
99 | followed_telegram_list = bili_database.query_group_relation(5, user_id)
100 | followed_dynamic_list = bili_database.query_group_relation(7, user_id)
101 |
102 | textMsg = ""
103 | if 'up主' in inputArgs:
104 | if followed_up_list:
105 | textMsg += '关注的up主:\n'
106 | for up_uid in followed_up_list:
107 | up_uid, up_name, _ = bili_database.query_info(2, up_uid)
108 | textMsg += '> ' + f"{up_name}(uid: {up_uid})" + '\n'
109 |
110 | else:
111 | textMsg += '无关注的up主\n'
112 | textMsg += '\n'
113 |
114 | if '直播' in inputArgs:
115 | if followed_liver_list:
116 | textMsg += '关注的主播:\n'
117 | for liver_uid in followed_liver_list:
118 | liver_uid, liver_name, _, _ = bili_database.query_info(3, liver_uid)
119 | textMsg += '> ' + f"{liver_name}(uid: {liver_uid})" + '\n'
120 | else:
121 | textMsg += '无关注的主播\n'
122 | textMsg += '\n'
123 |
124 | if '番剧' in inputArgs:
125 | if followed_telegram_list:
126 | textMsg += '关注的番剧\n'
127 | for season_id in followed_telegram_list:
128 | season_id, telegram_title, _, _ = bili_database.query_info(4, season_id)
129 | textMsg += '> ' + f"{telegram_title}(season_id: ss{season_id})" + '\n'
130 |
131 | else:
132 | textMsg += '无关注的番剧'
133 | textMsg += '\n'
134 |
135 | if '动态' in inputArgs:
136 | if followed_dynamic_list:
137 | textMsg += '关注的动态主\n'
138 | for uid in followed_dynamic_list:
139 | uid, u_name, _, _ = bili_database.query_info(5, uid)
140 | textMsg += '> ' + f"{u_name}(uid: {uid})" + '\n'
141 |
142 | else:
143 | textMsg += '无关注的动态主'
144 | textMsg += '\n'
145 |
146 | await listFollowingCommand.send(textMsg)
147 | else:
148 | await listFollowingCommand.finish("关注列表为空")
149 | except Exception as _:
150 | ex_type, ex_val, _ = sys.exc_info()
151 | exception_msg = f'【错误报告】\n查询关注时发生错误\n错误类型: {ex_type}\n错误值: {ex_val}\n'
152 | logger.error(f"{__PLUGIN_NAME}" + exception_msg + traceback.format_exc())
153 | await listFollowingCommand.finish()
154 |
155 | followUpCommand = on_command("关注up", permission=ALL_PERMISSION)
156 | @followUpCommand.handle()
157 | async def followUpCommandHandler(event: Union[PrivateMessageEvent, GroupMessageEvent], args: Message = CommandArg()):
158 | await create_user(event)
159 | uid_list = args.extract_plain_text().split()
160 | if isinstance(event, PrivateMessageEvent):
161 | success_list, fail_list = await follow_up_list(event.sender.user_id, uid_list, 0)
162 | if isinstance(event, GroupMessageEvent):
163 | success_list, fail_list = await follow_up_list(event.group_id, uid_list, 1)
164 | await followUpCommand.finish(f"关注成功:\n{success_list}\n关注失败:\n{fail_list}")
165 |
166 | unfollowUpCommand = on_command("取关up", permission=ALL_PERMISSION)
167 | @unfollowUpCommand.handle()
168 | async def unfollowUpCommandHandler(event: Union[PrivateMessageEvent, GroupMessageEvent], args: Message = CommandArg()):
169 | await create_user(event)
170 | uid_list = args.extract_plain_text().split()
171 | if isinstance(event, PrivateMessageEvent):
172 | success_list, fail_list = await unfollow_up_list(event.sender.user_id, uid_list, 0)
173 | if isinstance(event, GroupMessageEvent):
174 | success_list, fail_list = await unfollow_up_list(event.group_id, uid_list, 1)
175 | await unfollowUpCommand.finish(f"取关成功:\n{success_list}\n取关失败:\n{fail_list}")
176 |
177 | followTelegramCommand = on_command("关注番剧", permission=ALL_PERMISSION)
178 | @followTelegramCommand.handle()
179 | async def followTelegramCommandHandler(event: Union[PrivateMessageEvent, GroupMessageEvent], args: Message = CommandArg()):
180 | await create_user(event)
181 | tele_id_list = args.extract_plain_text().split()
182 | if isinstance(event, PrivateMessageEvent):
183 | success_list, fail_list = await follow_telegram_list(event.sender.user_id, tele_id_list, 0)
184 | if isinstance(event, GroupMessageEvent):
185 | success_list, fail_list = await follow_telegram_list(event.group_id, tele_id_list, 1)
186 | await followTelegramCommand.finish(f"关注成功:\n{success_list}\n关注失败:\n{fail_list}")
187 |
188 | unfollowTelegramCommand = on_command("取关番剧", permission=ALL_PERMISSION)
189 | @unfollowTelegramCommand.handle()
190 | async def unfollowTelegramCommandHandler(event: Union[PrivateMessageEvent, GroupMessageEvent], args: Message = CommandArg()):
191 | await create_user(event)
192 | season_id_list = args.extract_plain_text().split()
193 | if isinstance(event, PrivateMessageEvent):
194 | success_list, fail_list = await unfollow_telegram_list(event.sender.user_id, season_id_list, 0)
195 | if isinstance(event, GroupMessageEvent):
196 | success_list, fail_list = await unfollow_telegram_list(event.group_id, season_id_list, 1)
197 | await unfollowTelegramCommand.finish(f"取关成功:\n{success_list}\n取关失败:\n{fail_list}")
198 |
199 | follow_dynamic_command = on_command("关注动态", permission=ALL_PERMISSION)
200 | @follow_dynamic_command.handle()
201 | async def follow_dynamic_commandHandler(event: Union[PrivateMessageEvent, GroupMessageEvent], args: Message = CommandArg()):
202 | await create_user(event)
203 | uid_list = args.extract_plain_text().split()
204 | if isinstance(event, PrivateMessageEvent):
205 | success_list, fail_list = await follow_dynamic_list(event.sender.user_id, uid_list, 0)
206 | if isinstance(event, GroupMessageEvent):
207 | success_list, fail_list = await follow_dynamic_list(event.group_id, uid_list, 1)
208 | await follow_dynamic_command.finish(f"关注成功:\n{success_list}\n关注失败:\n{fail_list}")
209 |
210 | unfollow_dynamic_command = on_command("取关动态", permission=ALL_PERMISSION)
211 | @unfollow_dynamic_command.handle()
212 | async def unfollow_dynamic_commandHandler(event: Union[PrivateMessageEvent, GroupMessageEvent], args: Message = CommandArg()):
213 | await create_user(event)
214 | uid_list = args.extract_plain_text().split()
215 | if isinstance(event, PrivateMessageEvent):
216 | success_list, fail_list = await unfollow_dynamic_list(event.sender.user_id, uid_list, 0)
217 | if isinstance(event, GroupMessageEvent):
218 | success_list, fail_list = await unfollow_dynamic_list(event.group_id, uid_list, 1)
219 | await unfollow_dynamic_command.finish(f"取关成功:\n{success_list}\n取关失败:\n{fail_list}")
220 |
221 | followUpByShare = on_message(
222 | rule=regex('\[CQ:json,[\w\W]*"appid":100951776[\w\W]*space.bilibili.com[\w\W]*[\w\W]*\]') & privateMessageRule,
223 | permission=PRIVATE_FRIEND
224 | )
225 | @followUpByShare.handle()
226 | async def upShareHandler(event: PrivateMessageEvent):
227 | '''响应用户分享up主空间连接
228 |
229 | Args:
230 | event (PrivateMessageEvent): 消息事件
231 | '''
232 | await create_user(event)
233 | sJson = event.get_message()[-1].get('data')
234 | data = json.loads(sJson['data'])
235 | uid = data['meta']['news']['jumpUrl'].split('?')[0].split('/')[-1]
236 |
237 | success_list, fail_list = await follow_up_list(event.sender.user_id, [uid], 0)
238 |
239 | if success_list:
240 | await followUpByShare.finish(f"关注up成功: <{success_list[0]}>")
241 | elif fail_list:
242 | await followUpByShare.finish(f"关注up失败: <{fail_list[0]}>")
243 |
244 | followStreamerByShare = on_message(
245 | rule=regex('^\[CQ:json,[\w\W]*"appid":100951776[\w\W]*live.bilibili.com[\w\W]*') & privateMessageRule,
246 | permission=PRIVATE_FRIEND
247 | )
248 | @followStreamerByShare.handle()
249 | async def streamerShareHandler(event: PrivateMessageEvent):
250 | '''响应个人用户的直播间分享
251 |
252 | Args:
253 | event (PrivateMessageEvent): 消息事件
254 | '''
255 | try:
256 | await create_user(event)
257 | sJson = event.get_message()[-1].get('data')
258 | data = json.loads(sJson['data'])
259 | roomNumber = data['meta']['news']['jumpUrl'].split('?')[0].split('/')[-1]
260 |
261 | uid, _ = await bili_client.init_liver_info_by_room_id(roomNumber)
262 |
263 | success_list, fail_list = await follow_liver_list(event, event.sender.user_id, [uid], 0)
264 |
265 | if success_list:
266 | await followUpByShare.finish(f"关注主播成功: <{success_list[0]}>")
267 | elif fail_list:
268 | await followUpByShare.finish(f"关注主播失败: <{fail_list[0]}> ")
269 |
270 | except nonebot.exception.FinishedException:
271 | pass
272 | except BiliInvalidRoomId:
273 | await followStreamerByShare.finish('关注失败: 无效的房间号')
274 | except Exception:
275 | ex_type, ex_val, _ = sys.exc_info()
276 | logger.error(f'{__PLUGIN_NAME}获取主播 <{uid}> 信息时发生错误')
277 | logger.error(f'{__PLUGIN_NAME}错误类型: {ex_type},错误值: {ex_val}')
278 | await followStreamerByShare.finish('关注失败: 连接错误')
279 |
280 | followTelegramByShare = on_message(
281 | rule = regex('^\[CQ:json[\w\W]*"appid":100951776[\w\W]*www.bilibili.com\/bangumi\/play\/[\w\W]*') & privateMessageRule,
282 | permission=PRIVATE_FRIEND
283 | )
284 | @followTelegramByShare.handle()
285 | async def telegramShareHandler(event: PrivateMessageEvent):
286 | '''响应用户分享番剧页面
287 |
288 | Args:
289 | event (PrivateMessageEvent): 消息事件
290 | '''
291 | await create_user(event)
292 | sJson = event.get_message()[-1].get('data')
293 | data = json.loads(sJson['data'])
294 | epID = data['meta']['detail_1']['qqdocurl'].split('?')[0].split('/')[-1]
295 |
296 | success_list, fail_list = await follow_telegram_list(event.sender.user_id, [epID], 0)
297 |
298 | if success_list:
299 | await followUpByShare.finish(f"关注番剧成功: <{success_list[0]}>")
300 | elif fail_list:
301 | await followUpByShare.finish(f"关注番剧失败: <{fail_list[0]}> ")
302 |
303 | follow_by_share_short_url = on_message(
304 | rule = regex('^\[CQ:json[\w\W]*"appid":100951776[\w\W]*b23.tv[\w\W]*') & privateMessageRule,
305 | permission=PRIVATE_FRIEND
306 | )
307 | @follow_by_share_short_url.handle()
308 | async def short_url_handler(event: PrivateMessageEvent):
309 | try:
310 | await create_user(event)
311 | sJson = event.get_message()[-1].get('data')
312 | shortLink = re.search("https:\/\/b23.tv\/\w+", sJson['data'])
313 | short_url = shortLink.group()
314 | logger.debug(f"get short url = {short_url}")
315 |
316 | msg_type = 0
317 | user_id = event.sender.user_id
318 | id_type, target_uid = await bili_client.parse_short_url(short_url)
319 | #isSuccess, id_type, uid = await parseB23Url(short_url)
320 | if id_type == 0:
321 | success_list, fail_list = await follow_up_list(user_id, [target_uid], msg_type)
322 | if success_list:
323 | await follow_by_share_short_url.finish(f"关注成功: {success_list[0]}")
324 | elif fail_list:
325 | await follow_by_share_short_url.finish(f"关注失败: {fail_list[0]}")
326 |
327 | elif id_type == 1:
328 | liver_uid, _ = await bili_client.init_liver_info_by_room_id(target_uid)
329 | success_list, fail_list = await follow_liver_list(user_id, [liver_uid], msg_type)
330 | if success_list:
331 | await follow_by_share_short_url.finish(f"关注成功: {success_list[0]}")
332 | elif fail_list:
333 | await follow_by_share_short_url.finish(f"关注失败: {fail_list[0]}")
334 |
335 | elif id_type == 2:
336 | success_list, fail_list = await follow_telegram_list(user_id, [target_uid], msg_type)
337 | if success_list:
338 | await follow_by_share_short_url.finish(f"关注成功: {success_list[0]}")
339 |
340 | elif fail_list:
341 | await follow_by_share_short_url.finish(f"关注失败: {fail_list[0]}")
342 | else:
343 | await follow_by_share_short_url.finish(f"关注失败: 非法短链接{short_url}")
344 | except nonebot.exception.FinishedException:
345 | pass
346 | except BiliInvalidShortUrl:
347 | await follow_by_share_short_url.finish('关注失败: 非法或不支持的短链接')
348 | except Exception as _:
349 | ex_type, ex_val, _ = sys.exc_info()
350 | logger.error(f'{__PLUGIN_NAME}【错误报告】\n解析短链接 <{short_url}> 时发生错误\n错误类型: {ex_type}\n错误值: {ex_val}\n{traceback.format_exc()}')
351 | await follow_by_share_short_url.finish('关注失败: 连接错误')
352 |
353 | helpCommand = on_command("bilihelp", permission=ALL_PERMISSION, aliases={'B站帮助'})
354 | @helpCommand.handle()
355 | async def sendHelpMsg(event: MessageEvent):
356 | await create_user(event)
357 | helpMsg = ""
358 | with open(f'{PACKAGEPATH}/file/source/help.json', 'r', encoding='utf-8') as f:
359 | helpMsg = json.load(f)
360 | await helpCommand.finish(helpMsg)
361 |
362 | publicBroacast = on_command("broacast", aliases={'广播'}, permission=permission.SUPERUSER)
363 | @publicBroacast.handle()
364 | async def sendBroacast(event: MessageEvent):
365 | announcement = ""
366 | announcementPath = f'{PACKAGEPATH}/file/source/announcement.json'
367 | if os.path.exists(announcementPath):
368 | with open(announcementPath, 'r', encoding='utf-8') as f:
369 | announcement = json.load(f)
370 |
371 | users = GetAllUser()
372 | await SendMsgToUsers(announcement, users)
373 |
374 | groups = GetAllGroup()
375 | await SendMsgToGroups(announcement, groups)
376 |
377 | await publicBroacast.finish("公告发送成功")
378 | else:
379 | logger.debug(f'{__PLUGIN_NAME}公告文件不存在')
380 | await publicBroacast.finish("公告发送失败: 公告文件不存在")
381 |
382 | require("nonebot_plugin_apscheduler")
383 | from nonebot_plugin_apscheduler import scheduler
384 | logger.debug(f'{__PLUGIN_NAME}注册定时任务')
385 | scheduler.add_job(check_bili_live, "interval", minutes=1, id="bili_stream", misfire_grace_time=90)
386 | scheduler.add_job(check_up_update, "interval", minutes=2, id="bili_up", misfire_grace_time=90)
387 | scheduler.add_job(check_telegram_update, "interval", minutes=10, id="bili_telegram", misfire_grace_time=90)
388 | scheduler.add_job(check_dynamic_update, "interval", minutes=1, id="bili_dynamic", misfire_grace_time=90)
--------------------------------------------------------------------------------
/bili_src/basicFunc.py:
--------------------------------------------------------------------------------
1 | from typing import List, Tuple, Union
2 | import re
3 | import httpx
4 | import os
5 | import sys
6 | import traceback
7 | from nonebot import get_bot
8 | from nonebot.log import logger
9 | from os.path import abspath, dirname
10 | from nonebot.adapters.onebot.v11.event import GroupMessageEvent, PrivateMessageEvent
11 | from .db import bili_database
12 | from .exception import *
13 |
14 | __PLUGIN_NAME = "[bilibilibot~基础]"
15 |
16 | def GetAllUser() -> List[str]:
17 | """
18 | @description :
19 | 获取所有用户
20 | ---------
21 | @param :
22 | 无
23 | -------
24 | @Returns :
25 | 返回所有用户qq号组成的列表
26 | -------
27 | """
28 | qq_users = bili_database.query_all(3)
29 | return [i[0] for i in qq_users]
30 |
31 | def GetAllGroup() -> List[str]:
32 | """
33 | @description :
34 | 获取所有群
35 | ---------
36 | @param :
37 | 无
38 | -------
39 | @Returns :
40 | 返回所有群号组成的列表
41 | -------
42 | """
43 |
44 | qq_groups = bili_database.query_all(4)
45 | return [i[0] for i in qq_groups]
46 |
47 | async def SendMsgToUsers(msg: str, users: List[str]):
48 | """
49 | @description :
50 | 向所有用户发送公告
51 | ---------
52 | @param :
53 | msg: 内容
54 | users: 用户的qq列表
55 | -------
56 | @Returns :
57 | 无
58 | -------
59 | """
60 |
61 | bot = get_bot()
62 | for user in users:
63 | await bot.send_msg(message=msg, user_id = user)
64 |
65 | async def SendMsgToGroups(msg: str, groups: List[str]):
66 | '''向所有群组发送公告
67 |
68 | Args:
69 | msg (str): 公告内容
70 | groups (List[str]): 群组列表
71 | '''
72 |
73 | bot = get_bot()
74 | for group in groups:
75 | await bot.send_msg(message=msg, group_id = group)
76 |
77 | async def create_user(event: Union[PrivateMessageEvent, GroupMessageEvent]) -> None:
78 | '''接受消息后,创建用户
79 |
80 | Args:
81 | event (Union[PrivateMessageEvent, GroupMessageEvent]): 消息事件
82 | '''
83 |
84 | user_type = 0 if isinstance(event, PrivateMessageEvent) else 1
85 | user_id = event.sender.user_id if user_type == 0 else event.group_id
86 | try:
87 | user_info = bili_database.query_info(user_type, str(user_id))
88 | if not user_info:
89 | logger.info(f'{__PLUGIN_NAME}用户{user_id}不存在于数据库,即将创建')
90 | name = event.sender.nickname
91 | if user_type == 1:
92 | bot = get_bot()
93 | group_info = await bot.get_group_info(group_id=user_id)
94 | name = group_info["group_name"]
95 | bili_database.insert_info(user_type, user_id, name)
96 | except Exception as _:
97 | ex_type, ex_val, _ = sys.exc_info()
98 | exception_msg = '【错误报告】\n创建用户时发生错误\n错误类型: {}\n错误值: {}\n'.format(ex_type, ex_val)
99 | logger.error(f"{__PLUGIN_NAME}\n" + exception_msg + traceback.format_exc())
100 |
101 |
--------------------------------------------------------------------------------
/bili_src/biliStream.py:
--------------------------------------------------------------------------------
1 | from typing import Tuple, List
2 | import sys
3 | import traceback
4 | import nonebot
5 | from nonebot.log import logger
6 | from nonebot.adapters.onebot.v11 import MessageSegment
7 | from nonebot.adapters.onebot.v11.adapter import Adapter
8 | from .basicFunc import *
9 | from .exception import *
10 | import asyncio
11 | from .bili_client import bili_client
12 | from .bili_task import bili_task_manager
13 | __PLUGIN_NAME = "[bilibilibot~直播]"
14 | async def check_bili_live() -> None:
15 | """
16 | @description :
17 | 检查数据库中所有主播的开播状态
18 | 如果关注的主播开播,则通知所有关注的用户
19 | 如果主播开播状态改变,则更新数据库
20 | ---------
21 | @param :
22 | -------
23 | @Returns :
24 | -------
25 | """
26 | liver_list = list(bili_task_manager.liver_list.values())
27 |
28 | sched_bot = nonebot.get_bot()
29 |
30 | """results = await asyncio.gather(
31 | *[bili_client.get_live_status(liver_info[0], liver_info[3]) for liver_info in liver_list],
32 | return_exceptions=True
33 | )"""
34 | async with httpx.AsyncClient(headers={"User-Agent":"Mozilla/5.0"}) as client:
35 | results = await asyncio.gather(
36 | *[bili_client.get_live_status(client, liver_info["liver_uid"], liver_info["room_id"]) for liver_info in liver_list],
37 | return_exceptions=True
38 | )
39 | for i in range(len(liver_list)):
40 | if isinstance(results[i], tuple):
41 | if results[i][0] and not liver_list[i]["is_live"]:
42 | logger.info(f'[{__PLUGIN_NAME}]检测到主播 <{liver_list[i]["liver_name"]}> 已开播!')
43 | text_msg = '【直播动态】\n<{}>正在直播!\n标题: {}\n链接: {}'.format(liver_list[i]["liver_name"], results[i][1], f"https://live.bilibili.com/{liver_list[i]['room_id']}")
44 | reported_msg = text_msg + MessageSegment.image(results[i][2])
45 | logger.info(f'[{__PLUGIN_NAME}]向粉丝发送开播通知')
46 | bili_task_manager.update_liver_info(liver_list[i]["liver_uid"], True)
47 | #bili_database.update_info(1, 1, liver_list[i][0])
48 |
49 | user_list = liver_list[i]["user_follower"]
50 | await asyncio.gather(*[sched_bot.send_msg(message=reported_msg, user_id=user_id) for user_id in user_list])
51 | group_list = liver_list[i]["group_follower"]
52 | await asyncio.gather(*[sched_bot.send_msg(message=reported_msg, group_id=group_id) for group_id in group_list])
53 |
54 | elif not results[i][0] and liver_list[i]["is_live"]:
55 | logger.info(f'[{__PLUGIN_NAME}]检测到主播 <{liver_list[i]["liver_name"]}> 已下播')
56 | bili_task_manager.update_liver_info(liver_list[i]["liver_uid"], False)
57 | text_msg = '【直播动态】\n<{}>直播结束!'.format(liver_list[i]["liver_name"])
58 | user_list = liver_list[i]["user_follower"]
59 | await asyncio.gather(*[sched_bot.send_msg(message=text_msg, user_id=user_id) for user_id in user_list])
60 | group_list = liver_list[i]["group_follower"]
61 | await asyncio.gather(*[sched_bot.send_msg(message=text_msg, group_id=group_id) for group_id in group_list])
62 |
63 | elif isinstance(results[i], (BiliAPIRetCodeError, BiliStatusCodeError, BiliConnectionError)):
64 | exception_msg = f'[错误报告]\n检测主播 <{liver_list[i]["liver_name"]}> 开播情况时发生错误\n错误类型: {type(results[i])}\n错误信息: {results[i]}'
65 | logger.error(f"[{__PLUGIN_NAME}]" + exception_msg)
66 |
67 | async def follow_liver(uid: str, user_id: str, user_type: int) -> Tuple[bool, str]:
68 | '''根据用户/群关注主播,修改数据库
69 |
70 | Args:
71 | uid (str): 主播的uid
72 | user_id (str): 用户的uid或群号
73 | user_type (int): 0-用户,1-群
74 |
75 | Returns:
76 | Tuple[bool, str]: [是否成功,主播名(uid) | 主播uid(失败原因)]
77 | '''
78 |
79 | if not uid.isdigit():
80 | logger.error(f'{__PLUGIN_NAME}存在错误参数 <{uid}>')
81 | return (False, uid + "(错误参数)")
82 | uid = str(int(uid))
83 | try:
84 | if uid not in bili_task_manager.liver_list:
85 | liver_name, room_id = await bili_client.init_liver_info(uid)
86 |
87 | bili_task_manager.add_liver_info(uid, liver_name, False, room_id)
88 | if user_type == 0:
89 | bili_task_manager.add_user_follower(1, uid, user_id)
90 | else:
91 | bili_task_manager.add_group_follower(1, uid, user_id)
92 | #bili_database.insert_relation(2 + user_type, uid, user_id)
93 |
94 | logger.info(f"{__PLUGIN_NAME}用户/群 <{user_id}> 关注主播 <{liver_name}> 成功")
95 | return (True, liver_name + f"(uid: {uid})")
96 |
97 | if user_type == 0 and user_id in bili_task_manager.liver_list[uid]["user_follower"] or \
98 | user_type == 1 and user_id in bili_task_manager.liver_list[uid]["group_follower"]:
99 | logger.debug(f'{__PLUGIN_NAME}主播 <{bili_task_manager.liver_list[uid]["liver_name"]}> 已关注')
100 | return (False, uid + "(已关注)")
101 |
102 | if user_type == 0:
103 | bili_task_manager.add_user_follower(1, uid, user_id)
104 | elif user_type == 1:
105 | bili_task_manager.add_group_follower(1, uid, user_id)
106 |
107 | logger.info(f"{__PLUGIN_NAME}用户/群 <{user_id}> 关注主播 <{bili_task_manager.liver_list[uid]['liver_name']}> 成功")
108 | return (True, bili_task_manager.liver_list[uid]["liver_name"] + f"(uid: {uid})")
109 | except (BiliConnectionError, BiliAPIRetCodeError, BiliStatusCodeError):
110 | ex_type, ex_val, _ = sys.exc_info()
111 | exception_msg = f'【错误报告】\n获取主播 <{uid}> B站信息发生错误\n错误类型: {ex_type}\n错误值: {ex_val}\n'
112 | logger.error(f"{__PLUGIN_NAME}" + exception_msg + traceback.format_exc())
113 | return (False, uid + "(网络错误)")
114 | except BiliNoLiveRoom:
115 | return (False, uid + "(未开通直播间)")
116 | except BiliDatebaseError:
117 | ex_type, ex_val, _ = sys.exc_info()
118 | exception_msg = f'【错误报告】\n关注主播 <{uid}> 时数据库发生错误\n错误类型: {ex_type}\n错误值: {ex_val}\n'
119 | logger.error(f"{__PLUGIN_NAME}" + exception_msg + traceback.format_exc())
120 | return (False, uid + "(数据库错误)")
121 | except Exception:
122 | ex_type, ex_val, _ = sys.exc_info()
123 | exception_msg = f'【错误报告】\n关注主播 <{uid}> 时发生意料之外的错误\n错误类型: {ex_type}\n错误值: {ex_val}\n'
124 | logger.error(f"{__PLUGIN_NAME}" + exception_msg + traceback.format_exc())
125 | return (False, uid + "(未知错误,请查看日志)")
126 |
127 | async def unfollower_liver(uid: str, user_id: str, user_type: int) -> Tuple[bool, str]:
128 | '''根据用户取关主播,修改主播文件
129 |
130 | Args:
131 | uid (str): 主播的uid
132 | user_id (int):QQ号/群号
133 | user_type (int): 0-个人用户,1-群
134 |
135 | Returns:
136 | Tuple[bool, str]: [是否成功,主播名 | 失败原因]
137 | '''
138 | if not uid.isdigit():
139 | logger.error(f'{__PLUGIN_NAME}存在错误参数 <{uid}>')
140 | return (False, uid + "(错误参数)")
141 | try:
142 | # 处理未关注
143 | if uid not in bili_task_manager.liver_list or \
144 | user_type == 0 and user_id not in bili_task_manager.liver_list[uid]["user_follower"] or \
145 | user_type == 1 and user_id not in bili_task_manager.liver_list[uid]["group_follower"]:
146 | logger.info(f'{__PLUGIN_NAME}用户/群 <{user_id}> 未关注主播 <{uid}>')
147 | return (False, uid + "(未关注)")
148 |
149 | # 进行取关
150 | liver_name = bili_task_manager.liver_list[uid]["liver_name"]
151 | if user_type == 0:
152 | bili_task_manager.remove_user_follower(1, uid, user_id)
153 | else:
154 | bili_task_manager.remove_group_follower(1, uid, user_id)
155 |
156 | return (True, liver_name + f"(uid: {uid})")
157 |
158 | except BiliDatebaseError:
159 | ex_type, ex_val, _ = sys.exc_info()
160 | exception_msg = f'【错误报告】\n取关主播 <{uid}> 时数据库发生错误\n错误类型: {ex_type}\n错误值: {ex_val}\n'
161 | logger.error(f"{__PLUGIN_NAME}" + exception_msg + traceback.format_exc())
162 | return (False, uid + "(数据库错误)")
163 |
164 | async def follow_liver_list(
165 | user_id: int,
166 | uid_list: List[str],
167 | user_type: int
168 | ) -> List[List[str]]:
169 | '''用户/群对主播进行关注
170 |
171 | Args:
172 | user_id (int): 用户qq或群号
173 | uid_list (List[str]): 关注的主播uid
174 | user_type (int): 0-用户, 1-群
175 |
176 | Returns:
177 | List[List[str]]: [[关注成功], [关注失败]]
178 | '''
179 |
180 | success_list = []
181 | fail_list = []
182 |
183 | for uid in uid_list:
184 | isSuccess, s = await follow_liver(uid, str(user_id), user_type)
185 | if isSuccess:
186 | success_list.append(s)
187 | else:
188 | fail_list.append(s)
189 |
190 | return [success_list, fail_list]
191 |
192 |
193 | async def unfollow_liver_list(
194 | user_id: int,
195 | uid_list: List[str],
196 | user_type: int
197 | ) -> List[List[str]]:
198 | '''用户/群对主播取关
199 |
200 | Args:
201 | user_id (int): 用户qq/群号
202 | uid_list (List[str]): 取关主播列表
203 | user_type (int): 0-用户, 1-群
204 |
205 | Returns:
206 | List[List[str]]: [成功列表,失败列表]
207 | '''
208 |
209 | success_list = []
210 | fail_list = []
211 |
212 | for uid in uid_list:
213 | isSuccess, s = await unfollower_liver(uid, str(user_id), user_type)
214 | if isSuccess:
215 | success_list.append(s)
216 | else:
217 | fail_list.append(s)
218 |
219 | return [success_list, fail_list]
--------------------------------------------------------------------------------
/bili_src/biliTelegram.py:
--------------------------------------------------------------------------------
1 | from typing import Tuple, List
2 | import sys
3 | import asyncio
4 | import traceback
5 | import nonebot
6 | from nonebot.log import logger
7 | from nonebot.adapters.onebot.v11 import MessageSegment
8 | from .basicFunc import *
9 | from .exception import *
10 | from .bili_client import bili_client
11 | from .bili_task import bili_task_manager
12 |
13 | __PLUGIN_NAME = "[bilibilibot~番剧]"
14 |
15 | async def check_telegram_update():
16 | """
17 | @description :
18 | 检查数据库中的每一个影视节目是否更新,如果更新则向用户发送通知,并且更新文件
19 | ---------
20 | @param :
21 | 无
22 | -------
23 | @Returns :
24 | 无
25 | -------
26 | """
27 | telegram_list = list(bili_task_manager.telegram_list.values())
28 | telegram_list = [i for i in telegram_list if i["is_finish"] is False]
29 | sched_bot = nonebot.get_bot()
30 | # 只对未完结的番剧进行检查
31 | async with httpx.AsyncClient(headers={"User-Agent":"Mozilla/5.0"}) as client:
32 | results = await asyncio.gather(
33 | *[bili_client.get_telegram_latest_episode(client, telegram_info["season_id"], telegram_info["episode"]) for telegram_info in telegram_list],
34 | return_exceptions=True
35 | )
36 |
37 | for i in range(len(telegram_list)):
38 | if isinstance(results[i], tuple):
39 | if results[i][0] is True:
40 | logger.info(f'[{__PLUGIN_NAME}]检测到影视剧 <{telegram_list[i]["telegram_title"]}> 更新')
41 | text_msg = "【B站动态】\n《{}》已更新\n标题: {}\n链接: {}\n".format(
42 | telegram_list[i]["telegram_title"], results[i][2], results[i][3]
43 | )
44 | bili_task_manager.update_telegram_info(telegram_list[i]["season_id"], results[i][1], results[i][5])
45 | cover_msg = MessageSegment.image(results[i][4])
46 | reported_msg = text_msg + cover_msg
47 | logger.info(f'[{__PLUGIN_NAME}]向关注用户发送更新通知')
48 |
49 | # 通知用户
50 | user_list = telegram_list[i]["user_follower"]
51 | await asyncio.gather(*[sched_bot.send_msg(message=reported_msg, user_id=user_id) for user_id in user_list])
52 |
53 | group_list = telegram_list[i]["group_follower"]
54 | await asyncio.gather(*[sched_bot.send_msg(message=reported_msg, group_id=group_id) for group_id in group_list])
55 |
56 | elif isinstance(results[i], (BiliAPIRetCodeError, BiliStatusCodeError, BiliConnectionError)):
57 | exception_msg = f'[错误报告]\n检测番剧 <{telegram_list[i]["telegram_title"]}> 更新情况时发生错误\n错误类型: {type(results[i])}\n错误信息: {results[i]}'
58 | logger.error(f"[{__PLUGIN_NAME}]" + exception_msg)
59 |
60 | async def follow_telegram(id_prefix: str, telegram_id: int, user_id: str, user_type: int) -> Tuple[bool, str]:
61 | '''根据用户关注节目,修改节目的文件
62 |
63 | Args:
64 | id_prefix (str): ep | ss | md
65 | telegram_id (int): 节目的id
66 | user_id (str): 用户的qq号/群号
67 | user_type (int): 0-个人用户,1-群号
68 |
69 | Returns:
70 | Tuple[bool, str]: [是否成功,信息]
71 | '''
72 |
73 | try:
74 | if id_prefix == "ep":
75 | res = await bili_client.init_telegram_info_by_ep_id(telegram_id)
76 | elif id_prefix == "ss":
77 | res = await bili_client.init_telegram_info_by_season_id(telegram_id)
78 | elif id_prefix == "md":
79 | res = await bili_client.init_telegram_info_by_media_id(telegram_id)
80 | else:
81 | return (False, telegram_id + "(番剧id错误)")
82 |
83 | season_id, telegram_title, episode, is_finish = res
84 | season_id = str(season_id)
85 | logger.debug(f'{__PLUGIN_NAME}{bili_task_manager.telegram_list}')
86 |
87 | if season_id not in bili_task_manager.telegram_list:
88 | logger.debug(f'{__PLUGIN_NAME}{season_id}不在任务列表中存在')
89 | bili_task_manager.add_telegram_info(season_id, telegram_title, episode, is_finish)
90 | if user_type == 0:
91 | bili_task_manager.add_user_follower(2, season_id, user_id)
92 | else:
93 | bili_task_manager.add_group_follower(2, season_id, user_id)
94 |
95 | logger.info(f"[{__PLUGIN_NAME}]用户/群 <{user_id}> 关注番剧 <{telegram_title}> 成功")
96 | return (True, telegram_title + f"(season_id: {season_id})")
97 |
98 | if user_type == 0 and user_id in bili_task_manager.telegram_list[season_id]["user_follower"] or \
99 | user_type == 1 and user_id in bili_task_manager.telegram_list[season_id]["group_follower"]:
100 | logger.debug(f'{__PLUGIN_NAME}番剧 <{bili_task_manager.telegram_list[season_id]["telegram_title"]}> 已关注')
101 | return (False, season_id + "(已关注)")
102 |
103 | logger.debug(f'{__PLUGIN_NAME}进行关注{season_id}')
104 |
105 | if user_type == 0:
106 | bili_task_manager.add_user_follower(2, season_id, user_id)
107 | else:
108 | bili_task_manager.add_group_follower(2, season_id, user_id)
109 | logger.info(f"[{__PLUGIN_NAME}]用户/群 <{user_id}> 关注番剧 <{telegram_title}> 成功")
110 | return (True, telegram_title + f"(season_id: {season_id})")
111 |
112 | except BiliAPI404Error:
113 | return (False, f"{id_prefix}{telegram_id}" + "(番剧id错误)")
114 | except (BiliConnectionError, BiliAPIRetCodeError, BiliStatusCodeError):
115 | ex_type, ex_val, _ = sys.exc_info()
116 | exception_msg = f'【错误报告】\n获取番剧 <{id_prefix}{telegram_id}> 信息发生错误\n错误类型: {ex_type}\n错误值: {ex_val}\n'
117 | logger.error(f"[{__PLUGIN_NAME}]" + exception_msg + traceback.format_exc())
118 | return (False, f"{id_prefix}{telegram_id}" + "(网络错误)")
119 | except BiliDatebaseError:
120 | ex_type, ex_val, _ = sys.exc_info()
121 | exception_msg = f'【错误报告】\n关注番剧 <{season_id}> 时数据库发生错误\n错误类型: {ex_type}\n错误值: {ex_val}\n'
122 | logger.error(f"[{__PLUGIN_NAME}]" + exception_msg + traceback.format_exc())
123 | return (False, season_id + "(数据库错误)")
124 | except Exception:
125 | ex_type, ex_val, _ = sys.exc_info()
126 | exception_msg = f'【错误报告】\n关注番剧 <{id_prefix}{telegram_id}> 时发生意料之外的错误\n错误类型: {ex_type}\n错误值: {ex_val}\n'
127 | logger.error(f"[{__PLUGIN_NAME}]" + exception_msg + traceback.format_exc())
128 | return (False, f"{id_prefix}{telegram_id}" + "(未知错误,请查看日志)")
129 |
130 | async def unfollow_telegram(season_id: str, user_id: str, user_type: int) -> Tuple[bool, str]:
131 | '''根据用户/群取关节目,修改节目文件
132 |
133 | Args:
134 | season_id (str): 节目的ID
135 | user_id (str): 用户qq号/群号
136 | user_type (int): 0-个人用户,1-群号
137 |
138 | Returns:
139 | Tuple[bool, str]: [是否成功, 信息]
140 | '''
141 | if season_id[:2] != "ss" and not season_id[2:].isdigit():
142 | return (False, season_id + "(错误参数)")
143 |
144 | season_id = season_id[2:]
145 | logger.debug(f'{__PLUGIN_NAME}{bili_task_manager.telegram_list}')
146 |
147 | try:
148 | if season_id not in bili_task_manager.telegram_list or \
149 | user_type == 0 and user_id not in bili_task_manager.telegram_list[season_id]["user_follower"] or \
150 | user_type == 1 and user_id not in bili_task_manager.telegram_list[season_id]["group_follower"]:
151 | logger.info(f'{__PLUGIN_NAME}用户/群 <{user_id}> 未关注番剧 <{season_id}>')
152 | return (False, season_id + "(未关注)")
153 |
154 | telegram_title = bili_task_manager.telegram_list[season_id]["telegram_title"]
155 | if user_type == 0:
156 | bili_task_manager.remove_user_follower(2, season_id, user_id)
157 | else:
158 | bili_task_manager.remove_group_follower(2, season_id, user_id)
159 |
160 | return (True, telegram_title + f"(season_id: ss{season_id})")
161 |
162 | except BiliDatebaseError:
163 | ex_type, ex_val, _ = sys.exc_info()
164 | exception_msg = f'【错误报告】\n取关番剧 <{season_id}> 时数据库发生错误\n错误类型: {ex_type}\n错误值: {ex_val}\n'
165 | logger.error(f"[{__PLUGIN_NAME}]" + exception_msg + traceback.format_exc())
166 | return (False, season_id + "(数据库错误)")
167 |
168 | async def follow_telegram_list(
169 | user_id: int,
170 | telegram_id_list: List[str],
171 | user_type: int
172 | ) -> List[List[str]]:
173 | '''个人用户/群关注番剧
174 |
175 | Args:
176 | user_id (int): qq号/群号
177 | telegram_id_list (List[str]): 关注的番剧号
178 | user_type (int): 0-个人用户,1-群
179 |
180 | Returns:
181 | List[List[str]]: [是否成功,信息]
182 | '''
183 | # 在该函数中同时对telegram_id, season_id, media_id进行区分和统一处理
184 | success_list = []
185 | fail_list = []
186 | prefix_type = ('ep', 'ss', "md")
187 | valid_telegram_id_list = []
188 | for telegram_id in telegram_id_list:
189 | logger.debug(f'{__PLUGIN_NAME}telegram_id = {telegram_id}')
190 |
191 | if telegram_id[:2] not in prefix_type or not telegram_id[2:].isdigit():
192 | fail_list.append(telegram_id + "(错误参数)")
193 | else:
194 | valid_telegram_id_list.append(telegram_id)
195 | logger.debug(f'{__PLUGIN_NAME}valid list = {valid_telegram_id_list}')
196 |
197 | results = await asyncio.gather(
198 | *[follow_telegram(telegram_id[:2], int(telegram_id[2:]), str(user_id), user_type) for telegram_id in valid_telegram_id_list],
199 | return_exceptions=True
200 | )
201 |
202 | for is_success, msg in results:
203 | if is_success:
204 | success_list.append(msg)
205 | else:
206 | fail_list.append(msg)
207 |
208 | return [success_list, fail_list]
209 |
210 | async def unfollow_telegram_list(
211 | user_id: int,
212 | season_id_list: List[str],
213 | user_type: int
214 | ) -> List[List[str]]:
215 | '''个人用户/群取关番剧
216 |
217 | Args:
218 | user_id (int): qq号/群号
219 | season_id_list (List[str]): 取关的番剧号
220 | user_type (int): 0-个人用户,1-群
221 |
222 | Returns:
223 | List[List[str]]: [是否成功,信息]
224 | '''
225 | success_list = []
226 | fail_list = []
227 |
228 | for season_id in season_id_list:
229 | isSuccess, s = await unfollow_telegram(season_id, str(user_id), user_type)
230 | if isSuccess:
231 | success_list.append(s)
232 | else:
233 | fail_list.append(s)
234 |
235 | return [success_list, fail_list]
236 |
237 |
238 |
239 |
240 |
241 |
242 |
243 |
244 |
--------------------------------------------------------------------------------
/bili_src/biliVideo.py:
--------------------------------------------------------------------------------
1 | from .exception import BiliConnectionError, BiliDatebaseError
2 | import asyncio
3 | from typing import Tuple, List
4 | import sys
5 | import traceback
6 | import nonebot
7 | from nonebot.log import logger
8 | from nonebot.adapters.onebot.v11 import MessageSegment
9 | from .basicFunc import *
10 | from .bili_client import bili_client
11 | from .bili_task import bili_task_manager
12 |
13 | __PLUGIN_NAME = "[bilibilibot~视频]"
14 |
15 | # 视频
16 | async def check_up_update() -> None:
17 | """
18 | @description :
19 | 检查关注UP主是否更新新视频,如果更新则通知用户并写入文件
20 | ---------
21 | @param :
22 | -------
23 | @Returns :
24 | -------
25 | """
26 | schedBot = nonebot.get_bot()
27 | #assert status == True, "数据库发生错误"
28 | check_up_list = bili_task_manager.get_up_check_update_list()
29 | #logger.debug(f'{__PLUGIN_NAME}check_up_list = {check_up_list}')
30 | async with httpx.AsyncClient(headers={"User-Agent":"Mozilla/5.0"}) as client:
31 | results = await asyncio.gather(
32 | *[bili_client.get_latest_video(client, uid, bili_task_manager.up_list[uid]["latest_timestamp"]) for uid in check_up_list],
33 | return_exceptions=True
34 | )
35 |
36 | for i in range(len(check_up_list)):
37 | if isinstance(results[i], tuple):
38 | if results[i][0] is True:
39 | up_uid = check_up_list[i]
40 | up_name = bili_task_manager.up_list[up_uid]["up_name"]
41 |
42 | logger.info(f'{__PLUGIN_NAME}检测到up主<{up_name}>更新了视频')
43 | textMsg = f"【B站动态】\n <{up_name}> 更新了视频\n标题: {results[i][2]}\n链接: https://www.bilibili.com/video/{results[i][1]}"
44 |
45 | bili_task_manager.update_up_info(up_uid, results[i][3])
46 |
47 | user_list = bili_task_manager.up_list[up_uid]["user_follower"]
48 | for user_id in user_list:
49 | await schedBot.send_msg(message=textMsg + MessageSegment.image(results[i][4]), user_id=user_id)
50 |
51 | group_list = bili_task_manager.up_list[up_uid]["group_follower"]
52 | for group_id in group_list:
53 | await schedBot.send_msg(message=textMsg + MessageSegment.image(results[i][4]), group_id=group_id)
54 | elif isinstance(results[i], (BiliAPIRetCodeError, BiliStatusCodeError, BiliConnectionError)):
55 | exception_msg = f'[错误报告]\n检测up主 <{check_up_list[i]}> 更新情况时发生错误\n错误类型: {type(results[i])}\n错误信息: {results[i]}'
56 | logger.error(f"[{__PLUGIN_NAME}]" + exception_msg)
57 |
58 | async def follow_up(uid: str, user_id: str, user_type: int) -> Tuple[bool, str]:
59 | '''根据用户或群关注up,修改数据库
60 |
61 | Args:
62 | uid (str): up的uid
63 | user_id (str): 用户的qq或群号
64 | user_type (int): 0-个人用户,1-群
65 |
66 | Returns:
67 | Tuple[bool, str]: [是否成功,信息]
68 | '''
69 |
70 | # 处理参数错误
71 | if not uid.isdigit():
72 | logger.error(f'{__PLUGIN_NAME}存在错误参数<{uid}>')
73 | return (False, uid + "(错误参数)")
74 |
75 | uid = str(int(uid))
76 |
77 | try:
78 |
79 | # up信息不存在于数据库,对数据库进行更新
80 | if uid not in bili_task_manager.up_list:
81 | up_name, latest_timestamp = await bili_client.init_up_info(uid)
82 |
83 | if up_name:
84 | bili_task_manager.add_up_info(uid, up_name, latest_timestamp)
85 | if user_type == 0:
86 | bili_task_manager.add_user_follower(0, uid, user_id)
87 | else:
88 | bili_task_manager.add_group_follower(0, uid, user_id)
89 |
90 | logger.info(f"{__PLUGIN_NAME}用户/群 <{user_id}> 关注主播 <{up_name}> 成功")
91 | return (True, up_name + f"(uid: {uid})")
92 | else:
93 | logger.info(f'{__PLUGIN_NAME}up({uid})不存在,请检查uid')
94 | return (False, uid + "(uid错误)")
95 | up_name = bili_task_manager.up_list[uid]["up_name"]
96 | # 处理已关注
97 | if user_type == 0 and user_id in bili_task_manager.up_list[uid]["user_follower"] or \
98 | user_type == 1 and user_id in bili_task_manager.up_list[uid]["group_follower"]:
99 | logger.info(f"{__PLUGIN_NAME}用户/群 <{user_id}> 已经关注了up <{up_name}>")
100 | return (False, up_name + "(已关注)")
101 |
102 | # 进行关注
103 | if user_type == 0:
104 | bili_task_manager.add_user_follower(0, uid, user_id)
105 | elif user_type == 1:
106 | bili_task_manager.add_group_follower(0, uid, user_id)
107 | logger.info(f"{__PLUGIN_NAME}用户/群 <{user_id}> 关注up <{up_name}> 成功")
108 | return (True, up_name + f"(uid: {uid})")
109 | except BiliAPI404Error:
110 | return (False, uid + "(uid错误)")
111 | except (BiliConnectionError, BiliAPIRetCodeError, BiliStatusCodeError):
112 | ex_type, ex_val, _ = sys.exc_info()
113 | exception_msg = f'【错误报告】\n获取up主 <{uid}> B站信息发生错误\n错误类型: {ex_type}\n错误值: {ex_val}\n'
114 | logger.error(f"{__PLUGIN_NAME}" + exception_msg + traceback.format_exc())
115 | return (False, uid + "(网络错误)")
116 | except BiliDatebaseError:
117 | ex_type, ex_val, _ = sys.exc_info()
118 | exception_msg = f'【错误报告】\n关注up主 <{uid}>时数据库发生错误\n错误类型: {ex_type}\n错误值: {ex_val}\n'
119 | logger.error(f"{__PLUGIN_NAME}" + exception_msg + traceback.format_exc())
120 | return (False, uid + "(数据库错误)")
121 | except Exception:
122 | ex_type, ex_val, _ = sys.exc_info()
123 | exception_msg = f'【错误报告】\n关注up主 <{uid}>时发生意料之外的错误\n错误类型: {ex_type}\n错误值: {ex_val}\n'
124 | logger.error(f"{__PLUGIN_NAME}" + exception_msg + traceback.format_exc())
125 | return (False, uid + "(未知错误,请查看日志)")
126 |
127 | async def unfollow_up(uid: str, user_id: str, user_type: int) -> Tuple[bool, str]:
128 | '''根据个人用户或群取关up主,修改up文件
129 |
130 | Args:
131 | uid (str): up的uid
132 | user_id (str): 用户的qq号/群号
133 | user_type (int): 0-个人用户,1-群
134 |
135 | Returns:
136 | Tuple[bool, str]: [是否成功,信息]
137 | '''
138 |
139 | # 处理参数错误
140 | if not uid.isdigit():
141 | logger.error(f'{__PLUGIN_NAME}存在错误参数 <{uid}>')
142 | return (False, uid + "(错误参数)")
143 | try:
144 | # 处理未关注
145 | logger.debug(f"{uid}的关注列表{bili_task_manager.up_list[uid]['user_follower']}")
146 | if uid not in bili_task_manager.up_list or \
147 | user_type == 0 and user_id not in bili_task_manager.up_list[uid]["user_follower"] or \
148 | user_type == 1 and user_id not in bili_task_manager.up_list[uid]["group_follower"]:
149 | logger.info(f'{__PLUGIN_NAME}用户/群 <{user_id}> 未关注up <{uid}>')
150 | return (False, uid + "(未关注)")
151 |
152 | # 进行取关
153 | up_name = bili_task_manager.up_list[uid]["up_name"]
154 | if user_type == 0:
155 | bili_task_manager.remove_user_follower(0, uid, user_id)
156 | else:
157 | bili_task_manager.remove_group_follower(0, uid, user_id)
158 | logger.info(f'{__PLUGIN_NAME}用户/群 <{user_id}> 取关up <{uid}> 成功')
159 |
160 | return (True, up_name + f"(uid: {uid})")
161 | except BiliDatebaseError:
162 | ex_type, ex_val, _ = sys.exc_info()
163 | exception_msg = f'【错误报告】\n取关up主 <{uid}> 时数据库发生错误\n错误类型: {ex_type}\n错误值: {ex_val}\n'
164 | logger.error(f"{__PLUGIN_NAME}" + exception_msg + traceback.format_exc())
165 | return (False, uid + "(数据库错误)")
166 |
167 | async def follow_up_list(
168 | user_id: int,
169 | uid_list: List[str],
170 | user_type: int
171 | ) -> List[List[str]]:
172 | '''个人用户/群关注up主
173 |
174 | Args:
175 | user_id (int): qq号/群号
176 | uid_list (List[str]): up主的uid
177 | user_type (int): 0-个人用户,1-群
178 |
179 | Returns:
180 | List[List[str]]: [[关注成功列表],[关注失败列表]]
181 | '''
182 | successList = []
183 | failList = []
184 |
185 | for uid in uid_list:
186 | isSuccess, s = await follow_up(uid, str(user_id), user_type)
187 | if isSuccess:
188 | successList.append(s)
189 | else:
190 | failList.append(s)
191 |
192 | return [successList, failList]
193 |
194 | async def unfollow_up_list(
195 | user_id: int,
196 | uid_list: List[str],
197 | user_type: int
198 | ) -> List[List[str]]:
199 | '''个人用户/群取关up主
200 |
201 | Args:
202 | user_id (int): qq号/群号
203 | uid_list (List[str]): 取关的up主
204 | user_type (int): 0-个人用户,1-群
205 |
206 | Returns:
207 | List[List[str]]: [是否成功,信息]
208 | '''
209 | successList = []
210 | failList = []
211 |
212 | for uid in uid_list:
213 | isSuccess, s = await unfollow_up(uid, str(user_id), user_type)
214 | if isSuccess:
215 | successList.append(s)
216 | else:
217 | failList.append(s)
218 |
219 | return [successList, failList]
--------------------------------------------------------------------------------
/bili_src/bili_client.py:
--------------------------------------------------------------------------------
1 | import json
2 | import httpx
3 | from random import choice, uniform, randint
4 | import asyncio
5 | import time
6 | from typing import List, Tuple, Dict
7 | from .exception import BiliAPI404Error, BiliAPIRetCodeError, BiliConnectionError, BiliDatebaseError, BiliInvalidRoomId, BiliInvalidShortUrl, BiliNoLiveRoom, BiliStatusCodeError
8 | from nonebot.log import logger
9 | from functools import reduce
10 | from hashlib import md5
11 | import urllib.parse
12 | import uuid
13 | import re
14 | from nonebot import require
15 |
16 | __PLUGIN_NAME__ = "[bilibilibot~Client]"
17 | class BiliClient():
18 | def __init__(self) -> None:
19 | self.__proxy_pool__ = [None]
20 | self.__retry_times__ = 3
21 | self.__ua_list__ = [
22 | "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36",
23 | "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML like Gecko) Chrome/44.0.2403.155 Safari/537.36",
24 | "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36",
25 | "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2227.1 Safari/537.36",
26 | "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2227.0 Safari/537.36",
27 | "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2227.0 Safari/537.36",
28 | "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2226.0 Safari/537.36",
29 | "Mozilla/5.0 (Windows NT 6.4; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2225.0 Safari/537.36",
30 | "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2225.0 Safari/537.36",
31 | "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2224.3 Safari/537.36",
32 | "Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2214.93 Safari/537.36",
33 | "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2062.124 Safari/537.36",
34 | "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2049.0 Safari/537.36",
35 | "Mozilla/5.0 (Windows NT 4.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2049.0 Safari/537.36",
36 | "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.67 Safari/537.36",
37 | "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.67 Safari/537.36",
38 | "Mozilla/5.0 (X11; OpenBSD i386) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.125 Safari/537.36",
39 | "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1944.0 Safari/537.36",
40 | "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.3319.102 Safari/537.36",
41 | "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.2309.372 Safari/537.36",
42 | "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.2117.157 Safari/537.36",
43 | "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.47 Safari/537.36",
44 | "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1866.237 Safari/537.36",
45 | "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1847.137 Safari/4E423F",
46 | "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1847.116 Safari/537.36 Mozilla/5.0 (iPad; U; CPU OS 3_2 like Mac OS X; en-us) AppleWebKit/531.21.10 (KHTML, like Gecko) Version/4.0.4 Mobile/7B334b Safari/531.21.10",
47 | "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.517 Safari/537.36",
48 | "Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1667.0 Safari/537.36",
49 | "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1664.3 Safari/537.36",
50 | "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1664.3 Safari/537.36",
51 | "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.16 Safari/537.36",
52 | "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1623.0 Safari/537.36",
53 | "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/30.0.1599.17 Safari/537.36",
54 | "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1547.62 Safari/537.36",
55 | "Mozilla/5.0 (X11; CrOS i686 4319.74.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1547.57 Safari/537.36",
56 | "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1547.2 Safari/537.36",
57 | "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1468.0 Safari/537.36",
58 | "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1467.0 Safari/537.36",
59 | "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1464.0 Safari/537.36",
60 | "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1500.55 Safari/537.36",
61 | "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.93 Safari/537.36",
62 | "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.93 Safari/537.36",
63 | "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.93 Safari/537.36",
64 | "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.93 Safari/537.36",
65 | "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.93 Safari/537.36",
66 | "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.93 Safari/537.36",
67 | "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.90 Safari/537.36",
68 | "Mozilla/5.0 (X11; NetBSD) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.116 Safari/537.36",
69 | "Mozilla/5.0 (X11; CrOS i686 3912.101.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.116 Safari/537.36",
70 | "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.60 Safari/537.17",
71 | "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1309.0 Safari/537.17"
72 | ]
73 |
74 | self.API = {
75 | # 用于获取临时cookie
76 | "get_bili_cookie": "https://www.bilibili.com",
77 | "get_user_info_by_uid": "https://api.bilibili.com/x/space/wbi/acc/info",
78 | "get_latest_video_by_uid": "https://api.bilibili.com/x/space/wbi/arc/search",
79 | "get_live_info_by_room_id": "https://api.live.bilibili.com/room/v1/Room/get_info",
80 | "get_liver_info_by_uid": "https://api.live.bilibili.com/live_user/v1/Master/info",
81 | "get_telegram_info_by_media_id": "https://api.bilibili.com/pgc/review/user",
82 | "get_telegram_info_by_ep_id": "https://api.bilibili.com/pgc/view/web/season",
83 | "get_telegram_info_by_season_id": "https://api.bilibili.com/pgc/view/web/season",
84 | "get_telegram_latest_episode": "https://api.bilibili.com/pgc/view/web/season",
85 | "get_dynamic_list_by_uid": "https://api.bilibili.com/x/polymer/web-dynamic/v1/feed/space",
86 | "get_detail_dynamic_by_id": "https://t.bilibili.com/{}",
87 | "get_api_access": "https://api.bilibili.com/x/internal/gaia-gateway/ExClimbWuzhi",
88 | "get_img_sub_key": "https://api.bilibili.com/x/web-interface/nav",
89 | }
90 | self.headers = {"User-Agent": "Mozilla/5.0"}
91 | self.img_key = self.sub_key = ""
92 | self.get_img_key_and_sub_key()
93 | self.dynamic_cookie = httpx.Cookies()
94 | self.update_dynamic_cookie()
95 |
96 |
97 | def __request_header__(self) -> httpx.Headers:
98 | '''返回随机UA的header
99 |
100 | Returns:
101 | httpx.Headers: 返回header
102 | '''
103 | headers = {
104 | 'User-Agent': choice(self.__ua_list__),
105 | }
106 | return httpx.Headers(headers)
107 |
108 | async def get_today_img_key_and_sub_key(self) -> Tuple[str, str]:
109 | '''获取今天的img_key和sub_key参数
110 |
111 | Returns:
112 | Tuple[str, str]: (img_key, sub_key)
113 | '''
114 | try:
115 | async with httpx.AsyncClient(headers=self.headers) as client:
116 | r1 = await client.get("https://api.bilibili.com/x/web-interface/nav")
117 | except Exception as e:
118 | pass
119 |
120 | r1_json = r1.json()
121 | img_url: str = r1_json['data']['wbi_img']['img_url']
122 | sub_url: str = r1_json['data']['wbi_img']['sub_url']
123 |
124 | img_key = img_url.rsplit('/', 1)[1].split('.')[0]
125 | sub_key = sub_url.rsplit('/', 1)[1].split('.')[0]
126 |
127 | return img_key, sub_key
128 |
129 | def get_img_key_and_sub_key(self) -> None:
130 | '''获取今天的img_key和sub_key参数
131 |
132 | Returns:
133 | Tuple[str, str]: (img_key, sub_key)
134 | '''
135 | try:
136 | r1 = httpx.get(self.API["get_img_sub_key"], headers=self.headers)
137 | except Exception as e:
138 | logger.error(f"获取img_key和sub_key时发生错误: {e}")
139 | return "", ""
140 |
141 | r1_json = r1.json()
142 | img_url: str = r1_json['data']['wbi_img']['img_url']
143 | sub_url: str = r1_json['data']['wbi_img']['sub_url']
144 |
145 | img_key = img_url.rsplit('/', 1)[1].split('.')[0]
146 | sub_key = sub_url.rsplit('/', 1)[1].split('.')[0]
147 |
148 | self.img_key = img_key
149 | self.sub_key = sub_key
150 |
151 | def get_mixinkey(self, orig: str):
152 | '对 imgKey 和 subKey 进行字符顺序打乱编码'
153 | mixinKeyEncTab = [
154 | 46, 47, 18, 2, 53, 8, 23, 32, 15, 50, 10, 31, 58, 3, 45, 35, 27, 43, 5, 49,
155 | 33, 9, 42, 19, 29, 28, 14, 39, 12, 38, 41, 13, 37, 48, 7, 16, 24, 55, 40,
156 | 61, 26, 17, 0, 1, 60, 51, 30, 4, 22, 25, 54, 21, 56, 59, 6, 63, 57, 62, 11,
157 | 36, 20, 34, 44, 52
158 | ]
159 | return reduce(lambda s, i: s + orig[i], mixinKeyEncTab, '')[:32]
160 |
161 | def wbi_sign(self, params: dict):
162 | '为请求参数进行 wbi 签名'
163 | mixin_key = self.get_mixinkey(self.img_key + self.sub_key)
164 | curr_time = round(time.time())
165 | params['wts'] = curr_time # 添加 wts 字段
166 | params = dict(sorted(params.items())) # 按照 key 重排参数
167 | # 过滤 value 中的 "!'()*" 字符
168 | params = {
169 | k : ''.join(filter(lambda chr: chr not in "!'()*", str(v)))
170 | for k, v
171 | in params.items()
172 | }
173 | query = urllib.parse.urlencode(params) # 序列化参数
174 | wbi_sign = md5((query + mixin_key).encode()).hexdigest() # 计算 w_rid
175 | params['w_rid'] = wbi_sign
176 | return params
177 |
178 | async def get_latest_video(self, client: httpx.AsyncClient, uid: str, last_udpate_time: int) -> Tuple[bool, str, str, int, str]:
179 | """
180 | @description :
181 | 根据uid和时间戳, 返回元组,表示存在新视频或无新视频
182 | ---------
183 | @param :
184 | client: httpx.AsyncClient
185 | uid: 查询新视频用户的uid
186 | last_udpate_time: 数据库中记录的最新视频的时间戳
187 | -------
188 | @Returns :
189 | 返回一个元组[是否更新,bv号,标题,发布时间戳,封面的链接]
190 | -------
191 | """
192 | try:
193 | """mid={}&ps=1&tid=0&pn=1&order=pubdate&jsonp=jsonp"""
194 | params = {
195 | "mid": uid,
196 | "ps": 1,
197 | "tid": 0,
198 | "pn": 1,
199 | "order": "pubdate",
200 | "jsonp": "jsonp"
201 | }
202 | response = await client.get(url=self.API["get_latest_video_by_uid"], params=self.wbi_sign(params))
203 | except Exception as e:
204 | raise BiliConnectionError(0, uid, e.args[0])
205 |
206 | if response.status_code != 200:
207 | raise BiliStatusCodeError(0, uid, response.status_code)
208 |
209 | response = response.json()
210 | if response["code"] != 0:
211 | raise BiliAPIRetCodeError(0, uid, response["code"], response["message"])
212 |
213 | #logger.debug(f'{__PLUGIN_NAME__}{json.dumps(response, ensure_ascii=False, indent=4)}')
214 |
215 | latest_video = response['data']['list']['vlist'][0] if len(response['data']['list']['vlist']) != 0 else {}
216 | post_time = latest_video.get("created", 0)
217 |
218 | if post_time > last_udpate_time:
219 | # 发现新视频
220 | title = latest_video['title']
221 | bvID = latest_video['bvid']
222 | cover_url = latest_video['pic']
223 | return (True, bvID, title, post_time, cover_url)
224 |
225 | return (False, '', '', 0, '')
226 |
227 | async def init_up_info(self, uid: str) -> Tuple[str, int]:
228 | """
229 | @description :
230 | 根据uid查询up主信息
231 | ---------
232 | @param :
233 | uid: 用户的uid
234 | -------
235 | @Returns :
236 | 返回一个元组
237 | [up主名字,最新视频的时间戳]
238 | -------
239 | """
240 |
241 | async with httpx.AsyncClient(headers=self.headers) as client:
242 | try:
243 | response1 = await client.get(url=self.API["get_user_info_by_uid"], params=self.wbi_sign({"mid": uid}))
244 | except Exception as e:
245 | raise BiliConnectionError(0, uid, e.args[0])
246 |
247 | if response1.status_code != 200:
248 | raise BiliStatusCodeError(0, uid, response1.status_code)
249 |
250 | response1 = response1.json()
251 | if response1["code"] == -404:
252 | raise BiliAPI404Error()
253 | elif response1["code"] != 0:
254 | raise BiliAPIRetCodeError(0, uid, response1["code"], response1["message"])
255 |
256 | user_name = response1["data"]["name"]
257 | try:
258 | response2 = await client.get(url=self.API["get_latest_video_by_uid"], params=self.wbi_sign({"mid": uid,"ps": 1,"tid": 0,"pn": 1,"order": "pubdate","jsonp": "jsonp"}))
259 | except Exception as e:
260 | raise BiliConnectionError(0, uid, e.args[0])
261 |
262 | if response2.status_code != 200:
263 | raise BiliStatusCodeError(0, uid, response2.status_code)
264 |
265 | response2 = response2.json()
266 | if response2["code"] != 0:
267 | raise BiliAPIRetCodeError(0, uid, response2["code"], response2["message"])
268 |
269 | latest_video = response2['data']['list']['vlist'][0] if len(response2['data']['list']['vlist']) != 0 else {}
270 | post_time = latest_video.get("created", 0)
271 |
272 | return (user_name, post_time)
273 |
274 | async def get_live_status(self, client:httpx.AsyncClient, uid: str, room_id: str) -> Tuple[bool, str, str]:
275 | """
276 | @description :
277 | 根据房间号,获取直播间是否开播
278 | ---------
279 | @param :
280 | uid: 主播的uid
281 | room_id: 直播间号
282 | -------
283 | @Returns :
284 | 返回一个元组
285 | [是否正在直播,直播间标题,直播间封面链接]
286 | -------
287 | """
288 |
289 | try:
290 | response = await client.get(self.API["get_live_info_by_room_id"], params={"room_id": room_id})
291 | except Exception as e:
292 | raise BiliConnectionError(1, uid, e.args[0])
293 |
294 | if response.status_code != 200:
295 | raise BiliStatusCodeError(1, uid, response.status_code)
296 |
297 | response = response.json()
298 | if response["code"] != 0:
299 | raise BiliAPIRetCodeError(1, uid, response["code"], response["message"])
300 |
301 | live_status = response["data"]["live_status"]
302 | title = response["data"]["title"]
303 | cover_url = response["data"]["user_cover"]
304 |
305 | if live_status == 1:
306 | return (True, title, cover_url)
307 | else:
308 | return (False, "", "")
309 |
310 | async def init_liver_info(self, uid: str) -> Tuple[str, str]:
311 | """
312 | @description :
313 | 根据uid,返回直播间信息
314 | ---------
315 | @param :
316 | uid: 用户的uid
317 | -------
318 | @Returns :
319 | 返回一个元组
320 | [用户名,直播间房间号]
321 | -------
322 | """
323 |
324 | async with httpx.AsyncClient(headers=self.headers) as client:
325 | try:
326 | response = await client.get(self.API["get_liver_info_by_uid"], params={"uid": uid})
327 | except Exception as e:
328 | raise BiliConnectionError(0, uid, e.args[0])
329 |
330 | if response.status_code != 200:
331 | raise BiliStatusCodeError(1, uid, response.status_code)
332 |
333 | response = response.json()
334 | if response["code"] != 0:
335 | raise BiliAPIRetCodeError(1, uid, response["code"], response["message"])
336 |
337 | liver_name = response["data"]["info"]["uname"]
338 | room_id = response["data"]["room_id"]
339 |
340 | if room_id == 0:
341 | raise BiliNoLiveRoom(liver_name)
342 |
343 | return (liver_name, room_id)
344 |
345 | async def init_liver_info_by_room_id(self, room_id: str) -> Tuple[str, str]:
346 | '''根据直播房间号获得主播uid和主播用户名
347 |
348 | Args:
349 | room_id (str): 直播房间号
350 |
351 | Returns:
352 | Tuple[str, str]:
353 | 返回一个元组
354 | [主播uid, 主播用户名]
355 | '''
356 |
357 | async with httpx.AsyncClient(headers=self.headers) as client:
358 | try:
359 | response1 = await client.get(url=self.API["get_live_info_by_room_id"], params={"room_id": room_id})
360 | except Exception as e:
361 | raise BiliConnectionError(1, f"房间号:{room_id}", e.args[0])
362 |
363 | if response1.status_code != 200:
364 | raise BiliStatusCodeError(1, f"房间号:{room_id}", response1.status_code)
365 |
366 | response1 = response1.json()
367 | if response1["code"] == 1:
368 | raise BiliInvalidRoomId(room_id)
369 | if response1["code"] != 0:
370 | raise BiliAPIRetCodeError(1, f"房间号:{room_id}", response1["code"], response1["message"])
371 |
372 | uid = str(response1["data"]["uid"])
373 |
374 | try:
375 | response2 = await client.get(self.API["get_liver_info_by_uid"], params={"uid": uid})
376 | except Exception as e:
377 | raise BiliConnectionError(0, uid, e.args[0])
378 |
379 | if response2.status_code != 200:
380 | raise BiliStatusCodeError(1, uid, response2.status_code)
381 |
382 | response2 = response2.json()
383 | if response2["code"] != 0:
384 | raise BiliAPIRetCodeError(1, uid, response2["code"], response2["message"])
385 |
386 | liver_name = response2["data"]["info"]["uname"]
387 |
388 | return (uid, liver_name)
389 |
390 | async def init_telegram_info_by_ep_id(self, ep_id: int) -> Tuple[int, str, int, bool]:
391 | ''' 根据ep_id初始化番剧信息
392 |
393 | Args:
394 | ep_id (int): ep_id
395 |
396 | Returns:
397 | Tuple[int, str, int, bool]:
398 | 返回一个元组
399 | [season_id, 番剧名, 最新集, 是否完结]
400 | '''
401 |
402 | async with httpx.AsyncClient(headers=self.headers) as client:
403 | try:
404 | response = await client.get(url=self.API["get_telegram_info_by_ep_id"], params={"ep_id": ep_id})
405 | except Exception as e:
406 | raise BiliConnectionError(2, f"ep_id:{ep_id}", e.args[0])
407 |
408 | if response.status_code != 200:
409 | raise BiliStatusCodeError(2, f"ep_id:{ep_id}", response.status_code)
410 |
411 | response = response.json()
412 | if response["code"] == -404:
413 | raise BiliAPI404Error()
414 | elif response["code"] != 0:
415 | raise BiliAPIRetCodeError(2, f"ep_id:{ep_id}", response["code"], response["message"])
416 |
417 | season_id = response["result"]["season_id"]
418 | season_title = response["result"]["season_title"]
419 | is_finish = bool(response["result"]["publish"]["is_finish"])
420 | latest_episode = len(response["result"]["episodes"])
421 |
422 | return (season_id, season_title, latest_episode, is_finish)
423 |
424 | async def init_telegram_info_by_season_id(self, season_id: int) -> Tuple[int, str, int, bool]:
425 | ''' 根据season_id初始化番剧信息
426 |
427 | Args:
428 | season_id (int): season_id
429 |
430 | Returns:
431 | Tuple[int, str, int, bool]:
432 | 返回一个元组
433 | [season_id, 番剧名, 最新集, 是否完结]
434 | '''
435 |
436 | async with httpx.AsyncClient(headers=self.headers) as client:
437 | try:
438 | response = await client.get(url=self.API["get_telegram_info_by_season_id"], params={"season_id": season_id})
439 | except Exception as e:
440 | raise BiliConnectionError(2, f"season_id:{season_id}", e.args[0])
441 |
442 | if response.status_code != 200:
443 | raise BiliStatusCodeError(2, f"season_id:{season_id}", response.status_code)
444 |
445 | response = response.json()
446 | if response["code"] == -404:
447 | raise BiliAPI404Error()
448 | elif response["code"] != 0:
449 | raise BiliAPIRetCodeError(2, f"season_id:{season_id}", response["code"], response["message"])
450 |
451 | season_title = response["result"]["title"]
452 | is_finish = bool(response["result"]["publish"]["is_finish"])
453 | latest_episode = len(response["result"]["episodes"])
454 |
455 | return (season_id, season_title, latest_episode, is_finish)
456 |
457 | async def init_telegram_info_by_media_id(self, media_id: int) -> Tuple[int, str, int, bool]:
458 | ''' 根据media_id初始化番剧信息
459 |
460 | Args:
461 | media_id (int): media_id
462 |
463 | Returns:
464 | Tuple[int, str, int, bool]:
465 | 返回一个元组
466 | [season_id, 番剧名, 最新集, 是否完结]
467 | '''
468 |
469 | async with httpx.AsyncClient(headers=self.headers) as client:
470 | try:
471 | response = await client.get(url=self.API["get_telegram_info_by_media_id"], params={"media_id": media_id})
472 | except Exception as e:
473 | raise BiliConnectionError(2, f"media_id:{media_id}", e.args[0])
474 |
475 | if response.status_code != 200:
476 | raise BiliStatusCodeError(2, f"media_id:{media_id}", response.status_code)
477 |
478 | response = response.json()
479 | if response["code"] == -404:
480 | raise BiliAPI404Error()
481 | elif response["code"] != 0:
482 | raise BiliAPIRetCodeError(2, f"media_id:{media_id}", response["code"], response["message"])
483 |
484 | season_id = response["result"]["media"]["season_id"]
485 | season_title = response["result"]["media"]["title"]
486 | is_finish = False
487 | latest_episode = int(response["result"]["media"]["new_ep"]["index"])
488 |
489 | return (season_id, season_title, latest_episode, is_finish)
490 |
491 | async def get_telegram_latest_episode(self, client: httpx.AsyncClient, season_id: int, latest_timestamp: int) -> Tuple[bool, int, str, str, str, bool]:
492 | '''根据season_id获取番剧的最新集信息
493 |
494 | Args:
495 | season_id (int): season_id
496 | latest_timestamp (int): 记录的最新集数的发布时间
497 |
498 | Returns:
499 | Tuple[bool, int, str, str, str, bool]:
500 | 返回一个元组
501 | [是否更新, 最新集数发布时间, 最新集标题, 最新集链接, 封面链接, 是否完结]
502 | '''
503 |
504 |
505 | try:
506 | response = await client.get(url=self.API["get_telegram_latest_episode"], params={"season_id": season_id})
507 | except Exception as e:
508 | raise BiliConnectionError(2, f"season_id:{season_id}", e.args[0])
509 |
510 | if response.status_code != 200:
511 | raise BiliStatusCodeError(2, f"season_id:{season_id}", response.status_code)
512 |
513 | response = response.json()
514 | if response["code"] != 0:
515 | raise BiliAPIRetCodeError(2, f"season_id:{season_id}", response["code"], response["message"])
516 |
517 | episodes = response['result']['episodes']
518 | is_finish = bool(response["result"]["publish"]["is_finish"])
519 |
520 | latest_episode = episodes[-1]
521 | if latest_episode["pub_time"] > latest_timestamp:
522 | # 有更新
523 | cover_url = latest_episode['cover']
524 | title = latest_episode['share_copy']
525 | play_url = latest_episode['share_url']
526 | return (True, latest_episode["pub_time"], title, play_url, cover_url, is_finish)
527 | else:
528 | return (False, 0, "", "", "", is_finish)
529 |
530 | async def parse_short_url(self, short_url: str) -> Tuple[int, str]:
531 | '''解析b23.tv的短链接,返回短链接类型以及目标id
532 |
533 | Args:
534 | short_url (str): 短链接
535 |
536 | Returns:
537 | Tuple[int, str]:
538 | 返回一个元组
539 | [类型:0-up的uid,1-主播的房间号,2-番剧的ep_id, 目标id]
540 | '''
541 | async with httpx.AsyncClient(headers=self.headers) as client:
542 | try:
543 | response = await client.get(url=short_url)
544 | except Exception as e:
545 | raise BiliConnectionError(3, short_url, e.args[0])
546 |
547 | if response.status_code != 302:
548 | raise BiliStatusCodeError(3, short_url, response.status_code)
549 |
550 | origin_url = response.headers["Location"].split("?")[0]
551 | logger.debug(f"get origin url = {origin_url}")
552 | target_id = origin_url.split("/")[-1]
553 | if "space" in origin_url:
554 | return (0, target_id)
555 | elif "live" in origin_url:
556 | return (1, target_id)
557 | elif "bangumi" in origin_url:
558 | return (2, target_id)
559 |
560 | raise BiliInvalidShortUrl(short_url)
561 |
562 | def update_dynamic_cookie(self) -> None:
563 | headers = {
564 | 'user-agent': 'Mozilla/5.0',
565 | 'referer': 'https://www.bilibili.com/',
566 | "Host": "space.bilibili.com",
567 | 'accept-language': 'zh-CN,zh;q=0.9',
568 | }
569 | with httpx.Client(headers=headers) as client:
570 | cookies = httpx.Cookies({'_uuid': f'{str(uuid.uuid4()).upper()}{randint(0, 99999):05d}infoc'})
571 |
572 | response = client.get(
573 | 'https://www.bilibili.com/1/dynamic',
574 | cookies=cookies
575 | )
576 | spm_prefix = re.search(r'