diff --git a/README.md b/README.md
index 61599eb..974c9f6 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,7 @@
+
🔥 小红书作品采集工具:采集小红书作品信息;提取小红书作品下载地址;下载小红书无水印作品文件!
❤️ 作者仅在 GitHub 发布 XHS-Downloader,未与任何个人或网站合作,且没有任何收费计划!
@@ -172,6 +175,12 @@ async with XHS(work_path=work_path,
是否将每个作品的文件储存至单独的文件夹;文件夹名称与文件名称保持一致 |
false |
+
+| language |
+str |
+设置程序语言,目前支持:zh-CN |
+zh-CN |
+
🌐 Cookie
diff --git a/README_EN.md b/README_EN.md
new file mode 100644
index 0000000..e69de29
diff --git a/source/Static.py b/source/Static.py
deleted file mode 100644
index e2525cd..0000000
--- a/source/Static.py
+++ /dev/null
@@ -1,70 +0,0 @@
-from pathlib import Path
-
-__all__ = [
- "VERSION_MAJOR",
- "VERSION_MINOR",
- "VERSION_BETA",
- "ROOT",
- "REPOSITORY",
- "LICENCE",
- "RELEASES",
- "MASTER",
- "PROMPT",
- "GENERAL",
- "PROGRESS",
- "ERROR",
- "WARNING",
- "INFO",
- "DISCLAIMER_TEXT",
- "USERSCRIPT",
- "USERAGENT",
- "COOKIE",
-]
-
-VERSION_MAJOR = 1
-VERSION_MINOR = 8
-VERSION_BETA = True
-ROOT = Path(__file__).resolve().parent.parent
-
-REPOSITORY = "https://github.com/JoeanAmier/XHS-Downloader"
-LICENCE = "GNU General Public License v3.0"
-RELEASES = "https://github.com/JoeanAmier/XHS-Downloader/releases/latest"
-DISCLAIMER_TEXT = (
- "关于 XHS-Downloader 的 免责声明:",
- "",
- "1. 使用者对本项目的使用由使用者自行决定,并自行承担风险。作者对使用者使用本项目所产生的任何损失、责任、或风险概不负责。",
- "2. 本项目的作者提供的代码和功能是基于现有知识和技术的开发成果。作者尽力确保代码的正确性和安全性,但不保证代码完全没有错误或缺陷。",
- "3. 使用者在使用本项目时必须严格遵守 GNU General Public License v3.0 的要求,并在适当的地方注明使用了 GNU General Public License v3.0 的代码。",
- "4. 使用者在任何情况下均不得将本项目的作者、贡献者或其他相关方与使用者的使用行为联系起来,或要求其对使用者使用本项目所产生的任何损失或损害负责。",
- "5. 使用者在使用本项目的代码和功能时,必须自行研究相关法律法规,并确保其使用行为合法合规。任何因违反法律法规而导致的法律责任和风险,均由使用者自行承担。",
- "6. 本项目的作者不会提供 XHS-Downloader 项目的付费版本,也不会提供与 XHS-Downloader 项目相关的任何商业服务。",
- "7. 基于本项目进行的任何二次开发、修改或编译的程序与原创作者无关,原创作者不承担与二次开发行为或其结果相关的任何责任,使用者应自行对因"
- "二次开发可能带来的各种情况负全部责任。",
- "",
- "在使用本项目的代码和功能之前,请您认真考虑并接受以上免责声明。如果您对上述声明有任何疑问或不同意,请不要使用本项目的代码和功能。如果"
- "您使用了本项目的代码和功能,则视为您已完全理解并接受上述免责声明,并自愿承担使用本项目的一切风险和后果。",
- "",
- ">" * 50,
-)
-
-USERSCRIPT = "https://raw.githubusercontent.com/JoeanAmier/XHS-Downloader/master/static/XHS-Downloader.js"
-
-USERAGENT = (
- "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 "
- "Safari/537.36")
-COOKIE = (
- "abRequestId=54c534bb-a2c6-558f-8e03-5b4c5c45635c; xsecappid=xhs-pc-web; a1=18c286a400"
- "4jy56qvzejvp631col0hd3032h4zjez50000106381; webId=779c977da3a15b5623015be94bdcc9e9; g"
- "id=yYSJYK0qDW8KyYSJYK048quV84Vv2KAhudVhJduUKqySlx2818xfq4888y8KqYy8y2y2f8Jy; web_sess"
- "ion=030037a259ce5f15c8d560dc12224a9fdc2ed1; webBuild=3.19.4; websectiga=984412fef754c"
- "018e472127b8effd174be8a5d51061c991aadd200c69a2801d6; sec_poison_id=3dd48845-d604-4535"
- "-bcc2-a859e97518bf; unread={%22ub%22:%22655eb3d60000000032033955%22%2C%22ue%22:%22656"
- "e9ef2000000003801ff3d%22%2C%22uc%22:29}; cache_feeds=[]")
-
-MASTER = "b #fff200"
-PROMPT = "b turquoise2"
-GENERAL = "b bright_white"
-PROGRESS = "b bright_magenta"
-ERROR = "b bright_red"
-WARNING = "b bright_yellow"
-INFO = "b bright_green"
diff --git a/source/TUI/__init__.py b/source/TUI/__init__.py
new file mode 100644
index 0000000..e91bd0f
--- /dev/null
+++ b/source/TUI/__init__.py
@@ -0,0 +1,3 @@
+from .index import XHSDownloader
+
+__all__ = ['XHSDownloader']
diff --git a/source/TUI.py b/source/TUI/index.py
similarity index 60%
rename from source/TUI.py
rename to source/TUI/index.py
index 8853ef0..3a76075 100644
--- a/source/TUI.py
+++ b/source/TUI/index.py
@@ -16,9 +16,9 @@ from textual.widgets import Label
from textual.widgets import ProgressBar
from textual.widgets import RichLog
-from .App import XHS
-from .Settings import Settings
-from .Static import (
+from source.application import XHS
+from source.module import Settings
+from source.module import (
VERSION_MAJOR,
VERSION_MINOR,
VERSION_BETA,
@@ -32,9 +32,11 @@ from .Static import (
REPOSITORY,
RELEASES,
GENERAL,
- DISCLAIMER_TEXT,
USERSCRIPT,
)
+from source.translator import Chinese
+from source.translator import LANGUAGE
+from .setting import Setting
__all__ = ["XHSDownloader"]
@@ -53,20 +55,25 @@ def show_state(function):
class XHSDownloader(App):
CSS_PATH = ROOT.joinpath(
- "static/XHS-Downloader.tcss")
+ "static/css/index.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="l", action="choose_language", description="切换语言"),
+ # Binding(key="s", action="settings", description="程序设置"),
]
def __init__(self):
super().__init__()
- self.APP = XHS(**Settings(ROOT).run())
+ settings = Settings(ROOT).run()
+ self.prompt = LANGUAGE.get(settings["language"], Chinese)
+ self.APP = XHS(**settings, language_object=self.prompt)
self.url = None
self.tip = None
self.bar = None
+ self.setting = None
self.disclaimer = True
async def __aenter__(self):
@@ -78,18 +85,18 @@ class XHSDownloader(App):
def compose(self) -> ComposeResult:
yield Header()
- yield ScrollableContainer(Label(Text(f"开源协议:{LICENCE}", style=MASTER)),
+ yield ScrollableContainer(Label(Text(f"{self.prompt.open_source_protocol}{LICENCE}", style=MASTER)),
Label(
Text(
- f"项目地址:{REPOSITORY}",
+ f"{self.prompt.project_address}{REPOSITORY}",
style=MASTER)),
- Label(Text("请输入小红书图文/视频作品链接:",
+ Label(Text(self.prompt.input_box_title,
style=PROMPT), id="prompt"),
- Input(placeholder="多个链接之间使用空格分隔"),
- HorizontalScroll(Button("下载无水印图片/视频", id="deal"),
- Button("读取剪贴板", id="paste"),
- Button("清空输入框", id="reset"), ),
- # Label(Text("准备就绪", style=INFO), id="state"),
+ 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"), ),
+ id="index",
)
with Center():
yield ProgressBar(total=None, show_percentage=False, show_eta=False)
@@ -104,7 +111,7 @@ class XHSDownloader(App):
self.url = self.query_one(Input)
self.tip = self.query_one(RichLog)
self.bar = self.query_one(ProgressBar)
- self.tip.write(Text("\n".join(DISCLAIMER_TEXT), style=MASTER))
+ self.tip.write(Text("\n".join(self.prompt.disclaimer), style=MASTER))
def close_disclaimer(self):
if self.disclaimer:
@@ -122,16 +129,19 @@ class XHSDownloader(App):
@show_state
async def deal(self):
if not self.url.value:
- self.tip.write(Text("未输入任何小红书作品链接!", style=WARNING))
+ self.tip.write(Text(self.prompt.invalid_link, style=WARNING))
return
if any(await self.APP.extract(self.url.value, True, log=self.tip)):
self.url.value = ""
else:
- self.tip.write(Text("下载小红书作品文件失败!", style=ERROR))
+ self.tip.write(Text(self.prompt.download_failure, style=ERROR))
@show_state
async def action_check_update(self):
- self.tip.write(Text("正在检查新版本,请稍等...", style=WARNING))
+ self.tip.write(
+ Text(
+ self.prompt.check_update_notification,
+ style=WARNING))
try:
url = await self.APP.html.request_url(RELEASES, False, self.tip)
latest_major, latest_minor = map(
@@ -139,20 +149,41 @@ class XHSDownloader(App):
if latest_major > VERSION_MAJOR or latest_minor > VERSION_MINOR:
self.tip.write(
Text(
- f"检测到新版本:{latest_major}.{latest_minor}",
+ self.prompt.official_version_update(
+ latest_major,
+ latest_minor),
style=WARNING))
self.tip.write(RELEASES)
elif latest_minor == VERSION_MINOR and VERSION_BETA:
self.tip.write(
- Text("当前版本为开发版, 可更新至正式版!", style=WARNING))
+ Text(
+ self.prompt.development_version_update,
+ style=WARNING))
self.tip.write(RELEASES)
elif VERSION_BETA:
- self.tip.write(Text("当前已是最新开发版!", style=WARNING))
+ self.tip.write(
+ Text(
+ self.prompt.latest_development_version,
+ style=WARNING))
else:
- self.tip.write(Text("当前已是最新正式版!", style=INFO))
+ self.tip.write(
+ Text(
+ self.prompt.latest_official_version,
+ style=INFO))
except ValueError:
- self.tip.write(Text("检测新版本失败!", style=ERROR))
+ self.tip.write(Text(self.prompt.check_update_failure, style=ERROR))
@staticmethod
def action_user_script():
open(USERSCRIPT)
+
+ def action_choose_language(self):
+ pass
+
+ def action_settings(self):
+ if self.setting:
+ self.setting.remove()
+ self.setting = None
+ else:
+ self.setting = Setting()
+ self.query_one("#index").mount(self.setting)
diff --git a/source/TUI/setting.py b/source/TUI/setting.py
new file mode 100644
index 0000000..83e8b7d
--- /dev/null
+++ b/source/TUI/setting.py
@@ -0,0 +1,16 @@
+from textual.app import ComposeResult
+from textual.widgets import Label
+from textual.widgets import Static
+
+from source.module import ROOT
+
+__all__ = ["Setting"]
+
+
+class Setting(Static):
+ CSS_PATH = ROOT.joinpath(
+ "static/css/setting.tcss")
+
+ def compose(self) -> ComposeResult:
+ """Create child widgets for the app."""
+ yield Label("我是设置页")
diff --git a/source/__init__.py b/source/__init__.py
index 3cf8dd0..ef9a157 100644
--- a/source/__init__.py
+++ b/source/__init__.py
@@ -1,4 +1,4 @@
-from .App import XHS
from .TUI import XHSDownloader
+from .application import XHS
__all__ = ['XHS', 'XHSDownloader']
diff --git a/source/Downloader.py b/source/application/Downloader.py
similarity index 88%
rename from source/Downloader.py
rename to source/application/Downloader.py
index ab5e5b7..dfadfbb 100644
--- a/source/Downloader.py
+++ b/source/application/Downloader.py
@@ -2,10 +2,10 @@ from pathlib import Path
from aiohttp import ClientError
-from .Manager import Manager
-from .Static import ERROR
-from .Tools import logging
-from .Tools import retry as re_download
+from source.module import ERROR
+from source.module import Manager
+from source.module import logging
+from source.module import retry as re_download
__all__ = ['Download']
@@ -20,6 +20,7 @@ class Download:
self.chunk = manager.chunk
self.session = manager.download_session
self.retry = manager.retry
+ self.prompt = manager.prompt
self.folder_mode = manager.folder_mode
self.video_format = "mp4"
self.image_format = manager.image_format
@@ -50,7 +51,7 @@ class Download:
temp = self.temp.joinpath(name)
file = path.joinpath(name).with_suffix(f".{suffix}")
if self.manager.is_exists(file):
- logging(log, f"{name} 已存在,跳过下载!")
+ logging(log, self.prompt.skip_download(name))
return True
# self.__create_progress(
# bar, int(
@@ -62,13 +63,13 @@ class Download:
# self.__update_progress(bar, len(chunk))
self.manager.move(temp, file)
# self.__create_progress(bar, None)
- logging(log, f"{name} 下载成功!")
+ logging(log, self.prompt.download_success(name))
return True
except ClientError as error:
self.manager.delete(temp)
# self.__create_progress(bar, None)
logging(log, error, ERROR)
- logging(log, f"网络异常,{name} 下载失败!", ERROR)
+ logging(log, self.prompt.download_error(name), ERROR)
return False
@staticmethod
diff --git a/source/Explore.py b/source/application/Explore.py
similarity index 98%
rename from source/Explore.py
rename to source/application/Explore.py
index baab9d3..8b97ade 100644
--- a/source/Explore.py
+++ b/source/application/Explore.py
@@ -1,6 +1,6 @@
from datetime import datetime
-from .Converter import Namespace
+from source.expansion import Namespace
__all__ = ['Explore']
diff --git a/source/Html.py b/source/application/Html.py
similarity index 77%
rename from source/Html.py
rename to source/application/Html.py
index 991b4a2..2f77e70 100644
--- a/source/Html.py
+++ b/source/application/Html.py
@@ -1,9 +1,9 @@
from aiohttp import ClientError
-from .Manager import Manager
-from .Static import ERROR
-from .Tools import logging
-from .Tools import retry
+from source.module import ERROR
+from source.module import Manager
+from source.module import logging
+from source.module import retry
__all__ = ["Html"]
@@ -12,6 +12,7 @@ class Html:
def __init__(self, manager: Manager, ):
self.proxy = manager.proxy
self.retry = manager.retry
+ self.prompt = manager.prompt
self.session = manager.request_session
@retry
@@ -29,7 +30,7 @@ class Html:
return await response.text() if content else str(response.url)
except ClientError as error:
logging(log, error, ERROR)
- logging(log, f"网络异常,请求 {url} 失败!", ERROR)
+ logging(log, self.prompt.request_error(url), ERROR)
return ""
@staticmethod
diff --git a/source/Image.py b/source/application/Image.py
similarity index 97%
rename from source/Image.py
rename to source/application/Image.py
index 39664e7..c699fff 100644
--- a/source/Image.py
+++ b/source/application/Image.py
@@ -1,4 +1,4 @@
-from .Converter import Namespace
+from source.expansion import Namespace
from .Html import Html
__all__ = ['Image']
diff --git a/source/Video.py b/source/application/Video.py
similarity index 90%
rename from source/Video.py
rename to source/application/Video.py
index 9e926c0..2105b7a 100644
--- a/source/Video.py
+++ b/source/application/Video.py
@@ -1,4 +1,4 @@
-from .Converter import Namespace
+from source.expansion import Namespace
from .Html import Html
__all__ = ['Video']
diff --git a/source/application/__init__.py b/source/application/__init__.py
new file mode 100644
index 0000000..f58ddb2
--- /dev/null
+++ b/source/application/__init__.py
@@ -0,0 +1,3 @@
+from .app import XHS
+
+__all__ = ["XHS"]
diff --git a/source/App.py b/source/application/app.py
similarity index 82%
rename from source/App.py
rename to source/application/app.py
index 45018a6..b5c474d 100644
--- a/source/App.py
+++ b/source/application/app.py
@@ -1,18 +1,23 @@
from re import compile
-from .Converter import Converter
-from .Converter import Namespace
-from .Downloader import Download
-from .Explore import Explore
-from .Html import Html
-from .Image import Image
-from .Manager import Manager
-from .Static import (
+from source.expansion import Converter
+from source.expansion import Namespace
+from source.module import Manager
+from source.module import (
ROOT,
ERROR,
WARNING,
)
-from .Tools import logging
+from source.module import logging
+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
__all__ = ["XHS"]
@@ -42,7 +47,10 @@ class XHS:
record_data=False,
image_format="PNG",
folder_mode=False,
+ language="zh-CN",
+ language_object: Chinese | English = None,
):
+ self.prompt = language_object or LANGUAGE.get(language, Chinese)
self.manager = Manager(
ROOT,
work_path,
@@ -56,6 +64,7 @@ class XHS:
record_data,
image_format,
folder_mode,
+ self.prompt,
)
self.html = Html(self.manager)
self.image = Image()
@@ -77,16 +86,16 @@ class XHS:
if (u := container["下载地址"]) and download:
path = await self.download.run(u, name, container["作品类型"], log, bar)
elif not u:
- logging(log, "提取作品文件下载地址失败!", ERROR)
+ 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]:
# return # 调试代码
urls = await self.__extract_links(url, log)
if not urls:
- logging(log, "提取小红书作品链接失败!", WARNING)
+ logging(log, self.prompt.extract_link_failure, WARNING)
else:
- logging(log, f"共 {len(urls)} 个小红书作品待处理...")
+ logging(log, self.prompt.pending_processing(len(urls)))
# return urls # 调试代码
return [await self.__deal_extract(i, download, log, bar) for i in urls]
@@ -103,17 +112,17 @@ class XHS:
return urls
async def __deal_extract(self, url: str, download: bool, log, bar):
- logging(log, f"开始处理作品:{url}")
+ logging(log, self.prompt.start_processing(url))
html = await self.html.request_url(url, log=log)
# logging(log, html) # 调试代码
if not html:
- logging(log, f"{url} 获取数据失败!", ERROR)
+ logging(log, self.prompt.get_data_failure(url), ERROR)
return {}
namespace = self.__generate_data_object(html)
data = self.explore.run(namespace)
# logging(log, data) # 调试代码
if not data:
- logging(log, f"{url} 提取数据失败!", ERROR)
+ logging(log, self.prompt.extract_data_failure(url), ERROR)
return {}
match data["作品类型"]:
case "视频":
@@ -123,7 +132,7 @@ class XHS:
case _:
data["下载地址"] = []
await self.__download_files(data, download, log, bar)
- logging(log, f"作品处理完成:{url}")
+ logging(log, self.prompt.processing_completed(url))
return data
def __generate_data_object(self, html: str) -> Namespace:
diff --git a/source/expansion/__init__.py b/source/expansion/__init__.py
new file mode 100644
index 0000000..8dfb9b4
--- /dev/null
+++ b/source/expansion/__init__.py
@@ -0,0 +1,4 @@
+from .converter import Converter
+from .namespace import Namespace
+
+__all__ = ["Converter", "Namespace", ]
diff --git a/source/expansion/converter.py b/source/expansion/converter.py
new file mode 100644
index 0000000..c55516c
--- /dev/null
+++ b/source/expansion/converter.py
@@ -0,0 +1,51 @@
+from lxml.etree import HTML
+from yaml import safe_load
+
+__all__ = ["Converter"]
+
+
+class Converter:
+ INITIAL_STATE = "(//script)[last()]/text()"
+ KEYS_LINK = (
+ "note",
+ "noteDetailMap",
+ "[-1]",
+ "note",
+ )
+
+ def run(self, content: str) -> dict:
+ return self.__filter_object(
+ self.__convert_object(
+ self.__extract_object(content)))
+
+ def __extract_object(self, html: str) -> str:
+ html_tree = HTML(html)
+ return d[0] if (d := html_tree.xpath(self.INITIAL_STATE)) else ""
+
+ @staticmethod
+ def __convert_object(text: str) -> dict:
+ return safe_load(text.lstrip("window.__INITIAL_STATE__="))
+
+ @classmethod
+ def __filter_object(cls, data: dict) -> dict:
+ return cls.deep_get(data, cls.KEYS_LINK) or {}
+
+ @classmethod
+ def deep_get(cls, data: dict, keys: list | tuple, default=None):
+ try:
+ for key in keys:
+ if key.startswith("[") and key.endswith("]"):
+ data = cls.safe_get(data, int(key[1:-1]))
+ else:
+ data = data[key]
+ return data
+ except (KeyError, IndexError, ValueError):
+ return default
+
+ @staticmethod
+ def safe_get(data: dict | list | tuple | set, index: int):
+ if isinstance(data, dict):
+ return list(data.values())[index]
+ elif isinstance(data, list | tuple | set):
+ return data[index]
+ raise TypeError
diff --git a/source/Converter.py b/source/expansion/namespace.py
similarity index 58%
rename from source/Converter.py
rename to source/expansion/namespace.py
index a706d2e..1fbf534 100644
--- a/source/Converter.py
+++ b/source/expansion/namespace.py
@@ -1,57 +1,7 @@
from copy import deepcopy
from types import SimpleNamespace
-from lxml.etree import HTML
-from yaml import safe_load
-
-__all__ = ["Converter", "Namespace"]
-
-
-class Converter:
- INITIAL_STATE = "(//script)[last()]/text()"
- KEYS_LINK = (
- "note",
- "noteDetailMap",
- "[-1]",
- "note",
- )
-
- def run(self, content: str) -> dict:
- return self.__filter_object(
- self.__convert_object(
- self.__extract_object(content)))
-
- def __extract_object(self, html: str) -> str:
- html_tree = HTML(html)
- return d[0] if (d := html_tree.xpath(self.INITIAL_STATE)) else ""
-
- @staticmethod
- def __convert_object(text: str) -> dict:
- return safe_load(text.lstrip("window.__INITIAL_STATE__="))
-
- @classmethod
- def __filter_object(cls, data: dict) -> dict:
- return cls.deep_get(data, cls.KEYS_LINK) or {}
-
- @classmethod
- def deep_get(cls, data: dict, keys: list | tuple, default=None):
- try:
- for key in keys:
- if key.startswith("[") and key.endswith("]"):
- data = cls.safe_get(data, int(key[1:-1]))
- else:
- data = data[key]
- return data
- except (KeyError, IndexError, ValueError):
- return default
-
- @staticmethod
- def safe_get(data: dict | list | tuple | set, index: int):
- if isinstance(data, dict):
- return list(data.values())[index]
- elif isinstance(data, list | tuple | set):
- return data[index]
- raise TypeError
+__all__ = ["Namespace"]
class Namespace:
diff --git a/source/module/__init__.py b/source/module/__init__.py
new file mode 100644
index 0000000..071ba6d
--- /dev/null
+++ b/source/module/__init__.py
@@ -0,0 +1,53 @@
+from .extend import Account
+from .manager import Manager
+from .recorder import Recorder
+from .settings import Settings
+from .static import (
+ VERSION_MAJOR,
+ VERSION_MINOR,
+ VERSION_BETA,
+ ROOT,
+ REPOSITORY,
+ LICENCE,
+ RELEASES,
+ MASTER,
+ PROMPT,
+ GENERAL,
+ PROGRESS,
+ ERROR,
+ WARNING,
+ INFO,
+ USERSCRIPT,
+ USERAGENT,
+ COOKIE,
+)
+from .tools import (
+ retry,
+ logging,
+)
+
+__all__ = [
+ "Account",
+ "Settings",
+ "Recorder",
+ "Manager",
+ "VERSION_MAJOR",
+ "VERSION_MINOR",
+ "VERSION_BETA",
+ "ROOT",
+ "REPOSITORY",
+ "LICENCE",
+ "RELEASES",
+ "MASTER",
+ "PROMPT",
+ "GENERAL",
+ "PROGRESS",
+ "ERROR",
+ "WARNING",
+ "INFO",
+ "USERSCRIPT",
+ "USERAGENT",
+ "COOKIE",
+ "retry",
+ "logging",
+]
diff --git a/source/Extend.py b/source/module/extend.py
similarity index 100%
rename from source/Extend.py
rename to source/module/extend.py
diff --git a/source/Manager.py b/source/module/manager.py
similarity index 94%
rename from source/Manager.py
rename to source/module/manager.py
index 99943f3..a120bfa 100644
--- a/source/Manager.py
+++ b/source/module/manager.py
@@ -9,8 +9,10 @@ from shutil import rmtree
from aiohttp import ClientSession
from aiohttp import ClientTimeout
-from .Static import COOKIE
-from .Static import USERAGENT
+from source.translator import Chinese
+from source.translator import English
+from .static import COOKIE
+from .static import USERAGENT
__all__ = ["Manager"]
@@ -32,6 +34,7 @@ class Manager:
record_data: bool,
image_format: str,
folder_mode: bool,
+ language: Chinese | English,
):
self.root = root
self.temp = root.joinpath("./temp")
@@ -54,6 +57,7 @@ class Manager:
self.download_session = ClientSession(
headers={"User-Agent": self.headers["User-Agent"]},
timeout=ClientTimeout(connect=timeout))
+ self.prompt = language
def __check_path(self, path: str) -> Path:
if not path:
diff --git a/source/Recorder.py b/source/module/recorder.py
similarity index 100%
rename from source/Recorder.py
rename to source/module/recorder.py
diff --git a/source/Settings.py b/source/module/settings.py
similarity index 95%
rename from source/Settings.py
rename to source/module/settings.py
index d37da13..f5f2356 100644
--- a/source/Settings.py
+++ b/source/module/settings.py
@@ -19,6 +19,8 @@ class Settings:
"record_data": False,
"image_format": "PNG",
"folder_mode": False,
+ "language": "zh-CN",
+ # "server": False,
}
encode = "UTF-8-SIG" if system() == "Windows" else "UTF-8"
diff --git a/source/module/static.py b/source/module/static.py
new file mode 100644
index 0000000..d9d8b3c
--- /dev/null
+++ b/source/module/static.py
@@ -0,0 +1,52 @@
+from pathlib import Path
+
+__all__ = [
+ "VERSION_MAJOR",
+ "VERSION_MINOR",
+ "VERSION_BETA",
+ "ROOT",
+ "REPOSITORY",
+ "LICENCE",
+ "RELEASES",
+ "MASTER",
+ "PROMPT",
+ "GENERAL",
+ "PROGRESS",
+ "ERROR",
+ "WARNING",
+ "INFO",
+ "USERSCRIPT",
+ "USERAGENT",
+ "COOKIE",
+]
+
+VERSION_MAJOR = 1
+VERSION_MINOR = 8
+VERSION_BETA = True
+ROOT = Path(__file__).resolve().parent.parent.parent
+
+REPOSITORY = "https://github.com/JoeanAmier/XHS-Downloader"
+LICENCE = "GNU General Public License v3.0"
+RELEASES = "https://github.com/JoeanAmier/XHS-Downloader/releases/latest"
+
+USERSCRIPT = "https://raw.githubusercontent.com/JoeanAmier/XHS-Downloader/master/static/XHS-Downloader.js"
+
+USERAGENT = (
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 "
+ "Safari/537.36")
+COOKIE = (
+ "abRequestId=54c534bb-a2c6-558f-8e03-5b4c5c45635c; xsecappid=xhs-pc-web; a1=18c286a400"
+ "4jy56qvzejvp631col0hd3032h4zjez50000106381; webId=779c977da3a15b5623015be94bdcc9e9; g"
+ "id=yYSJYK0qDW8KyYSJYK048quV84Vv2KAhudVhJduUKqySlx2818xfq4888y8KqYy8y2y2f8Jy; web_sess"
+ "ion=030037a259ce5f15c8d560dc12224a9fdc2ed1; webBuild=3.19.4; websectiga=984412fef754c"
+ "018e472127b8effd174be8a5d51061c991aadd200c69a2801d6; sec_poison_id=3dd48845-d604-4535"
+ "-bcc2-a859e97518bf; unread={%22ub%22:%22655eb3d60000000032033955%22%2C%22ue%22:%22656"
+ "e9ef2000000003801ff3d%22%2C%22uc%22:29}; cache_feeds=[]")
+
+MASTER = "b #fff200"
+PROMPT = "b turquoise2"
+GENERAL = "b bright_white"
+PROGRESS = "b bright_magenta"
+ERROR = "b bright_red"
+WARNING = "b bright_yellow"
+INFO = "b bright_green"
diff --git a/source/Tools.py b/source/module/tools.py
similarity index 95%
rename from source/Tools.py
rename to source/module/tools.py
index e9a4040..a6658ad 100644
--- a/source/Tools.py
+++ b/source/module/tools.py
@@ -1,6 +1,6 @@
from rich.text import Text
-from .Static import INFO
+from .static import INFO
__all__ = ["retry", "logging"]
diff --git a/source/translator/__init__.py b/source/translator/__init__.py
new file mode 100644
index 0000000..f43334e
--- /dev/null
+++ b/source/translator/__init__.py
@@ -0,0 +1,13 @@
+from .chinese import Chinese
+from .english import English
+
+__all__ = [
+ "LANGUAGE",
+ "Chinese",
+ "English",
+]
+
+LANGUAGE = {
+ Chinese.code: Chinese,
+ English.code: English,
+}
diff --git a/source/translator/chinese.py b/source/translator/chinese.py
new file mode 100644
index 0000000..f36eedf
--- /dev/null
+++ b/source/translator/chinese.py
@@ -0,0 +1,87 @@
+from .template import Language
+
+__all__ = ["Chinese"]
+
+
+class Chinese(Language):
+ code: str = "zh-CN"
+ disclaimer: tuple[str] = (
+ "关于 XHS-Downloader 的 免责声明:",
+ "",
+ "1. 使用者对本项目的使用由使用者自行决定,并自行承担风险。作者对使用者使用本项目所产生的任何损失、责任、或风险概不负责。",
+ "2. 本项目的作者提供的代码和功能是基于现有知识和技术的开发成果。作者尽力确保代码的正确性和安全性,但不保证代码完全没有错误或缺陷。",
+ "3. 使用者在使用本项目时必须严格遵守 GNU General Public License v3.0 的要求,并在适当的地方注明使用了 GNU General Public License v3.0 的代码。",
+ "4. 使用者在任何情况下均不得将本项目的作者、贡献者或其他相关方与使用者的使用行为联系起来,或要求其对使用者使用本项目所产生的任何损失或损害负责。",
+ "5. 使用者在使用本项目的代码和功能时,必须自行研究相关法律法规,并确保其使用行为合法合规。任何因违反法律法规而导致的法律责任和风险,均由使用者自行承担。",
+ "6. 本项目的作者不会提供 XHS-Downloader 项目的付费版本,也不会提供与 XHS-Downloader 项目相关的任何商业服务。",
+ "7. 基于本项目进行的任何二次开发、修改或编译的程序与原创作者无关,原创作者不承担与二次开发行为或其结果相关的任何责任,使用者应自行对因"
+ "二次开发可能带来的各种情况负全部责任。",
+ "",
+ "在使用本项目的代码和功能之前,请您认真考虑并接受以上免责声明。如果您对上述声明有任何疑问或不同意,请不要使用本项目的代码和功能。如果"
+ "您使用了本项目的代码和功能,则视为您已完全理解并接受上述免责声明,并自愿承担使用本项目的一切风险和后果。",
+ "",
+ ">" * 50,
+ )
+
+ download_link_error: str = "提取作品文件下载地址失败!"
+ extract_link_failure: str = "提取小红书作品链接失败!"
+ invalid_link: str = "未输入任何小红书作品链接!"
+ download_failure: str = "下载小红书作品文件失败!"
+ check_update_notification: str = "正在检查新版本,请稍等..."
+ development_version_update: str = "当前版本为开发版, 可更新至正式版!"
+ latest_development_version: str = "当前已是最新开发版!"
+ latest_official_version: str = "当前已是最新正式版!"
+ check_update_failure: str = "检测新版本失败!"
+
+ open_source_protocol: str = "开源协议:"
+ project_address: str = "项目地址:"
+ input_box_title: str = "请输入小红书图文/视频作品链接:"
+ input_prompt: str = "多个链接之间使用空格分隔"
+ download_button: str = "下载无水印图片/视频"
+ paste_button: str = "读取剪贴板"
+ reset_button: str = "清空输入框"
+
+ exit_program: str = "退出程序"
+ check_updates: str = "检查更新"
+ get_script: str = "获取脚本"
+ choose_language: str = "选择语言"
+
+ @staticmethod
+ def request_error(url: str) -> str:
+ return f"网络异常,请求 {url} 失败!"
+
+ @staticmethod
+ def skip_download(name: str) -> str:
+ return f"{name} 已存在,跳过下载!"
+
+ @staticmethod
+ def download_success(name: str) -> str:
+ return f"{name} 下载成功!"
+
+ @staticmethod
+ def download_error(name: str) -> str:
+ return f"网络异常,{name} 下载失败!"
+
+ @staticmethod
+ def pending_processing(num: int) -> str:
+ return f"共 {num} 个小红书作品待处理..."
+
+ @staticmethod
+ def start_processing(url: str) -> str:
+ return f"开始处理作品:{url}"
+
+ @staticmethod
+ def get_data_failure(url: str) -> str:
+ return f"{url} 获取数据失败!"
+
+ @staticmethod
+ def extract_data_failure(url: str) -> str:
+ return f"{url} 提取数据失败!"
+
+ @staticmethod
+ def processing_completed(url: str) -> str:
+ return f"作品处理完成:{url}"
+
+ @staticmethod
+ def official_version_update(major: int, minor: int) -> str:
+ return f"检测到新版本:{major}.{minor}"
diff --git a/source/translator/english.py b/source/translator/english.py
new file mode 100644
index 0000000..e7f11dc
--- /dev/null
+++ b/source/translator/english.py
@@ -0,0 +1,7 @@
+from .template import Language
+
+__all__ = ["English"]
+
+
+class English(Language):
+ pass
diff --git a/source/translator/template.py b/source/translator/template.py
new file mode 100644
index 0000000..a2c0a86
--- /dev/null
+++ b/source/translator/template.py
@@ -0,0 +1,69 @@
+__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
diff --git a/static/XHS-Downloader.tcss b/static/css/index.tcss
similarity index 100%
rename from static/XHS-Downloader.tcss
rename to static/css/index.tcss
diff --git a/static/css/setting.tcss b/static/css/setting.tcss
new file mode 100644
index 0000000..e69de29