nonebot-plugin-parser 2.1.0__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.
Files changed (54) hide show
  1. {nonebot_plugin_parser-2.1.0 → nonebot_plugin_parser-2.1.1}/PKG-INFO +35 -15
  2. {nonebot_plugin_parser-2.1.0 → nonebot_plugin_parser-2.1.1}/README.md +4 -1
  3. {nonebot_plugin_parser-2.1.0 → nonebot_plugin_parser-2.1.1}/pyproject.toml +123 -121
  4. {nonebot_plugin_parser-2.1.0 → nonebot_plugin_parser-2.1.1}/src/nonebot_plugin_parser/config.py +4 -8
  5. {nonebot_plugin_parser-2.1.0 → nonebot_plugin_parser-2.1.1}/src/nonebot_plugin_parser/constants.py +17 -0
  6. {nonebot_plugin_parser-2.1.0 → nonebot_plugin_parser-2.1.1}/src/nonebot_plugin_parser/download/__init__.py +12 -8
  7. {nonebot_plugin_parser-2.1.0 → nonebot_plugin_parser-2.1.1}/src/nonebot_plugin_parser/download/task.py +2 -2
  8. {nonebot_plugin_parser-2.1.0 → nonebot_plugin_parser-2.1.1}/src/nonebot_plugin_parser/download/ytdlp.py +2 -3
  9. {nonebot_plugin_parser-2.1.0 → nonebot_plugin_parser-2.1.1}/src/nonebot_plugin_parser/matchers/__init__.py +25 -29
  10. {nonebot_plugin_parser-2.1.0 → nonebot_plugin_parser-2.1.1}/src/nonebot_plugin_parser/matchers/filter.py +1 -1
  11. nonebot_plugin_parser-2.1.0/src/nonebot_plugin_parser/matchers/preprocess.py → nonebot_plugin_parser-2.1.1/src/nonebot_plugin_parser/matchers/rule.py +37 -46
  12. {nonebot_plugin_parser-2.1.0 → nonebot_plugin_parser-2.1.1}/src/nonebot_plugin_parser/parsers/__init__.py +6 -2
  13. {nonebot_plugin_parser-2.1.0 → nonebot_plugin_parser-2.1.1}/src/nonebot_plugin_parser/parsers/acfun.py +4 -18
  14. {nonebot_plugin_parser-2.1.0 → nonebot_plugin_parser-2.1.1}/src/nonebot_plugin_parser/parsers/base.py +55 -19
  15. {nonebot_plugin_parser-2.1.0 → nonebot_plugin_parser-2.1.1}/src/nonebot_plugin_parser/parsers/bilibili/__init__.py +22 -32
  16. {nonebot_plugin_parser-2.1.0 → nonebot_plugin_parser-2.1.1}/src/nonebot_plugin_parser/parsers/bilibili/dynamic.py +5 -6
  17. {nonebot_plugin_parser-2.1.0 → nonebot_plugin_parser-2.1.1}/src/nonebot_plugin_parser/parsers/douyin/__init__.py +12 -13
  18. {nonebot_plugin_parser-2.1.0 → nonebot_plugin_parser-2.1.1}/src/nonebot_plugin_parser/parsers/douyin/video.py +1 -1
  19. {nonebot_plugin_parser-2.1.0 → nonebot_plugin_parser-2.1.1}/src/nonebot_plugin_parser/parsers/kuaishou.py +9 -21
  20. {nonebot_plugin_parser-2.1.0 → nonebot_plugin_parser-2.1.1}/src/nonebot_plugin_parser/parsers/nga.py +7 -18
  21. {nonebot_plugin_parser-2.1.0 → nonebot_plugin_parser-2.1.1}/src/nonebot_plugin_parser/parsers/tiktok.py +5 -16
  22. {nonebot_plugin_parser-2.1.0 → nonebot_plugin_parser-2.1.1}/src/nonebot_plugin_parser/parsers/twitter.py +6 -17
  23. {nonebot_plugin_parser-2.1.0 → nonebot_plugin_parser-2.1.1}/src/nonebot_plugin_parser/parsers/weibo.py +16 -28
  24. {nonebot_plugin_parser-2.1.0 → nonebot_plugin_parser-2.1.1}/src/nonebot_plugin_parser/parsers/xiaohongshu.py +12 -25
  25. {nonebot_plugin_parser-2.1.0 → nonebot_plugin_parser-2.1.1}/src/nonebot_plugin_parser/parsers/youtube.py +8 -8
  26. {nonebot_plugin_parser-2.1.0 → nonebot_plugin_parser-2.1.1}/src/nonebot_plugin_parser/renders/__init__.py +6 -9
  27. {nonebot_plugin_parser-2.1.0 → nonebot_plugin_parser-2.1.1}/src/nonebot_plugin_parser/renders/common.py +288 -231
  28. {nonebot_plugin_parser-2.1.0 → nonebot_plugin_parser-2.1.1}/src/nonebot_plugin_parser/__init__.py +0 -0
  29. {nonebot_plugin_parser-2.1.0 → nonebot_plugin_parser-2.1.1}/src/nonebot_plugin_parser/exception.py +0 -0
  30. {nonebot_plugin_parser-2.1.0 → nonebot_plugin_parser-2.1.1}/src/nonebot_plugin_parser/helper.py +0 -0
  31. {nonebot_plugin_parser-2.1.0 → nonebot_plugin_parser-2.1.1}/src/nonebot_plugin_parser/parsers/bilibili/article.py +0 -0
  32. {nonebot_plugin_parser-2.1.0 → nonebot_plugin_parser-2.1.1}/src/nonebot_plugin_parser/parsers/bilibili/common.py +0 -0
  33. {nonebot_plugin_parser-2.1.0 → nonebot_plugin_parser-2.1.1}/src/nonebot_plugin_parser/parsers/bilibili/favlist.py +0 -0
  34. {nonebot_plugin_parser-2.1.0 → nonebot_plugin_parser-2.1.1}/src/nonebot_plugin_parser/parsers/bilibili/live.py +0 -0
  35. {nonebot_plugin_parser-2.1.0 → nonebot_plugin_parser-2.1.1}/src/nonebot_plugin_parser/parsers/bilibili/opus.py +0 -0
  36. {nonebot_plugin_parser-2.1.0 → nonebot_plugin_parser-2.1.1}/src/nonebot_plugin_parser/parsers/bilibili/video.py +0 -0
  37. {nonebot_plugin_parser-2.1.0 → nonebot_plugin_parser-2.1.1}/src/nonebot_plugin_parser/parsers/cookie.py +0 -0
  38. {nonebot_plugin_parser-2.1.0 → nonebot_plugin_parser-2.1.1}/src/nonebot_plugin_parser/parsers/data.py +0 -0
  39. {nonebot_plugin_parser-2.1.0 → nonebot_plugin_parser-2.1.1}/src/nonebot_plugin_parser/parsers/douyin/slides.py +0 -0
  40. {nonebot_plugin_parser-2.1.0 → nonebot_plugin_parser-2.1.1}/src/nonebot_plugin_parser/renders/base.py +0 -0
  41. {nonebot_plugin_parser-2.1.0 → nonebot_plugin_parser-2.1.1}/src/nonebot_plugin_parser/renders/default.py +0 -0
  42. {nonebot_plugin_parser-2.1.0 → nonebot_plugin_parser-2.1.1}/src/nonebot_plugin_parser/renders/resources/HYSongYunLangHeiW-1.ttf +0 -0
  43. {nonebot_plugin_parser-2.1.0 → nonebot_plugin_parser-2.1.1}/src/nonebot_plugin_parser/renders/resources/bilibili.png +0 -0
  44. {nonebot_plugin_parser-2.1.0 → nonebot_plugin_parser-2.1.1}/src/nonebot_plugin_parser/renders/resources/douyin.png +0 -0
  45. {nonebot_plugin_parser-2.1.0 → nonebot_plugin_parser-2.1.1}/src/nonebot_plugin_parser/renders/resources/kuaishou.png +0 -0
  46. {nonebot_plugin_parser-2.1.0 → nonebot_plugin_parser-2.1.1}/src/nonebot_plugin_parser/renders/resources/media_button.png +0 -0
  47. {nonebot_plugin_parser-2.1.0 → nonebot_plugin_parser-2.1.1}/src/nonebot_plugin_parser/renders/resources/tiktok.png +0 -0
  48. {nonebot_plugin_parser-2.1.0 → nonebot_plugin_parser-2.1.1}/src/nonebot_plugin_parser/renders/resources/twitter.png +0 -0
  49. {nonebot_plugin_parser-2.1.0 → nonebot_plugin_parser-2.1.1}/src/nonebot_plugin_parser/renders/resources/weibo.png +0 -0
  50. {nonebot_plugin_parser-2.1.0 → nonebot_plugin_parser-2.1.1}/src/nonebot_plugin_parser/renders/resources/xiaohongshu.png +0 -0
  51. {nonebot_plugin_parser-2.1.0 → nonebot_plugin_parser-2.1.1}/src/nonebot_plugin_parser/renders/resources/youtube.png +0 -0
  52. {nonebot_plugin_parser-2.1.0 → nonebot_plugin_parser-2.1.1}/src/nonebot_plugin_parser/renders/templates/weibo.html.jinja +0 -0
  53. {nonebot_plugin_parser-2.1.0 → nonebot_plugin_parser-2.1.1}/src/nonebot_plugin_parser/renders/weibo.py +0 -0
  54. {nonebot_plugin_parser-2.1.0 → nonebot_plugin_parser-2.1.1}/src/nonebot_plugin_parser/utils.py +0 -0
