nonebot-plugin-parser 2.2.2__tar.gz → 2.2.3__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.2.2 → nonebot_plugin_parser-2.2.3}/PKG-INFO +14 -10
  2. {nonebot_plugin_parser-2.2.2 → nonebot_plugin_parser-2.2.3}/README.md +4 -1
  3. {nonebot_plugin_parser-2.2.2 → nonebot_plugin_parser-2.2.3}/pyproject.toml +72 -72
  4. {nonebot_plugin_parser-2.2.2 → nonebot_plugin_parser-2.2.3}/src/nonebot_plugin_parser/config.py +12 -6
  5. {nonebot_plugin_parser-2.2.2 → nonebot_plugin_parser-2.2.3}/src/nonebot_plugin_parser/download/__init__.py +7 -7
  6. {nonebot_plugin_parser-2.2.2 → nonebot_plugin_parser-2.2.3}/src/nonebot_plugin_parser/download/task.py +2 -2
  7. {nonebot_plugin_parser-2.2.2 → nonebot_plugin_parser-2.2.3}/src/nonebot_plugin_parser/download/ytdlp.py +33 -31
  8. {nonebot_plugin_parser-2.2.2 → nonebot_plugin_parser-2.2.3}/src/nonebot_plugin_parser/helper.py +9 -9
  9. {nonebot_plugin_parser-2.2.2 → nonebot_plugin_parser-2.2.3}/src/nonebot_plugin_parser/matchers/__init__.py +5 -5
  10. {nonebot_plugin_parser-2.2.2 → nonebot_plugin_parser-2.2.3}/src/nonebot_plugin_parser/parsers/__init__.py +18 -15
  11. {nonebot_plugin_parser-2.2.2 → nonebot_plugin_parser-2.2.3}/src/nonebot_plugin_parser/parsers/acfun.py +7 -7
  12. {nonebot_plugin_parser-2.2.2 → nonebot_plugin_parser-2.2.3}/src/nonebot_plugin_parser/parsers/base.py +10 -10
  13. {nonebot_plugin_parser-2.2.2 → nonebot_plugin_parser-2.2.3}/src/nonebot_plugin_parser/parsers/bilibili/__init__.py +11 -11
  14. {nonebot_plugin_parser-2.2.2 → nonebot_plugin_parser-2.2.3}/src/nonebot_plugin_parser/parsers/bilibili/opus.py +5 -2
  15. {nonebot_plugin_parser-2.2.2 → nonebot_plugin_parser-2.2.3}/src/nonebot_plugin_parser/parsers/data.py +4 -4
  16. {nonebot_plugin_parser-2.2.2 → nonebot_plugin_parser-2.2.3}/src/nonebot_plugin_parser/parsers/douyin/__init__.py +13 -3
  17. {nonebot_plugin_parser-2.2.2 → nonebot_plugin_parser-2.2.3}/src/nonebot_plugin_parser/parsers/kuaishou.py +7 -7
  18. {nonebot_plugin_parser-2.2.2 → nonebot_plugin_parser-2.2.3}/src/nonebot_plugin_parser/parsers/nga.py +6 -6
  19. {nonebot_plugin_parser-2.2.2 → nonebot_plugin_parser-2.2.3}/src/nonebot_plugin_parser/parsers/twitter.py +4 -4
  20. {nonebot_plugin_parser-2.2.2 → nonebot_plugin_parser-2.2.3}/src/nonebot_plugin_parser/parsers/weibo.py +4 -4
  21. {nonebot_plugin_parser-2.2.2 → nonebot_plugin_parser-2.2.3}/src/nonebot_plugin_parser/parsers/xiaohongshu.py +12 -6
  22. {nonebot_plugin_parser-2.2.2 → nonebot_plugin_parser-2.2.3}/src/nonebot_plugin_parser/parsers/youtube.py +12 -5
  23. {nonebot_plugin_parser-2.2.2 → nonebot_plugin_parser-2.2.3}/src/nonebot_plugin_parser/renders/base.py +14 -7
  24. {nonebot_plugin_parser-2.2.2 → nonebot_plugin_parser-2.2.3}/src/nonebot_plugin_parser/renders/common.py +36 -12
  25. {nonebot_plugin_parser-2.2.2 → nonebot_plugin_parser-2.2.3}/src/nonebot_plugin_parser/renders/default.py +2 -2
  26. {nonebot_plugin_parser-2.2.2 → nonebot_plugin_parser-2.2.3}/src/nonebot_plugin_parser/renders/weibo.py +1 -1
  27. {nonebot_plugin_parser-2.2.2 → nonebot_plugin_parser-2.2.3}/src/nonebot_plugin_parser/__init__.py +1 -1
  28. {nonebot_plugin_parser-2.2.2 → nonebot_plugin_parser-2.2.3}/src/nonebot_plugin_parser/constants.py +0 -0
  29. {nonebot_plugin_parser-2.2.2 → nonebot_plugin_parser-2.2.3}/src/nonebot_plugin_parser/exception.py +0 -0
  30. {nonebot_plugin_parser-2.2.2 → nonebot_plugin_parser-2.2.3}/src/nonebot_plugin_parser/matchers/filter.py +1 -1
  31. {nonebot_plugin_parser-2.2.2 → nonebot_plugin_parser-2.2.3}/src/nonebot_plugin_parser/matchers/rule.py +4 -4
  32. {nonebot_plugin_parser-2.2.2 → nonebot_plugin_parser-2.2.3}/src/nonebot_plugin_parser/parsers/bilibili/article.py +1 -1
  33. {nonebot_plugin_parser-2.2.2 → nonebot_plugin_parser-2.2.3}/src/nonebot_plugin_parser/parsers/bilibili/common.py +0 -0
  34. {nonebot_plugin_parser-2.2.2 → nonebot_plugin_parser-2.2.3}/src/nonebot_plugin_parser/parsers/bilibili/dynamic.py +0 -0
  35. {nonebot_plugin_parser-2.2.2 → nonebot_plugin_parser-2.2.3}/src/nonebot_plugin_parser/parsers/bilibili/favlist.py +0 -0
  36. {nonebot_plugin_parser-2.2.2 → nonebot_plugin_parser-2.2.3}/src/nonebot_plugin_parser/parsers/bilibili/live.py +0 -0
  37. {nonebot_plugin_parser-2.2.2 → nonebot_plugin_parser-2.2.3}/src/nonebot_plugin_parser/parsers/bilibili/video.py +0 -0
  38. {nonebot_plugin_parser-2.2.2 → nonebot_plugin_parser-2.2.3}/src/nonebot_plugin_parser/parsers/cookie.py +0 -0
  39. {nonebot_plugin_parser-2.2.2 → nonebot_plugin_parser-2.2.3}/src/nonebot_plugin_parser/parsers/douyin/slides.py +0 -0
  40. {nonebot_plugin_parser-2.2.2 → nonebot_plugin_parser-2.2.3}/src/nonebot_plugin_parser/parsers/douyin/video.py +0 -0
  41. {nonebot_plugin_parser-2.2.2 → nonebot_plugin_parser-2.2.3}/src/nonebot_plugin_parser/parsers/tiktok.py +1 -1
  42. {nonebot_plugin_parser-2.2.2 → nonebot_plugin_parser-2.2.3}/src/nonebot_plugin_parser/renders/__init__.py +1 -1
  43. {nonebot_plugin_parser-2.2.2 → nonebot_plugin_parser-2.2.3}/src/nonebot_plugin_parser/renders/resources/HYSongYunLangHeiW-1.ttf +0 -0
  44. {nonebot_plugin_parser-2.2.2 → nonebot_plugin_parser-2.2.3}/src/nonebot_plugin_parser/renders/resources/bilibili.png +0 -0
  45. {nonebot_plugin_parser-2.2.2 → nonebot_plugin_parser-2.2.3}/src/nonebot_plugin_parser/renders/resources/douyin.png +0 -0
  46. {nonebot_plugin_parser-2.2.2 → nonebot_plugin_parser-2.2.3}/src/nonebot_plugin_parser/renders/resources/kuaishou.png +0 -0
  47. {nonebot_plugin_parser-2.2.2 → nonebot_plugin_parser-2.2.3}/src/nonebot_plugin_parser/renders/resources/media_button.png +0 -0
  48. {nonebot_plugin_parser-2.2.2 → nonebot_plugin_parser-2.2.3}/src/nonebot_plugin_parser/renders/resources/tiktok.png +0 -0
  49. {nonebot_plugin_parser-2.2.2 → nonebot_plugin_parser-2.2.3}/src/nonebot_plugin_parser/renders/resources/twitter.png +0 -0
  50. {nonebot_plugin_parser-2.2.2 → nonebot_plugin_parser-2.2.3}/src/nonebot_plugin_parser/renders/resources/weibo.png +0 -0
  51. {nonebot_plugin_parser-2.2.2 → nonebot_plugin_parser-2.2.3}/src/nonebot_plugin_parser/renders/resources/xiaohongshu.png +0 -0
  52. {nonebot_plugin_parser-2.2.2 → nonebot_plugin_parser-2.2.3}/src/nonebot_plugin_parser/renders/resources/youtube.png +0 -0
  53. {nonebot_plugin_parser-2.2.2 → nonebot_plugin_parser-2.2.3}/src/nonebot_plugin_parser/renders/templates/weibo.html.jinja +0 -0
  54. {nonebot_plugin_parser-2.2.2 → nonebot_plugin_parser-2.2.3}/src/nonebot_plugin_parser/utils.py +3 -3
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: nonebot-plugin-parser
3
- Version: 2.2.2
3
+ Version: 2.2.3
4
4
  Summary: NoneBot2 链接分享解析 Alconna 版, 通用媒体卡片渲染(PIL 实现), 支持 B站/抖音/快手/微博/小红书/youtube/tiktok/twitter/acfun/nga
