nonebot-plugin-parser 2.4.3__tar.gz → 2.5.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {nonebot_plugin_parser-2.4.3 → nonebot_plugin_parser-2.5.1}/PKG-INFO +7 -17
- {nonebot_plugin_parser-2.4.3 → nonebot_plugin_parser-2.5.1}/README.md +3 -13
- {nonebot_plugin_parser-2.4.3 → nonebot_plugin_parser-2.5.1}/pyproject.toml +11 -12
- {nonebot_plugin_parser-2.4.3 → nonebot_plugin_parser-2.5.1}/src/nonebot_plugin_parser/config.py +17 -2
- {nonebot_plugin_parser-2.4.3 → nonebot_plugin_parser-2.5.1}/src/nonebot_plugin_parser/download/__init__.py +19 -3
- nonebot_plugin_parser-2.5.1/src/nonebot_plugin_parser/download/task.py +84 -0
- {nonebot_plugin_parser-2.4.3 → nonebot_plugin_parser-2.5.1}/src/nonebot_plugin_parser/helper.py +1 -2
- {nonebot_plugin_parser-2.4.3 → nonebot_plugin_parser-2.5.1}/src/nonebot_plugin_parser/matchers/__init__.py +2 -2
- {nonebot_plugin_parser-2.4.3 → nonebot_plugin_parser-2.5.1}/src/nonebot_plugin_parser/parsers/__init__.py +0 -2
- {nonebot_plugin_parser-2.4.3 → nonebot_plugin_parser-2.5.1}/src/nonebot_plugin_parser/parsers/acfun/__init__.py +5 -2
- {nonebot_plugin_parser-2.4.3 → nonebot_plugin_parser-2.5.1}/src/nonebot_plugin_parser/parsers/base.py +46 -29
- {nonebot_plugin_parser-2.4.3 → nonebot_plugin_parser-2.5.1}/src/nonebot_plugin_parser/parsers/bilibili/__init__.py +55 -30
- {nonebot_plugin_parser-2.4.3 → nonebot_plugin_parser-2.5.1}/src/nonebot_plugin_parser/parsers/bilibili/dynamic.py +60 -25
- {nonebot_plugin_parser-2.4.3 → nonebot_plugin_parser-2.5.1}/src/nonebot_plugin_parser/parsers/bilibili/opus.py +42 -33
- {nonebot_plugin_parser-2.4.3 → nonebot_plugin_parser-2.5.1}/src/nonebot_plugin_parser/parsers/data.py +89 -80
- {nonebot_plugin_parser-2.4.3 → nonebot_plugin_parser-2.5.1}/src/nonebot_plugin_parser/parsers/douyin/__init__.py +26 -26
- {nonebot_plugin_parser-2.4.3 → nonebot_plugin_parser-2.5.1}/src/nonebot_plugin_parser/parsers/kuaishou/__init__.py +13 -13
- {nonebot_plugin_parser-2.4.3 → nonebot_plugin_parser-2.5.1}/src/nonebot_plugin_parser/parsers/nga.py +12 -30
- {nonebot_plugin_parser-2.4.3 → nonebot_plugin_parser-2.5.1}/src/nonebot_plugin_parser/parsers/tiktok.py +8 -4
- {nonebot_plugin_parser-2.4.3 → nonebot_plugin_parser-2.5.1}/src/nonebot_plugin_parser/parsers/twitter.py +29 -41
- {nonebot_plugin_parser-2.4.3 → nonebot_plugin_parser-2.5.1}/src/nonebot_plugin_parser/parsers/weibo/__init__.py +25 -30
- {nonebot_plugin_parser-2.4.3 → nonebot_plugin_parser-2.5.1}/src/nonebot_plugin_parser/parsers/xiaohongshu/__init__.py +24 -25
- {nonebot_plugin_parser-2.4.3 → nonebot_plugin_parser-2.5.1}/src/nonebot_plugin_parser/parsers/youtube/__init__.py +11 -32
- {nonebot_plugin_parser-2.4.3 → nonebot_plugin_parser-2.5.1}/src/nonebot_plugin_parser/renders/__init__.py +6 -11
- {nonebot_plugin_parser-2.4.3 → nonebot_plugin_parser-2.5.1}/src/nonebot_plugin_parser/renders/base.py +41 -36
- nonebot_plugin_parser-2.5.1/src/nonebot_plugin_parser/renders/common.py +748 -0
- {nonebot_plugin_parser-2.4.3 → nonebot_plugin_parser-2.5.1}/src/nonebot_plugin_parser/renders/default.py +8 -8
- nonebot_plugin_parser-2.5.1/src/nonebot_plugin_parser/renders/htmlrender.py +38 -0
- nonebot_plugin_parser-2.5.1/src/nonebot_plugin_parser/renders/resources/__init__.py +10 -0
- nonebot_plugin_parser-2.5.1/src/nonebot_plugin_parser/renders/templates/card.html.jinja2 +668 -0
- {nonebot_plugin_parser-2.4.3 → nonebot_plugin_parser-2.5.1}/src/nonebot_plugin_parser/utils.py +11 -0
- nonebot_plugin_parser-2.4.3/src/nonebot_plugin_parser/download/task.py +0 -19
- nonebot_plugin_parser-2.4.3/src/nonebot_plugin_parser/renders/common.py +0 -665
- nonebot_plugin_parser-2.4.3/src/nonebot_plugin_parser/renders/htmlrender.py +0 -88
- nonebot_plugin_parser-2.4.3/src/nonebot_plugin_parser/renders/templates/card.html.jinja +0 -394
- nonebot_plugin_parser-2.4.3/src/nonebot_plugin_parser/renders/templates/weibo.html.jinja +0 -425
- nonebot_plugin_parser-2.4.3/src/nonebot_plugin_parser/renders/weibo.py +0 -18
- {nonebot_plugin_parser-2.4.3 → nonebot_plugin_parser-2.5.1}/src/nonebot_plugin_parser/__init__.py +0 -0
- {nonebot_plugin_parser-2.4.3 → nonebot_plugin_parser-2.5.1}/src/nonebot_plugin_parser/constants.py +0 -0
- {nonebot_plugin_parser-2.4.3 → nonebot_plugin_parser-2.5.1}/src/nonebot_plugin_parser/download/ytdlp.py +0 -0
- {nonebot_plugin_parser-2.4.3 → nonebot_plugin_parser-2.5.1}/src/nonebot_plugin_parser/exception.py +0 -0
- {nonebot_plugin_parser-2.4.3 → nonebot_plugin_parser-2.5.1}/src/nonebot_plugin_parser/matchers/filter.py +0 -0
- {nonebot_plugin_parser-2.4.3 → nonebot_plugin_parser-2.5.1}/src/nonebot_plugin_parser/matchers/rule.py +0 -0
- {nonebot_plugin_parser-2.4.3 → nonebot_plugin_parser-2.5.1}/src/nonebot_plugin_parser/parsers/acfun/video.py +0 -0
- {nonebot_plugin_parser-2.4.3 → nonebot_plugin_parser-2.5.1}/src/nonebot_plugin_parser/parsers/bilibili/article.py +0 -0
- {nonebot_plugin_parser-2.4.3 → nonebot_plugin_parser-2.5.1}/src/nonebot_plugin_parser/parsers/bilibili/common.py +0 -0
- {nonebot_plugin_parser-2.4.3 → nonebot_plugin_parser-2.5.1}/src/nonebot_plugin_parser/parsers/bilibili/favlist.py +0 -0
- {nonebot_plugin_parser-2.4.3 → nonebot_plugin_parser-2.5.1}/src/nonebot_plugin_parser/parsers/bilibili/live.py +0 -0
- {nonebot_plugin_parser-2.4.3 → nonebot_plugin_parser-2.5.1}/src/nonebot_plugin_parser/parsers/bilibili/video.py +0 -0
- {nonebot_plugin_parser-2.4.3 → nonebot_plugin_parser-2.5.1}/src/nonebot_plugin_parser/parsers/cookie.py +0 -0
- {nonebot_plugin_parser-2.4.3 → nonebot_plugin_parser-2.5.1}/src/nonebot_plugin_parser/parsers/douyin/slides.py +0 -0
- {nonebot_plugin_parser-2.4.3 → nonebot_plugin_parser-2.5.1}/src/nonebot_plugin_parser/parsers/douyin/video.py +0 -0
- {nonebot_plugin_parser-2.4.3 → nonebot_plugin_parser-2.5.1}/src/nonebot_plugin_parser/parsers/kuaishou/states.py +0 -0
- {nonebot_plugin_parser-2.4.3 → nonebot_plugin_parser-2.5.1}/src/nonebot_plugin_parser/parsers/weibo/article.py +0 -0
- {nonebot_plugin_parser-2.4.3 → nonebot_plugin_parser-2.5.1}/src/nonebot_plugin_parser/parsers/weibo/common.py +0 -0
- {nonebot_plugin_parser-2.4.3 → nonebot_plugin_parser-2.5.1}/src/nonebot_plugin_parser/parsers/weibo/show.py +0 -0
- {nonebot_plugin_parser-2.4.3 → nonebot_plugin_parser-2.5.1}/src/nonebot_plugin_parser/parsers/xiaohongshu/common.py +0 -0
- {nonebot_plugin_parser-2.4.3 → nonebot_plugin_parser-2.5.1}/src/nonebot_plugin_parser/parsers/xiaohongshu/discovery.py +0 -0
- {nonebot_plugin_parser-2.4.3 → nonebot_plugin_parser-2.5.1}/src/nonebot_plugin_parser/parsers/xiaohongshu/explore.py +0 -0
- {nonebot_plugin_parser-2.4.3 → nonebot_plugin_parser-2.5.1}/src/nonebot_plugin_parser/parsers/youtube/meta.py +0 -0
- {nonebot_plugin_parser-2.4.3 → nonebot_plugin_parser-2.5.1}/src/nonebot_plugin_parser/renders/resources/HYSongYunLangHeiW.ttf +0 -0
- {nonebot_plugin_parser-2.4.3 → nonebot_plugin_parser-2.5.1}/src/nonebot_plugin_parser/renders/resources/avatar.png +0 -0
- {nonebot_plugin_parser-2.4.3 → nonebot_plugin_parser-2.5.1}/src/nonebot_plugin_parser/renders/resources/bilibili.png +0 -0
- {nonebot_plugin_parser-2.4.3 → nonebot_plugin_parser-2.5.1}/src/nonebot_plugin_parser/renders/resources/douyin.png +0 -0
- {nonebot_plugin_parser-2.4.3 → nonebot_plugin_parser-2.5.1}/src/nonebot_plugin_parser/renders/resources/kuaishou.png +0 -0
- {nonebot_plugin_parser-2.4.3 → nonebot_plugin_parser-2.5.1}/src/nonebot_plugin_parser/renders/resources/play.png +0 -0
- {nonebot_plugin_parser-2.4.3 → nonebot_plugin_parser-2.5.1}/src/nonebot_plugin_parser/renders/resources/tiktok.png +0 -0
- {nonebot_plugin_parser-2.4.3 → nonebot_plugin_parser-2.5.1}/src/nonebot_plugin_parser/renders/resources/twitter.png +0 -0
- {nonebot_plugin_parser-2.4.3 → nonebot_plugin_parser-2.5.1}/src/nonebot_plugin_parser/renders/resources/weibo.png +0 -0
- {nonebot_plugin_parser-2.4.3 → nonebot_plugin_parser-2.5.1}/src/nonebot_plugin_parser/renders/resources/xiaohongshu.png +0 -0
- {nonebot_plugin_parser-2.4.3 → nonebot_plugin_parser-2.5.1}/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.
|
|
3
|
+
Version: 2.5.1
|
|
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
|
|
@@ -35,9 +35,9 @@ Requires-Dist: nonebot-plugin-localstore>=0.7.4,<1.0.0
|
|
|
35
35
|
Requires-Dist: nonebot-plugin-uninfo>=0.10.1,<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
|
-
Requires-Dist: yt-dlp[default]>=2026.
|
|
39
|
-
Requires-Dist: emosvg>=0.1.
|
|
40
|
-
Requires-Dist: emosvg>=0.1.
|
|
38
|
+
Requires-Dist: yt-dlp[default]>=2026.3.13 ; extra == 'all'
|
|
39
|
+
Requires-Dist: emosvg>=0.1.7 ; extra == 'all'
|
|
40
|
+
Requires-Dist: emosvg>=0.1.7 ; extra == 'emosvg'
|
|
41
41
|
Requires-Dist: nonebot-plugin-htmlkit>=0.1.0rc4 ; extra == 'htmlkit'
|
|
42
42
|
Requires-Dist: nonebot-plugin-htmlrender>=0.6.7 ; extra == 'htmlrender'
|
|
43
43
|
Requires-Dist: yt-dlp[default]>=2025.2.21 ; extra == 'ytdlp'
|
|
@@ -199,7 +199,7 @@ Description-Content-Type: text/markdown
|
|
|
199
199
|
|
|
200
200
|
uv add "nonebot-plugin-parser[htmlkit]"
|
|
201
201
|
|
|
202
|
-
`htmlrender`, 使用 `playwright` 渲染 `html`,
|
|
202
|
+
`htmlrender`, 使用 `playwright` 渲染 `html`, 插件自 `v2.5.0` 起已正式支持
|
|
203
203
|
|
|
204
204
|
uv add "nonebot-plugin-parser[htmlrender]"
|
|
205
205
|
|
|
@@ -300,8 +300,8 @@ parser_render_type="common"
|
|
|
300
300
|
parser_append_url=False
|
|
301
301
|
|
|
302
302
|
# [可选] 自定义渲染字体
|
|
303
|
-
# 配置字体文件名,并将字体文件放置于 localstore 生成的插件
|
|
304
|
-
# 例如: ./
|
|
303
|
+
# 配置字体文件名,并将字体文件放置于 localstore 生成的插件 config 目录下
|
|
304
|
+
# 例如: ./config/nonebot_plugin_parser/
|
|
305
305
|
parser_custom_font="LXGWZhenKaiGB-Regular.ttf"
|
|
306
306
|
|
|
307
307
|
# [可选] 是否需要转发媒体内容(超过 4 项时始终使用合并转发)
|
|
@@ -452,16 +452,6 @@ images = self.create_image_contents([
|
|
|
452
452
|
])
|
|
453
453
|
```
|
|
454
454
|
|
|
455
|
-
> 构建图文内容(适用于类似 Bilibili 动态图文混排)
|
|
456
|
-
|
|
457
|
-
```python
|
|
458
|
-
graphics = self.create_graphics_content(
|
|
459
|
-
image_url="https://example.com/image.jpg",
|
|
460
|
-
text="图片前的文字说明", # 可选
|
|
461
|
-
alt="图片描述" # 可选,居中显示
|
|
462
|
-
)
|
|
463
|
-
```
|
|
464
|
-
|
|
465
455
|
> 创建动图内容(GIF),平台一般只提供视频(后续插件会做自动转为 gif 的处理)
|
|
466
456
|
|
|
467
457
|
```python
|
|
@@ -145,7 +145,7 @@
|
|
|
145
145
|
|
|
146
146
|
uv add "nonebot-plugin-parser[htmlkit]"
|
|
147
147
|
|
|
148
|
-
`htmlrender`, 使用 `playwright` 渲染 `html`,
|
|
148
|
+
`htmlrender`, 使用 `playwright` 渲染 `html`, 插件自 `v2.5.0` 起已正式支持
|
|
149
149
|
|
|
150
150
|
uv add "nonebot-plugin-parser[htmlrender]"
|
|
151
151
|
|
|
@@ -246,8 +246,8 @@ parser_render_type="common"
|
|
|
246
246
|
parser_append_url=False
|
|
247
247
|
|
|
248
248
|
# [可选] 自定义渲染字体
|
|
249
|
-
# 配置字体文件名,并将字体文件放置于 localstore 生成的插件
|
|
250
|
-
# 例如: ./
|
|
249
|
+
# 配置字体文件名,并将字体文件放置于 localstore 生成的插件 config 目录下
|
|
250
|
+
# 例如: ./config/nonebot_plugin_parser/
|
|
251
251
|
parser_custom_font="LXGWZhenKaiGB-Regular.ttf"
|
|
252
252
|
|
|
253
253
|
# [可选] 是否需要转发媒体内容(超过 4 项时始终使用合并转发)
|
|
@@ -398,16 +398,6 @@ images = self.create_image_contents([
|
|
|
398
398
|
])
|
|
399
399
|
```
|
|
400
400
|
|
|
401
|
-
> 构建图文内容(适用于类似 Bilibili 动态图文混排)
|
|
402
|
-
|
|
403
|
-
```python
|
|
404
|
-
graphics = self.create_graphics_content(
|
|
405
|
-
image_url="https://example.com/image.jpg",
|
|
406
|
-
text="图片前的文字说明", # 可选
|
|
407
|
-
alt="图片描述" # 可选,居中显示
|
|
408
|
-
)
|
|
409
|
-
```
|
|
410
|
-
|
|
411
401
|
> 创建动图内容(GIF),平台一般只提供视频(后续插件会做自动转为 gif 的处理)
|
|
412
402
|
|
|
413
403
|
```python
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "nonebot-plugin-parser"
|
|
3
|
-
version = "2.
|
|
3
|
+
version = "2.5.1"
|
|
4
4
|
description = "NoneBot2 链接分享解析 Alconna 版, 现支持B站|抖音|快手|微博|小红书|YouTube|TikTok|Twitter|AcFun|NGA"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.10"
|
|
@@ -53,7 +53,6 @@ dependencies = [
|
|
|
53
53
|
"nonebot-plugin-apscheduler>=0.5.0,<1.0.0",
|
|
54
54
|
"nonebot-plugin-localstore>=0.7.4,<1.0.0",
|
|
55
55
|
"nonebot-plugin-uninfo>=0.10.1,<1.0.0",
|
|
56
|
-
|
|
57
56
|
]
|
|
58
57
|
|
|
59
58
|
[project.urls]
|
|
@@ -65,32 +64,32 @@ Repository = "https://github.com/fllesser/nonebot-plugin-parser"
|
|
|
65
64
|
htmlkit = ["nonebot-plugin-htmlkit>=0.1.0rc4"]
|
|
66
65
|
htmlrender = ["nonebot-plugin-htmlrender>=0.6.7"]
|
|
67
66
|
ytdlp = ["yt-dlp[default]>=2025.2.21"]
|
|
68
|
-
emosvg = ["emosvg>=0.1.
|
|
67
|
+
emosvg = ["emosvg>=0.1.7"]
|
|
69
68
|
all = [
|
|
70
69
|
"nonebot-plugin-htmlkit>=0.1.0rc4",
|
|
71
70
|
"nonebot-plugin-htmlrender>=0.6.7",
|
|
72
|
-
"yt-dlp[default]>=2026.
|
|
73
|
-
"emosvg>=0.1.
|
|
71
|
+
"yt-dlp[default]>=2026.3.13",
|
|
72
|
+
"emosvg>=0.1.7",
|
|
74
73
|
]
|
|
75
74
|
|
|
76
75
|
[dependency-groups]
|
|
77
76
|
dev = [
|
|
78
77
|
"nonebot2[fastapi]>=2.4.3,<3.0.0",
|
|
79
78
|
"nonebot-adapter-onebot>=2.4.6",
|
|
80
|
-
"ruff>=0.15.
|
|
79
|
+
"ruff>=0.15.6,<1.0.0",
|
|
81
80
|
{ include-group = "extras" },
|
|
82
81
|
{ include-group = "test" },
|
|
83
82
|
]
|
|
84
83
|
extras = [
|
|
85
|
-
"emosvg>=0.1.
|
|
84
|
+
"emosvg>=0.1.7",
|
|
86
85
|
"nonebot-plugin-htmlkit>=0.1.0rc4",
|
|
87
86
|
"nonebot-plugin-htmlrender>=0.6.7",
|
|
88
|
-
"yt-dlp[default]>=2026.
|
|
89
|
-
"types-yt-dlp>=2026.
|
|
87
|
+
"yt-dlp[default]>=2026.3.13",
|
|
88
|
+
"types-yt-dlp>=2026.3.13.20260314",
|
|
90
89
|
]
|
|
91
90
|
test = [
|
|
92
91
|
"nonebug>=0.4.3,<1.0.0",
|
|
93
|
-
"poethepoet>=0.42.
|
|
92
|
+
"poethepoet>=0.42.1",
|
|
94
93
|
"pytest-asyncio>=1.3.0,<1.4.0",
|
|
95
94
|
"pytest-cov>=7.0.0",
|
|
96
95
|
"pytest-xdist>=3.8.0,<4.0.0",
|
|
@@ -114,12 +113,12 @@ conflicts = [
|
|
|
114
113
|
]
|
|
115
114
|
|
|
116
115
|
[tool.uv.sources]
|
|
117
|
-
nonebug = { git = "https://github.com/nonebot/nonebug" }
|
|
116
|
+
nonebug = { git = "https://github.com/nonebot/nonebug", rev = "master" }
|
|
118
117
|
|
|
119
118
|
[tool.bumpversion]
|
|
120
119
|
tag = true
|
|
121
120
|
commit = true
|
|
122
|
-
current_version = "2.
|
|
121
|
+
current_version = "2.5.1"
|
|
123
122
|
message = "release: bump vesion from {current_version} to {new_version}"
|
|
124
123
|
|
|
125
124
|
[[tool.bumpversion.files]]
|
{nonebot_plugin_parser-2.4.3 → nonebot_plugin_parser-2.5.1}/src/nonebot_plugin_parser/config.py
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from pathlib import Path
|
|
2
2
|
|
|
3
|
-
from nonebot import require, get_driver, get_plugin_config
|
|
3
|
+
from nonebot import logger, require, get_driver, get_plugin_config
|
|
4
4
|
from apilmoji import ELK_SH_CDN, EmojiStyle
|
|
5
5
|
from pydantic import BaseModel
|
|
6
6
|
from bilibili_api.video import VideoCodecs, VideoQuality
|
|
@@ -143,7 +143,22 @@ class Config(BaseModel):
|
|
|
143
143
|
@property
|
|
144
144
|
def custom_font(self) -> Path | None:
|
|
145
145
|
"""自定义字体"""
|
|
146
|
-
|
|
146
|
+
if self.parser_custom_font:
|
|
147
|
+
font_path = self.config_dir / self.parser_custom_font
|
|
148
|
+
if font_path.exists():
|
|
149
|
+
return font_path
|
|
150
|
+
|
|
151
|
+
# 尝试从旧路径迁移字体文件
|
|
152
|
+
old_path = self.data_dir / self.parser_custom_font
|
|
153
|
+
if old_path.exists():
|
|
154
|
+
try:
|
|
155
|
+
old_path.rename(font_path)
|
|
156
|
+
logger.info(f"字体文件 {old_path} 成功迁移到 {font_path}")
|
|
157
|
+
except OSError:
|
|
158
|
+
logger.error(f"字体文件迁移失败, 请手动将其移动到 {font_path}")
|
|
159
|
+
return old_path
|
|
160
|
+
|
|
161
|
+
return font_path
|
|
147
162
|
|
|
148
163
|
@property
|
|
149
164
|
def need_forward_contents(self) -> bool:
|
|
@@ -98,7 +98,13 @@ class StreamDownloader:
|
|
|
98
98
|
"""download video file by url with stream"""
|
|
99
99
|
if video_name is None:
|
|
100
100
|
video_name = generate_file_name(url, ".mp4")
|
|
101
|
-
|
|
101
|
+
|
|
102
|
+
return await self.download_file(
|
|
103
|
+
url,
|
|
104
|
+
file_name=video_name,
|
|
105
|
+
ext_headers=ext_headers,
|
|
106
|
+
chunk_size=1024 * 1024,
|
|
107
|
+
)
|
|
102
108
|
|
|
103
109
|
@auto_task
|
|
104
110
|
async def download_audio(
|
|
@@ -111,7 +117,12 @@ class StreamDownloader:
|
|
|
111
117
|
"""download audio file by url with stream"""
|
|
112
118
|
if audio_name is None:
|
|
113
119
|
audio_name = generate_file_name(url, ".mp3")
|
|
114
|
-
|
|
120
|
+
|
|
121
|
+
return await self.download_file(
|
|
122
|
+
url,
|
|
123
|
+
file_name=audio_name,
|
|
124
|
+
ext_headers=ext_headers,
|
|
125
|
+
)
|
|
115
126
|
|
|
116
127
|
@auto_task
|
|
117
128
|
async def download_img(
|
|
@@ -124,7 +135,12 @@ class StreamDownloader:
|
|
|
124
135
|
"""download image file by url with stream"""
|
|
125
136
|
if img_name is None:
|
|
126
137
|
img_name = generate_file_name(url, ".jpg")
|
|
127
|
-
|
|
138
|
+
|
|
139
|
+
return await self.download_file(
|
|
140
|
+
url,
|
|
141
|
+
file_name=img_name,
|
|
142
|
+
ext_headers=ext_headers,
|
|
143
|
+
)
|
|
128
144
|
|
|
129
145
|
@auto_task
|
|
130
146
|
async def download_av_and_merge(
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
from typing import Any, TypeVar, ParamSpec
|
|
2
|
+
from asyncio import Task, create_task
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from functools import wraps
|
|
5
|
+
from collections.abc import Callable, Coroutine
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class PathTask:
|
|
9
|
+
def __init__(self, task: Task[Path]):
|
|
10
|
+
self._task: Task[Path] = task
|
|
11
|
+
self._path: Path | None = None
|
|
12
|
+
|
|
13
|
+
async def get(self) -> Path:
|
|
14
|
+
if self._path is not None:
|
|
15
|
+
return self._path
|
|
16
|
+
|
|
17
|
+
self._path = await self._task
|
|
18
|
+
return self._path
|
|
19
|
+
|
|
20
|
+
def __await__(self):
|
|
21
|
+
return self.get().__await__()
|
|
22
|
+
|
|
23
|
+
async def safe_get(self) -> Path | None:
|
|
24
|
+
"""任务失败, 返回 None"""
|
|
25
|
+
try:
|
|
26
|
+
return await self.get()
|
|
27
|
+
except Exception:
|
|
28
|
+
return None
|
|
29
|
+
|
|
30
|
+
@property
|
|
31
|
+
def uri(self) -> str | None:
|
|
32
|
+
return self._path.as_uri() if self._path is not None else None
|
|
33
|
+
|
|
34
|
+
def __repr__(self) -> str:
|
|
35
|
+
if self._path is not None:
|
|
36
|
+
return f"PathTask(path={self._path.name})"
|
|
37
|
+
else:
|
|
38
|
+
return f"PathTask(task={self._task.get_name()}, done={self._task.done()})"
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class OptionalPathTask:
|
|
42
|
+
"""封装可选的 PathTask, 提供便捷的 API 避免频繁判空"""
|
|
43
|
+
|
|
44
|
+
def __init__(self, path_task: PathTask | None = None):
|
|
45
|
+
self._path_task: PathTask | None = path_task
|
|
46
|
+
|
|
47
|
+
async def get(self) -> Path | None:
|
|
48
|
+
if self._path_task is None:
|
|
49
|
+
return None
|
|
50
|
+
return await self._path_task.get()
|
|
51
|
+
|
|
52
|
+
def __await__(self):
|
|
53
|
+
return self.get().__await__()
|
|
54
|
+
|
|
55
|
+
async def safe_get(self) -> Path | None:
|
|
56
|
+
"""任务失败, 返回 None"""
|
|
57
|
+
if self._path_task is None:
|
|
58
|
+
return None
|
|
59
|
+
return await self._path_task.safe_get()
|
|
60
|
+
|
|
61
|
+
@property
|
|
62
|
+
def uri(self) -> str | None:
|
|
63
|
+
if self._path_task is None:
|
|
64
|
+
return None
|
|
65
|
+
return self._path_task.uri
|
|
66
|
+
|
|
67
|
+
def __repr__(self) -> str:
|
|
68
|
+
return f"{self._path_task}"
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
P = ParamSpec("P")
|
|
72
|
+
T = TypeVar("T")
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def auto_task(func: Callable[P, Coroutine[Any, Any, Path]]) -> Callable[P, PathTask]:
|
|
76
|
+
"""装饰器:自动将异步函数调用转换为 Task, 完整保留类型提示"""
|
|
77
|
+
|
|
78
|
+
@wraps(func)
|
|
79
|
+
def wrapper(*args: P.args, **kwargs: P.kwargs) -> PathTask:
|
|
80
|
+
coro = func(*args, **kwargs)
|
|
81
|
+
name = " | ".join(str(arg) for arg in args if isinstance(arg, str))
|
|
82
|
+
return PathTask(create_task(coro, name=func.__name__ + " | " + name))
|
|
83
|
+
|
|
84
|
+
return wrapper
|
|
@@ -78,8 +78,8 @@ async def parser_handler(
|
|
|
78
78
|
logger.debug(f"命中缓存: {cache_key}, 结果: {result}")
|
|
79
79
|
|
|
80
80
|
# 3. 渲染内容消息并发送
|
|
81
|
-
renderer = get_renderer(result.platform.name)
|
|
82
|
-
async for message in renderer.render_messages(
|
|
81
|
+
renderer = get_renderer(result.platform.name)(result)
|
|
82
|
+
async for message in renderer.render_messages():
|
|
83
83
|
await message.send()
|
|
84
84
|
|
|
85
85
|
# 4. 缓存解析结果
|
|
@@ -23,7 +23,6 @@ from .data import (
|
|
|
23
23
|
ImageContent,
|
|
24
24
|
VideoContent,
|
|
25
25
|
DynamicContent,
|
|
26
|
-
GraphicsContent,
|
|
27
26
|
)
|
|
28
27
|
|
|
29
28
|
__all__ = [
|
|
@@ -31,7 +30,6 @@ __all__ = [
|
|
|
31
30
|
"Author",
|
|
32
31
|
"BaseParser",
|
|
33
32
|
"DynamicContent",
|
|
34
|
-
"GraphicsContent",
|
|
35
33
|
"ImageContent",
|
|
36
34
|
"ParseResult",
|
|
37
35
|
"Platform",
|
|
@@ -40,14 +40,17 @@ class AcfunParser(BaseParser):
|
|
|
40
40
|
video_name=f"acfun_{acid}.mp4",
|
|
41
41
|
)
|
|
42
42
|
|
|
43
|
-
video_content = self.create_video_content(
|
|
43
|
+
video_content = self.create_video_content(
|
|
44
|
+
video_task,
|
|
45
|
+
cover_url=video_info.coverUrl,
|
|
46
|
+
)
|
|
44
47
|
|
|
45
48
|
return self.result(
|
|
46
49
|
title=video_info.title,
|
|
47
50
|
text=video_info.text,
|
|
48
51
|
author=author,
|
|
49
52
|
timestamp=video_info.timestamp,
|
|
50
|
-
|
|
53
|
+
video=video_content,
|
|
51
54
|
)
|
|
52
55
|
|
|
53
56
|
async def parse_video_info(self, url: str):
|
|
@@ -4,9 +4,9 @@ from typing import TYPE_CHECKING, Any, TypeVar, ClassVar, cast
|
|
|
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, final
|
|
8
8
|
|
|
9
|
-
from .data import Platform, ParseResult, ParseResultKwargs
|
|
9
|
+
from .data import Platform, ParseResult, ImageContent, ParseResultKwargs
|
|
10
10
|
from ..config import pconfig as pconfig
|
|
11
11
|
from ..download import DOWNLOADER
|
|
12
12
|
from ..constants import IOS_HEADER, COMMON_HEADER, ANDROID_HEADER, COMMON_TIMEOUT
|
|
@@ -15,6 +15,7 @@ from ..constants import PlatformEnum as PlatformEnum
|
|
|
15
15
|
from ..exception import ParseException
|
|
16
16
|
from ..exception import IgnoreException as IgnoreException
|
|
17
17
|
from ..exception import DownloadException as DownloadException
|
|
18
|
+
from ..download.task import PathTask, OptionalPathTask
|
|
18
19
|
|
|
19
20
|
T = TypeVar("T", bound="BaseParser")
|
|
20
21
|
HandlerFunc = Callable[[T, Match[str]], Coroutine[Any, Any, ParseResult]]
|
|
@@ -83,9 +84,11 @@ class BaseParser:
|
|
|
83
84
|
"""获取所有已注册的 Parser 类"""
|
|
84
85
|
return cls._registry
|
|
85
86
|
|
|
87
|
+
@final
|
|
86
88
|
async def parse(self, keyword: str, searched: Match[str]) -> ParseResult:
|
|
87
89
|
return await self._handlers[keyword](self, searched)
|
|
88
90
|
|
|
91
|
+
@final
|
|
89
92
|
async def parse_with_redirect(
|
|
90
93
|
self,
|
|
91
94
|
url: str,
|
|
@@ -167,13 +170,13 @@ class BaseParser:
|
|
|
167
170
|
avatar_task = None
|
|
168
171
|
if avatar_url:
|
|
169
172
|
avatar_task = DOWNLOADER.download_img(avatar_url, ext_headers=self.headers)
|
|
170
|
-
return Author(name=name, avatar=avatar_task, description=description)
|
|
173
|
+
return Author(name=name, avatar=OptionalPathTask(avatar_task), description=description)
|
|
171
174
|
|
|
172
175
|
def create_video_content(
|
|
173
176
|
self,
|
|
174
|
-
url_or_task: str | Task[Path],
|
|
177
|
+
url_or_task: str | Task[Path] | PathTask,
|
|
175
178
|
cover_url: str | None = None,
|
|
176
|
-
duration: float =
|
|
179
|
+
duration: float | None = None,
|
|
177
180
|
):
|
|
178
181
|
"""创建视频内容"""
|
|
179
182
|
from .data import VideoContent
|
|
@@ -181,18 +184,21 @@ class BaseParser:
|
|
|
181
184
|
cover_task = None
|
|
182
185
|
if cover_url:
|
|
183
186
|
cover_task = DOWNLOADER.download_img(cover_url, ext_headers=self.headers)
|
|
187
|
+
|
|
184
188
|
if isinstance(url_or_task, str):
|
|
185
|
-
|
|
189
|
+
path_task = DOWNLOADER.download_video(url_or_task, ext_headers=self.headers)
|
|
190
|
+
elif isinstance(url_or_task, Task):
|
|
191
|
+
path_task = PathTask(url_or_task)
|
|
192
|
+
elif isinstance(url_or_task, PathTask):
|
|
193
|
+
path_task = url_or_task
|
|
186
194
|
|
|
187
|
-
return VideoContent(
|
|
195
|
+
return VideoContent(path_task, OptionalPathTask(cover_task), duration)
|
|
188
196
|
|
|
189
197
|
def create_image_contents(
|
|
190
198
|
self,
|
|
191
199
|
image_urls: list[str],
|
|
192
200
|
):
|
|
193
201
|
"""创建图片内容列表"""
|
|
194
|
-
from .data import ImageContent
|
|
195
|
-
|
|
196
202
|
contents: list[ImageContent] = []
|
|
197
203
|
for url in image_urls:
|
|
198
204
|
task = DOWNLOADER.download_img(url, ext_headers=self.headers)
|
|
@@ -201,15 +207,18 @@ class BaseParser:
|
|
|
201
207
|
|
|
202
208
|
def create_image_content(
|
|
203
209
|
self,
|
|
204
|
-
url_or_task: str | Task[Path],
|
|
210
|
+
url_or_task: str | Task[Path] | PathTask,
|
|
211
|
+
alt: str | None = None,
|
|
205
212
|
):
|
|
206
213
|
"""创建图片内容"""
|
|
207
|
-
from .data import ImageContent
|
|
208
|
-
|
|
209
214
|
if isinstance(url_or_task, str):
|
|
210
|
-
|
|
215
|
+
path_task = DOWNLOADER.download_img(url_or_task, ext_headers=self.headers)
|
|
216
|
+
elif isinstance(url_or_task, Task):
|
|
217
|
+
path_task = PathTask(url_or_task)
|
|
218
|
+
elif isinstance(url_or_task, PathTask):
|
|
219
|
+
path_task = url_or_task
|
|
211
220
|
|
|
212
|
-
return ImageContent(
|
|
221
|
+
return ImageContent(path_task, alt=alt)
|
|
213
222
|
|
|
214
223
|
def create_dynamic_contents(
|
|
215
224
|
self,
|
|
@@ -224,30 +233,38 @@ class BaseParser:
|
|
|
224
233
|
contents.append(DynamicContent(task))
|
|
225
234
|
return contents
|
|
226
235
|
|
|
227
|
-
def
|
|
236
|
+
def create_dynamic_content(
|
|
228
237
|
self,
|
|
229
|
-
url_or_task: str | Task[Path],
|
|
230
|
-
duration: float = 0.0,
|
|
238
|
+
url_or_task: str | Task[Path] | PathTask,
|
|
231
239
|
):
|
|
232
|
-
"""
|
|
233
|
-
from .data import
|
|
240
|
+
"""创建动态图片内容"""
|
|
241
|
+
from .data import DynamicContent
|
|
234
242
|
|
|
235
243
|
if isinstance(url_or_task, str):
|
|
236
|
-
|
|
244
|
+
path_task = DOWNLOADER.download_video(url_or_task, ext_headers=self.headers)
|
|
245
|
+
elif isinstance(url_or_task, Task):
|
|
246
|
+
path_task = PathTask(url_or_task)
|
|
247
|
+
elif isinstance(url_or_task, PathTask):
|
|
248
|
+
path_task = url_or_task
|
|
237
249
|
|
|
238
|
-
return
|
|
250
|
+
return DynamicContent(path_task)
|
|
239
251
|
|
|
240
|
-
def
|
|
252
|
+
def create_audio_content(
|
|
241
253
|
self,
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
alt: str | None = None,
|
|
254
|
+
url_or_task: str | Task[Path] | PathTask,
|
|
255
|
+
duration: float = 0.0,
|
|
245
256
|
):
|
|
246
|
-
"""
|
|
247
|
-
from .data import
|
|
257
|
+
"""创建音频内容"""
|
|
258
|
+
from .data import AudioContent
|
|
259
|
+
|
|
260
|
+
if isinstance(url_or_task, str):
|
|
261
|
+
path_task = DOWNLOADER.download_audio(url_or_task, ext_headers=self.headers)
|
|
262
|
+
elif isinstance(url_or_task, Task):
|
|
263
|
+
path_task = PathTask(url_or_task)
|
|
264
|
+
elif isinstance(url_or_task, PathTask):
|
|
265
|
+
path_task = url_or_task
|
|
248
266
|
|
|
249
|
-
|
|
250
|
-
return GraphicsContent(image_task, text, alt)
|
|
267
|
+
return AudioContent(path_task, duration)
|
|
251
268
|
|
|
252
269
|
@property
|
|
253
270
|
def downloader(self):
|