nonebot-plugin-bililive 2.0.5__tar.gz → 2.0.7__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {nonebot_plugin_bililive-2.0.5 → nonebot_plugin_bililive-2.0.7}/PKG-INFO +16 -9
- {nonebot_plugin_bililive-2.0.5 → nonebot_plugin_bililive-2.0.7}/README.md +11 -5
- {nonebot_plugin_bililive-2.0.5 → nonebot_plugin_bililive-2.0.7}/bililive/__init__.py +1 -1
- {nonebot_plugin_bililive-2.0.5 → nonebot_plugin_bililive-2.0.7}/bililive/cli/utils.py +0 -1
- {nonebot_plugin_bililive-2.0.5 → nonebot_plugin_bililive-2.0.7}/bililive/libs/dynamic/web.py +11 -8
- {nonebot_plugin_bililive-2.0.5 → nonebot_plugin_bililive-2.0.7}/bililive/plugins/pusher/dynamic_pusher.py +69 -42
- {nonebot_plugin_bililive-2.0.5 → nonebot_plugin_bililive-2.0.7}/bililive/utils/__init__.py +20 -11
- {nonebot_plugin_bililive-2.0.5 → nonebot_plugin_bililive-2.0.7}/bililive/utils/browser.py +27 -0
- {nonebot_plugin_bililive-2.0.5 → nonebot_plugin_bililive-2.0.7}/bililive/version.py +1 -1
- {nonebot_plugin_bililive-2.0.5 → nonebot_plugin_bililive-2.0.7}/nonebot_plugin_bililive/__init__.py +1 -1
- {nonebot_plugin_bililive-2.0.5 → nonebot_plugin_bililive-2.0.7}/pyproject.toml +6 -5
- {nonebot_plugin_bililive-2.0.5 → nonebot_plugin_bililive-2.0.7}/tests/test_maintenance.py +47 -2
- {nonebot_plugin_bililive-2.0.5 → nonebot_plugin_bililive-2.0.7}/LICENSE +0 -0
- {nonebot_plugin_bililive-2.0.5 → nonebot_plugin_bililive-2.0.7}/bililive/__main__.py +0 -0
- {nonebot_plugin_bililive-2.0.5 → nonebot_plugin_bililive-2.0.7}/bililive/cli/__init__.py +0 -0
- {nonebot_plugin_bililive-2.0.5 → nonebot_plugin_bililive-2.0.7}/bililive/cli/bot.py +0 -0
- {nonebot_plugin_bililive-2.0.5 → nonebot_plugin_bililive-2.0.7}/bililive/compat.py +0 -0
- {nonebot_plugin_bililive-2.0.5 → nonebot_plugin_bililive-2.0.7}/bililive/config.py +0 -0
- {nonebot_plugin_bililive-2.0.5 → nonebot_plugin_bililive-2.0.7}/bililive/database/__init__.py +0 -0
- {nonebot_plugin_bililive-2.0.5 → nonebot_plugin_bililive-2.0.7}/bililive/database/db.py +0 -0
- {nonebot_plugin_bililive-2.0.5 → nonebot_plugin_bililive-2.0.7}/bililive/database/models.py +0 -0
- {nonebot_plugin_bililive-2.0.5 → nonebot_plugin_bililive-2.0.7}/bililive/libs/__init__.py +0 -0
- {nonebot_plugin_bililive-2.0.5 → nonebot_plugin_bililive-2.0.7}/bililive/libs/dynamic/__init__.py +0 -0
- {nonebot_plugin_bililive-2.0.5 → nonebot_plugin_bililive-2.0.7}/bililive/libs/dynamic/card.py +0 -0
- {nonebot_plugin_bililive-2.0.5 → nonebot_plugin_bililive-2.0.7}/bililive/libs/dynamic/desc.py +0 -0
- {nonebot_plugin_bililive-2.0.5 → nonebot_plugin_bililive-2.0.7}/bililive/libs/dynamic/display.py +0 -0
- {nonebot_plugin_bililive-2.0.5 → nonebot_plugin_bililive-2.0.7}/bililive/libs/dynamic/user_profile.py +0 -0
- {nonebot_plugin_bililive-2.0.5 → nonebot_plugin_bililive-2.0.7}/bililive/plugins/__init__.py +0 -0
- {nonebot_plugin_bililive-2.0.5 → nonebot_plugin_bililive-2.0.7}/bililive/plugins/at/__init__.py +0 -0
- {nonebot_plugin_bililive-2.0.5 → nonebot_plugin_bililive-2.0.7}/bililive/plugins/at/at_off.py +0 -0
- {nonebot_plugin_bililive-2.0.5 → nonebot_plugin_bililive-2.0.7}/bililive/plugins/at/at_on.py +0 -0
- {nonebot_plugin_bililive-2.0.5 → nonebot_plugin_bililive-2.0.7}/bililive/plugins/auto_agree.py +0 -0
- {nonebot_plugin_bililive-2.0.5 → nonebot_plugin_bililive-2.0.7}/bililive/plugins/auto_delete.py +0 -0
- {nonebot_plugin_bililive-2.0.5 → nonebot_plugin_bililive-2.0.7}/bililive/plugins/dynamic/__init__.py +0 -0
- {nonebot_plugin_bililive-2.0.5 → nonebot_plugin_bililive-2.0.7}/bililive/plugins/dynamic/dynamic_off.py +0 -0
- {nonebot_plugin_bililive-2.0.5 → nonebot_plugin_bililive-2.0.7}/bililive/plugins/dynamic/dynamic_on.py +0 -0
- {nonebot_plugin_bililive-2.0.5 → nonebot_plugin_bililive-2.0.7}/bililive/plugins/help.py +0 -0
- {nonebot_plugin_bililive-2.0.5 → nonebot_plugin_bililive-2.0.7}/bililive/plugins/live/__init__.py +0 -0
- {nonebot_plugin_bililive-2.0.5 → nonebot_plugin_bililive-2.0.7}/bililive/plugins/live/live_now.py +0 -0
- {nonebot_plugin_bililive-2.0.5 → nonebot_plugin_bililive-2.0.7}/bililive/plugins/live/live_off.py +0 -0
- {nonebot_plugin_bililive-2.0.5 → nonebot_plugin_bililive-2.0.7}/bililive/plugins/live/live_on.py +0 -0
- {nonebot_plugin_bililive-2.0.5 → nonebot_plugin_bililive-2.0.7}/bililive/plugins/permission/__init__.py +0 -0
- {nonebot_plugin_bililive-2.0.5 → nonebot_plugin_bililive-2.0.7}/bililive/plugins/permission/permission_off.py +0 -0
- {nonebot_plugin_bililive-2.0.5 → nonebot_plugin_bililive-2.0.7}/bililive/plugins/permission/permission_on.py +0 -0
- {nonebot_plugin_bililive-2.0.5 → nonebot_plugin_bililive-2.0.7}/bililive/plugins/pusher/__init__.py +0 -0
- {nonebot_plugin_bililive-2.0.5 → nonebot_plugin_bililive-2.0.7}/bililive/plugins/pusher/live_pusher.py +0 -0
- {nonebot_plugin_bililive-2.0.5 → nonebot_plugin_bililive-2.0.7}/bililive/plugins/sub/__init__.py +0 -0
- {nonebot_plugin_bililive-2.0.5 → nonebot_plugin_bililive-2.0.7}/bililive/plugins/sub/add_sub.py +0 -0
- {nonebot_plugin_bililive-2.0.5 → nonebot_plugin_bililive-2.0.7}/bililive/plugins/sub/delete_sub.py +0 -0
- {nonebot_plugin_bililive-2.0.5 → nonebot_plugin_bililive-2.0.7}/bililive/plugins/sub/sub_list.py +0 -0
- {nonebot_plugin_bililive-2.0.5 → nonebot_plugin_bililive-2.0.7}/bililive/utils/captcha_solver.py +0 -0
- {nonebot_plugin_bililive-2.0.5 → nonebot_plugin_bililive-2.0.7}/bililive/utils/fonts_provider.py +0 -0
- {nonebot_plugin_bililive-2.0.5 → nonebot_plugin_bililive-2.0.7}/bililive/utils/mobile.js +0 -0
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: nonebot-plugin-bililive
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.7
|
|
4
4
|
Summary: Push bilibili dynamics and live notifications to QQ with NoneBot2.
|
|
5
|
-
Keywords: nonebot,nonebot2,nonebot-plugin,
|
|
5
|
+
Keywords: nonebot,nonebot2,nonebot-plugin,bilibili,onebot,onebot-v11
|
|
6
6
|
Author-Email: SK-415 <2967923486@qq.com>
|
|
7
7
|
License: AGPL-3.0-or-later
|
|
8
8
|
Classifier: Development Status :: 4 - Beta
|
|
9
|
-
Classifier: Framework ::
|
|
10
|
-
Classifier:
|
|
9
|
+
Classifier: Framework :: AsyncIO
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
11
|
Classifier: Programming Language :: Python :: 3
|
|
12
12
|
Classifier: Programming Language :: Python :: 3.10
|
|
13
13
|
Classifier: Programming Language :: Python :: 3.11
|
|
@@ -22,6 +22,7 @@ Requires-Dist: click>=8.1.3
|
|
|
22
22
|
Requires-Dist: httpx<1.0,>=0.28.1
|
|
23
23
|
Requires-Dist: nonebot-adapter-onebot>=2.4.6
|
|
24
24
|
Requires-Dist: nonebot-plugin-apscheduler>=0.5.0
|
|
25
|
+
Requires-Dist: nonebot-plugin-localstore>=0.7
|
|
25
26
|
Requires-Dist: nonebot2[fastapi]>=2.5.0
|
|
26
27
|
Requires-Dist: playwright>=1.58.0
|
|
27
28
|
Requires-Dist: pydantic<3.0,>=2.12.5
|
|
@@ -58,6 +59,8 @@ _✨ 将 B 站 UP 主动态与直播推送到 QQ 的 NoneBot2 插件 ✨_
|
|
|
58
59
|
|
|
59
60
|
BiliLive 是一个基于 NoneBot2 的 B 站推送插件,支持将 UP 主的直播与动态消息推送到 QQ 群或私聊场景。
|
|
60
61
|
|
|
62
|
+
目前支持的适配器:OneBot V11。
|
|
63
|
+
|
|
61
64
|
### 特性
|
|
62
65
|
|
|
63
66
|
- 支持按 UP 主维度分别开启或关闭动态、直播推送。
|
|
@@ -79,24 +82,28 @@ BiliLive 是一个基于 NoneBot2 的 B 站推送插件,支持将 UP 主的直
|
|
|
79
82
|
|
|
80
83
|
<details>
|
|
81
84
|
<summary>使用包管理器安装</summary>
|
|
82
|
-
|
|
85
|
+
|
|
86
|
+
在 NoneBot2 项目虚拟环境中安装:
|
|
83
87
|
|
|
84
88
|
pip install nonebot-plugin-bililive
|
|
85
89
|
|
|
86
90
|
|
|
87
|
-
|
|
91
|
+
然后在 NoneBot2 项目根目录的 pyproject.toml 中加载插件:
|
|
88
92
|
|
|
89
|
-
|
|
93
|
+
[tool.nonebot]
|
|
94
|
+
plugins = ["nonebot_plugin_bililive"]
|
|
90
95
|
|
|
91
96
|
</details>
|
|
92
97
|
|
|
98
|
+
首次使用动态截图功能时,插件会自动检查并安装 Chromium;如果需要自定义数据目录,可通过 BILILIVE_DIR 覆盖默认存储位置。未配置时,插件会使用 nonebot-plugin-localstore 提供的插件数据目录。
|
|
99
|
+
|
|
93
100
|
## ⚙️ 配置
|
|
94
101
|
|
|
95
102
|
在 NoneBot2 项目的 .env 文件中按需添加配置项:
|
|
96
103
|
|
|
97
104
|
| 配置项 | 必填 | 默认值 | 说明 |
|
|
98
105
|
|:-----:|:----:|:----:|:----|
|
|
99
|
-
| BILILIVE_DIR | 否 |
|
|
106
|
+
| BILILIVE_DIR | 否 | 由 nonebot-plugin-localstore 自动分配 | 插件数据目录,包含数据库、浏览器数据与动态偏移缓存 |
|
|
100
107
|
| BILILIVE_TO_ME | 否 | true | 是否需要 @机器人 或命令前缀触发 |
|
|
101
108
|
| BILILIVE_PROXY | 否 | 无 | HTTP 代理地址,用于 B 站请求和 Playwright 下载 |
|
|
102
109
|
| BILILIVE_INTERVAL | 否 | 10 | 默认轮询间隔,单位秒 |
|
|
@@ -136,7 +143,7 @@ BiliLive 是一个基于 NoneBot2 的 B 站推送插件,支持将 UP 主的直
|
|
|
136
143
|
|
|
137
144
|
### 效果图
|
|
138
145
|
|
|
139
|
-