5
5
  Keywords: acfun,bilibili,douyin,kuaishou,nga,nonebot,nonebot2,tiktok,twitter,video,weibo,xiaohongshu,youtube
6
6
  Author: fllesser
@@ -15,23 +15,24 @@ Classifier: Programming Language :: Python :: 3.10
15
15
  Classifier: Programming Language :: Python :: 3.11
16
16
  Classifier: Programming Language :: Python :: 3.12
17
17
  Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Programming Language :: Python :: 3.14
18
19
  Classifier: Topic :: Communications :: Chat
19
20
  Classifier: Topic :: Internet :: WWW/HTTP
20
21
  Classifier: Topic :: Multimedia :: Video
22
+ Requires-Dist: nonebot2>=2.4.3,<3.0.0
23
+ Requires-Dist: pillow>=11.0.0
24
+ Requires-Dist: pilmoji-for-parser>=0.1.2
25
+ Requires-Dist: tqdm>=4.67.1,<5.0.0
21
26
  Requires-Dist: aiofiles>=25.1.0
22
- Requires-Dist: beautifulsoup4>=4.12.0,<5.0.0
23
- Requires-Dist: bilibili-api-python>=17.4.0,<18.0.0
24
27
  Requires-Dist: curl-cffi>=0.13.0,<1.0.0
