发布 1.9 版本

This commit is contained in:
JoeanAmier
2024-03-15 22:21:03 +08:00
parent a997fe1a1d
commit f607be0aaf
47 changed files with 1045 additions and 589 deletions

View File

@@ -1,3 +1,6 @@
from typing import Callable
from rich.text import Text
from textual.app import ComposeResult
from textual.binding import Binding
from textual.screen import Screen
@@ -7,10 +10,9 @@ from textual.widgets import Label
from source.module import (
PROJECT,
)
from source.translator import (
Chinese,
English,
PROMPT,
MASTER,
INFO,
)
__all__ = ["About"]
@@ -32,13 +34,19 @@ class About(Screen):
description="返回首页/Back"),
]
def __init__(self, language: Chinese | English):
def __init__(self, message: Callable[[str], str]):
super().__init__()
self.prompt = language
self.message = message
def compose(self) -> ComposeResult:
yield Header()
yield Label()
yield Label(Text(self.message("如果 XHS-Downloader 对您有帮助,请考虑为它点个 Star感谢您的支持"), style=INFO),
classes="prompt", )
yield Label(Text(self.message("作者的其他开源项目"), style=PROMPT), classes="prompt", )
yield Label(Text("TikTokDownloader (抖音 / TikTok)", style=MASTER), classes="prompt", )
yield Label("https://github.com/JoeanAmier/TikTokDownloader")
yield Label(Text("KS-Downloader (快手)", style=MASTER), classes="prompt", )
yield Label("https://github.com/JoeanAmier/KS-Downloader")
yield Footer()
def on_mount(self) -> None:

View File

@@ -1,4 +1,4 @@
from typing import Type
from typing import Callable
from textual.app import App
from textual.widgets import RichLog
@@ -8,12 +8,8 @@ from source.module import (
ROOT,
)
from source.module import Settings
from source.translator import (
LANGUAGE,
Chinese,
English,
)
# from .about import About
from source.module import Translate
from .about import About
from .index import Index
from .loading import Loading
from .monitor import Monitor
@@ -31,7 +27,7 @@ class XHSDownloader(App):
def __init__(self):
super().__init__()
self.parameter: dict
self.prompt: Type[Chinese | English]
self.message: Callable[[str], str]
self.APP: XHS
self.__initialization()
@@ -44,19 +40,19 @@ class XHSDownloader(App):
def __initialization(self) -> None:
self.parameter = self.SETTINGS.run()
self.prompt = LANGUAGE.get(self.parameter["language"], Chinese)
self.APP = XHS(**self.parameter, language_object=self.prompt)
self.message = Translate(self.parameter["language"]).message()
self.APP = XHS(**self.parameter, transition=self.message)
async def on_mount(self) -> None:
self.install_screen(
Setting(
self.parameter,
self.prompt),
self.message),
name="setting")
self.install_screen(Index(self.APP, self.prompt), name="index")
self.install_screen(Loading(self.prompt), name="loading")
# self.install_screen(About(self.prompt), name="about")
self.install_screen(Record(self.APP, self.prompt), name="record")
self.install_screen(Index(self.APP, self.message), name="index")
self.install_screen(Loading(self.message), name="loading")
self.install_screen(About(self.message), name="about")
self.install_screen(Record(self.APP, self.message), name="record")
await self.push_screen("index")
async def action_settings(self):
@@ -77,24 +73,24 @@ class XHSDownloader(App):
async def refresh_screen(self):
self.pop_screen()
await self.APP.recorder.database.close()
await self.close_database()
await self.APP.close()
self.__initialization()
await self.__aenter__()
self.uninstall_screen("index")
self.uninstall_screen("setting")
self.uninstall_screen("loading")
# self.uninstall_screen("about")
self.uninstall_screen("about")
self.uninstall_screen("record")
self.install_screen(Index(self.APP, self.prompt), name="index")
self.install_screen(Index(self.APP, self.message), name="index")
self.install_screen(
Setting(
self.parameter,
self.prompt),
self.message),
name="setting")
self.install_screen(Loading(self.prompt), name="loading")
# self.install_screen(About(self.prompt), name="about")
self.install_screen(Record(self.APP, self.prompt), name="record")
self.install_screen(Loading(self.message), name="loading")
self.install_screen(About(self.message), name="about")
self.install_screen(Record(self.APP, self.message), name="record")
await self.push_screen("index")
def update_result(self, tip: str) -> None:
@@ -103,11 +99,17 @@ class XHSDownloader(App):
log.write(">" * 50)
async def action_check_update(self):
await self.push_screen(Update(self.APP, self.prompt), callback=self.update_result)
await self.push_screen(Update(self.APP, self.message), callback=self.update_result)
async def action_check_update_about(self):
await self.push_screen("index")
await self.action_check_update()
async def action_monitor(self):
await self.push_screen(Monitor(self.APP, self.prompt))
await self.push_screen(Monitor(self.APP, self.message))
async def close_database(self):
await self.APP.id_recorder.cursor.close()
await self.APP.id_recorder.database.close()
await self.APP.data_recorder.cursor.close()
await self.APP.data_recorder.database.close()

