nonebot-plugin-bililive 2.0.9__tar.gz → 2.1.0__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.
Files changed (56) hide show
  1. {nonebot_plugin_bililive-2.0.9 → nonebot_plugin_bililive-2.1.0}/PKG-INFO +6 -8
  2. {nonebot_plugin_bililive-2.0.9 → nonebot_plugin_bililive-2.1.0}/README.md +2 -3
  3. {nonebot_plugin_bililive-2.0.9/bililive → nonebot_plugin_bililive-2.1.0/nonebot_plugin_bililive}/__init__.py +5 -6
  4. nonebot_plugin_bililive-2.1.0/nonebot_plugin_bililive/bilibili_api.py +57 -0
  5. {nonebot_plugin_bililive-2.0.9/bililive → nonebot_plugin_bililive-2.1.0/nonebot_plugin_bililive}/cli/bot.py +3 -2
  6. nonebot_plugin_bililive-2.1.0/nonebot_plugin_bililive/config.py +90 -0
  7. {nonebot_plugin_bililive-2.0.9/bililive → nonebot_plugin_bililive-2.1.0/nonebot_plugin_bililive}/database/db.py +2 -3
  8. {nonebot_plugin_bililive-2.0.9/bililive → nonebot_plugin_bililive-2.1.0/nonebot_plugin_bililive}/plugins/help.py +1 -1
  9. {nonebot_plugin_bililive-2.0.9/bililive → nonebot_plugin_bililive-2.1.0/nonebot_plugin_bililive}/plugins/pusher/dynamic_pusher.py +22 -87
  10. {nonebot_plugin_bililive-2.0.9/bililive → nonebot_plugin_bililive-2.1.0/nonebot_plugin_bililive}/plugins/pusher/live_pusher.py +4 -4
  11. {nonebot_plugin_bililive-2.0.9/bililive → nonebot_plugin_bililive-2.1.0/nonebot_plugin_bililive}/plugins/sub/add_sub.py +3 -25
  12. {nonebot_plugin_bililive-2.0.9/bililive → nonebot_plugin_bililive-2.1.0/nonebot_plugin_bililive}/utils/__init__.py +13 -15
  13. {nonebot_plugin_bililive-2.0.9/bililive → nonebot_plugin_bililive-2.1.0/nonebot_plugin_bililive}/utils/browser.py +1 -1
  14. {nonebot_plugin_bililive-2.0.9/bililive → nonebot_plugin_bililive-2.1.0/nonebot_plugin_bililive}/utils/captcha_solver.py +1 -1
  15. {nonebot_plugin_bililive-2.0.9/bililive → nonebot_plugin_bililive-2.1.0/nonebot_plugin_bililive}/utils/fonts_provider.py +1 -1
  16. {nonebot_plugin_bililive-2.0.9/bililive → nonebot_plugin_bililive-2.1.0/nonebot_plugin_bililive}/version.py +1 -1
  17. {nonebot_plugin_bililive-2.0.9 → nonebot_plugin_bililive-2.1.0}/pyproject.toml +6 -10
  18. {nonebot_plugin_bililive-2.0.9 → nonebot_plugin_bililive-2.1.0}/tests/test_maintenance.py +21 -60
  19. nonebot_plugin_bililive-2.0.9/bililive/compat.py +0 -42
  20. nonebot_plugin_bililive-2.0.9/bililive/config.py +0 -123
  21. nonebot_plugin_bililive-2.0.9/nonebot-plugin-bililive/__init__.py +0 -1
  22. nonebot_plugin_bililive-2.0.9/nonebot_plugin_bililive/__init__.py +0 -25
  23. {nonebot_plugin_bililive-2.0.9 → nonebot_plugin_bililive-2.1.0}/LICENSE +0 -0
  24. {nonebot_plugin_bililive-2.0.9/bililive → nonebot_plugin_bililive-2.1.0/nonebot_plugin_bililive}/__main__.py +0 -0
  25. {nonebot_plugin_bililive-2.0.9/bililive → nonebot_plugin_bililive-2.1.0/nonebot_plugin_bililive}/cli/__init__.py +0 -0
  26. {nonebot_plugin_bililive-2.0.9/bililive → nonebot_plugin_bililive-2.1.0/nonebot_plugin_bililive}/cli/utils.py +0 -0
  27. {nonebot_plugin_bililive-2.0.9/bililive → nonebot_plugin_bililive-2.1.0/nonebot_plugin_bililive}/database/__init__.py +0 -0
  28. {nonebot_plugin_bililive-2.0.9/bililive → nonebot_plugin_bililive-2.1.0/nonebot_plugin_bililive}/database/models.py +0 -0
  29. {nonebot_plugin_bililive-2.0.9/bililive → nonebot_plugin_bililive-2.1.0/nonebot_plugin_bililive}/libs/__init__.py +0 -0
  30. {nonebot_plugin_bililive-2.0.9/bililive → nonebot_plugin_bililive-2.1.0/nonebot_plugin_bililive}/libs/dynamic/__init__.py +0 -0
  31. {nonebot_plugin_bililive-2.0.9/bililive → nonebot_plugin_bililive-2.1.0/nonebot_plugin_bililive}/libs/dynamic/card.py +0 -0
  32. {nonebot_plugin_bililive-2.0.9/bililive → nonebot_plugin_bililive-2.1.0/nonebot_plugin_bililive}/libs/dynamic/desc.py +0 -0
  33. {nonebot_plugin_bililive-2.0.9/bililive → nonebot_plugin_bililive-2.1.0/nonebot_plugin_bililive}/libs/dynamic/display.py +0 -0
  34. {nonebot_plugin_bililive-2.0.9/bililive → nonebot_plugin_bililive-2.1.0/nonebot_plugin_bililive}/libs/dynamic/user_profile.py +0 -0
  35. {nonebot_plugin_bililive-2.0.9/bililive → nonebot_plugin_bililive-2.1.0/nonebot_plugin_bililive}/libs/dynamic/web.py +0 -0
  36. {nonebot_plugin_bililive-2.0.9/bililive → nonebot_plugin_bililive-2.1.0/nonebot_plugin_bililive}/plugins/__init__.py +0 -0
  37. {nonebot_plugin_bililive-2.0.9/bililive → nonebot_plugin_bililive-2.1.0/nonebot_plugin_bililive}/plugins/at/__init__.py +0 -0
  38. {nonebot_plugin_bililive-2.0.9/bililive → nonebot_plugin_bililive-2.1.0/nonebot_plugin_bililive}/plugins/at/at_off.py +0 -0
  39. {nonebot_plugin_bililive-2.0.9/bililive → nonebot_plugin_bililive-2.1.0/nonebot_plugin_bililive}/plugins/at/at_on.py +0 -0
  40. {nonebot_plugin_bililive-2.0.9/bililive → nonebot_plugin_bililive-2.1.0/nonebot_plugin_bililive}/plugins/auto_agree.py +0 -0
  41. {nonebot_plugin_bililive-2.0.9/bililive → nonebot_plugin_bililive-2.1.0/nonebot_plugin_bililive}/plugins/auto_delete.py +0 -0
  42. {nonebot_plugin_bililive-2.0.9/bililive → nonebot_plugin_bililive-2.1.0/nonebot_plugin_bililive}/plugins/dynamic/__init__.py +0 -0
  43. {nonebot_plugin_bililive-2.0.9/bililive → nonebot_plugin_bililive-2.1.0/nonebot_plugin_bililive}/plugins/dynamic/dynamic_off.py +0 -0
  44. {nonebot_plugin_bililive-2.0.9/bililive → nonebot_plugin_bililive-2.1.0/nonebot_plugin_bililive}/plugins/dynamic/dynamic_on.py +0 -0
  45. {nonebot_plugin_bililive-2.0.9/bililive → nonebot_plugin_bililive-2.1.0/nonebot_plugin_bililive}/plugins/live/__init__.py +0 -0
  46. {nonebot_plugin_bililive-2.0.9/bililive → nonebot_plugin_bililive-2.1.0/nonebot_plugin_bililive}/plugins/live/live_now.py +0 -0
  47. {nonebot_plugin_bililive-2.0.9/bililive → nonebot_plugin_bililive-2.1.0/nonebot_plugin_bililive}/plugins/live/live_off.py +0 -0
  48. {nonebot_plugin_bililive-2.0.9/bililive → nonebot_plugin_bililive-2.1.0/nonebot_plugin_bililive}/plugins/live/live_on.py +0 -0
  49. {nonebot_plugin_bililive-2.0.9/bililive → nonebot_plugin_bililive-2.1.0/nonebot_plugin_bililive}/plugins/permission/__init__.py +0 -0
  50. {nonebot_plugin_bililive-2.0.9/bililive → nonebot_plugin_bililive-2.1.0/nonebot_plugin_bililive}/plugins/permission/permission_off.py +0 -0
  51. {nonebot_plugin_bililive-2.0.9/bililive → nonebot_plugin_bililive-2.1.0/nonebot_plugin_bililive}/plugins/permission/permission_on.py +0 -0
  52. {nonebot_plugin_bililive-2.0.9/bililive → nonebot_plugin_bililive-2.1.0/nonebot_plugin_bililive}/plugins/pusher/__init__.py +0 -0
  53. {nonebot_plugin_bililive-2.0.9/bililive → nonebot_plugin_bililive-2.1.0/nonebot_plugin_bililive}/plugins/sub/__init__.py +0 -0
  54. {nonebot_plugin_bililive-2.0.9/bililive → nonebot_plugin_bililive-2.1.0/nonebot_plugin_bililive}/plugins/sub/delete_sub.py +0 -0
  55. {nonebot_plugin_bililive-2.0.9/bililive → nonebot_plugin_bililive-2.1.0/nonebot_plugin_bililive}/plugins/sub/sub_list.py +0 -0
  56. {nonebot_plugin_bililive-2.0.9/bililive → nonebot_plugin_bililive-2.1.0/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.0.9
3
+ Version: 2.1.0
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: SK-415 <2967923486@qq.com>
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[fastapi]>=2.5.0
26
+ Requires-Dist: nonebot2>=2.5.0
27
27
  Requires-Dist: playwright>=1.58.0
28
- Requires-Dist: pydantic<3.0,>=2.12.5
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;如果需要自定义数据目录,可通过 BILILIVE_DIR 覆盖默认存储位置。未配置时,插件会使用 nonebot-plugin-localstore 提供的插件数据目录。
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
- 动态抓取默认优先使用 gRPC 接口;当部分 UID 命中 B 站风控时,插件会自动回退到 Playwright 持久化浏览器中的 cookies 请求网页动态接口。通常不需要额外配置 Cookie 登录;如果某些 UID 仍持续抓取失败,建议在插件使用的浏览器数据目录中登录一个常用的 B 站账号,以提高动态抓取成功率。
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;如果需要自定义数据目录,可通过 BILILIVE_DIR 覆盖默认存储位置。未配置时,插件会使用 nonebot-plugin-localstore 提供的插件数据目录。
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
- 动态抓取默认优先使用 gRPC 接口;当部分 UID 命中 B 站风控时,插件会自动回退到 Playwright 持久化浏览器中的 cookies 请求网页动态接口。通常不需要额外配置 Cookie 登录;如果某些 UID 仍持续抓取失败,建议在插件使用的浏览器数据目录中登录一个常用的 B 站账号,以提高动态抓取成功率。
86
+ 动态抓取使用 Playwright 持久化浏览器中的 cookies 请求网页动态接口。通常不需要额外配置 Cookie 登录;如果某些 UID 持续抓取失败,建议在插件使用的浏览器数据目录中登录一个常用的 B 站账号,以提高动态抓取成功率。
88
87
 
89
88
  ## 🎉 使用
90
89
 
@@ -1,10 +1,9 @@
1
1
  from nonebot.plugin import PluginMetadata
2
2
  from nonebot.plugin.manager import PluginLoader
3
3
 
4
- from .compat import patch_httpx_compat
5
- from .config import Config
4
+ from .config import Config, plugin_config
5
+ from .version import VERSION, __version__
6
6
 
7
- patch_httpx_compat()
8
7
 
9
8
  def bootstrap_plugin(force: bool = False):
10
9
  if force or isinstance(globals()["__loader__"], PluginLoader):
@@ -17,8 +16,6 @@ def bootstrap_plugin(force: bool = False):
17
16
 
18
17
  bootstrap_plugin()
19
18
 
20
- from .version import VERSION, __version__ # noqa: F401
21
-
22
19
  __plugin_meta__ = PluginMetadata(
23
20
  name="BiliLive",
24
21
  description="将B站UP主的动态和直播信息推送至QQ",
@@ -28,8 +25,10 @@ __plugin_meta__ = PluginMetadata(
28
25
  config=Config,
29
26
  supported_adapters={"~onebot.v11"},
30
27
  extra={
31
- "author": "SK-415",
28
+ "author": "Akiyy_Lab",
32
29
  "version": __version__,
33
30
  "priority": 1,
34
31
  },
35
32
  )
33
+
34
+ __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, logger
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="bililive.cli.bot: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": ["bililive.database.models"],
41
+ "models": ["nonebot_plugin_bililive.database.models"],
43
42
  "default_connection": "bililive",
44
43
  }
45
44
  },
