nonebot-plugin-parser 2.5.2__tar.gz → 2.6.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.
Files changed (80) hide show
  1. {nonebot_plugin_parser-2.5.2 → nonebot_plugin_parser-2.6.0}/PKG-INFO +23 -28
  2. {nonebot_plugin_parser-2.5.2 → nonebot_plugin_parser-2.6.0}/README.md +22 -27
  3. {nonebot_plugin_parser-2.5.2 → nonebot_plugin_parser-2.6.0}/pyproject.toml +2 -2
  4. {nonebot_plugin_parser-2.5.2 → nonebot_plugin_parser-2.6.0}/src/nonebot_plugin_parser/config.py +7 -0
  5. {nonebot_plugin_parser-2.5.2 → nonebot_plugin_parser-2.6.0}/src/nonebot_plugin_parser/download/__init__.py +23 -20
  6. nonebot_plugin_parser-2.6.0/src/nonebot_plugin_parser/download/task.py +20 -0
  7. {nonebot_plugin_parser-2.5.2 → nonebot_plugin_parser-2.6.0}/src/nonebot_plugin_parser/helper.py +13 -3
  8. {nonebot_plugin_parser-2.5.2 → nonebot_plugin_parser-2.6.0}/src/nonebot_plugin_parser/parsers/__init__.py +0 -2
  9. {nonebot_plugin_parser-2.5.2 → nonebot_plugin_parser-2.6.0}/src/nonebot_plugin_parser/parsers/acfun/__init__.py +1 -1
  10. {nonebot_plugin_parser-2.5.2 → nonebot_plugin_parser-2.6.0}/src/nonebot_plugin_parser/parsers/base.py +33 -55
  11. {nonebot_plugin_parser-2.5.2 → nonebot_plugin_parser-2.6.0}/src/nonebot_plugin_parser/parsers/bilibili/__init__.py +6 -6
  12. {nonebot_plugin_parser-2.5.2 → nonebot_plugin_parser-2.6.0}/src/nonebot_plugin_parser/parsers/data.py +27 -24
  13. {nonebot_plugin_parser-2.5.2 → nonebot_plugin_parser-2.6.0}/src/nonebot_plugin_parser/parsers/douyin/__init__.py +15 -11
  14. {nonebot_plugin_parser-2.5.2 → nonebot_plugin_parser-2.6.0}/src/nonebot_plugin_parser/parsers/douyin/video.py +4 -0
  15. {nonebot_plugin_parser-2.5.2 → nonebot_plugin_parser-2.6.0}/src/nonebot_plugin_parser/parsers/kuaishou/__init__.py +2 -2
  16. {nonebot_plugin_parser-2.5.2 → nonebot_plugin_parser-2.6.0}/src/nonebot_plugin_parser/parsers/nga.py +1 -1
  17. nonebot_plugin_parser-2.6.0/src/nonebot_plugin_parser/parsers/task.py +47 -0
  18. {nonebot_plugin_parser-2.5.2 → nonebot_plugin_parser-2.6.0}/src/nonebot_plugin_parser/parsers/tiktok.py +1 -1
  19. {nonebot_plugin_parser-2.5.2 → nonebot_plugin_parser-2.6.0}/src/nonebot_plugin_parser/parsers/twitter.py +12 -6
  20. nonebot_plugin_parser-2.6.0/src/nonebot_plugin_parser/parsers/utils.py +9 -0
  21. {nonebot_plugin_parser-2.5.2 → nonebot_plugin_parser-2.6.0}/src/nonebot_plugin_parser/parsers/weibo/__init__.py +10 -5
  22. {nonebot_plugin_parser-2.5.2 → nonebot_plugin_parser-2.6.0}/src/nonebot_plugin_parser/parsers/weibo/common.py +13 -0
  23. {nonebot_plugin_parser-2.5.2 → nonebot_plugin_parser-2.6.0}/src/nonebot_plugin_parser/parsers/weibo/show.py +5 -0
  24. {nonebot_plugin_parser-2.5.2 → nonebot_plugin_parser-2.6.0}/src/nonebot_plugin_parser/parsers/xiaohongshu/__init__.py +15 -11
  25. nonebot_plugin_parser-2.6.0/src/nonebot_plugin_parser/parsers/xiaohongshu/common.py +38 -0
  26. {nonebot_plugin_parser-2.5.2 → nonebot_plugin_parser-2.6.0}/src/nonebot_plugin_parser/parsers/xiaohongshu/discovery.py +9 -4
  27. {nonebot_plugin_parser-2.5.2 → nonebot_plugin_parser-2.6.0}/src/nonebot_plugin_parser/parsers/xiaohongshu/explore.py +14 -4
  28. {nonebot_plugin_parser-2.5.2 → nonebot_plugin_parser-2.6.0}/src/nonebot_plugin_parser/parsers/youtube/__init__.py +2 -2
  29. nonebot_plugin_parser-2.6.0/src/nonebot_plugin_parser/renders/__init__.py +51 -0
  30. {nonebot_plugin_parser-2.5.2 → nonebot_plugin_parser-2.6.0}/src/nonebot_plugin_parser/renders/base.py +29 -21
  31. {nonebot_plugin_parser-2.5.2 → nonebot_plugin_parser-2.6.0}/src/nonebot_plugin_parser/renders/common.py +19 -13
  32. {nonebot_plugin_parser-2.5.2 → nonebot_plugin_parser-2.6.0}/src/nonebot_plugin_parser/renders/default.py +1 -1
  33. {nonebot_plugin_parser-2.5.2 → nonebot_plugin_parser-2.6.0}/src/nonebot_plugin_parser/renders/htmlrender.py +5 -5
  34. {nonebot_plugin_parser-2.5.2 → nonebot_plugin_parser-2.6.0}/src/nonebot_plugin_parser/renders/resources/__init__.py +7 -0
  35. nonebot_plugin_parser-2.6.0/src/nonebot_plugin_parser/renders/resources/fallback_pic/1.jpg +0 -0
  36. nonebot_plugin_parser-2.6.0/src/nonebot_plugin_parser/renders/resources/fallback_pic/2.jpg +0 -0
  37. nonebot_plugin_parser-2.6.0/src/nonebot_plugin_parser/renders/resources/fallback_pic/3.jpg +0 -0
  38. nonebot_plugin_parser-2.6.0/src/nonebot_plugin_parser/renders/resources/fallback_pic/4.jpg +0 -0
  39. nonebot_plugin_parser-2.6.0/src/nonebot_plugin_parser/renders/resources/fallback_pic/5.jpg +0 -0
  40. nonebot_plugin_parser-2.6.0/src/nonebot_plugin_parser/renders/resources/fallback_pic/6.jpg +0 -0
  41. nonebot_plugin_parser-2.6.0/src/nonebot_plugin_parser/renders/resources/fallback_pic/7.jpg +0 -0
  42. nonebot_plugin_parser-2.6.0/src/nonebot_plugin_parser/renders/resources/fallback_pic/8.jpg +0 -0
  43. nonebot_plugin_parser-2.6.0/src/nonebot_plugin_parser/renders/resources/fallback_pic/9.jpg +0 -0
  44. nonebot_plugin_parser-2.6.0/src/nonebot_plugin_parser/renders/templates/card.html.jinja2 +643 -0
  45. {nonebot_plugin_parser-2.5.2 → nonebot_plugin_parser-2.6.0}/src/nonebot_plugin_parser/utils.py +23 -17
  46. nonebot_plugin_parser-2.5.2/src/nonebot_plugin_parser/download/task.py +0 -92
  47. nonebot_plugin_parser-2.5.2/src/nonebot_plugin_parser/parsers/xiaohongshu/common.py +0 -33
  48. nonebot_plugin_parser-2.5.2/src/nonebot_plugin_parser/renders/__init__.py +0 -43
  49. nonebot_plugin_parser-2.5.2/src/nonebot_plugin_parser/renders/templates/card.html.jinja2 +0 -668
  50. {nonebot_plugin_parser-2.5.2 → nonebot_plugin_parser-2.6.0}/src/nonebot_plugin_parser/__init__.py +0 -0
  51. {nonebot_plugin_parser-2.5.2 → nonebot_plugin_parser-2.6.0}/src/nonebot_plugin_parser/constants.py +0 -0
  52. {nonebot_plugin_parser-2.5.2 → nonebot_plugin_parser-2.6.0}/src/nonebot_plugin_parser/download/ytdlp.py +0 -0
  53. {nonebot_plugin_parser-2.5.2 → nonebot_plugin_parser-2.6.0}/src/nonebot_plugin_parser/exception.py +0 -0
  54. {nonebot_plugin_parser-2.5.2 → nonebot_plugin_parser-2.6.0}/src/nonebot_plugin_parser/matchers/__init__.py +0 -0
  55. {nonebot_plugin_parser-2.5.2 → nonebot_plugin_parser-2.6.0}/src/nonebot_plugin_parser/matchers/filter.py +0 -0
  56. {nonebot_plugin_parser-2.5.2 → nonebot_plugin_parser-2.6.0}/src/nonebot_plugin_parser/matchers/rule.py +0 -0
  57. {nonebot_plugin_parser-2.5.2 → nonebot_plugin_parser-2.6.0}/src/nonebot_plugin_parser/parsers/acfun/video.py +0 -0
  58. {nonebot_plugin_parser-2.5.2 → nonebot_plugin_parser-2.6.0}/src/nonebot_plugin_parser/parsers/bilibili/article.py +0 -0
  59. {nonebot_plugin_parser-2.5.2 → nonebot_plugin_parser-2.6.0}/src/nonebot_plugin_parser/parsers/bilibili/common.py +0 -0
  60. {nonebot_plugin_parser-2.5.2 → nonebot_plugin_parser-2.6.0}/src/nonebot_plugin_parser/parsers/bilibili/dynamic.py +0 -0
  61. {nonebot_plugin_parser-2.5.2 → nonebot_plugin_parser-2.6.0}/src/nonebot_plugin_parser/parsers/bilibili/favlist.py +0 -0
  62. {nonebot_plugin_parser-2.5.2 → nonebot_plugin_parser-2.6.0}/src/nonebot_plugin_parser/parsers/bilibili/live.py +0 -0
  63. {nonebot_plugin_parser-2.5.2 → nonebot_plugin_parser-2.6.0}/src/nonebot_plugin_parser/parsers/bilibili/opus.py +0 -0
  64. {nonebot_plugin_parser-2.5.2 → nonebot_plugin_parser-2.6.0}/src/nonebot_plugin_parser/parsers/bilibili/video.py +0 -0
  65. {nonebot_plugin_parser-2.5.2 → nonebot_plugin_parser-2.6.0}/src/nonebot_plugin_parser/parsers/cookie.py +0 -0
  66. {nonebot_plugin_parser-2.5.2 → nonebot_plugin_parser-2.6.0}/src/nonebot_plugin_parser/parsers/douyin/slides.py +0 -0
  67. {nonebot_plugin_parser-2.5.2 → nonebot_plugin_parser-2.6.0}/src/nonebot_plugin_parser/parsers/kuaishou/states.py +0 -0
  68. {nonebot_plugin_parser-2.5.2 → nonebot_plugin_parser-2.6.0}/src/nonebot_plugin_parser/parsers/weibo/article.py +0 -0
  69. {nonebot_plugin_parser-2.5.2 → nonebot_plugin_parser-2.6.0}/src/nonebot_plugin_parser/parsers/youtube/meta.py +0 -0
  70. {nonebot_plugin_parser-2.5.2 → nonebot_plugin_parser-2.6.0}/src/nonebot_plugin_parser/renders/resources/HYSongYunLangHeiW.ttf +0 -0
  71. {nonebot_plugin_parser-2.5.2 → nonebot_plugin_parser-2.6.0}/src/nonebot_plugin_parser/renders/resources/avatar.png +0 -0
  72. {nonebot_plugin_parser-2.5.2 → nonebot_plugin_parser-2.6.0}/src/nonebot_plugin_parser/renders/resources/bilibili.png +0 -0
  73. {nonebot_plugin_parser-2.5.2 → nonebot_plugin_parser-2.6.0}/src/nonebot_plugin_parser/renders/resources/douyin.png +0 -0
  74. {nonebot_plugin_parser-2.5.2 → nonebot_plugin_parser-2.6.0}/src/nonebot_plugin_parser/renders/resources/kuaishou.png +0 -0
  75. {nonebot_plugin_parser-2.5.2 → nonebot_plugin_parser-2.6.0}/src/nonebot_plugin_parser/renders/resources/play.png +0 -0
  76. {nonebot_plugin_parser-2.5.2 → nonebot_plugin_parser-2.6.0}/src/nonebot_plugin_parser/renders/resources/tiktok.png +0 -0
  77. {nonebot_plugin_parser-2.5.2 → nonebot_plugin_parser-2.6.0}/src/nonebot_plugin_parser/renders/resources/twitter.png +0 -0
  78. {nonebot_plugin_parser-2.5.2 → nonebot_plugin_parser-2.6.0}/src/nonebot_plugin_parser/renders/resources/weibo.png +0 -0
  79. {nonebot_plugin_parser-2.5.2 → nonebot_plugin_parser-2.6.0}/src/nonebot_plugin_parser/renders/resources/xiaohongshu.png +0 -0
  80. {nonebot_plugin_parser-2.5.2 → nonebot_plugin_parser-2.6.0}/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.5.2
3
+ Version: 2.6.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
@@ -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.create_video_content(video_url, cover_url, duration)
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.create_image_contents(image_urls)
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
- contents=[video, *images],
407
+ video=video,
408
+ contents=[*images],
405
409
  timestamp=timestamp,
406
410
  url=f"https://example.com/video/{video_id}",
407
411
  )
@@ -426,7 +430,7 @@ author = self.create_author(
426
430
 
427
431
  ```python
428
432
  # 方式1:传入 URL,自动下载
429
- video = self.create_video_content(
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.create_video_content(
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.create_image_contents([
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.create_video_content(video_url, cover_url, duration)
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.create_image_contents(image_urls)
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
- contents=[video, *images],
353
+ video=video,
354
+ contents=[*images],
351
355
  timestamp=timestamp,
352
356
  url=f"https://example.com/video/{video_id}",
353
357
  )
@@ -372,7 +376,7 @@ author = self.create_author(
372
376
 
373
377
  ```python
374
378
  # 方式1:传入 URL,自动下载
375
- video = self.create_video_content(
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.create_video_content(
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.create_image_contents([
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.5.2"
3
+ version = "2.6.0"
4
4
  description = "NoneBot2 链接分享解析 Alconna 版, 现支持B站|抖音|快手|微博|小红书|YouTube|TikTok|Twitter|AcFun|NGA"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"
@@ -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.5.2"
121
+ current_version = "2.6.0"
122
122
  message = "release: bump vesion from {current_version} to {new_version}"
123
123
 
124
124
  [[tool.bumpversion.files]]
@@ -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
  """是否需要转发媒体内容"""
@@ -41,8 +41,7 @@ class StreamDownloader:
41
41
  task_id = progress.add_task(description=desc, total=total)
42
42
  yield partial(progress.update, task_id)
43
43
 
44
- @auto_task
45
- async def download_file(
44
+ async def _download_file(
46
45
  self,
47
46
  url: str,
48
47
  *,
@@ -87,6 +86,23 @@ class StreamDownloader:
87
86
 
88
87
  return file_path
89
88
 
89
+ @auto_task
90
+ async def download_file(
91
+ self,
92
+ url: str,
93
+ *,
94
+ file_name: str | None = None,
95
+ ext_headers: dict[str, str] | None = None,
96
+ chunk_size: int = 64 * 1024,
97
+ ) -> Path:
98
+ """download file by url with stream"""
99
+ return await self._download_file(
100
+ url,
101
+ file_name=file_name,
102
+ ext_headers=ext_headers,
103
+ chunk_size=chunk_size,
104
+ )
105
+
90
106
  @auto_task
91
107
  async def download_video(
92
108
  self,
@@ -99,7 +115,7 @@ class StreamDownloader:
99
115
  if video_name is None:
100
116
  video_name = generate_file_name(url, ".mp4")
101
117
 
102
- return await self.download_file(
118
+ return await self._download_file(
103
119
  url,
104
120
  file_name=video_name,
105
121
  ext_headers=ext_headers,
@@ -118,7 +134,7 @@ class StreamDownloader:
118
134
  if audio_name is None:
119
135
  audio_name = generate_file_name(url, ".mp3")
120
136
 
121
- return await self.download_file(
137
+ return await self._download_file(
122
138
  url,
123
139
  file_name=audio_name,
124
140
  ext_headers=ext_headers,
@@ -136,7 +152,7 @@ class StreamDownloader:
136
152
  if img_name is None:
137
153
  img_name = generate_file_name(url, ".jpg")
138
154
 
139
- return await self.download_file(
155
+ return await self._download_file(
140
156
  url,
141
157
  file_name=img_name,
142
158
  ext_headers=ext_headers,
@@ -153,25 +169,12 @@ class StreamDownloader:
153
169
  ) -> Path:
154
170
  """download video and audio file by url with stream and merge"""
155
171
  v_path, a_path = await asyncio.gather(
156
- self.download_video(v_url, ext_headers=ext_headers),
157
- self.download_audio(a_url, ext_headers=ext_headers),
172
+ self._download_file(v_url, ext_headers=ext_headers),
173
+ self._download_file(a_url, ext_headers=ext_headers),
158
174
  )
159
175
  await merge_av(v_path=v_path, a_path=a_path, output_path=output_path)
160
176
  return output_path
161
177
 
162
- async def download_imgs_without_raise(
163
- self,
164
- urls: list[str],
165
- *,
166
- ext_headers: dict[str, str] | None = None,
167
- ) -> list[Path]:
168
- """download image files by urls with stream, ignore errors"""
169
- paths_or_errs = await asyncio.gather(
170
- *[self.download_img(url, ext_headers=ext_headers) for url in urls],
171
- return_exceptions=True,
172
- )
173
- return [p for p in paths_or_errs if isinstance(p, Path)]
174
-
175
178
  @auto_task
176
179
  async def download_m3u8(
177
180
  self,
@@ -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
@@ -78,7 +78,11 @@ class UniHelper:
78
78
  return Voice(path=audio_path)
79
79
 
80
80
  @classmethod
81
- def video_seg(cls, video_path: Path) -> Video | File | Text:
81
+ def video_seg(
82
+ cls,
83
+ video_path: Path,
84
+ thumbnail: Path | None = None,
85
+ ) -> Video | File | Text:
82
86
  """视频 Seg"""
83
87
  # 检测文件大小
84
88
  file_size_byte_count = int(video_path.stat().st_size)
@@ -89,9 +93,15 @@ class UniHelper:
89
93
  return cls.file_seg(video_path, display_name=video_path.name)
90
94
  else:
91
95
  if pconfig.use_base64:
92
- return Video(raw=video_path.read_bytes())
96
+ video = Video(raw=video_path.read_bytes())
97
+ if thumbnail and thumbnail.stat().st_size > 0:
98
+ video.thumbnail = cls.img_seg(thumbnail.read_bytes())
99
+ return video
93
100
  else:
94
- return Video(path=video_path)
101
+ video = Video(path=video_path)
102
+ if thumbnail and thumbnail.stat().st_size > 0:
103
+ video.thumbnail = cls.img_seg(thumbnail)
104
+ return video
95
105
 
96
106
  @staticmethod
97
107
  def file_seg(
@@ -22,14 +22,12 @@ from .data import (
22
22
  AudioContent,
23
23
  ImageContent,
24
24
  VideoContent,
25
- DynamicContent,
26
25
  )
27
26
 
28
27
  __all__ = [
29
28
  "AudioContent",
30
29
  "Author",
31
30
  "BaseParser",
32
- "DynamicContent",
33
31
  "ImageContent",
34
32
  "ParseResult",
35
33
  "Platform",
@@ -40,7 +40,7 @@ class AcfunParser(BaseParser):
40
40
  video_name=f"acfun_{acid}.mp4",
41
41
  )
42
42
 
43
- video_content = self.create_video_content(
43
+ video_content = self.create_video(
44
44
  video_task,
45
45
  cover_url=video_info.coverUrl,
46
46
  )
@@ -7,6 +7,7 @@ from collections.abc import Callable, Coroutine
7
7
  from typing_extensions import Unpack, final
8
8
 
9
9
  from .data import Platform, ParseResult, ImageContent, ParseResultKwargs
10
+ from .task import PathTask
10
11
  from ..config import pconfig as pconfig
11
12
  from ..download import DOWNLOADER
12
13
  from ..constants import IOS_HEADER, COMMON_HEADER, ANDROID_HEADER, COMMON_TIMEOUT
@@ -15,7 +16,6 @@ from ..constants import PlatformEnum as PlatformEnum
15
16
  from ..exception import ParseException
16
17
  from ..exception import IgnoreException as IgnoreException
17
18
  from ..exception import DownloadException as DownloadException
18
- from ..download.task import PathTask, OptionalPathTask
19
19
 
20
20
  T = TypeVar("T", bound="BaseParser")
21
21
  HandlerFunc = Callable[[T, Match[str]], Coroutine[Any, Any, ParseResult]]
@@ -167,34 +167,45 @@ class BaseParser:
167
167
  """创建作者对象"""
168
168
  from .data import Author
169
169
 
170
- avatar_task = None
170
+ author = Author(name=name, description=description)
171
+
171
172
  if avatar_url:
172
- avatar_task = DOWNLOADER.download_img(avatar_url, ext_headers=self.headers)
173
- return Author(name=name, avatar=OptionalPathTask(avatar_task), description=description)
173
+ author.avatar = PathTask(DOWNLOADER.download_img(avatar_url, ext_headers=self.headers))
174
+
175
+ return author
174
176
 
175
- def create_video_content(
177
+ def create_video(
176
178
  self,
177
- url_or_task: str | Task[Path] | PathTask,
179
+ url_or_task: str | Task[Path],
178
180
  cover_url: str | None = None,
179
181
  duration: float | None = None,
180
182
  ):
181
183
  """创建视频内容"""
182
184
  from .data import VideoContent
183
-
184
- cover_task = None
185
- if cover_url:
186
- cover_task = DOWNLOADER.download_img(cover_url, ext_headers=self.headers)
185
+ from ..utils import extract_video_cover
187
186
 
188
187
  if isinstance(url_or_task, str):
189
188
  path_task = DOWNLOADER.download_video(url_or_task, ext_headers=self.headers)
190
189
  elif isinstance(url_or_task, Task):
191
- path_task = PathTask(url_or_task)
192
- elif isinstance(url_or_task, PathTask):
193
190
  path_task = url_or_task
194
191
 
195
- return VideoContent(path_task, OptionalPathTask(cover_task), duration)
192
+ if cover_url:
193
+ cover_task = DOWNLOADER.download_img(cover_url, ext_headers=self.headers)
194
+ else:
195
+ # 如果没有封面 URL,尝试从视频中提取封面
196
+ async def extract_cover():
197
+ video_path = await path_task
198
+ return await extract_video_cover(video_path)
199
+
200
+ cover_task = extract_cover()
201
+
202
+ return VideoContent(
203
+ PathTask(path_task),
204
+ cover=PathTask(cover_task),
205
+ duration=duration,
206
+ )
196
207
 
197
- def create_image_contents(
208
+ def create_images(
198
209
  self,
199
210
  image_urls: list[str],
200
211
  ):
@@ -202,56 +213,25 @@ class BaseParser:
202
213
  contents: list[ImageContent] = []
203
214
  for url in image_urls:
204
215
  task = DOWNLOADER.download_img(url, ext_headers=self.headers)
205
- contents.append(ImageContent(task))
216
+ contents.append(ImageContent(PathTask(task)))
206
217
  return contents
207
218
 
208
- def create_image_content(
219
+ def create_image(
209
220
  self,
210
- url_or_task: str | Task[Path] | PathTask,
221
+ url_or_task: str | Task[Path],
211
222
  alt: str | None = None,
212
223
  ):
213
- """创建图片内容"""
224
+ """创建单个图片内容"""
214
225
  if isinstance(url_or_task, str):
215
226
  path_task = DOWNLOADER.download_img(url_or_task, ext_headers=self.headers)
216
227
  elif isinstance(url_or_task, Task):
217
- path_task = PathTask(url_or_task)
218
- elif isinstance(url_or_task, PathTask):
219
- path_task = url_or_task
220
-
221
- return ImageContent(path_task, alt=alt)
222
-
223
- def create_dynamic_contents(
224
- self,
225
- dynamic_urls: list[str],
226
- ):
227
- """创建动态图片内容列表"""
228
- from .data import DynamicContent
229
-
230
- contents: list[DynamicContent] = []
231
- for url in dynamic_urls:
232
- task = DOWNLOADER.download_video(url, ext_headers=self.headers)
233
- contents.append(DynamicContent(task))
234
- return contents
235
-
236
- def create_dynamic_content(
237
- self,
238
- url_or_task: str | Task[Path] | PathTask,
239
- ):
240
- """创建动态图片内容"""
241
- from .data import DynamicContent
242
-
243
- if isinstance(url_or_task, str):
244
- path_task = DOWNLOADER.download_video(url_or_task, ext_headers=self.headers)
245
- elif isinstance(url_or_task, Task):
246
- path_task = PathTask(url_or_task)
247
- elif isinstance(url_or_task, PathTask):
248
228
  path_task = url_or_task
249
229
 
250
- return DynamicContent(path_task)
230
+ return ImageContent(PathTask(path_task), alt=alt)
251
231
 
252
- def create_audio_content(
232
+ def create_audio(
253
233
  self,
254
- url_or_task: str | Task[Path] | PathTask,
234
+ url_or_task: str | Task[Path],
255
235
  duration: float = 0.0,
256
236
  ):
257
237
  """创建音频内容"""
@@ -260,11 +240,9 @@ class BaseParser:
260
240
  if isinstance(url_or_task, str):
261
241
  path_task = DOWNLOADER.download_audio(url_or_task, ext_headers=self.headers)
262
242
  elif isinstance(url_or_task, Task):
263
- path_task = PathTask(url_or_task)
264
- elif isinstance(url_or_task, PathTask):
265
243
  path_task = url_or_task
266
244
 
267
- return AudioContent(path_task, duration)
245
+ return AudioContent(PathTask(path_task), duration)
268
246
 
269
247
  @property
270
248
  def downloader(self):
@@ -148,7 +148,7 @@ class BilibiliParser(BaseParser):
148
148
  )
149
149
  return path
150
150
 
151
- video_content = self.create_video_content(
151
+ video_content = self.create_video(
152
152
  asyncio.create_task(download_video()),
153
153
  page_info.cover,
154
154
  page_info.duration,
@@ -188,7 +188,7 @@ class BilibiliParser(BaseParser):
188
188
  # 下载图片
189
189
  author = self.create_author(dynamic_info.name, dynamic_info.avatar)
190
190
  contents: list[MediaContent] = []
191
- contents.extend(self.create_image_contents(dynamic_info.image_urls))
191
+ contents.extend(self.create_images(dynamic_info.image_urls))
192
192
 
193
193
  repost = None
194
194
  if dynamic_info.type == "DYNAMIC_TYPE_FORWARD" and dynamic_info.orig is not None:
@@ -234,7 +234,7 @@ class BilibiliParser(BaseParser):
234
234
  if isinstance(node, str):
235
235
  result.graphics.append(node)
236
236
  else:
237
- result.graphics.append(self.create_image_content(node.url, alt=node.alt))
237
+ result.graphics.append(self.create_image(node.url, alt=node.alt))
238
238
 
239
239
  return result
240
240
 
@@ -252,12 +252,12 @@ class BilibiliParser(BaseParser):
252
252
  # 下载封面
253
253
  if cover := room_data.cover:
254
254
  cover_task = self.downloader.download_img(cover, ext_headers=self.headers)
255
- contents.append(ImageContent(cover_task))
255
+ contents.append(self.create_image(cover_task))
256
256
 
257
257
  # 下载关键帧
258
258
  if keyframe := room_data.keyframe:
259
259
  keyframe_task = self.downloader.download_img(keyframe, ext_headers=self.headers)
260
- contents.append(ImageContent(keyframe_task))
260
+ contents.append(self.create_image(keyframe_task))
261
261
 
262
262
  author = self.create_author(room_data.name, room_data.avatar)
263
263
 
@@ -288,7 +288,7 @@ class BilibiliParser(BaseParser):
288
288
 
289
289
  graphics: list[str | ImageContent] = []
290
290
  for fav in favdata.medias:
291
- graphics.append(self.create_image_content(fav.cover, alt=fav.desc))
291
+ graphics.append(self.create_image(fav.cover, alt=fav.desc))
292
292
  graphics.append(fav.desc)
293
293
 
294
294
  return self.result(