View File

@@ -1,3 +1,5 @@
from typing import Callable
from pyperclip import paste
from rich.text import Text
from textual import on
@@ -25,10 +27,6 @@ from source.module import (
REPOSITORY,
GENERAL,
)
from source.translator import (
English,
Chinese,
)
__all__ = ["Index"]
@@ -40,13 +38,13 @@ class Index(Screen):
Binding(key="s", action="settings", description="程序设置/Settings"),
Binding(key="r", action="record", description="下载记录/Record"),
Binding(key="m", action="monitor", description="开启监听/Monitor"),
# Binding(key="a", action="about", description="关于项目/About"),
Binding(key="a", action="about", description="关于项目/About"),
]
def __init__(self, app: XHS, language: Chinese | English):
def __init__(self, app: XHS, message: Callable[[str], str]):
super().__init__()
self.xhs = app
self.prompt = language
self.message = message
self.url = None
self.tip = None
@@ -55,24 +53,24 @@ class Index(Screen):
yield ScrollableContainer(
Label(
Text(
f"{self.prompt.open_source_protocol}{LICENCE}",
f"{self.message("开源协议")}: {LICENCE}",
style=MASTER)
),
Label(
Text(
f"{self.prompt.project_address}{REPOSITORY}",
f"{self.message("项目地址")}{REPOSITORY}",
style=MASTER)
),
Label(
Text(
self.prompt.input_box_title,
self.message("请输入小红书图文/视频作品链接"),
style=PROMPT), classes="prompt",
),
Input(placeholder=self.prompt.input_prompt),
Input(placeholder=self.message("多个链接之间使用空格分隔")),
HorizontalScroll(
Button(self.prompt.download_button, id="deal"),
Button(self.prompt.paste_button, id="paste"),
Button(self.prompt.reset_button, id="reset"),
Button(self.message("下载无水印作品文件"), id="deal"),
Button(self.message("读取剪贴板"), id="paste"),
Button(self.message("清空输入框"), id="reset"),
),
)
yield RichLog(markup=True, wrap=True)
@@ -82,14 +80,20 @@ class Index(Screen):
self.title = PROJECT
self.url = self.query_one(Input)
self.tip = self.query_one(RichLog)
self.tip.write(Text("\n".join(self.prompt.disclaimer), style=MASTER))
self.tip.write(
Text(
self.message("免责声明\n") +
f"\n{
">" *
50}",
style=MASTER), scroll_end=False)
@on(Button.Pressed, "#deal")
async def deal_button(self):
if self.url.value:
self.deal()
else:
self.tip.write(Text(self.prompt.invalid_link, style=WARNING))
self.tip.write(Text(self.message("未输入任何小红书作品链接"), style=WARNING))
self.tip.write(Text(">" * 50, style=GENERAL))
@on(Button.Pressed, "#reset")
@@ -106,6 +110,6 @@ class Index(Screen):
if any(await self.xhs.extract(self.url.value, True, log=self.tip)):
self.url.value = ""
else:
self.tip.write(Text(self.prompt.download_failure, style=ERROR))
self.tip.write(Text(self.message("下载小红书作品文件失败"), style=ERROR))
self.tip.write(Text(">" * 50, style=GENERAL))
self.app.pop_screen()

View File

@@ -1,25 +1,22 @@
from typing import Callable
from textual.app import ComposeResult
from textual.containers import Grid
from textual.screen import ModalScreen
from textual.widgets import Label
from textual.widgets import LoadingIndicator
from source.translator import (
English,
Chinese,
)
__all__ = ["Loading"]
class Loading(ModalScreen):
def __init__(self, language: Chinese | English):
def __init__(self, message: Callable[[str], str]):
super().__init__()
self.prompt = language
self.message = message
def compose(self) -> ComposeResult:
yield Grid(
Label(self.prompt.processing),
Label(self.message("程序处理中...")),
LoadingIndicator(),
classes="loading",
)

View File

