nonebot-plugin-parser 2.6.1__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.1 → nonebot_plugin_parser-2.6.2}/PKG-INFO +3 -3
- {nonebot_plugin_parser-2.6.1 → nonebot_plugin_parser-2.6.2}/README.md +1 -1
- {nonebot_plugin_parser-2.6.1 → nonebot_plugin_parser-2.6.2}/pyproject.toml +6 -8
- {nonebot_plugin_parser-2.6.1 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/parsers/acfun/__init__.py +1 -1
- {nonebot_plugin_parser-2.6.1 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/parsers/base.py +25 -8
- {nonebot_plugin_parser-2.6.1 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/parsers/bilibili/__init__.py +8 -17
- {nonebot_plugin_parser-2.6.1 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/parsers/data.py +24 -12
- {nonebot_plugin_parser-2.6.1 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/parsers/douyin/__init__.py +1 -1
- {nonebot_plugin_parser-2.6.1 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/parsers/tiktok.py +1 -1
- {nonebot_plugin_parser-2.6.1 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/parsers/twitter.py +5 -12
- {nonebot_plugin_parser-2.6.1 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/parsers/weibo/__init__.py +2 -3
- {nonebot_plugin_parser-2.6.1 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/renders/base.py +9 -23
- {nonebot_plugin_parser-2.6.1 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/renders/templates/card.html.jinja2 +43 -45
- {nonebot_plugin_parser-2.6.1 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/utils.py +26 -7
- {nonebot_plugin_parser-2.6.1 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/__init__.py +0 -0
- {nonebot_plugin_parser-2.6.1 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/config.py +0 -0
- {nonebot_plugin_parser-2.6.1 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/constants.py +0 -0
- {nonebot_plugin_parser-2.6.1 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/download/__init__.py +0 -0
- {nonebot_plugin_parser-2.6.1 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/download/task.py +0 -0
- {nonebot_plugin_parser-2.6.1 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/download/ytdlp.py +0 -0
- {nonebot_plugin_parser-2.6.1 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/exception.py +0 -0
- {nonebot_plugin_parser-2.6.1 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/helper.py +0 -0
- {nonebot_plugin_parser-2.6.1 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/matchers/__init__.py +0 -0
- {nonebot_plugin_parser-2.6.1 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/matchers/filter.py +0 -0
- {nonebot_plugin_parser-2.6.1 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/matchers/rule.py +0 -0
- {nonebot_plugin_parser-2.6.1 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/parsers/__init__.py +0 -0
- {nonebot_plugin_parser-2.6.1 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/parsers/acfun/video.py +0 -0
- {nonebot_plugin_parser-2.6.1 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/parsers/bilibili/article.py +0 -0
- {nonebot_plugin_parser-2.6.1 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/parsers/bilibili/common.py +0 -0
- {nonebot_plugin_parser-2.6.1 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/parsers/bilibili/dynamic.py +0 -0
- {nonebot_plugin_parser-2.6.1 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/parsers/bilibili/favlist.py +0 -0
- {nonebot_plugin_parser-2.6.1 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/parsers/bilibili/live.py +0 -0
- {nonebot_plugin_parser-2.6.1 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/parsers/bilibili/opus.py +0 -0
- {nonebot_plugin_parser-2.6.1 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/parsers/bilibili/video.py +0 -0
- {nonebot_plugin_parser-2.6.1 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/parsers/cookie.py +0 -0
- {nonebot_plugin_parser-2.6.1 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/parsers/douyin/slides.py +0 -0
- {nonebot_plugin_parser-2.6.1 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/parsers/douyin/video.py +0 -0
- {nonebot_plugin_parser-2.6.1 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/parsers/kuaishou/__init__.py +0 -0
- {nonebot_plugin_parser-2.6.1 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/parsers/kuaishou/states.py +0 -0
- {nonebot_plugin_parser-2.6.1 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/parsers/nga.py +0 -0
- {nonebot_plugin_parser-2.6.1 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/parsers/task.py +0 -0
- {nonebot_plugin_parser-2.6.1 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/parsers/utils.py +0 -0
- {nonebot_plugin_parser-2.6.1 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/parsers/weibo/article.py +0 -0
- {nonebot_plugin_parser-2.6.1 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/parsers/weibo/common.py +0 -0
- {nonebot_plugin_parser-2.6.1 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/parsers/weibo/show.py +0 -0
- {nonebot_plugin_parser-2.6.1 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/parsers/xiaohongshu/__init__.py +0 -0
- {nonebot_plugin_parser-2.6.1 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/parsers/xiaohongshu/common.py +0 -0
- {nonebot_plugin_parser-2.6.1 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/parsers/xiaohongshu/discovery.py +0 -0
- {nonebot_plugin_parser-2.6.1 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/parsers/xiaohongshu/explore.py +0 -0
- {nonebot_plugin_parser-2.6.1 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/parsers/youtube/__init__.py +0 -0
- {nonebot_plugin_parser-2.6.1 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/parsers/youtube/meta.py +0 -0
- {nonebot_plugin_parser-2.6.1 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/renders/__init__.py +0 -0
- {nonebot_plugin_parser-2.6.1 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/renders/common.py +0 -0
- {nonebot_plugin_parser-2.6.1 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/renders/default.py +0 -0
- {nonebot_plugin_parser-2.6.1 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/renders/htmlrender.py +0 -0
- {nonebot_plugin_parser-2.6.1 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/renders/resources/HYSongYunLangHeiW.ttf +0 -0
- {nonebot_plugin_parser-2.6.1 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/renders/resources/__init__.py +0 -0
- {nonebot_plugin_parser-2.6.1 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/renders/resources/avatar.png +0 -0
- {nonebot_plugin_parser-2.6.1 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/renders/resources/bilibili.png +0 -0
- {nonebot_plugin_parser-2.6.1 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/renders/resources/douyin.png +0 -0
- {nonebot_plugin_parser-2.6.1 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/renders/resources/fallback_pic/1.jpg +0 -0
- {nonebot_plugin_parser-2.6.1 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/renders/resources/fallback_pic/2.jpg +0 -0
- {nonebot_plugin_parser-2.6.1 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/renders/resources/fallback_pic/3.jpg +0 -0
- {nonebot_plugin_parser-2.6.1 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/renders/resources/fallback_pic/4.jpg +0 -0
- {nonebot_plugin_parser-2.6.1 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/renders/resources/fallback_pic/5.jpg +0 -0
- {nonebot_plugin_parser-2.6.1 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/renders/resources/fallback_pic/6.jpg +0 -0
- {nonebot_plugin_parser-2.6.1 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/renders/resources/fallback_pic/7.jpg +0 -0
- {nonebot_plugin_parser-2.6.1 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/renders/resources/fallback_pic/8.jpg +0 -0
- {nonebot_plugin_parser-2.6.1 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/renders/resources/fallback_pic/9.jpg +0 -0
- {nonebot_plugin_parser-2.6.1 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/renders/resources/kuaishou.png +0 -0
- {nonebot_plugin_parser-2.6.1 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/renders/resources/play.png +0 -0
- {nonebot_plugin_parser-2.6.1 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/renders/resources/tiktok.png +0 -0
- {nonebot_plugin_parser-2.6.1 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/renders/resources/twitter.png +0 -0
- {nonebot_plugin_parser-2.6.1 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/renders/resources/weibo.png +0 -0
- {nonebot_plugin_parser-2.6.1 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/renders/resources/xiaohongshu.png +0 -0
- {nonebot_plugin_parser-2.6.1 → 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
|
|
@@ -24,7 +24,7 @@ Requires-Dist: pillow>=11.0.0
|
|
|
24
24
|
Requires-Dist: aiofiles>=25.1.0
|
|
25
25
|
Requires-Dist: httpx>=0.27.2,<1.0.0
|
|
26
26
|
Requires-Dist: msgspec>=0.20.0,<1.0.0
|
|
27
|
-
Requires-Dist: nonebot2>=2.
|
|
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
|
|
@@ -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
|
|
|
@@ -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
|
|
|
@@ -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"
|
|
@@ -44,7 +44,7 @@ dependencies = [
|
|
|
44
44
|
"aiofiles>=25.1.0",
|
|
45
45
|
"httpx>=0.27.2,<1.0.0",
|
|
46
46
|
"msgspec>=0.20.0,<1.0.0",
|
|
47
|
-
"nonebot2>=2.
|
|
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",
|
|
@@ -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]]
|
|
@@ -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
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
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,
|
|
@@ -39,15 +39,15 @@ class BilibiliParser(BaseParser):
|
|
|
39
39
|
self._credential: Credential | None = None
|
|
40
40
|
self._cookies_file = pconfig.config_dir / "bilibili_cookies.json"
|
|
41
41
|
|
|
42
|
-
@handle("b23.tv", r"b23\.tv/[
|
|
43
|
-
@handle("bili2233", r"bili2233\.cn/[
|
|
42
|
+
@handle("b23.tv", r"b23\.tv/[0-9a-zA-Z._?%&+-=/#]+")
|
|
43
|
+
@handle("bili2233", r"bili2233\.cn/[0-9a-zA-Z._?%&+-=/#]+")
|
|
44
44
|
async def _parse_short_link(self, searched: Match[str]):
|
|
45
45
|
"""解析短链"""
|
|
46
46
|
url = f"https://{searched.group(0)}"
|
|
47
47
|
return await self.parse_with_redirect(url)
|
|
48
48
|
|
|
49
49
|
@handle("BV", r"^(?P<bvid>BV[0-9a-zA-Z]{10})(?:\s)?(?P<page_num>\d{1,3})?$")
|
|
50
|
-
@handle("/BV", r"bilibili\.com(?:/video)?/(?P<bvid>BV[0-
|
|
50
|
+
@handle("/BV", r"bilibili\.com(?:/video)?/(?P<bvid>BV[0-9A-Za-z]{10})(?:.*?[?&]p=(?P<page_num>\d{1,3}))?")
|
|
51
51
|
async def _parse_bv(self, searched: Match[str]):
|
|
52
52
|
"""解析视频信息"""
|
|
53
53
|
bvid = str(searched.group("bvid"))
|
|
@@ -56,7 +56,7 @@ class BilibiliParser(BaseParser):
|
|
|
56
56
|
return await self.parse_video(bvid=bvid, page_num=page_num)
|
|
57
57
|
|
|
58
58
|
@handle("av", r"^av(?P<avid>\d{6,})(?:\s)?(?P<page_num>\d{1,3})?$")
|
|
59
|
-
@handle("/av", r"bilibili\.com(?:/video)?/av(?P<avid>\d{6,})(
|
|
59
|
+
@handle("/av", r"bilibili\.com(?:/video)?/av(?P<avid>\d{6,})(?:.*?[?&]p=(?P<page_num>\d{1,3}))?")
|
|
60
60
|
async def _parse_av(self, searched: Match[str]):
|
|
61
61
|
"""解析视频信息"""
|
|
62
62
|
avid = int(searched.group("avid"))
|
|
@@ -65,8 +65,8 @@ class BilibiliParser(BaseParser):
|
|
|
65
65
|
return await self.parse_video(avid=avid, page_num=page_num)
|
|
66
66
|
|
|
67
67
|
@handle("/dynamic/", r"bilibili\.com/dynamic/(?P<dynamic_id>\d+)")
|
|
68
|
-
@handle("t.bili", r"t\.bilibili\.com/(?P<dynamic_id>\d+)")
|
|
69
68
|
@handle("/opus/", r"bilibili\.com/opus/(?P<dynamic_id>\d+)")
|
|
69
|
+
@handle("t.bili", r"t\.bilibili\.com/(?P<dynamic_id>\d+)")
|
|
70
70
|
async def _parse_dynamic(self, searched: Match[str]):
|
|
71
71
|
"""解析动态信息"""
|
|
72
72
|
dynamic_id = int(searched.group("dynamic_id"))
|
|
@@ -105,7 +105,7 @@ class BilibiliParser(BaseParser):
|
|
|
105
105
|
|
|
106
106
|
from .video import VideoInfo, AIConclusion
|
|
107
107
|
|
|
108
|
-
video =
|
|
108
|
+
video = Video(bvid=bvid, aid=avid, credential=await self.credential)
|
|
109
109
|
video_info = convert(await video.get_info(), VideoInfo)
|
|
110
110
|
# UP
|
|
111
111
|
author = self.create_author(video_info.owner.name, video_info.owner.face)
|
|
@@ -160,7 +160,7 @@ class BilibiliParser(BaseParser):
|
|
|
160
160
|
timestamp=page_info.timestamp,
|
|
161
161
|
text=video_info.desc,
|
|
162
162
|
author=author,
|
|
163
|
-
|
|
163
|
+
contents=[video_content],
|
|
164
164
|
extra={"info": ai_summary},
|
|
165
165
|
)
|
|
166
166
|
|
|
@@ -298,15 +298,6 @@ class BilibiliParser(BaseParser):
|
|
|
298
298
|
graphics=graphics,
|
|
299
299
|
)
|
|
300
300
|
|
|
301
|
-
async def _get_video(self, *, bvid: str | None = None, avid: int | None = None) -> Video:
|
|
302
|
-
"""解析视频"""
|
|
303
|
-
if avid:
|
|
304
|
-
return Video(aid=avid, credential=await self.credential)
|
|
305
|
-
elif bvid:
|
|
306
|
-
return Video(bvid=bvid, credential=await self.credential)
|
|
307
|
-
else:
|
|
308
|
-
raise ParseException("avid 和 bvid 至少指定一项")
|
|
309
|
-
|
|
310
301
|
async def extract_download_urls(
|
|
311
302
|
self,
|
|
312
303
|
video: Video | None = None,
|
|
@@ -324,7 +315,7 @@ class BilibiliParser(BaseParser):
|
|
|
324
315
|
)
|
|
325
316
|
|
|
326
317
|
if video is None:
|
|
327
|
-
video =
|
|
318
|
+
video = Video(bvid=bvid, aid=avid, credential=await self.credential)
|
|
328
319
|
|
|
329
320
|
# 获取下载数据
|
|
330
321
|
download_url_data = await video.get_download_url(page_index=page_index)
|
|
@@ -39,8 +39,10 @@ class VideoContent(MediaContent):
|
|
|
39
39
|
"""视频封面"""
|
|
40
40
|
duration: float | None = None
|
|
41
41
|
"""时长 单位: 秒"""
|
|
42
|
-
|
|
43
|
-
"""
|
|
42
|
+
is_gif: bool = False
|
|
43
|
+
"""是否是 GIF"""
|
|
44
|
+
gif_path: PathTask | None = None
|
|
45
|
+
"""视频转为 GIF 的路径"""
|
|
44
46
|
|
|
45
47
|
@property
|
|
46
48
|
def display_duration(self) -> str | None:
|
|
@@ -110,10 +112,8 @@ class ParseResult:
|
|
|
110
112
|
url: str | None = None
|
|
111
113
|
"""来源链接"""
|
|
112
114
|
|
|
113
|
-
video: VideoContent | None = None
|
|
114
|
-
"""视频内容"""
|
|
115
115
|
contents: list[MediaContent] = field(default_factory=list)
|
|
116
|
-
"""
|
|
116
|
+
"""媒体内容"""
|
|
117
117
|
graphics: list[str | ImageContent] = field(default_factory=list)
|
|
118
118
|
"""图文内容"""
|
|
119
119
|
|
|
@@ -146,9 +146,22 @@ class ParseResult:
|
|
|
146
146
|
def extra_info(self) -> str | None:
|
|
147
147
|
return self.extra.get("info")
|
|
148
148
|
|
|
149
|
+
@property
|
|
150
|
+
def video(self) -> VideoContent | None:
|
|
151
|
+
"""主视频 (只有 contents 首项为视频的时候才返回 否则为 None)"""
|
|
152
|
+
if len(self.contents) != 1:
|
|
153
|
+
return None
|
|
154
|
+
cont = self.contents[0]
|
|
155
|
+
return cont if isinstance(cont, VideoContent) and not cont.is_gif else None
|
|
156
|
+
|
|
157
|
+
@video.setter
|
|
158
|
+
def video(self, video: VideoContent | None):
|
|
159
|
+
if video is not None and len(self.contents) == 0:
|
|
160
|
+
self.contents.append(video)
|
|
161
|
+
|
|
149
162
|
@property
|
|
150
163
|
def video_contents(self) -> list[VideoContent]:
|
|
151
|
-
"""
|
|
164
|
+
"""获取所有视频内容(如果有)"""
|
|
152
165
|
return [cont for cont in self.contents if isinstance(cont, VideoContent)]
|
|
153
166
|
|
|
154
167
|
@property
|
|
@@ -171,6 +184,11 @@ class ParseResult:
|
|
|
171
184
|
covers.append(cont.path_task)
|
|
172
185
|
return covers
|
|
173
186
|
|
|
187
|
+
@property
|
|
188
|
+
def grid_medias(self) -> list[VideoContent | ImageContent]:
|
|
189
|
+
"""获取所有用于渲染图片网格的媒体内容(视频封面 + 图片)"""
|
|
190
|
+
return [cont for cont in self.contents if isinstance(cont, (VideoContent, ImageContent))]
|
|
191
|
+
|
|
174
192
|
@property
|
|
175
193
|
def formartted_datetime(self, fmt: str = "%Y-%m-%d %H:%M:%S") -> str | None:
|
|
176
194
|
"""格式化时间戳"""
|
|
@@ -184,11 +202,6 @@ class ParseResult:
|
|
|
184
202
|
if author.avatar:
|
|
185
203
|
yield author.avatar.get()
|
|
186
204
|
|
|
187
|
-
if self.video:
|
|
188
|
-
if self.video.cover:
|
|
189
|
-
yield self.video.cover.get()
|
|
190
|
-
yield self.video.path_task.get()
|
|
191
|
-
|
|
192
205
|
for cont in self.contents:
|
|
193
206
|
if not img_only or isinstance(cont, ImageContent):
|
|
194
207
|
yield cont.path_task.get()
|
|
@@ -248,7 +261,6 @@ class ParseResult:
|
|
|
248
261
|
class ParseResultKwargs(TypedDict, total=False):
|
|
249
262
|
title: str | None
|
|
250
263
|
text: str | None
|
|
251
|
-
video: VideoContent | None
|
|
252
264
|
contents: list[MediaContent]
|
|
253
265
|
graphics: list[str | ImageContent]
|
|
254
266
|
timestamp: int | None
|
|
@@ -130,7 +130,7 @@ class DouyinParser(BaseParser):
|
|
|
130
130
|
# 优先取动图
|
|
131
131
|
if dynamic_urls := slides_data.dynamic_urls:
|
|
132
132
|
for dynamic_url in dynamic_urls:
|
|
133
|
-
result.contents.append(self.
|
|
133
|
+
result.contents.append(self.create_gif(dynamic_url))
|
|
134
134
|
elif image_urls := slides_data.image_urls:
|
|
135
135
|
result.contents.extend(self.create_images(image_urls))
|
|
136
136
|
|
|
@@ -81,23 +81,16 @@ class TwitterParser(BaseParser):
|
|
|
81
81
|
)
|
|
82
82
|
|
|
83
83
|
for media in data.media_extended:
|
|
84
|
-
if media.type
|
|
85
|
-
|
|
84
|
+
if media.type in ["video", "gif"]:
|
|
85
|
+
video = self.create_video(
|
|
86
86
|
media.url,
|
|
87
87
|
media.thumbnail_url,
|
|
88
88
|
duration=media.duration,
|
|
89
|
+
is_gif=media.type == "gif",
|
|
89
90
|
)
|
|
90
|
-
|
|
91
|
+
result.contents.append(video)
|
|
91
92
|
elif media.type == "image":
|
|
92
93
|
result.contents.append(self.create_image(media.url))
|
|
93
|
-
elif media.type == "gif":
|
|
94
|
-
result.contents.append(
|
|
95
|
-
self.create_video(
|
|
96
|
-
media.url,
|
|
97
|
-
media.thumbnail_url,
|
|
98
|
-
duration=media.duration,
|
|
99
|
-
)
|
|
100
|
-
)
|
|
101
94
|
|
|
102
95
|
result.repost = self._collect_result(data.qrt) if data.qrt else None
|
|
103
96
|
|
|
@@ -166,7 +159,7 @@ class TwitterParser(BaseParser):
|
|
|
166
159
|
elif "下载图片" in text:
|
|
167
160
|
result.contents.append(self.create_image(href))
|
|
168
161
|
elif "下载 gif" in text:
|
|
169
|
-
result.contents.append(self.
|
|
162
|
+
result.contents.append(self.create_gif(href))
|
|
170
163
|
|
|
171
164
|
# 3. 提取标题
|
|
172
165
|
title_tag = soup.find("h3")
|
|
@@ -150,7 +150,7 @@ class WeiBoParser(BaseParser):
|
|
|
150
150
|
author=author,
|
|
151
151
|
title=play_info.title,
|
|
152
152
|
text=play_info.text,
|
|
153
|
-
|
|
153
|
+
contents=[video_content],
|
|
154
154
|
timestamp=play_info.real_date,
|
|
155
155
|
)
|
|
156
156
|
|
|
@@ -209,14 +209,13 @@ class WeiBoParser(BaseParser):
|
|
|
209
209
|
url=data.url,
|
|
210
210
|
)
|
|
211
211
|
|
|
212
|
-
#
|
|
212
|
+
# 主视频
|
|
213
213
|
if video_url := data.video_url:
|
|
214
214
|
result.video = self.create_video(
|
|
215
215
|
video_url,
|
|
216
216
|
data.cover_url,
|
|
217
217
|
data.duration,
|
|
218
218
|
)
|
|
219
|
-
|
|
220
219
|
# 添加图片内容
|
|
221
220
|
if image_urls := data.image_urls:
|
|
222
221
|
result.contents.extend(self.create_images(image_urls))
|
|
@@ -11,7 +11,7 @@ import aiofiles
|
|
|
11
11
|
from ..config import pconfig
|
|
12
12
|
from ..helper import UniHelper, UniMessage, ForwardNodeInner
|
|
13
13
|
from ..parsers import ParseResult, AudioContent, ImageContent, VideoContent
|
|
14
|
-
from ..exception import
|
|
14
|
+
from ..exception import DownloadException
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
class BaseRenderer(ABC):
|
|
@@ -39,16 +39,10 @@ class BaseRenderer(ABC):
|
|
|
39
39
|
other_segs: list[ForwardNodeInner] = []
|
|
40
40
|
|
|
41
41
|
def on_error(e: Exception):
|
|
42
|
-
if isinstance(e,
|
|
43
|
-
pass
|
|
44
|
-
elif isinstance(e, DownloadException):
|
|
42
|
+
if isinstance(e, DownloadException):
|
|
45
43
|
nonlocal failed_count
|
|
46
44
|
failed_count += 1
|
|
47
45
|
|
|
48
|
-
if self.result.video:
|
|
49
|
-
if video_path := await self.result.video.path_task.safe_get(on_error):
|
|
50
|
-
yield UniMessage(UniHelper.video_seg(video_path))
|
|
51
|
-
|
|
52
46
|
for cont in chain(
|
|
53
47
|
self.result.contents,
|
|
54
48
|
self.result.repost.contents if self.result.repost else (),
|
|
@@ -59,17 +53,16 @@ class BaseRenderer(ABC):
|
|
|
59
53
|
|
|
60
54
|
match cont:
|
|
61
55
|
case VideoContent() as video:
|
|
62
|
-
|
|
63
|
-
|
|
56
|
+
if video.gif_path and (gif_path := await video.gif_path.safe_get()):
|
|
57
|
+
mergeable_segs.append(UniHelper.img_seg(gif_path))
|
|
58
|
+
else:
|
|
59
|
+
thumbnail = await video.cover.safe_get() if video.cover else None
|
|
60
|
+
yield UniMessage(UniHelper.video_seg(path, thumbnail))
|
|
64
61
|
case AudioContent():
|
|
65
62
|
yield UniMessage(UniHelper.record_seg(path))
|
|
66
63
|
case ImageContent():
|
|
67
64
|
mergeable_segs.append(UniHelper.img_seg(path))
|
|
68
65
|
|
|
69
|
-
if self.result.repost and self.result.repost.video:
|
|
70
|
-
if video_path := await self.result.repost.video.path_task.safe_get(on_error):
|
|
71
|
-
yield UniMessage(UniHelper.video_seg(video_path))
|
|
72
|
-
|
|
73
66
|
for cont in chain(
|
|
74
67
|
self.result.graphics,
|
|
75
68
|
self.result.repost.graphics if self.result.repost else (),
|
|
@@ -85,15 +78,8 @@ class BaseRenderer(ABC):
|
|
|
85
78
|
mergeable_segs.append(img_seg)
|
|
86
79
|
|
|
87
80
|
if mergeable_segs or other_segs:
|
|
88
|
-
if (
|
|
89
|
-
|
|
90
|
-
or len(mergeable_segs) > 4
|
|
91
|
-
or len(other_segs) > 1
|
|
92
|
-
or (len(mergeable_segs) + len(other_segs)) > 4
|
|
93
|
-
):
|
|
94
|
-
forward_msg = UniHelper.construct_forward_message(
|
|
95
|
-
mergeable_segs + other_segs,
|
|
96
|
-
)
|
|
81
|
+
if pconfig.need_forward_contents or len(other_segs) > 1 or (len(mergeable_segs) + len(other_segs)) > 4:
|
|
82
|
+
forward_msg = UniHelper.construct_forward_message(mergeable_segs + other_segs)
|
|
97
83
|
yield UniMessage(forward_msg)
|
|
98
84
|
else:
|
|
99
85
|
if mergeable_segs:
|
|
@@ -577,52 +577,50 @@
|
|
|
577
577
|
{% if result.title and result.video %}<div class="title">{{ result.title }}</div>{% endif %}
|
|
578
578
|
{# ── Text(非动态类型) ── #}
|
|
579
579
|
{% if content_type != '动态' and result.text %}<div class="text">{{ result.text }}</div>{% endif %}
|
|
580
|
-
|
|
581
|
-
{
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
{%
|
|
585
|
-
|
|
586
|
-
{%
|
|
587
|
-
|
|
588
|
-
<
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
{% else %}
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
{%
|
|
597
|
-
|
|
598
|
-
<
|
|
599
|
-
|
|
600
|
-
{
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
</div>
|
|
606
|
-
{% endif %}
|
|
607
|
-
{# ── Graphics ── #}
|
|
608
|
-
{% elif result.graphics %}
|
|
609
|
-
{% for text_or_img in result.graphics %}
|
|
610
|
-
<div class="graphics-item">
|
|
611
|
-
{% if text_or_img is string %}
|
|
612
|
-
<div class="text">{{ text_or_img }}</div>
|
|
613
|
-
{% else %}
|
|
614
|
-
{% set img_uri = text_or_img.path_task.uri %}
|
|
615
|
-
{% set img_uri = (img_uri if img_uri else fallback_pic) %}
|
|
616
|
-
<div class="image-container">
|
|
617
|
-
<img src="{{ img_uri | safe }}"
|
|
618
|
-
class="single-image"
|
|
619
|
-
alt="graphics" />
|
|
620
|
-
</div>
|
|
621
|
-
{% endif %}
|
|
622
|
-
{% if text_or_img.alt %}<div class="graphics-alt">{{ text_or_img.alt }}</div>{% endif %}
|
|
623
|
-
</div>
|
|
624
|
-
{% endfor %}
|
|
580
|
+
|
|
581
|
+
{# ── Image Grid ── #}
|
|
582
|
+
{% set imgs = result.all_grid_images %}
|
|
583
|
+
{% if imgs %}
|
|
584
|
+
{% set count = imgs|length %}
|
|
585
|
+
{% if count == 1 %}
|
|
586
|
+
{% set img = imgs[0] %}
|
|
587
|
+
<div class="image-container">
|
|
588
|
+
<img src="{{ img.uri | safe }}"
|
|
589
|
+
class="single-image"
|
|
590
|
+
alt="image" />
|
|
591
|
+
</div>
|
|
592
|
+
{% else %}
|
|
593
|
+
{% set cols = 2 if count in [2, 4] else 3 %}
|
|
594
|
+
<div class="image-grid cols-{{ cols }}">
|
|
595
|
+
{% for img in imgs[:9] %}
|
|
596
|
+
{% set img_uri = (img.uri if img.uri else fallback_pic) %}
|
|
597
|
+
<div class="grid-item">
|
|
598
|
+
<img src="{{ img_uri | safe }}" alt="image" />
|
|
599
|
+
{% if loop.last and count > 9 %}
|
|
600
|
+
<div class="more-count">+{{ count - 9 }}</div>
|
|
601
|
+
{% endif %}
|
|
602
|
+
</div>
|
|
603
|
+
{% endfor %}
|
|
604
|
+
</div>
|
|
625
605
|
{% endif %}
|
|
606
|
+
{# ── Graphics ── #}
|
|
607
|
+
{% elif result.graphics %}
|
|
608
|
+
{% for text_or_img in result.graphics %}
|
|
609
|
+
<div class="graphics-item">
|
|
610
|
+
{% if text_or_img is string %}
|
|
611
|
+
<div class="text">{{ text_or_img }}</div>
|
|
612
|
+
{% else %}
|
|
613
|
+
{% set img_uri = text_or_img.path_task.uri %}
|
|
614
|
+
{% set img_uri = (img_uri if img_uri else fallback_pic) %}
|
|
615
|
+
<div class="image-container">
|
|
616
|
+
<img src="{{ img_uri | safe }}"
|
|
617
|
+
class="single-image"
|
|
618
|
+
alt="graphics" />
|
|
619
|
+
</div>
|
|
620
|
+
{% endif %}
|
|
621
|
+
{% if text_or_img.alt %}<div class="graphics-alt">{{ text_or_img.alt }}</div>{% endif %}
|
|
622
|
+
</div>
|
|
623
|
+
{% endfor %}
|
|
626
624
|
{% endif %}
|
|
627
625
|
{# ── Repost ── #}
|
|
628
626
|
{% if result.repost %}{{ render_card(result.repost, is_repost=True) }}{% endif %}
|
{nonebot_plugin_parser-2.6.1 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/utils.py
RENAMED
|
@@ -144,11 +144,11 @@ async def encode_video_to_h264(video_path: Path) -> Path:
|
|
|
144
144
|
return output_path
|
|
145
145
|
|
|
146
146
|
|
|
147
|
-
async def
|
|
148
|
-
"""
|
|
149
|
-
|
|
150
|
-
if
|
|
151
|
-
return
|
|
147
|
+
async def extract_video_first_frame(video_path: Path) -> Path:
|
|
148
|
+
"""从视频中提取第一帧"""
|
|
149
|
+
first_frame_path = video_path.with_suffix(".jpg")
|
|
150
|
+
if first_frame_path.exists():
|
|
151
|
+
return first_frame_path
|
|
152
152
|
|
|
153
153
|
cmd = [
|
|
154
154
|
"ffmpeg",
|
|
@@ -159,11 +159,30 @@ async def extract_video_cover(video_path: Path) -> Path:
|
|
|
159
159
|
"00:00:01",
|
|
160
160
|
"-vframes",
|
|
161
161
|
"1",
|
|
162
|
-
str(
|
|
162
|
+
str(first_frame_path),
|
|
163
163
|
]
|
|
164
164
|
|
|
165
165
|
await exec_ffmpeg_cmd(cmd)
|
|
166
|
-
return
|
|
166
|
+
return first_frame_path
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
async def convert_video_to_gif(video_path: Path) -> Path:
|
|
170
|
+
"""将视频转换为 GIF"""
|
|
171
|
+
gif_path = video_path.with_suffix(".gif")
|
|
172
|
+
if gif_path.exists():
|
|
173
|
+
return gif_path
|
|
174
|
+
|
|
175
|
+
cmd = [
|
|
176
|
+
"ffmpeg",
|
|
177
|
+
"-y",
|
|
178
|
+
"-i",
|
|
179
|
+
str(video_path),
|
|
180
|
+
"-c:v",
|
|
181
|
+
"gif",
|
|
182
|
+
str(gif_path),
|
|
183
|
+
]
|
|
184
|
+
await exec_ffmpeg_cmd(cmd)
|
|
185
|
+
return gif_path
|
|
167
186
|
|
|
168
187
|
|
|
169
188
|
def fmt_size(file_path: Path) -> str:
|
{nonebot_plugin_parser-2.6.1 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/__init__.py
RENAMED
|
File without changes
|
{nonebot_plugin_parser-2.6.1 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/config.py
RENAMED
|
File without changes
|
{nonebot_plugin_parser-2.6.1 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/constants.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{nonebot_plugin_parser-2.6.1 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/exception.py
RENAMED
|
File without changes
|
{nonebot_plugin_parser-2.6.1 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/helper.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{nonebot_plugin_parser-2.6.1 → nonebot_plugin_parser-2.6.2}/src/nonebot_plugin_parser/parsers/nga.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|