nonebot-plugin-parser 2.4.3__tar.gz → 2.5.0__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.0}/PKG-INFO +5 -15
- {nonebot_plugin_parser-2.4.3 → nonebot_plugin_parser-2.5.0}/README.md +2 -12
- {nonebot_plugin_parser-2.4.3 → nonebot_plugin_parser-2.5.0}/pyproject.toml +7 -8
- {nonebot_plugin_parser-2.4.3 → nonebot_plugin_parser-2.5.0}/src/nonebot_plugin_parser/config.py +17 -2
- {nonebot_plugin_parser-2.4.3 → nonebot_plugin_parser-2.5.0}/src/nonebot_plugin_parser/helper.py +1 -2
- {nonebot_plugin_parser-2.4.3 → nonebot_plugin_parser-2.5.0}/src/nonebot_plugin_parser/parsers/__init__.py +0 -2
- {nonebot_plugin_parser-2.4.3 → nonebot_plugin_parser-2.5.0}/src/nonebot_plugin_parser/parsers/base.py +9 -18
- {nonebot_plugin_parser-2.4.3 → nonebot_plugin_parser-2.5.0}/src/nonebot_plugin_parser/parsers/bilibili/__init__.py +37 -20
- {nonebot_plugin_parser-2.4.3 → nonebot_plugin_parser-2.5.0}/src/nonebot_plugin_parser/parsers/bilibili/dynamic.py +60 -25
- {nonebot_plugin_parser-2.4.3 → nonebot_plugin_parser-2.5.0}/src/nonebot_plugin_parser/parsers/bilibili/opus.py +42 -33
- {nonebot_plugin_parser-2.4.3 → nonebot_plugin_parser-2.5.0}/src/nonebot_plugin_parser/parsers/data.py +79 -34
- {nonebot_plugin_parser-2.4.3 → nonebot_plugin_parser-2.5.0}/src/nonebot_plugin_parser/parsers/nga.py +6 -16
- {nonebot_plugin_parser-2.4.3 → nonebot_plugin_parser-2.5.0}/src/nonebot_plugin_parser/parsers/weibo/__init__.py +5 -12
- {nonebot_plugin_parser-2.4.3 → nonebot_plugin_parser-2.5.0}/src/nonebot_plugin_parser/renders/base.py +19 -18
- {nonebot_plugin_parser-2.4.3 → nonebot_plugin_parser-2.5.0}/src/nonebot_plugin_parser/renders/common.py +75 -73
- {nonebot_plugin_parser-2.4.3 → nonebot_plugin_parser-2.5.0}/src/nonebot_plugin_parser/renders/default.py +5 -2
- nonebot_plugin_parser-2.5.0/src/nonebot_plugin_parser/renders/htmlrender.py +35 -0
- nonebot_plugin_parser-2.5.0/src/nonebot_plugin_parser/renders/resources/__init__.py +10 -0
- nonebot_plugin_parser-2.5.0/src/nonebot_plugin_parser/renders/templates/card.html.jinja +606 -0
- {nonebot_plugin_parser-2.4.3 → nonebot_plugin_parser-2.5.0}/src/nonebot_plugin_parser/utils.py +11 -0
- 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 → nonebot_plugin_parser-2.5.0}/src/nonebot_plugin_parser/__init__.py +0 -0
- {nonebot_plugin_parser-2.4.3 → nonebot_plugin_parser-2.5.0}/src/nonebot_plugin_parser/constants.py +0 -0
- {nonebot_plugin_parser-2.4.3 → nonebot_plugin_parser-2.5.0}/src/nonebot_plugin_parser/download/__init__.py +0 -0
- {nonebot_plugin_parser-2.4.3 → nonebot_plugin_parser-2.5.0}/src/nonebot_plugin_parser/download/task.py +0 -0
- {nonebot_plugin_parser-2.4.3 → nonebot_plugin_parser-2.5.0}/src/nonebot_plugin_parser/download/ytdlp.py +0 -0
- {nonebot_plugin_parser-2.4.3 → nonebot_plugin_parser-2.5.0}/src/nonebot_plugin_parser/exception.py +0 -0
- {nonebot_plugin_parser-2.4.3 → nonebot_plugin_parser-2.5.0}/src/nonebot_plugin_parser/matchers/__init__.py +0 -0
- {nonebot_plugin_parser-2.4.3 → nonebot_plugin_parser-2.5.0}/src/nonebot_plugin_parser/matchers/filter.py +0 -0
- {nonebot_plugin_parser-2.4.3 → nonebot_plugin_parser-2.5.0}/src/nonebot_plugin_parser/matchers/rule.py +0 -0
- {nonebot_plugin_parser-2.4.3 → nonebot_plugin_parser-2.5.0}/src/nonebot_plugin_parser/parsers/acfun/__init__.py +0 -0
- {nonebot_plugin_parser-2.4.3 → nonebot_plugin_parser-2.5.0}/src/nonebot_plugin_parser/parsers/acfun/video.py +0 -0
- {nonebot_plugin_parser-2.4.3 → nonebot_plugin_parser-2.5.0}/src/nonebot_plugin_parser/parsers/bilibili/article.py +0 -0
- {nonebot_plugin_parser-2.4.3 → nonebot_plugin_parser-2.5.0}/src/nonebot_plugin_parser/parsers/bilibili/common.py +0 -0
- {nonebot_plugin_parser-2.4.3 → nonebot_plugin_parser-2.5.0}/src/nonebot_plugin_parser/parsers/bilibili/favlist.py +0 -0
- {nonebot_plugin_parser-2.4.3 → nonebot_plugin_parser-2.5.0}/src/nonebot_plugin_parser/parsers/bilibili/live.py +0 -0
- {nonebot_plugin_parser-2.4.3 → nonebot_plugin_parser-2.5.0}/src/nonebot_plugin_parser/parsers/bilibili/video.py +0 -0
- {nonebot_plugin_parser-2.4.3 → nonebot_plugin_parser-2.5.0}/src/nonebot_plugin_parser/parsers/cookie.py +0 -0
- {nonebot_plugin_parser-2.4.3 → nonebot_plugin_parser-2.5.0}/src/nonebot_plugin_parser/parsers/douyin/__init__.py +0 -0
- {nonebot_plugin_parser-2.4.3 → nonebot_plugin_parser-2.5.0}/src/nonebot_plugin_parser/parsers/douyin/slides.py +0 -0
- {nonebot_plugin_parser-2.4.3 → nonebot_plugin_parser-2.5.0}/src/nonebot_plugin_parser/parsers/douyin/video.py +0 -0
- {nonebot_plugin_parser-2.4.3 → nonebot_plugin_parser-2.5.0}/src/nonebot_plugin_parser/parsers/kuaishou/__init__.py +0 -0
- {nonebot_plugin_parser-2.4.3 → nonebot_plugin_parser-2.5.0}/src/nonebot_plugin_parser/parsers/kuaishou/states.py +0 -0
- {nonebot_plugin_parser-2.4.3 → nonebot_plugin_parser-2.5.0}/src/nonebot_plugin_parser/parsers/tiktok.py +0 -0
- {nonebot_plugin_parser-2.4.3 → nonebot_plugin_parser-2.5.0}/src/nonebot_plugin_parser/parsers/twitter.py +0 -0
- {nonebot_plugin_parser-2.4.3 → nonebot_plugin_parser-2.5.0}/src/nonebot_plugin_parser/parsers/weibo/article.py +0 -0
- {nonebot_plugin_parser-2.4.3 → nonebot_plugin_parser-2.5.0}/src/nonebot_plugin_parser/parsers/weibo/common.py +0 -0
- {nonebot_plugin_parser-2.4.3 → nonebot_plugin_parser-2.5.0}/src/nonebot_plugin_parser/parsers/weibo/show.py +0 -0
- {nonebot_plugin_parser-2.4.3 → nonebot_plugin_parser-2.5.0}/src/nonebot_plugin_parser/parsers/xiaohongshu/__init__.py +0 -0
- {nonebot_plugin_parser-2.4.3 → nonebot_plugin_parser-2.5.0}/src/nonebot_plugin_parser/parsers/xiaohongshu/common.py +0 -0
- {nonebot_plugin_parser-2.4.3 → nonebot_plugin_parser-2.5.0}/src/nonebot_plugin_parser/parsers/xiaohongshu/discovery.py +0 -0
- {nonebot_plugin_parser-2.4.3 → nonebot_plugin_parser-2.5.0}/src/nonebot_plugin_parser/parsers/xiaohongshu/explore.py +0 -0
- {nonebot_plugin_parser-2.4.3 → nonebot_plugin_parser-2.5.0}/src/nonebot_plugin_parser/parsers/youtube/__init__.py +0 -0
- {nonebot_plugin_parser-2.4.3 → nonebot_plugin_parser-2.5.0}/src/nonebot_plugin_parser/parsers/youtube/meta.py +0 -0
- {nonebot_plugin_parser-2.4.3 → nonebot_plugin_parser-2.5.0}/src/nonebot_plugin_parser/renders/__init__.py +0 -0
- {nonebot_plugin_parser-2.4.3 → nonebot_plugin_parser-2.5.0}/src/nonebot_plugin_parser/renders/resources/HYSongYunLangHeiW.ttf +0 -0
- {nonebot_plugin_parser-2.4.3 → nonebot_plugin_parser-2.5.0}/src/nonebot_plugin_parser/renders/resources/avatar.png +0 -0
- {nonebot_plugin_parser-2.4.3 → nonebot_plugin_parser-2.5.0}/src/nonebot_plugin_parser/renders/resources/bilibili.png +0 -0
- {nonebot_plugin_parser-2.4.3 → nonebot_plugin_parser-2.5.0}/src/nonebot_plugin_parser/renders/resources/douyin.png +0 -0
- {nonebot_plugin_parser-2.4.3 → nonebot_plugin_parser-2.5.0}/src/nonebot_plugin_parser/renders/resources/kuaishou.png +0 -0
- {nonebot_plugin_parser-2.4.3 → nonebot_plugin_parser-2.5.0}/src/nonebot_plugin_parser/renders/resources/play.png +0 -0
- {nonebot_plugin_parser-2.4.3 → nonebot_plugin_parser-2.5.0}/src/nonebot_plugin_parser/renders/resources/tiktok.png +0 -0
- {nonebot_plugin_parser-2.4.3 → nonebot_plugin_parser-2.5.0}/src/nonebot_plugin_parser/renders/resources/twitter.png +0 -0
- {nonebot_plugin_parser-2.4.3 → nonebot_plugin_parser-2.5.0}/src/nonebot_plugin_parser/renders/resources/weibo.png +0 -0
- {nonebot_plugin_parser-2.4.3 → nonebot_plugin_parser-2.5.0}/src/nonebot_plugin_parser/renders/resources/xiaohongshu.png +0 -0
- {nonebot_plugin_parser-2.4.3 → nonebot_plugin_parser-2.5.0}/src/nonebot_plugin_parser/renders/resources/youtube.png +0 -0
- {nonebot_plugin_parser-2.4.3 → nonebot_plugin_parser-2.5.0}/src/nonebot_plugin_parser/renders/weibo.py +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.0
|
|
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
|
|
@@ -36,8 +36,8 @@ 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
38
|
Requires-Dist: yt-dlp[default]>=2026.2.21 ; extra == 'all'
|
|
39
|
-
Requires-Dist: emosvg>=0.1.
|
|
40
|
-
Requires-Dist: emosvg>=0.1.
|
|
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'
|
|
@@ -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
|
|
@@ -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.0"
|
|
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,12 +64,12 @@ 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
71
|
"yt-dlp[default]>=2026.2.21",
|
|
73
|
-
"emosvg>=0.1.
|
|
72
|
+
"emosvg>=0.1.7",
|
|
74
73
|
]
|
|
75
74
|
|
|
76
75
|
[dependency-groups]
|
|
@@ -82,7 +81,7 @@ dev = [
|
|
|
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
87
|
"yt-dlp[default]>=2026.2.21",
|
|
@@ -90,7 +89,7 @@ extras = [
|
|
|
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.0"
|
|
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.0}/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:
|
|
@@ -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",
|
|
@@ -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
|
|
@@ -83,9 +83,11 @@ class BaseParser:
|
|
|
83
83
|
"""获取所有已注册的 Parser 类"""
|
|
84
84
|
return cls._registry
|
|
85
85
|
|
|
86
|
+
@final
|
|
86
87
|
async def parse(self, keyword: str, searched: Match[str]) -> ParseResult:
|
|
87
88
|
return await self._handlers[keyword](self, searched)
|
|
88
89
|
|
|
90
|
+
@final
|
|
89
91
|
async def parse_with_redirect(
|
|
90
92
|
self,
|
|
91
93
|
url: str,
|
|
@@ -191,8 +193,6 @@ class BaseParser:
|
|
|
191
193
|
image_urls: list[str],
|
|
192
194
|
):
|
|
193
195
|
"""创建图片内容列表"""
|
|
194
|
-
from .data import ImageContent
|
|
195
|
-
|
|
196
196
|
contents: list[ImageContent] = []
|
|
197
197
|
for url in image_urls:
|
|
198
198
|
task = DOWNLOADER.download_img(url, ext_headers=self.headers)
|
|
@@ -202,14 +202,13 @@ class BaseParser:
|
|
|
202
202
|
def create_image_content(
|
|
203
203
|
self,
|
|
204
204
|
url_or_task: str | Task[Path],
|
|
205
|
+
alt: str | None = None,
|
|
205
206
|
):
|
|
206
207
|
"""创建图片内容"""
|
|
207
|
-
from .data import ImageContent
|
|
208
|
-
|
|
209
208
|
if isinstance(url_or_task, str):
|
|
210
209
|
url_or_task = DOWNLOADER.download_img(url_or_task, ext_headers=self.headers)
|
|
211
210
|
|
|
212
|
-
return ImageContent(url_or_task)
|
|
211
|
+
return ImageContent(url_or_task, alt=alt)
|
|
213
212
|
|
|
214
213
|
def create_dynamic_contents(
|
|
215
214
|
self,
|
|
@@ -237,17 +236,9 @@ class BaseParser:
|
|
|
237
236
|
|
|
238
237
|
return AudioContent(url_or_task, duration)
|
|
239
238
|
|
|
240
|
-
def
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
text: str | None = None,
|
|
244
|
-
alt: str | None = None,
|
|
245
|
-
):
|
|
246
|
-
"""创建图文内容 图片不能为空 文字可空 渲染时文字在前 图片在后"""
|
|
247
|
-
from .data import GraphicsContent
|
|
248
|
-
|
|
249
|
-
image_task = DOWNLOADER.download_img(image_url, ext_headers=self.headers)
|
|
250
|
-
return GraphicsContent(image_task, text, alt)
|
|
239
|
+
def create_empty_graphics(self) -> list[str | ImageContent]:
|
|
240
|
+
"""创建空的图片内容列表"""
|
|
241
|
+
return []
|
|
251
242
|
|
|
252
243
|
@property
|
|
253
244
|
def downloader(self):
|
|
@@ -22,6 +22,7 @@ from ..base import (
|
|
|
22
22
|
)
|
|
23
23
|
from ..data import Platform, ImageContent, MediaContent
|
|
24
24
|
from ..cookie import ck2dict
|
|
25
|
+
from .dynamic import DynamicInfo
|
|
25
26
|
|
|
26
27
|
# 选择客户端
|
|
27
28
|
select_client("curl_cffi")
|
|
@@ -160,20 +161,31 @@ class BilibiliParser(BaseParser):
|
|
|
160
161
|
"""解析动态或图文"""
|
|
161
162
|
from bilibili_api.dynamic import Dynamic
|
|
162
163
|
|
|
163
|
-
from .dynamic import
|
|
164
|
+
from .dynamic import DynamicWrapper
|
|
164
165
|
|
|
165
166
|
dynamic = Dynamic(dynamic_id, await self.credential)
|
|
166
167
|
if await dynamic.is_article():
|
|
167
168
|
return await self._parse_bilibli_api_opus(dynamic.turn_to_opus())
|
|
168
169
|
|
|
169
|
-
dynamic_info = convert(await dynamic.get_info(),
|
|
170
|
-
|
|
170
|
+
dynamic_info = convert(await dynamic.get_info(), DynamicWrapper).item
|
|
171
|
+
return await self._parse_dynamic_info(dynamic_info)
|
|
172
|
+
|
|
173
|
+
async def _parse_dynamic_info(self, dynamic_info: DynamicInfo):
|
|
174
|
+
if dynamic_info.is_video():
|
|
175
|
+
if (major := dynamic_info.modules.major) and (archive := major.archive):
|
|
176
|
+
result = await self.parse_video(bvid=archive.bvid)
|
|
177
|
+
result.text = dynamic_info.text
|
|
178
|
+
result.extra["content_type"] = "动态"
|
|
179
|
+
return result
|
|
171
180
|
|
|
172
181
|
# 下载图片
|
|
182
|
+
author = self.create_author(dynamic_info.name, dynamic_info.avatar)
|
|
173
183
|
contents: list[MediaContent] = []
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
184
|
+
contents.extend(self.create_image_contents(dynamic_info.image_urls))
|
|
185
|
+
|
|
186
|
+
repost = None
|
|
187
|
+
if dynamic_info.type == "DYNAMIC_TYPE_FORWARD" and dynamic_info.orig is not None:
|
|
188
|
+
repost = await self._parse_dynamic_info(dynamic_info.orig)
|
|
177
189
|
|
|
178
190
|
return self.result(
|
|
179
191
|
title=dynamic_info.title,
|
|
@@ -181,6 +193,8 @@ class BilibiliParser(BaseParser):
|
|
|
181
193
|
timestamp=dynamic_info.timestamp,
|
|
182
194
|
author=author,
|
|
183
195
|
contents=contents,
|
|
196
|
+
repost=repost,
|
|
197
|
+
extra={"content_type": "动态"},
|
|
184
198
|
)
|
|
185
199
|
|
|
186
200
|
async def parse_opus_by_id(self, opus_id: int):
|
|
@@ -191,7 +205,7 @@ class BilibiliParser(BaseParser):
|
|
|
191
205
|
async def _parse_bilibli_api_opus(self, bili_opus: Opus):
|
|
192
206
|
"""解析图文动态(Opus)"""
|
|
193
207
|
|
|
194
|
-
from .opus import OpusItem
|
|
208
|
+
from .opus import OpusItem
|
|
195
209
|
|
|
196
210
|
opus_info = await bili_opus.get_info()
|
|
197
211
|
if not isinstance(opus_info, dict):
|
|
@@ -201,23 +215,19 @@ class BilibiliParser(BaseParser):
|
|
|
201
215
|
logger.debug(f"opus_data: {opus_data}")
|
|
202
216
|
author = self.create_author(*opus_data.name_avatar)
|
|
203
217
|
|
|
204
|
-
#
|
|
205
|
-
|
|
206
|
-
current_text = ""
|
|
207
|
-
|
|
218
|
+
# 按顺序处理图文内容
|
|
219
|
+
graphics = self.create_empty_graphics()
|
|
208
220
|
for node in opus_data.extract_nodes():
|
|
209
|
-
if isinstance(node,
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
current_text += node.text
|
|
221
|
+
if isinstance(node, str):
|
|
222
|
+
graphics.append(node)
|
|
223
|
+
else:
|
|
224
|
+
graphics.append(self.create_image_content(node.url, alt=node.alt))
|
|
214
225
|
|
|
215
226
|
return self.result(
|
|
216
227
|
title=opus_data.title,
|
|
217
228
|
author=author,
|
|
218
229
|
timestamp=opus_data.timestamp,
|
|
219
|
-
|
|
220
|
-
text=current_text.strip(),
|
|
230
|
+
graphics=graphics,
|
|
221
231
|
)
|
|
222
232
|
|
|
223
233
|
async def parse_live(self, room_id: int):
|
|
@@ -266,11 +276,18 @@ class BilibiliParser(BaseParser):
|
|
|
266
276
|
|
|
267
277
|
favdata = convert(fav_dict, FavData)
|
|
268
278
|
|
|
279
|
+
author = self.create_author(favdata.info.upper.name, favdata.info.upper.face)
|
|
280
|
+
|
|
281
|
+
graphics: list[str | ImageContent] = []
|
|
282
|
+
for fav in favdata.medias:
|
|
283
|
+
graphics.append(self.create_image_content(fav.cover, alt=fav.desc))
|
|
284
|
+
graphics.append(fav.desc)
|
|
285
|
+
|
|
269
286
|
return self.result(
|
|
270
287
|
title=favdata.title,
|
|
271
288
|
timestamp=favdata.timestamp,
|
|
272
|
-
author=
|
|
273
|
-
|
|
289
|
+
author=author,
|
|
290
|
+
graphics=graphics,
|
|
274
291
|
)
|
|
275
292
|
|
|
276
293
|
async def _get_video(self, *, bvid: str | None = None, avid: int | None = None) -> Video:
|
|
@@ -26,11 +26,26 @@ class VideoArchive(Struct):
|
|
|
26
26
|
title: str
|
|
27
27
|
desc: str
|
|
28
28
|
cover: str
|
|
29
|
-
|
|
29
|
+
duration_text: str = ""
|
|
30
30
|
# jump_url: str
|
|
31
31
|
# stat: dict[str, str]
|
|
32
32
|
# badge: dict[str, Any] | None = None
|
|
33
33
|
|
|
34
|
+
@property
|
|
35
|
+
def duration_seconds(self) -> float:
|
|
36
|
+
"""将 duration_text(如 '3:42')解析为秒数"""
|
|
37
|
+
if not self.duration_text:
|
|
38
|
+
return 0.0
|
|
39
|
+
parts = self.duration_text.split(":")
|
|
40
|
+
try:
|
|
41
|
+
if len(parts) == 2:
|
|
42
|
+
return int(parts[0]) * 60 + int(parts[1])
|
|
43
|
+
elif len(parts) == 3:
|
|
44
|
+
return int(parts[0]) * 3600 + int(parts[1]) * 60 + int(parts[2])
|
|
45
|
+
except ValueError:
|
|
46
|
+
pass
|
|
47
|
+
return 0.0
|
|
48
|
+
|
|
34
49
|
|
|
35
50
|
class OpusImage(Struct):
|
|
36
51
|
"""图文动态图片信息"""
|
|
@@ -73,6 +88,8 @@ class DynamicMajor(Struct):
|
|
|
73
88
|
"""获取标题"""
|
|
74
89
|
if self.type == "MAJOR_TYPE_ARCHIVE" and self.archive:
|
|
75
90
|
return self.archive.title
|
|
91
|
+
if self.type == "MAJOR_TYPE_OPUS" and self.opus:
|
|
92
|
+
return self.opus.title
|
|
76
93
|
return None
|
|
77
94
|
|
|
78
95
|
@property
|
|
@@ -102,6 +119,13 @@ class DynamicMajor(Struct):
|
|
|
102
119
|
return self.archive.cover
|
|
103
120
|
return None
|
|
104
121
|
|
|
122
|
+
@property
|
|
123
|
+
def duration(self) -> float:
|
|
124
|
+
"""获取视频时长(秒)"""
|
|
125
|
+
if self.type == "MAJOR_TYPE_ARCHIVE" and self.archive:
|
|
126
|
+
return self.archive.duration_seconds
|
|
127
|
+
return 0.0
|
|
128
|
+
|
|
105
129
|
|
|
106
130
|
class DynamicModule(Struct):
|
|
107
131
|
"""动态模块"""
|
|
@@ -110,6 +134,8 @@ class DynamicModule(Struct):
|
|
|
110
134
|
module_dynamic: dict[str, Any] | None = None
|
|
111
135
|
module_stat: dict[str, Any] | None = None
|
|
112
136
|
|
|
137
|
+
_cached_major: DynamicMajor | None = None
|
|
138
|
+
|
|
113
139
|
@property
|
|
114
140
|
def author_name(self) -> str:
|
|
115
141
|
"""获取作者名称"""
|
|
@@ -126,7 +152,7 @@ class DynamicModule(Struct):
|
|
|
126
152
|
return self.module_author.pub_ts
|
|
127
153
|
|
|
128
154
|
@property
|
|
129
|
-
def
|
|
155
|
+
def _major_info(self) -> dict[str, Any] | None:
|
|
130
156
|
"""获取主要内容信息"""
|
|
131
157
|
if self.module_dynamic:
|
|
132
158
|
if major := self.module_dynamic.get("major"):
|
|
@@ -135,6 +161,24 @@ class DynamicModule(Struct):
|
|
|
135
161
|
return self.module_dynamic
|
|
136
162
|
return None
|
|
137
163
|
|
|
164
|
+
@property
|
|
165
|
+
def major(self) -> DynamicMajor | None:
|
|
166
|
+
"""获取缓存的 DynamicMajor 实例"""
|
|
167
|
+
if self._cached_major is None:
|
|
168
|
+
major_info = self._major_info
|
|
169
|
+
if major_info:
|
|
170
|
+
self._cached_major = convert(major_info, DynamicMajor)
|
|
171
|
+
return self._cached_major
|
|
172
|
+
|
|
173
|
+
@property
|
|
174
|
+
def desc_text(self) -> str | None:
|
|
175
|
+
"""获取动态自身的文字描述(非 major 内容的文字)"""
|
|
176
|
+
if self.module_dynamic:
|
|
177
|
+
desc = self.module_dynamic.get("desc")
|
|
178
|
+
if desc and isinstance(desc, dict):
|
|
179
|
+
return desc.get("text")
|
|
180
|
+
return None
|
|
181
|
+
|
|
138
182
|
|
|
139
183
|
class DynamicInfo(Struct):
|
|
140
184
|
"""动态信息"""
|
|
@@ -144,6 +188,7 @@ class DynamicInfo(Struct):
|
|
|
144
188
|
visible: bool
|
|
145
189
|
modules: DynamicModule
|
|
146
190
|
basic: dict[str, Any] | None = None
|
|
191
|
+
orig: "DynamicInfo | None" = None
|
|
147
192
|
|
|
148
193
|
@property
|
|
149
194
|
def name(self) -> str:
|
|
@@ -163,41 +208,31 @@ class DynamicInfo(Struct):
|
|
|
163
208
|
@property
|
|
164
209
|
def title(self) -> str | None:
|
|
165
210
|
"""获取标题"""
|
|
166
|
-
|
|
167
|
-
if major_info:
|
|
168
|
-
major = convert(major_info, DynamicMajor)
|
|
211
|
+
if major := self.modules.major:
|
|
169
212
|
return major.title
|
|
170
|
-
return None
|
|
171
213
|
|
|
172
214
|
@property
|
|
173
215
|
def text(self) -> str | None:
|
|
174
|
-
"""
|
|
175
|
-
|
|
176
|
-
if
|
|
177
|
-
|
|
216
|
+
"""获取文本内容(优先取动态自身文字,回退到 major 的文字)"""
|
|
217
|
+
# 优先取动态自身描述(如发视频时附带的文字)
|
|
218
|
+
if desc_text := self.modules.desc_text:
|
|
219
|
+
return desc_text
|
|
220
|
+
# 回退到 major 的文字(图文摘要、视频简介等)
|
|
221
|
+
if major := self.modules.major:
|
|
178
222
|
return major.text
|
|
179
|
-
return None
|
|
180
223
|
|
|
181
224
|
@property
|
|
182
225
|
def image_urls(self) -> list[str]:
|
|
183
226
|
"""获取图片URL列表"""
|
|
184
|
-
|
|
185
|
-
if major_info:
|
|
186
|
-
major = convert(major_info, DynamicMajor)
|
|
227
|
+
if major := self.modules.major:
|
|
187
228
|
return major.image_urls
|
|
188
229
|
return []
|
|
189
230
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
if major_info:
|
|
195
|
-
major = convert(major_info, DynamicMajor)
|
|
196
|
-
return major.cover_url
|
|
197
|
-
return None
|
|
198
|
-
|
|
231
|
+
def is_video(self) -> bool:
|
|
232
|
+
"""判断是否为视频动态"""
|
|
233
|
+
major = self.modules.major
|
|
234
|
+
return major is not None and major.archive is not None
|
|
199
235
|
|
|
200
|
-
class DynamicData(Struct):
|
|
201
|
-
"""动态项目"""
|
|
202
236
|
|
|
237
|
+
class DynamicWrapper(Struct):
|
|
203
238
|
item: DynamicInfo
|
|
@@ -1,25 +1,9 @@
|
|
|
1
1
|
from typing import Any
|
|
2
|
-
from
|
|
2
|
+
from dataclasses import dataclass
|
|
3
3
|
|
|
4
4
|
from msgspec import Struct
|
|
5
5
|
|
|
6
6
|
|
|
7
|
-
class TextNode(Struct, tag="TextNode"):
|
|
8
|
-
"""图文动态文本节点"""
|
|
9
|
-
|
|
10
|
-
text: str
|
|
11
|
-
"""文本内容"""
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
class ImageNode(Struct, tag="ImageNode"):
|
|
15
|
-
"""图文动态图片节点"""
|
|
16
|
-
|
|
17
|
-
url: str
|
|
18
|
-
"""图片链接"""
|
|
19
|
-
alt: str | None = None
|
|
20
|
-
"""图片描述"""
|
|
21
|
-
|
|
22
|
-
|
|
23
7
|
class Author(Struct):
|
|
24
8
|
"""图文动态作者信息"""
|
|
25
9
|
|
|
@@ -102,6 +86,16 @@ class Info(Struct):
|
|
|
102
86
|
basic: Basic | None = None
|
|
103
87
|
|
|
104
88
|
|
|
89
|
+
@dataclass(slots=True)
|
|
90
|
+
class ImageNode:
|
|
91
|
+
"""图文动态图片节点"""
|
|
92
|
+
|
|
93
|
+
url: str
|
|
94
|
+
"""图片链接"""
|
|
95
|
+
alt: str | None = None
|
|
96
|
+
"""图片描述"""
|
|
97
|
+
|
|
98
|
+
|
|
105
99
|
class OpusItem(Struct):
|
|
106
100
|
"""图文动态项目"""
|
|
107
101
|
|
|
@@ -124,30 +118,45 @@ class OpusItem(Struct):
|
|
|
124
118
|
return module.module_author.pub_ts
|
|
125
119
|
return None
|
|
126
120
|
|
|
127
|
-
def extract_nodes(self)
|
|
128
|
-
"""
|
|
121
|
+
def extract_nodes(self):
|
|
122
|
+
"""提取图文节点(保持顺序)"""
|
|
129
123
|
for module in self.item.modules:
|
|
130
124
|
if module.module_type == "MODULE_TYPE_CONTENT" and module.module_content:
|
|
131
|
-
|
|
125
|
+
iterator = iter(module.module_content.paragraphs)
|
|
126
|
+
for paragraph in iterator:
|
|
132
127
|
# 处理文本段落
|
|
133
128
|
if paragraph.text and paragraph.text.nodes:
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
129
|
+
cur_text = "".join(
|
|
130
|
+
text for text, _ in self._extract_texts_from_nodes(paragraph.text.nodes)
|
|
131
|
+
).strip()
|
|
132
|
+
if cur_text:
|
|
133
|
+
yield cur_text
|
|
139
134
|
# 处理图片段落
|
|
140
135
|
if paragraph.pic and paragraph.pic.pics:
|
|
141
136
|
for pic in paragraph.pic.pics:
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
137
|
+
image_node = ImageNode(url=pic.url)
|
|
138
|
+
next_text = ""
|
|
139
|
+
if (next_par := next(iterator, None)) and next_par.text and next_par.text.nodes:
|
|
140
|
+
for text, color in self._extract_texts_from_nodes(next_par.text.nodes):
|
|
141
|
+
if color == "#999999":
|
|
142
|
+
image_node.alt = text
|
|
143
|
+
else:
|
|
144
|
+
next_text += text
|
|
145
|
+
yield image_node
|
|
146
|
+
next_text = next_text.strip()
|
|
147
|
+
if next_text:
|
|
148
|
+
yield next_text
|
|
149
|
+
|
|
150
|
+
def _extract_texts_from_nodes(self, nodes: list[dict[str, Any]]) -> list[tuple[str, str | None]]:
|
|
145
151
|
"""从节点列表中提取文本内容"""
|
|
146
|
-
|
|
152
|
+
texts: list[tuple[str, str | None]] = []
|
|
147
153
|
for node in nodes:
|
|
148
|
-
if node.get("type") in
|
|
154
|
+
if node.get("type") in (
|
|
149
155
|
"TEXT_NODE_TYPE_WORD",
|
|
150
156
|
"TEXT_NODE_TYPE_RICH",
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
157
|
+
) and node.get("word"):
|
|
158
|
+
text = node["word"]["words"]
|
|
159
|
+
color = node["word"]["color"]
|
|
160
|
+
texts.append((text, color))
|
|
161
|
+
|
|
162
|
+
return texts
|