|
|
140
147
|
|
|
141
148
|
## 开发
|
|
142
149
|
|
|
@@ -24,6 +24,8 @@ _✨ 将 B 站 UP 主动态与直播推送到 QQ 的 NoneBot2 插件 ✨_
|
|
|
24
24
|
|
|
25
25
|
BiliLive 是一个基于 NoneBot2 的 B 站推送插件,支持将 UP 主的直播与动态消息推送到 QQ 群或私聊场景。
|
|
26
26
|
|
|
27
|
+
目前支持的适配器:OneBot V11。
|
|
28
|
+
|
|
27
29
|
### 特性
|
|
28
30
|
|
|
29
31
|
- 支持按 UP 主维度分别开启或关闭动态、直播推送。
|
|
@@ -45,24 +47,28 @@ BiliLive 是一个基于 NoneBot2 的 B 站推送插件,支持将 UP 主的直
|
|
|
45
47
|
|
|
46
48
|
<details>
|
|
47
49
|
<summary>使用包管理器安装</summary>
|
|
48
|
-
|
|
50
|
+
|
|
51
|
+
在 NoneBot2 项目虚拟环境中安装:
|
|
49
52
|
|
|
50
53
|
pip install nonebot-plugin-bililive
|
|
51
54
|
|
|
52
55
|
|
|
53
|
-
|
|
56
|
+
然后在 NoneBot2 项目根目录的 pyproject.toml 中加载插件:
|
|
54
57
|
|
|
55
|
-
|
|
58
|
+
[tool.nonebot]
|
|
59
|
+
plugins = ["nonebot_plugin_bililive"]
|
|
56
60
|
|
|
57
61
|
</details>
|
|
58
62
|
|
|
63
|
+
首次使用动态截图功能时,插件会自动检查并安装 Chromium;如果需要自定义数据目录,可通过 BILILIVE_DIR 覆盖默认存储位置。未配置时,插件会使用 nonebot-plugin-localstore 提供的插件数据目录。
|
|
64
|
+
|
|
59
65
|
## ⚙️ 配置
|
|
60
66
|
|
|
61
67
|
在 NoneBot2 项目的 .env 文件中按需添加配置项:
|
|
62
68
|
|
|
63
69
|
| 配置项 | 必填 | 默认值 | 说明 |
|
|
64
70
|
|:-----:|:----:|:----:|:----|
|
|
65
|
-
| BILILIVE_DIR | 否 |
|
|
71
|
+
| BILILIVE_DIR | 否 | 由 nonebot-plugin-localstore 自动分配 | 插件数据目录,包含数据库、浏览器数据与动态偏移缓存 |
|
|
66
72
|
| BILILIVE_TO_ME | 否 | true | 是否需要 @机器人 或命令前缀触发 |
|
|
67
73
|
| BILILIVE_PROXY | 否 | 无 | HTTP 代理地址,用于 B 站请求和 Playwright 下载 |
|
|
68
74
|
| BILILIVE_INTERVAL | 否 | 10 | 默认轮询间隔,单位秒 |
|
|
@@ -102,7 +108,7 @@ BiliLive 是一个基于 NoneBot2 的 B 站推送插件,支持将 UP 主的直
|
|
|
102
108
|
|
|
103
109
|
### 效果图
|
|
104
110
|
|
|
105
|
-