25
28
  Requires-Dist: httpx>=0.27.2,<1.0.0
26
29
  Requires-Dist: msgspec>=0.20.0,<1.0.0
27
- Requires-Dist: nonebot-plugin-alconna>=0.59.4
30
+ Requires-Dist: beautifulsoup4>=4.12.0,<5.0.0
31
+ Requires-Dist: bilibili-api-python>=17.4.0,<18.0.0
32
+ Requires-Dist: nonebot-plugin-alconna>=0.59.4,<1.0.0
28
33
  Requires-Dist: nonebot-plugin-apscheduler>=0.5.0,<1.0.0
29
34
  Requires-Dist: nonebot-plugin-localstore>=0.7.4,<1.0.0
30
- Requires-Dist: nonebot-plugin-uninfo>=0.10.0
31
- Requires-Dist: nonebot2>=2.4.3,<3.0.0
32
- Requires-Dist: pillow>=11.0.0
33
- Requires-Dist: pilmoji-for-parser>=0.1.0
34
- Requires-Dist: tqdm>=4.67.1,<5.0.0
35
+ Requires-Dist: nonebot-plugin-uninfo>=0.10.1,<1.0.0
35
36
  Requires-Dist: nonebot-plugin-htmlkit>=0.1.0rc4 ; extra == 'all'
36
37
  Requires-Dist: yt-dlp[default]>=2025.11.12 ; extra == 'all'
37
38
  Requires-Dist: nonebot-plugin-htmlkit>=0.1.0rc4 ; extra == 'htmlkit'
@@ -53,7 +54,7 @@ Description-Content-Type: text/markdown
53
54
  ## ✨ [Nonebot2](https://github.com/nonebot/nonebot2) 链接分享自动解析插件 ✨
