优化项目交互界面

This commit is contained in:
yongquan
2024-01-11 23:02:50 +08:00
parent eba43f2172
commit 6f0cda3668
16 changed files with 188 additions and 114 deletions

View File

@@ -1,3 +1,5 @@
from typing import Type
from textual.app import App
from source.application import XHS
@@ -5,21 +7,27 @@ from source.module import (
ROOT,
)
from source.module import Settings
from source.translator import Chinese
from source.translator import LANGUAGE
from source.translator import (
LANGUAGE,
Chinese,
English,
)
from .index import Index
from .loading import Loading
from .setting import Setting
__all__ = ["XHSDownloader"]
class XHSDownloader(App):
settings = Settings(ROOT)
def __init__(self):
super().__init__()
self.settings = Settings(ROOT)
self.parameter = self.settings.run()
self.prompt = LANGUAGE.get(self.parameter["language"], Chinese)
self.APP = XHS(**self.parameter, language_object=self.prompt)
self.parameter: dict
self.prompt: Type[Chinese | English]
self.APP: XHS
self.__initialization()
async def __aenter__(self):
await self.APP.__aenter__()
@@ -28,13 +36,30 @@ class XHSDownloader(App):
async def __aexit__(self, exc_type, exc_value, traceback):
await self.APP.__aexit__(exc_type, exc_value, traceback)
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)
async def on_mount(self) -> None:
self.install_screen(Setting(), name="setting")
self.install_screen(Setting(self.parameter), name="setting")
self.install_screen(Index(self.APP, self.prompt), name="index")
self.install_screen(Loading(), name="loading")
await self.push_screen("index")
async def action_settings(self):
await self.push_screen("setting")
async def save_settings(data: dict) -> None:
self.settings.update(data)
await self.refresh_screen()
await self.push_screen("setting", save_settings)
async def action_index(self):
await self.push_screen("index")
async def refresh_screen(self):
await self.push_screen("loading")
self.uninstall_screen("setting")
self.__initialization()
self.install_screen(Setting(self.parameter), name="setting")
await self.push_screen("index")

View File

