nonebot-plugin-bililive 2.0.9__tar.gz → 2.1.1__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.9 → nonebot_plugin_bililive-2.1.1}/PKG-INFO +6 -8
- {nonebot_plugin_bililive-2.0.9 → nonebot_plugin_bililive-2.1.1}/README.md +2 -3
- nonebot_plugin_bililive-2.1.1/nonebot-plugin-bililive/__init__.py +3 -0
- {nonebot_plugin_bililive-2.0.9/bililive → nonebot_plugin_bililive-2.1.1/nonebot_plugin_bililive}/__init__.py +9 -6
- nonebot_plugin_bililive-2.1.1/nonebot_plugin_bililive/bilibili_api.py +57 -0
- {nonebot_plugin_bililive-2.0.9/bililive → nonebot_plugin_bililive-2.1.1/nonebot_plugin_bililive}/cli/bot.py +3 -2
- nonebot_plugin_bililive-2.1.1/nonebot_plugin_bililive/config.py +90 -0
- {nonebot_plugin_bililive-2.0.9/bililive → nonebot_plugin_bililive-2.1.1/nonebot_plugin_bililive}/database/db.py +2 -3
- {nonebot_plugin_bililive-2.0.9/bililive → nonebot_plugin_bililive-2.1.1/nonebot_plugin_bililive}/plugins/help.py +1 -1
- {nonebot_plugin_bililive-2.0.9/bililive → nonebot_plugin_bililive-2.1.1/nonebot_plugin_bililive}/plugins/pusher/dynamic_pusher.py +22 -87
- {nonebot_plugin_bililive-2.0.9/bililive → nonebot_plugin_bililive-2.1.1/nonebot_plugin_bililive}/plugins/pusher/live_pusher.py +4 -4
- {nonebot_plugin_bililive-2.0.9/bililive → nonebot_plugin_bililive-2.1.1/nonebot_plugin_bililive}/plugins/sub/add_sub.py +3 -25
- {nonebot_plugin_bililive-2.0.9/bililive → nonebot_plugin_bililive-2.1.1/nonebot_plugin_bililive}/utils/__init__.py +14 -20
- {nonebot_plugin_bililive-2.0.9/bililive → nonebot_plugin_bililive-2.1.1/nonebot_plugin_bililive}/utils/browser.py +1 -1
- {nonebot_plugin_bililive-2.0.9/bililive → nonebot_plugin_bililive-2.1.1/nonebot_plugin_bililive}/utils/captcha_solver.py +1 -1
- {nonebot_plugin_bililive-2.0.9/bililive → nonebot_plugin_bililive-2.1.1/nonebot_plugin_bililive}/utils/fonts_provider.py +1 -1
- {nonebot_plugin_bililive-2.0.9/bililive → nonebot_plugin_bililive-2.1.1/nonebot_plugin_bililive}/version.py +1 -1
- {nonebot_plugin_bililive-2.0.9 → nonebot_plugin_bililive-2.1.1}/pyproject.toml +6 -8
- {nonebot_plugin_bililive-2.0.9 → nonebot_plugin_bililive-2.1.1}/tests/test_maintenance.py +25 -55
- nonebot_plugin_bililive-2.0.9/bililive/compat.py +0 -42
- nonebot_plugin_bililive-2.0.9/bililive/config.py +0 -123
- nonebot_plugin_bililive-2.0.9/nonebot-plugin-bililive/__init__.py +0 -1
- nonebot_plugin_bililive-2.0.9/nonebot_plugin_bililive/__init__.py +0 -25
- {nonebot_plugin_bililive-2.0.9 → nonebot_plugin_bililive-2.1.1}/LICENSE +0 -0
- {nonebot_plugin_bililive-2.0.9/bililive → nonebot_plugin_bililive-2.1.1/nonebot_plugin_bililive}/__main__.py +0 -0
- {nonebot_plugin_bililive-2.0.9/bililive → nonebot_plugin_bililive-2.1.1/nonebot_plugin_bililive}/cli/__init__.py +0 -0
- {nonebot_plugin_bililive-2.0.9/bililive → nonebot_plugin_bililive-2.1.1/nonebot_plugin_bililive}/cli/utils.py +0 -0
- {nonebot_plugin_bililive-2.0.9/bililive → nonebot_plugin_bililive-2.1.1/nonebot_plugin_bililive}/database/__init__.py +0 -0
- {nonebot_plugin_bililive-2.0.9/bililive → nonebot_plugin_bililive-2.1.1/nonebot_plugin_bililive}/database/models.py +0 -0
- {nonebot_plugin_bililive-2.0.9/bililive → nonebot_plugin_bililive-2.1.1/nonebot_plugin_bililive}/libs/__init__.py +0 -0
- {nonebot_plugin_bililive-2.0.9/bililive → nonebot_plugin_bililive-2.1.1/nonebot_plugin_bililive}/libs/dynamic/__init__.py +0 -0
- {nonebot_plugin_bililive-2.0.9/bililive → nonebot_plugin_bililive-2.1.1/nonebot_plugin_bililive}/libs/dynamic/card.py +0 -0
- {nonebot_plugin_bililive-2.0.9/bililive → nonebot_plugin_bililive-2.1.1/nonebot_plugin_bililive}/libs/dynamic/desc.py +0 -0
- {nonebot_plugin_bililive-2.0.9/bililive → nonebot_plugin_bililive-2.1.1/nonebot_plugin_bililive}/libs/dynamic/display.py +0 -0
- {nonebot_plugin_bililive-2.0.9/bililive → nonebot_plugin_bililive-2.1.1/nonebot_plugin_bililive}/libs/dynamic/user_profile.py +0 -0
- {nonebot_plugin_bililive-2.0.9/bililive → nonebot_plugin_bililive-2.1.1/nonebot_plugin_bililive}/libs/dynamic/web.py +0 -0
- {nonebot_plugin_bililive-2.0.9/bililive → nonebot_plugin_bililive-2.1.1/nonebot_plugin_bililive}/plugins/__init__.py +0 -0
- {nonebot_plugin_bililive-2.0.9/bililive → nonebot_plugin_bililive-2.1.1/nonebot_plugin_bililive}/plugins/at/__init__.py +0 -0
- {nonebot_plugin_bililive-2.0.9/bililive → nonebot_plugin_bililive-2.1.1/nonebot_plugin_bililive}/plugins/at/at_off.py +0 -0
- {nonebot_plugin_bililive-2.0.9/bililive → nonebot_plugin_bililive-2.1.1/nonebot_plugin_bililive}/plugins/at/at_on.py +0 -0
- {nonebot_plugin_bililive-2.0.9/bililive → nonebot_plugin_bililive-2.1.1/nonebot_plugin_bililive}/plugins/auto_agree.py +0 -0
- {nonebot_plugin_bililive-2.0.9/bililive → nonebot_plugin_bililive-2.1.1/nonebot_plugin_bililive}/plugins/auto_delete.py +0 -0
- {nonebot_plugin_bililive-2.0.9/bililive → nonebot_plugin_bililive-2.1.1/nonebot_plugin_bililive}/plugins/dynamic/__init__.py +0 -0
- {nonebot_plugin_bililive-2.0.9/bililive → nonebot_plugin_bililive-2.1.1/nonebot_plugin_bililive}/plugins/dynamic/dynamic_off.py +0 -0
- {nonebot_plugin_bililive-2.0.9/bililive → nonebot_plugin_bililive-2.1.1/nonebot_plugin_bililive}/plugins/dynamic/dynamic_on.py +0 -0
- {nonebot_plugin_bililive-2.0.9/bililive → nonebot_plugin_bililive-2.1.1/nonebot_plugin_bililive}/plugins/live/__init__.py +0 -0
- {nonebot_plugin_bililive-2.0.9/bililive → nonebot_plugin_bililive-2.1.1/nonebot_plugin_bililive}/plugins/live/live_now.py +0 -0
- {nonebot_plugin_bililive-2.0.9/bililive → nonebot_plugin_bililive-2.1.1/nonebot_plugin_bililive}/plugins/live/live_off.py +0 -0
- {nonebot_plugin_bililive-2.0.9/bililive → nonebot_plugin_bililive-2.1.1/nonebot_plugin_bililive}/plugins/live/live_on.py +0 -0
- {nonebot_plugin_bililive-2.0.9/bililive → nonebot_plugin_bililive-2.1.1/nonebot_plugin_bililive}/plugins/permission/__init__.py +0 -0
- {nonebot_plugin_bililive-2.0.9/bililive → nonebot_plugin_bililive-2.1.1/nonebot_plugin_bililive}/plugins/permission/permission_off.py +0 -0
- {nonebot_plugin_bililive-2.0.9/bililive → nonebot_plugin_bililive-2.1.1/nonebot_plugin_bililive}/plugins/permission/permission_on.py +0 -0
- {nonebot_plugin_bililive-2.0.9/bililive → nonebot_plugin_bililive-2.1.1/nonebot_plugin_bililive}/plugins/pusher/__init__.py +0 -0
- {nonebot_plugin_bililive-2.0.9/bililive → nonebot_plugin_bililive-2.1.1/nonebot_plugin_bililive}/plugins/sub/__init__.py +0 -0
- {nonebot_plugin_bililive-2.0.9/bililive → nonebot_plugin_bililive-2.1.1/nonebot_plugin_bililive}/plugins/sub/delete_sub.py +0 -0
- {nonebot_plugin_bililive-2.0.9/bililive → nonebot_plugin_bililive-2.1.1/nonebot_plugin_bililive}/plugins/sub/sub_list.py +0 -0
- {nonebot_plugin_bililive-2.0.9/bililive → nonebot_plugin_bililive-2.1.1/nonebot_plugin_bililive}/utils/mobile.js +0 -0
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: nonebot-plugin-bililive
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.1.1
|
|
4
4
|
Summary: Push bilibili dynamics and live notifications to QQ with NoneBot2.
|
|
5
5
|
Keywords: nonebot,nonebot2,nonebot-plugin,bilibili,onebot,onebot-v11
|
|
6
|
-
Author-Email:
|
|
6
|
+
Author-Email: Akiyy_Lab <2806578374@qq.com>
|
|
7
7
|
License: AGPL-3.0-or-later
|
|
8
8
|
Classifier: Development Status :: 4 - Beta
|
|
9
9
|
Classifier: Framework :: AsyncIO
|
|
@@ -23,12 +23,11 @@ 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
25
|
Requires-Dist: nonebot-plugin-localstore>=0.7
|
|
26
|
-
Requires-Dist: nonebot2
|
|
26
|
+
Requires-Dist: nonebot2>=2.5.0
|
|
27
27
|
Requires-Dist: playwright>=1.58.0
|
|
28
|
-
Requires-Dist: pydantic<3.0,>=
|
|
28
|
+
Requires-Dist: pydantic<3.0,>=1.10.0
|
|
29
29
|
Requires-Dist: python-dotenv>=1.2.2
|
|
30
30
|
Requires-Dist: tortoise-orm[asyncpg]>=0.19.3
|
|
31
|
-
Requires-Dist: bilireq>=0.2.13
|
|
32
31
|
Requires-Dist: packaging>=26.0
|
|
33
32
|
Requires-Dist: msvc-runtime>=14.34.31931; sys_platform == "win32"
|
|
34
33
|
Description-Content-Type: text/markdown
|
|
@@ -95,7 +94,7 @@ BiliLive 是一个基于 NoneBot2 的 B 站推送插件,支持将 UP 主的直
|
|
|
95
94
|
|
|
96
95
|
</details>
|
|
97
96
|
|
|
98
|
-
首次使用动态截图功能时,插件会自动检查并安装 Chromium
|
|
97
|
+
首次使用动态截图功能时,插件会自动检查并安装 Chromium。插件数据目录统一由 nonebot-plugin-localstore 提供,无需再单独配置数据目录。
|
|
99
98
|
|
|
100
99
|
## ⚙️ 配置
|
|
101
100
|
|
|
@@ -103,7 +102,6 @@ BiliLive 是一个基于 NoneBot2 的 B 站推送插件,支持将 UP 主的直
|
|
|
103
102
|
|
|
104
103
|
| 配置项 | 必填 | 默认值 | 说明 |
|
|
105
104
|
|:-----:|:----:|:----:|:----|
|
|
106
|
-
| BILILIVE_DIR | 否 | 由 nonebot-plugin-localstore 自动分配 | 插件数据目录,包含数据库、浏览器数据与动态偏移缓存 |
|
|
107
105
|
| BILILIVE_TO_ME | 否 | true | 是否需要 @机器人 或命令前缀触发 |
|
|
108
106
|
| BILILIVE_PROXY | 否 | 无 | HTTP 代理地址,用于 B 站请求和 Playwright 下载 |
|
|
109
107
|
| BILILIVE_INTERVAL | 否 | 10 | 默认轮询间隔,单位秒 |
|
|
@@ -119,7 +117,7 @@ BiliLive 是一个基于 NoneBot2 的 B 站推送插件,支持将 UP 主的直
|
|
|
119
117
|
| BILILIVE_DYNAMIC_BIG_IMAGE | 否 | false | 是否优先展示大图 |
|
|
120
118
|
| BILILIVE_COMMAND_PREFIX | 否 | 空字符串 | 命令额外前缀 |
|
|
121
119
|
|
|
122
|
-
|
|
120
|
+
动态抓取使用 Playwright 持久化浏览器中的 cookies 请求网页动态接口。通常不需要额外配置 Cookie 登录;如果某些 UID 持续抓取失败,建议在插件使用的浏览器数据目录中登录一个常用的 B 站账号,以提高动态抓取成功率。
|
|
123
121
|
|
|
124
122
|
## 🎉 使用
|
|
125
123
|
|
|
@@ -60,7 +60,7 @@ BiliLive 是一个基于 NoneBot2 的 B 站推送插件,支持将 UP 主的直
|
|
|
60
60
|
|
|
61
61
|
</details>
|
|
62
62
|
|
|
63
|
-
首次使用动态截图功能时,插件会自动检查并安装 Chromium
|
|
63
|
+
首次使用动态截图功能时,插件会自动检查并安装 Chromium。插件数据目录统一由 nonebot-plugin-localstore 提供,无需再单独配置数据目录。
|
|
64
64
|
|
|
65
65
|
## ⚙️ 配置
|
|
66
66
|
|
|
@@ -68,7 +68,6 @@ BiliLive 是一个基于 NoneBot2 的 B 站推送插件,支持将 UP 主的直
|
|
|
68
68
|
|
|
69
69
|
| 配置项 | 必填 | 默认值 | 说明 |
|
|
70
70
|
|:-----:|:----:|:----:|:----|
|
|
71
|
-
| BILILIVE_DIR | 否 | 由 nonebot-plugin-localstore 自动分配 | 插件数据目录,包含数据库、浏览器数据与动态偏移缓存 |
|
|
72
71
|
| BILILIVE_TO_ME | 否 | true | 是否需要 @机器人 或命令前缀触发 |
|
|
73
72
|
| BILILIVE_PROXY | 否 | 无 | HTTP 代理地址,用于 B 站请求和 Playwright 下载 |
|
|
74
73
|
| BILILIVE_INTERVAL | 否 | 10 | 默认轮询间隔,单位秒 |
|
|
@@ -84,7 +83,7 @@ BiliLive 是一个基于 NoneBot2 的 B 站推送插件,支持将 UP 主的直
|
|
|
84
83
|
| BILILIVE_DYNAMIC_BIG_IMAGE | 否 | false | 是否优先展示大图 |
|
|
85
84
|
| BILILIVE_COMMAND_PREFIX | 否 | 空字符串 | 命令额外前缀 |
|
|
86
85
|
|
|
87
|
-
|
|
86
|
+
动态抓取使用 Playwright 持久化浏览器中的 cookies 请求网页动态接口。通常不需要额外配置 Cookie 登录;如果某些 UID 持续抓取失败,建议在插件使用的浏览器数据目录中登录一个常用的 B 站账号,以提高动态抓取成功率。
|
|
88
87
|
|
|
89
88
|
## 🎉 使用
|
|
90
89
|
|
|
@@ -1,10 +1,13 @@
|
|
|
1
|
+
from nonebot import require
|
|
1
2
|
from nonebot.plugin import PluginMetadata
|
|
2
3
|
from nonebot.plugin.manager import PluginLoader
|
|
3
4
|
|
|
4
|
-
from .
|
|
5
|
-
from .
|
|
5
|
+
from .config import Config, plugin_config
|
|
6
|
+
from .version import VERSION, __version__
|
|
7
|
+
|
|
8
|
+
require("nonebot_plugin_localstore")
|
|
9
|
+
require("nonebot_plugin_apscheduler")
|
|
6
10
|
|
|
7
|
-
patch_httpx_compat()
|
|
8
11
|
|
|
9
12
|
def bootstrap_plugin(force: bool = False):
|
|
10
13
|
if force or isinstance(globals()["__loader__"], PluginLoader):
|
|
@@ -17,8 +20,6 @@ def bootstrap_plugin(force: bool = False):
|
|
|
17
20
|
|
|
18
21
|
bootstrap_plugin()
|
|
19
22
|
|
|
20
|
-
from .version import VERSION, __version__ # noqa: F401
|
|
21
|
-
|
|
22
23
|
__plugin_meta__ = PluginMetadata(
|
|
23
24
|
name="BiliLive",
|
|
24
25
|
description="将B站UP主的动态和直播信息推送至QQ",
|
|
@@ -28,8 +29,10 @@ __plugin_meta__ = PluginMetadata(
|
|
|
28
29
|
config=Config,
|
|
29
30
|
supported_adapters={"~onebot.v11"},
|
|
30
31
|
extra={
|
|
31
|
-
"author": "
|
|
32
|
+
"author": "Akiyy_Lab",
|
|
32
33
|
"version": __version__,
|
|
33
34
|
"priority": 1,
|
|
34
35
|
},
|
|
35
36
|
)
|
|
37
|
+
|
|
38
|
+
__all__ = ["__plugin_meta__", "__version__", "VERSION", "plugin_config"]
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import httpx
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class BilibiliAPIError(RuntimeError):
|
|
5
|
+
def __init__(self, code: int, message: str, data=None):
|
|
6
|
+
super().__init__(f"{code} {message}")
|
|
7
|
+
self.code = code
|
|
8
|
+
self.message = message
|
|
9
|
+
self.data = data
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
async def request_bilibili_api(
|
|
13
|
+
method: str,
|
|
14
|
+
url: str,
|
|
15
|
+
*,
|
|
16
|
+
proxy: str | None = None,
|
|
17
|
+
headers: dict[str, str] | None = None,
|
|
18
|
+
timeout: int = 20,
|
|
19
|
+
**kwargs,
|
|
20
|
+
):
|
|
21
|
+
request_headers = {
|
|
22
|
+
"User-Agent": "Mozilla/5.0",
|
|
23
|
+
"Referer": "https://www.bilibili.com/",
|
|
24
|
+
}
|
|
25
|
+
if headers:
|
|
26
|
+
request_headers.update(headers)
|
|
27
|
+
|
|
28
|
+
async with httpx.AsyncClient(
|
|
29
|
+
proxy=proxy,
|
|
30
|
+
headers=request_headers,
|
|
31
|
+
timeout=timeout,
|
|
32
|
+
follow_redirects=True,
|
|
33
|
+
) as client:
|
|
34
|
+
response = await client.request(method, url, **kwargs)
|
|
35
|
+
|
|
36
|
+
payload = response.json()
|
|
37
|
+
if payload.get("code") != 0:
|
|
38
|
+
raise BilibiliAPIError(
|
|
39
|
+
payload.get("code", -1),
|
|
40
|
+
payload.get("message") or payload.get("msg") or "unknown error",
|
|
41
|
+
payload.get("data"),
|
|
42
|
+
)
|
|
43
|
+
return payload.get("data")
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
async def get_live_rooms_info_by_uids(
|
|
47
|
+
uids: list[int],
|
|
48
|
+
*,
|
|
49
|
+
proxy: str | None = None,
|
|
50
|
+
) -> dict[str, dict]:
|
|
51
|
+
data = await request_bilibili_api(
|
|
52
|
+
"POST",
|
|
53
|
+
"https://api.live.bilibili.com/room/v1/Room/get_status_info_by_uids",
|
|
54
|
+
proxy=proxy,
|
|
55
|
+
json={"uids": uids},
|
|
56
|
+
)
|
|
57
|
+
return data or {}
|
|
@@ -2,8 +2,9 @@ import sys
|
|
|
2
2
|
from os import path
|
|
3
3
|
|
|
4
4
|
import nonebot
|
|
5
|
+
from nonebot import logger
|
|
5
6
|
from nonebot.adapters.onebot.v11 import Adapter
|
|
6
|
-
from nonebot.log import default_format
|
|
7
|
+
from nonebot.log import default_format
|
|
7
8
|
|
|
8
9
|
nonebot.init()
|
|
9
10
|
driver = nonebot.get_driver()
|
|
@@ -35,4 +36,4 @@ logger.add(
|
|
|
35
36
|
|
|
36
37
|
|
|
37
38
|
def run():
|
|
38
|
-
nonebot.run(app="
|
|
39
|
+
nonebot.run(app="nonebot_plugin_bililive.cli.bot:app")
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
|
|
2
|
+
from collections.abc import Mapping
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from nonebot import get_plugin_config, logger
|
|
6
|
+
from nonebot.compat import PYDANTIC_V2, ConfigDict, field_validator, model_validator
|
|
7
|
+
from pydantic import BaseModel
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
# 其他地方出现的类似 from .. import config,均是从 __init__.py 导入的 Config 实例
|
|
11
|
+
class Config(BaseModel):
|
|
12
|
+
if PYDANTIC_V2:
|
|
13
|
+
model_config = ConfigDict(extra="ignore")
|
|
14
|
+
else:
|
|
15
|
+
|
|
16
|
+
class Config:
|
|
17
|
+
extra = "ignore"
|
|
18
|
+
|
|
19
|
+
fastapi_reload: bool = False
|
|
20
|
+
bililive_to_me: bool = True
|
|
21
|
+
bililive_live_off_notify: bool = False
|
|
22
|
+
bililive_proxy: str | None = None
|
|
23
|
+
bililive_interval: int = 10
|
|
24
|
+
bililive_live_interval: int = 10
|
|
25
|
+
bililive_dynamic_interval: int = 0
|
|
26
|
+
bililive_dynamic_at: bool = False
|
|
27
|
+
bililive_screenshot_style: str = "mobile"
|
|
28
|
+
bililive_captcha_address: str = "https://captcha-cd.ngworks.cn"
|
|
29
|
+
bililive_captcha_token: str = "bililive"
|
|
30
|
+
bililive_browser_ua: str | None = None
|
|
31
|
+
bililive_dynamic_timeout: int = 30
|
|
32
|
+
bililive_dynamic_font_source: str = "system"
|
|
33
|
+
bililive_dynamic_font: str | None = "Noto Sans CJK SC"
|
|
34
|
+
bililive_dynamic_big_image: bool = False
|
|
35
|
+
bililive_command_prefix: str = ""
|
|
36
|
+
|
|
37
|
+
@model_validator(mode="before")
|
|
38
|
+
@classmethod
|
|
39
|
+
def migrate_legacy_keys(cls, values: Any):
|
|
40
|
+
if not isinstance(values, Mapping):
|
|
41
|
+
return values
|
|
42
|
+
|
|
43
|
+
data = dict(values)
|
|
44
|
+
for legacy_key, new_key in {
|
|
45
|
+
"haruka_to_me": "bililive_to_me",
|
|
46
|
+
"haruka_live_off_notify": "bililive_live_off_notify",
|
|
47
|
+
"haruka_proxy": "bililive_proxy",
|
|
48
|
+
"haruka_interval": "bililive_interval",
|
|
49
|
+
"haruka_live_interval": "bililive_live_interval",
|
|
50
|
+
"haruka_dynamic_interval": "bililive_dynamic_interval",
|
|
51
|
+
"haruka_dynamic_at": "bililive_dynamic_at",
|
|
52
|
+
"haruka_screenshot_style": "bililive_screenshot_style",
|
|
53
|
+
"haruka_captcha_address": "bililive_captcha_address",
|
|
54
|
+
"haruka_captcha_token": "bililive_captcha_token",
|
|
55
|
+
"haruka_browser_ua": "bililive_browser_ua",
|
|
56
|
+
"haruka_dynamic_timeout": "bililive_dynamic_timeout",
|
|
57
|
+
"haruka_dynamic_font_source": "bililive_dynamic_font_source",
|
|
58
|
+
"haruka_dynamic_font": "bililive_dynamic_font",
|
|
59
|
+
"haruka_dynamic_big_image": "bililive_dynamic_big_image",
|
|
60
|
+
"haruka_command_prefix": "bililive_command_prefix",
|
|
61
|
+
}.items():
|
|
62
|
+
if new_key not in data and legacy_key in data:
|
|
63
|
+
data[new_key] = data[legacy_key]
|
|
64
|
+
|
|
65
|
+
return data
|
|
66
|
+
|
|
67
|
+
@field_validator("bililive_interval")
|
|
68
|
+
@classmethod
|
|
69
|
+
def interval_non_negative(cls, value: int):
|
|
70
|
+
return 10 if value < 1 else value
|
|
71
|
+
|
|
72
|
+
@field_validator("bililive_live_interval")
|
|
73
|
+
@classmethod
|
|
74
|
+
def live_interval_non_negative(cls, value: int):
|
|
75
|
+
return 10 if value < 1 else value
|
|
76
|
+
|
|
77
|
+
@field_validator("bililive_dynamic_interval")
|
|
78
|
+
@classmethod
|
|
79
|
+
def dynamic_interval_non_negative(cls, value: int):
|
|
80
|
+
return 0 if value < 1 else value
|
|
81
|
+
|
|
82
|
+
@field_validator("bililive_screenshot_style")
|
|
83
|
+
@classmethod
|
|
84
|
+
def screenshot_style(cls, value: str):
|
|
85
|
+
if value != "mobile":
|
|
86
|
+
logger.warning("截图样式目前只支持 mobile,pc 样式现已被弃用")
|
|
87
|
+
return "mobile"
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
plugin_config = get_plugin_config(Config)
|
|
@@ -2,8 +2,7 @@ import asyncio
|
|
|
2
2
|
import json
|
|
3
3
|
from pathlib import Path
|
|
4
4
|
|
|
5
|
-
from nonebot import get_driver
|
|
6
|
-
from nonebot.log import logger
|
|
5
|
+
from nonebot import get_driver, logger
|
|
7
6
|
from packaging.version import Version as version_parser
|
|
8
7
|
from tortoise import Tortoise
|
|
9
8
|
from tortoise.connection import connections
|
|
@@ -39,7 +38,7 @@ class DB:
|
|
|
39
38
|
},
|
|
40
39
|
"apps": {
|
|
41
40
|
"bililive_app": {
|
|
42
|
-
"models": ["
|
|
41
|
+
"models": ["nonebot_plugin_bililive.database.models"],
|
|
43
42
|
"default_connection": "bililive",
|
|
44
43
|
}
|
|
45
44
|
},
|
|
@@ -8,13 +8,8 @@ from apscheduler.events import (
|
|
|
8
8
|
EVENT_JOB_MISSED,
|
|
9
9
|
EVENT_SCHEDULER_STARTED,
|
|
10
10
|
)
|
|
11
|
-
from
|
|
12
|
-
from bilireq.grpc.dynamic import grpc_get_user_dynamics
|
|
13
|
-
from bilireq.grpc.protos.bilibili.app.dynamic.v2.dynamic_pb2 import DynamicType
|
|
14
|
-
from grpc import StatusCode
|
|
15
|
-
from grpc.aio import AioRpcError
|
|
11
|
+
from nonebot import logger
|
|
16
12
|
from nonebot.adapters.onebot.v11.message import MessageSegment
|
|
17
|
-
from nonebot.log import logger
|
|
18
13
|
|
|
19
14
|
from ...config import plugin_config
|
|
20
15
|
from ...database import DB as db
|
|
@@ -32,11 +27,9 @@ from ...utils import (
|
|
|
32
27
|
scheduler,
|
|
33
28
|
)
|
|
34
29
|
|
|
35
|
-
GRPC_RISK_CONTROL_RETRY_SECONDS = 3600
|
|
36
30
|
WEB_REQUEST_BANNED_RETRY_SECONDS = 300
|
|
37
31
|
WEB_REQUEST_ERROR_RETRY_SECONDS = 600
|
|
38
32
|
dynamic_risk_control_until = {}
|
|
39
|
-
dynamic_web_fallback_until = {}
|
|
40
33
|
WEB_SKIP_DYNAMIC_TYPES = {
|
|
41
34
|
"DYNAMIC_TYPE_LIVE_RCMD",
|
|
42
35
|
"DYNAMIC_TYPE_LIVE",
|
|
@@ -60,96 +53,48 @@ async def throttle_dynamic_loop():
|
|
|
60
53
|
|
|
61
54
|
|
|
62
55
|
def get_dynamic_id(dynamic, use_web_fallback: bool) -> int:
|
|
63
|
-
|
|
64
|
-
return dynamic.dynamic_id
|
|
65
|
-
return int(dynamic.extend.dyn_id_str)
|
|
56
|
+
return dynamic.dynamic_id
|
|
66
57
|
|
|
67
58
|
|
|
68
59
|
def get_dynamic_type(dynamic, use_web_fallback: bool):
|
|
69
|
-
|
|
70
|
-
return dynamic.dynamic_type
|
|
71
|
-
return dynamic.card_type
|
|
60
|
+
return dynamic.dynamic_type
|
|
72
61
|
|
|
73
62
|
|
|
74
63
|
def get_dynamic_author_name(dynamic, use_web_fallback: bool) -> str:
|
|
75
|
-
|
|
76
|
-
return dynamic.author_name
|
|
77
|
-
return dynamic.modules[0].module_author.author.name
|
|
64
|
+
return dynamic.author_name
|
|
78
65
|
|
|
79
66
|
|
|
80
67
|
def get_dynamic_type_message(dynamic_type, use_web_fallback: bool) -> str:
|
|
81
|
-
|
|
82
|
-
return WEB_DYNAMIC_TYPE_MESSAGES.get(dynamic_type, "发布了新动态")
|
|
83
|
-
return {
|
|
84
|
-
0: "发布了新动态",
|
|
85
|
-
DynamicType.forward: "转发了一条动态",
|
|
86
|
-
DynamicType.word: "发布了新文字动态",
|
|
87
|
-
DynamicType.draw: "发布了新图文动态",
|
|
88
|
-
DynamicType.av: "发布了新投稿",
|
|
89
|
-
DynamicType.article: "发布了新专栏",
|
|
90
|
-
DynamicType.music: "发布了新音频",
|
|
91
|
-
}.get(dynamic_type, "发布了新动态")
|
|
68
|
+
return WEB_DYNAMIC_TYPE_MESSAGES.get(dynamic_type, "发布了新动态")
|
|
92
69
|
|
|
93
70
|
|
|
94
71
|
def should_skip_dynamic(dynamic_type, use_web_fallback: bool) -> bool:
|
|
95
|
-
|
|
96
|
-
return dynamic_type in WEB_SKIP_DYNAMIC_TYPES
|
|
97
|
-
return dynamic_type in [
|
|
98
|
-
DynamicType.live_rcmd,
|
|
99
|
-
DynamicType.live,
|
|
100
|
-
DynamicType.ad,
|
|
101
|
-
DynamicType.banner,
|
|
102
|
-
]
|
|
72
|
+
return dynamic_type in WEB_SKIP_DYNAMIC_TYPES
|
|
103
73
|
|
|
104
74
|
|
|
105
75
|
async def get_user_dynamics_with_web_fallback(uid: int) -> tuple[list, bool]:
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
)
|
|
76
|
+
try:
|
|
77
|
+
payload = await get_user_dynamics_payload_in_browser(uid)
|
|
78
|
+
return parse_web_dynamic_payload(payload), True
|
|
79
|
+
except WebDynamicError as browser_error:
|
|
80
|
+
logger.debug(
|
|
81
|
+
f"浏览器上下文动态接口获取失败,尝试直连 Web API:{uid} "
|
|
82
|
+
f"{browser_error.code} {browser_error.msg}"
|
|
83
|
+
)
|
|
115
84
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
85
|
+
cookies = await get_bilibili_cookies()
|
|
86
|
+
if not cookies:
|
|
87
|
+
raise WebDynamicError(-1, "browser cookies unavailable")
|
|
88
|
+
return (
|
|
89
|
+
await get_user_dynamics_web(
|
|
120
90
|
uid,
|
|
121
91
|
cookies,
|
|
122
92
|
proxy=plugin_config.bililive_proxy,
|
|
123
93
|
user_agent=plugin_config.bililive_browser_ua or None,
|
|
124
94
|
timeout=plugin_config.bililive_dynamic_timeout,
|
|
125
|
-
)
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
if fallback_until is not None:
|
|
129
|
-
if fallback_until > monotonic():
|
|
130
|
-
logger.debug(f"动态 gRPC 接口仍在风控,继续使用 Web 接口:{uid}")
|
|
131
|
-
return await fetch_web_dynamics(), True
|
|
132
|
-
del dynamic_web_fallback_until[uid]
|
|
133
|
-
|
|
134
|
-
try:
|
|
135
|
-
dynamics = (
|
|
136
|
-
await grpc_get_user_dynamics(
|
|
137
|
-
uid,
|
|
138
|
-
timeout=plugin_config.bililive_dynamic_timeout,
|
|
139
|
-
proxy=plugin_config.bililive_proxy,
|
|
140
|
-
)
|
|
141
|
-
).list
|
|
142
|
-
dynamic_web_fallback_until.pop(uid, None)
|
|
143
|
-
return list(dynamics), False
|
|
144
|
-
except GrpcError as e:
|
|
145
|
-
if e.code != -352:
|
|
146
|
-
raise
|
|
147
|
-
logger.warning(
|
|
148
|
-
f"动态 gRPC 接口触发风控,切换 Web 接口:{uid} "
|
|
149
|
-
f"{e.code} {e.msg}"
|
|
150
|
-
)
|
|
151
|
-
dynamic_web_fallback_until[uid] = monotonic() + GRPC_RISK_CONTROL_RETRY_SECONDS
|
|
152
|
-
return await fetch_web_dynamics(), True
|
|
95
|
+
),
|
|
96
|
+
True,
|
|
97
|
+
)
|
|
153
98
|
|
|
154
99
|
|
|
155
100
|
async def process_dynamic_uid(uid: int):
|
|
@@ -174,16 +119,6 @@ async def process_dynamic_uid(uid: int):
|
|
|
174
119
|
except asyncio.CancelledError:
|
|
175
120
|
logger.debug(f"动态轮询任务已取消:{name}({uid})")
|
|
176
121
|
return
|
|
177
|
-
except AioRpcError as e:
|
|
178
|
-
if e.code() == StatusCode.DEADLINE_EXCEEDED:
|
|
179
|
-
logger.error(f"爬取动态超时,将在下个轮询中重试:{e.code()} {e.details()}")
|
|
180
|
-
else:
|
|
181
|
-
logger.error(f"爬取动态失败:{e.code()} {e.details()}")
|
|
182
|
-
await throttle_dynamic_loop()
|
|
183
|
-
return
|
|
184
|
-
except GrpcError as e:
|
|
185
|
-
logger.error(f"爬取动态失败:{e.code} {e.msg}")
|
|
186
|
-
return
|
|
187
122
|
except WebDynamicError as e:
|
|
188
123
|
retry_seconds = (
|
|
189
124
|
WEB_REQUEST_BANNED_RETRY_SECONDS
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import time
|
|
2
2
|
|
|
3
|
-
from
|
|
3
|
+
from nonebot import logger
|
|
4
4
|
from nonebot.adapters.onebot.v11.message import MessageSegment
|
|
5
|
-
from nonebot.log import logger
|
|
6
5
|
|
|
6
|
+
from ...bilibili_api import get_live_rooms_info_by_uids
|
|
7
7
|
from ...config import plugin_config
|
|
8
8
|
from ...database import DB as db
|
|
9
|
-
from ...utils import
|
|
9
|
+
from ...utils import calc_time_total, safe_send, scheduler
|
|
10
10
|
|
|
11
11
|
status = {}
|
|
12
12
|
live_time = {}
|
|
@@ -32,7 +32,7 @@ async def live_sched():
|
|
|
32
32
|
if not uids: # 订阅为空
|
|
33
33
|
return
|
|
34
34
|
logger.debug(f"爬取直播列表,目前开播{sum(status.values())}人,总共{len(uids)}人")
|
|
35
|
-
res = await
|
|
35
|
+
res = await get_live_rooms_info_by_uids(uids, proxy=plugin_config.bililive_proxy)
|
|
36
36
|
if not res:
|
|
37
37
|
return
|
|
38
38
|
for uid, info in res.items():
|
|
@@ -1,11 +1,8 @@
|
|
|
1
|
-
from bilireq.exceptions import ResponseCodeError
|
|
2
|
-
from bilireq.user import get_user_info
|
|
3
1
|
from nonebot.adapters.onebot.v11.event import MessageEvent
|
|
4
2
|
from nonebot.params import ArgPlainText
|
|
5
3
|
|
|
6
4
|
from ...database import DB as db
|
|
7
5
|
from ...utils import (
|
|
8
|
-
PROXIES,
|
|
9
6
|
get_type_id,
|
|
10
7
|
get_user_name_by_uid,
|
|
11
8
|
handle_uid,
|
|
@@ -31,28 +28,9 @@ async def _(event: MessageEvent, uid: str = ArgPlainText("uid")):
|
|
|
31
28
|
user = await db.get_user(uid=uid)
|
|
32
29
|
name = user and user.name
|
|
33
30
|
if not name:
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
name = await get_user_name_by_uid(uid)
|
|
38
|
-
if name:
|
|
39
|
-
logger_message = f"UID {uid} 使用 card 接口回退获取昵称成功"
|
|
40
|
-
from nonebot.log import logger
|
|
41
|
-
|
|
42
|
-
logger.warning(logger_message)
|
|
43
|
-
elif e.code in [-400, -404]:
|
|
44
|
-
await add_sub.finish("UID不存在,注意UID不是房间号")
|
|
45
|
-
elif e.code == -412:
|
|
46
|
-
await add_sub.finish("操作过于频繁IP暂时被风控,请半小时后再尝试")
|
|
47
|
-
else:
|
|
48
|
-
await add_sub.finish(
|
|
49
|
-
f"未知错误,请联系开发者反馈,错误内容:\n\
|
|
50
|
-
{str(e)}"
|
|
51
|
-
)
|
|
52
|
-
except Exception:
|
|
53
|
-
name = await get_user_name_by_uid(uid)
|
|
54
|
-
if not name:
|
|
55
|
-
raise
|
|
31
|
+
name = await get_user_name_by_uid(uid)
|
|
32
|
+
if not name:
|
|
33
|
+
await add_sub.finish("UID不存在,注意UID不是房间号")
|
|
56
34
|
|
|
57
35
|
result = await db.add_sub(
|
|
58
36
|
uid=uid,
|
|
@@ -3,14 +3,13 @@ import contextlib
|
|
|
3
3
|
import datetime
|
|
4
4
|
import re
|
|
5
5
|
import sys
|
|
6
|
-
from pathlib import Path
|
|
7
6
|
from typing import Annotated
|
|
8
7
|
|
|
9
8
|
import httpx
|
|
10
9
|
import nonebot
|
|
11
|
-
|
|
10
|
+
import nonebot_plugin_localstore as store
|
|
11
|
+
from nonebot import logger
|
|
12
12
|
from nonebot import on_command as _on_command
|
|
13
|
-
from nonebot import require
|
|
14
13
|
from nonebot.adapters.onebot.v11 import (
|
|
15
14
|
ActionFailed,
|
|
16
15
|
Bot,
|
|
@@ -22,7 +21,6 @@ from nonebot.adapters.onebot.v11 import (
|
|
|
22
21
|
from nonebot.adapters.onebot.v11.event import GroupMessageEvent, PrivateMessageEvent
|
|
23
22
|
from nonebot.adapters.onebot.v11.permission import GROUP_ADMIN, GROUP_OWNER
|
|
24
23
|
from nonebot.exception import FinishedException
|
|
25
|
-
from nonebot.log import logger
|
|
26
24
|
from nonebot.matcher import Matcher
|
|
27
25
|
from nonebot.params import ArgPlainText, CommandArg, RawCommand
|
|
28
26
|
from nonebot.permission import SUPERUSER
|
|
@@ -30,17 +28,10 @@ from nonebot.rule import Rule
|
|
|
30
28
|
|
|
31
29
|
from ..config import plugin_config
|
|
32
30
|
|
|
33
|
-
require("nonebot_plugin_localstore")
|
|
34
|
-
import nonebot_plugin_localstore as store
|
|
35
|
-
|
|
36
|
-
PLUGIN_ENTRY_NAME = "nonebot_plugin_bililive"
|
|
37
31
|
|
|
38
|
-
|
|
39
|
-
def get_data_dir() -> Path:
|
|
32
|
+
def get_data_dir():
|
|
40
33
|
"""获取插件数据目录。"""
|
|
41
|
-
|
|
42
|
-
return Path(plugin_config.bililive_dir).resolve()
|
|
43
|
-
return store.get_data_dir(PLUGIN_ENTRY_NAME)
|
|
34
|
+
return store.get_plugin_data_dir()
|
|
44
35
|
|
|
45
36
|
|
|
46
37
|
def get_path(*other):
|
|
@@ -99,9 +90,16 @@ async def search_user(keyword: str):
|
|
|
99
90
|
"""
|
|
100
91
|
url = "https://api.bilibili.com/x/web-interface/search/type"
|
|
101
92
|
data = {"keyword": keyword, "search_type": "bili_user"}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
93
|
+
async with httpx.AsyncClient(
|
|
94
|
+
proxy=plugin_config.bililive_proxy,
|
|
95
|
+
headers={"User-Agent": "Mozilla/5.0", "Referer": "https://www.bilibili.com/"},
|
|
96
|
+
timeout=20,
|
|
97
|
+
follow_redirects=True,
|
|
98
|
+
) as client:
|
|
99
|
+
response = await client.get(url, params=data)
|
|
100
|
+
payload = response.json()
|
|
101
|
+
logger.debug(payload)
|
|
102
|
+
return payload.get("data")
|
|
105
103
|
|
|
106
104
|
|
|
107
105
|
async def get_user_name_by_uid(uid: int | str) -> str | None:
|
|
@@ -303,10 +301,6 @@ def on_startup():
|
|
|
303
301
|
def on_command(cmd, *args, **kwargs):
|
|
304
302
|
return _on_command(plugin_config.bililive_command_prefix + cmd, *args, **kwargs)
|
|
305
303
|
|
|
306
|
-
|
|
307
|
-
PROXIES = {"all://": plugin_config.bililive_proxy}
|
|
308
|
-
|
|
309
|
-
require("nonebot_plugin_apscheduler")
|
|
310
304
|
from nonebot_plugin_apscheduler import scheduler # noqa
|
|
311
305
|
|
|
312
306
|
from .browser import ( # noqa
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name = "nonebot-plugin-bililive"
|
|
3
3
|
description = "Push bilibili dynamics and live notifications to QQ with NoneBot2."
|
|
4
4
|
authors = [
|
|
5
|
-
{ name = "
|
|
5
|
+
{ name = "Akiyy_Lab", email = "2806578374@qq.com" },
|
|
6
6
|
]
|
|
7
7
|
requires-python = ">=3.10,<4.0"
|
|
8
8
|
readme = "README.md"
|
|
@@ -30,17 +30,16 @@ dependencies = [
|
|
|
30
30
|
"nonebot-adapter-onebot>=2.4.6",
|
|
31
31
|
"nonebot-plugin-apscheduler>=0.5.0",
|
|
32
32
|
"nonebot-plugin-localstore>=0.7",
|
|
33
|
-
"nonebot2
|
|
33
|
+
"nonebot2>=2.5.0",
|
|
34
34
|
"playwright>=1.58.0",
|
|
35
|
-
"pydantic>=
|
|
35
|
+
"pydantic>=1.10.0,<3.0",
|
|
36
36
|
"python-dotenv>=1.2.2",
|
|
37
37
|
"tortoise-orm[asyncpg]>=0.19.3",
|
|
38
|
-
"bilireq>=0.2.13",
|
|
39
38
|
"packaging>=26.0",
|
|
40
39
|
"msvc-runtime>=14.34.31931; sys_platform == \"win32\"",
|
|
41
40
|
]
|
|
42
41
|
dynamic = []
|
|
43
|
-
version = "2.
|
|
42
|
+
version = "2.1.1"
|
|
44
43
|
|
|
45
44
|
[project.license]
|
|
46
45
|
text = "AGPL-3.0-or-later"
|
|
@@ -56,7 +55,7 @@ nonebot-plugin-bililive = "nonebot_plugin_bililive"
|
|
|
56
55
|
nonebot_plugin_bililive = "nonebot_plugin_bililive"
|
|
57
56
|
|
|
58
57
|
[project.scripts]
|
|
59
|
-
bililive = "
|
|
58
|
+
bililive = "nonebot_plugin_bililive.__main__:main"
|
|
60
59
|
|
|
61
60
|
[tool.nonebot]
|
|
62
61
|
adapters = [
|
|
@@ -73,11 +72,10 @@ distribution = true
|
|
|
73
72
|
|
|
74
73
|
[tool.pdm.version]
|
|
75
74
|
source = "file"
|
|
76
|
-
path = "
|
|
75
|
+
path = "nonebot_plugin_bililive/version.py"
|
|
77
76
|
|
|
78
77
|
[tool.pdm.build]
|
|
79
78
|
includes = [
|
|
80
|
-
"bililive",
|
|
81
79
|
"nonebot_plugin_bililive",
|
|
82
80
|
"nonebot-plugin-bililive",
|
|
83
81
|
]
|
|
@@ -1,14 +1,11 @@
|
|
|
1
1
|
import sys
|
|
2
2
|
import tempfile
|
|
3
3
|
import unittest
|
|
4
|
-
import importlib
|
|
5
4
|
from importlib import import_module
|
|
6
5
|
from pathlib import Path
|
|
7
6
|
from types import ModuleType, SimpleNamespace
|
|
8
7
|
from unittest.mock import AsyncMock, patch
|
|
9
8
|
|
|
10
|
-
import httpx
|
|
11
|
-
|
|
12
9
|
|
|
13
10
|
class DummyDriver:
|
|
14
11
|
config = {}
|
|
@@ -38,32 +35,32 @@ class DummyScheduler:
|
|
|
38
35
|
fake_apscheduler.scheduler = DummyScheduler()
|
|
39
36
|
|
|
40
37
|
|
|
41
|
-
def
|
|
42
|
-
return Path(tempfile.gettempdir()) /
|
|
38
|
+
def _get_plugin_data_dir() -> Path:
|
|
39
|
+
return Path(tempfile.gettempdir()) / "nonebot_plugin_bililive"
|
|
43
40
|
|
|
44
41
|
|
|
45
|
-
fake_localstore.
|
|
42
|
+
fake_localstore.get_plugin_data_dir = _get_plugin_data_dir
|
|
46
43
|
|
|
47
44
|
|
|
48
45
|
with patch("nonebot.get_driver", return_value=DummyDriver()), patch(
|
|
49
|
-
"nonebot.
|
|
50
|
-
), patch.dict(
|
|
46
|
+
"nonebot.get_plugin_config", side_effect=lambda cls: cls()
|
|
47
|
+
), patch("nonebot.require", return_value=None), patch.dict(
|
|
51
48
|
sys.modules,
|
|
52
49
|
{
|
|
53
50
|
"nonebot_plugin_apscheduler": fake_apscheduler,
|
|
54
51
|
"nonebot_plugin_localstore": fake_localstore,
|
|
55
52
|
},
|
|
56
53
|
):
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
web_dynamic = import_module("bililive.libs.dynamic.web")
|
|
54
|
+
Config = import_module("nonebot_plugin_bililive.config").Config
|
|
55
|
+
core_version = import_module("nonebot_plugin_bililive.version")
|
|
56
|
+
db_module = import_module("nonebot_plugin_bililive.database.db")
|
|
57
|
+
web_dynamic = import_module("nonebot_plugin_bililive.libs.dynamic.web")
|
|
62
58
|
plugin_entry = import_module("nonebot_plugin_bililive")
|
|
59
|
+
hyphen_plugin_entry = import_module("nonebot-plugin-bililive")
|
|
63
60
|
DB = db_module.DB
|
|
64
|
-
models = import_module("
|
|
61
|
+
models = import_module("nonebot_plugin_bililive.database.models")
|
|
65
62
|
Group = models.Group
|
|
66
|
-
get_path = import_module("
|
|
63
|
+
get_path = import_module("nonebot_plugin_bililive.utils").get_path
|
|
67
64
|
|
|
68
65
|
|
|
69
66
|
class ConfigTests(unittest.TestCase):
|
|
@@ -84,8 +81,8 @@ class ConfigTests(unittest.TestCase):
|
|
|
84
81
|
self.assertEqual(config.bililive_screenshot_style, "mobile")
|
|
85
82
|
|
|
86
83
|
def test_legacy_haruka_config_names_are_still_supported(self):
|
|
87
|
-
config = Config
|
|
88
|
-
{
|
|
84
|
+
config = Config(
|
|
85
|
+
**{
|
|
89
86
|
"haruka_interval": -1,
|
|
90
87
|
"haruka_live_interval": 12,
|
|
91
88
|
"haruka_command_prefix": "hb",
|
|
@@ -97,35 +94,23 @@ class ConfigTests(unittest.TestCase):
|
|
|
97
94
|
self.assertEqual(config.bililive_command_prefix, "hb")
|
|
98
95
|
|
|
99
96
|
|
|
100
|
-
class CompatTests(unittest.TestCase):
|
|
101
|
-
def test_httpx_compat_adds_legacy_proxy_alias(self):
|
|
102
|
-
compat.patch_httpx_compat()
|
|
103
|
-
|
|
104
|
-
self.assertTrue(hasattr(httpx._types, "ProxiesTypes"))
|
|
105
|
-
|
|
106
|
-
def test_httpx_compat_accepts_legacy_proxies_keyword(self):
|
|
107
|
-
compat.patch_httpx_compat()
|
|
108
|
-
|
|
109
|
-
client = httpx.AsyncClient(proxies={"all://": None})
|
|
110
|
-
self.assertIsInstance(client, httpx.AsyncClient)
|
|
111
|
-
self.addCleanup(lambda: __import__("asyncio").run(client.aclose()))
|
|
112
|
-
|
|
113
|
-
|
|
114
97
|
class PluginEntryTests(unittest.TestCase):
|
|
115
|
-
def
|
|
98
|
+
def test_plugin_entry_exposes_plugin_metadata(self):
|
|
116
99
|
self.assertEqual(
|
|
117
100
|
plugin_entry.__plugin_meta__.homepage,
|
|
118
101
|
"https://github.com/Akiyy-dev/nonebot-plugin-bililive",
|
|
119
102
|
)
|
|
120
103
|
self.assertEqual(plugin_entry.__plugin_meta__.config, Config)
|
|
104
|
+
self.assertEqual(plugin_entry.__plugin_meta__.extra["author"], "Akiyy_Lab")
|
|
121
105
|
self.assertEqual(plugin_entry.__version__, core_version.__version__)
|
|
106
|
+
self.assertIs(hyphen_plugin_entry.__plugin_meta__, plugin_entry.__plugin_meta__)
|
|
122
107
|
|
|
123
108
|
def test_default_data_dir_uses_localstore(self):
|
|
124
|
-
expected =
|
|
109
|
+
expected = _get_plugin_data_dir() / "data.sqlite3"
|
|
125
110
|
|
|
126
111
|
self.assertEqual(Path(get_path("data.sqlite3")), expected)
|
|
127
112
|
|
|
128
|
-
def
|
|
113
|
+
def test_pyproject_uses_direct_plugin_entrypoint(self):
|
|
129
114
|
pyproject = Path("pyproject.toml").read_text(encoding="utf-8")
|
|
130
115
|
|
|
131
116
|
self.assertIn(
|
|
@@ -136,28 +121,13 @@ class PluginEntryTests(unittest.TestCase):
|
|
|
136
121
|
'nonebot_plugin_bililive = "nonebot_plugin_bililive"',
|
|
137
122
|
pyproject,
|
|
138
123
|
)
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
), patch.dict(
|
|
144
|
-
sys.modules,
|
|
145
|
-
{
|
|
146
|
-
"nonebot_plugin_apscheduler": fake_apscheduler,
|
|
147
|
-
"nonebot_plugin_localstore": fake_localstore,
|
|
148
|
-
},
|
|
149
|
-
):
|
|
150
|
-
alias_module = importlib.import_module("nonebot-plugin-bililive")
|
|
151
|
-
|
|
152
|
-
self.assertEqual(alias_module.__plugin_meta__.name, plugin_entry.__plugin_meta__.name)
|
|
153
|
-
self.assertEqual(
|
|
154
|
-
alias_module.__plugin_meta__.homepage,
|
|
155
|
-
plugin_entry.__plugin_meta__.homepage,
|
|
156
|
-
)
|
|
157
|
-
self.assertEqual(
|
|
158
|
-
alias_module.__plugin_meta__.supported_adapters,
|
|
159
|
-
plugin_entry.__plugin_meta__.supported_adapters,
|
|
124
|
+
self.assertIn('bililive = "nonebot_plugin_bililive.__main__:main"', pyproject)
|
|
125
|
+
self.assertIn(
|
|
126
|
+
'includes = ["nonebot_plugin_bililive", "nonebot-plugin-bililive"]',
|
|
127
|
+
pyproject,
|
|
160
128
|
)
|
|
129
|
+
self.assertNotIn('nonebot2[fastapi]>=', pyproject)
|
|
130
|
+
self.assertNotIn('bilireq>=', pyproject)
|
|
161
131
|
|
|
162
132
|
|
|
163
133
|
class WebDynamicTests(unittest.TestCase):
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
from collections.abc import Mapping
|
|
2
|
-
from typing import Any
|
|
3
|
-
|
|
4
|
-
import httpx
|
|
5
|
-
import httpx._types as httpx_types
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
def _normalize_proxy_value(proxies: Any):
|
|
9
|
-
if proxies is None:
|
|
10
|
-
return None
|
|
11
|
-
if isinstance(proxies, Mapping):
|
|
12
|
-
if "all://" in proxies:
|
|
13
|
-
return proxies["all://"]
|
|
14
|
-
for value in proxies.values():
|
|
15
|
-
if value is not None:
|
|
16
|
-
return value
|
|
17
|
-
return None
|
|
18
|
-
return proxies
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
def patch_httpx_compat():
|
|
22
|
-
if not hasattr(httpx_types, "ProxiesTypes") and hasattr(httpx_types, "ProxyTypes"):
|
|
23
|
-
httpx_types.ProxiesTypes = httpx_types.ProxyTypes
|
|
24
|
-
|
|
25
|
-
if getattr(httpx.AsyncClient, "__bililive_compat_patch__", False):
|
|
26
|
-
return
|
|
27
|
-
|
|
28
|
-
original_async_client = httpx.AsyncClient
|
|
29
|
-
|
|
30
|
-
class CompatAsyncClient(original_async_client):
|
|
31
|
-
__bililive_compat_patch__ = True
|
|
32
|
-
|
|
33
|
-
def __init__(self, *args, proxies=Ellipsis, **kwargs):
|
|
34
|
-
if proxies is not Ellipsis and "proxy" not in kwargs:
|
|
35
|
-
kwargs["proxy"] = _normalize_proxy_value(proxies)
|
|
36
|
-
super().__init__(*args, **kwargs)
|
|
37
|
-
|
|
38
|
-
CompatAsyncClient.__name__ = original_async_client.__name__
|
|
39
|
-
CompatAsyncClient.__qualname__ = original_async_client.__qualname__
|
|
40
|
-
CompatAsyncClient.__module__ = original_async_client.__module__
|
|
41
|
-
|
|
42
|
-
httpx.AsyncClient = CompatAsyncClient
|
|
@@ -1,123 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
from loguru import logger
|
|
3
|
-
from nonebot import get_driver
|
|
4
|
-
from pydantic import (
|
|
5
|
-
AliasChoices,
|
|
6
|
-
BaseModel,
|
|
7
|
-
ConfigDict,
|
|
8
|
-
Field,
|
|
9
|
-
ValidationInfo,
|
|
10
|
-
field_validator,
|
|
11
|
-
)
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
def compat_field(default, new_name: str, legacy_name: str):
|
|
15
|
-
return Field(
|
|
16
|
-
default=default,
|
|
17
|
-
validation_alias=AliasChoices(new_name, legacy_name),
|
|
18
|
-
)
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
# 其他地方出现的类似 from .. import config,均是从 __init__.py 导入的 Config 实例
|
|
22
|
-
class Config(BaseModel):
|
|
23
|
-
model_config = ConfigDict(extra="ignore")
|
|
24
|
-
|
|
25
|
-
fastapi_reload: bool = False
|
|
26
|
-
bililive_dir: str | None = compat_field(None, "bililive_dir", "haruka_dir")
|
|
27
|
-
bililive_to_me: bool = compat_field(True, "bililive_to_me", "haruka_to_me")
|
|
28
|
-
bililive_live_off_notify: bool = compat_field(
|
|
29
|
-
False,
|
|
30
|
-
"bililive_live_off_notify",
|
|
31
|
-
"haruka_live_off_notify",
|
|
32
|
-
)
|
|
33
|
-
bililive_proxy: str | None = compat_field(
|
|
34
|
-
None,
|
|
35
|
-
"bililive_proxy",
|
|
36
|
-
"haruka_proxy",
|
|
37
|
-
)
|
|
38
|
-
bililive_interval: int = compat_field(10, "bililive_interval", "haruka_interval")
|
|
39
|
-
bililive_live_interval: int = compat_field(
|
|
40
|
-
10,
|
|
41
|
-
"bililive_live_interval",
|
|
42
|
-
"haruka_live_interval",
|
|
43
|
-
)
|
|
44
|
-
bililive_dynamic_interval: int = compat_field(
|
|
45
|
-
0,
|
|
46
|
-
"bililive_dynamic_interval",
|
|
47
|
-
"haruka_dynamic_interval",
|
|
48
|
-
)
|
|
49
|
-
bililive_dynamic_at: bool = compat_field(
|
|
50
|
-
False,
|
|
51
|
-
"bililive_dynamic_at",
|
|
52
|
-
"haruka_dynamic_at",
|
|
53
|
-
)
|
|
54
|
-
bililive_screenshot_style: str = compat_field(
|
|
55
|
-
"mobile",
|
|
56
|
-
"bililive_screenshot_style",
|
|
57
|
-
"haruka_screenshot_style",
|
|
58
|
-
)
|
|
59
|
-
bililive_captcha_address: str = compat_field(
|
|
60
|
-
"https://captcha-cd.ngworks.cn",
|
|
61
|
-
"bililive_captcha_address",
|
|
62
|
-
"haruka_captcha_address",
|
|
63
|
-
)
|
|
64
|
-
bililive_captcha_token: str = compat_field(
|
|
65
|
-
"bililive",
|
|
66
|
-
"bililive_captcha_token",
|
|
67
|
-
"haruka_captcha_token",
|
|
68
|
-
)
|
|
69
|
-
bililive_browser_ua: str | None = compat_field(
|
|
70
|
-
None,
|
|
71
|
-
"bililive_browser_ua",
|
|
72
|
-
"haruka_browser_ua",
|
|
73
|
-
)
|
|
74
|
-
bililive_dynamic_timeout: int = compat_field(
|
|
75
|
-
30,
|
|
76
|
-
"bililive_dynamic_timeout",
|
|
77
|
-
"haruka_dynamic_timeout",
|
|
78
|
-
)
|
|
79
|
-
bililive_dynamic_font_source: str = compat_field(
|
|
80
|
-
"system",
|
|
81
|
-
"bililive_dynamic_font_source",
|
|
82
|
-
"haruka_dynamic_font_source",
|
|
83
|
-
)
|
|
84
|
-
bililive_dynamic_font: str | None = compat_field(
|
|
85
|
-
"Noto Sans CJK SC",
|
|
86
|
-
"bililive_dynamic_font",
|
|
87
|
-
"haruka_dynamic_font",
|
|
88
|
-
)
|
|
89
|
-
bililive_dynamic_big_image: bool = compat_field(
|
|
90
|
-
False,
|
|
91
|
-
"bililive_dynamic_big_image",
|
|
92
|
-
"haruka_dynamic_big_image",
|
|
93
|
-
)
|
|
94
|
-
bililive_command_prefix: str = compat_field(
|
|
95
|
-
"",
|
|
96
|
-
"bililive_command_prefix",
|
|
97
|
-
"haruka_command_prefix",
|
|
98
|
-
)
|
|
99
|
-
|
|
100
|
-
@field_validator(
|
|
101
|
-
"bililive_interval", "bililive_live_interval", "bililive_dynamic_interval"
|
|
102
|
-
)
|
|
103
|
-
@classmethod
|
|
104
|
-
def non_negative(cls, v: int, info: ValidationInfo):
|
|
105
|
-
"""定时器为负返回默认值"""
|
|
106
|
-
default = cls.model_fields[info.field_name].default
|
|
107
|
-
return default if v < 1 else v
|
|
108
|
-
|
|
109
|
-
@field_validator("bililive_screenshot_style")
|
|
110
|
-
@classmethod
|
|
111
|
-
def screenshot_style(cls, v: str):
|
|
112
|
-
if v != "mobile":
|
|
113
|
-
logger.warning("截图样式目前只支持 mobile,pc 样式现已被弃用")
|
|
114
|
-
return "mobile"
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
global_config = get_driver().config
|
|
118
|
-
if hasattr(global_config, "model_dump"):
|
|
119
|
-
plugin_config = Config.model_validate(global_config.model_dump())
|
|
120
|
-
elif hasattr(global_config, "dict"):
|
|
121
|
-
plugin_config = Config.model_validate(global_config.dict())
|
|
122
|
-
else:
|
|
123
|
-
plugin_config = Config.model_validate(global_config)
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
from nonebot_plugin_bililive import * # noqa: F403
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
from nonebot.plugin import PluginMetadata
|
|
2
|
-
from nonebot.plugin.manager import PluginLoader
|
|
3
|
-
|
|
4
|
-
from bililive import VERSION, __version__, bootstrap_plugin
|
|
5
|
-
from bililive.config import Config, plugin_config
|
|
6
|
-
|
|
7
|
-
if isinstance(globals()["__loader__"], PluginLoader):
|
|
8
|
-
bootstrap_plugin(force=True)
|
|
9
|
-
|
|
10
|
-
__plugin_meta__ = PluginMetadata(
|
|
11
|
-
name="BiliLive",
|
|
12
|
-
description="将 B 站 UP 主的动态和直播信息推送至 QQ",
|
|
13
|
-
usage="发送“帮助”查看命令列表,发送“关注 UID”订阅 UP 主",
|
|
14
|
-
homepage="https://github.com/Akiyy-dev/nonebot-plugin-bililive",
|
|
15
|
-
type="application",
|
|
16
|
-
config=Config,
|
|
17
|
-
supported_adapters={"~onebot.v11"},
|
|
18
|
-
extra={
|
|
19
|
-
"author": "SK-415",
|
|
20
|
-
"version": __version__,
|
|
21
|
-
"priority": 1,
|
|
22
|
-
},
|
|
23
|
-
)
|
|
24
|
-
|
|
25
|
-
__all__ = ["__plugin_meta__", "__version__", "VERSION", "plugin_config"]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|