nonebot-plugin-parser 2.6.7__tar.gz → 2.6.8__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.7 → nonebot_plugin_parser-2.6.8}/PKG-INFO +1 -1
- nonebot_plugin_parser-2.6.8/pyproject.toml +224 -0
- nonebot_plugin_parser-2.6.7/pyproject.toml → nonebot_plugin_parser-2.6.8/pyproject.toml.orig +2 -2
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.8}/src/nonebot_plugin_parser/download/__init__.py +15 -37
- nonebot_plugin_parser-2.6.8/src/nonebot_plugin_parser/download/rich.py +25 -0
- nonebot_plugin_parser-2.6.8/src/nonebot_plugin_parser/parsers/douyin/__init__.py +118 -0
- nonebot_plugin_parser-2.6.8/src/nonebot_plugin_parser/parsers/douyin/aweme.py +82 -0
- nonebot_plugin_parser-2.6.7/src/nonebot_plugin_parser/parsers/douyin/__init__.py +0 -137
- nonebot_plugin_parser-2.6.7/src/nonebot_plugin_parser/parsers/douyin/slides.py +0 -63
- nonebot_plugin_parser-2.6.7/src/nonebot_plugin_parser/parsers/douyin/video.py +0 -103
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.8}/README.md +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.8}/src/nonebot_plugin_parser/__init__.py +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.8}/src/nonebot_plugin_parser/config.py +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.8}/src/nonebot_plugin_parser/constants.py +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.8}/src/nonebot_plugin_parser/download/task.py +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.8}/src/nonebot_plugin_parser/download/ytdlp.py +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.8}/src/nonebot_plugin_parser/exception.py +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.8}/src/nonebot_plugin_parser/helper.py +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.8}/src/nonebot_plugin_parser/matchers/__init__.py +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.8}/src/nonebot_plugin_parser/matchers/filter.py +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.8}/src/nonebot_plugin_parser/matchers/rule.py +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.8}/src/nonebot_plugin_parser/parsers/__init__.py +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.8}/src/nonebot_plugin_parser/parsers/acfun/__init__.py +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.8}/src/nonebot_plugin_parser/parsers/acfun/video.py +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.8}/src/nonebot_plugin_parser/parsers/base.py +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.8}/src/nonebot_plugin_parser/parsers/bilibili/__init__.py +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.8}/src/nonebot_plugin_parser/parsers/bilibili/article.py +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.8}/src/nonebot_plugin_parser/parsers/bilibili/common.py +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.8}/src/nonebot_plugin_parser/parsers/bilibili/dynamic.py +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.8}/src/nonebot_plugin_parser/parsers/bilibili/favlist.py +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.8}/src/nonebot_plugin_parser/parsers/bilibili/live.py +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.8}/src/nonebot_plugin_parser/parsers/bilibili/opus.py +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.8}/src/nonebot_plugin_parser/parsers/bilibili/video.py +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.8}/src/nonebot_plugin_parser/parsers/cookie.py +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.8}/src/nonebot_plugin_parser/parsers/data.py +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.8}/src/nonebot_plugin_parser/parsers/kuaishou/__init__.py +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.8}/src/nonebot_plugin_parser/parsers/kuaishou/states.py +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.8}/src/nonebot_plugin_parser/parsers/nga.py +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.8}/src/nonebot_plugin_parser/parsers/task.py +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.8}/src/nonebot_plugin_parser/parsers/tiktok.py +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.8}/src/nonebot_plugin_parser/parsers/twitter.py +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.8}/src/nonebot_plugin_parser/parsers/utils.py +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.8}/src/nonebot_plugin_parser/parsers/weibo/__init__.py +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.8}/src/nonebot_plugin_parser/parsers/weibo/article.py +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.8}/src/nonebot_plugin_parser/parsers/weibo/common.py +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.8}/src/nonebot_plugin_parser/parsers/weibo/show.py +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.8}/src/nonebot_plugin_parser/parsers/xiaohongshu/__init__.py +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.8}/src/nonebot_plugin_parser/parsers/xiaohongshu/common.py +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.8}/src/nonebot_plugin_parser/parsers/xiaohongshu/discovery.py +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.8}/src/nonebot_plugin_parser/parsers/xiaohongshu/explore.py +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.8}/src/nonebot_plugin_parser/parsers/youtube/__init__.py +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.8}/src/nonebot_plugin_parser/parsers/youtube/meta.py +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.8}/src/nonebot_plugin_parser/renders/__init__.py +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.8}/src/nonebot_plugin_parser/renders/base.py +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.8}/src/nonebot_plugin_parser/renders/common/__init__.py +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.8}/src/nonebot_plugin_parser/renders/common/assets.py +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.8}/src/nonebot_plugin_parser/renders/common/font.py +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.8}/src/nonebot_plugin_parser/renders/common/renderer.py +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.8}/src/nonebot_plugin_parser/renders/default.py +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.8}/src/nonebot_plugin_parser/renders/htmlrender.py +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.8}/src/nonebot_plugin_parser/renders/resources/HYSongYunLangHeiW.ttf +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.8}/src/nonebot_plugin_parser/renders/resources/__init__.py +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.8}/src/nonebot_plugin_parser/renders/resources/avatar.png +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.8}/src/nonebot_plugin_parser/renders/resources/bilibili.png +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.8}/src/nonebot_plugin_parser/renders/resources/douyin.png +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.8}/src/nonebot_plugin_parser/renders/resources/fallback_pic/1.jpg +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.8}/src/nonebot_plugin_parser/renders/resources/fallback_pic/2.jpg +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.8}/src/nonebot_plugin_parser/renders/resources/fallback_pic/3.jpg +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.8}/src/nonebot_plugin_parser/renders/resources/fallback_pic/4.jpg +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.8}/src/nonebot_plugin_parser/renders/resources/fallback_pic/5.jpg +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.8}/src/nonebot_plugin_parser/renders/resources/fallback_pic/6.jpg +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.8}/src/nonebot_plugin_parser/renders/resources/fallback_pic/7.jpg +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.8}/src/nonebot_plugin_parser/renders/resources/fallback_pic/8.jpg +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.8}/src/nonebot_plugin_parser/renders/resources/fallback_pic/9.jpg +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.8}/src/nonebot_plugin_parser/renders/resources/kuaishou.png +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.8}/src/nonebot_plugin_parser/renders/resources/play.png +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.8}/src/nonebot_plugin_parser/renders/resources/tiktok.png +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.8}/src/nonebot_plugin_parser/renders/resources/twitter.png +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.8}/src/nonebot_plugin_parser/renders/resources/weibo.png +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.8}/src/nonebot_plugin_parser/renders/resources/xiaohongshu.png +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.8}/src/nonebot_plugin_parser/renders/resources/youtube.png +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.8}/src/nonebot_plugin_parser/renders/templates/card.html.jinja2 +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.8}/src/nonebot_plugin_parser/utils.py +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.8
|
|
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
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "nonebot-plugin-parser"
|
|
3
|
+
version = "2.6.8"
|
|
4
|
+
description = "NoneBot2 链接分享解析 Alconna 版, 现支持B站|抖音|快手|微博|小红书|YouTube|TikTok|Twitter|AcFun|NGA"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.10"
|
|
7
|
+
keywords = [
|
|
8
|
+
"acfun",
|
|
9
|
+
"bilibili",
|
|
10
|
+
"douyin",
|
|
11
|
+
"kuaishou",
|
|
12
|
+
"nga",
|
|
13
|
+
"nonebot",
|
|
14
|
+
"nonebot2",
|
|
15
|
+
"tiktok",
|
|
16
|
+
"twitter",
|
|
17
|
+
"video",
|
|
18
|
+
"weibo",
|
|
19
|
+
"xiaohongshu",
|
|
20
|
+
"youtube",
|
|
21
|
+
]
|
|
22
|
+
classifiers = [
|
|
23
|
+
"Development Status :: 5 - Production/Stable",
|
|
24
|
+
"Framework :: AsyncIO",
|
|
25
|
+
"Operating System :: OS Independent",
|
|
26
|
+
"Programming Language :: Python :: 3",
|
|
27
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
28
|
+
"Programming Language :: Python :: 3.10",
|
|
29
|
+
"Programming Language :: Python :: 3.11",
|
|
30
|
+
"Programming Language :: Python :: 3.12",
|
|
31
|
+
"Programming Language :: Python :: 3.13",
|
|
32
|
+
"Programming Language :: Python :: 3.14",
|
|
33
|
+
"Topic :: Communications :: Chat",
|
|
34
|
+
"Topic :: Internet :: WWW/HTTP",
|
|
35
|
+
"Topic :: Multimedia :: Video",
|
|
36
|
+
]
|
|
37
|
+
dependencies = [
|
|
38
|
+
"rich>=13.0.0",
|
|
39
|
+
"pillow>=11.0.0",
|
|
40
|
+
"aiofiles>=25.1.0",
|
|
41
|
+
"httpx>=0.27.2,<1.0.0",
|
|
42
|
+
"msgspec>=0.20.0,<1.0.0",
|
|
43
|
+
"nonebot2>=2.5.0,<3.0.0",
|
|
44
|
+
"apilmoji[rich]>=0.3.1,<1.0.0",
|
|
45
|
+
"beautifulsoup4>=4.12.0,<5.0.0",
|
|
46
|
+
"curl_cffi>=0.13.0,<1.0.0,!=0.14.0",
|
|
47
|
+
"bilibili-api-python>=17.4.2,<18.0.0",
|
|
48
|
+
"nonebot-plugin-uninfo>=0.10.1,<1.0.0",
|
|
49
|
+
"nonebot-plugin-alconna>=0.60.4,<1.0.0",
|
|
50
|
+
"nonebot-plugin-localstore>=0.7.4,<1.0.0",
|
|
51
|
+
"nonebot-plugin-apscheduler>=0.5.0,<1.0.0",
|
|
52
|
+
]
|
|
53
|
+
|
|
54
|
+
[[project.authors]]
|
|
55
|
+
name = "fllesser"
|
|
56
|
+
email = "fllessive@gmail.com"
|
|
57
|
+
|
|
58
|
+
[project.urls]
|
|
59
|
+
IssueTracker = "https://github.com/fllesser/nonebot-plugin-parser/issues"
|
|
60
|
+
Release = "https://github.com/fllesser/nonebot-plugin-parser/releases"
|
|
61
|
+
Repository = "https://github.com/fllesser/nonebot-plugin-parser"
|
|
62
|
+
|
|
63
|
+
[project.optional-dependencies]
|
|
64
|
+
htmlkit = ["nonebot-plugin-htmlkit>=0.1.0rc4"]
|
|
65
|
+
htmlrender = ["nonebot-plugin-htmlrender>=0.6.7"]
|
|
66
|
+
ytdlp = ["yt-dlp[default]>=2025.2.21"]
|
|
67
|
+
emosvg = ["emosvg>=0.1.7"]
|
|
68
|
+
all = [
|
|
69
|
+
"nonebot-plugin-htmlkit>=0.1.0rc4",
|
|
70
|
+
"nonebot-plugin-htmlrender>=0.6.7",
|
|
71
|
+
"yt-dlp[default]>=2026.3.13",
|
|
72
|
+
"emosvg>=0.1.7",
|
|
73
|
+
]
|
|
74
|
+
|
|
75
|
+
[dependency-groups]
|
|
76
|
+
dev = [
|
|
77
|
+
"nonebot2[fastapi]>=2.5.0,<3.0.0",
|
|
78
|
+
"nonebot-adapter-onebot>=2.4.6",
|
|
79
|
+
"ruff>=0.15.6,<1.0.0",
|
|
80
|
+
{ include-group = "extras" },
|
|
81
|
+
{ include-group = "test" },
|
|
82
|
+
]
|
|
83
|
+
extras = [
|
|
84
|
+
"emosvg>=0.1.7",
|
|
85
|
+
"nonebot-plugin-htmlkit>=0.1.0rc4",
|
|
86
|
+
"nonebot-plugin-htmlrender>=0.6.7",
|
|
87
|
+
"yt-dlp[default]>=2026.3.13",
|
|
88
|
+
"types-yt-dlp>=2026.3.13.20260314",
|
|
89
|
+
]
|
|
90
|
+
test = [
|
|
91
|
+
"nonebug>=0.4.4,<1.0.0",
|
|
92
|
+
"poethepoet>=0.42.1,<1.0.0",
|
|
93
|
+
"pytest-asyncio>=1.3.0,<1.4.0",
|
|
94
|
+
"pytest-cov>=7.0.0",
|
|
95
|
+
"pytest-xdist>=3.8.0,<4.0.0",
|
|
96
|
+
"respx>=0.22.0",
|
|
97
|
+
]
|
|
98
|
+
pydantic-v1 = ["pydantic<2.0.0"]
|
|
99
|
+
pydantic-v2 = ["pydantic>=2.0.0"]
|
|
100
|
+
telegram = ["nonebot-adapter-telegram>=0.1.0b20"]
|
|
101
|
+
|
|
102
|
+
[build-system]
|
|
103
|
+
requires = ["uv_build>=0.11.0,<0.12.0"]
|
|
104
|
+
build-backend = "uv_build"
|
|
105
|
+
|
|
106
|
+
[tool.uv]
|
|
107
|
+
conflicts = [[
|
|
108
|
+
{ group = "pydantic-v1" },
|
|
109
|
+
{ group = "pydantic-v2" },
|
|
110
|
+
{ group = "telegram" },
|
|
111
|
+
]]
|
|
112
|
+
|
|
113
|
+
[tool.bumpversion]
|
|
114
|
+
tag = true
|
|
115
|
+
commit = true
|
|
116
|
+
current_version = "2.6.8"
|
|
117
|
+
message = "release: bump vesion from {current_version} to {new_version}"
|
|
118
|
+
|
|
119
|
+
[[tool.bumpversion.files]]
|
|
120
|
+
filename = "uv.lock"
|
|
121
|
+
search = """
|
|
122
|
+
name = "nonebot-plugin-parser"
|
|
123
|
+
version = "{current_version}""""
|
|
124
|
+
replace = """
|
|
125
|
+
name = "nonebot-plugin-parser"
|
|
126
|
+
version = "{new_version}""""
|
|
127
|
+
|
|
128
|
+
[tool.coverage.report]
|
|
129
|
+
show_missing = true
|
|
130
|
+
exclude_lines = [
|
|
131
|
+
"raise NotImplementedError",
|
|
132
|
+
"if TYPE_CHECKING:",
|
|
133
|
+
"@overload",
|
|
134
|
+
"except",
|
|
135
|
+
"raise",
|
|
136
|
+
]
|
|
137
|
+
|
|
138
|
+
[tool.nonebot]
|
|
139
|
+
plugins = ["nonebot_plugin_parser"]
|
|
140
|
+
|
|
141
|
+
[[tool.nonebot.adapters]]
|
|
142
|
+
name = "Onebot V11"
|
|
143
|
+
module_name = "nonebot.adapters.onebot.v11"
|
|
144
|
+
|
|
145
|
+
[tool.poe.tasks]
|
|
146
|
+
test-other = "pytest tests/others --cov=src --cov-report=xml --junitxml=junit.xml -n auto"
|
|
147
|
+
test-parser = "pytest tests/parsers --cov=src --cov-report=xml --junitxml=junit.xml -n auto"
|
|
148
|
+
test-render = "pytest tests/renders --cov=src --cov-report=xml --junitxml=junit.xml"
|
|
149
|
+
bump = "bump-my-version bump"
|
|
150
|
+
show-bump = "bump-my-version show-bump"
|
|
151
|
+
|
|
152
|
+
[tool.basedpyright]
|
|
153
|
+
pythonVersion = "3.10"
|
|
154
|
+
pythonPlatform = "All"
|
|
155
|
+
typeCheckingMode = "standard"
|
|
156
|
+
disableBytesTypePromotions = true
|
|
157
|
+
|
|
158
|
+
[tool.basedpyright.defineConstant]
|
|
159
|
+
PYDANTIC_V2 = true
|
|
160
|
+
|
|
161
|
+
[[tool.basedpyright.executionEnvironments]]
|
|
162
|
+
root = "./tests"
|
|
163
|
+
extraPaths = ["./src"]
|
|
164
|
+
|
|
165
|
+
[[tool.basedpyright.executionEnvironments]]
|
|
166
|
+
root = "./src"
|
|
167
|
+
|
|
168
|
+
[tool.pytest]
|
|
169
|
+
addopts = [
|
|
170
|
+
"--import-mode=prepend",
|
|
171
|
+
"--strict-markers",
|
|
172
|
+
"--tb=short",
|
|
173
|
+
"-ra",
|
|
174
|
+
"-s",
|
|
175
|
+
"-v",
|
|
176
|
+
]
|
|
177
|
+
pythonpath = ["src"]
|
|
178
|
+
asyncio_mode = "auto"
|
|
179
|
+
asyncio_default_fixture_loop_scope = "session"
|
|
180
|
+
|
|
181
|
+
[tool.ruff]
|
|
182
|
+
line-length = 120
|
|
183
|
+
|
|
184
|
+
[tool.ruff.format]
|
|
185
|
+
line-ending = "lf"
|
|
186
|
+
|
|
187
|
+
[tool.ruff.lint]
|
|
188
|
+
select = [
|
|
189
|
+
"F",
|
|
190
|
+
"W",
|
|
191
|
+
"E",
|
|
192
|
+
"I",
|
|
193
|
+
"UP",
|
|
194
|
+
"Q",
|
|
195
|
+
"PYI",
|
|
196
|
+
"T10",
|
|
197
|
+
"T20",
|
|
198
|
+
"C4",
|
|
199
|
+
"PT",
|
|
200
|
+
"TID",
|
|
201
|
+
"ASYNC",
|
|
202
|
+
"RUF",
|
|
203
|
+
]
|
|
204
|
+
ignore = [
|
|
205
|
+
"E402",
|
|
206
|
+
"UP037",
|
|
207
|
+
"RUF001",
|
|
208
|
+
"RUF002",
|
|
209
|
+
"RUF003",
|
|
210
|
+
"W191",
|
|
211
|
+
"TID252",
|
|
212
|
+
"ASYNC240",
|
|
213
|
+
]
|
|
214
|
+
|
|
215
|
+
[tool.ruff.lint.isort]
|
|
216
|
+
length-sort = true
|
|
217
|
+
known-first-party = [
|
|
218
|
+
"nonebot_plugin_parser",
|
|
219
|
+
"tests/*",
|
|
220
|
+
]
|
|
221
|
+
extra-standard-library = ["typing_extensions"]
|
|
222
|
+
|
|
223
|
+
[tool.ruff.lint.pyupgrade]
|
|
224
|
+
keep-runtime-typing = true
|
nonebot_plugin_parser-2.6.7/pyproject.toml → nonebot_plugin_parser-2.6.8/pyproject.toml.orig
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "nonebot-plugin-parser"
|
|
3
|
-
version = "2.6.
|
|
3
|
+
version = "2.6.8"
|
|
4
4
|
description = "NoneBot2 链接分享解析 Alconna 版, 现支持B站|抖音|快手|微博|小红书|YouTube|TikTok|Twitter|AcFun|NGA"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.10"
|
|
@@ -115,7 +115,7 @@ conflicts = [
|
|
|
115
115
|
[tool.bumpversion]
|
|
116
116
|
tag = true
|
|
117
117
|
commit = true
|
|
118
|
-
current_version = "2.6.
|
|
118
|
+
current_version = "2.6.8"
|
|
119
119
|
message = "release: bump vesion from {current_version} to {new_version}"
|
|
120
120
|
|
|
121
121
|
[[tool.bumpversion.files]]
|
|
@@ -1,20 +1,13 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
from pathlib import Path
|
|
3
|
-
from functools import partial
|
|
4
|
-
from contextlib import contextmanager
|
|
5
3
|
from urllib.parse import urljoin
|
|
6
4
|
|
|
7
5
|
import httpx
|
|
8
6
|
import aiofiles
|
|
9
7
|
import curl_cffi
|
|
10
8
|
from nonebot import logger, get_driver
|
|
11
|
-
from rich.progress import (
|
|
12
|
-
Progress,
|
|
13
|
-
BarColumn,
|
|
14
|
-
TextColumn,
|
|
15
|
-
DownloadColumn,
|
|
16
|
-
)
|
|
17
9
|
|
|
10
|
+
from .rich import progress_bar, add_progress_task
|
|
18
11
|
from .task import auto_task
|
|
19
12
|
from ..utils import merge_av, safe_unlink, generate_file_name, is_module_available
|
|
20
13
|
from ..config import pconfig
|
|
@@ -31,22 +24,6 @@ class StreamDownloader:
|
|
|
31
24
|
async def aclose(self):
|
|
32
25
|
await self.client.aclose()
|
|
33
26
|
|
|
34
|
-
@staticmethod
|
|
35
|
-
@contextmanager
|
|
36
|
-
def rich_progress(
|
|
37
|
-
desc: str,
|
|
38
|
-
total: int | None = None,
|
|
39
|
-
):
|
|
40
|
-
with Progress(
|
|
41
|
-
TextColumn("[bold blue]{task.description}", justify="right"),
|
|
42
|
-
BarColumn(bar_width=None),
|
|
43
|
-
"[progress.percentage]{task.percentage:>3.1f}%",
|
|
44
|
-
"•",
|
|
45
|
-
DownloadColumn(),
|
|
46
|
-
) as progress:
|
|
47
|
-
task_id = progress.add_task(description=desc, total=total)
|
|
48
|
-
yield partial(progress.update, task_id)
|
|
49
|
-
|
|
50
27
|
@staticmethod
|
|
51
28
|
def _validate_content_length(
|
|
52
29
|
response: httpx.Response | curl_cffi.Response,
|
|
@@ -84,10 +61,11 @@ class StreamDownloader:
|
|
|
84
61
|
response.raise_for_status()
|
|
85
62
|
content_length = self._validate_content_length(response)
|
|
86
63
|
|
|
87
|
-
with
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
64
|
+
with progress_bar:
|
|
65
|
+
update_progress = add_progress_task(
|
|
66
|
+
f"httpx | {file_path.name}",
|
|
67
|
+
content_length,
|
|
68
|
+
)
|
|
91
69
|
async with aiofiles.open(file_path, "wb") as file:
|
|
92
70
|
async for chunk in response.aiter_bytes(chunk_size):
|
|
93
71
|
await file.write(chunk)
|
|
@@ -112,10 +90,11 @@ class StreamDownloader:
|
|
|
112
90
|
response.raise_for_status()
|
|
113
91
|
content_length = self._validate_content_length(response)
|
|
114
92
|
|
|
115
|
-
with
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
93
|
+
with progress_bar:
|
|
94
|
+
update_progress = add_progress_task(
|
|
95
|
+
f"curl_cffi | {file_path.name}",
|
|
96
|
+
content_length,
|
|
97
|
+
)
|
|
119
98
|
async with aiofiles.open(file_path, "wb") as file:
|
|
120
99
|
async for chunk in response.aiter_content(chunk_size=8192):
|
|
121
100
|
await file.write(chunk)
|
|
@@ -241,15 +220,14 @@ class StreamDownloader:
|
|
|
241
220
|
video_path = pconfig.cache_dir / video_name
|
|
242
221
|
|
|
243
222
|
try:
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
223
|
+
with progress_bar:
|
|
224
|
+
async with aiofiles.open(video_path, "wb") as f:
|
|
225
|
+
update_progress = add_progress_task(desc=video_name)
|
|
247
226
|
for url in await self._get_m3u8_slices(m3u8_url):
|
|
248
227
|
async with self.client.stream("GET", url, headers=ext_headers) as response:
|
|
249
228
|
async for chunk in response.aiter_bytes(chunk_size=1024 * 1024):
|
|
250
229
|
await f.write(chunk)
|
|
251
|
-
|
|
252
|
-
update_progress(advance=len(chunk), total=total_size)
|
|
230
|
+
update_progress(advance=len(chunk))
|
|
253
231
|
except httpx.HTTPError:
|
|
254
232
|
await safe_unlink(video_path)
|
|
255
233
|
logger.exception("m3u8 视频下载失败")
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from functools import partial
|
|
2
|
+
|
|
3
|
+
from rich.progress import (
|
|
4
|
+
Progress,
|
|
5
|
+
BarColumn,
|
|
6
|
+
TextColumn,
|
|
7
|
+
DownloadColumn,
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
progress_bar: Progress = Progress(
|
|
11
|
+
TextColumn("[bold blue]{task.description}", justify="right"),
|
|
12
|
+
BarColumn(bar_width=None),
|
|
13
|
+
"[progress.percentage]{task.percentage:>3.1f}%",
|
|
14
|
+
"•",
|
|
15
|
+
DownloadColumn(),
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def add_progress_task(
|
|
20
|
+
desc: str,
|
|
21
|
+
total: int | None = None,
|
|
22
|
+
):
|
|
23
|
+
task_id = progress_bar.add_task(description=desc, total=total)
|
|
24
|
+
progress_bar.start_task(task_id)
|
|
25
|
+
return partial(progress_bar.update, task_id)
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import re
|
|
2
|
+
import json
|
|
3
|
+
from typing import ClassVar
|
|
4
|
+
|
|
5
|
+
from httpx import AsyncClient
|
|
6
|
+
|
|
7
|
+
from ..base import (
|
|
8
|
+
COMMON_TIMEOUT,
|
|
9
|
+
Platform,
|
|
10
|
+
BaseParser,
|
|
11
|
+
PlatformEnum,
|
|
12
|
+
ParseException,
|
|
13
|
+
handle,
|
|
14
|
+
)
|
|
15
|
+
from .aweme import decoder
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class DouyinParser(BaseParser):
|
|
19
|
+
platform: ClassVar[Platform] = Platform(name=PlatformEnum.DOUYIN, display_name="抖音")
|
|
20
|
+
|
|
21
|
+
def __init__(self):
|
|
22
|
+
super().__init__()
|
|
23
|
+
self.headers.update(
|
|
24
|
+
{
|
|
25
|
+
"Origin": "https://open.douyin.com",
|
|
26
|
+
"Referer": "https://open.douyin.com/",
|
|
27
|
+
}
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
# https://v.douyin.com/_2ljF4AmKL8
|
|
31
|
+
@handle("v.douyin", r"v\.douyin\.com/[a-zA-Z0-9_\-]+")
|
|
32
|
+
@handle("jx.douyin", r"jx\.douyin\.com/[a-zA-Z0-9_\-]+")
|
|
33
|
+
async def _parse_short_link(self, searched: re.Match[str]):
|
|
34
|
+
url = f"https://{searched.group(0)}"
|
|
35
|
+
return await self.parse_with_redirect(url)
|
|
36
|
+
|
|
37
|
+
# https://www.douyin.com/video/7521023890996514083
|
|
38
|
+
# https://www.douyin.com/note/7469411074119322899
|
|
39
|
+
@handle("douyin", r"douyin\.com/[a-z]+/(?P<aweme_id>\d+)")
|
|
40
|
+
@handle("iesdouyin", r"iesdouyin\.com/share/[a-z]+/(?P<aweme_id>\d+)")
|
|
41
|
+
@handle("m.douyin", r"m\.douyin\.com/share/[a-z]+/(?P<aweme_id>\d+)")
|
|
42
|
+
# https://jingxuan.douyin.com/m/video/7574300896016862490?app=yumme&utm_source=copy_link
|
|
43
|
+
@handle("jingxuan.douyin", r"jingxuan\.douyin.com/m/[a-z]+/(?P<aweme_id>\d+)")
|
|
44
|
+
async def _parse_douyin(self, searched: re.Match[str]):
|
|
45
|
+
aweme_id = searched.group("aweme_id")
|
|
46
|
+
return await self.parse_aweme(aweme_id)
|
|
47
|
+
|
|
48
|
+
async def parse_aweme(self, aweme_id: str):
|
|
49
|
+
async with AsyncClient(
|
|
50
|
+
headers=self.headers,
|
|
51
|
+
timeout=COMMON_TIMEOUT,
|
|
52
|
+
follow_redirects=True,
|
|
53
|
+
verify=False,
|
|
54
|
+
) as client:
|
|
55
|
+
response = await client.get(
|
|
56
|
+
"https://www.douyin.com/aweme/v1/web/aweme/detail/",
|
|
57
|
+
params={"aweme_id": aweme_id, "aid": "6383"},
|
|
58
|
+
)
|
|
59
|
+
if response.status_code != 200:
|
|
60
|
+
raise ParseException(f"status: {response.status_code}")
|
|
61
|
+
aweme = decoder.decode(response.content).aweme_detail
|
|
62
|
+
|
|
63
|
+
# 作者
|
|
64
|
+
author = self.create_author(
|
|
65
|
+
aweme.author.nickname,
|
|
66
|
+
aweme.author.avatar_thumb.url_list[-1],
|
|
67
|
+
aweme.author.signature,
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
# 先以部分数据构建结果,后续再填充内容,避免使用临时变量
|
|
71
|
+
result = self.result(
|
|
72
|
+
text=aweme.share_info.text,
|
|
73
|
+
author=author,
|
|
74
|
+
timestamp=aweme.create_time,
|
|
75
|
+
url=aweme.share_url.split("?")[0],
|
|
76
|
+
)
|
|
77
|
+
if music := aweme.music:
|
|
78
|
+
if not music.is_original_sound:
|
|
79
|
+
if music.play_url.uri == "":
|
|
80
|
+
extra = json.loads(music.extra)
|
|
81
|
+
music_url = extra.get("original_song_url")
|
|
82
|
+
else:
|
|
83
|
+
music_url = music.play_url.uri
|
|
84
|
+
if music_url:
|
|
85
|
+
result.contents.append(
|
|
86
|
+
self.create_audio(
|
|
87
|
+
url_or_task=music_url,
|
|
88
|
+
duration=music.duration,
|
|
89
|
+
)
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
# 添加图片内容
|
|
93
|
+
if images := aweme.images:
|
|
94
|
+
for image in images:
|
|
95
|
+
if image.clip_type == 2 or image.clip_type is None:
|
|
96
|
+
result.contents.append(
|
|
97
|
+
self.create_image(
|
|
98
|
+
url_or_task=image.url_list[-1],
|
|
99
|
+
)
|
|
100
|
+
)
|
|
101
|
+
elif image_video := image.video:
|
|
102
|
+
result.contents.extend(
|
|
103
|
+
[
|
|
104
|
+
self.create_image(
|
|
105
|
+
url_or_task=image_video.cover.url_list[-1],
|
|
106
|
+
),
|
|
107
|
+
self.create_video(url_or_task=image_video.play_addr.url),
|
|
108
|
+
]
|
|
109
|
+
)
|
|
110
|
+
# 添加视频内容
|
|
111
|
+
elif video := aweme.video:
|
|
112
|
+
result.video = self.create_video(
|
|
113
|
+
video.play_addr.url,
|
|
114
|
+
video.cover_original_scale.url_list[-1] if video.cover_original_scale else video.cover.url_list[-1],
|
|
115
|
+
video.duration // 1000,
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
return result
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
from msgspec import Struct, field
|
|
2
|
+
from msgspec.json import Decoder
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class Addr(Struct):
|
|
6
|
+
uri: str
|
|
7
|
+
|
|
8
|
+
@property
|
|
9
|
+
def url(self) -> str:
|
|
10
|
+
return f"https://aweme.snssdk.com/aweme/v1/play/?video_id={self.uri}&ratio=1080p&line=0"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class UrlList(Struct):
|
|
14
|
+
url_list: list[str]
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class Author(Struct):
|
|
18
|
+
uid: str
|
|
19
|
+
nickname: str
|
|
20
|
+
avatar_thumb: UrlList
|
|
21
|
+
"""头像"""
|
|
22
|
+
signature: str | None = None
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class Video(Struct):
|
|
26
|
+
cover: UrlList
|
|
27
|
+
duration: int
|
|
28
|
+
"""视频时长(/1000)"""
|
|
29
|
+
play_addr: Addr
|
|
30
|
+
cover_original_scale: UrlList | None = None
|
|
31
|
+
|
|
32
|
+
@property
|
|
33
|
+
def url(self) -> str:
|
|
34
|
+
return self.play_addr.url
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class Image(Struct):
|
|
38
|
+
url_list: list[str]
|
|
39
|
+
uri: str = field(default="")
|
|
40
|
+
clip_type: int | None = field(default=None)
|
|
41
|
+
"""=2 or None 是普通图片"""
|
|
42
|
+
video: Video | None = field(default=None)
|
|
43
|
+
"""Live Photo 视频"""
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class MusicPlayUrl(Struct):
|
|
47
|
+
uri: str
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class Music(Struct):
|
|
51
|
+
duration: int
|
|
52
|
+
mid: str
|
|
53
|
+
play_url: MusicPlayUrl
|
|
54
|
+
extra: str
|
|
55
|
+
is_original_sound: bool
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class ShareInfo(Struct):
|
|
59
|
+
share_desc: str
|
|
60
|
+
share_desc_info: str
|
|
61
|
+
|
|
62
|
+
@property
|
|
63
|
+
def text(self) -> str:
|
|
64
|
+
return self.share_desc_info.replace(f"#{self.share_desc}#", "", 1)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class Aweme(Struct):
|
|
68
|
+
aweme_id: str
|
|
69
|
+
author: Author
|
|
70
|
+
share_info: ShareInfo
|
|
71
|
+
create_time: int
|
|
72
|
+
share_url: str
|
|
73
|
+
images: list[Image] | None = field(default=None)
|
|
74
|
+
music: Music | None = field(default=None)
|
|
75
|
+
video: Video | None = field(default=None)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class Response(Struct):
|
|
79
|
+
aweme_detail: Aweme
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
decoder = Decoder(Response)
|
|
@@ -1,137 +0,0 @@
|
|
|
1
|
-
import re
|
|
2
|
-
from typing import ClassVar
|
|
3
|
-
|
|
4
|
-
from httpx import AsyncClient
|
|
5
|
-
from nonebot import logger
|
|
6
|
-
|
|
7
|
-
from ..base import (
|
|
8
|
-
COMMON_TIMEOUT,
|
|
9
|
-
Platform,
|
|
10
|
-
BaseParser,
|
|
11
|
-
PlatformEnum,
|
|
12
|
-
ParseException,
|
|
13
|
-
handle,
|
|
14
|
-
)
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
class DouyinParser(BaseParser):
|
|
18
|
-
platform: ClassVar[Platform] = Platform(name=PlatformEnum.DOUYIN, display_name="抖音")
|
|
19
|
-
|
|
20
|
-
# https://v.douyin.com/_2ljF4AmKL8
|
|
21
|
-
@handle("v.douyin", r"v\.douyin\.com/[a-zA-Z0-9_\-]+")
|
|
22
|
-
@handle("jx.douyin", r"jx\.douyin\.com/[a-zA-Z0-9_\-]+")
|
|
23
|
-
async def _parse_short_link(self, searched: re.Match[str]):
|
|
24
|
-
url = f"https://{searched.group(0)}"
|
|
25
|
-
return await self.parse_with_redirect(url)
|
|
26
|
-
|
|
27
|
-
# https://www.douyin.com/video/7521023890996514083
|
|
28
|
-
# https://www.douyin.com/note/7469411074119322899
|
|
29
|
-
@handle("douyin", r"douyin\.com/(?P<ty>video|note)/(?P<vid>\d+)")
|
|
30
|
-
@handle("iesdouyin", r"iesdouyin\.com/share/(?P<ty>slides|video|note)/(?P<vid>\d+)")
|
|
31
|
-
@handle("m.douyin", r"m\.douyin\.com/share/(?P<ty>slides|video|note)/(?P<vid>\d+)")
|
|
32
|
-
# https://jingxuan.douyin.com/m/video/7574300896016862490?app=yumme&utm_source=copy_link
|
|
33
|
-
@handle("jingxuan.douyin", r"jingxuan\.douyin.com/m/(?P<ty>slides|video|note)/(?P<vid>\d+)")
|
|
34
|
-
async def _parse_douyin(self, searched: re.Match[str]):
|
|
35
|
-
ty, vid = searched.group("ty"), searched.group("vid")
|
|
36
|
-
if ty == "slides":
|
|
37
|
-
return await self.parse_slides(vid)
|
|
38
|
-
|
|
39
|
-
for url in (self._build_m_douyin_url(ty, vid), self._build_iesdouyin_url(ty, vid)):
|
|
40
|
-
try:
|
|
41
|
-
return await self.parse_video(url)
|
|
42
|
-
except ParseException as e:
|
|
43
|
-
logger.warning(f"failed to parse {url}, error: {e}")
|
|
44
|
-
continue
|
|
45
|
-
raise ParseException("分享已删除或资源直链提取失败, 请稍后再试")
|
|
46
|
-
|
|
47
|
-
@staticmethod
|
|
48
|
-
def _build_iesdouyin_url(ty: str, vid: str) -> str:
|
|
49
|
-
return f"https://www.iesdouyin.com/share/{ty}/{vid}"
|
|
50
|
-
|
|
51
|
-
@staticmethod
|
|
52
|
-
def _build_m_douyin_url(ty: str, vid: str) -> str:
|
|
53
|
-
return f"https://m.douyin.com/share/{ty}/{vid}"
|
|
54
|
-
|
|
55
|
-
async def parse_video(self, url: str):
|
|
56
|
-
from . import video
|
|
57
|
-
|
|
58
|
-
async with AsyncClient(
|
|
59
|
-
headers=self.ios_headers,
|
|
60
|
-
timeout=COMMON_TIMEOUT,
|
|
61
|
-
follow_redirects=False,
|
|
62
|
-
verify=False,
|
|
63
|
-
) as client:
|
|
64
|
-
response = await client.get(url)
|
|
65
|
-
if response.status_code != 200:
|
|
66
|
-
raise ParseException(f"status: {response.status_code}")
|
|
67
|
-
text = response.text
|
|
68
|
-
|
|
69
|
-
pattern = re.compile(
|
|
70
|
-
pattern=r"window\._ROUTER_DATA\s*=\s*(.*?)</script>",
|
|
71
|
-
flags=re.DOTALL,
|
|
72
|
-
)
|
|
73
|
-
matched = pattern.search(text)
|
|
74
|
-
|
|
75
|
-
if not matched or not matched.group(1):
|
|
76
|
-
raise ParseException("can't find _ROUTER_DATA in html")
|
|
77
|
-
|
|
78
|
-
video_data = video.decoder.decode(matched.group(1).strip()).video_data
|
|
79
|
-
|
|
80
|
-
# 作者
|
|
81
|
-
author = self.create_author(
|
|
82
|
-
video_data.author.nickname,
|
|
83
|
-
video_data.avatar_url,
|
|
84
|
-
)
|
|
85
|
-
|
|
86
|
-
# 先以部分数据构建结果,后续再填充内容,避免使用临时变量
|
|
87
|
-
result = self.result(
|
|
88
|
-
title=video_data.desc,
|
|
89
|
-
author=author,
|
|
90
|
-
timestamp=video_data.create_time,
|
|
91
|
-
)
|
|
92
|
-
|
|
93
|
-
# 添加图片内容
|
|
94
|
-
if image_urls := video_data.image_urls:
|
|
95
|
-
result.contents.extend(self.create_images(image_urls))
|
|
96
|
-
# 添加视频内容
|
|
97
|
-
elif video_url := video_data.video_url:
|
|
98
|
-
result.video = self.create_video(
|
|
99
|
-
video_url,
|
|
100
|
-
video_data.cover_url,
|
|
101
|
-
video_data.duration,
|
|
102
|
-
)
|
|
103
|
-
|
|
104
|
-
return result
|
|
105
|
-
|
|
106
|
-
async def parse_slides(self, video_id: str):
|
|
107
|
-
from . import slides
|
|
108
|
-
|
|
109
|
-
url = "https://www.iesdouyin.com/web/api/v2/aweme/slidesinfo/"
|
|
110
|
-
params = {
|
|
111
|
-
"aweme_ids": f"[{video_id}]",
|
|
112
|
-
"request_source": "200",
|
|
113
|
-
}
|
|
114
|
-
async with AsyncClient(headers=self.android_headers, verify=False) as client:
|
|
115
|
-
response = await client.get(url, params=params)
|
|
116
|
-
response.raise_for_status()
|
|
117
|
-
|
|
118
|
-
slides_data = slides.decoder.decode(response.content).aweme_details[0]
|
|
119
|
-
|
|
120
|
-
# 作者
|
|
121
|
-
author = self.create_author(slides_data.name, slides_data.avatar_url)
|
|
122
|
-
|
|
123
|
-
# 先以部分数据构建结果,后续再填充内容,避免使用临时变量
|
|
124
|
-
result = self.result(
|
|
125
|
-
title=slides_data.desc,
|
|
126
|
-
author=author,
|
|
127
|
-
timestamp=slides_data.create_time,
|
|
128
|
-
)
|
|
129
|
-
|
|
130
|
-
# 优先取动图
|
|
131
|
-
if dynamic_urls := slides_data.dynamic_urls:
|
|
132
|
-
for dynamic_url in dynamic_urls:
|
|
133
|
-
result.contents.append(self.create_gif(dynamic_url))
|
|
134
|
-
elif image_urls := slides_data.image_urls:
|
|
135
|
-
result.contents.extend(self.create_images(image_urls))
|
|
136
|
-
|
|
137
|
-
return result
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
from random import choice
|
|
2
|
-
|
|
3
|
-
from msgspec import Struct, field
|
|
4
|
-
from msgspec.json import Decoder
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
class PlayAddr(Struct):
|
|
8
|
-
url_list: list[str]
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
class Cover(Struct):
|
|
12
|
-
url_list: list[str]
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
class Video(Struct):
|
|
16
|
-
play_addr: PlayAddr
|
|
17
|
-
cover: Cover
|
|
18
|
-
duration: int
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
class Image(Struct):
|
|
22
|
-
video: Video | None = None
|
|
23
|
-
url_list: list[str] = field(default_factory=list)
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
class Avatar(Struct):
|
|
27
|
-
url_list: list[str]
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
class Author(Struct):
|
|
31
|
-
nickname: str
|
|
32
|
-
# avatar_larger: Avatar
|
|
33
|
-
avatar_thumb: Avatar
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
class SlidesData(Struct):
|
|
37
|
-
author: Author
|
|
38
|
-
desc: str
|
|
39
|
-
create_time: int
|
|
40
|
-
images: list[Image]
|
|
41
|
-
|
|
42
|
-
@property
|
|
43
|
-
def name(self) -> str:
|
|
44
|
-
return self.author.nickname
|
|
45
|
-
|
|
46
|
-
@property
|
|
47
|
-
def avatar_url(self) -> str:
|
|
48
|
-
return choice(self.author.avatar_thumb.url_list)
|
|
49
|
-
|
|
50
|
-
@property
|
|
51
|
-
def image_urls(self) -> list[str]:
|
|
52
|
-
return [choice(image.url_list) for image in self.images]
|
|
53
|
-
|
|
54
|
-
@property
|
|
55
|
-
def dynamic_urls(self) -> list[str]:
|
|
56
|
-
return [choice(image.video.play_addr.url_list) for image in self.images if image.video]
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
class SlidesInfo(Struct):
|
|
60
|
-
aweme_details: list[SlidesData] = field(default_factory=list)
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
decoder = Decoder(SlidesInfo)
|
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
from random import choice
|
|
2
|
-
from typing import Any
|
|
3
|
-
|
|
4
|
-
from msgspec import Struct, field
|
|
5
|
-
from msgspec.json import Decoder
|
|
6
|
-
|
|
7
|
-
from ..base import ParseException
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
class Avatar(Struct):
|
|
11
|
-
url_list: list[str]
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
class Author(Struct):
|
|
15
|
-
nickname: str
|
|
16
|
-
avatar_thumb: Avatar | None = None
|
|
17
|
-
avatar_medium: Avatar | None = None
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
class PlayAddr(Struct):
|
|
21
|
-
url_list: list[str]
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
class Cover(Struct):
|
|
25
|
-
url_list: list[str]
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
class Video(Struct):
|
|
29
|
-
play_addr: PlayAddr
|
|
30
|
-
cover: Cover
|
|
31
|
-
duration: int
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
class Image(Struct):
|
|
35
|
-
video: Video | None = None
|
|
36
|
-
url_list: list[str] = field(default_factory=list)
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
class VideoData(Struct):
|
|
40
|
-
create_time: int
|
|
41
|
-
author: Author
|
|
42
|
-
desc: str
|
|
43
|
-
images: list[Image] | None = None
|
|
44
|
-
video: Video | None = None
|
|
45
|
-
|
|
46
|
-
@property
|
|
47
|
-
def image_urls(self) -> list[str]:
|
|
48
|
-
return [choice(image.url_list) for image in self.images] if self.images else []
|
|
49
|
-
|
|
50
|
-
@property
|
|
51
|
-
def video_url(self) -> str | None:
|
|
52
|
-
return choice(self.video.play_addr.url_list).replace("playwm", "play") if self.video else None
|
|
53
|
-
|
|
54
|
-
@property
|
|
55
|
-
def cover_url(self) -> str | None:
|
|
56
|
-
return choice(self.video.cover.url_list) if self.video else None
|
|
57
|
-
|
|
58
|
-
@property
|
|
59
|
-
def duration(self) -> int | None:
|
|
60
|
-
return self.video.duration // 1000 if self.video else None
|
|
61
|
-
|
|
62
|
-
@property
|
|
63
|
-
def avatar_url(self) -> str | None:
|
|
64
|
-
if avatar := self.author.avatar_thumb:
|
|
65
|
-
return choice(avatar.url_list)
|
|
66
|
-
elif avatar := self.author.avatar_medium:
|
|
67
|
-
return choice(avatar.url_list)
|
|
68
|
-
return None
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
class VideoInfoRes(Struct):
|
|
72
|
-
item_list: list[VideoData] = field(default_factory=list)
|
|
73
|
-
|
|
74
|
-
@property
|
|
75
|
-
def video_data(self) -> VideoData:
|
|
76
|
-
if len(self.item_list) == 0:
|
|
77
|
-
raise ParseException("can't find data in videoInfoRes")
|
|
78
|
-
return choice(self.item_list)
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
class VideoOrNotePage(Struct):
|
|
82
|
-
video_info_res: VideoInfoRes = field(name="videoInfoRes", default_factory=VideoInfoRes)
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
class LoaderData(Struct):
|
|
86
|
-
video_page: VideoOrNotePage | None = field(name="video_(id)/page", default=None)
|
|
87
|
-
note_page: VideoOrNotePage | None = field(name="note_(id)/page", default=None)
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
class RouterData(Struct):
|
|
91
|
-
loader_data: LoaderData = field(name="loaderData", default_factory=LoaderData)
|
|
92
|
-
errors: dict[str, Any] | None = None
|
|
93
|
-
|
|
94
|
-
@property
|
|
95
|
-
def video_data(self) -> VideoData:
|
|
96
|
-
if page := self.loader_data.video_page:
|
|
97
|
-
return page.video_info_res.video_data
|
|
98
|
-
elif page := self.loader_data.note_page:
|
|
99
|
-
return page.video_info_res.video_data
|
|
100
|
-
raise ParseException("can't find video_(id)/page or note_(id)/page in router data")
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
decoder = Decoder(RouterData)
|
|
File without changes
|
{nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.8}/src/nonebot_plugin_parser/__init__.py
RENAMED
|
File without changes
|
{nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.8}/src/nonebot_plugin_parser/config.py
RENAMED
|
File without changes
|
{nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.8}/src/nonebot_plugin_parser/constants.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.8}/src/nonebot_plugin_parser/exception.py
RENAMED
|
File without changes
|
{nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.8}/src/nonebot_plugin_parser/helper.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.8}/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
|
|
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.7 → nonebot_plugin_parser-2.6.8}/src/nonebot_plugin_parser/utils.py
RENAMED
|
File without changes
|