parsehub 1.0.0__py3-none-any.whl
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.
- parsehub/__init__.py +7 -0
- parsehub/config/__init__.py +3 -0
- parsehub/config/config.py +31 -0
- parsehub/deps/submodule_manager.py +31 -0
- parsehub/deps/xhs/locale/po_to_mo.py +25 -0
- parsehub/deps/xhs/main.py +94 -0
- parsehub/deps/xhs/source/CLI/__init__.py +3 -0
- parsehub/deps/xhs/source/CLI/main.py +330 -0
- parsehub/deps/xhs/source/TUI/__init__.py +3 -0
- parsehub/deps/xhs/source/TUI/about.py +76 -0
- parsehub/deps/xhs/source/TUI/app.py +114 -0
- parsehub/deps/xhs/source/TUI/index.py +142 -0
- parsehub/deps/xhs/source/TUI/loading.py +22 -0
- parsehub/deps/xhs/source/TUI/monitor.py +76 -0
- parsehub/deps/xhs/source/TUI/progress.py +9 -0
- parsehub/deps/xhs/source/TUI/record.py +55 -0
- parsehub/deps/xhs/source/TUI/setting.py +251 -0
- parsehub/deps/xhs/source/TUI/update.py +86 -0
- parsehub/deps/xhs/source/__init__.py +11 -0
- parsehub/deps/xhs/source/application/__init__.py +3 -0
- parsehub/deps/xhs/source/application/app.py +522 -0
- parsehub/deps/xhs/source/application/download.py +327 -0
- parsehub/deps/xhs/source/application/explore.py +66 -0
- parsehub/deps/xhs/source/application/image.py +50 -0
- parsehub/deps/xhs/source/application/request.py +90 -0
- parsehub/deps/xhs/source/application/video.py +20 -0
- parsehub/deps/xhs/source/expansion/__init__.py +9 -0
- parsehub/deps/xhs/source/expansion/browser.py +111 -0
- parsehub/deps/xhs/source/expansion/cleaner.py +92 -0
- parsehub/deps/xhs/source/expansion/converter.py +64 -0
- parsehub/deps/xhs/source/expansion/file_folder.py +25 -0
- parsehub/deps/xhs/source/expansion/namespace.py +84 -0
- parsehub/deps/xhs/source/expansion/truncate.py +35 -0
- parsehub/deps/xhs/source/module/__init__.py +40 -0
- parsehub/deps/xhs/source/module/extend.py +5 -0
- parsehub/deps/xhs/source/module/manager.py +259 -0
- parsehub/deps/xhs/source/module/model.py +14 -0
- parsehub/deps/xhs/source/module/recorder.py +127 -0
- parsehub/deps/xhs/source/module/settings.py +72 -0
- parsehub/deps/xhs/source/module/static.py +79 -0
- parsehub/deps/xhs/source/module/tools.py +34 -0
- parsehub/deps/xhs/source/module/translator.py +27 -0
- parsehub/main.py +39 -0
- parsehub/parsers/__init__.py +0 -0
- parsehub/parsers/base/__init__.py +0 -0
- parsehub/parsers/base/base.py +54 -0
- parsehub/parsers/base/yt_dlp_parser.py +158 -0
- parsehub/parsers/parser/__init__.py +0 -0
- parsehub/parsers/parser/bilibili.py +134 -0
- parsehub/parsers/parser/douyin.py +101 -0
- parsehub/parsers/parser/facebook.py +11 -0
- parsehub/parsers/parser/instagram.py +50 -0
- parsehub/parsers/parser/tieba.py +99 -0
- parsehub/parsers/parser/twitter.py +113 -0
- parsehub/parsers/parser/weibo.py +59 -0
- parsehub/parsers/parser/xhs_.py +50 -0
- parsehub/parsers/parser/youtube.py +27 -0
- parsehub/tools/__init__.py +2 -0
- parsehub/tools/llm.py +27 -0
- parsehub/tools/transcriptions.py +110 -0
- parsehub/types/__init__.py +11 -0
- parsehub/types/error.py +1 -0
- parsehub/types/media.py +87 -0
- parsehub/types/parse_result.py +267 -0
- parsehub/types/subtitles.py +53 -0
- parsehub/types/summary_result.py +6 -0
- parsehub/utiles/bilibili_api.py +283 -0
- parsehub/utiles/download_file.py +76 -0
- parsehub/utiles/img_host.py +61 -0
- parsehub/utiles/utile.py +88 -0
- parsehub/utiles/weibo_api.py +227 -0
- parsehub/utiles/whisper_api.py +72 -0
- parsehub-1.0.0.dist-info/LICENSE +21 -0
- parsehub-1.0.0.dist-info/METADATA +80 -0
- parsehub-1.0.0.dist-info/RECORD +77 -0
- parsehub-1.0.0.dist-info/WHEEL +5 -0
- parsehub-1.0.0.dist-info/top_level.txt +1 -0
parsehub/__init__.py
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
import sys
|
|
3
|
+
from os import getenv
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from dotenv import load_dotenv
|
|
7
|
+
|
|
8
|
+
load_dotenv()
|
|
9
|
+
|
|
10
|
+
UA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass
|
|
14
|
+
class ParseHubConfig:
|
|
15
|
+
DOWNLOAD_DIR = getenv("DOWNLOAD_DIR", Path(sys.argv[0]).parent / Path("downloads/"))
|
|
16
|
+
"""默认下载目录"""
|
|
17
|
+
|
|
18
|
+
douyin_api = getenv("DOUYIN_API", "https://douyin.wtf")
|
|
19
|
+
"""抖音解析API地址, 项目地址: https://github.com/Evil0ctal/Douyin_TikTok_Download_API"""
|
|
20
|
+
|
|
21
|
+
provider = getenv("PROVIDER", "openai").lower()
|
|
22
|
+
"""模型提供商"""
|
|
23
|
+
|
|
24
|
+
api_key = getenv("API_KEY")
|
|
25
|
+
"""API Key"""
|
|
26
|
+
|
|
27
|
+
base_url = getenv("BASE_URL", "https://api.openai.com/v1")
|
|
28
|
+
"""API 地址"""
|
|
29
|
+
|
|
30
|
+
model = getenv("MODEL", "gpt-4o-mini")
|
|
31
|
+
"""AI总结模型名称"""
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
import sys
|
|
3
|
+
from typing import Dict, Optional
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class SubmoduleManager:
|
|
7
|
+
def __init__(self):
|
|
8
|
+
self.deps_dir = Path(__file__).parent
|
|
9
|
+
self.path_mappings: Dict[str, Path] = {}
|
|
10
|
+
|
|
11
|
+
def add_submodule(
|
|
12
|
+
self, name: str, relative_path: str, source_dir: Optional[str] = None
|
|
13
|
+
):
|
|
14
|
+
"""添加子模块路径映射"""
|
|
15
|
+
submodule_path = self.deps_dir / relative_path
|
|
16
|
+
self.path_mappings[name] = submodule_path
|
|
17
|
+
|
|
18
|
+
# 添加主模块路径
|
|
19
|
+
if str(submodule_path) not in sys.path:
|
|
20
|
+
sys.path.insert(0, str(submodule_path))
|
|
21
|
+
|
|
22
|
+
# 添加源码目录路径(如果指定)
|
|
23
|
+
if source_dir:
|
|
24
|
+
source_path = submodule_path / source_dir
|
|
25
|
+
if str(source_path) not in sys.path:
|
|
26
|
+
sys.path.insert(0, str(source_path))
|
|
27
|
+
|
|
28
|
+
def setup_all(self):
|
|
29
|
+
"""设置所有子模块的路径"""
|
|
30
|
+
# 添加所有需要的子模块
|
|
31
|
+
self.add_submodule("xhs", "xhs", "xhs")
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from subprocess import run
|
|
3
|
+
|
|
4
|
+
__all__ = []
|
|
5
|
+
ROOT = Path(__file__).resolve().parent
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def scan_directory():
|
|
9
|
+
return [
|
|
10
|
+
item.joinpath("LC_MESSAGES/xhs.po") for item in ROOT.iterdir() if item.is_dir()
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def generate_map(files: list[Path]):
|
|
15
|
+
return [(i, i.with_suffix(".mo")) for i in files]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def generate_mo(maps: list[tuple[Path, Path]]):
|
|
19
|
+
for i, j in maps:
|
|
20
|
+
command = f'msgfmt "{i}" -o "{j}"'
|
|
21
|
+
print(run(command, shell=True, text=True))
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
if __name__ == "__main__":
|
|
25
|
+
generate_mo(generate_map(scan_directory()))
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
from asyncio import run
|
|
2
|
+
from asyncio.exceptions import CancelledError
|
|
3
|
+
from contextlib import suppress
|
|
4
|
+
from sys import argv
|
|
5
|
+
|
|
6
|
+
from source import Settings
|
|
7
|
+
from source import XHS
|
|
8
|
+
from source import XHSDownloader
|
|
9
|
+
from source import cli
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
async def example():
|
|
13
|
+
"""通过代码设置参数,适合二次开发"""
|
|
14
|
+
# 示例链接
|
|
15
|
+
error_link = "https://github.com/JoeanAmier/XHS_Downloader"
|
|
16
|
+
demo_link = "https://www.xiaohongshu.com/explore/xxxxxxxxxx"
|
|
17
|
+
multiple_links = f"{demo_link} {demo_link} {demo_link}"
|
|
18
|
+
# 实例对象
|
|
19
|
+
work_path = "D:\\" # 作品数据/文件保存根路径,默认值:项目根路径
|
|
20
|
+
folder_name = "Download" # 作品文件储存文件夹名称(自动创建),默认值:Download
|
|
21
|
+
name_format = "作品标题 作品描述"
|
|
22
|
+
user_agent = "" # User-Agent
|
|
23
|
+
cookie = "" # 小红书网页版 Cookie,无需登录,可选参数,登录状态对数据采集有影响
|
|
24
|
+
proxy = None # 网络代理
|
|
25
|
+
timeout = 5 # 请求数据超时限制,单位:秒,默认值:10
|
|
26
|
+
chunk = 1024 * 1024 * 10 # 下载文件时,每次从服务器获取的数据块大小,单位:字节
|
|
27
|
+
max_retry = 2 # 请求数据失败时,重试的最大次数,单位:秒,默认值:5
|
|
28
|
+
record_data = False # 是否保存作品数据至文件
|
|
29
|
+
image_format = "WEBP" # 图文作品文件下载格式,支持:PNG、WEBP
|
|
30
|
+
folder_mode = False # 是否将每个作品的文件储存至单独的文件夹
|
|
31
|
+
# async with XHS() as xhs:
|
|
32
|
+
# pass # 使用默认参数
|
|
33
|
+
async with XHS(
|
|
34
|
+
work_path=work_path,
|
|
35
|
+
folder_name=folder_name,
|
|
36
|
+
name_format=name_format,
|
|
37
|
+
user_agent=user_agent,
|
|
38
|
+
cookie=cookie,
|
|
39
|
+
proxy=proxy,
|
|
40
|
+
timeout=timeout,
|
|
41
|
+
chunk=chunk,
|
|
42
|
+
max_retry=max_retry,
|
|
43
|
+
record_data=record_data,
|
|
44
|
+
image_format=image_format,
|
|
45
|
+
folder_mode=folder_mode,
|
|
46
|
+
) as xhs: # 使用自定义参数
|
|
47
|
+
download = True # 是否下载作品文件,默认值:False
|
|
48
|
+
# 返回作品详细信息,包括下载地址
|
|
49
|
+
# 获取数据失败时返回空字典
|
|
50
|
+
print(
|
|
51
|
+
await xhs.extract(
|
|
52
|
+
error_link,
|
|
53
|
+
download,
|
|
54
|
+
)
|
|
55
|
+
)
|
|
56
|
+
print(await xhs.extract(demo_link, download, index=[1, 2]))
|
|
57
|
+
# 支持传入多个作品链接
|
|
58
|
+
print(
|
|
59
|
+
await xhs.extract(
|
|
60
|
+
multiple_links,
|
|
61
|
+
download,
|
|
62
|
+
)
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
async def app():
|
|
67
|
+
async with XHSDownloader() as xhs:
|
|
68
|
+
await xhs.run_async()
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
async def server(
|
|
72
|
+
host="0.0.0.0",
|
|
73
|
+
port=8000,
|
|
74
|
+
log_level="info",
|
|
75
|
+
):
|
|
76
|
+
async with XHS(**Settings().run()) as xhs:
|
|
77
|
+
await xhs.run_server(
|
|
78
|
+
host,
|
|
79
|
+
port,
|
|
80
|
+
log_level,
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
if __name__ == "__main__":
|
|
85
|
+
with suppress(
|
|
86
|
+
KeyboardInterrupt,
|
|
87
|
+
CancelledError,
|
|
88
|
+
):
|
|
89
|
+
if len(argv) == 1:
|
|
90
|
+
run(app())
|
|
91
|
+
elif argv[1] == "server":
|
|
92
|
+
run(server())
|
|
93
|
+
else:
|
|
94
|
+
cli()
|
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
from asyncio import run
|
|
2
|
+
from contextlib import suppress
|
|
3
|
+
from pathlib import Path as Root
|
|
4
|
+
from textwrap import fill
|
|
5
|
+
|
|
6
|
+
from click import Context
|
|
7
|
+
from click import (
|
|
8
|
+
command,
|
|
9
|
+
option,
|
|
10
|
+
Path,
|
|
11
|
+
Choice,
|
|
12
|
+
pass_context,
|
|
13
|
+
echo,
|
|
14
|
+
)
|
|
15
|
+
from rich import print
|
|
16
|
+
from rich.panel import Panel
|
|
17
|
+
from rich.table import Table
|
|
18
|
+
|
|
19
|
+
from source.application import XHS
|
|
20
|
+
from source.expansion import BrowserCookie
|
|
21
|
+
from source.module import (
|
|
22
|
+
ROOT,
|
|
23
|
+
PROJECT,
|
|
24
|
+
)
|
|
25
|
+
from source.module import Settings
|
|
26
|
+
from source.module import Translate
|
|
27
|
+
|
|
28
|
+
__all__ = ["cli"]
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def check_value(function):
|
|
32
|
+
def inner(ctx: Context, param, value):
|
|
33
|
+
if not value:
|
|
34
|
+
return
|
|
35
|
+
return function(ctx, param, value)
|
|
36
|
+
|
|
37
|
+
return inner
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class CLI:
|
|
41
|
+
def __init__(self, ctx: Context, **kwargs):
|
|
42
|
+
# print(kwargs)
|
|
43
|
+
self.ctx = ctx
|
|
44
|
+
self.url = kwargs.pop("url")
|
|
45
|
+
self.index = self.__format_index(kwargs.pop("index"))
|
|
46
|
+
self.path = kwargs.pop("settings")
|
|
47
|
+
self.update = kwargs.pop("update_settings")
|
|
48
|
+
# print(kwargs)
|
|
49
|
+
self.settings = Settings(self.__check_settings_path())
|
|
50
|
+
self.parameter = self.settings.run() | self.__clean_params(kwargs)
|
|
51
|
+
self.APP = XHS(**self.parameter)
|
|
52
|
+
|
|
53
|
+
async def __aenter__(self):
|
|
54
|
+
await self.APP.__aenter__()
|
|
55
|
+
return self
|
|
56
|
+
|
|
57
|
+
async def __aexit__(self, exc_type, exc_value, traceback):
|
|
58
|
+
await self.APP.__aexit__(exc_type, exc_value, traceback)
|
|
59
|
+
|
|
60
|
+
async def run(self):
|
|
61
|
+
if self.url:
|
|
62
|
+
await self.APP.extract_cli(self.url, index=self.index)
|
|
63
|
+
self.__update_settings()
|
|
64
|
+
|
|
65
|
+
def __update_settings(self):
|
|
66
|
+
if self.update:
|
|
67
|
+
self.settings.update(self.parameter)
|
|
68
|
+
|
|
69
|
+
def __check_settings_path(self) -> Path:
|
|
70
|
+
if not self.path:
|
|
71
|
+
return ROOT
|
|
72
|
+
return s.parent if (s := Root(self.path)).is_file() else ROOT
|
|
73
|
+
|
|
74
|
+
@staticmethod
|
|
75
|
+
def __merge_cookie(data: dict) -> None:
|
|
76
|
+
if not data["cookie"] and (bc := data["browser_cookie"]):
|
|
77
|
+
data["cookie"] = bc
|
|
78
|
+
data.pop("browser_cookie")
|
|
79
|
+
|
|
80
|
+
def __clean_params(self, data: dict) -> dict:
|
|
81
|
+
self.__merge_cookie(data)
|
|
82
|
+
return {k: v for k, v in data.items() if v}
|
|
83
|
+
|
|
84
|
+
@staticmethod
|
|
85
|
+
def __format_index(index: str) -> list:
|
|
86
|
+
if index:
|
|
87
|
+
result = []
|
|
88
|
+
values = index.split()
|
|
89
|
+
for i in values:
|
|
90
|
+
with suppress(ValueError):
|
|
91
|
+
result.append(int(i))
|
|
92
|
+
return result
|
|
93
|
+
return []
|
|
94
|
+
|
|
95
|
+
@staticmethod
|
|
96
|
+
@check_value
|
|
97
|
+
def version(ctx: Context, param, value) -> None:
|
|
98
|
+
echo(PROJECT)
|
|
99
|
+
ctx.exit()
|
|
100
|
+
|
|
101
|
+
@staticmethod
|
|
102
|
+
@check_value
|
|
103
|
+
def read_cookie(ctx: Context, param, value) -> str:
|
|
104
|
+
return BrowserCookie.get(
|
|
105
|
+
value,
|
|
106
|
+
domains=[
|
|
107
|
+
"xiaohongshu.com",
|
|
108
|
+
],
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
@staticmethod
|
|
112
|
+
@check_value
|
|
113
|
+
def help_(ctx: Context, param, value) -> None:
|
|
114
|
+
_ = Translate("").message()
|
|
115
|
+
table = Table(highlight=True, box=None, show_header=True)
|
|
116
|
+
|
|
117
|
+
# 添加表格的列名
|
|
118
|
+
table.add_column("parameter", no_wrap=True, style="bold")
|
|
119
|
+
table.add_column("abbreviation", no_wrap=True, style="bold")
|
|
120
|
+
table.add_column("type", no_wrap=True, style="bold")
|
|
121
|
+
table.add_column(
|
|
122
|
+
"description",
|
|
123
|
+
no_wrap=True,
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
# TODO: 语言设置未生效
|
|
127
|
+
options = (
|
|
128
|
+
("--url", "-u", "str", _("小红书作品链接")),
|
|
129
|
+
(
|
|
130
|
+
"--index",
|
|
131
|
+
"-i",
|
|
132
|
+
"str",
|
|
133
|
+
_(
|
|
134
|
+
'下载指定序号的图片文件,仅对图文作品生效;多个序号输入示例:"1 3 5 7"'
|
|
135
|
+
),
|
|
136
|
+
),
|
|
137
|
+
("--work_path", "-wp", "str", _("作品数据 / 文件保存根路径")),
|
|
138
|
+
("--folder_name", "-fn", "str", _("作品文件储存文件夹名称")),
|
|
139
|
+
("--name_format", "-nf", "str", _("作品文件名称格式")),
|
|
140
|
+
# ("--sec_ch_ua", "-su", "str", _("Sec-Ch-Ua")),
|
|
141
|
+
# ("--sec_ch_ua_platform", "-sp", "str", _("Sec-Ch-Ua-Platform")),
|
|
142
|
+
("--user_agent", "-ua", "str", _("User-Agent")),
|
|
143
|
+
("--cookie", "-ck", "str", _("小红书网页版 Cookie,无需登录")),
|
|
144
|
+
("--proxy", "-p", "str", _("网络代理")),
|
|
145
|
+
("--timeout", "-t", "int", _("请求数据超时限制,单位:秒")),
|
|
146
|
+
(
|
|
147
|
+
"--chunk",
|
|
148
|
+
"-c",
|
|
149
|
+
"int",
|
|
150
|
+
_("下载文件时,每次从服务器获取的数据块大小,单位:字节"),
|
|
151
|
+
),
|
|
152
|
+
("--max_retry", "-mr", "int", _("请求数据失败时,重试的最大次数")),
|
|
153
|
+
("--record_data", "-rd", "bool", _("是否记录作品数据至文件")),
|
|
154
|
+
(
|
|
155
|
+
"--image_format",
|
|
156
|
+
"-if",
|
|
157
|
+
"choice",
|
|
158
|
+
_("图文作品文件下载格式,支持:PNG、WEBP"),
|
|
159
|
+
),
|
|
160
|
+
("--live_download", "-ld", "bool", _("图文动图文件下载开关")),
|
|
161
|
+
("--download_record", "-dr", "bool", _("作品下载记录开关")),
|
|
162
|
+
(
|
|
163
|
+
"--folder_mode",
|
|
164
|
+
"-fm",
|
|
165
|
+
"bool",
|
|
166
|
+
_("是否将每个作品的文件储存至单独的文件夹"),
|
|
167
|
+
),
|
|
168
|
+
("--language", "-l", "choice", _("设置程序语言,目前支持:zh_CN、en_GB")),
|
|
169
|
+
("--settings", "-s", "str", _("读取指定配置文件")),
|
|
170
|
+
(
|
|
171
|
+
"--browser_cookie",
|
|
172
|
+
"-bc",
|
|
173
|
+
"choice",
|
|
174
|
+
fill(
|
|
175
|
+
_(
|
|
176
|
+
"从指定的浏览器读取小红书网页版 Cookie,支持:{0}; 输入浏览器名称或序号"
|
|
177
|
+
).format(
|
|
178
|
+
", ".join(
|
|
179
|
+
f"{i}: {j}"
|
|
180
|
+
for i, j in enumerate(
|
|
181
|
+
BrowserCookie.SUPPORT_BROWSER.keys(),
|
|
182
|
+
start=1,
|
|
183
|
+
)
|
|
184
|
+
)
|
|
185
|
+
),
|
|
186
|
+
width=40,
|
|
187
|
+
),
|
|
188
|
+
),
|
|
189
|
+
("--update_settings", "-us", "flag", _("是否更新配置文件")),
|
|
190
|
+
("--help", "-h", "flag", _("查看详细参数说明")),
|
|
191
|
+
("--version", "-v", "flag", _("查看 XHS-Downloader 版本")),
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
for option in options:
|
|
195
|
+
table.add_row(*option)
|
|
196
|
+
|
|
197
|
+
print(
|
|
198
|
+
Panel(
|
|
199
|
+
table,
|
|
200
|
+
border_style="bold",
|
|
201
|
+
title="XHS-Downloader CLI Parameters",
|
|
202
|
+
title_align="left",
|
|
203
|
+
)
|
|
204
|
+
)
|
|
205
|
+
ctx.exit()
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
@command(name="XHS-Downloader", help=PROJECT)
|
|
209
|
+
@option(
|
|
210
|
+
"--url",
|
|
211
|
+
"-u",
|
|
212
|
+
)
|
|
213
|
+
@option(
|
|
214
|
+
"--index",
|
|
215
|
+
"-i",
|
|
216
|
+
)
|
|
217
|
+
@option(
|
|
218
|
+
"--work_path",
|
|
219
|
+
"-wp",
|
|
220
|
+
type=Path(file_okay=False),
|
|
221
|
+
)
|
|
222
|
+
@option(
|
|
223
|
+
"--folder_name",
|
|
224
|
+
"-fn",
|
|
225
|
+
)
|
|
226
|
+
@option(
|
|
227
|
+
"--name_format",
|
|
228
|
+
"-nf",
|
|
229
|
+
)
|
|
230
|
+
# @option("--sec_ch_ua", "-su", )
|
|
231
|
+
# @option("--sec_ch_ua_platform", "-sp", )
|
|
232
|
+
@option(
|
|
233
|
+
"--user_agent",
|
|
234
|
+
"-ua",
|
|
235
|
+
)
|
|
236
|
+
@option(
|
|
237
|
+
"--cookie",
|
|
238
|
+
"-ck",
|
|
239
|
+
)
|
|
240
|
+
@option(
|
|
241
|
+
"--proxy",
|
|
242
|
+
"-p",
|
|
243
|
+
)
|
|
244
|
+
@option(
|
|
245
|
+
"--timeout",
|
|
246
|
+
"-t",
|
|
247
|
+
type=int,
|
|
248
|
+
)
|
|
249
|
+
@option(
|
|
250
|
+
"--chunk",
|
|
251
|
+
"-c",
|
|
252
|
+
type=int,
|
|
253
|
+
)
|
|
254
|
+
@option(
|
|
255
|
+
"--max_retry",
|
|
256
|
+
"-mr",
|
|
257
|
+
type=int,
|
|
258
|
+
)
|
|
259
|
+
@option(
|
|
260
|
+
"--record_data",
|
|
261
|
+
"-rd",
|
|
262
|
+
type=bool,
|
|
263
|
+
)
|
|
264
|
+
@option(
|
|
265
|
+
"--image_format",
|
|
266
|
+
"-if",
|
|
267
|
+
type=Choice(["png", "PNG", "webp", "WEBP"]),
|
|
268
|
+
)
|
|
269
|
+
@option(
|
|
270
|
+
"--live_download",
|
|
271
|
+
"-ld",
|
|
272
|
+
type=bool,
|
|
273
|
+
)
|
|
274
|
+
@option(
|
|
275
|
+
"--download_record",
|
|
276
|
+
"-dr",
|
|
277
|
+
type=bool,
|
|
278
|
+
)
|
|
279
|
+
@option(
|
|
280
|
+
"--folder_mode",
|
|
281
|
+
"-fm",
|
|
282
|
+
type=bool,
|
|
283
|
+
)
|
|
284
|
+
@option(
|
|
285
|
+
"--language",
|
|
286
|
+
"-l",
|
|
287
|
+
type=Choice(["zh_CN", "en_GB"]),
|
|
288
|
+
)
|
|
289
|
+
@option(
|
|
290
|
+
"--settings",
|
|
291
|
+
"-s",
|
|
292
|
+
type=Path(dir_okay=False),
|
|
293
|
+
)
|
|
294
|
+
@option(
|
|
295
|
+
"--browser_cookie",
|
|
296
|
+
"-bc",
|
|
297
|
+
type=Choice(
|
|
298
|
+
list(BrowserCookie.SUPPORT_BROWSER.keys())
|
|
299
|
+
+ [str(i) for i in range(1, len(BrowserCookie.SUPPORT_BROWSER) + 1)]
|
|
300
|
+
),
|
|
301
|
+
callback=CLI.read_cookie,
|
|
302
|
+
)
|
|
303
|
+
@option(
|
|
304
|
+
"--update_settings",
|
|
305
|
+
"-us",
|
|
306
|
+
type=bool,
|
|
307
|
+
is_flag=True,
|
|
308
|
+
)
|
|
309
|
+
@option(
|
|
310
|
+
"-h",
|
|
311
|
+
is_flag=True,
|
|
312
|
+
is_eager=True,
|
|
313
|
+
expose_value=False,
|
|
314
|
+
callback=CLI.help_,
|
|
315
|
+
)
|
|
316
|
+
@option(
|
|
317
|
+
"--version",
|
|
318
|
+
"-v",
|
|
319
|
+
is_flag=True,
|
|
320
|
+
is_eager=True,
|
|
321
|
+
expose_value=False,
|
|
322
|
+
callback=CLI.version,
|
|
323
|
+
)
|
|
324
|
+
@pass_context
|
|
325
|
+
def cli(ctx, **kwargs):
|
|
326
|
+
async def main():
|
|
327
|
+
async with CLI(ctx, **kwargs) as xhs:
|
|
328
|
+
await xhs.run()
|
|
329
|
+
|
|
330
|
+
run(main())
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
from typing import Callable
|
|
2
|
+
|
|
3
|
+
from rich.text import Text
|
|
4
|
+
from textual.app import ComposeResult
|
|
5
|
+
from textual.binding import Binding
|
|
6
|
+
from textual.screen import Screen
|
|
7
|
+
from textual.widgets import Footer
|
|
8
|
+
from textual.widgets import Header
|
|
9
|
+
from textual.widgets import Label
|
|
10
|
+
|
|
11
|
+
from ..module import (
|
|
12
|
+
PROJECT,
|
|
13
|
+
PROMPT,
|
|
14
|
+
MASTER,
|
|
15
|
+
INFO,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
__all__ = ["About"]
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class About(Screen):
|
|
22
|
+
BINDINGS = [
|
|
23
|
+
Binding(key="Q", action="quit", description="退出程序/Quit"),
|
|
24
|
+
Binding(key="U", action="check_update", description="检查更新/Update"),
|
|
25
|
+
Binding(key="B", action="index", description="返回首页/Back"),
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
def __init__(self, message: Callable[[str], str]):
|
|
29
|
+
super().__init__()
|
|
30
|
+
self.message = message
|
|
31
|
+
|
|
32
|
+
def compose(self) -> ComposeResult:
|
|
33
|
+
yield Header()
|
|
34
|
+
yield Label(
|
|
35
|
+
Text(
|
|
36
|
+
self.message(
|
|
37
|
+
"如果 XHS-Downloader 对您有帮助,请考虑为它点个 Star,感谢您的支持!"
|
|
38
|
+
),
|
|
39
|
+
style=INFO,
|
|
40
|
+
),
|
|
41
|
+
classes="prompt",
|
|
42
|
+
)
|
|
43
|
+
yield Label(
|
|
44
|
+
Text("Discord 社区", style=PROMPT),
|
|
45
|
+
classes="prompt",
|
|
46
|
+
)
|
|
47
|
+
yield Label(
|
|
48
|
+
f"{self.message("邀请链接:")}https://discord.com/invite/ZYtmgKud9Y"
|
|
49
|
+
)
|
|
50
|
+
yield Label(
|
|
51
|
+
Text(self.message("作者的其他开源项目"), style=PROMPT),
|
|
52
|
+
classes="prompt",
|
|
53
|
+
)
|
|
54
|
+
yield Label(
|
|
55
|
+
Text("TikTokDownloader (抖音 / TikTok)", style=MASTER),
|
|
56
|
+
classes="prompt",
|
|
57
|
+
)
|
|
58
|
+
yield Label("https://github.com/JoeanAmier/TikTokDownloader")
|
|
59
|
+
yield Label(
|
|
60
|
+
Text("KS-Downloader (快手)", style=MASTER),
|
|
61
|
+
classes="prompt",
|
|
62
|
+
)
|
|
63
|
+
yield Label("https://github.com/JoeanAmier/KS-Downloader")
|
|
64
|
+
yield Footer()
|
|
65
|
+
|
|
66
|
+
def on_mount(self) -> None:
|
|
67
|
+
self.title = PROJECT
|
|
68
|
+
|
|
69
|
+
async def action_quit(self) -> None:
|
|
70
|
+
await self.app.action_quit()
|
|
71
|
+
|
|
72
|
+
async def action_index(self):
|
|
73
|
+
await self.app.push_screen("index")
|
|
74
|
+
|
|
75
|
+
async def action_check_update(self):
|
|
76
|
+
await self.app.run_action("update_and_return")
|