@@ -1,30 +1,47 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: nonebot-plugin-parser
3
- Version: 2.1.0
3
+ Version: 2.1.1
4
4
  Summary: NoneBot2 链接分享解析 Alconna 版, 通用媒体卡片渲染(PIL 实现), 支持 B站/抖音/快手/微博/小红书/youtube/tiktok/twitter/acfun/nga
5
- Keywords: nonebot,nonebot2,video,bilibili,youtube,tiktok,twitter,kuaishou,acfun,weibo,xiaohongshu,nga,douyin
5
+ Keywords: acfun,bilibili,douyin,kuaishou,nga,nonebot,nonebot2,tiktok,twitter,video,weibo,xiaohongshu,youtube
6
6
  Author: fllesser
7
7
  Author-email: fllesser <fllessive@gmail.com>
8
- Requires-Dist: msgspec>=0.19.0,<1.0.0
9
- Requires-Dist: httpx>=0.27.2,<1.0.0
10
- Requires-Dist: tqdm>=4.67.1,<5.0.0
8
+ Classifier: Development Status :: 5 - Production/Stable
9
+ Classifier: Framework :: AsyncIO
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Operating System :: OS Independent
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3 :: Only
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Topic :: Communications :: Chat
18
+ Classifier: Topic :: Internet :: WWW/HTTP
19
+ Classifier: Topic :: Multimedia :: Video
11
20
  Requires-Dist: aiofiles>=25.1.0
