nonebot-plugin-parser 2.6.0__tar.gz → 2.6.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.2}/PKG-INFO +8 -8
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.2}/README.md +4 -4
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.2}/pyproject.toml +9 -11
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/download/__init__.py +114 -58
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/helper.py +10 -5
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/matchers/__init__.py +3 -3
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/parsers/__init__.py +2 -2
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/parsers/acfun/__init__.py +1 -1
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/parsers/base.py +35 -18
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/parsers/bilibili/__init__.py +9 -18
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/parsers/data.py +26 -12
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/parsers/douyin/__init__.py +1 -1
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/parsers/kuaishou/__init__.py +5 -2
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/parsers/kuaishou/states.py +4 -0
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/parsers/tiktok.py +4 -4
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/parsers/twitter.py +5 -12
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/parsers/weibo/__init__.py +2 -3
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/parsers/youtube/__init__.py +3 -3
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/renders/__init__.py +2 -9
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/renders/base.py +9 -23
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/renders/templates/card.html.jinja2 +43 -45
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/utils.py +33 -7
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/__init__.py +0 -0
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/config.py +0 -0
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/constants.py +0 -0
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/download/task.py +0 -0
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/download/ytdlp.py +0 -0
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/exception.py +0 -0
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/matchers/filter.py +0 -0
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/matchers/rule.py +0 -0
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/parsers/acfun/video.py +0 -0
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/parsers/bilibili/article.py +0 -0
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/parsers/bilibili/common.py +0 -0
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/parsers/bilibili/dynamic.py +0 -0
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/parsers/bilibili/favlist.py +0 -0
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/parsers/bilibili/live.py +0 -0
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/parsers/bilibili/opus.py +0 -0
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/parsers/bilibili/video.py +0 -0
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/parsers/cookie.py +0 -0
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/parsers/douyin/slides.py +0 -0
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/parsers/douyin/video.py +0 -0
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/parsers/nga.py +0 -0
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/parsers/task.py +0 -0
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/parsers/utils.py +0 -0
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/parsers/weibo/article.py +0 -0
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/parsers/weibo/common.py +0 -0
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/parsers/weibo/show.py +0 -0
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/parsers/xiaohongshu/__init__.py +0 -0
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/parsers/xiaohongshu/common.py +0 -0
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/parsers/xiaohongshu/discovery.py +0 -0
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/parsers/xiaohongshu/explore.py +0 -0
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/parsers/youtube/meta.py +0 -0
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/renders/common.py +0 -0
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/renders/default.py +0 -0
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/renders/htmlrender.py +0 -0
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/renders/resources/HYSongYunLangHeiW.ttf +0 -0
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/renders/resources/__init__.py +0 -0
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/renders/resources/avatar.png +0 -0
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/renders/resources/bilibili.png +0 -0
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/renders/resources/douyin.png +0 -0
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/renders/resources/fallback_pic/1.jpg +0 -0
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/renders/resources/fallback_pic/2.jpg +0 -0
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/renders/resources/fallback_pic/3.jpg +0 -0
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/renders/resources/fallback_pic/4.jpg +0 -0
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/renders/resources/fallback_pic/5.jpg +0 -0
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/renders/resources/fallback_pic/6.jpg +0 -0
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/renders/resources/fallback_pic/7.jpg +0 -0
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/renders/resources/fallback_pic/8.jpg +0 -0
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/renders/resources/fallback_pic/9.jpg +0 -0
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/renders/resources/kuaishou.png +0 -0
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/renders/resources/play.png +0 -0
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/renders/resources/tiktok.png +0 -0
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/renders/resources/twitter.png +0 -0
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/renders/resources/weibo.png +0 -0
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/renders/resources/xiaohongshu.png +0 -0
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/renders/resources/youtube.png +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: nonebot-plugin-parser
|
|
3
|
-
Version: 2.6.
|
|
3
|
+
Version: 2.6.2
|
|
4
4
|
Summary: NoneBot2 链接分享解析 Alconna 版, 现支持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
|
|
@@ -19,20 +19,20 @@ Classifier: Programming Language :: Python :: 3.14
|
|
|
19
19
|
Classifier: Topic :: Communications :: Chat
|
|
20
20
|
Classifier: Topic :: Internet :: WWW/HTTP
|
|
21
21
|
Classifier: Topic :: Multimedia :: Video
|
|
22
|
-
Requires-Dist: nonebot2>=2.4.3,<3.0.0
|
|
23
22
|
Requires-Dist: rich>=13.0.0
|
|
24
23
|
Requires-Dist: pillow>=11.0.0
|
|
25
24
|
Requires-Dist: aiofiles>=25.1.0
|
|
26
25
|
Requires-Dist: httpx>=0.27.2,<1.0.0
|
|
27
26
|
Requires-Dist: msgspec>=0.20.0,<1.0.0
|
|
27
|
+
Requires-Dist: nonebot2>=2.5.0,<3.0.0
|
|
28
28
|
Requires-Dist: apilmoji[rich]>=0.3.1,<1.0.0
|
|
29
29
|
Requires-Dist: beautifulsoup4>=4.12.0,<5.0.0
|
|
30
30
|
Requires-Dist: curl-cffi>=0.13.0,!=0.14.0,<1.0.0
|
|
31
31
|
Requires-Dist: bilibili-api-python>=17.4.1,<18.0.0
|
|
32
|
+
Requires-Dist: nonebot-plugin-uninfo>=0.10.1,<1.0.0
|
|
32
33
|
Requires-Dist: nonebot-plugin-alconna>=0.60.4,<1.0.0
|
|
33
|
-
Requires-Dist: nonebot-plugin-apscheduler>=0.5.0,<1.0.0
|
|
34
34
|
Requires-Dist: nonebot-plugin-localstore>=0.7.4,<1.0.0
|
|
35
|
-
Requires-Dist: nonebot-plugin-
|
|
35
|
+
Requires-Dist: nonebot-plugin-apscheduler>=0.5.0,<1.0.0
|
|
36
36
|
Requires-Dist: nonebot-plugin-htmlkit>=0.1.0rc4 ; extra == 'all'
|
|
37
37
|
Requires-Dist: nonebot-plugin-htmlrender>=0.6.7 ; extra == 'all'
|
|
38
38
|
Requires-Dist: yt-dlp[default]>=2026.3.13 ; extra == 'all'
|
|
@@ -65,7 +65,7 @@ Description-Content-Type: text/markdown
|
|
|
65
65
|
[](https://github.com/astral-sh/uv)
|
|
66
66
|
[](https://github.com/astral-sh/ruff)
|
|
67
67
|
<br/>
|
|
68
|
-
[](https://github.com/j178/prek)
|
|
69
69
|
[](https://codecov.io/gh/fllesser/nonebot-plugin-parser)
|
|
70
70
|
[](https://qm.qq.com/q/y4T4CjHimc)
|
|
71
71
|
|
|
@@ -416,7 +416,7 @@ class ExampleParser(BaseParser):
|
|
|
416
416
|
<details>
|
|
417
417
|
<summary>辅助函数</summary>
|
|
418
418
|
|
|
419
|
-
>
|
|
419
|
+
> 构建作者
|
|
420
420
|
|
|
421
421
|
```python
|
|
422
422
|
author = self.create_author(
|
|
@@ -473,5 +473,5 @@ real_url = await self.get_redirect_url(
|
|
|
473
473
|
|
|
474
474
|
## 🎉 致谢
|
|
475
475
|
|
|
476
|
-
- [nonebot-plugin-resolver](https://github.com/zhiyu1998/nonebot-plugin-resolver)
|
|
477
|
-
- [parse-video-py](https://github.com/wujunwei928/parse-video-py)
|
|
476
|
+
- [nonebot-plugin-resolver](https://github.com/zhiyu1998/nonebot-plugin-resolver) - 本项目最初基于此插件进行开发,在此表示感谢。尽管当前版本代码已完全重构,但仍感谢原项目提供的初始思路和参考。
|
|
477
|
+
- [parse-video-py](https://github.com/wujunwei928/parse-video-py) - 在抖音解析功能实现方面提供了技术参考和借鉴。
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
[](https://github.com/astral-sh/uv)
|
|
12
12
|
[](https://github.com/astral-sh/ruff)
|
|
13
13
|
<br/>
|
|
14
|
-
[](https://github.com/j178/prek)
|
|
15
15
|
[](https://codecov.io/gh/fllesser/nonebot-plugin-parser)
|
|
16
16
|
[](https://qm.qq.com/q/y4T4CjHimc)
|
|
17
17
|
|
|
@@ -362,7 +362,7 @@ class ExampleParser(BaseParser):
|
|
|
362
362
|
<details>
|
|
363
363
|
<summary>辅助函数</summary>
|
|
364
364
|
|
|
365
|
-
>
|
|
365
|
+
> 构建作者
|
|
366
366
|
|
|
367
367
|
```python
|
|
368
368
|
author = self.create_author(
|
|
@@ -419,5 +419,5 @@ real_url = await self.get_redirect_url(
|
|
|
419
419
|
|
|
420
420
|
## 🎉 致谢
|
|
421
421
|
|
|
422
|
-
- [nonebot-plugin-resolver](https://github.com/zhiyu1998/nonebot-plugin-resolver)
|
|
423
|
-
- [parse-video-py](https://github.com/wujunwei928/parse-video-py)
|
|
422
|
+
- [nonebot-plugin-resolver](https://github.com/zhiyu1998/nonebot-plugin-resolver) - 本项目最初基于此插件进行开发,在此表示感谢。尽管当前版本代码已完全重构,但仍感谢原项目提供的初始思路和参考。
|
|
423
|
+
- [parse-video-py](https://github.com/wujunwei928/parse-video-py) - 在抖音解析功能实现方面提供了技术参考和借鉴。
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "nonebot-plugin-parser"
|
|
3
|
-
version = "2.6.
|
|
3
|
+
version = "2.6.2"
|
|
4
4
|
description = "NoneBot2 链接分享解析 Alconna 版, 现支持B站|抖音|快手|微博|小红书|YouTube|TikTok|Twitter|AcFun|NGA"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.10"
|
|
@@ -39,20 +39,20 @@ classifiers = [
|
|
|
39
39
|
]
|
|
40
40
|
|
|
41
41
|
dependencies = [
|
|
42
|
-
"nonebot2>=2.4.3,<3.0.0",
|
|
43
42
|
"rich>=13.0.0",
|
|
44
43
|
"pillow>=11.0.0",
|
|
45
44
|
"aiofiles>=25.1.0",
|
|
46
45
|
"httpx>=0.27.2,<1.0.0",
|
|
47
46
|
"msgspec>=0.20.0,<1.0.0",
|
|
47
|
+
"nonebot2>=2.5.0,<3.0.0",
|
|
48
48
|
"apilmoji[rich]>=0.3.1,<1.0.0",
|
|
49
49
|
"beautifulsoup4>=4.12.0,<5.0.0",
|
|
50
50
|
"curl_cffi>=0.13.0,<1.0.0,!=0.14.0",
|
|
51
51
|
"bilibili-api-python>=17.4.1,<18.0.0",
|
|
52
|
+
"nonebot-plugin-uninfo>=0.10.1,<1.0.0",
|
|
52
53
|
"nonebot-plugin-alconna>=0.60.4,<1.0.0",
|
|
53
|
-
"nonebot-plugin-apscheduler>=0.5.0,<1.0.0",
|
|
54
54
|
"nonebot-plugin-localstore>=0.7.4,<1.0.0",
|
|
55
|
-
"nonebot-plugin-
|
|
55
|
+
"nonebot-plugin-apscheduler>=0.5.0,<1.0.0",
|
|
56
56
|
]
|
|
57
57
|
|
|
58
58
|
[project.urls]
|
|
@@ -74,7 +74,7 @@ all = [
|
|
|
74
74
|
|
|
75
75
|
[dependency-groups]
|
|
76
76
|
dev = [
|
|
77
|
-
"nonebot2[fastapi]>=2.
|
|
77
|
+
"nonebot2[fastapi]>=2.5.0,<3.0.0",
|
|
78
78
|
"nonebot-adapter-onebot>=2.4.6",
|
|
79
79
|
"ruff>=0.15.6,<1.0.0",
|
|
80
80
|
{ include-group = "extras" },
|
|
@@ -88,8 +88,8 @@ extras = [
|
|
|
88
88
|
"types-yt-dlp>=2026.3.13.20260314",
|
|
89
89
|
]
|
|
90
90
|
test = [
|
|
91
|
-
"nonebug>=0.4.
|
|
92
|
-
"poethepoet>=0.42.1",
|
|
91
|
+
"nonebug>=0.4.4,<1.0.0",
|
|
92
|
+
"poethepoet>=0.42.1,<1.0.0",
|
|
93
93
|
"pytest-asyncio>=1.3.0,<1.4.0",
|
|
94
94
|
"pytest-cov>=7.0.0",
|
|
95
95
|
"pytest-xdist>=3.8.0,<4.0.0",
|
|
@@ -112,13 +112,11 @@ conflicts = [
|
|
|
112
112
|
],
|
|
113
113
|
]
|
|
114
114
|
|
|
115
|
-
[tool.uv.sources]
|
|
116
|
-
nonebug = { git = "https://github.com/nonebot/nonebug", rev = "master" }
|
|
117
115
|
|
|
118
116
|
[tool.bumpversion]
|
|
119
117
|
tag = true
|
|
120
118
|
commit = true
|
|
121
|
-
current_version = "2.6.
|
|
119
|
+
current_version = "2.6.2"
|
|
122
120
|
message = "release: bump vesion from {current_version} to {new_version}"
|
|
123
121
|
|
|
124
122
|
[[tool.bumpversion.files]]
|
|
@@ -150,7 +148,7 @@ test-render = "pytest tests/renders --cov=src --cov-report=xml --junitxml=junit.
|
|
|
150
148
|
bump = "bump-my-version bump"
|
|
151
149
|
show-bump = "bump-my-version show-bump"
|
|
152
150
|
|
|
153
|
-
[tool.
|
|
151
|
+
[tool.basedpyright]
|
|
154
152
|
pythonVersion = "3.10"
|
|
155
153
|
pythonPlatform = "All"
|
|
156
154
|
defineConstant = { PYDANTIC_V2 = true }
|
|
@@ -4,9 +4,10 @@ from functools import partial
|
|
|
4
4
|
from contextlib import contextmanager
|
|
5
5
|
from urllib.parse import urljoin
|
|
6
6
|
|
|
7
|
+
import httpx
|
|
7
8
|
import aiofiles
|
|
8
|
-
|
|
9
|
-
from nonebot import logger
|
|
9
|
+
import curl_cffi
|
|
10
|
+
from nonebot import logger, get_driver
|
|
10
11
|
from rich.progress import (
|
|
11
12
|
Progress,
|
|
12
13
|
BarColumn,
|
|
@@ -15,22 +16,27 @@ from rich.progress import (
|
|
|
15
16
|
)
|
|
16
17
|
|
|
17
18
|
from .task import auto_task
|
|
18
|
-
from ..utils import merge_av, safe_unlink, generate_file_name
|
|
19
|
+
from ..utils import merge_av, safe_unlink, generate_file_name, is_module_available
|
|
19
20
|
from ..config import pconfig
|
|
20
21
|
from ..constants import COMMON_HEADER, DOWNLOAD_TIMEOUT
|
|
21
22
|
from ..exception import IgnoreException, DownloadException
|
|
22
23
|
|
|
23
24
|
|
|
24
25
|
class StreamDownloader:
|
|
25
|
-
"""Downloader class for downloading files with stream"""
|
|
26
|
-
|
|
27
26
|
def __init__(self):
|
|
28
27
|
self.headers: dict[str, str] = COMMON_HEADER.copy()
|
|
29
28
|
self.cache_dir: Path = pconfig.cache_dir
|
|
30
|
-
self.client: AsyncClient = AsyncClient(timeout=DOWNLOAD_TIMEOUT, verify=False)
|
|
29
|
+
self.client: httpx.AsyncClient = httpx.AsyncClient(timeout=DOWNLOAD_TIMEOUT, verify=False)
|
|
30
|
+
|
|
31
|
+
async def aclose(self):
|
|
32
|
+
await self.client.aclose()
|
|
31
33
|
|
|
34
|
+
@staticmethod
|
|
32
35
|
@contextmanager
|
|
33
|
-
def rich_progress(
|
|
36
|
+
def rich_progress(
|
|
37
|
+
desc: str,
|
|
38
|
+
total: int | None = None,
|
|
39
|
+
):
|
|
34
40
|
with Progress(
|
|
35
41
|
TextColumn("[bold blue]{task.description}", justify="right"),
|
|
36
42
|
BarColumn(bar_width=None),
|
|
@@ -41,53 +47,83 @@ class StreamDownloader:
|
|
|
41
47
|
task_id = progress.add_task(description=desc, total=total)
|
|
42
48
|
yield partial(progress.update, task_id)
|
|
43
49
|
|
|
44
|
-
|
|
50
|
+
@staticmethod
|
|
51
|
+
def _validate_content_length(
|
|
52
|
+
response: httpx.Response | curl_cffi.Response,
|
|
53
|
+
) -> int:
|
|
54
|
+
"""获取文件长度"""
|
|
55
|
+
content_length = response.headers.get("Content-Length")
|
|
56
|
+
content_length = int(content_length) if content_length else 0
|
|
57
|
+
|
|
58
|
+
if content_length == 0:
|
|
59
|
+
logger.warning(f"媒体 url: {response.url}, 大小为 0, 取消下载")
|
|
60
|
+
raise IgnoreException
|
|
61
|
+
|
|
62
|
+
if (file_size := content_length / 1024 / 1024) > pconfig.max_size:
|
|
63
|
+
logger.warning(f"媒体 url: {response.url} 大小 {file_size:.2f} MB, 超过 {pconfig.max_size} MB, 取消下载")
|
|
64
|
+
raise IgnoreException
|
|
65
|
+
|
|
66
|
+
return content_length
|
|
67
|
+
|
|
68
|
+
async def _download_file_with_httpx(
|
|
45
69
|
self,
|
|
46
70
|
url: str,
|
|
47
71
|
*,
|
|
48
|
-
|
|
49
|
-
|
|
72
|
+
file_path: Path,
|
|
73
|
+
headers: dict[str, str],
|
|
50
74
|
chunk_size: int = 64 * 1024,
|
|
51
75
|
) -> Path:
|
|
52
76
|
"""download file by url with stream"""
|
|
53
|
-
if not file_name:
|
|
54
|
-
file_name = generate_file_name(url)
|
|
55
|
-
file_path = self.cache_dir / file_name
|
|
56
|
-
# 如果文件存在,则直接返回
|
|
57
|
-
if file_path.exists():
|
|
58
|
-
return file_path
|
|
59
77
|
|
|
60
|
-
|
|
78
|
+
async with self.client.stream(
|
|
79
|
+
"GET",
|
|
80
|
+
url,
|
|
81
|
+
headers=headers,
|
|
82
|
+
follow_redirects=True,
|
|
83
|
+
) as response:
|
|
84
|
+
response.raise_for_status()
|
|
85
|
+
content_length = self._validate_content_length(response)
|
|
86
|
+
|
|
87
|
+
with self.rich_progress(
|
|
88
|
+
f"httpx | {file_path.name}",
|
|
89
|
+
content_length,
|
|
90
|
+
) as update_progress:
|
|
91
|
+
async with aiofiles.open(file_path, "wb") as file:
|
|
92
|
+
async for chunk in response.aiter_bytes(chunk_size):
|
|
93
|
+
await file.write(chunk)
|
|
94
|
+
update_progress(advance=len(chunk))
|
|
61
95
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
96
|
+
return file_path
|
|
97
|
+
|
|
98
|
+
async def _download_file_with_curl_cffi(
|
|
99
|
+
self,
|
|
100
|
+
url: str,
|
|
101
|
+
*,
|
|
102
|
+
file_path: Path,
|
|
103
|
+
headers: dict[str, str],
|
|
104
|
+
) -> Path:
|
|
105
|
+
async with curl_cffi.AsyncSession(allow_redirects=True) as session:
|
|
106
|
+
response: curl_cffi.Response = await session.get(
|
|
107
|
+
url,
|
|
108
|
+
headers=headers,
|
|
109
|
+
timeout=DOWNLOAD_TIMEOUT,
|
|
110
|
+
stream=True,
|
|
111
|
+
)
|
|
112
|
+
response.raise_for_status()
|
|
113
|
+
content_length = self._validate_content_length(response)
|
|
114
|
+
|
|
115
|
+
with self.rich_progress(
|
|
116
|
+
f"curl_cffi | {file_path.name}",
|
|
117
|
+
content_length,
|
|
118
|
+
) as update_progress:
|
|
119
|
+
async with aiofiles.open(file_path, "wb") as file:
|
|
120
|
+
async for chunk in response.aiter_content(chunk_size=8192):
|
|
121
|
+
await file.write(chunk)
|
|
122
|
+
update_progress(advance=len(chunk))
|
|
86
123
|
|
|
87
124
|
return file_path
|
|
88
125
|
|
|
89
|
-
|
|
90
|
-
async def download_file(
|
|
126
|
+
async def _download_file(
|
|
91
127
|
self,
|
|
92
128
|
url: str,
|
|
93
129
|
*,
|
|
@@ -95,13 +131,28 @@ class StreamDownloader:
|
|
|
95
131
|
ext_headers: dict[str, str] | None = None,
|
|
96
132
|
chunk_size: int = 64 * 1024,
|
|
97
133
|
) -> Path:
|
|
98
|
-
"""download file by url with
|
|
99
|
-
|
|
100
|
-
url
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
134
|
+
"""download file by url with fallback"""
|
|
135
|
+
if not file_name:
|
|
136
|
+
file_name = generate_file_name(url)
|
|
137
|
+
file_path = self.cache_dir / file_name
|
|
138
|
+
if file_path.exists():
|
|
139
|
+
return file_path
|
|
140
|
+
|
|
141
|
+
headers = {**self.headers, **(ext_headers or {})}
|
|
142
|
+
|
|
143
|
+
try:
|
|
144
|
+
path = await self._download_file_with_httpx(
|
|
145
|
+
url, file_path=file_path, headers=headers, chunk_size=chunk_size
|
|
146
|
+
)
|
|
147
|
+
except httpx.HTTPError:
|
|
148
|
+
logger.opt(exception=True).warning(f"下载失败(httpx) | url: {url}")
|
|
149
|
+
try:
|
|
150
|
+
path = await self._download_file_with_curl_cffi(url, file_path=file_path, headers=headers)
|
|
151
|
+
except curl_cffi.CurlError:
|
|
152
|
+
logger.opt(exception=True).warning(f"下载失败(curl_cffi) | url: {url}")
|
|
153
|
+
raise DownloadException("媒体下载失败")
|
|
154
|
+
|
|
155
|
+
return path
|
|
105
156
|
|
|
106
157
|
@auto_task
|
|
107
158
|
async def download_video(
|
|
@@ -199,7 +250,7 @@ class StreamDownloader:
|
|
|
199
250
|
await f.write(chunk)
|
|
200
251
|
total_size += len(chunk)
|
|
201
252
|
update_progress(advance=len(chunk), total=total_size)
|
|
202
|
-
except HTTPError:
|
|
253
|
+
except httpx.HTTPError:
|
|
203
254
|
await safe_unlink(video_path)
|
|
204
255
|
logger.exception("m3u8 视频下载失败")
|
|
205
256
|
raise DownloadException("m3u8 视频下载失败")
|
|
@@ -224,13 +275,18 @@ class StreamDownloader:
|
|
|
224
275
|
return slices
|
|
225
276
|
|
|
226
277
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
278
|
+
downloader: StreamDownloader = StreamDownloader()
|
|
279
|
+
"""全局下载器实例,提供下载功能"""
|
|
280
|
+
yt_dlp_downloader = None
|
|
281
|
+
"""yt-dlp 下载器实例,提供下载视频功能,若 yt-dlp 未安装则为 None"""
|
|
231
282
|
|
|
283
|
+
if is_module_available("yt_dlp"):
|
|
232
284
|
from .ytdlp import YtdlpDownloader
|
|
233
285
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
286
|
+
yt_dlp_downloader = YtdlpDownloader()
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
@get_driver().on_shutdown
|
|
290
|
+
async def close_download_client():
|
|
291
|
+
logger.debug("正在关闭下载器...")
|
|
292
|
+
await downloader.aclose()
|
{nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/helper.py
RENAMED
|
@@ -31,6 +31,14 @@ EMOJI_MAP = {
|
|
|
31
31
|
"resolving": ("424", "👀"),
|
|
32
32
|
"done": ("144", "🎉"),
|
|
33
33
|
}
|
|
34
|
+
"""emoji 映射"""
|
|
35
|
+
|
|
36
|
+
ID_ADAPTERS = {
|
|
37
|
+
SupportAdapter.onebot11,
|
|
38
|
+
SupportAdapter.qq,
|
|
39
|
+
SupportAdapter.milky,
|
|
40
|
+
}
|
|
41
|
+
"""支持的传入 emoji id 发送 reaction 的适配器"""
|
|
34
42
|
|
|
35
43
|
|
|
36
44
|
class UniHelper:
|
|
@@ -127,15 +135,12 @@ class UniHelper:
|
|
|
127
135
|
message_id = uniseg.get_message_id(event)
|
|
128
136
|
target = uniseg.get_target(event)
|
|
129
137
|
|
|
130
|
-
if target.adapter in
|
|
131
|
-
emoji = EMOJI_MAP[status][0]
|
|
132
|
-
else:
|
|
133
|
-
emoji = EMOJI_MAP[status][1]
|
|
138
|
+
emoji = EMOJI_MAP[status][0 if target.adapter in ID_ADAPTERS else 1]
|
|
134
139
|
|
|
135
140
|
try:
|
|
136
141
|
await uniseg.message_reaction(emoji, message_id=message_id)
|
|
137
142
|
except Exception:
|
|
138
|
-
logger.warning(f"reaction {emoji} to {message_id} failed, maybe not support")
|
|
143
|
+
logger.opt(exception=True).warning(f"reaction {emoji} to {message_id} failed, maybe not support")
|
|
139
144
|
|
|
140
145
|
@classmethod
|
|
141
146
|
def with_reaction(cls, func: Callable[..., Awaitable[Any]]):
|
|
@@ -112,9 +112,9 @@ async def _(message: Message = CommandArg()):
|
|
|
112
112
|
await UniMessage(UniHelper.file_seg(audio_path)).send()
|
|
113
113
|
|
|
114
114
|
|
|
115
|
-
from ..download import
|
|
115
|
+
from ..download import yt_dlp_downloader
|
|
116
116
|
|
|
117
|
-
if
|
|
117
|
+
if yt_dlp_downloader is not None:
|
|
118
118
|
from ..parsers import YouTubeParser
|
|
119
119
|
|
|
120
120
|
@on_command("ym", priority=3, block=True).handle()
|
|
@@ -128,7 +128,7 @@ if YTDLP_DOWNLOADER is not None:
|
|
|
128
128
|
|
|
129
129
|
url = matched.group(0)
|
|
130
130
|
|
|
131
|
-
audio_path = await
|
|
131
|
+
audio_path = await yt_dlp_downloader.download_audio(url)
|
|
132
132
|
await UniMessage(UniHelper.record_seg(audio_path)).send()
|
|
133
133
|
|
|
134
134
|
if pconfig.need_upload:
|
|
@@ -7,10 +7,10 @@ from .douyin import DouyinParser as DouyinParser
|
|
|
7
7
|
from .twitter import TwitterParser as TwitterParser
|
|
8
8
|
from .bilibili import BilibiliParser as BilibiliParser
|
|
9
9
|
from .kuaishou import KuaiShouParser as KuaiShouParser
|
|
10
|
-
from ..download import
|
|
10
|
+
from ..download import yt_dlp_downloader as yt_dlp_downloader
|
|
11
11
|
from .xiaohongshu import XiaoHongShuParser as XiaoHongShuParser
|
|
12
12
|
|
|
13
|
-
if
|
|
13
|
+
if yt_dlp_downloader is not None:
|
|
14
14
|
from .tiktok import TikTokParser as TikTokParser
|
|
15
15
|
from .youtube import YouTubeParser as YouTubeParser
|
|
16
16
|
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
from re import Match, Pattern, compile
|
|
2
2
|
from abc import ABC
|
|
3
|
-
from typing import TYPE_CHECKING, Any, TypeVar, ClassVar, cast
|
|
3
|
+
from typing import TYPE_CHECKING, Any, TypeVar, ClassVar, cast, final
|
|
4
4
|
from asyncio import Task
|
|
5
5
|
from pathlib import Path
|
|
6
6
|
from collections.abc import Callable, Coroutine
|
|
7
|
-
from typing_extensions import Unpack
|
|
7
|
+
from typing_extensions import Unpack
|
|
8
8
|
|
|
9
9
|
from .data import Platform, ParseResult, ImageContent, ParseResultKwargs
|
|
10
10
|
from .task import PathTask
|
|
11
11
|
from ..config import pconfig as pconfig
|
|
12
|
-
from ..download import
|
|
12
|
+
from ..download import downloader
|
|
13
13
|
from ..constants import IOS_HEADER, COMMON_HEADER, ANDROID_HEADER, COMMON_TIMEOUT
|
|
14
14
|
from ..constants import DOWNLOAD_TIMEOUT as DOWNLOAD_TIMEOUT
|
|
15
15
|
from ..constants import PlatformEnum as PlatformEnum
|
|
@@ -170,7 +170,7 @@ class BaseParser:
|
|
|
170
170
|
author = Author(name=name, description=description)
|
|
171
171
|
|
|
172
172
|
if avatar_url:
|
|
173
|
-
author.avatar = PathTask(
|
|
173
|
+
author.avatar = PathTask(downloader.download_img(avatar_url, ext_headers=self.headers))
|
|
174
174
|
|
|
175
175
|
return author
|
|
176
176
|
|
|
@@ -179,31 +179,48 @@ class BaseParser:
|
|
|
179
179
|
url_or_task: str | Task[Path],
|
|
180
180
|
cover_url: str | None = None,
|
|
181
181
|
duration: float | None = None,
|
|
182
|
+
is_gif: bool = False,
|
|
182
183
|
):
|
|
183
|
-
"""
|
|
184
|
+
"""创建视频内容, 未指定封面时会尝试从视频中提取封面"""
|
|
184
185
|
from .data import VideoContent
|
|
185
|
-
from ..utils import
|
|
186
|
+
from ..utils import convert_video_to_gif, extract_video_first_frame
|
|
186
187
|
|
|
187
188
|
if isinstance(url_or_task, str):
|
|
188
|
-
path_task =
|
|
189
|
+
path_task = downloader.download_video(url_or_task, ext_headers=self.headers)
|
|
189
190
|
elif isinstance(url_or_task, Task):
|
|
190
191
|
path_task = url_or_task
|
|
191
192
|
|
|
193
|
+
video_content = VideoContent(PathTask(path_task), duration=duration, is_gif=is_gif)
|
|
194
|
+
|
|
192
195
|
if cover_url:
|
|
193
|
-
cover_task =
|
|
196
|
+
cover_task = downloader.download_img(cover_url, ext_headers=self.headers)
|
|
194
197
|
else:
|
|
195
198
|
# 如果没有封面 URL,尝试从视频中提取封面
|
|
196
199
|
async def extract_cover():
|
|
197
200
|
video_path = await path_task
|
|
198
|
-
return await
|
|
201
|
+
return await extract_video_first_frame(video_path)
|
|
199
202
|
|
|
200
203
|
cover_task = extract_cover()
|
|
201
204
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
205
|
+
video_content.cover = PathTask(cover_task)
|
|
206
|
+
|
|
207
|
+
if is_gif:
|
|
208
|
+
# 需要转换为 GIF
|
|
209
|
+
async def convert_to_gif():
|
|
210
|
+
video_path = await path_task
|
|
211
|
+
return await convert_video_to_gif(video_path)
|
|
212
|
+
|
|
213
|
+
video_content.gif_path = PathTask(convert_to_gif())
|
|
214
|
+
|
|
215
|
+
return video_content
|
|
216
|
+
|
|
217
|
+
def create_gif(
|
|
218
|
+
self,
|
|
219
|
+
url_or_task: str | Task[Path],
|
|
220
|
+
cover_url: str | None = None,
|
|
221
|
+
):
|
|
222
|
+
"""创建 GIF 内容"""
|
|
223
|
+
return self.create_video(url_or_task, cover_url=cover_url, is_gif=True)
|
|
207
224
|
|
|
208
225
|
def create_images(
|
|
209
226
|
self,
|
|
@@ -212,7 +229,7 @@ class BaseParser:
|
|
|
212
229
|
"""创建图片内容列表"""
|
|
213
230
|
contents: list[ImageContent] = []
|
|
214
231
|
for url in image_urls:
|
|
215
|
-
task =
|
|
232
|
+
task = downloader.download_img(url, ext_headers=self.headers)
|
|
216
233
|
contents.append(ImageContent(PathTask(task)))
|
|
217
234
|
return contents
|
|
218
235
|
|
|
@@ -223,7 +240,7 @@ class BaseParser:
|
|
|
223
240
|
):
|
|
224
241
|
"""创建单个图片内容"""
|
|
225
242
|
if isinstance(url_or_task, str):
|
|
226
|
-
path_task =
|
|
243
|
+
path_task = downloader.download_img(url_or_task, ext_headers=self.headers)
|
|
227
244
|
elif isinstance(url_or_task, Task):
|
|
228
245
|
path_task = url_or_task
|
|
229
246
|
|
|
@@ -238,7 +255,7 @@ class BaseParser:
|
|
|
238
255
|
from .data import AudioContent
|
|
239
256
|
|
|
240
257
|
if isinstance(url_or_task, str):
|
|
241
|
-
path_task =
|
|
258
|
+
path_task = downloader.download_audio(url_or_task, ext_headers=self.headers)
|
|
242
259
|
elif isinstance(url_or_task, Task):
|
|
243
260
|
path_task = url_or_task
|
|
244
261
|
|
|
@@ -246,4 +263,4 @@ class BaseParser:
|
|
|
246
263
|
|
|
247
264
|
@property
|
|
248
265
|
def downloader(self):
|
|
249
|
-
return
|
|
266
|
+
return downloader
|