diff --git a/README.md b/README.md
index e047c7d..6f61cfc 100644
--- a/README.md
+++ b/README.md
@@ -25,7 +25,9 @@
✅ 持久化储存作品信息至文件
✅ 作品文件储存至单独文件夹
✅ 后台监听剪贴板下载作品
+✅ 记录已下载作品 ID
☑️ 支持 API 调用功能
+☑️ 支持命令行参数下载作品文件
脚本功能
- ✅ 下载小红书无水印作品文件
@@ -38,11 +40,11 @@
📸 程序截图
🎥 点击图片观看演示视频
-
+
-
+
-
+
🔗 支持链接
-# 示例链接
-error_link = "https://github.com/JoeanAmier/XHS_Downloader"
-demo_link = "https://www.xiaohongshu.com/explore/xxxxxxxxxx"
-multiple_links = f"{demo_link} {demo_link} {demo_link}"
-# 实例对象
-work_path = "D:\\" # 作品数据/文件保存根路径,默认值:项目根路径
-folder_name = "Download" # 作品文件储存文件夹名称(自动创建),默认值:Download
-user_agent = "" # 请求头 User-Agent
-cookie = "" # 小红书网页版 Cookie,无需登录
-proxy = None # 网络代理
-timeout = 5 # 请求数据超时限制,单位:秒,默认值:10
-chunk = 1024 * 1024 * 10 # 下载文件时,每次从服务器获取的数据块大小,单位:字节
-max_retry = 2 # 请求数据失败时,重试的最大次数,单位:秒,默认值:5
-record_data = False # 是否记录作品数据至文件
-image_format = "WEBP" # 图文作品文件下载格式,支持:PNG、WEBP
-folder_mode = False # 是否将每个作品的文件储存至单独的文件夹
-async with XHS() as xhs:
- pass # 使用默认参数
-async with XHS(work_path=work_path,
- folder_name=folder_name,
- user_agent=user_agent,
- cookie=cookie,
- proxy=proxy,
- timeout=timeout,
- chunk=chunk,
- max_retry=max_retry,
- record_data=record_data,
- image_format=image_format,
- folder_mode=folder_mode,
- ) as xhs: # 使用自定义参数
- download = True # 是否下载作品文件,默认值:False
- # 返回作品详细信息,包括下载地址
- print(await xhs.extract(error_link, download)) # 获取数据失败时返回空字典
- print(await xhs.extract(demo_link, download))
- print(await xhs.extract(multiple_links, download)) # 支持传入多个作品链接
+async def example():
+ """通过代码设置参数,适合二次开发"""
+ # 示例链接
+ error_link = "https://github.com/JoeanAmier/XHS_Downloader"
+ demo_link = "https://www.xiaohongshu.com/explore/xxxxxxxxxx"
+ multiple_links = f"{demo_link} {demo_link} {demo_link}"
+ # 实例对象
+ work_path = "D:\\" # 作品数据/文件保存根路径,默认值:项目根路径
+ folder_name = "Download" # 作品文件储存文件夹名称(自动创建),默认值:Download
+ user_agent = "" # 请求头 User-Agent,可选参数
+ cookie = "" # 小红书网页版 Cookie,无需登录,必需参数
+ proxy = None # 网络代理
+ timeout = 5 # 请求数据超时限制,单位:秒,默认值:10
+ chunk = 1024 * 1024 * 10 # 下载文件时,每次从服务器获取的数据块大小,单位:字节
+ max_retry = 2 # 请求数据失败时,重试的最大次数,单位:秒,默认值:5
+ record_data = False # 是否记录作品数据至文件
+ image_format = "WEBP" # 图文作品文件下载格式,支持:PNG、WEBP
+ folder_mode = False # 是否将每个作品的文件储存至单独的文件夹
+ async with XHS() as xhs:
+ pass # 使用默认参数
+ async with XHS(work_path=work_path,
+ folder_name=folder_name,
+ user_agent=user_agent,
+ cookie=cookie,
+ proxy=proxy,
+ timeout=timeout,
+ chunk=chunk,
+ max_retry=max_retry,
+ record_data=record_data,
+ image_format=image_format,
+ folder_mode=folder_mode,
+ ) as xhs: # 使用自定义参数
+ download = True # 是否下载作品文件,默认值:False
+ efficient = True # 高效模式,禁用请求延时
+ # 返回作品详细信息,包括下载地址
+ print(await xhs.extract(error_link, download, efficient)) # 获取数据失败时返回空字典
+ print(await xhs.extract(demo_link, download, efficient))
+ print(await xhs.extract(multiple_links, download, efficient)) # 支持传入多个作品链接
⚙️ 配置文件
项目根目录下的 settings.json
文件,首次运行自动生成,可以自定义部分运行参数。
@@ -199,14 +204,19 @@ async with XHS(work_path=work_path,
🌐 Cookie
-- 打开浏览器(可选无痕模式启动),访问小红书任意网页
-- 按
F12
打开开发人员工具
-- 选择
控制台
选项卡
-- 输入
document.cookie
后回车确认
-- 输出内容即为所需 Cookie
+- 打开浏览器(可选无痕模式启动),访问
https://www.xiaohongshu.com/explore
+- 按下
F12
打开开发人员工具
+- 选择
网络
选项卡
+- 选择
Fetch/XHR
筛选器
+- 点击小红书页面任意作品
+- 在
网络
选项卡挑选包含 Cookie 的数据包
+- 检查 Cookie 是否包含
web_session
字段
+- 全选复制包含
web_session
字段的 Cookie
+🗳 下载记录
+XHS-Downloader 会将下载过的作品 ID 储存至数据库,当重复下载相同的作品时,XHS-Downloader 会自动跳过该作品的文件下载(即使作品文件不存在),如果想要重新下载作品文件,请先删除数据库中对应的作品 ID,再使用 XHS-Downloader 下载作品文件!
♥️ 支持项目
如果 XHS-Downloader 对您有帮助,请考虑为它点个 Star ⭐,感谢您的支持!
@@ -230,6 +240,9 @@ async with XHS(work_path=work_path,
Email: yonglelolu@gmail.com
+如果您在使用 XHS-Downloader 的时候遇到问题,请先阅读《提问的智慧》,然后加入 QQ 群聊寻求帮助!
+
+
如果您通过 Email 联系我,我可能无法及时查看并回复信息,我会尽力在七天内回复您的邮件;如果有紧急事项或需要更快的回复,请通过其他方式与我联系,谢谢理解!
如果您对抖音 / TikTok 感兴趣,可以了解一下我的另一个开源项目 TikTokDownloader
diff --git a/main.py b/main.py
index 374a4fb..f429a88 100644
--- a/main.py
+++ b/main.py
@@ -13,8 +13,8 @@ async def example():
# 实例对象
work_path = "D:\\" # 作品数据/文件保存根路径,默认值:项目根路径
folder_name = "Download" # 作品文件储存文件夹名称(自动创建),默认值:Download
- user_agent = "" # 请求头 User-Agent
- cookie = "" # 小红书网页版 Cookie,无需登录
+ user_agent = "" # 请求头 User-Agent,可选参数
+ cookie = "" # 小红书网页版 Cookie,无需登录,必需参数
proxy = None # 网络代理
timeout = 5 # 请求数据超时限制,单位:秒,默认值:10
chunk = 1024 * 1024 * 10 # 下载文件时,每次从服务器获取的数据块大小,单位:字节
@@ -37,10 +37,11 @@ async def example():
folder_mode=folder_mode,
) as xhs: # 使用自定义参数
download = True # 是否下载作品文件,默认值:False
+ efficient = True # 高效模式,禁用请求延时
# 返回作品详细信息,包括下载地址
- print(await xhs.extract(error_link, download)) # 获取数据失败时返回空字典
- print(await xhs.extract(demo_link, download))
- print(await xhs.extract(multiple_links, download)) # 支持传入多个作品链接
+ print(await xhs.extract(error_link, download, efficient)) # 获取数据失败时返回空字典
+ print(await xhs.extract(demo_link, download, efficient))
+ print(await xhs.extract(multiple_links, download, efficient)) # 支持传入多个作品链接
async def main():
diff --git a/requirements.txt b/requirements.txt
index ab6a106..077ea43 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -3,4 +3,4 @@ textual>=0.47.1
pyperclip>=1.8.2
lxml>=5.1.0
PyYAML>=6.0.1
-aiosqlite>=0.19.0
+aiosqlite>=0.20.0
diff --git a/source/TUI/about.py b/source/TUI/about.py
new file mode 100644
index 0000000..3fa19ee
--- /dev/null
+++ b/source/TUI/about.py
@@ -0,0 +1,45 @@
+from textual.app import ComposeResult
+from textual.binding import Binding
+from textual.screen import Screen
+from textual.widgets import Footer
+from textual.widgets import Header
+from textual.widgets import Label
+
+from source.module import (
+ PROJECT,
+)
+from source.translator import (
+ Chinese,
+ English,
+)
+
+__all__ = ["About"]
+
+
+class About(Screen):
+ BINDINGS = [
+ Binding(
+ key="q",
+ action="quit",
+ description="退出程序/Quit"),
+ Binding(
+ key="u",
+ action="check_update_about",
+ description="检查更新/Update"),
+ Binding(
+ key="b",
+ action="index",
+ description="返回首页/Back"),
+ ]
+
+ def __init__(self, language: Chinese | English):
+ super().__init__()
+ self.prompt = language
+
+ def compose(self) -> ComposeResult:
+ yield Header()
+ yield Label()
+ yield Footer()
+
+ def on_mount(self) -> None:
+ self.title = PROJECT
diff --git a/source/TUI/app.py b/source/TUI/app.py
index 34107ce..09d64ea 100644
--- a/source/TUI/app.py
+++ b/source/TUI/app.py
@@ -13,9 +13,11 @@
Chinese,
English,
)
+# from .about import About
from .index import Index
from .loading import Loading
from .monitor import Monitor
+from .record import Record
from .setting import Setting
from .update import Update
@@ -53,6 +55,8 @@ async def on_mount(self) -> None:
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")
await self.push_screen("index")
async def action_settings(self):
@@ -62,15 +66,26 @@ async def save_settings(data: dict) -> None:
await self.push_screen("setting", save_settings)
+ async def action_about(self):
+ await self.push_screen("about")
+
async def action_index(self):
await self.push_screen("index")
+ async def action_record(self):
+ await self.push_screen("record")
+
async def refresh_screen(self):
self.pop_screen()
+ await self.APP.recorder.database.close()
+ 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("record")
self.install_screen(Index(self.APP, self.prompt), name="index")
self.install_screen(
Setting(
@@ -78,13 +93,21 @@ async def refresh_screen(self):
self.prompt),
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")
await self.push_screen("index")
def update_result(self, tip: str) -> None:
- self.query_one(RichLog).write(tip)
+ log = self.query_one(RichLog)
+ log.write(tip)
+ log.write(">" * 50)
async def action_check_update(self):
await self.push_screen(Update(self.APP, self.prompt), callback=self.update_result)
- async def action_clipboard(self):
+ 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))
diff --git a/source/TUI/index.py b/source/TUI/index.py
index f9ae2b8..d8cd2ca 100644
--- a/source/TUI/index.py
+++ b/source/TUI/index.py
@@ -1,9 +1,7 @@
-from asyncio import create_task
-from webbrowser import open
-
from pyperclip import paste
from rich.text import Text
from textual import on
+from textual import work
from textual.app import ComposeResult
from textual.binding import Binding
from textual.containers import HorizontalScroll
@@ -26,7 +24,6 @@
LICENCE,
REPOSITORY,
GENERAL,
- USERSCRIPT,
)
from source.translator import (
English,
@@ -40,9 +37,10 @@ class Index(Screen):
BINDINGS = [
Binding(key="q", action="quit", description="退出程序/Quit"),
Binding(key="u", action="check_update", description="检查更新/Update"),
- Binding(key="m", action="user_script", description="获取脚本/Script"),
Binding(key="s", action="settings", description="程序设置/Settings"),
- Binding(key="c", action="clipboard", description="监听链接/ClipBoard"),
+ Binding(key="r", action="record", description="下载记录/Record"),
+ Binding(key="m", action="monitor", description="开启监听/Monitor"),
+ # Binding(key="a", action="about", description="关于项目/About"),
]
def __init__(self, app: XHS, language: Chinese | English):
@@ -68,7 +66,7 @@ def compose(self) -> ComposeResult:
Label(
Text(
self.prompt.input_box_title,
- style=PROMPT), id="prompt",
+ style=PROMPT), classes="prompt",
),
Input(placeholder=self.prompt.input_prompt),
HorizontalScroll(
@@ -89,7 +87,7 @@ def on_mount(self) -> None:
@on(Button.Pressed, "#deal")
async def deal_button(self):
if self.url.value:
- await create_task(self.deal())
+ self.deal()
else:
self.tip.write(Text(self.prompt.invalid_link, style=WARNING))
self.tip.write(Text(">" * 50, style=GENERAL))
@@ -102,6 +100,7 @@ def reset_button(self):
def paste_button(self):
self.query_one(Input).value = paste()
+ @work()
async def deal(self):
await self.app.push_screen("loading")
if any(await self.xhs.extract(self.url.value, True, log=self.tip)):
@@ -109,7 +108,3 @@ async def deal(self):
else:
self.tip.write(Text(self.prompt.download_failure, style=ERROR))
self.app.pop_screen()
-
- @staticmethod
- def action_user_script():
- open(USERSCRIPT)
diff --git a/source/TUI/monitor.py b/source/TUI/monitor.py
index 0b08325..5d017af 100644
--- a/source/TUI/monitor.py
+++ b/source/TUI/monitor.py
@@ -37,7 +37,7 @@ def __init__(self, app: XHS, language: Chinese | English):
def compose(self) -> ComposeResult:
yield Header()
- yield Label(Text(self.prompt.monitor_mode, style=INFO), id="monitor")
+ yield Label(Text(self.prompt.monitor_mode, style=INFO), classes="prompt")
yield RichLog(markup=True, wrap=True)
yield Button(self.prompt.close_monitor, id="close")
yield Footer()
diff --git a/source/TUI/record.py b/source/TUI/record.py
new file mode 100644
index 0000000..1cdb0f2
--- /dev/null
+++ b/source/TUI/record.py
@@ -0,0 +1,46 @@
+from textual import on
+from textual.app import ComposeResult
+from textual.containers import Grid
+from textual.containers import HorizontalScroll
+from textual.screen import ModalScreen
+from textual.widgets import Button
+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):
+ super().__init__()
+ self.xhs = app
+ self.prompt = language
+
+ def compose(self) -> ComposeResult:
+ yield Grid(
+ Label(self.prompt.record_title, classes="prompt"),
+ Input(placeholder=self.prompt.record_placeholder, id="id", ),
+ HorizontalScroll(
+ Button(self.prompt.record_enter_button, id="enter", ),
+ Button(self.prompt.record_close_button, id="close"), ),
+ id="record",
+ )
+
+ async def delete(self, text: str):
+ await self.xhs.recorder.delete_many(text.split())
+
+ @on(Button.Pressed, "#enter")
+ async def save_settings(self):
+ text = self.query_one(Input)
+ await self.delete(text.value)
+ text.value = ""
+
+ @on(Button.Pressed, "#close")
+ def reset(self):
+ self.dismiss()
diff --git a/source/application/app.py b/source/application/app.py
index 7910504..59241e4 100644
--- a/source/application/app.py
+++ b/source/application/app.py
@@ -10,6 +10,7 @@
from source.expansion import Converter
from source.expansion import Namespace
+from source.module import IDRecorder
from source.module import Manager
from source.module import (
ROOT,
@@ -81,6 +82,7 @@ def __init__(
self.explore = Explore()
self.convert = Converter()
self.download = Download(self.manager)
+ self.recorder = IDRecorder(self.manager)
self.clipboard_cache: str = ""
self.queue = Queue()
self.event = Event()
@@ -96,11 +98,19 @@ async def __download_files(self, container: dict, download: bool, log, bar):
name = self.__naming_rules(container)
path = self.manager.folder
if (u := container["下载地址"]) and download:
- path = await self.download.run(u, name, container["作品类型"], log, bar)
+ if await self.skip_download(i := container["作品ID"]):
+ logging(log, self.prompt.exist_record(i))
+ else:
+ path, result = await self.download.run(u, name, container["作品类型"], log, bar)
+ await self.__add_record(i, result)
elif not u:
logging(log, self.prompt.download_link_error, ERROR)
self.manager.save_data(path, name, container)
+ async def __add_record(self, id_: str, result: tuple) -> None:
+ if all(result):
+ await self.recorder.add(id_)
+
async def extract(self, url: str, download=False, efficient=False, log=None, bar=None) -> list[dict]:
# return # 调试代码
urls = await self.__extract_links(url, log)
@@ -179,6 +189,9 @@ async def __receive_link(self, delay: int, *args, **kwargs):
def stop_monitor(self):
self.event.set()
+ async def skip_download(self, id_: str) -> bool:
+ return bool(await self.recorder.select(id_))
+
@staticmethod
async def __suspend(efficient: bool) -> None:
if efficient:
@@ -186,9 +199,11 @@ async def __suspend(efficient: bool) -> None:
await wait()
async def __aenter__(self):
+ await self.recorder.__aenter__()
return self
async def __aexit__(self, exc_type, exc_value, traceback):
+ await self.recorder.__aexit__(exc_type, exc_value, traceback)
await self.close()
async def close(self):
diff --git a/source/application/download.py b/source/application/download.py
index db39e47..616cb92 100644
--- a/source/application/download.py
+++ b/source/application/download.py
@@ -33,7 +33,7 @@ def __init__(self, manager: Manager, ):
self.video_format = "mp4"
self.image_format = manager.image_format
- async def run(self, urls: list, name: str, type_: str, log, bar) -> Path:
+ async def run(self, urls: list, name: str, type_: str, log, bar) -> tuple[Path, tuple]:
path = self.__generate_path(name)
match type_:
case "视频":
@@ -52,8 +52,8 @@ async def run(self, urls: list, name: str, type_: str, log, bar) -> Path:
bar) for url,
name,
format_ in tasks]
- await gather(*tasks)
- return path
+ result = await gather(*tasks)
+ return path, result
def __generate_path(self, name: str):
path = self.manager.archive(self.folder, name, self.folder_mode)
diff --git a/source/module/__init__.py b/source/module/__init__.py
index 245563f..8d81284 100644
--- a/source/module/__init__.py
+++ b/source/module/__init__.py
@@ -1,6 +1,6 @@
from .extend import Account
from .manager import Manager
-from .recorder import Recorder
+from .recorder import IDRecorder
from .settings import Settings
from .static import (
VERSION_MAJOR,
@@ -31,7 +31,7 @@
__all__ = [
"Account",
"Settings",
- "Recorder",
+ "IDRecorder",
"Manager",
"VERSION_MAJOR",
"VERSION_MINOR",
diff --git a/source/module/recorder.py b/source/module/recorder.py
index d5d55bd..4eb9afc 100644
--- a/source/module/recorder.py
+++ b/source/module/recorder.py
@@ -1,5 +1,46 @@
-__all__ = ["Recorder"]
+from aiosqlite import connect
+from source.module import Manager
-class Recorder:
- pass
+__all__ = ["IDRecorder"]
+
+
+class IDRecorder:
+ def __init__(self, manager: Manager):
+ self.file = manager.root.joinpath("XHS-Downloader.db")
+ self.database = None
+ self.cursor = None
+
+ async def __connect_database(self):
+ self.database = await connect(self.file)
+ self.cursor = await self.database.cursor()
+ await self.cursor.execute("CREATE TABLE IF NOT EXISTS explore_ids (ID TEXT PRIMARY KEY);")
+ await self.database.commit()
+
+ async def select(self, id_: str):
+ await self.cursor.execute("SELECT ID FROM explore_ids WHERE ID=?", (id_,))
+ return await self.cursor.fetchone()
+
+ async def add(self, id_: str) -> None:
+ await self.cursor.execute("REPLACE INTO explore_ids VALUES (?);", (id_,))
+ await self.database.commit()
+
+ async def delete(self, id_: str) -> None:
+ if id_:
+ await self.cursor.execute("DELETE FROM explore_ids WHERE ID=?", (id_,))
+ await self.database.commit()
+
+ async def delete_many(self, ids: list | tuple):
+ [await self.delete(i) for i in ids]
+
+ async def all(self):
+ await self.cursor.execute("SELECT ID FROM explore_ids")
+ return [i[0] for i in await self.cursor.fetchmany()]
+
+ async def __aenter__(self):
+ await self.__connect_database()
+ return self
+
+ async def __aexit__(self, exc_type, exc_value, traceback):
+ await self.cursor.close()
+ await self.database.close()
diff --git a/source/module/static.py b/source/module/static.py
index 13a219f..1dd1aee 100644
--- a/source/module/static.py
+++ b/source/module/static.py
@@ -23,7 +23,7 @@
VERSION_MAJOR = 1
VERSION_MINOR = 8
-VERSION_BETA = True
+VERSION_BETA = False
ROOT = Path(__file__).resolve().parent.parent.parent
PROJECT = f"XHS-Downloader V{VERSION_MAJOR}.{
VERSION_MINOR}{" Beta" if VERSION_BETA else ""}"
diff --git a/source/translator/chinese.py b/source/translator/chinese.py
index 7887845..3ee3280 100644
--- a/source/translator/chinese.py
+++ b/source/translator/chinese.py
@@ -74,13 +74,18 @@ class Chinese:
monitor_text: str = "程序会自动读取并提取剪贴板中的小红书作品链接,并自动下载链接对应的作品文件,如需关闭,请点击关闭按钮,或者向剪贴板写入 “close” 文本!"
close_monitor: str = "退出监听剪贴板模式"
+ record_title: str = "请输入待删除的小红书作品链接或作品 ID:"
+ record_placeholder: str = "支持输入作品 ID 或包含作品 ID 的作品链接,多个链接或 ID 之间使用空格分隔"
+ record_enter_button: str = "删除指定作品 ID"
+ record_close_button: str = "返回"
+
@staticmethod
def request_error(url: str) -> str:
return f"网络异常,请求 {url} 失败!"
@staticmethod
def skip_download(name: str) -> str:
- return f"{name} 已存在,跳过下载!"
+ return f"{name} 文件已存在,跳过下载!"
@staticmethod
def download_success(name: str) -> str:
@@ -113,3 +118,7 @@ def processing_completed(url: str) -> str:
@staticmethod
def official_version_update(major: int, minor: int) -> str:
return f"检测到新版本:{major}.{minor}"
+
+ @staticmethod
+ def exist_record(id_: str) -> str:
+ return f"作品 {id_} 存在下载记录,跳过下载!"
diff --git a/source/translator/english.py b/source/translator/english.py
index c005c06..7d51fc1 100644
--- a/source/translator/english.py
+++ b/source/translator/english.py
@@ -95,6 +95,13 @@ class English(Chinese):
"please click the close button or write the \"close\" text to the clipboard!")
close_monitor: str = "Exit monitoring clipboard mode"
+ record_title: str = "Please enter the link or ID of the Xiaohongshu work to be deleted:"
+ record_placeholder: str = (
+ "Support input of works ID or links containing work ID, with multiple links or IDs "
+ "separated by spaces")
+ record_enter_button: str = "Delete specified works ID"
+ record_close_button: str = "return"
+
@staticmethod
def request_error(url: str) -> str:
return f"Network error, failed to access {url}!"
@@ -134,3 +141,7 @@ def processing_completed(url: str) -> str:
@staticmethod
def official_version_update(major: int, minor: int) -> str:
return f"New version detected: {major}.{minor}"
+
+ @staticmethod
+ def exist_record(id_: str) -> str:
+ return f"works {id_} has a download record, skipping download!"
diff --git a/static/XHS-Downloader.tcss b/static/XHS-Downloader.tcss
index 058748d..cfbbc47 100644
--- a/static/XHS-Downloader.tcss
+++ b/static/XHS-Downloader.tcss
@@ -1,4 +1,4 @@
-ScrollableContainer, RichLog, Monitor {
+ScrollableContainer, RichLog, Monitor, About {
background: #2f3542;
}
Button {
@@ -17,7 +17,7 @@ Button {
.horizontal-layout > * {
width: 25vw;
}
-Button#deal, Button#paste, Button#save {
+Button#deal, Button#paste, Button#save, Button#enter {
tint: #27ae60 60%;
}
Button#reset, Button#abandon, Button#close {
@@ -32,7 +32,7 @@ Label {
Label.params {
margin: 1 0 0 0;
}
-Label#prompt, Label#monitor {
+Label.prompt {
padding: 1;
}
Bar {
@@ -52,6 +52,13 @@ Bar > .bar--complete {
background: #353b48;
border: double #747d8c;
}
+#record {
+ grid-size: 1 3;
+ width: 80vw;
+ height: 12;
+ background: #353b48;
+ border: double #747d8c;
+}
ModalScreen {
align: center middle;
}
diff --git "a/static/screenshot/\347\250\213\345\272\217\350\277\220\350\241\214\346\210\252\345\233\276CN1.png" "b/static/screenshot/\347\250\213\345\272\217\350\277\220\350\241\214\346\210\252\345\233\276CN1.png"
index 13d547c..064ef7b 100644
Binary files "a/static/screenshot/\347\250\213\345\272\217\350\277\220\350\241\214\346\210\252\345\233\276CN1.png" and "b/static/screenshot/\347\250\213\345\272\217\350\277\220\350\241\214\346\210\252\345\233\276CN1.png" differ
diff --git "a/static/screenshot/\347\250\213\345\272\217\350\277\220\350\241\214\346\210\252\345\233\276CN2.png" "b/static/screenshot/\347\250\213\345\272\217\350\277\220\350\241\214\346\210\252\345\233\276CN2.png"
index 7bac5ab..17b2286 100644
Binary files "a/static/screenshot/\347\250\213\345\272\217\350\277\220\350\241\214\346\210\252\345\233\276CN2.png" and "b/static/screenshot/\347\250\213\345\272\217\350\277\220\350\241\214\346\210\252\345\233\276CN2.png" differ
diff --git "a/static/screenshot/\347\250\213\345\272\217\350\277\220\350\241\214\346\210\252\345\233\276CN3.png" "b/static/screenshot/\347\250\213\345\272\217\350\277\220\350\241\214\346\210\252\345\233\276CN3.png"
index 281c5b4..1da0e73 100644
Binary files "a/static/screenshot/\347\250\213\345\272\217\350\277\220\350\241\214\346\210\252\345\233\276CN3.png" and "b/static/screenshot/\347\250\213\345\272\217\350\277\220\350\241\214\346\210\252\345\233\276CN3.png" differ
diff --git "a/static/screenshot/\347\250\213\345\272\217\350\277\220\350\241\214\346\210\252\345\233\276EN1.png" "b/static/screenshot/\347\250\213\345\272\217\350\277\220\350\241\214\346\210\252\345\233\276EN1.png"
index 0d51c46..2131fc6 100644
Binary files "a/static/screenshot/\347\250\213\345\272\217\350\277\220\350\241\214\346\210\252\345\233\276EN1.png" and "b/static/screenshot/\347\250\213\345\272\217\350\277\220\350\241\214\346\210\252\345\233\276EN1.png" differ
diff --git "a/static/screenshot/\347\250\213\345\272\217\350\277\220\350\241\214\346\210\252\345\233\276EN2.png" "b/static/screenshot/\347\250\213\345\272\217\350\277\220\350\241\214\346\210\252\345\233\276EN2.png"
index 7f83fe1..37db41c 100644
Binary files "a/static/screenshot/\347\250\213\345\272\217\350\277\220\350\241\214\346\210\252\345\233\276EN2.png" and "b/static/screenshot/\347\250\213\345\272\217\350\277\220\350\241\214\346\210\252\345\233\276EN2.png" differ
diff --git "a/static/screenshot/\347\250\213\345\272\217\350\277\220\350\241\214\346\210\252\345\233\276EN3.png" "b/static/screenshot/\347\250\213\345\272\217\350\277\220\350\241\214\346\210\252\345\233\276EN3.png"
index 3bf6138..dae7f4a 100644
Binary files "a/static/screenshot/\347\250\213\345\272\217\350\277\220\350\241\214\346\210\252\345\233\276EN3.png" and "b/static/screenshot/\347\250\213\345\272\217\350\277\220\350\241\214\346\210\252\345\233\276EN3.png" differ
diff --git "a/static/screenshot/\350\216\267\345\217\226Cookie\347\244\272\346\204\217\345\233\276.png" "b/static/screenshot/\350\216\267\345\217\226Cookie\347\244\272\346\204\217\345\233\276.png"
index ea63efa..2c09176 100644
Binary files "a/static/screenshot/\350\216\267\345\217\226Cookie\347\244\272\346\204\217\345\233\276.png" and "b/static/screenshot/\350\216\267\345\217\226Cookie\347\244\272\346\204\217\345\233\276.png" differ