12
- Requires-Dist: pillow>=11.0.0
13
- Requires-Dist: curl-cffi>=0.13.0,<1.0.0
14
- Requires-Dist: bilibili-api-python>=17.4.0,<18.0.0
15
21
  Requires-Dist: beautifulsoup4>=4.12.0,<5.0.0
16
- Requires-Dist: yt-dlp>=2025.10.14
17
- Requires-Dist: nonebot2>=2.4.3,<3.0.0
18
- Requires-Dist: nonebot-plugin-localstore>=0.7.4,<1.0.0
19
- Requires-Dist: nonebot-plugin-apscheduler>=0.5.0,<1.0.0
22
+ Requires-Dist: bilibili-api-python>=17.4.0,<18.0.0
23
+ Requires-Dist: curl-cffi>=0.13.0,<1.0.0
24
+ Requires-Dist: httpx>=0.27.2,<1.0.0
25
+ Requires-Dist: msgspec>=0.19.0,<1.0.0
20
26
  Requires-Dist: nonebot-plugin-alconna>=0.59.4
21
- Requires-Dist: nonebot-plugin-uninfo>=0.9.0
22
- Requires-Dist: nonebot-plugin-htmlkit>=0.1.0rc3 ; extra == 'htmlkit'
27
+ Requires-Dist: nonebot-plugin-apscheduler>=0.5.0,<1.0.0
28
+ Requires-Dist: nonebot-plugin-localstore>=0.7.4,<1.0.0
29
+ Requires-Dist: nonebot-plugin-uninfo>=0.10.0
30
+ Requires-Dist: nonebot2>=2.4.3,<3.0.0
31
+ Requires-Dist: pillow>=11.0.0
32
+ Requires-Dist: pilmoji-for-parser
33
+ Requires-Dist: tqdm>=4.67.1,<5.0.0
34
+ Requires-Dist: nonebot-plugin-htmlkit>=0.1.0rc4 ; extra == 'all'
35
+ Requires-Dist: yt-dlp>=2025.10.14 ; extra == 'all'
36
+ Requires-Dist: nonebot-plugin-htmlkit>=0.1.0rc4 ; extra == 'htmlkit'
37
+ Requires-Dist: yt-dlp>=2025.10.14 ; extra == 'ytdlp'
23
38
  Requires-Python: >=3.10
24
39
  Project-URL: IssueTracker, https://github.com/fllesser/nonebot-plugin-parser/issues
25
40
  Project-URL: Release, https://github.com/fllesser/nonebot-plugin-parser/releases
26
41
  Project-URL: Repository, https://github.com/fllesser/nonebot-plugin-parser
42
+ Provides-Extra: all
27
43
  Provides-Extra: htmlkit
44
+ Provides-Extra: ytdlp
28
45
  Description-Content-Type: text/markdown
29
46
 
30
47
  <div align="center">
@@ -80,7 +97,10 @@ Description-Content-Type: text/markdown
80
97
  ## 💿 安装
81
98
  > [!Warning]
82
99
  > **如果你已经在使用 nonebot-plugin-resolver[2],请在安装此插件前卸载**
83
-
100
+
101
+ > [!Important]
102
+ > 插件可选依赖 `htmlkit`, `ytdlp`, `all`,分别用于 htmlkit 渲染和 youtube / tiktok 解析,如果需要使用,请在安装时指定,如 `nb plugin install nonebot-plugin-parser[ytdlp]`
103
+
84
104
  <details open>
85
105
  <summary>使用 nb-cli 安装/更新</summary>
86
106
  在 nonebot2 项目的根目录下打开命令行, 输入以下指令即可安装
@@ -51,7 +51,10 @@
51
51
  ## 💿 安装
52
52
  > [!Warning]
53
53
  > **如果你已经在使用 nonebot-plugin-resolver[2],请在安装此插件前卸载**
54
-
54
+
55
+ > [!Important]
56
+ > 插件可选依赖 `htmlkit`, `ytdlp`, `all`,分别用于 htmlkit 渲染和 youtube / tiktok 解析,如果需要使用,请在安装时指定,如 `nb plugin install nonebot-plugin-parser[ytdlp]`
57
+
55
58
  <details open>
56
59
  <summary>使用 nb-cli 安装/更新</summary>
57
60
  在 nonebot2 项目的根目录下打开命令行, 输入以下指令即可安装
@@ -1,87 +1,112 @@
1
1
  [project]
2
2
  name = "nonebot-plugin-parser"
3
- version = "2.1.0"
3
+ version = "2.1.1"
4
4
  description = "NoneBot2 链接分享解析 Alconna 版, 通用媒体卡片渲染(PIL 实现), 支持 B站/抖音/快手/微博/小红书/youtube/tiktok/twitter/acfun/nga"