@@ -1,3 +1,5 @@
from typing import Callable
from rich.text import Text
from textual import on
from textual import work
@@ -16,10 +18,6 @@ from source.module import (
MASTER,
INFO,
)
from source.translator import (
English,
Chinese,
)
__all__ = ["Monitor"]
@@ -30,16 +28,16 @@ class Monitor(Screen):
Binding(key="c", action="close", description="关闭监听/Close"),
]
def __init__(self, app: XHS, language: Chinese | English):
def __init__(self, app: XHS, message: Callable[[str], str]):
super().__init__()
self.xhs = app
self.prompt = language
self.message = message
def compose(self) -> ComposeResult:
yield Header()
yield Label(Text(self.prompt.monitor_mode, style=INFO), classes="prompt")
yield Label(Text(self.message("已启动监听剪贴板模式"), style=INFO), classes="prompt")
yield RichLog(markup=True, wrap=True)
yield Button(self.prompt.close_monitor, id="close")
yield Button(self.message("退出监听剪贴板模式"), id="close")
yield Footer()
@on(Button.Pressed, "#close")
@@ -54,7 +52,9 @@ class Monitor(Screen):
def on_mount(self) -> None:
self.title = PROJECT
self.query_one(RichLog).write(
Text(self.prompt.monitor_text, style=MASTER))
Text(self.message(
"程序会自动读取并提取剪贴板中的小红书作品链接,并自动下载链接对应的作品文件,如需关闭,请点击关闭按钮,或者向剪贴板写入 “close” 文本!"),
style=MASTER))
self.run_monitor()
def action_close(self):

View File

@@ -1,3 +1,5 @@
from typing import Callable
from textual import on
from textual.app import ComposeResult
from textual.containers import Grid
@@ -8,32 +10,30 @@ from textual.widgets import Input
from textual.widgets import Label
from source.application import XHS
from source.translator import (
Chinese,
English,
)
__all__ = ["Record"]
class Record(ModalScreen):
def __init__(self, app: XHS, language: Chinese | English):
def __init__(self, app: XHS, message: Callable[[str], str]):
super().__init__()
self.xhs = app
self.prompt = language
self.message = message
def compose(self) -> ComposeResult:
yield Grid(
Label(self.prompt.record_title, classes="prompt"),
Input(placeholder=self.prompt.record_placeholder, id="id", ),
Label(self.message("请输入待删除的小红书作品链接或作品 ID"), classes="prompt"),
Input(placeholder=self.message("支持输入作品 ID 或包含作品 ID 的作品链接,多个链接或 ID 之间使用空格分隔"),
id="id", ),
HorizontalScroll(
Button(self.prompt.record_enter_button, id="enter", ),
Button(self.prompt.record_close_button, id="close"), ),
Button(self.message("删除指定作品 ID"), id="enter", ),
Button(self.message("返回首页"), id="close"), ),
id="record",
)
async def delete(self, text: str):
await self.xhs.recorder.delete_many(text.split())
await self.xhs.id_recorder.delete(text)
@on(Button.Pressed, "#enter")
async def save_settings(self):

View File

