nonebot-plugin-parser 2.6.0__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.6.0 → nonebot_plugin_parser-2.6.1}/PKG-INFO +7 -7
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.1}/README.md +3 -3
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.1}/pyproject.toml +6 -6
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/download/__init__.py +114 -58
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/helper.py +10 -5
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/matchers/__init__.py +3 -3
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/parsers/__init__.py +2 -2
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/parsers/base.py +10 -10
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/parsers/bilibili/__init__.py +1 -1
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/parsers/data.py +2 -0
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/parsers/kuaishou/__init__.py +5 -2
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/parsers/kuaishou/states.py +4 -0
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/parsers/tiktok.py +3 -3
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/parsers/youtube/__init__.py +3 -3
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/renders/__init__.py +2 -9
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/utils.py +7 -0
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/__init__.py +0 -0
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/config.py +0 -0
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/constants.py +0 -0
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/download/task.py +0 -0
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/download/ytdlp.py +0 -0
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/exception.py +0 -0
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/matchers/filter.py +0 -0
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/matchers/rule.py +0 -0
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/parsers/acfun/__init__.py +0 -0
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/parsers/acfun/video.py +0 -0
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/parsers/bilibili/article.py +0 -0
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/parsers/bilibili/common.py +0 -0
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/parsers/bilibili/dynamic.py +0 -0
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/parsers/bilibili/favlist.py +0 -0
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/parsers/bilibili/live.py +0 -0
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/parsers/bilibili/opus.py +0 -0
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/parsers/bilibili/video.py +0 -0
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/parsers/cookie.py +0 -0
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/parsers/douyin/__init__.py +0 -0
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/parsers/douyin/slides.py +0 -0
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/parsers/douyin/video.py +0 -0
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/parsers/nga.py +0 -0
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/parsers/task.py +0 -0
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/parsers/twitter.py +0 -0
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/parsers/utils.py +0 -0
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/parsers/weibo/__init__.py +0 -0
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/parsers/weibo/article.py +0 -0
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/parsers/weibo/common.py +0 -0
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/parsers/weibo/show.py +0 -0
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/parsers/xiaohongshu/__init__.py +0 -0
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/parsers/xiaohongshu/common.py +0 -0
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/parsers/xiaohongshu/discovery.py +0 -0
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/parsers/xiaohongshu/explore.py +0 -0
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/parsers/youtube/meta.py +0 -0
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/renders/base.py +0 -0
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/renders/common.py +0 -0
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/renders/default.py +0 -0
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/renders/htmlrender.py +0 -0
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/renders/resources/HYSongYunLangHeiW.ttf +0 -0
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/renders/resources/__init__.py +0 -0
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/renders/resources/avatar.png +0 -0
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/renders/resources/bilibili.png +0 -0
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/renders/resources/douyin.png +0 -0
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/renders/resources/fallback_pic/1.jpg +0 -0
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/renders/resources/fallback_pic/2.jpg +0 -0
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/renders/resources/fallback_pic/3.jpg +0 -0
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/renders/resources/fallback_pic/4.jpg +0 -0
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/renders/resources/fallback_pic/5.jpg +0 -0
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/renders/resources/fallback_pic/6.jpg +0 -0
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/renders/resources/fallback_pic/7.jpg +0 -0
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/renders/resources/fallback_pic/8.jpg +0 -0
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/renders/resources/fallback_pic/9.jpg +0 -0
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/renders/resources/kuaishou.png +0 -0
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/renders/resources/play.png +0 -0
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/renders/resources/tiktok.png +0 -0
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/renders/resources/twitter.png +0 -0
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/renders/resources/weibo.png +0 -0
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/renders/resources/xiaohongshu.png +0 -0
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/renders/resources/youtube.png +0 -0
- {nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/renders/templates/card.html.jinja2 +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: nonebot-plugin-parser
|
|
3
|
-
Version: 2.6.
|
|
3
|
+
Version: 2.6.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'
|
|
@@ -416,7 +416,7 @@ class ExampleParser(BaseParser):
|
|
|
416
416
|
<details>
|
|
417
417
|
<summary>辅助函数</summary>
|
|
418
418
|
|
|
419
|
-
>
|
|
419
|
+
> 构建作者
|
|
420
420
|
|
|
421
421
|
```python
|
|
422
422
|
author = self.create_author(
|
|
@@ -473,5 +473,5 @@ real_url = await self.get_redirect_url(
|
|
|
473
473
|
|
|
474
474
|
## 🎉 致谢
|
|
475
475
|
|
|
476
|
-
- [nonebot-plugin-resolver](https://github.com/zhiyu1998/nonebot-plugin-resolver)
|
|
477
|
-
- [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) - 在抖音解析功能实现方面提供了技术参考和借鉴。
|
|
@@ -362,7 +362,7 @@ class ExampleParser(BaseParser):
|
|
|
362
362
|
<details>
|
|
363
363
|
<summary>辅助函数</summary>
|
|
364
364
|
|
|
365
|
-
>
|
|
365
|
+
> 构建作者
|
|
366
366
|
|
|
367
367
|
```python
|
|
368
368
|
author = self.create_author(
|
|
@@ -419,5 +419,5 @@ real_url = await self.get_redirect_url(
|
|
|
419
419
|
|
|
420
420
|
## 🎉 致谢
|
|
421
421
|
|
|
422
|
-
- [nonebot-plugin-resolver](https://github.com/zhiyu1998/nonebot-plugin-resolver)
|
|
423
|
-
- [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.6.
|
|
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.6.
|
|
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 }
|
|
@@ -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()
|
{nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/helper.py
RENAMED
|
@@ -31,6 +31,14 @@ EMOJI_MAP = {
|
|
|
31
31
|
"resolving": ("424", "👀"),
|
|
32
32
|
"done": ("144", "🎉"),
|
|
33
33
|
}
|
|
34
|
+
"""emoji 映射"""
|
|
35
|
+
|
|
36
|
+
ID_ADAPTERS = {
|
|
37
|
+
SupportAdapter.onebot11,
|
|
38
|
+
SupportAdapter.qq,
|
|
39
|
+
SupportAdapter.milky,
|
|
40
|
+
}
|
|
41
|
+
"""支持的传入 emoji id 发送 reaction 的适配器"""
|
|
34
42
|
|
|
35
43
|
|
|
36
44
|
class UniHelper:
|
|
@@ -127,15 +135,12 @@ class UniHelper:
|
|
|
127
135
|
message_id = uniseg.get_message_id(event)
|
|
128
136
|
target = uniseg.get_target(event)
|
|
129
137
|
|
|
130
|
-
if target.adapter in
|
|
131
|
-
emoji = EMOJI_MAP[status][0]
|
|
132
|
-
else:
|
|
133
|
-
emoji = EMOJI_MAP[status][1]
|
|
138
|
+
emoji = EMOJI_MAP[status][0 if target.adapter in ID_ADAPTERS else 1]
|
|
134
139
|
|
|
135
140
|
try:
|
|
136
141
|
await uniseg.message_reaction(emoji, message_id=message_id)
|
|
137
142
|
except Exception:
|
|
138
|
-
logger.warning(f"reaction {emoji} to {message_id} failed, maybe not support")
|
|
143
|
+
logger.opt(exception=True).warning(f"reaction {emoji} to {message_id} failed, maybe not support")
|
|
139
144
|
|
|
140
145
|
@classmethod
|
|
141
146
|
def with_reaction(cls, func: Callable[..., Awaitable[Any]]):
|
|
@@ -112,9 +112,9 @@ async def _(message: Message = CommandArg()):
|
|
|
112
112
|
await UniMessage(UniHelper.file_seg(audio_path)).send()
|
|
113
113
|
|
|
114
114
|
|
|
115
|
-
from ..download import
|
|
115
|
+
from ..download import yt_dlp_downloader
|
|
116
116
|
|
|
117
|
-
if
|
|
117
|
+
if yt_dlp_downloader is not None:
|
|
118
118
|
from ..parsers import YouTubeParser
|
|
119
119
|
|
|
120
120
|
@on_command("ym", priority=3, block=True).handle()
|
|
@@ -128,7 +128,7 @@ if YTDLP_DOWNLOADER is not None:
|
|
|
128
128
|
|
|
129
129
|
url = matched.group(0)
|
|
130
130
|
|
|
131
|
-
audio_path = await
|
|
131
|
+
audio_path = await yt_dlp_downloader.download_audio(url)
|
|
132
132
|
await UniMessage(UniHelper.record_seg(audio_path)).send()
|
|
133
133
|
|
|
134
134
|
if pconfig.need_upload:
|
|
@@ -7,10 +7,10 @@ from .douyin import DouyinParser as DouyinParser
|
|
|
7
7
|
from .twitter import TwitterParser as TwitterParser
|
|
8
8
|
from .bilibili import BilibiliParser as BilibiliParser
|
|
9
9
|
from .kuaishou import KuaiShouParser as KuaiShouParser
|
|
10
|
-
from ..download import
|
|
10
|
+
from ..download import yt_dlp_downloader as yt_dlp_downloader
|
|
11
11
|
from .xiaohongshu import XiaoHongShuParser as XiaoHongShuParser
|
|
12
12
|
|
|
13
|
-
if
|
|
13
|
+
if yt_dlp_downloader is not None:
|
|
14
14
|
from .tiktok import TikTokParser as TikTokParser
|
|
15
15
|
from .youtube import YouTubeParser as YouTubeParser
|
|
16
16
|
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
from re import Match, Pattern, compile
|
|
2
2
|
from abc import ABC
|
|
3
|
-
from typing import TYPE_CHECKING, Any, TypeVar, ClassVar, cast
|
|
3
|
+
from typing import TYPE_CHECKING, Any, TypeVar, ClassVar, cast, final
|
|
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
|
|
8
8
|
|
|
9
9
|
from .data import Platform, ParseResult, ImageContent, ParseResultKwargs
|
|
10
10
|
from .task import PathTask
|
|
11
11
|
from ..config import pconfig as pconfig
|
|
12
|
-
from ..download import
|
|
12
|
+
from ..download import downloader
|
|
13
13
|
from ..constants import IOS_HEADER, COMMON_HEADER, ANDROID_HEADER, COMMON_TIMEOUT
|
|
14
14
|
from ..constants import DOWNLOAD_TIMEOUT as DOWNLOAD_TIMEOUT
|
|
15
15
|
from ..constants import PlatformEnum as PlatformEnum
|
|
@@ -170,7 +170,7 @@ class BaseParser:
|
|
|
170
170
|
author = Author(name=name, description=description)
|
|
171
171
|
|
|
172
172
|
if avatar_url:
|
|
173
|
-
author.avatar = PathTask(
|
|
173
|
+
author.avatar = PathTask(downloader.download_img(avatar_url, ext_headers=self.headers))
|
|
174
174
|
|
|
175
175
|
return author
|
|
176
176
|
|
|
@@ -185,12 +185,12 @@ class BaseParser:
|
|
|
185
185
|
from ..utils import extract_video_cover
|
|
186
186
|
|
|
187
187
|
if isinstance(url_or_task, str):
|
|
188
|
-
path_task =
|
|
188
|
+
path_task = downloader.download_video(url_or_task, ext_headers=self.headers)
|
|
189
189
|
elif isinstance(url_or_task, Task):
|
|
190
190
|
path_task = url_or_task
|
|
191
191
|
|
|
192
192
|
if cover_url:
|
|
193
|
-
cover_task =
|
|
193
|
+
cover_task = downloader.download_img(cover_url, ext_headers=self.headers)
|
|
194
194
|
else:
|
|
195
195
|
# 如果没有封面 URL,尝试从视频中提取封面
|
|
196
196
|
async def extract_cover():
|
|
@@ -212,7 +212,7 @@ class BaseParser:
|
|
|
212
212
|
"""创建图片内容列表"""
|
|
213
213
|
contents: list[ImageContent] = []
|
|
214
214
|
for url in image_urls:
|
|
215
|
-
task =
|
|
215
|
+
task = downloader.download_img(url, ext_headers=self.headers)
|
|
216
216
|
contents.append(ImageContent(PathTask(task)))
|
|
217
217
|
return contents
|
|
218
218
|
|
|
@@ -223,7 +223,7 @@ class BaseParser:
|
|
|
223
223
|
):
|
|
224
224
|
"""创建单个图片内容"""
|
|
225
225
|
if isinstance(url_or_task, str):
|
|
226
|
-
path_task =
|
|
226
|
+
path_task = downloader.download_img(url_or_task, ext_headers=self.headers)
|
|
227
227
|
elif isinstance(url_or_task, Task):
|
|
228
228
|
path_task = url_or_task
|
|
229
229
|
|
|
@@ -238,7 +238,7 @@ class BaseParser:
|
|
|
238
238
|
from .data import AudioContent
|
|
239
239
|
|
|
240
240
|
if isinstance(url_or_task, str):
|
|
241
|
-
path_task =
|
|
241
|
+
path_task = downloader.download_audio(url_or_task, ext_headers=self.headers)
|
|
242
242
|
elif isinstance(url_or_task, Task):
|
|
243
243
|
path_task = url_or_task
|
|
244
244
|
|
|
@@ -246,4 +246,4 @@ class BaseParser:
|
|
|
246
246
|
|
|
247
247
|
@property
|
|
248
248
|
def downloader(self):
|
|
249
|
-
return
|
|
249
|
+
return downloader
|
|
@@ -54,7 +54,6 @@ class KuaiShouParser(BaseParser):
|
|
|
54
54
|
# 构建作者
|
|
55
55
|
author = self.create_author(photo.name, photo.head_url)
|
|
56
56
|
|
|
57
|
-
# 先以部分数据构建结果,后续再填充内容,避免使用临时变量
|
|
58
57
|
result = self.result(
|
|
59
58
|
title=photo.caption,
|
|
60
59
|
author=author,
|
|
@@ -64,7 +63,11 @@ class KuaiShouParser(BaseParser):
|
|
|
64
63
|
|
|
65
64
|
# 添加视频内容
|
|
66
65
|
if video_url := photo.video_url:
|
|
67
|
-
result.video = self.create_video(
|
|
66
|
+
result.video = self.create_video(
|
|
67
|
+
video_url,
|
|
68
|
+
photo.cover_url,
|
|
69
|
+
photo.duration_in_seconds,
|
|
70
|
+
)
|
|
68
71
|
|
|
69
72
|
# 添加图片内容
|
|
70
73
|
if img_urls := photo.img_urls:
|
|
@@ -42,6 +42,10 @@ class Photo(Struct):
|
|
|
42
42
|
def name(self) -> str:
|
|
43
43
|
return self.user_name.replace("\u3164", "").strip()
|
|
44
44
|
|
|
45
|
+
@property
|
|
46
|
+
def duration_in_seconds(self):
|
|
47
|
+
return self.duration // 1000
|
|
48
|
+
|
|
45
49
|
@property
|
|
46
50
|
def cover_url(self):
|
|
47
51
|
return choice(self.cover_urls).url if len(self.cover_urls) != 0 else None
|
|
@@ -3,7 +3,7 @@ from typing import ClassVar
|
|
|
3
3
|
|
|
4
4
|
from .base import BaseParser, PlatformEnum, handle
|
|
5
5
|
from .data import Author, Platform
|
|
6
|
-
from ..download import
|
|
6
|
+
from ..download import yt_dlp_downloader
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
class TikTokParser(BaseParser):
|
|
@@ -18,10 +18,10 @@ class TikTokParser(BaseParser):
|
|
|
18
18
|
url = await self.get_redirect_url(url)
|
|
19
19
|
|
|
20
20
|
# 获取视频信息
|
|
21
|
-
video_info = await
|
|
21
|
+
video_info = await yt_dlp_downloader.extract_video_info(url)
|
|
22
22
|
|
|
23
23
|
# 下载封面和视频
|
|
24
|
-
video =
|
|
24
|
+
video = yt_dlp_downloader.download_video(url)
|
|
25
25
|
video_content = self.create_video(
|
|
26
26
|
video,
|
|
27
27
|
video_info.thumbnail,
|
|
@@ -5,7 +5,7 @@ from httpx import AsyncClient
|
|
|
5
5
|
|
|
6
6
|
from ..base import Platform, BaseParser, PlatformEnum, handle, pconfig
|
|
7
7
|
from ..cookie import save_cookies_with_netscape
|
|
8
|
-
from ...download import
|
|
8
|
+
from ...download import yt_dlp_downloader
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
class YouTubeParser(BaseParser):
|
|
@@ -28,7 +28,7 @@ class YouTubeParser(BaseParser):
|
|
|
28
28
|
return await self.parse_video(url)
|
|
29
29
|
|
|
30
30
|
async def parse_video(self, url: str):
|
|
31
|
-
video_info = await
|
|
31
|
+
video_info = await yt_dlp_downloader.extract_video_info(url, self.cookies_file)
|
|
32
32
|
author = await self._fetch_author_info(video_info.channel_id)
|
|
33
33
|
|
|
34
34
|
result = self.result(
|
|
@@ -38,7 +38,7 @@ class YouTubeParser(BaseParser):
|
|
|
38
38
|
)
|
|
39
39
|
|
|
40
40
|
if video_info.duration <= pconfig.duration_maximum:
|
|
41
|
-
video =
|
|
41
|
+
video = yt_dlp_downloader.download_video(url, self.cookies_file)
|
|
42
42
|
result.video = self.create_video(
|
|
43
43
|
video,
|
|
44
44
|
video_info.thumbnail,
|
|
@@ -6,19 +6,12 @@ from .base import BaseRenderer
|
|
|
6
6
|
from .common import CommonRenderer
|
|
7
7
|
from .default import DefaultRenderer
|
|
8
8
|
|
|
9
|
+
RENDERER: type[BaseRenderer] | None = None
|
|
9
10
|
|
|
10
|
-
|
|
11
|
-
"""检查模块是否可用"""
|
|
12
|
-
import importlib.util
|
|
13
|
-
|
|
14
|
-
return importlib.util.find_spec(module_name) is not None
|
|
15
|
-
|
|
16
|
-
|
|
11
|
+
from ..utils import is_module_available
|
|
17
12
|
from ..config import pconfig
|
|
18
13
|
from ..constants import RenderType
|
|
19
14
|
|
|
20
|
-
RENDERER: type[BaseRenderer] | None = None
|
|
21
|
-
|
|
22
15
|
match pconfig.render_type:
|
|
23
16
|
case RenderType.common:
|
|
24
17
|
RENDERER = CommonRenderer
|
{nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/utils.py
RENAMED
|
@@ -195,3 +195,10 @@ def write_json_to_data(data: dict[str, Any] | str, file_name: str):
|
|
|
195
195
|
with open(path, "w") as f:
|
|
196
196
|
json.dump(data, f, ensure_ascii=False, indent=4)
|
|
197
197
|
logger.success(f"数据写入 {path} 成功")
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def is_module_available(module_name: str) -> bool:
|
|
201
|
+
"""检查模块是否可用"""
|
|
202
|
+
import importlib.util
|
|
203
|
+
|
|
204
|
+
return importlib.util.find_spec(module_name) is not None
|
{nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/__init__.py
RENAMED
|
File without changes
|
{nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/config.py
RENAMED
|
File without changes
|
{nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/constants.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/exception.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{nonebot_plugin_parser-2.6.0 → nonebot_plugin_parser-2.6.1}/src/nonebot_plugin_parser/parsers/nga.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|