更新项目代码

This commit is contained in:
JoeamAmier 2024-01-06 18:11:09 +08:00
parent 48cfa60862
commit 952cf3496a
30 changed files with 474 additions and 179 deletions

View File

@ -1,6 +1,7 @@
<div align="center">
<img src="static/XHS-Downloader.png" alt="" height="256" width="256"><br>
<h1>XHS-Downloader</h1>
<p>简体中文 | <a href="README_EN.md">English</a></p>
<img alt="GitHub" src="https://img.shields.io/github/license/JoeanAmier/XHS-Downloader?style=for-the-badge&color=ff7a45">
<img alt="GitHub forks" src="https://img.shields.io/github/forks/JoeanAmier/XHS-Downloader?style=for-the-badge&color=9254de">
<img alt="GitHub Repo stars" src="https://img.shields.io/github/stars/JoeanAmier/XHS-Downloader?style=for-the-badge&color=ff7875">
@ -9,7 +10,9 @@
<img alt="GitHub code size in bytes" src="https://img.shields.io/github/languages/code-size/JoeanAmier/XHS-Downloader?style=for-the-badge&color=73d13d">
<img alt="GitHub release (with filter)" src="https://img.shields.io/github/v/release/JoeanAmier/XHS-Downloader?style=for-the-badge&color=40a9ff">
<img alt="GitHub all releases" src="https://img.shields.io/github/downloads/JoeanAmier/XHS-Downloader/total?style=for-the-badge&color=f759ab">
</div>
<br>
<div align="center">
<p>🔥 <b>小红书作品采集工具</b>:采集小红书作品信息;提取小红书作品下载地址;下载小红书无水印作品文件!</p>
<p>❤️ 作者仅在 GitHub 发布 XHS-Downloader未与任何个人或网站合作且没有任何收费计划</p>
</div>
@ -172,6 +175,12 @@ async with XHS(work_path=work_path,
<td align="center">是否将每个作品的文件储存至单独的文件夹;文件夹名称与文件名称保持一致</td>
<td align="center">false</td>
</tr>
<tr>
<td align="center">language</td>
<td align="center">str</td>
<td align="center">设置程序语言,目前支持:<code>zh-CN</code></td>
<td align="center">zh-CN</td>
</tr>
</tbody>
</table>
<h1>🌐 Cookie</h1>

0
README_EN.md Normal file
View File

View File

@ -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"

3
source/TUI/__init__.py Normal file
View File

@ -0,0 +1,3 @@
from .index import XHSDownloader
__all__ = ['XHSDownloader']

View File

@ -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)

16
source/TUI/setting.py Normal file
View File

@ -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("我是设置页")

View File

@ -1,4 +1,4 @@
from .App import XHS
from .TUI import XHSDownloader
from .application import XHS
__all__ = ['XHS', 'XHSDownloader']

View File

@ -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

View File

@ -1,6 +1,6 @@
from datetime import datetime
from .Converter import Namespace
from source.expansion import Namespace
__all__ = ['Explore']

View File

@ -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

View File

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

View File

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

View File

@ -0,0 +1,3 @@
from .app import XHS
__all__ = ["XHS"]

View File

@ -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:

View File

@ -0,0 +1,4 @@
from .converter import Converter
from .namespace import Namespace
__all__ = ["Converter", "Namespace", ]

View File

@ -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

View File

@ -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:

53
source/module/__init__.py Normal file
View File

@ -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",
]

View File

@ -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:

View File

@ -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"

52
source/module/static.py Normal file
View File

@ -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"

View File

@ -1,6 +1,6 @@
from rich.text import Text
from .Static import INFO
from .static import INFO
__all__ = ["retry", "logging"]

View File

@ -0,0 +1,13 @@
from .chinese import Chinese
from .english import English
__all__ = [
"LANGUAGE",
"Chinese",
"English",
]
LANGUAGE = {
Chinese.code: Chinese,
English.code: English,
}

View File

@ -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}"

View File

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

View File

@ -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

0
static/css/setting.tcss Normal file
View File