nonebot-plugin-parser 2.6.7__tar.gz → 2.6.9__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.9}/PKG-INFO +1 -1
- nonebot_plugin_parser-2.6.9/pyproject.toml +224 -0
- nonebot_plugin_parser-2.6.7/pyproject.toml → nonebot_plugin_parser-2.6.9/pyproject.toml.orig +2 -2
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.9}/src/nonebot_plugin_parser/download/__init__.py +19 -44
- nonebot_plugin_parser-2.6.9/src/nonebot_plugin_parser/download/rich.py +40 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.9}/src/nonebot_plugin_parser/parsers/bilibili/__init__.py +40 -4
- nonebot_plugin_parser-2.6.9/src/nonebot_plugin_parser/parsers/douyin/__init__.py +118 -0
- nonebot_plugin_parser-2.6.9/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.9}/README.md +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.9}/src/nonebot_plugin_parser/__init__.py +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.9}/src/nonebot_plugin_parser/config.py +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.9}/src/nonebot_plugin_parser/constants.py +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.9}/src/nonebot_plugin_parser/download/task.py +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.9}/src/nonebot_plugin_parser/download/ytdlp.py +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.9}/src/nonebot_plugin_parser/exception.py +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.9}/src/nonebot_plugin_parser/helper.py +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.9}/src/nonebot_plugin_parser/matchers/__init__.py +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.9}/src/nonebot_plugin_parser/matchers/filter.py +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.9}/src/nonebot_plugin_parser/matchers/rule.py +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.9}/src/nonebot_plugin_parser/parsers/__init__.py +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.9}/src/nonebot_plugin_parser/parsers/acfun/__init__.py +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.9}/src/nonebot_plugin_parser/parsers/acfun/video.py +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.9}/src/nonebot_plugin_parser/parsers/base.py +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.9}/src/nonebot_plugin_parser/parsers/bilibili/article.py +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.9}/src/nonebot_plugin_parser/parsers/bilibili/common.py +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.9}/src/nonebot_plugin_parser/parsers/bilibili/dynamic.py +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.9}/src/nonebot_plugin_parser/parsers/bilibili/favlist.py +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.9}/src/nonebot_plugin_parser/parsers/bilibili/live.py +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.9}/src/nonebot_plugin_parser/parsers/bilibili/opus.py +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.9}/src/nonebot_plugin_parser/parsers/bilibili/video.py +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.9}/src/nonebot_plugin_parser/parsers/cookie.py +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.9}/src/nonebot_plugin_parser/parsers/data.py +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.9}/src/nonebot_plugin_parser/parsers/kuaishou/__init__.py +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.9}/src/nonebot_plugin_parser/parsers/kuaishou/states.py +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.9}/src/nonebot_plugin_parser/parsers/nga.py +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.9}/src/nonebot_plugin_parser/parsers/task.py +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.9}/src/nonebot_plugin_parser/parsers/tiktok.py +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.9}/src/nonebot_plugin_parser/parsers/twitter.py +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.9}/src/nonebot_plugin_parser/parsers/utils.py +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.9}/src/nonebot_plugin_parser/parsers/weibo/__init__.py +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.9}/src/nonebot_plugin_parser/parsers/weibo/article.py +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.9}/src/nonebot_plugin_parser/parsers/weibo/common.py +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.9}/src/nonebot_plugin_parser/parsers/weibo/show.py +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.9}/src/nonebot_plugin_parser/parsers/xiaohongshu/__init__.py +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.9}/src/nonebot_plugin_parser/parsers/xiaohongshu/common.py +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.9}/src/nonebot_plugin_parser/parsers/xiaohongshu/discovery.py +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.9}/src/nonebot_plugin_parser/parsers/xiaohongshu/explore.py +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.9}/src/nonebot_plugin_parser/parsers/youtube/__init__.py +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.9}/src/nonebot_plugin_parser/parsers/youtube/meta.py +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.9}/src/nonebot_plugin_parser/renders/__init__.py +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.9}/src/nonebot_plugin_parser/renders/base.py +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.9}/src/nonebot_plugin_parser/renders/common/__init__.py +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.9}/src/nonebot_plugin_parser/renders/common/assets.py +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.9}/src/nonebot_plugin_parser/renders/common/font.py +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.9}/src/nonebot_plugin_parser/renders/common/renderer.py +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.9}/src/nonebot_plugin_parser/renders/default.py +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.9}/src/nonebot_plugin_parser/renders/htmlrender.py +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.9}/src/nonebot_plugin_parser/renders/resources/HYSongYunLangHeiW.ttf +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.9}/src/nonebot_plugin_parser/renders/resources/__init__.py +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.9}/src/nonebot_plugin_parser/renders/resources/avatar.png +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.9}/src/nonebot_plugin_parser/renders/resources/bilibili.png +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.9}/src/nonebot_plugin_parser/renders/resources/douyin.png +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.9}/src/nonebot_plugin_parser/renders/resources/fallback_pic/1.jpg +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.9}/src/nonebot_plugin_parser/renders/resources/fallback_pic/2.jpg +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.9}/src/nonebot_plugin_parser/renders/resources/fallback_pic/3.jpg +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.9}/src/nonebot_plugin_parser/renders/resources/fallback_pic/4.jpg +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.9}/src/nonebot_plugin_parser/renders/resources/fallback_pic/5.jpg +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.9}/src/nonebot_plugin_parser/renders/resources/fallback_pic/6.jpg +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.9}/src/nonebot_plugin_parser/renders/resources/fallback_pic/7.jpg +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.9}/src/nonebot_plugin_parser/renders/resources/fallback_pic/8.jpg +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.9}/src/nonebot_plugin_parser/renders/resources/fallback_pic/9.jpg +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.9}/src/nonebot_plugin_parser/renders/resources/kuaishou.png +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.9}/src/nonebot_plugin_parser/renders/resources/play.png +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.9}/src/nonebot_plugin_parser/renders/resources/tiktok.png +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.9}/src/nonebot_plugin_parser/renders/resources/twitter.png +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.9}/src/nonebot_plugin_parser/renders/resources/weibo.png +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.9}/src/nonebot_plugin_parser/renders/resources/xiaohongshu.png +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.9}/src/nonebot_plugin_parser/renders/resources/youtube.png +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.9}/src/nonebot_plugin_parser/renders/templates/card.html.jinja2 +0 -0
- {nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.9}/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.9
|
|
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.9"
|
|
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.9"
|
|
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.9/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.9"
|
|
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.9"
|
|
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 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,14 +61,14 @@ class StreamDownloader:
|
|
|
84
61
|
response.raise_for_status()
|
|
85
62
|
content_length = self._validate_content_length(response)
|
|
86
63
|
|
|
87
|
-
|
|
64
|
+
update_progress = add_progress_task(
|
|
88
65
|
f"httpx | {file_path.name}",
|
|
89
66
|
content_length,
|
|
90
|
-
)
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
67
|
+
)
|
|
68
|
+
async with aiofiles.open(file_path, "wb") as file:
|
|
69
|
+
async for chunk in response.aiter_bytes(chunk_size):
|
|
70
|
+
await file.write(chunk)
|
|
71
|
+
update_progress(advance=len(chunk))
|
|
95
72
|
|
|
96
73
|
return file_path
|
|
97
74
|
|
|
@@ -112,14 +89,14 @@ class StreamDownloader:
|
|
|
112
89
|
response.raise_for_status()
|
|
113
90
|
content_length = self._validate_content_length(response)
|
|
114
91
|
|
|
115
|
-
|
|
92
|
+
update_progress = add_progress_task(
|
|
116
93
|
f"curl_cffi | {file_path.name}",
|
|
117
94
|
content_length,
|
|
118
|
-
)
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
95
|
+
)
|
|
96
|
+
async with aiofiles.open(file_path, "wb") as file:
|
|
97
|
+
async for chunk in response.aiter_content(chunk_size=8192):
|
|
98
|
+
await file.write(chunk)
|
|
99
|
+
update_progress(advance=len(chunk))
|
|
123
100
|
|
|
124
101
|
return file_path
|
|
125
102
|
|
|
@@ -242,14 +219,12 @@ class StreamDownloader:
|
|
|
242
219
|
|
|
243
220
|
try:
|
|
244
221
|
async with aiofiles.open(video_path, "wb") as f:
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
async
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
total_size += len(chunk)
|
|
252
|
-
update_progress(advance=len(chunk), total=total_size)
|
|
222
|
+
update_progress = add_progress_task(desc=video_name)
|
|
223
|
+
for url in await self._get_m3u8_slices(m3u8_url):
|
|
224
|
+
async with self.client.stream("GET", url, headers=ext_headers) as response:
|
|
225
|
+
async for chunk in response.aiter_bytes(chunk_size=1024 * 1024):
|
|
226
|
+
await f.write(chunk)
|
|
227
|
+
update_progress(advance=len(chunk))
|
|
253
228
|
except httpx.HTTPError:
|
|
254
229
|
await safe_unlink(video_path)
|
|
255
230
|
logger.exception("m3u8 视频下载失败")
|
|
@@ -0,0 +1,40 @@
|
|
|
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)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
from nonebot import get_driver
|
|
29
|
+
|
|
30
|
+
driver = get_driver()
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@driver.on_startup
|
|
34
|
+
async def enter_progress_bar():
|
|
35
|
+
progress_bar.start()
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@driver.on_shutdown
|
|
39
|
+
async def exit_progress_bar():
|
|
40
|
+
progress_bar.stop()
|
|
@@ -6,10 +6,16 @@ from collections.abc import AsyncGenerator
|
|
|
6
6
|
|
|
7
7
|
from msgspec import convert
|
|
8
8
|
from nonebot import logger
|
|
9
|
-
from bilibili_api import
|
|
9
|
+
from bilibili_api import (
|
|
10
|
+
HEADERS,
|
|
11
|
+
Credential,
|
|
12
|
+
get_client,
|
|
13
|
+
select_client,
|
|
14
|
+
request_settings,
|
|
15
|
+
)
|
|
10
16
|
from bilibili_api.opus import Opus
|
|
11
17
|
from bilibili_api.video import Video
|
|
12
|
-
from bilibili_api.login_v2 import QrCodeLogin, QrCodeLoginEvents
|
|
18
|
+
from bilibili_api.login_v2 import QrCodeLogin, QrCodeLoginEvents, QrCodeLoginChannel
|
|
13
19
|
|
|
14
20
|
from ..base import (
|
|
15
21
|
BaseParser,
|
|
@@ -353,18 +359,48 @@ class BilibiliParser(BaseParser):
|
|
|
353
359
|
|
|
354
360
|
async def login_with_qrcode(self) -> bytes:
|
|
355
361
|
"""通过二维码登录获取哔哩哔哩登录凭证"""
|
|
356
|
-
self._qr_login = QrCodeLogin()
|
|
362
|
+
self._qr_login = QrCodeLogin(platform=QrCodeLoginChannel.WEB)
|
|
357
363
|
await self._qr_login.generate_qrcode()
|
|
358
364
|
|
|
359
365
|
qr_pic = self._qr_login.get_qrcode_picture()
|
|
360
366
|
return qr_pic.content
|
|
361
367
|
|
|
368
|
+
@staticmethod
|
|
369
|
+
async def check_state(qr: QrCodeLogin):
|
|
370
|
+
client = get_client()
|
|
371
|
+
resp = await client.request(
|
|
372
|
+
method="GET",
|
|
373
|
+
url="https://passport.bilibili.com/x/passport-login/web/qrcode/poll",
|
|
374
|
+
params={"qrcode_key": qr._QrCodeLogin__qr_key}, # pyright: ignore[reportAttributeAccessIssue]
|
|
375
|
+
)
|
|
376
|
+
result: dict = resp.json()["data"] # pyright: ignore[reportIndexIssue]
|
|
377
|
+
code = result["code"]
|
|
378
|
+
if code == 86101:
|
|
379
|
+
return QrCodeLoginEvents.SCAN
|
|
380
|
+
elif code == 86090:
|
|
381
|
+
return QrCodeLoginEvents.CONF
|
|
382
|
+
elif code == 86038:
|
|
383
|
+
return QrCodeLoginEvents.TIMEOUT
|
|
384
|
+
else:
|
|
385
|
+
ac_time_value = result["refresh_token"]
|
|
386
|
+
cookies = resp.cookies
|
|
387
|
+
sessdata = cookies["SESSDATA"]
|
|
388
|
+
bili_jct = cookies["bili_jct"]
|
|
389
|
+
dedeuserid = cookies["DedeUserID"]
|
|
390
|
+
qr._QrCodeLogin__credential = Credential( # pyright: ignore[reportAttributeAccessIssue]
|
|
391
|
+
sessdata=sessdata,
|
|
392
|
+
bili_jct=bili_jct,
|
|
393
|
+
dedeuserid=dedeuserid,
|
|
394
|
+
ac_time_value=ac_time_value,
|
|
395
|
+
)
|
|
396
|
+
return QrCodeLoginEvents.DONE
|
|
397
|
+
|
|
362
398
|
async def check_qr_state(self) -> AsyncGenerator[str]:
|
|
363
399
|
"""检查二维码登录状态"""
|
|
364
400
|
scan_tip_pending = True
|
|
365
401
|
|
|
366
402
|
for _ in range(30):
|
|
367
|
-
state = await self.
|
|
403
|
+
state = await self.check_state(self._qr_login)
|
|
368
404
|
match state:
|
|
369
405
|
case QrCodeLoginEvents.DONE:
|
|
370
406
|
yield "登录成功"
|
|
@@ -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.9}/src/nonebot_plugin_parser/__init__.py
RENAMED
|
File without changes
|
{nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.9}/src/nonebot_plugin_parser/config.py
RENAMED
|
File without changes
|
{nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.9}/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.9}/src/nonebot_plugin_parser/exception.py
RENAMED
|
File without changes
|
{nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.9}/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
|
{nonebot_plugin_parser-2.6.7 → nonebot_plugin_parser-2.6.9}/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.9}/src/nonebot_plugin_parser/utils.py
RENAMED
|
File without changes
|