|
|
106
112
|
|
|
107
113
|
## 开发
|
|
108
114
|
|
|
@@ -22,7 +22,7 @@ from .version import VERSION, __version__ # noqa: F401
|
|
|
22
22
|
__plugin_meta__ = PluginMetadata(
|
|
23
23
|
name="BiliLive",
|
|
24
24
|
description="将B站UP主的动态和直播信息推送至QQ",
|
|
25
|
-
usage="
|
|
25
|
+
usage="发送“帮助”查看命令列表,发送“关注 UID”订阅 UP 主",
|
|
26
26
|
homepage="https://github.com/Akiyy-dev/nonebot-plugin-bililive",
|
|
27
27
|
type="application",
|
|
28
28
|
config=Config,
|
{nonebot_plugin_bililive-2.0.5 → nonebot_plugin_bililive-2.0.7}/bililive/libs/dynamic/web.py
RENAMED
|
@@ -47,6 +47,16 @@ def parse_web_dynamic_items(payload: dict) -> list[WebDynamicItem]:
|
|
|
47
47
|
return parsed_items
|
|
48
48
|
|
|
49
49
|
|
|
50
|
+
def parse_web_dynamic_payload(payload: dict) -> list[WebDynamicItem]:
|
|
51
|
+
if payload.get("code") != 0:
|
|
52
|
+
raise WebDynamicError(
|
|
53
|
+
payload.get("code"),
|
|
54
|
+
payload.get("message") or "unknown error",
|
|
55
|
+
payload.get("data"),
|
|
56
|
+
)
|
|
57
|
+
return parse_web_dynamic_items(payload)
|
|
58
|
+
|
|
59
|
+
|
|
50
60
|
async def get_user_dynamics_web(
|
|
51
61
|
uid: int,
|
|
52
62
|
cookies: dict[str, str],
|
|
@@ -69,11 +79,4 @@ async def get_user_dynamics_web(
|
|
|
69
79
|
follow_redirects=True,
|
|
70
80
|
) as client:
|
|
71
81
|
response = await client.get(WEB_DYNAMIC_URL, params={"host_mid": uid})
|
|
72
|
-
|
|
73
|
-
if payload.get("code") != 0:
|
|
74
|
-
raise WebDynamicError(
|
|
75
|
-
payload.get("code"),
|
|
76
|
-
payload.get("message") or "unknown error",
|
|
77
|
-
payload.get("data"),
|
|
78
|
-
)
|
|
79
|
-
return parse_web_dynamic_items(payload)
|
|
82
|
+
return parse_web_dynamic_payload(response.json())
|
|
@@ -19,15 +19,22 @@ from nonebot.log import logger
|
|
|
19
19
|
from ...config import plugin_config
|
|
20
20
|
from ...database import DB as db
|
|
21
21
|
from ...database import dynamic_offset as offset
|
|
22
|
-
from ...libs.dynamic.web import
|
|
22
|
+
from ...libs.dynamic.web import (
|
|
23
|
+
WebDynamicError,
|
|
24
|
+
get_user_dynamics_web,
|
|
25
|
+
parse_web_dynamic_payload,
|
|
26
|
+
)
|
|
23
27
|
from ...utils import (
|
|
24
28
|
get_bilibili_cookies,
|
|
25
29
|
get_dynamic_screenshot,
|
|
30
|
+
get_user_dynamics_payload_in_browser,
|
|
26
31
|
safe_send,
|
|
27
32
|
scheduler,
|
|
28
33
|
)
|
|
29
34
|
|
|
30
|
-
|
|
35
|
+
GRPC_RISK_CONTROL_RETRY_SECONDS = 3600
|
|
36
|
+
WEB_REQUEST_BANNED_RETRY_SECONDS = 300
|
|
37
|
+
WEB_REQUEST_ERROR_RETRY_SECONDS = 600
|
|
31
38
|
dynamic_risk_control_until = {}
|
|
32
39
|
dynamic_web_fallback_until = {}
|
|
33
40
|
WEB_SKIP_DYNAMIC_TYPES = {
|
|
@@ -44,6 +51,7 @@ WEB_DYNAMIC_TYPE_MESSAGES = {
|
|
|
44
51
|
"DYNAMIC_TYPE_ARTICLE": "发布了新专栏",
|
|
45
52
|
"DYNAMIC_TYPE_MUSIC": "发布了新音频",
|
|
46
53
|
}
|
|
54
|
+
DYNAMIC_FETCH_CONCURRENCY = 4
|
|
47
55
|
|
|
48
56
|
|
|
49
57
|
async def throttle_dynamic_loop():
|
|
@@ -95,21 +103,32 @@ def should_skip_dynamic(dynamic_type, use_web_fallback: bool) -> bool:
|
|
|
95
103
|
|
|
96
104
|
|
|
97
105
|
async def get_user_dynamics_with_web_fallback(uid: int) -> tuple[list, bool]:
|
|
106
|
+
async def fetch_web_dynamics() -> list:
|
|
107
|
+
try:
|
|
108
|
+
payload = await get_user_dynamics_payload_in_browser(uid)
|
|
109
|
+
return parse_web_dynamic_payload(payload)
|
|
110
|
+
except WebDynamicError as browser_error:
|
|
111
|
+
logger.debug(
|
|
112
|
+
f"浏览器上下文动态接口获取失败,尝试直连 Web API:{uid} "
|
|
113
|
+
f"{browser_error.code} {browser_error.msg}"
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
cookies = await get_bilibili_cookies()
|
|
117
|
+
if not cookies:
|
|
118
|
+
raise WebDynamicError(-1, "browser cookies unavailable")
|
|
119
|
+
return await get_user_dynamics_web(
|
|
120
|
+
uid,
|
|
121
|
+
cookies,
|
|
122
|
+
proxy=plugin_config.bililive_proxy,
|
|
123
|
+
user_agent=plugin_config.bililive_browser_ua or None,
|
|
124
|
+
timeout=plugin_config.bililive_dynamic_timeout,
|
|
125
|
+
)
|
|
126
|
+
|
|
98
127
|
fallback_until = dynamic_web_fallback_until.get(uid)
|
|
99
128
|
if fallback_until is not None:
|
|
100
129
|
if fallback_until > monotonic():
|
|
101
130
|
logger.debug(f"动态 gRPC 接口仍在风控,继续使用 Web 接口:{uid}")
|
|
102
|
-
|
|
103
|
-
if not cookies:
|
|
104
|
-
raise WebDynamicError(-1, "browser cookies unavailable")
|
|
105
|
-
dynamics = await get_user_dynamics_web(
|
|
106
|
-
uid,
|
|
107
|
-
cookies,
|
|
108
|
-
proxy=plugin_config.bililive_proxy,
|
|
109
|
-
user_agent=plugin_config.bililive_browser_ua or None,
|
|
110
|
-
timeout=plugin_config.bililive_dynamic_timeout,
|
|
111
|
-
)
|
|
112
|
-
return dynamics, True
|
|
131
|
+
return await fetch_web_dynamics(), True
|
|
113
132
|
del dynamic_web_fallback_until[uid]
|
|
114
133
|
|
|
115
134
|
try:
|
|
@@ -129,36 +148,14 @@ async def get_user_dynamics_with_web_fallback(uid: int) -> tuple[list, bool]:
|
|
|
129
148
|
f"动态 gRPC 接口触发风控,切换 Web 接口:{uid} "
|
|
130
149
|
f"{e.code} {e.msg}"
|
|
131
150
|
)
|
|
132
|
-
dynamic_web_fallback_until[uid] = monotonic() +
|
|
133
|
-
|
|
134
|
-
if not cookies:
|
|
135
|
-
raise WebDynamicError(-1, "browser cookies unavailable")
|
|
136
|
-
dynamics = await get_user_dynamics_web(
|
|
137
|
-
uid,
|
|
138
|
-
cookies,
|
|
139
|
-
proxy=plugin_config.bililive_proxy,
|
|
140
|
-
user_agent=plugin_config.bililive_browser_ua or None,
|
|
141
|
-
timeout=plugin_config.bililive_dynamic_timeout,
|
|
142
|
-
)
|
|
143
|
-
return dynamics, True
|
|
144
|
-
|
|
151
|
+
dynamic_web_fallback_until[uid] = monotonic() + GRPC_RISK_CONTROL_RETRY_SECONDS
|
|
152
|
+
return await fetch_web_dynamics(), True
|
|
145
153
|
|
|
146
|
-
async def dy_sched():
|
|
147
|
-
"""动态推送"""
|
|
148
|
-
if not await db.wait_until_ready():
|
|
149
|
-
logger.debug("数据库尚未初始化完成,跳过本轮动态推送")
|
|
150
|
-
await throttle_dynamic_loop()
|
|
151
|
-
return
|
|
152
154
|
|
|
153
|
-
|
|
154
|
-
if not uid:
|
|
155
|
-
# 没有订阅先暂停一秒再跳过,不然会导致 CPU 占用过高
|
|
156
|
-
await throttle_dynamic_loop()
|
|
157
|
-
return
|
|
155
|
+
async def process_dynamic_uid(uid: int):
|
|
158
156
|
user = await db.get_user(uid=uid)
|
|
159
157
|
if user is None:
|
|
160
158
|
logger.warning(f"动态推送跳过异常订阅 UID:{uid}")
|
|
161
|
-
await throttle_dynamic_loop()
|
|
162
159
|
return
|
|
163
160
|
name = user.name
|
|
164
161
|
|
|
@@ -186,16 +183,19 @@ async def dy_sched():
|
|
|
186
183
|
return
|
|
187
184
|
except GrpcError as e:
|
|
188
185
|
logger.error(f"爬取动态失败:{e.code} {e.msg}")
|
|
189
|
-
await throttle_dynamic_loop()
|
|
190
186
|
return
|
|
191
187
|
except WebDynamicError as e:
|
|
192
|
-
|
|
193
|
-
|
|
188
|
+
retry_seconds = (
|
|
189
|
+
WEB_REQUEST_BANNED_RETRY_SECONDS
|
|
190
|
+
if e.code == -412
|
|
191
|
+
else WEB_REQUEST_ERROR_RETRY_SECONDS
|
|
192
|
+
)
|
|
193
|
+
dynamic_risk_control_until[uid] = monotonic() + retry_seconds
|
|
194
|
+
retry_minutes = max(retry_seconds // 60, 1)
|
|
194
195
|
logger.warning(
|
|
195
196
|
f"动态 Web 接口获取失败,{name}({uid})将在 "
|
|
196
197
|
f"{retry_minutes} 分钟后重试:{e.code} {e.msg}"
|
|
197
198
|
)
|
|
198
|
-
await throttle_dynamic_loop()
|
|
199
199
|
return
|
|
200
200
|
|
|
201
201
|
dynamic_risk_control_until.pop(uid, None)
|
|
@@ -256,6 +256,30 @@ async def dy_sched():
|
|
|
256
256
|
await db.update_user(uid, name)
|
|
257
257
|
|
|
258
258
|
|
|
259
|
+
async def dy_sched():
|
|
260
|
+
"""动态推送"""
|
|
261
|
+
if not await db.wait_until_ready():
|
|
262
|
+
logger.debug("数据库尚未初始化完成,跳过本轮动态推送")
|
|
263
|
+
await throttle_dynamic_loop()
|
|
264
|
+
return
|
|
265
|
+
|
|
266
|
+
uids = await db.get_uid_list("dynamic")
|
|
267
|
+
if not uids:
|
|
268
|
+
# 没有订阅先暂停一秒再跳过,不然会导致 CPU 占用过高
|
|
269
|
+
await throttle_dynamic_loop()
|
|
270
|
+
return
|
|
271
|
+
|
|
272
|
+
logger.debug(f"爬取动态列表,总共 {len(uids)} 人")
|
|
273
|
+
semaphore = asyncio.Semaphore(DYNAMIC_FETCH_CONCURRENCY)
|
|
274
|
+
|
|
275
|
+
async def run_for_uid(uid: int):
|
|
276
|
+
async with semaphore:
|
|
277
|
+
await process_dynamic_uid(uid)
|
|
278
|
+
|
|
279
|
+
await asyncio.gather(*(run_for_uid(uid) for uid in uids))
|
|
280
|
+
await throttle_dynamic_loop()
|
|
281
|
+
|
|
282
|
+
|
|
259
283
|
def dynamic_lisener(event):
|
|
260
284
|
if hasattr(event, "job_id") and event.job_id != "dynamic_sched":
|
|
261
285
|
return
|
|
@@ -277,4 +301,7 @@ else:
|
|
|
277
301
|
"interval",
|
|
278
302
|
seconds=plugin_config.bililive_dynamic_interval,
|
|
279
303
|
id="dynamic_sched",
|
|
304
|
+
coalesce=True,
|
|
305
|
+
max_instances=1,
|
|
306
|
+
misfire_grace_time=5,
|
|
280
307
|
)
|
|
@@ -30,15 +30,22 @@ from nonebot.rule import Rule
|
|
|
30
30
|
|
|
31
31
|
from ..config import plugin_config
|
|
32
32
|
|
|
33
|
+
require("nonebot_plugin_localstore")
|
|
34
|
+
import nonebot_plugin_localstore as store
|
|
33
35
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
+
PLUGIN_ENTRY_NAME = "nonebot_plugin_bililive"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def get_data_dir() -> Path:
|
|
40
|
+
"""获取插件数据目录。"""
|
|
36
41
|
if plugin_config.bililive_dir:
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
+
return Path(plugin_config.bililive_dir).resolve()
|
|
43
|
+
return store.get_data_dir(PLUGIN_ENTRY_NAME)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def get_path(*other):
|
|
47
|
+
"""获取插件数据文件绝对路径。"""
|
|
48
|
+
return str(get_data_dir().joinpath(*other))
|
|
42
49
|
|
|
43
50
|
|
|
44
51
|
async def handle_uid(
|
|
@@ -290,9 +297,7 @@ def on_startup():
|
|
|
290
297
|
check_proxy()
|
|
291
298
|
install()
|
|
292
299
|
asyncio.get_event_loop().run_until_complete(check_playwright_env())
|
|
293
|
-
|
|
294
|
-
if not Path(get_path()).is_dir():
|
|
295
|
-
Path(get_path()).mkdir(parents=True)
|
|
300
|
+
get_data_dir().mkdir(parents=True, exist_ok=True)
|
|
296
301
|
|
|
297
302
|
|
|
298
303
|
def on_command(cmd, *args, **kwargs):
|
|
@@ -304,4 +309,8 @@ PROXIES = {"all://": plugin_config.bililive_proxy}
|
|
|
304
309
|
require("nonebot_plugin_apscheduler")
|
|
305
310
|
from nonebot_plugin_apscheduler import scheduler # noqa
|
|
306
311
|
|
|
307
|
-
from .browser import
|
|
312
|
+
from .browser import ( # noqa
|
|
313
|
+
get_bilibili_cookies,
|
|
314
|
+
get_dynamic_screenshot,
|
|
315
|
+
get_user_dynamics_payload_in_browser,
|
|
316
|
+
)
|
|
@@ -16,6 +16,7 @@ from .fonts_provider import fill_font
|
|
|
16
16
|
|
|
17
17
|
_browser: BrowserContext | None = None
|
|
18
18
|
mobile_js = Path(__file__).parent.joinpath("mobile.js")
|
|
19
|
+
WEB_DYNAMIC_URL = "https://api.bilibili.com/x/polymer/web-dynamic/v1/feed/space"
|
|
19
20
|
|
|
20
21
|
|
|
21
22
|
async def init_browser(proxy=plugin_config.bililive_proxy, **kwargs) -> BrowserContext:
|
|
@@ -72,6 +73,32 @@ async def get_bilibili_cookies() -> dict[str, str]:
|
|
|
72
73
|
return {cookie["name"]: cookie["value"] for cookie in cookies}
|
|
73
74
|
|
|
74
75
|
|
|
76
|
+
async def get_user_dynamics_payload_in_browser(uid: int) -> dict:
|
|
77
|
+
browser = await get_browser()
|
|
78
|
+
page = await browser.new_page()
|
|
79
|
+
try:
|
|
80
|
+
await page.goto(
|
|
81
|
+
"https://www.bilibili.com/",
|
|
82
|
+
wait_until="domcontentloaded",
|
|
83
|
+
timeout=plugin_config.bililive_dynamic_timeout * 1000,
|
|
84
|
+
)
|
|
85
|
+
return await page.evaluate(
|
|
86
|
+
"""async ({ url, uid }) => {
|
|
87
|
+
const response = await fetch(`${url}?host_mid=${uid}`, {
|
|
88
|
+
credentials: 'include',
|
|
89
|
+
headers: {
|
|
90
|
+
accept: 'application/json, text/plain, */*',
|
|
91
|
+
},
|
|
92
|
+
});
|
|
93
|
+
return await response.json();
|
|
94
|
+
}""",
|
|
95
|
+
{"url": WEB_DYNAMIC_URL, "uid": str(uid)},
|
|
96
|
+
)
|
|
97
|
+
finally:
|
|
98
|
+
with contextlib.suppress(Exception):
|
|
99
|
+
await page.close()
|
|
100
|
+
|
|
101
|
+
|
|
75
102
|
async def get_dynamic_screenshot(
|
|
76
103
|
dynamic_id,
|
|
77
104
|
style=plugin_config.bililive_screenshot_style,
|
{nonebot_plugin_bililive-2.0.5 → nonebot_plugin_bililive-2.0.7}/nonebot_plugin_bililive/__init__.py
RENAMED
|
@@ -10,7 +10,7 @@ if isinstance(globals()["__loader__"], PluginLoader):
|
|
|
10
10
|
__plugin_meta__ = PluginMetadata(
|
|
11
11
|
name="BiliLive",
|
|
12
12
|
description="将 B 站 UP 主的动态和直播信息推送至 QQ",
|
|
13
|
-
usage="
|
|
13
|
+
usage="发送“帮助”查看命令列表,发送“关注 UID”订阅 UP 主",
|
|
14
14
|
homepage="https://github.com/Akiyy-dev/nonebot-plugin-bililive",
|
|
15
15
|
type="application",
|
|
16
16
|
config=Config,
|
|
@@ -10,13 +10,14 @@ keywords = [
|
|
|
10
10
|
"nonebot",
|
|
11
11
|
"nonebot2",
|
|
12
12
|
"nonebot-plugin",
|
|
13
|
-
"qqbot",
|
|
14
13
|
"bilibili",
|
|
14
|
+
"onebot",
|
|
15
|
+
"onebot-v11",
|
|
15
16
|
]
|
|
16
17
|
classifiers = [
|
|
17
18
|
"Development Status :: 4 - Beta",
|
|
18
|
-
"Framework ::
|
|
19
|
-
"
|
|
19
|
+
"Framework :: AsyncIO",
|
|
20
|
+
"Operating System :: OS Independent",
|
|
20
21
|
"Programming Language :: Python :: 3",
|
|
21
22
|
"Programming Language :: Python :: 3.10",
|
|
22
23
|
"Programming Language :: Python :: 3.11",
|
|
@@ -28,6 +29,7 @@ dependencies = [
|
|
|
28
29
|
"httpx>=0.28.1,<1.0",
|
|
29
30
|
"nonebot-adapter-onebot>=2.4.6",
|
|
30
31
|
"nonebot-plugin-apscheduler>=0.5.0",
|
|
32
|
+
"nonebot-plugin-localstore>=0.7",
|
|
31
33
|
"nonebot2[fastapi]>=2.5.0",
|
|
32
34
|
"playwright>=1.58.0",
|
|
33
35
|
"pydantic>=2.12.5,<3.0",
|
|
@@ -38,7 +40,7 @@ dependencies = [
|
|
|
38
40
|
"msvc-runtime>=14.34.31931; sys_platform == \"win32\"",
|
|
39
41
|
]
|
|
40
42
|
dynamic = []
|
|
41
|
-
version = "2.0.
|
|
43
|
+
version = "2.0.7"
|
|
42
44
|
|
|
43
45
|
[project.license]
|
|
44
46
|
text = "AGPL-3.0-or-later"
|
|
@@ -50,7 +52,6 @@ Documentation = "https://github.com/Akiyy-dev/nonebot-plugin-bililive#readme"
|
|
|
50
52
|
Issues = "https://github.com/Akiyy-dev/nonebot-plugin-bililive/issues"
|
|
51
53
|
|
|
52
54
|
[project.entry-points."nonebot.plugin"]
|
|
53
|
-
nonebot-plugin-bililive = "nonebot_plugin_bililive"
|
|
54
55
|
nonebot_plugin_bililive = "nonebot_plugin_bililive"
|
|
55
56
|
|
|
56
57
|
[project.scripts]
|
|
@@ -20,12 +20,39 @@ class DummyDriver:
|
|
|
20
20
|
|
|
21
21
|
|
|
22
22
|
fake_apscheduler = ModuleType("nonebot_plugin_apscheduler")
|
|
23
|
-
|
|
23
|
+
fake_localstore = ModuleType("nonebot_plugin_localstore")
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class DummyScheduler:
|
|
27
|
+
def add_listener(self, *args, **kwargs):
|
|
28
|
+
return None
|
|
29
|
+
|
|
30
|
+
def add_job(self, *args, **kwargs):
|
|
31
|
+
return None
|
|
32
|
+
|
|
33
|
+
def get_job(self, *args, **kwargs):
|
|
34
|
+
return None
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
fake_apscheduler.scheduler = DummyScheduler()
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _get_data_dir(plugin_name: str | None) -> Path:
|
|
41
|
+
return Path(tempfile.gettempdir()) / (plugin_name or "nonebot2")
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
fake_localstore.get_data_dir = _get_data_dir
|
|
24
45
|
|
|
25
46
|
|
|
26
47
|
with patch("nonebot.get_driver", return_value=DummyDriver()), patch(
|
|
27
48
|
"nonebot.require", return_value=None
|
|
28
|
-
), patch.dict(
|
|
49
|
+
), patch.dict(
|
|
50
|
+
sys.modules,
|
|
51
|
+
{
|
|
52
|
+
"nonebot_plugin_apscheduler": fake_apscheduler,
|
|
53
|
+
"nonebot_plugin_localstore": fake_localstore,
|
|
54
|
+
},
|
|
55
|
+
):
|
|
29
56
|
compat = import_module("bililive.compat")
|
|
30
57
|
Config = import_module("bililive.config").Config
|
|
31
58
|
core_version = import_module("bililive.version")
|
|
@@ -35,6 +62,7 @@ with patch("nonebot.get_driver", return_value=DummyDriver()), patch(
|
|
|
35
62
|
DB = db_module.DB
|
|
36
63
|
models = import_module("bililive.database.models")
|
|
37
64
|
Group = models.Group
|
|
65
|
+
get_path = import_module("bililive.utils").get_path
|
|
38
66
|
|
|
39
67
|
|
|
40
68
|
class ConfigTests(unittest.TestCase):
|
|
@@ -91,6 +119,11 @@ class PluginEntryTests(unittest.TestCase):
|
|
|
91
119
|
self.assertEqual(plugin_entry.__plugin_meta__.config, Config)
|
|
92
120
|
self.assertEqual(plugin_entry.__version__, core_version.__version__)
|
|
93
121
|
|
|
122
|
+
def test_default_data_dir_uses_localstore(self):
|
|
123
|
+
expected = _get_data_dir("nonebot_plugin_bililive") / "data.sqlite3"
|
|
124
|
+
|
|
125
|
+
self.assertEqual(Path(get_path("data.sqlite3")), expected)
|
|
126
|
+
|
|
94
127
|
|
|
95
128
|
class WebDynamicTests(unittest.TestCase):
|
|
96
129
|
def test_parse_web_dynamic_items_extracts_required_fields(self):
|
|
@@ -131,6 +164,18 @@ class WebDynamicTests(unittest.TestCase):
|
|
|
131
164
|
|
|
132
165
|
self.assertEqual(web_dynamic.parse_web_dynamic_items(payload), [])
|
|
133
166
|
|
|
167
|
+
def test_parse_web_dynamic_payload_raises_for_error_code(self):
|
|
168
|
+
payload = {
|
|
169
|
+
"code": -412,
|
|
170
|
+
"message": "request was banned",
|
|
171
|
+
"data": None,
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
with self.assertRaises(web_dynamic.WebDynamicError) as context:
|
|
175
|
+
web_dynamic.parse_web_dynamic_payload(payload)
|
|
176
|
+
|
|
177
|
+
self.assertEqual(context.exception.code, -412)
|
|
178
|
+
|
|
134
179
|
|
|
135
180
|
class DBPermissionTests(unittest.IsolatedAsyncioTestCase):
|
|
136
181
|
async def test_db_init_enables_global_fallback(self):
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{nonebot_plugin_bililive-2.0.5 → nonebot_plugin_bililive-2.0.7}/bililive/database/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{nonebot_plugin_bililive-2.0.5 → nonebot_plugin_bililive-2.0.7}/bililive/libs/dynamic/__init__.py
RENAMED
|
File without changes
|
{nonebot_plugin_bililive-2.0.5 → nonebot_plugin_bililive-2.0.7}/bililive/libs/dynamic/card.py
RENAMED
|
File without changes
|
{nonebot_plugin_bililive-2.0.5 → nonebot_plugin_bililive-2.0.7}/bililive/libs/dynamic/desc.py
RENAMED
|
File without changes
|
{nonebot_plugin_bililive-2.0.5 → nonebot_plugin_bililive-2.0.7}/bililive/libs/dynamic/display.py
RENAMED
|
File without changes
|
|
File without changes
|
{nonebot_plugin_bililive-2.0.5 → nonebot_plugin_bililive-2.0.7}/bililive/plugins/__init__.py
RENAMED
|
File without changes
|
{nonebot_plugin_bililive-2.0.5 → nonebot_plugin_bililive-2.0.7}/bililive/plugins/at/__init__.py
RENAMED
|
File without changes
|
{nonebot_plugin_bililive-2.0.5 → nonebot_plugin_bililive-2.0.7}/bililive/plugins/at/at_off.py
RENAMED
|
File without changes
|
{nonebot_plugin_bililive-2.0.5 → nonebot_plugin_bililive-2.0.7}/bililive/plugins/at/at_on.py
RENAMED
|
File without changes
|
{nonebot_plugin_bililive-2.0.5 → nonebot_plugin_bililive-2.0.7}/bililive/plugins/auto_agree.py
RENAMED
|
File without changes
|
{nonebot_plugin_bililive-2.0.5 → nonebot_plugin_bililive-2.0.7}/bililive/plugins/auto_delete.py
RENAMED
|
File without changes
|
{nonebot_plugin_bililive-2.0.5 → nonebot_plugin_bililive-2.0.7}/bililive/plugins/dynamic/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{nonebot_plugin_bililive-2.0.5 → nonebot_plugin_bililive-2.0.7}/bililive/plugins/live/__init__.py
RENAMED
|
File without changes
|
{nonebot_plugin_bililive-2.0.5 → nonebot_plugin_bililive-2.0.7}/bililive/plugins/live/live_now.py
RENAMED
|
File without changes
|
{nonebot_plugin_bililive-2.0.5 → nonebot_plugin_bililive-2.0.7}/bililive/plugins/live/live_off.py
RENAMED
|
File without changes
|
{nonebot_plugin_bililive-2.0.5 → nonebot_plugin_bililive-2.0.7}/bililive/plugins/live/live_on.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{nonebot_plugin_bililive-2.0.5 → nonebot_plugin_bililive-2.0.7}/bililive/plugins/pusher/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{nonebot_plugin_bililive-2.0.5 → nonebot_plugin_bililive-2.0.7}/bililive/plugins/sub/__init__.py
RENAMED
|
File without changes
|
{nonebot_plugin_bililive-2.0.5 → nonebot_plugin_bililive-2.0.7}/bililive/plugins/sub/add_sub.py
RENAMED
|
File without changes
|
{nonebot_plugin_bililive-2.0.5 → nonebot_plugin_bililive-2.0.7}/bililive/plugins/sub/delete_sub.py
RENAMED
|
File without changes
|
{nonebot_plugin_bililive-2.0.5 → nonebot_plugin_bililive-2.0.7}/bililive/plugins/sub/sub_list.py
RENAMED
|
File without changes
|
{nonebot_plugin_bililive-2.0.5 → nonebot_plugin_bililive-2.0.7}/bililive/utils/captcha_solver.py
RENAMED
|
File without changes
|
{nonebot_plugin_bililive-2.0.5 → nonebot_plugin_bililive-2.0.7}/bililive/utils/fonts_provider.py
RENAMED
|
File without changes
|
|
File without changes
|