@@ -53,11 +53,9 @@ def show_state(function):
class Index(Screen):
CSS_PATH = ROOT.joinpath(
"static/css/index.tcss")
CSS_PATH = ROOT.joinpath("static/XHS-Downloader.tcss")
BINDINGS = [
Binding(key="q", action="quit", description="退出程序"),
# ("d", "toggle_dark", "切换主题"),
Binding(key="u", action="check_update", description="检查更新"),
Binding(key="m", action="user_script", description="获取脚本"),
Binding(key="s", action="settings", description="程序设置"),
@@ -74,18 +72,29 @@ class Index(Screen):
def compose(self) -> ComposeResult:
yield Header()
yield ScrollableContainer(Label(Text(f"{self.prompt.open_source_protocol}{LICENCE}", style=MASTER)),
Label(
Text(
f"{self.prompt.project_address}{REPOSITORY}",
style=MASTER)),
Label(Text(self.prompt.input_box_title,
style=PROMPT), id="prompt"),
Input(placeholder=self.prompt.input_prompt),
HorizontalScroll(Button(self.prompt.download_button, id="deal"),
Button(self.prompt.paste_button, id="paste"),
Button(self.prompt.reset_button, id="reset"), ),
)
yield ScrollableContainer(
Label(
Text(
f"{self.prompt.open_source_protocol}{LICENCE}",
style=MASTER)
),
Label(
Text(
f"{self.prompt.project_address}{REPOSITORY}",
style=MASTER)
),
Label(
Text(
self.prompt.input_box_title,
style=PROMPT), id="prompt",
),
Input(placeholder=self.prompt.input_prompt),
HorizontalScroll(
Button(self.prompt.download_button, id="deal"),
Button(self.prompt.paste_button, id="paste"),
Button(self.prompt.reset_button, id="reset"),
),
)
with Center():
yield ProgressBar(total=None, show_percentage=False, show_eta=False)
yield RichLog(markup=True)

10
source/TUI/loading.py Normal file
View File

@@ -0,0 +1,10 @@
from textual.app import ComposeResult
from textual.screen import Screen
from textual.widgets import LoadingIndicator
__all__ = ["Loading"]
class Loading(Screen):
def compose(self) -> ComposeResult:
yield LoadingIndicator()

View File

@@ -1,9 +1,16 @@
from textual import on
from textual.app import ComposeResult
from textual.binding import Binding
from textual.containers import Container
from textual.containers import ScrollableContainer
from textual.screen import Screen
from textual.widgets import Button
from textual.widgets import Checkbox
from textual.widgets import Footer
from textual.widgets import Header
from textual.widgets import Input
from textual.widgets import Label
from textual.widgets import Select
from source.module import ROOT
@@ -11,17 +18,83 @@ __all__ = ["Setting"]
class Setting(Screen):
CSS_PATH = ROOT.joinpath(
"static/css/setting.tcss")
CSS_PATH = ROOT.joinpath("static/XHS-Downloader.tcss")
BINDINGS = [
Binding(key="q", action="quit", description="退出程序"),
Binding(key="b", action="index", description="返回首页"),
]
def __init__(self, data: dict):
super().__init__()
self.data = data
def compose(self) -> ComposeResult:
yield Header()
yield Label("我是设置页,敬请期待!")
yield ScrollableContainer(
Label("工作路径:", classes="params", ),
Input(self.data["work_path"], placeholder="程序根路径", valid_empty=True, id="work_path", ),
Label("文件夹名称:", classes="params", ),
Input(self.data["folder_name"], placeholder="Download", id="folder_name", ),
Label("User-Agent", classes="params", ),
Input(self.data["user_agent"], placeholder="默认 UA", valid_empty=True, id="user_agent", ),
Label("Cookie", classes="params", ),
Input(self.data["cookie"], placeholder="内置 Cookie建议自行设置", valid_empty=True, id="cookie", ),
Label("网络代理:", classes="params", ),
Input(self.data["proxy"], placeholder="无代理", valid_empty=True, id="proxy", ),
Label("请求超时限制:", classes="params", ),
Input(str(self.data["timeout"]), placeholder="10", type="integer", id="timeout", ),
Label("数据块大小:", classes="params", ),
Input(str(self.data["chunk"]), placeholder="1048576", type="integer", id="chunk", ),
Label("最大重试次数:", classes="params", ),
Input(str(self.data["max_retry"]), placeholder="5", type="integer", id="max_retry", ),
Container(
Label("", classes="params", ),
Label("", classes="params", ),
Label("图片下载格式", classes="params", ),
Label("程序语言", classes="params", ),
classes="horizontal-layout",
),
Container(
Checkbox("记录作品数据", id="record_data", value=self.data["record_data"], ),
Checkbox("文件夹归档模式", 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(("zh-CN", "en-US"),
value=self.data["language"],
allow_blank=False,
id="language",
disabled=True, ),
classes="horizontal-layout"),
Container(
Button("保存设置", id="save", ),
Button("放弃更改", id="abandon", ),
classes="settings_button", ),
)
yield Footer()
def on_mount(self) -> None:
self.title = "程序设置"
@on(Button.Pressed, "#save")
def save_settings(self):
self.dismiss({
"work_path": self.query_one("#work_path").value,
"folder_name": self.query_one("#folder_name").value,
"user_agent": self.query_one("#user_agent").value,
"cookie": self.query_one("#cookie").value,
"proxy": self.query_one("#proxy").value or None,
"timeout": int(self.query_one("#timeout").value),
"chunk": int(self.query_one("#chunk").value),
"max_retry": int(self.query_one("#max_retry").value),
"record_data": self.query_one("#record_data").value,
"image_format": self.query_one("#image_format").value,
"folder_mode": self.query_one("#folder_mode").value,
"language": self.query_one("#language").value,
})
@on(Button.Pressed, "#abandon")
def reset(self):
self.dismiss(self.data)

View File

@@ -9,16 +9,17 @@ from source.module import (
WARNING,
)
from source.module import logging
from source.module import wait
from source.translator import (
LANGUAGE,
Chinese,
English,
)
from .Downloader import Download
from .Explore import Explore
from .Html import Html
from .Image import Image
from .Video import Video
from .download import Download
from .explore import Explore
from .image import Image
from .request import Html
from .video import Video
__all__ = ["XHS"]
@@ -89,7 +90,7 @@ class XHS:
logging(log, self.prompt.download_link_error, ERROR)
self.manager.save_data(path, name, container)
async def extract(self, url: str, download=False, log=None, bar=None) -> list[dict]:
async def extract(self, url: str, download=False, efficient=False, log=None, bar=None) -> list[dict]:
# return # 调试代码
urls = await self.__extract_links(url, log)
if not urls:
@@ -97,7 +98,7 @@ class XHS:
else:
logging(log, self.prompt.pending_processing(len(urls)))
# return urls # 调试代码
return [await self.__deal_extract(i, download, log, bar) for i in urls]
return [await self.__deal_extract(i, download, efficient, log, bar) for i in urls]
async def __extract_links(self, url: str, log) -> list:
urls = []
@@ -111,13 +112,14 @@ class XHS:
urls.append(u.group())
return urls
async def __deal_extract(self, url: str, download: bool, log, bar):
async def __deal_extract(self, url: str, download: bool, efficient: bool, log, bar):
logging(log, self.prompt.start_processing(url))
html = await self.html.request_url(url, log=log)
namespace = self.__generate_data_object(html)
if not namespace:
logging(log, self.prompt.get_data_failure(url), ERROR)
return {}
await self.__suspend(efficient)
data = self.explore.run(namespace)
# logging(log, data) # 调试代码
if not data:
@@ -140,7 +142,15 @@ class XHS:
def __naming_rules(self, data: dict) -> str:
"""下载文件默认使用 作品标题 或 作品 ID 作为文件名称,可修改此方法自定义文件名称格式"""
return self.manager.filter_name(data["品标题"]) or data["ID"]
author = self.manager.filter_name(data["者昵称"]) or data["ID"]
title = self.manager.filter_name(data["作品标题"]) or data["作品ID"]
return f"{author}-{title}"
@staticmethod
async def __suspend(efficient: bool) -> None:
if efficient:
return
await wait()
async def __aenter__(self):
return self

View File

@@ -53,6 +53,8 @@ class Download:
async def __download(self, url: str, path: Path, name: str, format_: str, log, bar):
try:
async with self.session.get(url, proxy=self.proxy) as response:
if response.status != 200:
return False
suffix = self.__extract_type(
response.headers.get("Content-Type")) or format_
temp = self.temp.joinpath(name)

View File

@@ -1,5 +1,5 @@
from source.expansion import Namespace
from .Html import Html
from .request import Html
__all__ = ['Image']

View File

@@ -27,6 +27,8 @@ class Html:
url,
proxy=self.proxy,
) as response:
if response.status != 200:
return ""
return await response.text() if content else str(response.url)
except ClientError as error:
logging(log, str(error), ERROR)

View File

@@ -1,5 +1,5 @@
from source.expansion import Namespace
from .Html import Html
from .request import Html
__all__ = ['Video']

View File

@@ -29,4 +29,4 @@ def logging(log, text, style=INFO):
async def wait():
await sleep(randint(15, 35) * 0.1)
await sleep(randint(15, 45) * 0.1)

View File

@@ -1,9 +1,7 @@
from .template import Language
__all__ = ["Chinese"]
class Chinese(Language):
class Chinese:
code: str = "zh-CN"
disclaimer: tuple[str] = (
"关于 XHS-Downloader 的 免责声明:",

View File

@@ -1,7 +1,7 @@
from .template import Language
from .chinese import Chinese
__all__ = ["English"]
class English(Language):
class English(Chinese):
pass

View File

@@ -1,69 +0,0 @@
__all__ = ["Language"]
class Language:
code: str = None
disclaimer: tuple[str] = None
download_link_error: str = None
extract_link_failure: str = None
invalid_link: str = None
download_failure: str = None
check_update_notification: str = None
development_version_update: str = None
latest_development_version: str = None
latest_official_version: str = None
check_update_failure: str = None
open_source_protocol: str = None
project_address: str = None
input_box_title: str = None
input_prompt: str = None
download_button: str = None
paste_button: str = None
reset_button: str = None
exit_program: str = None
check_updates: str = None
get_script: str = None
choose_language: str = None
@staticmethod
def request_error(url: str) -> str:
pass
@staticmethod
def skip_download(name: str) -> str:
pass
@staticmethod
def download_success(name: str) -> str:
pass
@staticmethod
def download_error(name: str) -> str:
pass
@staticmethod
def pending_processing(num: int) -> str:
pass
@staticmethod
def start_processing(url: str) -> str:
pass
@staticmethod
def get_data_failure(url: str) -> str:
pass
@staticmethod
def extract_data_failure(url: str) -> str:
pass
@staticmethod
def processing_completed(url: str) -> str:
pass
@staticmethod
def official_version_update(major: int, minor: int) -> str:
pass

View File

@@ -3,10 +3,21 @@ Button {
margin: 1 1;
text-style: bold;
}
Button#deal, Button#paste {
.vertical-layout {
layout: vertical;
height: auto;
}
.horizontal-layout, .settings_button {
layout: horizontal;
height: auto;
}
.horizontal-layout > * {
width: 25vw;
}
Button#deal, Button#paste, Button#save {
tint: #27ae60 60%;
}
Button#reset {
Button#reset, Button#abandon {
tint: #c0392b 60%;
}
Label {
@@ -15,6 +26,9 @@ Label {
content-align-vertical: middle;
text-style: bold;
}
Label.params {
margin: 1 0 0 0;
}
Label#prompt {
padding: 1;
}