@@ -1,3 +1,5 @@
from typing import Callable
from textual import on
from textual.app import ComposeResult
from textual.binding import Binding
@@ -12,12 +14,6 @@ from textual.widgets import Input
from textual.widgets import Label
from textual.widgets import Select
from source.translator import (
LANGUAGE,
Chinese,
English,
)
__all__ = ["Setting"]
@@ -27,66 +23,65 @@ class Setting(Screen):
Binding(key="b", action="index", description="返回首页/Back"),
]
def __init__(self, data: dict, language: Chinese | English):
def __init__(self, data: dict, message: Callable[[str], str]):
super().__init__()
self.data = data
self.prompt = language
self.message = message
def compose(self) -> ComposeResult:
yield Header()
yield ScrollableContainer(
Label(self.prompt.work_path, classes="params", ),
Input(self.data["work_path"], placeholder=self.prompt.work_path_placeholder, valid_empty=True,
Label(self.message("作品数据 / 文件保存根路径"), classes="params", ),
Input(self.data["work_path"], placeholder=self.message("程序根路径"), valid_empty=True,
id="work_path", ),
Label(self.prompt.folder_name, classes="params", ),
Label(self.message("作品文件储存文件夹名称"), classes="params", ),
Input(self.data["folder_name"], placeholder="Download", id="folder_name", ),
Label(self.prompt.user_agent, classes="params", ),
Input(self.data["user_agent"], placeholder=self.prompt.user_agent_placeholder, valid_empty=True,
Label(self.message("User-Agent"), classes="params", ),
Input(self.data["user_agent"], placeholder=self.message("默认 User-Agent"), valid_empty=True,
id="user_agent", ),
Label(self.prompt.cookie, classes="params", ),
Label(self.message("小红书网页版 Cookie"), classes="params", ),
Input(placeholder=self.__check_cookie(), valid_empty=True, id="cookie", ),
Label(self.prompt.proxy, classes="params", ),
Input(self.data["proxy"], placeholder=self.prompt.proxy_placeholder, valid_empty=True, id="proxy", ),
Label(self.prompt.timeout, classes="params", ),
Label(self.message("网络代理"), classes="params", ),
Input(self.data["proxy"], placeholder=self.message("不使用代理"), valid_empty=True, id="proxy", ),
Label(self.message("请求数据超时限制,单位:秒"), classes="params", ),
Input(str(self.data["timeout"]), placeholder="10", type="integer", id="timeout", ),
Label(self.prompt.chunk, classes="params", ),
Label(self.message("下载文件时,每次从服务器获取的数据块大小,单位:字节"), classes="params", ),
Input(str(self.data["chunk"]), placeholder="1048576", type="integer", id="chunk", ),
Label(self.prompt.max_retry, classes="params", ),
Label(self.message("请求数据失败时,重试的最大次数"), classes="params", ),
Input(str(self.data["max_retry"]), placeholder="5", type="integer", id="max_retry", ),
Container(
Label("", classes="params", ),
Label("", classes="params", ),
Label(self.prompt.image_format, classes="params", ),
Label(self.prompt.language, classes="params", ),
Label(self.message("图片下载格式"), classes="params", ),
Label(self.message("程序语言"), classes="params", ),
classes="horizontal-layout",
),
Container(
Checkbox(self.prompt.record_data, id="record_data", value=self.data["record_data"], ),
Checkbox(self.prompt.folder_mode, id="folder_mode", value=self.data["folder_mode"], ),
Checkbox(self.message("记录作品数据"), id="record_data", value=self.data["record_data"], ),
Checkbox(self.message("作品文件夹归档模式"), id="folder_mode", value=self.data["folder_mode"], ),
Select.from_values(
("PNG", "WEBP"),
value=self.data["image_format"],
allow_blank=False,
id="image_format"),
Select.from_values(list(LANGUAGE.keys()),
Select.from_values(["zh_CN", "en_GB"],
value=self.data["language"],
allow_blank=False,
id="language", ),
classes="horizontal-layout"),
Container(
Button(self.prompt.save_button, id="save", ),
Button(self.prompt.abandon_button, id="abandon", ),
Button(self.message("保存配置"), id="save", ),
Button(self.message("放弃更改"), id="abandon", ),
classes="settings_button", ),
)
yield Footer()
def __check_cookie(self) -> str:
if self.data["cookie"]:
return self.prompt.cookie_placeholder_true
return self.prompt.cookie_placeholder_false
return self.message("小红书网页版 Cookie无需登录参数已设置")
return self.message("小红书网页版 Cookie无需登录参数未设置")
def on_mount(self) -> None:
self.title = self.prompt.settings_title
self.title = self.message("程序设置")
@on(Button.Pressed, "#save")
def save_settings(self):

View File

@@ -1,3 +1,5 @@
from typing import Callable
from aiohttp import ClientTimeout
from rich.text import Text
from textual import work
@@ -17,23 +19,19 @@ from source.module import (
INFO,
RELEASES,
)
from source.translator import (
English,
Chinese,
)
__all__ = ["Update"]
class Update(ModalScreen):
def __init__(self, app: XHS, language: Chinese | English):
def __init__(self, app: XHS, message: Callable[[str], str]):
super().__init__()
self.xhs = app
self.prompt = language
self.message = message
def compose(self) -> ComposeResult:
yield Grid(
Label(self.prompt.check_update_notification),
Label(self.message("正在检查新版本,请稍等...")),
LoadingIndicator(),
classes="loading",
)
@@ -45,25 +43,22 @@ class Update(ModalScreen):
latest_major, latest_minor = map(
int, url.split("/")[-1].split(".", 1))
if latest_major > VERSION_MAJOR or latest_minor > VERSION_MINOR:
tip = Text(
f"{self.prompt.official_version_update(
latest_major,
latest_minor)}\n{RELEASES}",
style=WARNING)
tip = Text(f"{self.message("检测到新版本:{0}.{1}").format(
VERSION_MAJOR, VERSION_MINOR)}\n{RELEASES}", style=WARNING)
elif latest_minor == VERSION_MINOR and VERSION_BETA:
tip = Text(
f"{self.prompt.development_version_update}\n{RELEASES}",
f"{self.message("当前版本为开发版, 可更新至正式版")}\n{RELEASES}",
style=WARNING)
elif VERSION_BETA:
tip = Text(
self.prompt.latest_development_version,
self.message("当前已是最新开发版"),
style=WARNING)
else:
tip = Text(
self.prompt.latest_official_version,
self.message("当前已是最新正式版"),
style=INFO)
except ValueError:
tip = Text(self.prompt.check_update_failure, style=ERROR)
tip = Text(self.message("检测新版本失败"), style=ERROR)
self.dismiss(tip)
def on_mount(self) -> None: