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.
Files changed (77) hide show
  1. parsehub/__init__.py +7 -0
  2. parsehub/config/__init__.py +3 -0
  3. parsehub/config/config.py +31 -0
  4. parsehub/deps/submodule_manager.py +31 -0
  5. parsehub/deps/xhs/locale/po_to_mo.py +25 -0
  6. parsehub/deps/xhs/main.py +94 -0
  7. parsehub/deps/xhs/source/CLI/__init__.py +3 -0
  8. parsehub/deps/xhs/source/CLI/main.py +330 -0
  9. parsehub/deps/xhs/source/TUI/__init__.py +3 -0
  10. parsehub/deps/xhs/source/TUI/about.py +76 -0
  11. parsehub/deps/xhs/source/TUI/app.py +114 -0
  12. parsehub/deps/xhs/source/TUI/index.py +142 -0
  13. parsehub/deps/xhs/source/TUI/loading.py +22 -0
  14. parsehub/deps/xhs/source/TUI/monitor.py +76 -0
  15. parsehub/deps/xhs/source/TUI/progress.py +9 -0
  16. parsehub/deps/xhs/source/TUI/record.py +55 -0
  17. parsehub/deps/xhs/source/TUI/setting.py +251 -0
  18. parsehub/deps/xhs/source/TUI/update.py +86 -0
  19. parsehub/deps/xhs/source/__init__.py +11 -0
  20. parsehub/deps/xhs/source/application/__init__.py +3 -0
  21. parsehub/deps/xhs/source/application/app.py +522 -0
  22. parsehub/deps/xhs/source/application/download.py +327 -0
  23. parsehub/deps/xhs/source/application/explore.py +66 -0
  24. parsehub/deps/xhs/source/application/image.py +50 -0
  25. parsehub/deps/xhs/source/application/request.py +90 -0
  26. parsehub/deps/xhs/source/application/video.py +20 -0
  27. parsehub/deps/xhs/source/expansion/__init__.py +9 -0
  28. parsehub/deps/xhs/source/expansion/browser.py +111 -0
  29. parsehub/deps/xhs/source/expansion/cleaner.py +92 -0
  30. parsehub/deps/xhs/source/expansion/converter.py +64 -0
  31. parsehub/deps/xhs/source/expansion/file_folder.py +25 -0
  32. parsehub/deps/xhs/source/expansion/namespace.py +84 -0
  33. parsehub/deps/xhs/source/expansion/truncate.py +35 -0
  34. parsehub/deps/xhs/source/module/__init__.py +40 -0
  35. parsehub/deps/xhs/source/module/extend.py +5 -0
  36. parsehub/deps/xhs/source/module/manager.py +259 -0
  37. parsehub/deps/xhs/source/module/model.py +14 -0
  38. parsehub/deps/xhs/source/module/recorder.py +127 -0
  39. parsehub/deps/xhs/source/module/settings.py +72 -0
  40. parsehub/deps/xhs/source/module/static.py +79 -0
  41. parsehub/deps/xhs/source/module/tools.py +34 -0
  42. parsehub/deps/xhs/source/module/translator.py +27 -0
  43. parsehub/main.py +39 -0
  44. parsehub/parsers/__init__.py +0 -0
  45. parsehub/parsers/base/__init__.py +0 -0
  46. parsehub/parsers/base/base.py +54 -0
  47. parsehub/parsers/base/yt_dlp_parser.py +158 -0
  48. parsehub/parsers/parser/__init__.py +0 -0
  49. parsehub/parsers/parser/bilibili.py +134 -0
  50. parsehub/parsers/parser/douyin.py +101 -0
  51. parsehub/parsers/parser/facebook.py +11 -0
  52. parsehub/parsers/parser/instagram.py +50 -0
  53. parsehub/parsers/parser/tieba.py +99 -0
  54. parsehub/parsers/parser/twitter.py +113 -0
  55. parsehub/parsers/parser/weibo.py +59 -0
  56. parsehub/parsers/parser/xhs_.py +50 -0
  57. parsehub/parsers/parser/youtube.py +27 -0
  58. parsehub/tools/__init__.py +2 -0
  59. parsehub/tools/llm.py +27 -0
  60. parsehub/tools/transcriptions.py +110 -0
  61. parsehub/types/__init__.py +11 -0
  62. parsehub/types/error.py +1 -0
  63. parsehub/types/media.py +87 -0
  64. parsehub/types/parse_result.py +267 -0
  65. parsehub/types/subtitles.py +53 -0
  66. parsehub/types/summary_result.py +6 -0
  67. parsehub/utiles/bilibili_api.py +283 -0
  68. parsehub/utiles/download_file.py +76 -0
  69. parsehub/utiles/img_host.py +61 -0
  70. parsehub/utiles/utile.py +88 -0
  71. parsehub/utiles/weibo_api.py +227 -0
  72. parsehub/utiles/whisper_api.py +72 -0
  73. parsehub-1.0.0.dist-info/LICENSE +21 -0
  74. parsehub-1.0.0.dist-info/METADATA +80 -0
  75. parsehub-1.0.0.dist-info/RECORD +77 -0
  76. parsehub-1.0.0.dist-info/WHEEL +5 -0
  77. parsehub-1.0.0.dist-info/top_level.txt +1 -0
parsehub/__init__.py ADDED
@@ -0,0 +1,7 @@
1
+ from .deps.submodule_manager import SubmoduleManager
2
+ from .main import ParseHub
3
+
4
+ manager = SubmoduleManager()
5
+ manager.setup_all()
6
+
7
+ __all__ = ["ParseHub"]
@@ -0,0 +1,3 @@
1
+ from .config import ParseHubConfig
2
+
3
+ __all__ = ["ParseHubConfig"]
@@ -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,3 @@
1
+ from .main import cli
2
+
3
+ __all__ = ["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,3 @@
1
+ from .app import XHSDownloader
2
+
3
+ __all__ = ["XHSDownloader"]
@@ -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")