5
- authors = [{ "name" = "fllesser", "email" = "fllessive@gmail.com" }]
6
5
  readme = "README.md"
7
6
  requires-python = ">=3.10"
7
+ authors = [{ "name" = "fllesser", "email" = "fllessive@gmail.com" }]
8
8
  keywords = [
9
+ "acfun",
10
+ "bilibili",
11
+ "douyin",
12
+ "kuaishou",
13
+ "nga",
9
14
  "nonebot",
10
15
  "nonebot2",
11
- "video",
12
- "bilibili",
13
- "youtube",
14
16
  "tiktok",
15
17
  "twitter",
16
- "kuaishou",
17
- "acfun",
18
+ "video",
18
19
  "weibo",
19
20
  "xiaohongshu",
20
- "nga",
21
- "douyin",
21
+ "youtube"
22
+ ]
23
+ classifiers = [
24
+ "Development Status :: 5 - Production/Stable",
25
+ "Framework :: AsyncIO",
26
+ "License :: OSI Approved :: MIT License",
27
+ "Operating System :: OS Independent",
28
+ "Programming Language :: Python :: 3",
29
+ "Programming Language :: Python :: 3 :: Only",
30
+ "Programming Language :: Python :: 3.10",
31
+ "Programming Language :: Python :: 3.11",
32
+ "Programming Language :: Python :: 3.12",
33
+ "Topic :: Communications :: Chat",
34
+ "Topic :: Internet :: WWW/HTTP",
35
+ "Topic :: Multimedia :: Video",
22
36
  ]
23
37
  dependencies = [
24
- "msgspec>=0.19.0,<1.0.0",
25
- "httpx>=0.27.2,<1.0.0",
26
- "tqdm>=4.67.1,<5.0.0",
27
38
  "aiofiles>=25.1.0",
28
- "pillow>=11.0.0",
29
-
30
- # 后续改为可选依赖
31
- "curl_cffi>=0.13.0,<1.0.0",
32
- "bilibili-api-python>=17.4.0,<18.0.0",
33
39
  "beautifulsoup4>=4.12.0,<5.0.0",
34
- "yt-dlp>=2025.10.14",
35
-
36
- "nonebot2>=2.4.3,<3.0.0",
37
- "nonebot-plugin-localstore>=0.7.4,<1.0.0",
38
- "nonebot-plugin-apscheduler>=0.5.0,<1.0.0",
40
+ "bilibili-api-python>=17.4.0,<18.0.0",
41
+ "curl_cffi>=0.13.0,<1.0.0",
42
+ "httpx>=0.27.2,<1.0.0",
43
+ "msgspec>=0.19.0,<1.0.0",
39
44
  "nonebot-plugin-alconna>=0.59.4",
40
- "nonebot-plugin-uninfo>=0.9.0",
45
+ "nonebot-plugin-apscheduler>=0.5.0,<1.0.0",
46
+ "nonebot-plugin-localstore>=0.7.4,<1.0.0",
47
+ "nonebot-plugin-uninfo>=0.10.0",
48
+ "nonebot2>=2.4.3,<3.0.0",
49
+ "pillow>=11.0.0",
50
+ "pilmoji-for-parser",
51
+ "tqdm>=4.67.1,<5.0.0",
41
52
  ]
42
53
 
43
- [project.optional-dependencies]
44
- htmlkit = ["nonebot-plugin-htmlkit>=0.1.0rc3"]
45
-
46
54
  [project.urls]
47
- Repository = "https://github.com/fllesser/nonebot-plugin-parser"
48
55
  IssueTracker = "https://github.com/fllesser/nonebot-plugin-parser/issues"
49
56
  Release = "https://github.com/fllesser/nonebot-plugin-parser/releases"
57
+ Repository = "https://github.com/fllesser/nonebot-plugin-parser"
58
+
59
+ [project.optional-dependencies]
60
+ all = ["nonebot-plugin-htmlkit>=0.1.0rc4", "yt-dlp>=2025.10.14"]
61
+ htmlkit = ["nonebot-plugin-htmlkit>=0.1.0rc4"]
62
+ ytdlp = ["yt-dlp>=2025.10.14"]
50
63
 
51
64
  [dependency-groups]
52
65
  dev = [
53
- "nb-cli>=1.4.2",
54
66
  "nonebot2[fastapi]>=2.4.3,<3.0.0",
55
67
  "pre-commit>=4.3.0",
56
- "ruff>=0.14.0,<1.0.0",
68
+ "ruff>=0.14.3,<1.0.0",
57
69
  ]
58
-
70
+ extras = ["nonebot-plugin-htmlkit>=0.1.0rc4", "yt-dlp>=2025.10.14"]
71
+ pydantic-v1 = ["pydantic<2.0.0"] # renovate:ignore
72
+ pydantic-v2 = ["pydantic>=2.0.0"]
73
+ telegram = ["nonebot-adapter-telegram>=0.1.0b20"]
59
74
  test = [
60
- "nonebot2[fastapi]>=2.4.3,<3.0.0",
61
75
  "nonebot-adapter-onebot>=2.4.6",
76
+ "nonebot2[fastapi]>=2.4.3,<3.0.0",
62
77
  "nonebug>=0.4.3,<1.0.0",
63
- "pytest-xdist>=3.8.0,<4.0.0",
78
+ "poethepoet>=0.37.0",
64
79
  "pytest-asyncio>=1.2.0,<1.3.0",
65
80
  "pytest-cov>=7.0.0",
66
- "poethepoet>=0.37.0",
81
+ "pytest-xdist>=3.8.0,<4.0.0",
67
82
  "respx>=0.22.0",
68
83
  ]
69
84
 
70
- telegram = ["nonebot-adapter-telegram>=0.1.0b20"]
71
- pydantic-v1 = ["pydantic<2.0.0"] # renovate:ignore
72
- pydantic-v2 = ["pydantic>=2.0.0"]
85
+ [build-system]
86
+ requires = ["uv_build>=0.9.0,<0.10.0"]
87
+ build-backend = "uv_build"
88
+
89
+ [tool.bumpversion]
90
+ current_version = "2.1.1"
91
+ commit = true
92
+ message = "🔖 release: bump vesion from {current_version} to {new_version}"
93
+ tag = true
73
94
 
74
- extras = ["nonebot-plugin-htmlkit>=0.1.0rc3"]
95
+ [[tool.bumpversion.files]]
96
+ filename = "uv.lock"
97
+ search = "name = \"nonebot-plugin-parser\"\nversion = \"{current_version}\""
98
+ replace = "name = \"nonebot-plugin-parser\"\nversion = \"{new_version}\""
75
99
 
76
- [tool.uv]
77
- required-version = ">=0.9.2"
78
- default-groups = ["test", "dev", "extras"]
79
- conflicts = [
80
- [
81
- { group = "pydantic-v1" },
82
- { group = "pydantic-v2" },
83
- { group = "telegram" },
84
- ],
100
+ [tool.coverage.report]
101
+ show_missing = true # 相当于 --cov-report=term-missing:cite[1]
102
+ # omit = [ # 排除不需要统计的文件
103
+ # ]
104
+ exclude_lines = [
105
+ "raise NotImplementedError",
106
+ "if TYPE_CHECKING:",
107
+ "@overload",
108
+ "except",
109
+ "raise",
85
110
  ]
86
111
 
87
112
  [tool.nonebot]
@@ -91,20 +116,6 @@ adapters = [
91
116
  ]
92
117
  plugins = ["nonebot_plugin_parser"]
93
118
 
94
-
95
- [tool.pytest.ini_options]
96
- asyncio_mode = "auto"
97
- asyncio_default_fixture_loop_scope = "session"
98
- pythonpath = ["src"]
99
- addopts = [
100
- "-v", # 详细输出
101
- "-s", # 显示打印信息
102
- "--tb=short", # 简短的错误回溯
103
- "-ra", # 显示所有测试结果摘要
104
- "--strict-markers", # 严格标记模式
105
- "--import-mode=prepend", # 导入模式
106
- ]
107
-
108
119
  [tool.poe.tasks]
109
120
  test_others = "pytest tests/others --cov=src --cov-report=xml:coverage1.xml --junitxml=junit1.xml -n auto"
110
121
  test_parsers = "pytest tests/parsers --cov=src --cov-report=xml:coverage2.xml --junitxml=junit2.xml -n auto"
@@ -112,19 +123,31 @@ test_render = "pytest tests/render --cov=src --cov-report=xml:coverage3.xml --ju
112
123
  bump = "bump-my-version bump"
113
124
  show-bump = "bump-my-version show-bump"
114
125
 
126
+ [tool.pyright]
127
+ pythonVersion = "3.10"
128
+ pythonPlatform = "All"
129
+ defineConstant = { PYDANTIC_V2 = true }
130
+ executionEnvironments = [
131
+ { root = "./tests", extraPaths = [
132
+ "./src",
133
+ ] },
134
+ { root = "./src" },
135
+ ]
136
+ typeCheckingMode = "standard"
137
+ disableBytesTypePromotions = true
115
138
 
116
- [tool.coverage.report]
117
- show_missing = true # 相当于 --cov-report=term-missing:cite[1]
118
- # omit = [ # 排除不需要统计的文件
119
-
120
- # ]
121
- exclude_lines = [
122
- "raise NotImplementedError",
123
- "if TYPE_CHECKING:",
124
- "@overload",
125
- "except",
126
- "raise",
139
+ [tool.pytest.ini_options]
140
+ addopts = [
141
+ "-v", # 详细输出
142
+ "-s", # 显示打印信息
143
+ "--tb=short", # 简短的错误回溯
144
+ "-ra", # 显示所有测试结果摘要
145
+ "--strict-markers", # 严格标记模式
146
+ "--import-mode=prepend", # 导入模式
127
147
  ]
148
+ pythonpath = ["src"]
149
+ asyncio_mode = "auto"
150
+ asyncio_default_fixture_loop_scope = "session"
128
151
 
129
152
  [tool.ruff]
130
153
  line-length = 120
@@ -135,33 +158,32 @@ line-ending = "lf"
135
158
 
136
159
  [tool.ruff.lint]
137
160
  select = [
138
- "F", # Pyflakes
139
- "W", # pycodestyle warnings
140
- "E", # pycodestyle errors
141
- "I", # isort
142
- "UP", # pyupgrade
143
- "ASYNC", # flake8-async
144
- "C4", # flake8-comprehensions
145
- "T10", # flake8-debugger
146
- "T20", # flake8-print
147
- "PYI", # flake8-pyi
148
- "PT", # flake8-pytest-style
149
- "Q", # flake8-quotes
150
- "TID", # flake8-tidy-imports
151
- "RUF", # Ruff-specific rules
161
+ "F", # Pyflakes
162
+ "W", # pycodestyle warnings
163
+ "E", # pycodestyle errors
164
+ "I", # isort
165
+ "UP", # pyupgrade
166
+ "ASYNC", # flake8-async
167
+ "C4", # flake8-comprehensions
168
+ "T10", # flake8-debugger
169
+ "T20", # flake8-print
170
+ "PYI", # flake8-pyi
171
+ "PT", # flake8-pytest-style
172
+ "Q", # flake8-quotes
173
+ "TID", # flake8-tidy-imports
174
+ "RUF", # Ruff-specific rules
152
175
  ]
153
176
  ignore = [
154
- "E402", # module-import-not-at-top-of-file
177
+ "E402", # module-import-not-at-top-of-file
155
178
  "UP037", # quoted-annotation
156
- "RUF001", # ambiguous-unicode-character-string
157
- "RUF002", # ambiguous-unicode-character-docstring
158
- "RUF003", # ambiguous-unicode-character-comment
159
- "W191", # indentation contains tabs
179
+ "RUF001", # ambiguous-unicode-character-string
180
+ "RUF002", # ambiguous-unicode-character-docstring
181
+ "RUF003", # ambiguous-unicode-character-comment
182
+ "W191", # indentation contains tabs
160
183
  # "I001", # isort: imports are incorrectly sorted
161
- "TID252", # 相对导入
184
+ "TID252", # 相对导入
162
185
  ]
163
186
 
164
-
165
187
  [tool.ruff.lint.isort]
166
188
  force-sort-within-sections = true
167
189
  known-first-party = ["tests/*"]
@@ -174,33 +196,13 @@ mark-parentheses = false
174
196
  [tool.ruff.lint.pyupgrade]
175
197
  keep-runtime-typing = true
176
198
 
177
-
178
- [tool.pyright]
179
- pythonVersion = "3.10"
180
- pythonPlatform = "All"
181
- defineConstant = { PYDANTIC_V2 = true }
182
- executionEnvironments = [
183
- { root = "./tests", extraPaths = [
184
- "./src",
185
- ] },
186
- { root = "./src" },
199
+ [tool.uv]
200
+ required-version = ">=0.9.7"
201
+ default-groups = ["test", "dev", "extras"]
202
+ conflicts = [
203
+ [
204
+ { group = "pydantic-v1" },
205
+ { group = "pydantic-v2" },
206
+ { group = "telegram" },
207
+ ],
187
208
  ]
188
- typeCheckingMode = "standard"
189
- reportShadowedImports = false
190
- disableBytesTypePromotions = true
191
-
192
- [build-system]
193
- requires = ["uv_build>=0.9.0,<0.10.0"]
194
- build-backend = "uv_build"
195
-
196
-
197
- [tool.bumpversion]
198
- current_version = "2.1.0"
199
- commit = true
200
- message = "🔖 release: bump vesion from {current_version} to {new_version}"
201
- tag = true
202
-
203
- [[tool.bumpversion.files]]
204
- filename = "uv.lock"
205
- search = "name = \"nonebot-plugin-parser\"\nversion = \"{current_version}\""
206
- replace = "name = \"nonebot-plugin-parser\"\nversion = \"{new_version}\""
@@ -1,11 +1,12 @@
1
1
  from enum import Enum
2
2
  from pathlib import Path
3
- from typing import Literal
4
3
 
5
4
  from bilibili_api.video import VideoCodecs
6
5
  from nonebot import get_driver, get_plugin_config, require
7
6
  from pydantic import BaseModel
8
7
 
8
+ from .constants import PlatformEnum
9
+
9
10
  _nickname: str = next(iter(get_driver().config.nickname), "")
10
11
  """全局名称"""
11
12
 
@@ -17,11 +18,6 @@ _config_dir: Path = _store.get_plugin_config_dir()
17
18
  _data_dir: Path = _store.get_plugin_data_dir()
18
19
 
19
20
 
20
- PlatformNames = Literal[
21
- "bilibili", "acfun", "douyin", "youtube", "kuaishou", "twitter", "tiktok", "weibo", "xiaohongshu", "nga"
22
- ]
23
-
24
-
25
21
  class RenderType(str, Enum):
26
22
  default = "default"
27
23
  common = "common"
@@ -45,7 +41,7 @@ class Config(BaseModel):
45
41
  """视频/音频最大时长"""
46
42
  parser_append_url: bool = False
47
43
  """是否在解析结果中附加原始URL"""
48
- parser_disabled_platforms: list[PlatformNames] = []
44
+ parser_disabled_platforms: list[PlatformEnum] = []
49
45
  """禁止的解析器"""
50
46
  parser_bili_video_codes: list[VideoCodecs] = [VideoCodecs.AVC, VideoCodecs.AV1, VideoCodecs.HEV]
51
47
  """B站视频编码"""
@@ -87,7 +83,7 @@ class Config(BaseModel):
87
83
  return self.parser_duration_maximum
88
84
 
89
85
  @property
90
- def disabled_platforms(self) -> list[PlatformNames]:
86
+ def disabled_platforms(self) -> list[PlatformEnum]:
91
87
  """禁止的解析器"""
92
88
  return self.parser_disabled_platforms
93
89
 
@@ -1,3 +1,4 @@
1
+ from enum import Enum
1
2
  from typing import Final
2
3
 
3
4
  from httpx import Timeout
@@ -20,3 +21,19 @@ ANDROID_HEADER: Final[dict[str, str]] = {
20
21
  COMMON_TIMEOUT: Final[Timeout] = Timeout(connect=15.0, read=20.0, write=10.0, pool=10.0)
21
22
 
22
23
  DOWNLOAD_TIMEOUT: Final[Timeout] = Timeout(connect=15.0, read=240.0, write=10.0, pool=10.0)
24
+
25
+
26
+ class PlatformEnum(str, Enum):
27
+ ACFUN = "acfun"
28
+ BILIBILI = "bilibili"
29
+ DOUYIN = "douyin"
30
+ KUAISHOU = "kuaishou"
31
+ NGA = "nga"
32
+ TIKTOK = "tiktok"
33
+ TWITTER = "twitter"
34
+ WEIBO = "weibo"
35
+ XIAOHONGSHU = "xiaohongshu"
36
+ YOUTUBE = "youtube"
37
+
38
+ def __str__(self) -> str:
39
+ return self.value
@@ -2,7 +2,7 @@ import asyncio
2
2
  from pathlib import Path
3
3
 
4
4
  import aiofiles
5
- import httpx
5
+ from httpx import AsyncClient, HTTPError
6
6
  from nonebot import logger
7
7
  from tqdm.asyncio import tqdm
8
8
 
@@ -11,7 +11,6 @@ from ..constants import COMMON_HEADER, DOWNLOAD_TIMEOUT
11
11
  from ..exception import DownloadException, SizeLimitException, ZeroSizeException
12
12
  from ..utils import generate_file_name, merge_av, safe_unlink
13
13
  from .task import auto_task
14
- from .ytdlp import YtdlpDownloader
15
14
 
16
15
 
17
16
  class StreamDownloader:
@@ -20,10 +19,7 @@ class StreamDownloader:
20
19
  def __init__(self):
21
20
  self.headers: dict[str, str] = COMMON_HEADER.copy()
22
21
  self.cache_dir: Path = pconfig.cache_dir
23
- self.client: httpx.AsyncClient = httpx.AsyncClient(
24
- timeout=DOWNLOAD_TIMEOUT,
25
- verify=False,
26
- )
22
+ self.client: AsyncClient = AsyncClient(timeout=DOWNLOAD_TIMEOUT, verify=False)
27
23
 
28
24
  @auto_task
29
25
  async def streamd(
@@ -76,7 +72,7 @@ class StreamDownloader:
76
72
  await file.write(chunk)
77
73
  bar.update(len(chunk))
78
74
 
79
- except httpx.HTTPError:
75
+ except HTTPError:
80
76
  await safe_unlink(file_path)
81
77
  logger.exception(f"下载失败 | url: {url}, file_path: {file_path}")
82
78
  raise DownloadException("媒体下载失败")
@@ -217,4 +213,12 @@ class StreamDownloader:
217
213
 
218
214
 
219
215
  DOWNLOADER: StreamDownloader = StreamDownloader()
220
- YTDLP_DOWNLOADER: YtdlpDownloader = YtdlpDownloader()
216
+
217
+ try:
218
+ import yt_dlp as yt_dlp
219
+
220
+ from .ytdlp import YtdlpDownloader
221
+
222
+ YTDLP_DOWNLOADER = YtdlpDownloader()
223
+ except ImportError:
224
+ YTDLP_DOWNLOADER = None
@@ -3,8 +3,8 @@ from collections.abc import Callable, Coroutine
3
3
  from functools import wraps
4
4
  from typing import Any, ParamSpec, TypeVar
5
5
 
6
- P = ParamSpec("P") # 保留参数类型
7
- T = TypeVar("T") # 保留返回类型
6
+ P = ParamSpec("P")
7
+ T = TypeVar("T")
8
8
 
9
9
 
10
10
  def auto_task(func: Callable[P, Coroutine[Any, Any, T]]) -> Callable[P, Task[T]]:
@@ -2,8 +2,7 @@ import asyncio
2
2
  from pathlib import Path
3
3
  from typing import Any
4
4
 
5
- import msgspec
6
- from msgspec import Struct
5
+ from msgspec import Struct, convert
7
6
  import yt_dlp
8
7
 
9
8
  from ..config import pconfig
@@ -73,7 +72,7 @@ class YtdlpDownloader:
73
72
  if not info_dict:
74
73
  raise ParseException("获取视频信息失败")
75
74
 
76
- video_info = msgspec.convert(info_dict, VideoInfo)
75
+ video_info = convert(info_dict, VideoInfo)
77
76
  self._video_info_mapping[url] = video_info
78
77
  return video_info
79
78
 
@@ -1,18 +1,16 @@
1
1
  """统一的解析器 matcher"""
2
2
 
3
- import re
4
3
  from typing import Literal
5
4
 
6
5
  from nonebot import get_driver, logger
7
6
  from nonebot.adapters import Event
8
7
  from nonebot_plugin_alconna import SupportAdapter
9
- from nonebot_plugin_alconna.uniseg import get_message_id, get_target, message_reaction
10
8
 
11
9
  from ..config import pconfig
12
10
  from ..parsers import BaseParser, ParseResult
13
11
  from ..renders import get_renderer
14
12
  from ..utils import LimitedSizeDict
15
- from .preprocess import Keyword, KwdRegexMatched, on_keyword_regex
13
+ from .rule import Searched, SearchResult, on_keyword_regex
16
14
 
17
15
 
18
16
  def _get_enabled_parser_classes() -> list[type[BaseParser]]:
@@ -21,28 +19,24 @@ def _get_enabled_parser_classes() -> list[type[BaseParser]]:
21
19
  return [_cls for _cls in all_subclass if _cls.platform.name not in disabled_platforms]
22
20
 
23
21
 
24
- ENABLED_PARSER_CLASSES = _get_enabled_parser_classes()
25
-
26
-
27
- def _get_enabled_patterns() -> list[tuple[str, str]]:
28
- """根据配置获取启用的平台正则表达式列表"""
29
-
30
- return [pattern for _cls in ENABLED_PARSER_CLASSES for pattern in _cls.patterns]
31
-
32
-
33
22
  # 关键词 Parser 映射
34
23
  KEYWORD_PARSER_MAP: dict[str, BaseParser] = {}
35
24
 
36
25
 
37
26
  @get_driver().on_startup
38
- def build_keyword_parsers_map():
27
+ def register_parser_matcher():
28
+ enabled_parser_classes = _get_enabled_parser_classes()
29
+
39
30
  enabled_platform_names = []
40
- for _cls in ENABLED_PARSER_CLASSES:
31
+ for _cls in enabled_parser_classes:
41
32
  parser = _cls()
42
33
  enabled_platform_names.append(parser.platform.display_name)
43
34
  for keyword, _ in _cls.patterns:
44
35
  KEYWORD_PARSER_MAP[keyword] = parser
45
- logger.info(f"启动的平台: {', '.join(sorted(enabled_platform_names))}")
36
+ logger.info(f"启用平台: {', '.join(sorted(enabled_platform_names))}")
37
+
38
+ parser_matcher = on_keyword_regex(*[pattern for _cls in enabled_parser_classes for pattern in _cls.patterns])
39
+ parser_matcher.append_handler(parser_handler)
46
40
 
47
41
 
48
42
  # 缓存结果
@@ -53,28 +47,24 @@ def clear_result_cache():
53
47
  _RESULT_CACHE.clear()
54
48
 
55
49
 
56
- parser_matcher = on_keyword_regex(*_get_enabled_patterns())
57
-
58
-
59
- @parser_matcher.handle()
60
- async def _(
50
+ async def parser_handler(
61
51
  event: Event,
62
- keyword: str = Keyword(),
63
- matched: re.Match[str] = KwdRegexMatched(),
52
+ sr: SearchResult = Searched(),
64
53
  ):
65
54
  """统一的解析处理器"""
66
55
  # 响应用户处理中
67
56
  await _message_reaction(event, "resolving")
68
57
 
69
- cache_key = matched.group(0)
70
58
  # 1. 获取缓存结果
59
+ cache_key = sr.searched.group(0)
71
60
  result = _RESULT_CACHE.get(cache_key)
61
+
72
62
  if result is None:
73
63
  # 2. 获取对应平台 parser
74
- parser = KEYWORD_PARSER_MAP[keyword]
64
+ parser = KEYWORD_PARSER_MAP[sr.keyword]
75
65
 
76
66
  try:
77
- result = await parser.parse(matched)
67
+ result = await parser.parse(sr.keyword, sr.searched)
78
68
  except Exception:
79
69
  # await UniMessage(str(e)).send()
80
70
  await _message_reaction(event, "fail")
@@ -99,17 +89,23 @@ async def _(
99
89
  await _message_reaction(event, "done")
100
90
 
101
91
 
102
- async def _message_reaction(event: Event, status: Literal["fail", "resolving", "done"]) -> None:
92
+ from nonebot_plugin_alconna import uniseg
93
+
94
+
95
+ async def _message_reaction(
96
+ event: Event,
97
+ status: Literal["fail", "resolving", "done"],
98
+ ) -> None:
103
99
  emoji_map = {
104
100
  "fail": ["10060", "❌"],
105
101
  "resolving": ["424", "👀"],
106
102
  "done": ["144", "🎉"],
107
103
  }
108
- message_id = get_message_id(event)
109
- target = get_target(event)
104
+ message_id = uniseg.get_message_id(event)
105
+ target = uniseg.get_target(event)
110
106
  if target.adapter == SupportAdapter.onebot11:
111
107
  emoji = emoji_map[status][0]
112
108
  else:
113
109
  emoji = emoji_map[status][1]
114
110
 
115
- await message_reaction(emoji, message_id=message_id)
111
+ await uniseg.message_reaction(emoji, message_id=message_id)