nonebot-plugin-parser 2.5.3__tar.gz → 2.6.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.5.3 → nonebot_plugin_parser-2.6.1}/PKG-INFO +27 -32
- {nonebot_plugin_parser-2.5.3 → nonebot_plugin_parser-2.6.1}/README.md +23 -28
- {nonebot_plugin_parser-2.5.3 → nonebot_plugin_parser-2.6.1}/pyproject.toml +6 -6
- {nonebot_plugin_parser-2.5.3 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/config.py +7 -0
- {nonebot_plugin_parser-2.5.3 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/download/__init__.py +114 -58
- nonebot_plugin_parser-2.6.1/src/nonebot_plugin_parser/download/task.py +20 -0
- {nonebot_plugin_parser-2.5.3 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/helper.py +23 -8
- {nonebot_plugin_parser-2.5.3 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/matchers/__init__.py +3 -3
- {nonebot_plugin_parser-2.5.3 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/parsers/__init__.py +2 -4
- {nonebot_plugin_parser-2.5.3 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/parsers/acfun/__init__.py +1 -1
- {nonebot_plugin_parser-2.5.3 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/parsers/base.py +42 -64
- {nonebot_plugin_parser-2.5.3 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/parsers/bilibili/__init__.py +7 -7
- {nonebot_plugin_parser-2.5.3 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/parsers/data.py +29 -24
- {nonebot_plugin_parser-2.5.3 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/parsers/douyin/__init__.py +15 -11
- {nonebot_plugin_parser-2.5.3 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/parsers/douyin/video.py +4 -0
- {nonebot_plugin_parser-2.5.3 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/parsers/kuaishou/__init__.py +6 -3
- {nonebot_plugin_parser-2.5.3 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/parsers/kuaishou/states.py +4 -0
- {nonebot_plugin_parser-2.5.3 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/parsers/nga.py +1 -1
- nonebot_plugin_parser-2.6.1/src/nonebot_plugin_parser/parsers/task.py +47 -0
- {nonebot_plugin_parser-2.5.3 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/parsers/tiktok.py +4 -4
- {nonebot_plugin_parser-2.5.3 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/parsers/twitter.py +12 -6
- nonebot_plugin_parser-2.6.1/src/nonebot_plugin_parser/parsers/utils.py +9 -0
- {nonebot_plugin_parser-2.5.3 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/parsers/weibo/__init__.py +10 -5
- {nonebot_plugin_parser-2.5.3 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/parsers/weibo/common.py +13 -0
- {nonebot_plugin_parser-2.5.3 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/parsers/weibo/show.py +5 -0
- {nonebot_plugin_parser-2.5.3 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/parsers/xiaohongshu/__init__.py +15 -11
- nonebot_plugin_parser-2.6.1/src/nonebot_plugin_parser/parsers/xiaohongshu/common.py +38 -0
- {nonebot_plugin_parser-2.5.3 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/parsers/xiaohongshu/discovery.py +9 -4
- {nonebot_plugin_parser-2.5.3 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/parsers/xiaohongshu/explore.py +14 -4
- {nonebot_plugin_parser-2.5.3 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/parsers/youtube/__init__.py +5 -5
- nonebot_plugin_parser-2.6.1/src/nonebot_plugin_parser/renders/__init__.py +44 -0
- {nonebot_plugin_parser-2.5.3 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/renders/base.py +26 -18
- {nonebot_plugin_parser-2.5.3 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/renders/common.py +19 -13
- {nonebot_plugin_parser-2.5.3 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/renders/default.py +1 -1
- {nonebot_plugin_parser-2.5.3 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/renders/htmlrender.py +5 -5
- {nonebot_plugin_parser-2.5.3 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/renders/resources/__init__.py +7 -0
- nonebot_plugin_parser-2.6.1/src/nonebot_plugin_parser/renders/resources/fallback_pic/1.jpg +0 -0
- nonebot_plugin_parser-2.6.1/src/nonebot_plugin_parser/renders/resources/fallback_pic/2.jpg +0 -0
- nonebot_plugin_parser-2.6.1/src/nonebot_plugin_parser/renders/resources/fallback_pic/3.jpg +0 -0
- nonebot_plugin_parser-2.6.1/src/nonebot_plugin_parser/renders/resources/fallback_pic/4.jpg +0 -0
- nonebot_plugin_parser-2.6.1/src/nonebot_plugin_parser/renders/resources/fallback_pic/5.jpg +0 -0
- nonebot_plugin_parser-2.6.1/src/nonebot_plugin_parser/renders/resources/fallback_pic/6.jpg +0 -0
- nonebot_plugin_parser-2.6.1/src/nonebot_plugin_parser/renders/resources/fallback_pic/7.jpg +0 -0
- nonebot_plugin_parser-2.6.1/src/nonebot_plugin_parser/renders/resources/fallback_pic/8.jpg +0 -0
- nonebot_plugin_parser-2.6.1/src/nonebot_plugin_parser/renders/resources/fallback_pic/9.jpg +0 -0
- {nonebot_plugin_parser-2.5.3 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/renders/templates/card.html.jinja2 +36 -9
- {nonebot_plugin_parser-2.5.3 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/utils.py +25 -12
- nonebot_plugin_parser-2.5.3/src/nonebot_plugin_parser/download/task.py +0 -92
- nonebot_plugin_parser-2.5.3/src/nonebot_plugin_parser/parsers/xiaohongshu/common.py +0 -33
- nonebot_plugin_parser-2.5.3/src/nonebot_plugin_parser/renders/__init__.py +0 -43
- {nonebot_plugin_parser-2.5.3 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/__init__.py +0 -0
- {nonebot_plugin_parser-2.5.3 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/constants.py +0 -0
- {nonebot_plugin_parser-2.5.3 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/download/ytdlp.py +0 -0
- {nonebot_plugin_parser-2.5.3 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/exception.py +0 -0
- {nonebot_plugin_parser-2.5.3 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/matchers/filter.py +0 -0
- {nonebot_plugin_parser-2.5.3 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/matchers/rule.py +0 -0
- {nonebot_plugin_parser-2.5.3 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/parsers/acfun/video.py +0 -0
- {nonebot_plugin_parser-2.5.3 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/parsers/bilibili/article.py +0 -0
- {nonebot_plugin_parser-2.5.3 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/parsers/bilibili/common.py +0 -0
- {nonebot_plugin_parser-2.5.3 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/parsers/bilibili/dynamic.py +0 -0
- {nonebot_plugin_parser-2.5.3 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/parsers/bilibili/favlist.py +0 -0
- {nonebot_plugin_parser-2.5.3 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/parsers/bilibili/live.py +0 -0
- {nonebot_plugin_parser-2.5.3 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/parsers/bilibili/opus.py +0 -0
- {nonebot_plugin_parser-2.5.3 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/parsers/bilibili/video.py +0 -0
- {nonebot_plugin_parser-2.5.3 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/parsers/cookie.py +0 -0
- {nonebot_plugin_parser-2.5.3 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/parsers/douyin/slides.py +0 -0
- {nonebot_plugin_parser-2.5.3 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/parsers/weibo/article.py +0 -0
- {nonebot_plugin_parser-2.5.3 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/parsers/youtube/meta.py +0 -0
- {nonebot_plugin_parser-2.5.3 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/renders/resources/HYSongYunLangHeiW.ttf +0 -0
- {nonebot_plugin_parser-2.5.3 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/renders/resources/avatar.png +0 -0
- {nonebot_plugin_parser-2.5.3 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/renders/resources/bilibili.png +0 -0
- {nonebot_plugin_parser-2.5.3 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/renders/resources/douyin.png +0 -0
- {nonebot_plugin_parser-2.5.3 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/renders/resources/kuaishou.png +0 -0
- {nonebot_plugin_parser-2.5.3 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/renders/resources/play.png +0 -0
- {nonebot_plugin_parser-2.5.3 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/renders/resources/tiktok.png +0 -0
- {nonebot_plugin_parser-2.5.3 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/renders/resources/twitter.png +0 -0
- {nonebot_plugin_parser-2.5.3 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/renders/resources/weibo.png +0 -0
- {nonebot_plugin_parser-2.5.3 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/renders/resources/xiaohongshu.png +0 -0
- {nonebot_plugin_parser-2.5.3 → nonebot_plugin_parser-2.6.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.6.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
|
|
@@ -19,20 +19,20 @@ Classifier: Programming Language :: Python :: 3.14
|
|
|
19
19
|
Classifier: Topic :: Communications :: Chat
|
|
20
20
|
Classifier: Topic :: Internet :: WWW/HTTP
|
|
21
21
|
Classifier: Topic :: Multimedia :: Video
|
|
22
|
-
Requires-Dist: nonebot2>=2.4.3,<3.0.0
|
|
23
22
|
Requires-Dist: rich>=13.0.0
|
|
24
23
|
Requires-Dist: pillow>=11.0.0
|
|
25
24
|
Requires-Dist: aiofiles>=25.1.0
|
|
26
25
|
Requires-Dist: httpx>=0.27.2,<1.0.0
|
|
27
26
|
Requires-Dist: msgspec>=0.20.0,<1.0.0
|
|
27
|
+
Requires-Dist: nonebot2>=2.4.3,<3.0.0
|
|
28
28
|
Requires-Dist: apilmoji[rich]>=0.3.1,<1.0.0
|
|
29
29
|
Requires-Dist: beautifulsoup4>=4.12.0,<5.0.0
|
|
30
30
|
Requires-Dist: curl-cffi>=0.13.0,!=0.14.0,<1.0.0
|
|
31
31
|
Requires-Dist: bilibili-api-python>=17.4.1,<18.0.0
|
|
32
|
+
Requires-Dist: nonebot-plugin-uninfo>=0.10.1,<1.0.0
|
|
32
33
|
Requires-Dist: nonebot-plugin-alconna>=0.60.4,<1.0.0
|
|
33
|
-
Requires-Dist: nonebot-plugin-apscheduler>=0.5.0,<1.0.0
|
|
34
34
|
Requires-Dist: nonebot-plugin-localstore>=0.7.4,<1.0.0
|
|
35
|
-
Requires-Dist: nonebot-plugin-
|
|
35
|
+
Requires-Dist: nonebot-plugin-apscheduler>=0.5.0,<1.0.0
|
|
36
36
|
Requires-Dist: nonebot-plugin-htmlkit>=0.1.0rc4 ; extra == 'all'
|
|
37
37
|
Requires-Dist: nonebot-plugin-htmlrender>=0.6.7 ; extra == 'all'
|
|
38
38
|
Requires-Dist: yt-dlp[default]>=2026.3.13 ; extra == 'all'
|
|
@@ -71,7 +71,7 @@ Description-Content-Type: text/markdown
|
|
|
71
71
|
|
|
72
72
|
</div>
|
|
73
73
|
|
|
74
|
-
> [!IMPORTANT]
|
|
74
|
+
> [!IMPORTANT]
|
|
75
75
|
> **收藏项目**,你将从 GitHub 上无延迟地接收所有发布通知~ ⭐️
|
|
76
76
|
|
|
77
77
|
<img width="100%" src="https://starify.komoridevs.icu/api/starify?owner=fllesser&repo=nonebot-plugin-parser" alt="starify" />
|
|
@@ -81,14 +81,14 @@ Description-Content-Type: text/markdown
|
|
|
81
81
|
| 平台 | 触发的消息形态 | 视频 | 图集 | 音频 |
|
|
82
82
|
| ------- | --------------------------------- | ---- | ---- | ---- |
|
|
83
83
|
| B 站 | av 号/BV 号/链接/短链/卡片/小程序 | ✅ | ✅ | ✅ |
|
|
84
|
-
| 抖音 | 链接(分享链接,兼容电脑端链接) | ✅ | ✅ | ❌️
|
|
85
|
-
| 微博 | 链接(博文,视频,show, 文章) | ✅ | ✅ | ❌️
|
|
86
|
-
| 小红书 | 链接(含短链)/卡片 | ✅ | ✅ | ❌️
|
|
87
|
-
| 快手 | 链接(包含标准链接和短链) | ✅ | ✅ | ❌️
|
|
88
|
-
| acfun | 链接 | ✅ | ❌️
|
|
89
|
-
| youtube | 链接(含短链) | ✅ | ❌️
|
|
90
|
-
| tiktok | 链接 | ✅ | ❌️
|
|
91
|
-
| twitter | 链接 | ✅ | ✅ | ❌️
|
|
84
|
+
| 抖音 | 链接(分享链接,兼容电脑端链接) | ✅ | ✅ | ❌️ |
|
|
85
|
+
| 微博 | 链接(博文,视频,show, 文章) | ✅ | ✅ | ❌️ |
|
|
86
|
+
| 小红书 | 链接(含短链)/卡片 | ✅ | ✅ | ❌️ |
|
|
87
|
+
| 快手 | 链接(包含标准链接和短链) | ✅ | ✅ | ❌️ |
|
|
88
|
+
| acfun | 链接 | ✅ | ❌️ | ❌️ |
|
|
89
|
+
| youtube | 链接(含短链) | ✅ | ❌️ | ✅ |
|
|
90
|
+
| tiktok | 链接 | ✅ | ❌️ | ❌️ |
|
|
91
|
+
| twitter | 链接 | ✅ | ✅ | ❌️ |
|
|
92
92
|
|
|
93
93
|
支持的链接,可参考 [测试链接](https://github.com/fllesser/nonebot-plugin-parser/blob/master/tests/others/test_urls.md)
|
|
94
94
|
|
|
@@ -108,7 +108,7 @@ Description-Content-Type: text/markdown
|
|
|
108
108
|
|
|
109
109
|
## 💿 安装
|
|
110
110
|
|
|
111
|
-
> [!Warning]
|
|
111
|
+
> [!Warning]
|
|
112
112
|
> **如果你已经在使用 nonebot-plugin-resolver[2],请在安装此插件前卸载**
|
|
113
113
|
|
|
114
114
|
<details>
|
|
@@ -304,6 +304,9 @@ parser_append_url=False
|
|
|
304
304
|
# 例如: ./config/nonebot_plugin_parser/
|
|
305
305
|
parser_custom_font="LXGWZhenKaiGB-Regular.ttf"
|
|
306
306
|
|
|
307
|
+
# [可选] 用户名及标题字体粗细程度(仅 htmlrender),如果使用 LXGW ZhenKai 字体建议设置为 500~600 之间
|
|
308
|
+
parser_custom_font_weight: int = 700
|
|
309
|
+
|
|
307
310
|
# [可选] 是否需要转发媒体内容(超过 4 项时始终使用合并转发)
|
|
308
311
|
parser_need_forward_contents=True
|
|
309
312
|
|
|
@@ -390,18 +393,19 @@ class ExampleParser(BaseParser):
|
|
|
390
393
|
|
|
391
394
|
# 4. 视频内容
|
|
392
395
|
author = self.create_author(author_name, avatar_url)
|
|
393
|
-
video = self.
|
|
396
|
+
video = self.create_video(video_url, cover_url, duration)
|
|
394
397
|
|
|
395
398
|
# 5. 图集内容
|
|
396
399
|
image_urls = data.get("images")
|
|
397
|
-
images = self.
|
|
400
|
+
images = self.create_image(image_urls)
|
|
398
401
|
|
|
399
402
|
# 6. 返回解析结果
|
|
400
403
|
return self.result(
|
|
401
404
|
title=title,
|
|
402
405
|
text=description,
|
|
403
406
|
author=author,
|
|
404
|
-
|
|
407
|
+
video=video,
|
|
408
|
+
contents=[*images],
|
|
405
409
|
timestamp=timestamp,
|
|
406
410
|
url=f"https://example.com/video/{video_id}",
|
|
407
411
|
)
|
|
@@ -412,7 +416,7 @@ class ExampleParser(BaseParser):
|
|
|
412
416
|
<details>
|
|
413
417
|
<summary>辅助函数</summary>
|
|
414
418
|
|
|
415
|
-
>
|
|
419
|
+
> 构建作者
|
|
416
420
|
|
|
417
421
|
```python
|
|
418
422
|
author = self.create_author(
|
|
@@ -426,7 +430,7 @@ author = self.create_author(
|
|
|
426
430
|
|
|
427
431
|
```python
|
|
428
432
|
# 方式1:传入 URL,自动下载
|
|
429
|
-
video = self.
|
|
433
|
+
video = self.create_video(
|
|
430
434
|
url_or_task="https://example.com/video.mp4",
|
|
431
435
|
cover_url="https://example.com/cover.jpg", # 可选
|
|
432
436
|
duration=120.5 # 可选,单位:秒
|
|
@@ -435,7 +439,7 @@ video = self.create_video_content(
|
|
|
435
439
|
# 方式2:传入已创建的下载任务
|
|
436
440
|
from nonebot_plugin_parser.download import DOWNLOADER
|
|
437
441
|
video_task = DOWNLOADER.download_video(url, ext_headers=self.headers)
|
|
438
|
-
video = self.
|
|
442
|
+
video = self.create_video(
|
|
439
443
|
url_or_task=video_task,
|
|
440
444
|
cover_url=cover_url,
|
|
441
445
|
duration=duration
|
|
@@ -446,21 +450,12 @@ video = self.create_video_content(
|
|
|
446
450
|
|
|
447
451
|
```python
|
|
448
452
|
# 并发下载图集内容
|
|
449
|
-
images = self.
|
|
453
|
+
images = self.create_images([
|
|
450
454
|
"https://example.com/img1.jpg",
|
|
451
455
|
"https://example.com/img2.jpg",
|
|
452
456
|
])
|
|
453
457
|
```
|
|
454
458
|
|
|
455
|
-
> 创建动图内容(GIF),平台一般只提供视频(后续插件会做自动转为 gif 的处理)
|
|
456
|
-
|
|
457
|
-
```python
|
|
458
|
-
dynamics = self.create_dynamic_contents([
|
|
459
|
-
"https://example.com/dynamic1.mp4",
|
|
460
|
-
"https://example.com/dynamic2.mp4",
|
|
461
|
-
])
|
|
462
|
-
```
|
|
463
|
-
|
|
464
459
|
> 重定向 url
|
|
465
460
|
|
|
466
461
|
```python
|
|
@@ -478,5 +473,5 @@ real_url = await self.get_redirect_url(
|
|
|
478
473
|
|
|
479
474
|
## 🎉 致谢
|
|
480
475
|
|
|
481
|
-
[nonebot-plugin-resolver](https://github.com/zhiyu1998/nonebot-plugin-resolver)
|
|
482
|
-
[parse-video-py](https://github.com/wujunwei928/parse-video-py)
|
|
476
|
+
- [nonebot-plugin-resolver](https://github.com/zhiyu1998/nonebot-plugin-resolver) - 本项目最初基于此插件进行开发,在此表示感谢。尽管当前版本代码已完全重构,但仍感谢原项目提供的初始思路和参考。
|
|
477
|
+
- [parse-video-py](https://github.com/wujunwei928/parse-video-py) - 在抖音解析功能实现方面提供了技术参考和借鉴。
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
|
|
18
18
|
</div>
|
|
19
19
|
|
|
20
|
-
> [!IMPORTANT]
|
|
20
|
+
> [!IMPORTANT]
|
|
21
21
|
> **收藏项目**,你将从 GitHub 上无延迟地接收所有发布通知~ ⭐️
|
|
22
22
|
|
|
23
23
|
<img width="100%" src="https://starify.komoridevs.icu/api/starify?owner=fllesser&repo=nonebot-plugin-parser" alt="starify" />
|
|
@@ -27,14 +27,14 @@
|
|
|
27
27
|
| 平台 | 触发的消息形态 | 视频 | 图集 | 音频 |
|
|
28
28
|
| ------- | --------------------------------- | ---- | ---- | ---- |
|
|
29
29
|
| B 站 | av 号/BV 号/链接/短链/卡片/小程序 | ✅ | ✅ | ✅ |
|
|
30
|
-
| 抖音 | 链接(分享链接,兼容电脑端链接) | ✅ | ✅ | ❌️
|
|
31
|
-
| 微博 | 链接(博文,视频,show, 文章) | ✅ | ✅ | ❌️
|
|
32
|
-
| 小红书 | 链接(含短链)/卡片 | ✅ | ✅ | ❌️
|
|
33
|
-
| 快手 | 链接(包含标准链接和短链) | ✅ | ✅ | ❌️
|
|
34
|
-
| acfun | 链接 | ✅ | ❌️
|
|
35
|
-
| youtube | 链接(含短链) | ✅ | ❌️
|
|
36
|
-
| tiktok | 链接 | ✅ | ❌️
|
|
37
|
-
| twitter | 链接 | ✅ | ✅ | ❌️
|
|
30
|
+
| 抖音 | 链接(分享链接,兼容电脑端链接) | ✅ | ✅ | ❌️ |
|
|
31
|
+
| 微博 | 链接(博文,视频,show, 文章) | ✅ | ✅ | ❌️ |
|
|
32
|
+
| 小红书 | 链接(含短链)/卡片 | ✅ | ✅ | ❌️ |
|
|
33
|
+
| 快手 | 链接(包含标准链接和短链) | ✅ | ✅ | ❌️ |
|
|
34
|
+
| acfun | 链接 | ✅ | ❌️ | ❌️ |
|
|
35
|
+
| youtube | 链接(含短链) | ✅ | ❌️ | ✅ |
|
|
36
|
+
| tiktok | 链接 | ✅ | ❌️ | ❌️ |
|
|
37
|
+
| twitter | 链接 | ✅ | ✅ | ❌️ |
|
|
38
38
|
|
|
39
39
|
支持的链接,可参考 [测试链接](https://github.com/fllesser/nonebot-plugin-parser/blob/master/tests/others/test_urls.md)
|
|
40
40
|
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
|
|
55
55
|
## 💿 安装
|
|
56
56
|
|
|
57
|
-
> [!Warning]
|
|
57
|
+
> [!Warning]
|
|
58
58
|
> **如果你已经在使用 nonebot-plugin-resolver[2],请在安装此插件前卸载**
|
|
59
59
|
|
|
60
60
|
<details>
|
|
@@ -250,6 +250,9 @@ parser_append_url=False
|
|
|
250
250
|
# 例如: ./config/nonebot_plugin_parser/
|
|
251
251
|
parser_custom_font="LXGWZhenKaiGB-Regular.ttf"
|
|
252
252
|
|
|
253
|
+
# [可选] 用户名及标题字体粗细程度(仅 htmlrender),如果使用 LXGW ZhenKai 字体建议设置为 500~600 之间
|
|
254
|
+
parser_custom_font_weight: int = 700
|
|
255
|
+
|
|
253
256
|
# [可选] 是否需要转发媒体内容(超过 4 项时始终使用合并转发)
|
|
254
257
|
parser_need_forward_contents=True
|
|
255
258
|
|
|
@@ -336,18 +339,19 @@ class ExampleParser(BaseParser):
|
|
|
336
339
|
|
|
337
340
|
# 4. 视频内容
|
|
338
341
|
author = self.create_author(author_name, avatar_url)
|
|
339
|
-
video = self.
|
|
342
|
+
video = self.create_video(video_url, cover_url, duration)
|
|
340
343
|
|
|
341
344
|
# 5. 图集内容
|
|
342
345
|
image_urls = data.get("images")
|
|
343
|
-
images = self.
|
|
346
|
+
images = self.create_image(image_urls)
|
|
344
347
|
|
|
345
348
|
# 6. 返回解析结果
|
|
346
349
|
return self.result(
|
|
347
350
|
title=title,
|
|
348
351
|
text=description,
|
|
349
352
|
author=author,
|
|
350
|
-
|
|
353
|
+
video=video,
|
|
354
|
+
contents=[*images],
|
|
351
355
|
timestamp=timestamp,
|
|
352
356
|
url=f"https://example.com/video/{video_id}",
|
|
353
357
|
)
|
|
@@ -358,7 +362,7 @@ class ExampleParser(BaseParser):
|
|
|
358
362
|
<details>
|
|
359
363
|
<summary>辅助函数</summary>
|
|
360
364
|
|
|
361
|
-
>
|
|
365
|
+
> 构建作者
|
|
362
366
|
|
|
363
367
|
```python
|
|
364
368
|
author = self.create_author(
|
|
@@ -372,7 +376,7 @@ author = self.create_author(
|
|
|
372
376
|
|
|
373
377
|
```python
|
|
374
378
|
# 方式1:传入 URL,自动下载
|
|
375
|
-
video = self.
|
|
379
|
+
video = self.create_video(
|
|
376
380
|
url_or_task="https://example.com/video.mp4",
|
|
377
381
|
cover_url="https://example.com/cover.jpg", # 可选
|
|
378
382
|
duration=120.5 # 可选,单位:秒
|
|
@@ -381,7 +385,7 @@ video = self.create_video_content(
|
|
|
381
385
|
# 方式2:传入已创建的下载任务
|
|
382
386
|
from nonebot_plugin_parser.download import DOWNLOADER
|
|
383
387
|
video_task = DOWNLOADER.download_video(url, ext_headers=self.headers)
|
|
384
|
-
video = self.
|
|
388
|
+
video = self.create_video(
|
|
385
389
|
url_or_task=video_task,
|
|
386
390
|
cover_url=cover_url,
|
|
387
391
|
duration=duration
|
|
@@ -392,21 +396,12 @@ video = self.create_video_content(
|
|
|
392
396
|
|
|
393
397
|
```python
|
|
394
398
|
# 并发下载图集内容
|
|
395
|
-
images = self.
|
|
399
|
+
images = self.create_images([
|
|
396
400
|
"https://example.com/img1.jpg",
|
|
397
401
|
"https://example.com/img2.jpg",
|
|
398
402
|
])
|
|
399
403
|
```
|
|
400
404
|
|
|
401
|
-
> 创建动图内容(GIF),平台一般只提供视频(后续插件会做自动转为 gif 的处理)
|
|
402
|
-
|
|
403
|
-
```python
|
|
404
|
-
dynamics = self.create_dynamic_contents([
|
|
405
|
-
"https://example.com/dynamic1.mp4",
|
|
406
|
-
"https://example.com/dynamic2.mp4",
|
|
407
|
-
])
|
|
408
|
-
```
|
|
409
|
-
|
|
410
405
|
> 重定向 url
|
|
411
406
|
|
|
412
407
|
```python
|
|
@@ -424,5 +419,5 @@ real_url = await self.get_redirect_url(
|
|
|
424
419
|
|
|
425
420
|
## 🎉 致谢
|
|
426
421
|
|
|
427
|
-
[nonebot-plugin-resolver](https://github.com/zhiyu1998/nonebot-plugin-resolver)
|
|
428
|
-
[parse-video-py](https://github.com/wujunwei928/parse-video-py)
|
|
422
|
+
- [nonebot-plugin-resolver](https://github.com/zhiyu1998/nonebot-plugin-resolver) - 本项目最初基于此插件进行开发,在此表示感谢。尽管当前版本代码已完全重构,但仍感谢原项目提供的初始思路和参考。
|
|
423
|
+
- [parse-video-py](https://github.com/wujunwei928/parse-video-py) - 在抖音解析功能实现方面提供了技术参考和借鉴。
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "nonebot-plugin-parser"
|
|
3
|
-
version = "2.
|
|
3
|
+
version = "2.6.1"
|
|
4
4
|
description = "NoneBot2 链接分享解析 Alconna 版, 现支持B站|抖音|快手|微博|小红书|YouTube|TikTok|Twitter|AcFun|NGA"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.10"
|
|
@@ -39,20 +39,20 @@ classifiers = [
|
|
|
39
39
|
]
|
|
40
40
|
|
|
41
41
|
dependencies = [
|
|
42
|
-
"nonebot2>=2.4.3,<3.0.0",
|
|
43
42
|
"rich>=13.0.0",
|
|
44
43
|
"pillow>=11.0.0",
|
|
45
44
|
"aiofiles>=25.1.0",
|
|
46
45
|
"httpx>=0.27.2,<1.0.0",
|
|
47
46
|
"msgspec>=0.20.0,<1.0.0",
|
|
47
|
+
"nonebot2>=2.4.3,<3.0.0",
|
|
48
48
|
"apilmoji[rich]>=0.3.1,<1.0.0",
|
|
49
49
|
"beautifulsoup4>=4.12.0,<5.0.0",
|
|
50
50
|
"curl_cffi>=0.13.0,<1.0.0,!=0.14.0",
|
|
51
51
|
"bilibili-api-python>=17.4.1,<18.0.0",
|
|
52
|
+
"nonebot-plugin-uninfo>=0.10.1,<1.0.0",
|
|
52
53
|
"nonebot-plugin-alconna>=0.60.4,<1.0.0",
|
|
53
|
-
"nonebot-plugin-apscheduler>=0.5.0,<1.0.0",
|
|
54
54
|
"nonebot-plugin-localstore>=0.7.4,<1.0.0",
|
|
55
|
-
"nonebot-plugin-
|
|
55
|
+
"nonebot-plugin-apscheduler>=0.5.0,<1.0.0",
|
|
56
56
|
]
|
|
57
57
|
|
|
58
58
|
[project.urls]
|
|
@@ -118,7 +118,7 @@ nonebug = { git = "https://github.com/nonebot/nonebug", rev = "master" }
|
|
|
118
118
|
[tool.bumpversion]
|
|
119
119
|
tag = true
|
|
120
120
|
commit = true
|
|
121
|
-
current_version = "2.
|
|
121
|
+
current_version = "2.6.1"
|
|
122
122
|
message = "release: bump vesion from {current_version} to {new_version}"
|
|
123
123
|
|
|
124
124
|
[[tool.bumpversion.files]]
|
|
@@ -150,7 +150,7 @@ test-render = "pytest tests/renders --cov=src --cov-report=xml --junitxml=junit.
|
|
|
150
150
|
bump = "bump-my-version bump"
|
|
151
151
|
show-bump = "bump-my-version show-bump"
|
|
152
152
|
|
|
153
|
-
[tool.
|
|
153
|
+
[tool.basedpyright]
|
|
154
154
|
pythonVersion = "3.10"
|
|
155
155
|
pythonPlatform = "All"
|
|
156
156
|
defineConstant = { PYDANTIC_V2 = true }
|
{nonebot_plugin_parser-2.5.3 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/config.py
RENAMED
|
@@ -48,6 +48,8 @@ class Config(BaseModel):
|
|
|
48
48
|
"""Renderer 类型"""
|
|
49
49
|
parser_custom_font: str | None = None
|
|
50
50
|
"""自定义字体"""
|
|
51
|
+
parser_custom_font_weight: int = 700
|
|
52
|
+
"""字体粗细程度"""
|
|
51
53
|
parser_need_forward_contents: bool = True
|
|
52
54
|
"""是否需要转发媒体内容"""
|
|
53
55
|
parser_emoji_cdn: str = ELK_SH_CDN
|
|
@@ -160,6 +162,11 @@ class Config(BaseModel):
|
|
|
160
162
|
|
|
161
163
|
return font_path
|
|
162
164
|
|
|
165
|
+
@property
|
|
166
|
+
def custom_font_weight(self) -> int:
|
|
167
|
+
"""字体粗细程度"""
|
|
168
|
+
return self.parser_custom_font_weight
|
|
169
|
+
|
|
163
170
|
@property
|
|
164
171
|
def need_forward_contents(self) -> bool:
|
|
165
172
|
"""是否需要转发媒体内容"""
|
|
@@ -4,9 +4,10 @@ from functools import partial
|
|
|
4
4
|
from contextlib import contextmanager
|
|
5
5
|
from urllib.parse import urljoin
|
|
6
6
|
|
|
7
|
+
import httpx
|
|
7
8
|
import aiofiles
|
|
8
|
-
|
|
9
|
-
from nonebot import logger
|
|
9
|
+
import curl_cffi
|
|
10
|
+
from nonebot import logger, get_driver
|
|
10
11
|
from rich.progress import (
|
|
11
12
|
Progress,
|
|
12
13
|
BarColumn,
|
|
@@ -15,22 +16,27 @@ from rich.progress import (
|
|
|
15
16
|
)
|
|
16
17
|
|
|
17
18
|
from .task import auto_task
|
|
18
|
-
from ..utils import merge_av, safe_unlink, generate_file_name
|
|
19
|
+
from ..utils import merge_av, safe_unlink, generate_file_name, is_module_available
|
|
19
20
|
from ..config import pconfig
|
|
20
21
|
from ..constants import COMMON_HEADER, DOWNLOAD_TIMEOUT
|
|
21
22
|
from ..exception import IgnoreException, DownloadException
|
|
22
23
|
|
|
23
24
|
|
|
24
25
|
class StreamDownloader:
|
|
25
|
-
"""Downloader class for downloading files with stream"""
|
|
26
|
-
|
|
27
26
|
def __init__(self):
|
|
28
27
|
self.headers: dict[str, str] = COMMON_HEADER.copy()
|
|
29
28
|
self.cache_dir: Path = pconfig.cache_dir
|
|
30
|
-
self.client: AsyncClient = AsyncClient(timeout=DOWNLOAD_TIMEOUT, verify=False)
|
|
29
|
+
self.client: httpx.AsyncClient = httpx.AsyncClient(timeout=DOWNLOAD_TIMEOUT, verify=False)
|
|
30
|
+
|
|
31
|
+
async def aclose(self):
|
|
32
|
+
await self.client.aclose()
|
|
31
33
|
|
|
34
|
+
@staticmethod
|
|
32
35
|
@contextmanager
|
|
33
|
-
def rich_progress(
|
|
36
|
+
def rich_progress(
|
|
37
|
+
desc: str,
|
|
38
|
+
total: int | None = None,
|
|
39
|
+
):
|
|
34
40
|
with Progress(
|
|
35
41
|
TextColumn("[bold blue]{task.description}", justify="right"),
|
|
36
42
|
BarColumn(bar_width=None),
|
|
@@ -41,53 +47,83 @@ class StreamDownloader:
|
|
|
41
47
|
task_id = progress.add_task(description=desc, total=total)
|
|
42
48
|
yield partial(progress.update, task_id)
|
|
43
49
|
|
|
44
|
-
|
|
50
|
+
@staticmethod
|
|
51
|
+
def _validate_content_length(
|
|
52
|
+
response: httpx.Response | curl_cffi.Response,
|
|
53
|
+
) -> int:
|
|
54
|
+
"""获取文件长度"""
|
|
55
|
+
content_length = response.headers.get("Content-Length")
|
|
56
|
+
content_length = int(content_length) if content_length else 0
|
|
57
|
+
|
|
58
|
+
if content_length == 0:
|
|
59
|
+
logger.warning(f"媒体 url: {response.url}, 大小为 0, 取消下载")
|
|
60
|
+
raise IgnoreException
|
|
61
|
+
|
|
62
|
+
if (file_size := content_length / 1024 / 1024) > pconfig.max_size:
|
|
63
|
+
logger.warning(f"媒体 url: {response.url} 大小 {file_size:.2f} MB, 超过 {pconfig.max_size} MB, 取消下载")
|
|
64
|
+
raise IgnoreException
|
|
65
|
+
|
|
66
|
+
return content_length
|
|
67
|
+
|
|
68
|
+
async def _download_file_with_httpx(
|
|
45
69
|
self,
|
|
46
70
|
url: str,
|
|
47
71
|
*,
|
|
48
|
-
|
|
49
|
-
|
|
72
|
+
file_path: Path,
|
|
73
|
+
headers: dict[str, str],
|
|
50
74
|
chunk_size: int = 64 * 1024,
|
|
51
75
|
) -> Path:
|
|
52
76
|
"""download file by url with stream"""
|
|
53
|
-
if not file_name:
|
|
54
|
-
file_name = generate_file_name(url)
|
|
55
|
-
file_path = self.cache_dir / file_name
|
|
56
|
-
# 如果文件存在,则直接返回
|
|
57
|
-
if file_path.exists():
|
|
58
|
-
return file_path
|
|
59
77
|
|
|
60
|
-
|
|
78
|
+
async with self.client.stream(
|
|
79
|
+
"GET",
|
|
80
|
+
url,
|
|
81
|
+
headers=headers,
|
|
82
|
+
follow_redirects=True,
|
|
83
|
+
) as response:
|
|
84
|
+
response.raise_for_status()
|
|
85
|
+
content_length = self._validate_content_length(response)
|
|
86
|
+
|
|
87
|
+
with self.rich_progress(
|
|
88
|
+
f"httpx | {file_path.name}",
|
|
89
|
+
content_length,
|
|
90
|
+
) as update_progress:
|
|
91
|
+
async with aiofiles.open(file_path, "wb") as file:
|
|
92
|
+
async for chunk in response.aiter_bytes(chunk_size):
|
|
93
|
+
await file.write(chunk)
|
|
94
|
+
update_progress(advance=len(chunk))
|
|
61
95
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
96
|
+
return file_path
|
|
97
|
+
|
|
98
|
+
async def _download_file_with_curl_cffi(
|
|
99
|
+
self,
|
|
100
|
+
url: str,
|
|
101
|
+
*,
|
|
102
|
+
file_path: Path,
|
|
103
|
+
headers: dict[str, str],
|
|
104
|
+
) -> Path:
|
|
105
|
+
async with curl_cffi.AsyncSession(allow_redirects=True) as session:
|
|
106
|
+
response: curl_cffi.Response = await session.get(
|
|
107
|
+
url,
|
|
108
|
+
headers=headers,
|
|
109
|
+
timeout=DOWNLOAD_TIMEOUT,
|
|
110
|
+
stream=True,
|
|
111
|
+
)
|
|
112
|
+
response.raise_for_status()
|
|
113
|
+
content_length = self._validate_content_length(response)
|
|
114
|
+
|
|
115
|
+
with self.rich_progress(
|
|
116
|
+
f"curl_cffi | {file_path.name}",
|
|
117
|
+
content_length,
|
|
118
|
+
) as update_progress:
|
|
119
|
+
async with aiofiles.open(file_path, "wb") as file:
|
|
120
|
+
async for chunk in response.aiter_content(chunk_size=8192):
|
|
121
|
+
await file.write(chunk)
|
|
122
|
+
update_progress(advance=len(chunk))
|
|
86
123
|
|
|
87
124
|
return file_path
|
|
88
125
|
|
|
89
|
-
|
|
90
|
-
async def download_file(
|
|
126
|
+
async def _download_file(
|
|
91
127
|
self,
|
|
92
128
|
url: str,
|
|
93
129
|
*,
|
|
@@ -95,13 +131,28 @@ class StreamDownloader:
|
|
|
95
131
|
ext_headers: dict[str, str] | None = None,
|
|
96
132
|
chunk_size: int = 64 * 1024,
|
|
97
133
|
) -> Path:
|
|
98
|
-
"""download file by url with
|
|
99
|
-
|
|
100
|
-
url
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
134
|
+
"""download file by url with fallback"""
|
|
135
|
+
if not file_name:
|
|
136
|
+
file_name = generate_file_name(url)
|
|
137
|
+
file_path = self.cache_dir / file_name
|
|
138
|
+
if file_path.exists():
|
|
139
|
+
return file_path
|
|
140
|
+
|
|
141
|
+
headers = {**self.headers, **(ext_headers or {})}
|
|
142
|
+
|
|
143
|
+
try:
|
|
144
|
+
path = await self._download_file_with_httpx(
|
|
145
|
+
url, file_path=file_path, headers=headers, chunk_size=chunk_size
|
|
146
|
+
)
|
|
147
|
+
except httpx.HTTPError:
|
|
148
|
+
logger.opt(exception=True).warning(f"下载失败(httpx) | url: {url}")
|
|
149
|
+
try:
|
|
150
|
+
path = await self._download_file_with_curl_cffi(url, file_path=file_path, headers=headers)
|
|
151
|
+
except curl_cffi.CurlError:
|
|
152
|
+
logger.opt(exception=True).warning(f"下载失败(curl_cffi) | url: {url}")
|
|
153
|
+
raise DownloadException("媒体下载失败")
|
|
154
|
+
|
|
155
|
+
return path
|
|
105
156
|
|
|
106
157
|
@auto_task
|
|
107
158
|
async def download_video(
|
|
@@ -199,7 +250,7 @@ class StreamDownloader:
|
|
|
199
250
|
await f.write(chunk)
|
|
200
251
|
total_size += len(chunk)
|
|
201
252
|
update_progress(advance=len(chunk), total=total_size)
|
|
202
|
-
except HTTPError:
|
|
253
|
+
except httpx.HTTPError:
|
|
203
254
|
await safe_unlink(video_path)
|
|
204
255
|
logger.exception("m3u8 视频下载失败")
|
|
205
256
|
raise DownloadException("m3u8 视频下载失败")
|
|
@@ -224,13 +275,18 @@ class StreamDownloader:
|
|
|
224
275
|
return slices
|
|
225
276
|
|
|
226
277
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
278
|
+
downloader: StreamDownloader = StreamDownloader()
|
|
279
|
+
"""全局下载器实例,提供下载功能"""
|
|
280
|
+
yt_dlp_downloader = None
|
|
281
|
+
"""yt-dlp 下载器实例,提供下载视频功能,若 yt-dlp 未安装则为 None"""
|
|
231
282
|
|
|
283
|
+
if is_module_available("yt_dlp"):
|
|
232
284
|
from .ytdlp import YtdlpDownloader
|
|
233
285
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
286
|
+
yt_dlp_downloader = YtdlpDownloader()
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
@get_driver().on_shutdown
|
|
290
|
+
async def close_download_client():
|
|
291
|
+
logger.debug("正在关闭下载器...")
|
|
292
|
+
await downloader.aclose()
|
|
@@ -0,0 +1,20 @@
|
|
|
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
|
+
P = ParamSpec("P")
|
|
8
|
+
T = TypeVar("T")
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def auto_task(func: Callable[P, Coroutine[Any, Any, Path]]) -> Callable[P, Task[Path]]:
|
|
12
|
+
"""装饰器:自动将异步函数调用转换为 Task, 完整保留类型提示"""
|
|
13
|
+
|
|
14
|
+
@wraps(func)
|
|
15
|
+
def wrapper(*args: P.args, **kwargs: P.kwargs) -> Task[Path]:
|
|
16
|
+
coro = func(*args, **kwargs)
|
|
17
|
+
name = " | ".join(str(arg) for arg in args if isinstance(arg, str))
|
|
18
|
+
return create_task(coro, name=func.__name__ + " | " + name)
|
|
19
|
+
|
|
20
|
+
return wrapper
|