@@ -13,7 +13,7 @@ async def _():
13
13
  for matcher in matchers_list:
14
14
  if (
15
15
  matcher.plugin_name
16
- and matcher.plugin_name.startswith("bililive")
16
+ and matcher.plugin_name.startswith("nonebot_plugin_bililive")
17
17
  and matcher.__doc__
18
18
  ):
19
19
  message += matcher.__doc__ + "\n"
@@ -8,13 +8,8 @@ from apscheduler.events import (
8
8
  EVENT_JOB_MISSED,
9
9
  EVENT_SCHEDULER_STARTED,
10
10
  )
11
- from bilireq.exceptions import GrpcError
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
- if use_web_fallback:
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
- if use_web_fallback:
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
- if use_web_fallback:
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
- if use_web_fallback:
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
- if use_web_fallback:
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
- 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
- )
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
- cookies = await get_bilibili_cookies()
117
- if not cookies:
118
- raise WebDynamicError(-1, "browser cookies unavailable")
119
- return await get_user_dynamics_web(
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
- fallback_until = dynamic_web_fallback_until.get(uid)
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 bilireq.live import get_rooms_info_by_uids
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 PROXIES, calc_time_total, safe_send, scheduler
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 get_rooms_info_by_uids(uids, reqtype="web", proxies=PROXIES)
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
- try:
35
- name = (await get_user_info(uid, reqtype="web", proxies=PROXIES))["name"]
36
- except ResponseCodeError as e:
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,12 @@ 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
- from bilireq.utils import get
10
+ from nonebot import logger, require
12
11
  from nonebot import on_command as _on_command
13
- from nonebot import require
14
12
  from nonebot.adapters.onebot.v11 import (
15
13
  ActionFailed,
16
14
  Bot,
@@ -22,7 +20,6 @@ from nonebot.adapters.onebot.v11 import (
22
20
  from nonebot.adapters.onebot.v11.event import GroupMessageEvent, PrivateMessageEvent
23
21
  from nonebot.adapters.onebot.v11.permission import GROUP_ADMIN, GROUP_OWNER
24
22
  from nonebot.exception import FinishedException
25
- from nonebot.log import logger
26
23
  from nonebot.matcher import Matcher
27
24
  from nonebot.params import ArgPlainText, CommandArg, RawCommand
28
25
  from nonebot.permission import SUPERUSER
@@ -33,14 +30,10 @@ from ..config import plugin_config
33
30
  require("nonebot_plugin_localstore")
34
31
  import nonebot_plugin_localstore as store
35
32
 
36
- PLUGIN_ENTRY_NAME = "nonebot_plugin_bililive"
37
33
 
38
-
39
- def get_data_dir() -> Path:
34
+ def get_data_dir():
40
35
  """获取插件数据目录。"""
41
- if plugin_config.bililive_dir:
42
- return Path(plugin_config.bililive_dir).resolve()
43
- return store.get_data_dir(PLUGIN_ENTRY_NAME)
36
+ return store.get_plugin_data_dir()
44
37
 
45
38
 
46
39
  def get_path(*other):
@@ -99,9 +92,16 @@ async def search_user(keyword: str):
99
92
  """
100
93
  url = "https://api.bilibili.com/x/web-interface/search/type"
101
94
  data = {"keyword": keyword, "search_type": "bili_user"}
102
- resp = await get(url, params=data)
103
- logger.debug(resp)
104
- return resp
95
+ async with httpx.AsyncClient(
96
+ proxy=plugin_config.bililive_proxy,
97
+ headers={"User-Agent": "Mozilla/5.0", "Referer": "https://www.bilibili.com/"},
98
+ timeout=20,
99
+ follow_redirects=True,
100
+ ) as client:
101
+ response = await client.get(url, params=data)
102
+ payload = response.json()
103
+ logger.debug(payload)
104
+ return payload.get("data")
105
105
 
106
106
 
107
107
  async def get_user_name_by_uid(uid: int | str) -> str | None:
@@ -304,8 +304,6 @@ def on_command(cmd, *args, **kwargs):
304
304
  return _on_command(plugin_config.bililive_command_prefix + cmd, *args, **kwargs)
305
305
 
306
306
 
307
- PROXIES = {"all://": plugin_config.bililive_proxy}
308
-
309
307
  require("nonebot_plugin_apscheduler")
310
308
  from nonebot_plugin_apscheduler import scheduler # noqa
311
309
 
@@ -5,7 +5,7 @@ import re
5
5
  import sys
6
6
  from pathlib import Path
7
7
 
8
- from nonebot.log import logger
8
+ from nonebot import logger
9
9
  from playwright.__main__ import main
10
10
  from playwright.async_api import BrowserContext, Page, async_playwright
11
11
 
@@ -3,7 +3,7 @@ from dataclasses import dataclass
3
3
  from typing import Any
4
4
 
5
5
  import httpx
6
- from loguru import logger
6
+ from nonebot import logger
7
7
  from playwright._impl._api_structures import Position
8
8
  from playwright.async_api import Page, Response
9
9
 
@@ -1,6 +1,6 @@
1
1
  from pathlib import Path
2
2
 
3
- from loguru import logger
3
+ from nonebot import logger
4
4
  from playwright.async_api import Request, Route
5
5
  from yarl import URL
6
6
 
@@ -1,4 +1,4 @@
1
1
  from packaging.version import Version
2
2
 
3
- __version__ = "2.0.9"
3
+ __version__ = "2.1.0"
4
4
  VERSION = Version(__version__)
@@ -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 = "SK-415", email = "2967923486@qq.com" },
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[fastapi]>=2.5.0",
33
+ "nonebot2>=2.5.0",
34
34
  "playwright>=1.58.0",
35
- "pydantic>=2.12.5,<3.0",
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.0.9"
42
+ version = "2.1.0"
44
43
 
45
44
  [project.license]
46
45
  text = "AGPL-3.0-or-later"
@@ -52,11 +51,10 @@ Documentation = "https://github.com/Akiyy-dev/nonebot-plugin-bililive#readme"
52
51
  Issues = "https://github.com/Akiyy-dev/nonebot-plugin-bililive/issues"
53
52
 
54
53
  [project.entry-points."nonebot.plugin"]
55
- nonebot-plugin-bililive = "nonebot_plugin_bililive"
56
54
  nonebot_plugin_bililive = "nonebot_plugin_bililive"
57
55
 
58
56
  [project.scripts]
59
- bililive = "bililive.__main__:main"
57
+ bililive = "nonebot_plugin_bililive.__main__:main"
60
58
 
61
59
  [tool.nonebot]
62
60
  adapters = [
@@ -73,13 +71,11 @@ distribution = true
73
71
 
74
72
  [tool.pdm.version]
75
73
  source = "file"
76
- path = "bililive/version.py"
74
+ path = "nonebot_plugin_bililive/version.py"
77
75
 
78
76
  [tool.pdm.build]
79
77
  includes = [
80
- "bililive",
81
78
  "nonebot_plugin_bililive",
82
- "nonebot-plugin-bililive",
83
79
  ]
84
80
 
85
81
  [tool.pdm.dev-dependencies]
@@ -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,31 @@ class DummyScheduler:
38
35
  fake_apscheduler.scheduler = DummyScheduler()
39
36
 
40
37
 
41
- def _get_data_dir(plugin_name: str | None) -> Path:
42
- return Path(tempfile.gettempdir()) / (plugin_name or "nonebot2")
38
+ def _get_plugin_data_dir() -> Path:
39
+ return Path(tempfile.gettempdir()) / "nonebot_plugin_bililive"
43
40
 
44
41
 
45
- fake_localstore.get_data_dir = _get_data_dir
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.require", return_value=None
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
- compat = import_module("bililive.compat")
58
- Config = import_module("bililive.config").Config
59
- core_version = import_module("bililive.version")
60
- db_module = import_module("bililive.database.db")
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")
63
59
  DB = db_module.DB
64
- models = import_module("bililive.database.models")
60
+ models = import_module("nonebot_plugin_bililive.database.models")
65
61
  Group = models.Group
66
- get_path = import_module("bililive.utils").get_path
62
+ get_path = import_module("nonebot_plugin_bililive.utils").get_path
67
63
 
68
64
 
69
65
  class ConfigTests(unittest.TestCase):
@@ -84,8 +80,8 @@ class ConfigTests(unittest.TestCase):
84
80
  self.assertEqual(config.bililive_screenshot_style, "mobile")
85
81
 
86
82
  def test_legacy_haruka_config_names_are_still_supported(self):
87
- config = Config.model_validate(
88
- {
83
+ config = Config(
84
+ **{
89
85
  "haruka_interval": -1,
90
86
  "haruka_live_interval": 12,
91
87
  "haruka_command_prefix": "hb",
@@ -97,67 +93,32 @@ class ConfigTests(unittest.TestCase):
97
93
  self.assertEqual(config.bililive_command_prefix, "hb")
98
94
 
99
95
 
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
96
  class PluginEntryTests(unittest.TestCase):
115
- def test_wrapper_entry_exposes_plugin_metadata(self):
97
+ def test_plugin_entry_exposes_plugin_metadata(self):
116
98
  self.assertEqual(
117
99
  plugin_entry.__plugin_meta__.homepage,
118
100
  "https://github.com/Akiyy-dev/nonebot-plugin-bililive",
119
101
  )
120
102
  self.assertEqual(plugin_entry.__plugin_meta__.config, Config)
103
+ self.assertEqual(plugin_entry.__plugin_meta__.extra["author"], "Akiyy_Lab")
121
104
  self.assertEqual(plugin_entry.__version__, core_version.__version__)
122
105
 
123
106
  def test_default_data_dir_uses_localstore(self):
124
- expected = _get_data_dir("nonebot_plugin_bililive") / "data.sqlite3"
107
+ expected = _get_plugin_data_dir() / "data.sqlite3"
125
108
 
126
109
  self.assertEqual(Path(get_path("data.sqlite3")), expected)
127
110
 
128
- def test_pyproject_declares_hyphen_and_module_plugin_entrypoints(self):
111
+ def test_pyproject_uses_direct_plugin_entrypoint(self):
129
112
  pyproject = Path("pyproject.toml").read_text(encoding="utf-8")
130
113
 
131
- self.assertIn(
132
- 'nonebot-plugin-bililive = "nonebot_plugin_bililive"',
133
- pyproject,
134
- )
135
114
  self.assertIn(
136
115
  'nonebot_plugin_bililive = "nonebot_plugin_bililive"',
137
116
  pyproject,
138
117
  )
139
-
140
- def test_hyphenated_module_alias_can_be_imported(self):
141
- with patch("nonebot.get_driver", return_value=DummyDriver()), patch(
142
- "nonebot.require", return_value=None
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,
160
- )
118
+ self.assertNotIn('nonebot-plugin-bililive = "nonebot_plugin_bililive"', pyproject)
119
+ self.assertIn('bililive = "nonebot_plugin_bililive.__main__:main"', pyproject)
120
+ self.assertNotIn('nonebot2[fastapi]>=', pyproject)
121
+ self.assertNotIn('bilireq>=', pyproject)
161
122
 
162
123
 
163
124
  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"]