54
55
  [![LICENSE](https://img.shields.io/github/license/fllesser/nonebot-plugin-parser.svg)](./LICENSE)
55
56
  [![pypi](https://img.shields.io/pypi/v/nonebot-plugin-parser.svg)](https://pypi.python.org/pypi/nonebot-plugin-parser)
56
- [![python](https://img.shields.io/badge/python-3.10|3.11|3.12|3.13-blue.svg)](https://python.org)
57
+ [![python](https://img.shields.io/badge/python-3.10|3.11|3.12|3.13|3.14-blue.svg)](https://python.org)
57
58
  [![uv](https://img.shields.io/badge/package%20manager-uv-black?style=flat-square&logo=uv)](https://github.com/astral-sh/uv)
58
59
  [![ruff](https://img.shields.io/badge/code%20style-ruff-black?style=flat-square&logo=ruff)](https://github.com/astral-sh/ruff)
59
60
  <br/>
@@ -256,6 +257,9 @@ parser_custom_font="LXGWZhenKaiGB-Regular.ttf"
256
257
 
257
258
  # [可选] 是否需要转发媒体内容(超过 4 项时始终使用合并转发)
258
259
  parser_need_forward_contents=True
260
+
261
+ # [可选] emoji 渲染样式 "apple", "google", "twitter", "facebook"(默认)
262
+ parser_emoji_style="facebook"
259
263
  ```
260
264
 
261
265
  </details>
@@ -6,7 +6,7 @@
6
6
  ## ✨ [Nonebot2](https://github.com/nonebot/nonebot2) 链接分享自动解析插件 ✨
7
7
  [![LICENSE](https://img.shields.io/github/license/fllesser/nonebot-plugin-parser.svg)](./LICENSE)
8
8
  [![pypi](https://img.shields.io/pypi/v/nonebot-plugin-parser.svg)](https://pypi.python.org/pypi/nonebot-plugin-parser)
9
- [![python](https://img.shields.io/badge/python-3.10|3.11|3.12|3.13-blue.svg)](https://python.org)
9
+ [![python](https://img.shields.io/badge/python-3.10|3.11|3.12|3.13|3.14-blue.svg)](https://python.org)
10
10
  [![uv](https://img.shields.io/badge/package%20manager-uv-black?style=flat-square&logo=uv)](https://github.com/astral-sh/uv)
11
11
  [![ruff](https://img.shields.io/badge/code%20style-ruff-black?style=flat-square&logo=ruff)](https://github.com/astral-sh/ruff)
12
12
  <br/>
@@ -209,6 +209,9 @@ parser_custom_font="LXGWZhenKaiGB-Regular.ttf"
209
209
 
210
210
  # [可选] 是否需要转发媒体内容(超过 4 项时始终使用合并转发)
211
211
  parser_need_forward_contents=True
212
+
213
+ # [可选] emoji 渲染样式 "apple", "google", "twitter", "facebook"(默认)
214
+ parser_emoji_style="facebook"
212
215
  ```
213
216
 
214
217
  </details>
@@ -1,8 +1,6 @@
1
- #:tombi schema.strict = false
2
-
3
1
  [project]
4
2
  name = "nonebot-plugin-parser"
5
- version = "2.2.2"
3
+ version = "2.2.3"
6
4
  description = "NoneBot2 链接分享解析 Alconna 版, 通用媒体卡片渲染(PIL 实现), 支持 B站/抖音/快手/微博/小红书/youtube/tiktok/twitter/acfun/nga"
7
5
  readme = "README.md"
8
6
  requires-python = ">=3.10"
@@ -20,8 +18,9 @@ keywords = [
20
18
  "video",
21
19
  "weibo",
22
20
  "xiaohongshu",
23
- "youtube"
21
+ "youtube",
24
22
  ]
23
+
25
24
  classifiers = [
26
25
  "Development Status :: 5 - Production/Stable",
27
26
  "Framework :: AsyncIO",
@@ -33,25 +32,27 @@ classifiers = [
33
32
  "Programming Language :: Python :: 3.11",
34
33
  "Programming Language :: Python :: 3.12",
35
34
  "Programming Language :: Python :: 3.13",
35
+ "Programming Language :: Python :: 3.14",
36
36
  "Topic :: Communications :: Chat",
37
37
  "Topic :: Internet :: WWW/HTTP",
38
38
  "Topic :: Multimedia :: Video",
39
39
  ]
40
+
40
41
  dependencies = [
42
+ "nonebot2>=2.4.3,<3.0.0",
43
+ "pillow>=11.0.0",
44
+ "pilmoji-for-parser>=0.1.2",
45
+ "tqdm>=4.67.1,<5.0.0",
41
46
  "aiofiles>=25.1.0",
42
- "beautifulsoup4>=4.12.0,<5.0.0",
43
- "bilibili-api-python>=17.4.0,<18.0.0",
44
47
  "curl_cffi>=0.13.0,<1.0.0",
45
48
  "httpx>=0.27.2,<1.0.0",
46
49
  "msgspec>=0.20.0,<1.0.0",
47
- "nonebot-plugin-alconna>=0.59.4",
50
+ "beautifulsoup4>=4.12.0,<5.0.0",
51
+ "bilibili-api-python>=17.4.0,<18.0.0",
52
+ "nonebot-plugin-alconna>=0.59.4,<1.0.0",
48
53
  "nonebot-plugin-apscheduler>=0.5.0,<1.0.0",
49
54
  "nonebot-plugin-localstore>=0.7.4,<1.0.0",
50
- "nonebot-plugin-uninfo>=0.10.0",
51
- "nonebot2>=2.4.3,<3.0.0",
52
- "pillow>=11.0.0",
53
- "pilmoji-for-parser>=0.1.0",
54
- "tqdm>=4.67.1,<5.0.0",
55
+ "nonebot-plugin-uninfo>=0.10.1,<1.0.0",
55
56
  ]
56
57
 
57
58
  [project.urls]
@@ -67,32 +68,50 @@ ytdlp = ["yt-dlp[default]>=2025.11.12"]
67
68
  [dependency-groups]
68
69
  dev = [
69
70
  "nonebot2[fastapi]>=2.4.3,<3.0.0",
70
- "ruff>=0.14.4,<1.0.0",
71
+ "ruff>=0.14.7,<1.0.0",
72
+ { include-group = "extras" },
73
+ { include-group = "test" },
74
+ ]
75
+ extras = [
76
+ "nonebot-plugin-htmlkit>=0.1.0rc4",
77
+ "yt-dlp[default]>=2025.11.12",
78
+ "types-yt-dlp>=2025.11.12.20251115",
71
79
  ]
72
- extras = ["nonebot-plugin-htmlkit>=0.1.0rc4", "yt-dlp[default]>=2025.11.12"]
73
- pydantic-v1 = ["pydantic<2.0.0"] # renovate:ignore
74
- pydantic-v2 = ["pydantic>=2.0.0"]
75
- telegram = ["nonebot-adapter-telegram>=0.1.0b20"]
76
80
  test = [
77
81
  "nonebot-adapter-onebot>=2.4.6",
78
82
  "nonebot2[fastapi]>=2.4.3,<3.0.0",
79
83
  "nonebug>=0.4.3,<1.0.0",
80
- "poethepoet>=0.37.0",
84
+ "poethepoet>=0.38.0",
81
85
  "pytest-asyncio>=1.3.0,<1.4.0",
82
86
  "pytest-cov>=7.0.0",
83
87
  "pytest-xdist>=3.8.0,<4.0.0",
84
88
  "respx>=0.22.0",
85
89
  ]
90
+ pydantic-v1 = ["pydantic<2.0.0"]
91
+ pydantic-v2 = ["pydantic>=2.0.0"]
92
+ telegram = ["nonebot-adapter-telegram>=0.1.0b20"]
86
93
 
87
94
  [build-system]
88
95
  requires = ["uv_build>=0.9.0,<0.10.0"]
89
96
  build-backend = "uv_build"
90
97
 
98
+ [tool.uv]
99
+ conflicts = [
100
+ [
101
+ { group = "pydantic-v1" },
102
+ { group = "pydantic-v2" },
103
+ { group = "telegram" },
104
+ ],
105
+ ]
106
+
107
+ [tool.uv.sources]
108
+ nonebug = { git = "https://github.com/fllesser/nonebug", rev = "dev/migrate-uv" }
109
+
91
110
  [tool.bumpversion]
92
- current_version = "2.2.2"
111
+ tag = true
93
112
  commit = true
113
+ current_version = "2.2.3"
94
114
  message = "release: bump vesion from {current_version} to {new_version}"
95
- tag = true
96
115
 
97
116
  [[tool.bumpversion.files]]
98
117
  filename = "uv.lock"
@@ -100,9 +119,7 @@ search = "name = \"nonebot-plugin-parser\"\nversion = \"{current_version}\""
100
119
  replace = "name = \"nonebot-plugin-parser\"\nversion = \"{new_version}\""
101
120
 
102
121
  [tool.coverage.report]
103
- show_missing = true # 相当于 --cov-report=term-missing:cite[1]
104
- # omit = [ # 排除不需要统计的文件
105
- # ]
122
+ show_missing = true
106
123
  exclude_lines = [
107
124
  "raise NotImplementedError",
108
125
  "if TYPE_CHECKING:",
@@ -119,9 +136,9 @@ adapters = [
119
136
  plugins = ["nonebot_plugin_parser"]
120
137
 
121
138
  [tool.poe.tasks]
122
- test_others = "pytest tests/others --cov=src --cov-report=xml:coverage1.xml --junitxml=junit1.xml -n auto"
123
- test_parsers = "pytest tests/parsers --cov=src --cov-report=xml:coverage2.xml --junitxml=junit2.xml -n auto"
124
- test_render = "pytest tests/render --cov=src --cov-report=xml:coverage3.xml --junitxml=junit3.xml"
139
+ test_others = "pytest tests/others --cov=src --cov-report=xml --junitxml=junit.xml -n auto"
140
+ test_parsers = "pytest tests/parsers --cov=src --cov-report=xml --junitxml=junit.xml -n auto"
141
+ test_render = "pytest tests/render --cov=src --cov-report=xml --junitxml=junit.xml"
125
142
  bump = "bump-my-version bump"
126
143
  show-bump = "bump-my-version show-bump"
127
144
 
@@ -138,14 +155,14 @@ executionEnvironments = [
138
155
  typeCheckingMode = "standard"
139
156
  disableBytesTypePromotions = true
140
157
 
141
- [tool.pytest.ini_options]
158
+ [tool.pytest]
142
159
  addopts = [
143
- "-v", # 详细输出
144
- "-s", # 显示打印信息
145
- "--tb=short", # 简短的错误回溯
146
- "-ra", # 显示所有测试结果摘要
147
- "--strict-markers", # 严格标记模式
148
- "--import-mode=prepend", # 导入模式
160
+ "--import-mode=prepend", # 导入模式
161
+ "--strict-markers", # 严格标记模式
162
+ "--tb=short", # 简短的错误回溯
163
+ "-ra", # 显示所有测试结果摘要
164
+ "-s", # 显示打印信息
165
+ "-v", # 详细输出
149
166
  ]
150
167
  pythonpath = ["src"]
151
168
  asyncio_mode = "auto"
@@ -153,58 +170,41 @@ asyncio_default_fixture_loop_scope = "session"
153
170
 
154
171
  [tool.ruff]
155
172
  line-length = 120
156
- target-version = "py310"
157
173
 
158
174
  [tool.ruff.format]
159
175
  line-ending = "lf"
160
176
 
161
177
  [tool.ruff.lint]
162
178
  select = [
163
- "F", # Pyflakes
164
- "W", # pycodestyle warnings
165
- "E", # pycodestyle errors
166
- "I", # isort
167
- "UP", # pyupgrade
168
- "ASYNC", # flake8-async
169
- "C4", # flake8-comprehensions
170
- "T10", # flake8-debugger
171
- "T20", # flake8-print
172
- "PYI", # flake8-pyi
173
- "PT", # flake8-pytest-style
174
- "Q", # flake8-quotes
175
- "TID", # flake8-tidy-imports
176
- "RUF", # Ruff-specific rules
179
+ "F", # Pyflakes
180
+ "W", # pycodestyle warnings
181
+ "E", # pycodestyle errors
182
+ "I", # isort
183
+ "UP", # pyupgrade
184
+ "Q", # flake8-quotes
185
+ "PYI", # flake8-pyi
186
+ "T10", # flake8-debugger
187
+ "T20", # flake8-print
188
+ "C4", # flake8-comprehensions
189
+ "PT", # flake8-pytest-style
190
+ "TID", # flake8-tidy-imports
191
+ "ASYNC", # flake8-async
192
+ "RUF", # Ruff-specific rules
177
193
  ]
178
194
  ignore = [
179
- "E402", # module-import-not-at-top-of-file
195
+ "E402", # module-import-not-at-top-of-file
180
196
  "UP037", # quoted-annotation
181
- "RUF001", # ambiguous-unicode-character-string
182
- "RUF002", # ambiguous-unicode-character-docstring
183
- "RUF003", # ambiguous-unicode-character-comment
184
- "W191", # indentation contains tabs
185
- # "I001", # isort: imports are incorrectly sorted
186
- "TID252", # 相对导入
197
+ "RUF001", # ambiguous-unicode-character-string
198
+ "RUF002", # ambiguous-unicode-character-docstring
199
+ "RUF003", # ambiguous-unicode-character-comment
200
+ "W191", # indentation contains tabs
201
+ "TID252", # relative-import
187
202
  ]
188
203
 
189
204
  [tool.ruff.lint.isort]
190
- force-sort-within-sections = true
191
- known-first-party = ["tests/*"]
205
+ length-sort = true
206
+ known-first-party = ["nonebot_plugin_parser", "tests/*"]
192
207
  extra-standard-library = ["typing_extensions"]
193
208
 
194
- [tool.ruff.lint.flake8-pytest-style]
195
- fixture-parentheses = false
196
- mark-parentheses = false
197
-
198
209
  [tool.ruff.lint.pyupgrade]
199
210
  keep-runtime-typing = true
200
-
201
- [tool.uv]
202
- required-version = ">=0.9.7"
203
- default-groups = ["test", "dev", "extras"]
204
- conflicts = [
205
- [
206
- { group = "pydantic-v1" },
207
- { group = "pydantic-v2" },
208
- { group = "telegram" },
209
- ],
210
- ]
@@ -1,18 +1,17 @@
1
1
  from enum import Enum
2
2
  from pathlib import Path
3
3
 
4
- from bilibili_api.video import VideoCodecs, VideoQuality
5
- from nonebot import get_driver, get_plugin_config, require
4
+ from nonebot import require, get_driver, get_plugin_config
5
+ from pilmoji import EmojiStyle
6
6
  from pydantic import BaseModel
7
+ from bilibili_api.video import VideoCodecs, VideoQuality
7
8
 
8
9
  from .constants import PlatformEnum
9
10
 
10
- _nickname: str = next(iter(get_driver().config.nickname), "")
11
- """全局名称"""
12
-
13
11
  require("nonebot_plugin_localstore")
14
12
  import nonebot_plugin_localstore as _store
15
13
 
14
+ _nickname: str = next(iter(get_driver().config.nickname), "")
16
15
  _cache_dir: Path = _store.get_plugin_cache_dir()
17
16
  _config_dir: Path = _store.get_plugin_config_dir()
18
17
  _data_dir: Path = _store.get_plugin_data_dir()
@@ -57,10 +56,12 @@ class Config(BaseModel):
57
56
  """自定义字体"""
58
57
  parser_need_forward_contents: bool = True
59
58
  """是否需要转发媒体内容"""
59
+ parser_emoji_style: EmojiStyle = EmojiStyle.FACEBOOK
60
+ """Pilmoji 表情样式"""
60
61
 
61
62
  @property
62
63
  def nickname(self) -> str:
63
- """全局名称"""
64
+ """机器人昵称"""
64
65
  return _nickname
65
66
 
66
67
  @property
@@ -148,6 +149,11 @@ class Config(BaseModel):
148
149
  """是否需要转发媒体内容"""
149
150
  return self.parser_need_forward_contents
150
151
 
152
+ @property
153
+ def emoji_style(self) -> EmojiStyle:
154
+ """Pilmoji 表情样式"""
155
+ return self.parser_emoji_style
156
+
151
157
 
152
158
  pconfig: Config = get_plugin_config(Config)
153
159
  """配置"""
@@ -2,15 +2,15 @@ import asyncio
2
2
  from pathlib import Path
3
3
 
4
4
  import aiofiles
5
- from httpx import AsyncClient, HTTPError
5
+ from httpx import HTTPError, AsyncClient
6
6
  from nonebot import logger
7
7
  from tqdm.asyncio import tqdm
8
8
 
9
+ from .task import auto_task
10
+ from ..utils import merge_av, safe_unlink, generate_file_name
9
11
  from ..config import pconfig
10
12
  from ..constants import COMMON_HEADER, DOWNLOAD_TIMEOUT
11
- from ..exception import DownloadException, SizeLimitException, ZeroSizeException
12
- from ..utils import generate_file_name, merge_av, safe_unlink
13
- from .task import auto_task
13
+ from ..exception import DownloadException, ZeroSizeException, SizeLimitException
14
14
 
15
15
 
16
16
  class StreamDownloader:
@@ -63,7 +63,7 @@ class StreamDownloader:
63
63
  raise ZeroSizeException
64
64
 
65
65
  if (file_size := content_length / 1024 / 1024) > pconfig.max_size:
66
- logger.warning(f"媒体 url: {url} 大小 {file_size:.2f} MB 超过 {pconfig.max_size} MB, 取消下载")
66
+ logger.warning(f"媒体 url: {url} 大小 {file_size:.2f} MB 超过 {{pconfig.max_size}} MB, 取消下载")
67
67
  raise SizeLimitException
68
68
 
69
69
  with self.get_progress_bar(file_name, content_length) as bar:
@@ -136,7 +136,7 @@ class StreamDownloader:
136
136
 
137
137
  Args:
138
138
  url (str): url address
139
- audio_name (str | None ): audio name. Defaults to get name by parse_url_resource_name.
139
+ audio_name (str | None ): audio name. Defaults to generate from url.
140
140
  ext_headers (dict[str, str] | None): ext headers. Defaults to None.
141
141
 
142
142
  Returns:
@@ -161,7 +161,7 @@ class StreamDownloader:
161
161
 
162
162
  Args:
163
163
  url (str): url
164
- img_name (str | None): image name. Defaults to None.
164
+ img_name (str | None): image name. Defaults to generate from url.
165
165
  ext_headers (dict[str, str] | None): ext headers. Defaults to None.
166
166
 
167
167
  Returns:
@@ -1,7 +1,7 @@
1
+ from typing import Any, TypeVar, ParamSpec
1
2
  from asyncio import Task, create_task
2
- from collections.abc import Callable, Coroutine
3
3
  from functools import wraps
4
- from typing import Any, ParamSpec, TypeVar
4
+ from collections.abc import Callable, Coroutine
5
5
 
6
6
  P = ParamSpec("P")
7
7
  T = TypeVar("T")
@@ -1,14 +1,14 @@
1
1
  import asyncio
2
+ from typing import TYPE_CHECKING
2
3
  from pathlib import Path
3
- from typing import Any
4
4
 
5
- from msgspec import Struct, convert
6
5
  import yt_dlp
6
+ from msgspec import Struct, convert
7
7
 
8
- from ..config import pconfig
9
- from ..exception import DurationLimitException, ParseException
10
- from ..utils import LimitedSizeDict, generate_file_name
11
8
  from .task import auto_task
9
+ from ..utils import LimitedSizeDict, generate_file_name
10
+ from ..config import pconfig
11
+ from ..exception import ParseException, DurationLimitException
12
12
 
13
13
 
14
14
  class VideoInfo(Struct):
@@ -38,16 +38,19 @@ class YtdlpDownloader:
38
38
  """YtdlpDownloader class"""
39
39
 
40
40
  def __init__(self):
41
+ if TYPE_CHECKING:
42
+ from yt_dlp import _Params
43
+
41
44
  self._video_info_mapping = LimitedSizeDict[str, VideoInfo]()
42
- self._ydl_extract_base_opts: dict[str, Any] = {
45
+ self._extract_base_opts: _Params = {
43
46
  "quiet": True,
44
- "skip_download": True,
47
+ "skip_download": "1",
45
48
  "force_generic_extractor": True,
46
49
  }
47
- self._ydl_download_base_opts: dict[str, Any] = {}
50
+ self._download_base_opts: _Params = {}
48
51
  if proxy := pconfig.proxy:
49
- self._ydl_download_base_opts["proxy"] = proxy
50
- self._ydl_extract_base_opts["proxy"] = proxy
52
+ self._download_base_opts["proxy"] = proxy
53
+ self._extract_base_opts["proxy"] = proxy
51
54
 
52
55
  async def extract_video_info(self, url: str, cookiefile: Path | None = None) -> VideoInfo:
53
56
  """get video info by url
@@ -62,12 +65,12 @@ class YtdlpDownloader:
62
65
  video_info = self._video_info_mapping.get(url, None)
63
66
  if video_info:
64
67
  return video_info
65
- ydl_opts = self._ydl_extract_base_opts.copy()
68
+ ydl_opts = self._extract_base_opts.copy()
66
69
 
67
70
  if cookiefile:
68
71
  ydl_opts["cookiefile"] = str(cookiefile)
69
72
 
70
- with yt_dlp.YoutubeDL(ydl_opts) as ydl: # pyright: ignore[reportArgumentType]
73
+ with yt_dlp.YoutubeDL(ydl_opts) as ydl:
71
74
  info_dict = await asyncio.to_thread(ydl.extract_info, url, download=False)
72
75
  if not info_dict:
73
76
  raise ParseException("获取视频信息失败")
@@ -95,17 +98,17 @@ class YtdlpDownloader:
95
98
  video_path = pconfig.cache_dir / generate_file_name(url, ".mp4")
96
99
  if video_path.exists():
97
100
  return video_path
98
- ydl_opts = {
99
- "outtmpl": f"{video_path}",
100
- "merge_output_format": "mp4",
101
- "format": f"bv[filesize<={duration // 10 + 10}M]+ba/b[filesize<={duration // 8 + 10}M]",
102
- "postprocessors": [{"key": "FFmpegVideoConvertor", "preferedformat": "mp4"}],
103
- } | self._ydl_download_base_opts
101
+
102
+ ydl_opts = self._download_base_opts.copy()
103
+ ydl_opts["outtmpl"] = str(video_path)
104
+ ydl_opts["merge_output_format"] = "mp4"
105
+ ydl_opts["format"] = f"bv[filesize<={duration // 10 + 10}M]+ba/b[filesize<={duration // 8 + 10}M]"
106
+ ydl_opts["postprocessors"] = [{"key": "FFmpegVideoConvertor", "preferedformat": "mp4"}]
104
107
 
105
108
  if cookiefile:
106
109
  ydl_opts["cookiefile"] = str(cookiefile)
107
110
 
108
- with yt_dlp.YoutubeDL(ydl_opts) as ydl: # pyright: ignore[reportArgumentType]
111
+ with yt_dlp.YoutubeDL(ydl_opts) as ydl:
109
112
  await asyncio.to_thread(ydl.download, [url])
110
113
  return video_path
111
114
 
@@ -125,20 +128,19 @@ class YtdlpDownloader:
125
128
  if audio_path.exists():
126
129
  return audio_path
127
130
 
128
- ydl_opts = {
129
- "outtmpl": f"{pconfig.cache_dir / file_name}.%(ext)s",
130
- "format": "bestaudio/best",
131
- "postprocessors": [
132
- {
133
- "key": "FFmpegExtractAudio",
134
- "preferredcodec": "flac",
135
- "preferredquality": "0",
136
- }
137
- ],
138
- } | self._ydl_download_base_opts
131
+ ydl_opts = self._download_base_opts.copy()
132
+ ydl_opts["outtmpl"] = f"{pconfig.cache_dir / file_name}.%(ext)s"
133
+ ydl_opts["format"] = "bestaudio/best"
134
+ ydl_opts["postprocessors"] = [
135
+ {
136
+ "key": "FFmpegExtractAudio",
137
+ "preferredcodec": "flac",
138
+ "preferredquality": "0",
139
+ }
140
+ ]
139
141
 
140
142
  if cookiefile:
141
143
  ydl_opts["cookiefile"] = str(cookiefile)
142
- with yt_dlp.YoutubeDL(ydl_opts) as ydl: # pyright: ignore[reportArgumentType]
144
+ with yt_dlp.YoutubeDL(ydl_opts) as ydl:
143
145
  await asyncio.to_thread(ydl.download, [url])
144
146
  return audio_path
@@ -1,22 +1,22 @@
1
- from collections.abc import Awaitable, Callable, Sequence
2
- from functools import wraps
1
+ from typing import Any, Literal, ClassVar
3
2
  from pathlib import Path
4
- from typing import Any, ClassVar, Literal
3
+ from functools import wraps
4
+ from collections.abc import Callable, Sequence, Awaitable
5
5
 
6
6
  from nonebot import logger
7
- from nonebot.adapters import Event
8
7
  from nonebot.matcher import current_bot, current_event
8
+ from nonebot.adapters import Event
9
9
  from nonebot_plugin_alconna import SupportAdapter, uniseg
10
10
  from nonebot_plugin_alconna.uniseg import (
11
- CustomNode,
12
11
  File,
13
- Image,
14
- Reference,
15
- Segment,
16
12
  Text,
17
- UniMessage,
13
+ Image,
18
14
  Video,
19
15
  Voice,
16
+ Segment,
17
+ Reference,
18
+ CustomNode,
19
+ UniMessage,
20
20
  )
21
21
 
22
22
  from .config import pconfig
@@ -1,13 +1,13 @@
1
1
  """统一的解析器 matcher"""
2
2
 
3
- from nonebot import get_driver, logger
3
+ from nonebot import logger, get_driver
4
4
 
5
+ from .rule import Searched, SearchResult, on_keyword_regex
6
+ from ..utils import LimitedSizeDict
5
7
  from ..config import pconfig
6
8
  from ..helper import UniHelper
7
9
  from ..parsers import BaseParser, ParseResult
8
10
  from ..renders import get_renderer
9
- from ..utils import LimitedSizeDict
10
- from .rule import Searched, SearchResult, on_keyword_regex
11
11
 
12
12
 
13
13
  def _get_enabled_parser_classes() -> list[type[BaseParser]]:
@@ -75,12 +75,12 @@ import re
75
75
  from typing import cast
76
76
 
77
77
  from nonebot import on_command
78
- from nonebot.adapters import Message
79
78
  from nonebot.params import CommandArg
79
+ from nonebot.adapters import Message
80
80
  from nonebot_plugin_alconna import UniMessage
81
81
 
82
- from ..download import DOWNLOADER
83
82
  from ..parsers import BilibiliParser
83
+ from ..download import DOWNLOADER
84
84
 
85
85
 
86
86
  @on_command("bm", priority=3, block=True).handle()
@@ -1,32 +1,35 @@
1
1
  # 导出所有 Parser 类
2
- from ..download import YTDLP_DOWNLOADER
3
- from .acfun import AcfunParser as AcfunParser
2
+ from .nga import NGAParser as NGAParser
4
3
  from .base import BaseParser as BaseParser
5
- from .bilibili import BilibiliParser as BilibiliParser
4
+ from .acfun import AcfunParser as AcfunParser
5
+ from .weibo import WeiBoParser as WeiBoParser
6
6
  from .douyin import DouyinParser as DouyinParser
7
- from .kuaishou import KuaiShouParser as KuaiShouParser
8
- from .nga import NGAParser as NGAParser
9
7
  from .twitter import TwitterParser as TwitterParser
10
- from .weibo import WeiBoParser as WeiBoParser
8
+ from .bilibili import BilibiliParser as BilibiliParser
9
+ from .kuaishou import KuaiShouParser as KuaiShouParser
10
+ from ..download import YTDLP_DOWNLOADER
11
11
  from .xiaohongshu import XiaoHongShuParser as XiaoHongShuParser
12
12
 
13
13
  if YTDLP_DOWNLOADER is not None:
14
14
  from .tiktok import TikTokParser as TikTokParser
15
15
  from .youtube import YouTubeParser as YouTubeParser
16
16
 
17
- from .base import handle as handle
18
- from .data import AudioContent as AudioContent
19
- from .data import Author
20
- from .data import DynamicContent as DynamicContent
21
- from .data import GraphicsContent as GraphicsContent
22
- from .data import ImageContent as ImageContent
23
- from .data import ParseResult as ParseResult
24
- from .data import Platform as Platform
25
- from .data import VideoContent as VideoContent
17
+ from .base import handle
18
+ from .data import (
19
+ Author,
20
+ Platform,
21
+ ParseResult,
22
+ AudioContent,
23
+ ImageContent,
24
+ VideoContent,
25
+ DynamicContent,
26
+ GraphicsContent,
27
+ )
26
28
 
27
29
  __all__ = [
28
30
  "AudioContent",
29
31
  "Author",
32
+ "BaseParser",
30
33
  "DynamicContent",
31
34
  "GraphicsContent",
32
35
  "ImageContent",