Repository: JoeanAmier/XHS_Downloader
Branch: master
Commit: 8a8c1acb4b84
Files: 75
Total size: 615.4 KB
Directory structure:
gitextract_832couqt/
├── .github/
│ ├── ISSUE_TEMPLATE/
│ │ ├── bug_report.md
│ │ └── feature_request.md
│ ├── dependabot.yml
│ └── workflows/
│ ├── Close_Stale_Issues_and_PRs.yaml
│ ├── Delete_untagged_images.yml
│ ├── Manually_build_executable_programs.yml
│ ├── Manually_docker_image.yml
│ ├── Release_build_executable_program.yml
│ └── Release_docker_image.yml
├── .gitignore
├── .python-version
├── Dockerfile
├── LICENSE
├── README.md
├── README_EN.md
├── example.py
├── locale/
│ ├── README.md
│ ├── en_US/
│ │ └── LC_MESSAGES/
│ │ ├── xhs.mo
│ │ └── xhs.po
│ ├── generate_path.py
│ ├── po_to_mo.py
│ ├── xhs.pot
│ └── zh_CN/
│ └── LC_MESSAGES/
│ ├── xhs.mo
│ └── xhs.po
├── main.py
├── pyproject.toml
├── requirements.txt
├── source/
│ ├── CLI/
│ │ ├── __init__.py
│ │ └── main.py
│ ├── TUI/
│ │ ├── __init__.py
│ │ ├── about.py
│ │ ├── app.py
│ │ ├── index.py
│ │ ├── loading.py
│ │ ├── monitor.py
│ │ ├── progress.py
│ │ ├── record.py
│ │ ├── setting.py
│ │ └── update.py
│ ├── __init__.py
│ ├── application/
│ │ ├── __init__.py
│ │ ├── app.py
│ │ ├── download.py
│ │ ├── explore.py
│ │ ├── image.py
│ │ ├── request.py
│ │ ├── user_posted.py
│ │ └── video.py
│ ├── expansion/
│ │ ├── __init__.py
│ │ ├── browser.py
│ │ ├── cleaner.py
│ │ ├── converter.py
│ │ ├── error.py
│ │ ├── file_folder.py
│ │ ├── namespace.py
│ │ ├── pyi_rth_beartype.py
│ │ └── truncate.py
│ ├── module/
│ │ ├── __init__.py
│ │ ├── extend.py
│ │ ├── manager.py
│ │ ├── mapping.py
│ │ ├── model.py
│ │ ├── recorder.py
│ │ ├── script.py
│ │ ├── settings.py
│ │ ├── static.py
│ │ └── tools.py
│ └── translation/
│ ├── __init__.py
│ └── translate.py
└── static/
├── 20250619.js
├── Release_Notes.md
├── XHS-Downloader.icns
├── XHS-Downloader.js
├── XHS-Downloader.tcss
└── 自动滚动页面.js
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.md
================================================
---
name: Bug report
about: 报告项目问题
title: '[功能异常] '
labels: ''
assignees: JoeanAmier
---
**问题描述**
清晰简洁地描述该错误是什么。
A clear and concise description of what the bug is.
**重现步骤**
重现该问题的步骤:
Steps to reproduce the behavior:
1. ...
2. ...
3. ...
**预期结果**
清晰简洁地描述您预期会发生的情况。
A clear and concise description of what you expected to happen.
**补充信息**
在此添加有关该问题的任何其他上下文信息,例如:操作系统、运行方式、配置文件、错误截图、运行日志等。
请注意:提供配置文件时,请删除 Cookie 内容,避免敏感数据泄露!
Add any other contextual information about the issue here, such as operating system, runtime mode, configuration files,
error screenshots, runtime logs, etc.
Please note: When providing configuration files, please delete cookie content to avoid sensitive data leakage!
================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.md
================================================
---
name: Feature request
about: 功能优化建议
title: '[优化建议] '
labels: ''
assignees: JoeanAmier
---
**功能请求**
清晰简洁地描述问题是什么。例如:当 [...] 时,我总是感到沮丧。
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**描述您希望的解决方案**
清晰简洁地描述您希望发生的情况。
A clear and concise description of what you want to happen.
**描述您考虑过的替代方案**
清晰简洁地描述您考虑过的任何替代解决方案或功能。
A clear and concise description of any alternative solutions or features you've considered.
**补充信息**
在此添加有关功能请求的任何其他上下文或截图。
Add any other context or screenshots about the feature request here.
================================================
FILE: .github/dependabot.yml
================================================
version: 2
registries:
pip_mirror:
type: python-index
url: https://mirrors.ustc.edu.cn/pypi/simple
updates:
- package-ecosystem: "uv"
directory: "/"
schedule:
interval: "cron"
cronjob: "0 0 * * 6"
timezone: "Asia/Shanghai"
target-branch: "develop"
registries:
- pip_mirror
================================================
FILE: .github/workflows/Close_Stale_Issues_and_PRs.yaml
================================================
name: "自动管理过时的问题和PR"
on:
schedule:
- cron: "0 0 * * 6"
workflow_dispatch:
permissions:
issues: write
pull-requests: write
jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v9
with:
stale-issue-message: |
⚠️ 此 Issue 已超过一定时间未活动,如果没有进一步更新,将在 14 天后关闭。
⚠️ This issue has been inactive for a certain period of time. If there are no further updates, it will be closed in 14 days.
close-issue-message: |
🔒 由于长时间未响应,此 Issue 已被自动关闭。如有需要,请重新打开或提交新 issue。
🔒 Due to prolonged inactivity, this issue has been automatically closed. If needed, please reopen it or submit a new issue.
stale-pr-message: |
⚠️ 此 PR 已超过一定时间未更新,请更新,否则将在 14 天后关闭。
⚠️ This PR has not been updated for a certain period of time. Please update it, otherwise it will be closed in 14 days.
close-pr-message: |
🔒 此 PR 已因无更新而自动关闭。如仍需合并,请重新打开或提交新 PR。
🔒 This PR has been automatically closed due to inactivity. If you still wish to merge it, please reopen it or submit a new PR.
days-before-stale: 28
days-before-close: 14
ascending: true
stale-issue-label: "未跟进问题(Stale)"
close-issue-label: "自动关闭(Close)"
stale-pr-label: "未跟进问题(Stale)"
close-pr-label: "自动关闭(Close)"
exempt-issue-labels: "功能异常(bug),文档补充(docs),功能优化(enhancement),适合新手(good first issue),"
exempt-pr-labels: "功能异常(bug),文档补充(docs),功能优化(enhancement),适合新手(good first issue),"
================================================
FILE: .github/workflows/Delete_untagged_images.yml
================================================
name: 删除 GHCR Untagged 镜像
on:
schedule:
- cron: "0 0 15 * *"
release:
types: [ published ]
workflow_dispatch:
jobs:
delete-untagged:
runs-on: ubuntu-latest
steps:
- name: Delete all containers from package without tags
uses: Chizkiyahu/delete-untagged-ghcr-action@v6
with:
token: ${{ secrets.PAT_TOKEN }}
repository_owner: ${{ github.repository_owner }}
repository: ${{ github.repository }}
package_name: "xhs-downloader"
untagged_only: true
owner_type: user
================================================
FILE: .github/workflows/Manually_build_executable_programs.yml
================================================
name: 构建可执行文件
on:
workflow_dispatch:
jobs:
build:
name: 构建于 ${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ windows-latest, windows-11-arm, macos-15-intel, macos-latest ]
steps:
- name: 签出存储库
uses: actions/checkout@v4
- name: 设置 Python
uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: 安装依赖项
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install pyinstaller
- name: 构建 Win 可执行文件
if: runner.os == 'Windows'
run: |
echo "DATE=$(Get-Date -Format 'yyyyMMdd')" >> $env:GITHUB_ENV
pyinstaller --icon=./static/XHS-Downloader.ico --add-data "static:static" --add-data "locale:locale" --collect-all fastmcp --collect-all rich --runtime-hook ./source/expansion/pyi_rth_beartype.py main.py
shell: pwsh
- name: 构建 Mac 可执行文件
if: runner.os == 'macOS'
run: |
echo "DATE=$(date +'%Y%m%d')" >> $GITHUB_ENV
pyinstaller --icon=./static/XHS-Downloader.icns --add-data "static:static" --add-data "locale:locale" --collect-all fastmcp --collect-all rich --runtime-hook ./source/expansion/pyi_rth_beartype.py main.py
- name: 上传文件
uses: actions/upload-artifact@v4
with:
name: XHS-Downloader_${{ runner.os }}_${{ runner.arch }}_${{ env.DATE }}
path: dist/main/
================================================
FILE: .github/workflows/Manually_docker_image.yml
================================================
name: 构建并发布 Docker 镜像
on:
workflow_dispatch:
inputs:
is_beta:
type: boolean
required: true
description: "开发版"
default: true
custom_version:
type: string
required: false
description: "版本号"
default: ""
permissions:
contents: read
packages: write
attestations: write
id-token: write
env:
REGISTRY: ghcr.io
DOCKER_REPO: ${{ secrets.DOCKERHUB_USERNAME }}/xhs-downloader
GHCR_REPO: ghcr.io/${{ secrets.DOCKERHUB_USERNAME }}/xhs-downloader
jobs:
publish-docker:
runs-on: ubuntu-latest
steps:
- name: 拉取源码
uses: actions/checkout@v4
with:
fetch-depth: 1
- name: 获取最新的发布标签
id: get-latest-release
run: |
if [ -z "${{ github.event.inputs.custom_version }}" ]; then
LATEST_TAG=$(curl -s \
-H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" \
https://api.github.com/repos/${{ github.repository }}/releases/latest \
| jq -r '.tag_name')
else
LATEST_TAG=${{ github.event.inputs.custom_version }}
fi
if [ -z "$LATEST_TAG" ]; then
exit 1
fi
echo "LATEST_TAG=$LATEST_TAG" >> $GITHUB_ENV
- name: 设置 QEMU
uses: docker/setup-qemu-action@v3
- name: 设置 Docker Buildx
uses: docker/setup-buildx-action@v3
- name: 生成标签
id: generate-tags
run: |
if [ "${{ inputs.is_beta }}" == "true" ]; then
LATEST_TAG="${LATEST_TAG%.*}.$(( ${LATEST_TAG##*.} + 1 ))"
echo "LATEST_TAG=$LATEST_TAG" >> $GITHUB_ENV
TAGS="${{ env.DOCKER_REPO }}:${LATEST_TAG}-dev,${{ env.GHCR_REPO }}:${LATEST_TAG}-dev"
else
TAGS="${{ env.DOCKER_REPO }}:${LATEST_TAG},${{ env.DOCKER_REPO }}:latest,${{ env.GHCR_REPO }}:${LATEST_TAG},${{ env.GHCR_REPO }}:latest"
fi
echo "TAGS=$TAGS" >> $GITHUB_ENV
- name: 登录到 Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: 登录到 GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: 构建和推送 Docker 镜像到 Docker Hub 和 GHCR
uses: docker/build-push-action@v6
with:
context: .
platforms: linux/amd64,linux/arm64,linux/arm/v7
push: true
tags: ${{ env.TAGS }}
provenance: false
sbom: false
================================================
FILE: .github/workflows/Release_build_executable_program.yml
================================================
name: 自动构建并发布可执行文件
on:
release:
types: [ published ]
permissions:
contents: write
discussions: write
jobs:
build:
name: 构建于 ${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ windows-latest, windows-11-arm, macos-15-intel, macos-latest ]
steps:
- name: 签出存储库
uses: actions/checkout@v4
- name: 设置 Python
uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: 安装依赖项
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install pyinstaller
- name: 构建 Win 可执行文件
if: runner.os == 'Windows'
run: |
pyinstaller --icon=./static/XHS-Downloader.ico --add-data "static:static" --add-data "locale:locale" --collect-all fastmcp --collect-all rich --runtime-hook ./source/expansion/pyi_rth_beartype.py main.py
shell: pwsh
- name: 构建 Mac 可执行文件
if: runner.os == 'macOS'
run: |
pyinstaller --icon=./static/XHS-Downloader.icns --add-data "static:static" --add-data "locale:locale" --collect-all fastmcp --collect-all rich --runtime-hook ./source/expansion/pyi_rth_beartype.py main.py
- name: 创建压缩包
run: |
7z a "XHS-Downloader_V${{ github.event.release.tag_name }}_${{ runner.os }}_${{ runner.arch }}.zip" ./dist/main/*
shell: bash
- name: 上传文件到 release
uses: softprops/action-gh-release@v2
with:
files: |
./XHS-Downloader_V*.zip
name: XHS-Downloader V${{ github.event.release.tag_name }}
body_path: ./static/Release_Notes.md
draft: ${{ github.event.release.draft }}
prerelease: ${{ github.event.release.prerelease }}
================================================
FILE: .github/workflows/Release_docker_image.yml
================================================
name: 自动构建并发布 Docker 镜像
on:
release:
types: [published]
permissions:
contents: read
packages: write
attestations: write
id-token: write
env:
REGISTRY: ghcr.io
DOCKER_REPO: ${{ secrets.DOCKERHUB_USERNAME }}/xhs-downloader
GHCR_REPO: ghcr.io/${{ secrets.DOCKERHUB_USERNAME }}/xhs-downloader
jobs:
publish-docker:
runs-on: ubuntu-latest
steps:
- name: 拉取源码
uses: actions/checkout@v4
with:
fetch-depth: 1
- name: 设置 QEMU
uses: docker/setup-qemu-action@v3
- name: 设置 Docker Buildx
uses: docker/setup-buildx-action@v3
- name: 登录到 Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: 登录到 GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: 构建和推送 Docker 镜像到 Docker Hub 和 GHCR
uses: docker/build-push-action@v6
with:
context: .
platforms: linux/amd64,linux/arm64,linux/arm/v7
push: true
tags: |
${{ env.DOCKER_REPO }}:${{ github.event.release.tag_name }}
${{ env.DOCKER_REPO }}:latest
${{ env.GHCR_REPO }}:${{ github.event.release.tag_name }}
${{ env.GHCR_REPO }}:latest
provenance: false
sbom: false
================================================
FILE: .gitignore
================================================
__pycache__/
*.pyc
/.venv/
/.ruff_cache/
/.idea/
/.run/
/Volume/
!/.github/
================================================
FILE: .python-version
================================================
3.12
================================================
FILE: Dockerfile
================================================
# ---- 阶段 1: 构建器 (Builder) ----
# 使用一个功能完整的镜像,它包含编译工具或可以轻松安装它们
FROM python:3.12-bullseye as builder
# 安装编译 uvloop 和 httptools 所需的系统依赖 (C编译器等)
RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential \
&& rm -rf /var/lib/apt/lists/*
# 设置工作目录
WORKDIR /app
# 复制需求文件
COPY requirements.txt .
# 在这个具备编译环境的阶段安装所有 Python 依赖
# 安装到一个独立的目录 /install 中,以便后续复制
RUN pip install --no-cache-dir --prefix="/install" -r requirements.txt
# ---- 阶段 2: 最终镜像 (Final Image) ----
# 使用轻量级 slim 镜像作为最终的运行环境
FROM python:3.12-slim
# 设置工作目录
WORKDIR /app
# 添加元数据标签
LABEL name="XHS-Downloader" authors="JoeanAmier" repository="https://github.com/JoeanAmier/XHS-Downloader"
# 从构建器阶段,将已经安装好的依赖包复制到最终镜像的系统路径中
COPY --from=builder /install /usr/local
# 复制项目代码和相关文件
COPY locale /app/locale
COPY source /app/source
COPY static/XHS-Downloader.tcss /app/static/XHS-Downloader.tcss
COPY LICENSE /app/LICENSE
COPY main.py /app/main.py
# 暴露端口
EXPOSE 5556
# 创建挂载点
VOLUME /app/Volume
# 设置容器启动命令
CMD ["python", "main.py"]
================================================
FILE: LICENSE
================================================
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc.
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
Copyright (C)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
Copyright (C)
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
.
================================================
FILE: README.md
================================================
# 💰 项目赞助
## DartNode
[](https://dartnode.com "Powered by DartNode - Free VPS for Open Source")
***
## ZMTO
🔥 RedNote Link Extraction/Content Collection Tool:Extract account-published, favorites, and liked notes links; extract search result notes links and user links; collect RedNote notes information; extract RedNote notes download addresses; download RedNote notes files!
🔥 "RedNote", "XiaoHongShu" and "小红书" have the same meaning, and this project is collectively referred to as "RedNote".
⭐ Due to the author's limited energy, I was unable to update the English document in a timely manner, and the content may have become outdated, partial translation is machine translation, the translation result may be incorrect, Suggest referring to Chinese documentation. If you want to contribute to translation, we warmly welcome you.
📑 Project Features
Program Features and User Script Features (Click to Expand)
Supports entering multiple notes links at once, separated by spaces; the program will automatically extract valid links without additional processing!
🪟 About the Terminal
⭐ It is recommended to use the Windows Terminal (default terminal for Windows 11) to run the program for the best display effect!
🥣 Usage
If you only need to download notes files, it is recommended to choose Program Run; if you have other needs, it is recommended to choose Source Code Run!
⚠️ Cookies are optional. If issues occur, please try configuring or updating them and retry!
⚠️ When Cookie is not set, video works can only be downloaded in low resolution; it is recommended to configure Cookie to obtain higher quality (no need to log in to the account)!
🖱 Program Run
⭐ Mac OS, Windows 10 and above users can go to Releases or Actions to download the program package, unzip it, open the program folder, and double-click to run main to use.
⭐ This project includes GitHub Actions for automatic building executable files. Users can use GitHub Actions to build the latest source code into executable files at any time!
⭐ For the automatic building executable files tutorial, please refer to the Build of Executable File Guide section of this document. If you need a more detailed step-by-step tutorial with illustrations, please check out this article!
Note: Due to the macOS platform's executable file main not being code-signed, it will be restricted by system security measures on first run. Please execute the command xattr -cr project_folder_path in the terminal to remove the security flag, after which it can run normally.
If you use the program in this way, the default download path for files is: .\_internal\Volume\Download; the configuration file path is: .\_internal\Volume\settings.json
Update Methods
Method 1: Download and extract the files, then copy the old version of the _internal\Volume folder into the new version's _internal folder.
Method 2: Download and extract the files (do not run the program), then copy all files and directly overwrite the old version.
Note: The <image name> here must be consistent with the image name you used in the first step (joeanamier/xhs-downloader or ghcr.io/joeanamier/xhs-downloader)
When running the project via Docker, the command line call mode is not supported. The clipboard reading and clipboard monitoring functions are unavailable, but pasting content notes fine. Please provide feedback if other features are not functioning properly!
🛠 Command Line Mode
The project supports command line mode. If you want to download specific images from a text and image notes, you can use this mode to set the image sequence number you want to download!
Note: When the --index parameter is not set, multiple notes links can be passed in. All links must be enclosed in quotation marks and separated by spaces. When the --index parameter is set, multiple notes links are not supported. Even if multiple links are passed in, the program will only process the first link!
The bool type parameters support setting with true, false, 1, 0, yes, no, on or off (case insensitive).
Read Browser Cookies
This feature is no longer available. Please refer to the Obtain Cookie tutorial!
You can use the command line to read cookies from browser and write them to the configuration file!
Compatibility note: The third-party module this feature depends on has not been updated for a long time and may not properly support the latest browser versions. If the feature is not working properly, please try obtaining cookies manually!
🖥 Server Mode
Server modes include API mode and MCP mode!
API Mode
Start: Run the command: python .\main.py api
Stop: Press Ctrl + C to stop the server
Open http://127.0.0.1:5556/docs or http://127.0.0.1:5556/redoc; you will see automatically generated interactive API documentation!
Request endpoint:/xhs/detail
Request method:POST
Request format:JSON
Request parameters:
Parameter
Type
Description
Default
url
str
RedNote notes link, auto-extraction, does not support multiple links; Required parameter
None
download
bool
Whether to download the notes file; set to true will take more time; Optional parameter
false
index
list[int]
Download specific image files by index, only effective for text and image notes; not effective when the download parameter is set to false; Optional parameter
null
cookie
str
Cookie used when requesting data; Optional parameter
Settings cookie Value
proxy
str
Proxy used when requesting data; Optional parameter
Settings proxy Value
skip
bool
Whether to skip notes with download records; set to true will not return notes data with download records; Optional parameter
When downloading images, you can specify the sequence numbers of the images to download. By default, post information is not returned. If you need the post information, please explicitly state so during the conversation.
📜 Others
Due to the date information carried in the links of RedNote notes, using links obtained from previous dates may be subject to risk control. It is recommended to use the latest RedNote notes links when downloading RedNote work files
Windows system requires running programs as an administrator to read Chromium, Chrome, Edge browser cookies
If the function to save notes data to a file is enabled, the notes data will be stored by default in the ./Volume/Download/ExploreData.db file
The program's download records will be stored in the ./Volume/ExploreID.db file
To prevent high-frequency requests from impacting the platform's servers, this project includes a built-in request delay mechanism
🕹 User Script
If your browser has the Tampermonkey extension installed, you can use the userscript to try the project's features!
View Tampermonkey userscript screenshots (click to expand)
Note: Using the XHS-Downloader user script to batch extract notes links, in combination with the XHS-Downloader program, can achieve batch downloading of notes files!
Modify user script language
🌐 Connect to Server
⭐ This project supports interaction with the main program through a browser userscript, enabling one-click push of download tasks.
Function Description:
In the project program's configuration file, you need to set the script_server parameter to true
Keep the project program running in the background, where it will act as a server to receive commands from the userscript (TUI, MCP, and API modes are all supported)
When you visit a post page in your browser, click the Push Download Task option in the userscript menu
The userscript will send the download task to the project program, which will handle and download the files
📜 Script Instructions
When downloading notes from RedNote, the script requires time to process the files. Please wait for a moment and do not click the download button multiple times.
When extracting links for posts, collects, likes, and board from an account, the script can automatically scroll the page until all notes are loaded.
When extracting recommended notes links, search notes, and user links, the script can automatically scroll a specified number of times to load more content. The default number of page scrolls is 50.
The automatic scrolling page function is turned off by default; Users can freely open and modify the number of times the page is scrolled, and the modification will take effect immediately.
If the automatic page scroll feature is not enabled, users need to manually scroll the page to load more content before performing other actions.
Support packaging and downloading of work files; This feature is enabled by default, and notes from multiple files will be downloaded in compressed file format
When pushing download tasks to the server, the file format, name rules, etc. settings will be based on the server configuration file settings
Using global proxy tools may cause script download failures. If there are issues, please try disabling the proxy tool. If necessary, contact the author for feedback.
XHS-Downloader userscript only implements the data collection functionality for visible content and does not include any paid or cracked features.
The automatic page scroll feature has been refactored and is turned off by default! Enabling this feature may be detected as automated behavior by RedNote, potentially resulting in account risk control or banning.
💻 Secondary Development
If you have other needs, you can perform code calls or modifications based on the comments in example.py!
The project uses pyperclip to implement clipboard reading functionality, which varies across different systems.
On Windows, no additional modules are needed.
On Mac, this module makes use of the pbcopy and pbpaste commands, which should come with the os.
On Linux, this module makes use of the xclip or xsel commands, which should come with the os. Otherwise run "sudo apt-get install xclip" or "sudo apt-get install xsel" (Note: xsel does not always seem to work.)
Otherwise on Linux, you will need the qtpy or PyQT5 modules installed.
⚙️ Configuration File
The ./Volume/settings.json file in the project's root directory is automatically generated on the first run. You can use it to customize the program's operating parameters. If an invalid parameter value is set, the program will revert to its default value.
If you are unable to modify settings through the program's interface, you can edit this configuration file directly. If your computer lacks a suitable program for editing JSON files, we recommend using an online tool. Remember to restart the software after making changes for them to take effect.
Parameter
Type
Description
Default Value
mapping_data
str: str
#Author alias mapping data, format: author ID: author alias
null
work_path
str
Root path for saving notes data/files
Project root path/Volume
folder_name
str
Name of the folder for storing notes files
Download
name_format
str
#Format of notes file name, separated by spaces between fields, supports fields: 收藏数量、评论数量、分享数量、点赞数量、作品标签、作品ID、作品标题、作品描述、作品类型、发布时间、最后更新时间、作者昵称、作者ID
发布时间 作者昵称 作品标题
user_agent
str
Browser User Agent
Built-in Chrome User Agent
cookie
str
RedNote web version cookie, No login required, non essential parameters!
None
proxy
str
Set program proxy
null
timeout
int
Request data timeout limit, in seconds
10
chunk
int
Size of data chunk to fetch from the server each time when downloading files, in bytes
2097152(2 MB)
max_retry
int
Maximum number of retries when requesting data fails
5
record_data
bool
Whether to save notes data to a file, saved in SQLite format
false
image_format
str
Download format for image notes files, supported: AUTO、PNG、WEBP、JPEG、HEIC Some notes do not have files in HEIC format, and the downloaded files may be in WEBP format When set toAUTO, it represents dynamic format, and the actual format depends on the server's response data
JPEG
image_download
bool
Switch for downloading image and atlas notes files
Whether to store each notes files in a separate folder; the folder name matches the file name
false
download_record
bool
Do record the ID of successfully downloaded notes? If enabled, the program will automatically skip downloading notes with records
true
author_archive
bool
#Whether to save each author's notes into a separate folder; The folder name is authorID_nickname
false
write_mtime
bool
Whether to modify the modified time attribute of the notes file to the publication time of the notes.
false
language
str
Set program language. Currently supported: zh_CN, en_US
zh_CN
script_server
bool
Whether to enable the user script server for receiving download tasks from the browser user script (effective in TUI, MCP, and API modes)
false
name_format instructions (Currently only supports Chinese values) :
收藏数量: Number of Collections
评论数量: Number of Comments
分享数量: Number of Shares
点赞数量: Number of Likes
作品标签: Notes Tags
作品ID: Notes ID
作品标题: Notes Title
作品描述: Notes Description
作品类型: Notes Type
发布时间: Publish Time
最后更新时间: Last Updated Time
作者昵称: Author Nickname
作者ID: Author ID
When author_archive is set to true, the program will store each author's notes in dedicated folders. If an author's nickname changes, the program automatically updates the nickname portion in existing downloaded filenames!
Additionally, you can configure author aliases through the mapping_data parameter. When an alias is set, the program will use your custom alias instead of the original nickname in filenames!
Additional Notes: The parameters user_agent examples are provided for reference; Strongly recommend setting according to actual browser information!
🌐 Cookie
Open the browser (optional: start in incognito mode) and visit https://www.xiaohongshu.com/explore
Log in to your RedNote account (can be skipped)
Press F12 to open the developer tools
Select the Network tab
Check Preserve log
In the Filter input box, enter cookie-name:web_session
Select the Fetch/XHR filter
Click on any piece of notes on the RedNote page
In the Network tab, select any data packet (if no packets appear, repeat step 7)
Copy and paste the entire Cookie into the program or configuration file
🗳 Download Records
XHS-Downloader will store the IDs of downloaded notes in a database. When downloading the same notes again, XHS-Downloader will automatically skip the file download (even if the notes file does not exist). If you want to re-download the notes file, please delete the corresponding notes ID from the database and then use XHS-Downloader to download the notes file again!
This feature is enabled by default. If it is turned off, XHS-Downloader will check if the file exists. If the file exists, it will skip the download!
Build of Executable File Guide
Build of Executable File Guide (Click to Expand)
This guide will walk you through forking this repository and executing GitHub Actions to automatically build and package
the program based on the latest source code!
---
## Steps to Use
### 1. Fork the Repository
1. Click the **Fork** button at the top right of the project repository to fork it to your personal GitHub account
2. Your forked repository address will look like this: `https://github.com/your-username/this-repo`
---
### 2. Enable GitHub Actions
1. Go to the page of your forked repository
2. Click the **Settings** tab at the top
3. Click the **Actions** tab on the right
4. Click the **General** option
5. Under **Actions permissions**, select **Allow all actions and reusable workflows** and click the **Save** button
---
### 3. Manually Trigger the Build Process
1. In your forked repository, click the **Actions** tab at the top
2. Find the workflow named **构建可执行文件**
3. Click the **Run workflow** button on the right:
- Select the **master** or **develop** branch
- Click **Run workflow**
---
### 4. Check the Build Progress
1. On the **Actions** page, you can see the execution records of the triggered workflow
2. Click on the run record to view detailed logs to check the build progress and status
---
### 5. Download the Build Result
1. Once the build is complete, go to the corresponding run record page
2. In the **Artifacts** section at the bottom of the page, you will see the built result file
3. Click to download and save it to your local machine to get the built program
---
## Notes
1. **Resource Usage**:
- GitHub provides free build environments for Actions, with a monthly usage limit (2000 minutes) for free-tier
users
2. **Code Modifications**:
- You are free to modify the code in your forked repository to customize the build process
- After making changes, you can trigger the build process again to get your customized version
3. **Stay in Sync with the Main Repository**:
- If the main repository is updated with new code or workflows, it is recommended that you periodically sync your
forked repository to get the latest features and fixes
---
## Frequently Asked Questions
### Q1: Why can't I trigger the workflow?
A: Please ensure that you have followed the steps to **Enable Actions**. Otherwise, GitHub will prevent the workflow
from running
### Q2: What should I do if the build process fails?
A:
- Check the run logs to understand the cause of the failure
- Ensure there are no syntax errors or dependency issues in the code
- If the problem persists, please open an issue on
the [Issues page](https://github.com/JoeanAmier/XHS-Downloader/issues)
### Q3: Can I directly use the Actions from the main repository?
A: Due to permission restrictions, you cannot directly trigger Actions from the main repository. Please use the forked
repository to execute the build process
⭐ Star History
♥️ Support the Project
If XHS-Downloader has been helpful to you, please consider giving it a Star ⭐, Thank you for your support!
微信(WeChat)
支付宝(Alipay)
If you are willing, you may consider making a donation to provide additional support for XHS-Downloader!
🌟 Contribution Guidelines
Welcome to contributing to this project! To keep the codebase clean, efficient, and easy to maintain, please read the following guidelines carefully to ensure that your contributions can be accepted and integrated smoothly.
Before starting development, please pull the latest code from the develop branch as the basis for your modifications; this helps avoid merge conflicts and ensures your changes are based on the latest state of the project.
If your changes involve multiple unrelated features or issues, please split them into several independent commits or pull requests.
Each pull request should focus on a single feature or fix as much as possible, to facilitate code review and testing.
Follow the existing coding style; make sure your code is consistent with the style already present in the project; please use the Ruff tool to maintain code formatting standards.
Write code that is easy to read; add appropriate annotation to help others understand your intentions.
Each commit should include a clear and concise commit message describing the changes made. The commit message should follow this format: <type>: <short description>
When you are ready to submit a pull request, please prioritize submitting them to the develop branch; this provides maintainers with a buffer zone for additional testing and review before final merging into the master branch.
It is recommended to communicate with the author before starting development or when encountering questions to ensure alignment in direction and avoid redundant efforts or unnecessary commits.
# 💰 Project Sponsorship
## DartNode
[](https://dartnode.com "Powered by DartNode - Free VPS for Open Source")
***
## ZMTO
ZMTO: A professional cloud infrastructure provider offering sophisticated solutions with reliable technology and expert support. We also empower qualified open source initiatives with enterprise-grade VPS infrastructure, driving sustainable development and innovation in the open source ecosystem.
⚠️ Disclaimer
The user's use of this project is entirely at their own discretion and responsibility. The author assumes no liability for any losses, claims, or risks arising from the user's use of this project.
The code and functionalities provided by the author of this project are based on current knowledge and technological developments. The author strives to ensure the correctness and security of the code according to existing technical capabilities but does not guarantee that the code is entirely free of errors or defects.
All third-party libraries, plugins, or services relied upon by this project follow their respective open-source or commercial licenses. Users must review and comply with those license agreements. The author assumes no responsibility for the stability, security, or compliance of third-party components.
When using the code and features of this project, users must independently research relevant laws and regulations and ensure their actions are legal and compliant. Any legal liabilities or risks arising from violations of laws and regulations shall be borne solely by the user.
Users must not use this tool to engage in any activities that infringe intellectual property rights, including but not limited to downloading or distributing copyright-protected content without authorization. The developers do not participate in, support, or endorse any unauthorized acquisition or distribution of illegal content.
This project assumes no responsibility for the compliance of any data processing activities (including collection, storage, and transmission) conducted by users. Users must comply with relevant laws and regulations and ensure that their processing activities are lawful and proper. Legal liabilities resulting from non-compliant operations shall be borne by the user.
Under no circumstances may users associate the author, contributors, or other related parties of this project with their usage of the project, nor may they hold these parties responsible for any loss or damage arising from such usage.
The author of this project will not provide a paid version of the XHS-Downloader project, nor will they offer any commercial services related to the XHS-Downloader project.
Any secondary development, modification, or compilation based on this project is unrelated to the original author. The original author assumes no liability for any consequences resulting from such secondary development. Users bear full responsibility for all outcomes arising from such modifications.
This project grants no patent licenses; if the use of this project leads to patent disputes or infringement, the user bears all associated risks and responsibilities. Without written authorization from the author or rights holder, users may not use this project for any commercial promotion, marketing, or re-licensing.
The author reserves the right to terminate service to any user who violates this disclaimer at any time and may require them to destroy all obtained code and derivative notes.
The author reserves the right to update this disclaimer at any time without prior notice. Continued use of the project constitutes acceptance of the revised terms.
Before using the code and functionalities of this project, please carefully consider and accept the above disclaimer. If you have any questions or disagree with the statement, please do not use the code and functionalities of this project. If you use the code and functionalities of this project, it is considered that you fully understand and accept the above disclaimer, and willingly assume all risks and consequences associated with the use of this project.
# 💡 Project References
* https://github.com/encode/httpx/
* https://github.com/tiangolo/fastapi
* https://github.com/textualize/textual/
* https://github.com/pyinstaller/pyinstaller
* https://github.com/zbowling/beartype-pyinstaller-repro
* https://github.com/jlowin/fastmcp
* https://github.com/omnilib/aiosqlite
* https://github.com/carpedm20/emoji/
* https://github.com/asweigart/pyperclip
* https://github.com/lxml/lxml
* https://github.com/yaml/pyyaml
* https://github.com/pallets/click/
* https://github.com/encode/uvicorn
* https://github.com/Tinche/aiofiles
================================================
FILE: example.py
================================================
from asyncio import run
from pyperclip import paste
from httpx import post
from rich import print
from source import XHS
async def example():
"""通过代码设置参数,适合二次开发"""
# 示例链接
demo_link = "https://www.xiaohongshu.com/explore/XXX?xsec_token=XXX"
# 实例对象
work_path = "D:\\" # 作品数据/文件保存根路径,默认值:项目根路径
folder_name = "Download" # 作品文件储存文件夹名称(自动创建),默认值:Download
name_format = "作品标题 作品描述"
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" # 图文作品文件下载格式,支持:AUTO、PNG、WEBP、JPEG、HEIC
folder_mode = False # 是否将每个作品的文件储存至单独的文件夹
image_download = True # 图文、图集作品文件下载开关
video_download = True # 视频作品文件下载开关
live_download = False # 图文动图文件下载开关
download_record = True # 是否记录下载成功的作品 ID
language = "zh_CN" # 设置程序提示语言
author_archive = True # 是否将每个作者的作品存至单独的文件夹
write_mtime = True # 是否将作品文件的 修改时间 修改为作品的发布时间
# read_cookie = None # 读取浏览器 Cookie,支持设置浏览器名称(字符串)或者浏览器序号(整数),设置为 None 代表不读取
# async with XHS() as xhs:
# pass # 使用默认参数
async with XHS(
work_path=work_path,
folder_name=folder_name,
name_format=name_format,
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,
image_download=image_download,
video_download=video_download,
live_download=live_download,
download_record=download_record,
language=language,
# read_cookie=read_cookie,
author_archive=author_archive,
write_mtime=write_mtime,
) as xhs: # 使用自定义参数
download = True # 是否下载作品文件,默认值:False
# 返回作品详细信息,包括下载地址
# 获取数据失败时返回空字典
print(
await xhs.extract(
demo_link,
download,
index=[
1,
2,
5,
],
)
)
async def example_api():
"""通过 API 设置参数,适合二次开发"""
server = "http://127.0.0.1:5556/xhs/detail"
data = {
"url": "", # 必需参数
"download": True,
"index": [
3,
6,
9,
],
"proxy": "http://127.0.0.1:10808",
}
response = post(server, json=data, timeout=10)
print(response.json())
async def test():
url = "" or paste()
if not url:
return
async with XHS(
download_record=False,
) as xhs:
print(
await xhs.extract(
url,
# download=True,
)
)
if __name__ == "__main__":
# run(example())
# run(example_api())
run(test())
================================================
FILE: locale/README.md
================================================
# 命令参考
**运行命令前,确保已经安装了 `gettext` 软件包,并配置好环境变量。**
**Before running the command, ensure that the `gettext` package is installed and the environment variables are properly
configured.**
* `xgettext --files-from=py_files.txt -d xhs -o xhs.pot`
* `mkdir zh_CN\LC_MESSAGES`
* `msginit -l zh_CN -o zh_CN/LC_MESSAGES/xhs.po -i xhs.pot`
* `mkdir en_US\LC_MESSAGES`
* `msginit -l en_US -o en_US/LC_MESSAGES/xhs.po -i xhs.pot`
* `msgmerge -U zh_CN/LC_MESSAGES/xhs.po xhs.pot`
* `msgmerge -U en_US/LC_MESSAGES/xhs.po xhs.pot`
# 翻译贡献指南
* 如果想要贡献支持更多语言,请在终端切换至 `locale` 文件夹,运行命令
`msginit -l 语言代码 -o 语言代码/LC_MESSAGES/xhs.po -i xhs.pot`
生成 po 文件并编辑翻译。
* 如果想要贡献改进翻译结果,请直接编辑 `xhs.po` 文件内容。
* 仅需提交 `xhs.po` 文件,作者会转换格式并合并。
# Translation Contribution Guide
* If you want to contribute support for more languages, please switch to the `locale` folder in the terminal and run the
command `msginit -l language_code -o language_code/LC_MESSAGES/xhs.po -i xhs.pot` to generate the po file and edit the
translation.
* If you want to contribute to improving the translation, please directly edit the content of the `xhs.po` file.
* Only the `xhs.po` file needs to be submitted, and the author will convert the format and merge it.
================================================
FILE: locale/en_US/LC_MESSAGES/xhs.po
================================================
# English translations for XHS-Downloader package.
# Copyright (C) 2024 THE XHS-Downloader'S COPYRIGHT HOLDER
# This file is distributed under the same license as the XHS-Downloader package.
# FIRST AUTHOR , 2024.
#
msgid ""
msgstr ""
"Project-Id-Version: XHS-Downloader 2.7\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-02-06 16:45+0800\n"
"PO-Revision-Date: 2024-12-22 14:14+0800\n"
"Last-Translator: \n"
"Language-Team: English\n"
"Language: en_US\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:223
#, python-brace-format
msgid "作品 {0} 存在下载记录,跳过下载"
msgstr "notes {0} has a download record, skip download"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:247
msgid "提取作品文件下载地址失败"
msgstr "Failed to extract the download address for the RedNote notes files"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:280
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:328
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:743
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:930
msgid "提取小红书作品链接失败"
msgstr "Failed to extract the links for RedNote notes"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:288
#, python-brace-format
msgid "共 {0} 个小红书作品待处理..."
msgstr "{0} notes from RedNote are awaiting processing..."
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:309
#, python-brace-format
msgid "共处理 {0} 个作品,成功 {1} 个,失败 {2} 个,跳过 {3} 个"
msgstr "Processed a total of {0} notes: {1} succeeded, {2} failed, {3} skipped"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:400
#, python-brace-format
msgid "作品 {0} 存在下载记录,跳过处理"
msgstr "notes {0} has a download record, skip processing"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:404
#, python-brace-format
msgid "开始处理作品:{0}"
msgstr "Start processing the notes: {0}"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:412
#, python-brace-format
msgid "{0} 获取数据失败"
msgstr "{0} failed to retrieve data"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:425
#, python-brace-format
msgid "{0} 提取数据失败"
msgstr "{0} failed to extract data"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:439
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\download.py:82
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\explore.py:81
msgid "视频"
msgstr "video"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:442
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\download.py:89
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\explore.py:82
msgid "图文"
msgstr "image"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:443
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\download.py:90
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\explore.py:81
msgid "图集"
msgstr "LivePhoto"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:447
#, python-brace-format
msgid "未知的作品类型:{0}"
msgstr "Unknown notes type: {0}"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:505
#, python-brace-format
msgid "作品处理完成:{0}"
msgstr "notes processing completed: {0}"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:611
msgid ""
"程序会自动读取并提取剪贴板中的小红书作品链接,并自动下载链接对应的作品文件,"
"如需关闭,请点击关闭按钮,或者向剪贴板写入 “close” 文本!"
msgstr ""
"The program will automatically read and extract the link to RedNote notes "
"from the clipboard, and automatically download the corresponding work file. "
"If you want to close it, please click the close button or write the "
"\"close\" text to the clipboard!"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:712
msgid "跳转至项目 GitHub 仓库"
msgstr "Jump to the project's GitHub repository"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:713
msgid "重定向至项目 GitHub 仓库主页"
msgstr "Redirect to the project's GitHub repository homepage"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:721
msgid "获取作品数据及下载地址"
msgstr "Fetch notes data and download links"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:753
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:937
msgid "获取小红书作品数据成功"
msgstr "Successfully obtained data on RedNote notes"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:755
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:939
msgid "获取小红书作品数据失败"
msgstr "Failed to obtain data on RedNote notes"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:825
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:870
msgid "小红书作品链接"
msgstr "Link to RedNote notes"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:873
msgid "指定需要下载的图文作品序号"
msgstr "Specify the serial number of the images notes to be downloaded"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:877
msgid "是否需要返回作品信息数据"
msgstr "Whether to return notes information data"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:891
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:896
msgid "作品文件下载任务执行完毕"
msgstr "notes file download task completed"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:901
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:906
msgid "作品文件下载任务未执行"
msgstr "notes file download task not executed"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\download.py:131
msgid "视频作品下载功能已关闭,跳过下载"
msgstr "The video download function has been turned off, skip download"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\download.py:150
msgid "图文作品下载功能已关闭,跳过下载"
msgstr "The image download function has been turned off, skip download"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\download.py:182
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\download.py:192
#, python-brace-format
msgid "{0} 文件已存在,跳过下载"
msgstr "{0} already exists, skipping download"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\download.py:221
#, python-brace-format
msgid "文件 {0} 缓存异常,重新下载"
msgstr "File {0} cache exception, download again"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\download.py:248
#, python-brace-format
msgid "文件 {0} 下载成功"
msgstr "file {0} download successful"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\download.py:254
#, python-brace-format
msgid "网络异常,{0} 下载失败,错误信息: {1}"
msgstr "Network error, {0} download failed, error message: {1}"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\download.py:332
#, python-brace-format
msgid "文件 {0} 格式判断失败,错误信息:{1}"
msgstr "Format recognition failed for file {0}, error message: {1}"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\explore.py:53
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\explore.py:58
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\explore.py:79
msgid "未知"
msgstr "unknown"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\request.py:66
#, python-brace-format
msgid "网络异常,{0} 请求失败: {1}"
msgstr "Network error, {0} request failed: {1}"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:129
msgid "小红书作品链接,多个链接使用空格分隔"
msgstr "RedNote notes links, separate multiple links with spaces"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:136
msgid ""
"下载指定序号的图片文件,仅对图文/图集作品生效;多个序号输入示例:\"1 3 5 7\""
msgstr ""
"Download images files with specified serial numbers, only effective for "
"images notes; Example of multiple serial numbers input: \"1 3 5 7\""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:141
msgid "作品数据/文件保存根路径"
msgstr "Root path for saving notes data / files"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:142
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:40
msgid "作品文件储存文件夹名称"
msgstr "Name of the folder for storing notes files"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:143
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:49
msgid "作品文件名称格式"
msgstr "Format of notes file name"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:145
msgid "小红书网页版 Cookie,无需登录"
msgstr "RedNote web version cookie, no need to log in"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:146
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:78
msgid "网络代理"
msgstr "Network proxy"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:147
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:88
msgid "请求数据超时限制,单位:秒"
msgstr "Network request timeout limit, in seconds"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:153
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:98
msgid "下载文件时,每次从服务器获取的数据块大小,单位:字节"
msgstr ""
"When downloading a file, the size of the data block obtained from the server "
"each time, in bytes"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:156
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:108
msgid "请求数据失败时,重试的最大次数"
msgstr "The maximum number of retries when data request fails"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:157
msgid "是否记录作品数据至文件"
msgstr "Record notes data to file"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:162
msgid "图文作品文件下载格式,支持:PNG、WEBP、JPEG、HEIC、AUTO"
msgstr ""
"Image notes file download format, supporting: PNG、WEBP、JPEG、HEIC、AUTO"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:164
msgid "动态图片下载开关"
msgstr "LivePhoto download switch"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:165
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:149
msgid "作品下载记录开关"
msgstr "Download record switch"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:170
msgid "是否将每个作品的文件储存至单独的文件夹"
msgstr "Whether to save each work's files into separate folders"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:176
msgid "是否将每个作者的作品储存至单独的文件夹"
msgstr "Whether to save each author's notes into separate folders"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:183
msgid "是否将作品文件的修改时间属性修改为作品的发布时间"
msgstr ""
"Would you like to set the file's modified time attribute to match the work's "
"publication time"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:187
msgid "设置程序语言,目前支持:zh_CN、en_US"
msgstr "Set the programming language, currently supports: zh_CN、en_US"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:188
msgid "读取指定配置文件"
msgstr "Read specified configuration file"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:190
#, python-brace-format
msgid "从指定的浏览器读取小红书网页版 Cookie,支持:{0}; 输入浏览器名称或序号"
msgstr ""
"Read RedNote web version cookies from the specified browser, supporting: "
"{0}; Enter browser name or serial number"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:203
msgid "是否更新配置文件"
msgstr "Do you need to update the configuration file"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:209
msgid "查看详细参数说明"
msgstr "View detailed parameter descriptions"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:210
msgid "查看 XHS-Downloader 版本"
msgstr "View XHS Downloader Version"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\expansion\browser.py:53
#, python-brace-format
msgid ""
"读取指定浏览器的 Cookie 并写入配置文件\n"
"Windows 系统需要以管理员身份运行程序才能读取 Chromium、Chrome、Edge 浏览器 "
"Cookie!\n"
"{options}\n"
"请输入浏览器名称或序号:"
msgstr ""
"Read cookies from the specified browser and write them to the configuration "
"file\n"
"The Windows system requires running programs as an administrator to read "
"Chromium, Chrome, Edge browser cookies!\n"
"{options}\n"
"Please enter your browser name or serial number:"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\expansion\browser.py:63
msgid "未选择浏览器!"
msgstr "Browser not selected!"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\expansion\browser.py:75
msgid "浏览器名称或序号输入错误!"
msgstr "Browser name or serial number input error!"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\expansion\browser.py:81
msgid "获取 Cookie 失败,未找到 Cookie 数据!"
msgstr "Failed to retrieve cookie, no cookie data found!"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\expansion\browser.py:119
msgid "从浏览器读取 Cookie 功能不支持当前平台!"
msgstr ""
"The cookie reading function from the browser is not supported on the current "
"platform!"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\expansion\cleaner.py:45
msgid "不受支持的操作系统类型,可能无法正常去除非法字符!"
msgstr ""
"Unsupported operating system type, may not be able to remove illegal "
"characters properly!"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\module\manager.py:240
#, python-brace-format
msgid "代理 {0} 测试成功"
msgstr "Agent {0} tested successfully"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\module\manager.py:244
#, python-brace-format
msgid "代理 {0} 测试超时"
msgstr "Agent {0} test timeout"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\module\manager.py:252
#, python-brace-format
msgid "代理 {0} 测试失败:{1}"
msgstr "Agent {0} test failed: {1}"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\module\mapping.py:55
#, python-brace-format
msgid "{old_folder} 文件夹不存在,跳过处理"
msgstr "{old_folder} directory does not exist, skipping processing"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\module\mapping.py:81
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\module\mapping.py:101
msgid "文件夹"
msgstr "folder"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\module\mapping.py:85
#, python-brace-format
msgid "文件夹 {old_folder} 已重命名为 {new_folder}"
msgstr "The folder {old_folder} has been renamed to {new_folder}"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\module\mapping.py:105
#, python-brace-format
msgid "文件夹 {old_} 重命名为 {new_}"
msgstr "The folder {old_} has been renamed to {new_}"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\module\mapping.py:171
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\module\mapping.py:185
msgid "文件"
msgstr "file"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\module\mapping.py:175
#, python-brace-format
msgid "文件 {old_file} 重命名为 {new_file}"
msgstr "The file {old_file} has been renamed to {new_file}"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\module\mapping.py:193
#, python-brace-format
msgid "{type} {old}被占用,重命名失败: {error}"
msgstr "{type} {old} is occupied, renaming failed: {error}"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\module\mapping.py:202
#, python-brace-format
msgid "{type} {new}名称重复,重命名失败: {error}"
msgstr "{type} {new} already exists, renaming failed: {error}"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\module\mapping.py:211
#, python-brace-format
msgid "处理{type} {old}时发生预期之外的错误: {error}"
msgstr "An unexpected error occurred while processing {type} {old}: {error}"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\module\tools.py:32
msgid ""
"如需重新尝试处理该对象,请关闭所有正在访问该对象的窗口或程序,然后直接按下回"
"车键!\n"
"如需跳过处理该对象,请输入任意字符后按下回车键!"
msgstr ""
"If you want to retry processing this object, please close all windows or "
"programs currently accessing it, then press Enter directly!\n"
"If you want to skip processing this object, please enter any character and "
"then press Enter!"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\about.py:20
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\index.py:29
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\monitor.py:20
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:15
msgid "退出程序"
msgstr "Quit"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\about.py:21
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\index.py:30
msgid "检查更新"
msgstr "Update"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\about.py:22
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\record.py:35
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:16
msgid "返回首页"
msgstr "Return"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\about.py:35
msgid "如果 XHS-Downloader 对您有帮助,请考虑为它点个 Star,感谢您的支持!"
msgstr ""
"If XHS-Downloader is helpful to you, please consider giving it Star. Thank "
"you for your support!"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\about.py:42
msgid "Discord 社区"
msgstr "Discord Community"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\about.py:46
msgid "邀请链接:"
msgstr "Invitation link: "
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\about.py:48
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\about.py:61
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\about.py:70
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\index.py:56
msgid "点击访问"
msgstr "Click to visit"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\about.py:51
msgid "作者的其他开源项目"
msgstr "Other open-source projects of the author"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\index.py:31
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:231
msgid "程序设置"
msgstr "Settings"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\index.py:32
msgid "下载记录"
msgstr "Record"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\index.py:33
msgid "开启监听"
msgstr "Monitor"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\index.py:34
msgid "关于项目"
msgstr "About"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\index.py:49
msgid "开源协议: "
msgstr "Open source protocol: "
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\index.py:52
msgid "项目地址: "
msgstr "Repository link: "
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\index.py:59
msgid "请输入小红书图文/视频作品链接"
msgstr "Please enter the link to the RedNote image or video notes"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\index.py:62
msgid "多个链接之间使用空格分隔"
msgstr "Separate multiple links with spaces"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\index.py:64
msgid "下载作品文件"
msgstr "Download notes files"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\index.py:65
msgid "读取剪贴板"
msgstr "Read the clipboard"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\index.py:66
msgid "清空输入框"
msgstr "Clear the input box"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\index.py:82
msgid "免责声明\n"
msgstr ""
"Disclaimer for XHS-Downloader:\n"
"\n"
"1.The use of this project is entirely at the user's own discretion and risk. "
"The author assumes no responsibility or liability of any kind for any loss, "
"damage, or risk arising from the user's use of this project.\n"
"2.The code and functionalities provided by the author of this project are "
"developed based on current knowledge and technology. The author makes every "
"effort to ensure the correctness and security of the code according to "
"current technical standards but does not guarantee that the code is "
"completely free of errors or defects.\n"
"3.All third-party libraries, plugins, or services used by this project "
"follow their original open-source or commercial licenses. Users must review "
"and comply with these license agreements accordingly. The author assumes no "
"responsibility for the stability, security, or compliance of any third-party "
"components.\n"
"4.When using this project, users must strictly comply with the requirements "
"of the GNU General Public License v3.0 and clearly indicate in appropriate "
"places that the code was used under the GNU General Public License v3.0.\n"
"5.When using the code and functionalities of this project, users must "
"independently research relevant laws and regulations and ensure that their "
"usage is legal and compliant. Any legal liabilities or risks arising from "
"violations of laws and regulations shall be borne solely by the user.\n"
"6.Users must not use this tool to engage in any activities that infringe "
"intellectual property rights, including but not limited to downloading or "
"distributing copyrighted content without authorization. Developers do not "
"participate in, support, or endorse the acquisition or distribution of any "
"illegal or unauthorized content.\n"
"7.This project assumes no responsibility for the compliance of data "
"processing activities (including collection, storage, and transmission) "
"performed by users. Users must comply with relevant laws and regulations and "
"ensure that such activities are lawful and proper. Legal liabilities "
"resulting from non-compliant operations shall be borne by the user.\n"
"8.Under no circumstances may users associate the author, contributors, or "
"other related parties of this project with their usage of the project, nor "
"may they hold these parties liable for any loss or damage resulting from "
"such usage.\n"
"9.The author of this project will not provide a paid version of the XHS-"
"Downloader project, nor will they offer any commercial services related to "
"it.\n"
"10.Any secondary development, modification, or compilation based on this "
"project is unrelated to the original author. The original author assumes no "
"liability for any consequences resulting from such secondary development. "
"Users bear full responsibility for all outcomes arising from such "
"modifications.\n"
"11.This project does not grant users any patent licenses. If the use of this "
"project leads to patent disputes or infringement, the user assumes all "
"associated risks and responsibilities. Without written authorization from "
"the author or rights holder, users may not use this project for any "
"commercial promotion, advertising, or re-licensing.\n"
"12.The author reserves the right to terminate service to any user who "
"violates this disclaimer at any time and may require them to destroy all "
"obtained code and derivative works.\n"
"13.The author reserves the right to update this disclaimer at any time "
"without prior notice. Continued use of the project constitutes acceptance of "
"the revised terms.\n"
"\n"
"Before using the code and functionalities of this project, please carefully "
"consider and accept the above disclaimer. If you have any questions or "
"disagree with the above statements, please do not use the code and "
"functionalities of this project. If you do use the code and functionalities "
"of this project, it shall be deemed that you have fully understood and "
"accepted the above disclaimer and voluntarily assume all risks and "
"consequences associated with its use.\n"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\index.py:93
msgid "未输入任何小红书作品链接"
msgstr "No RedNote notes links provided"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\index.py:122
msgid "下载小红书作品文件失败"
msgstr "Failed to download the RedNote notes files"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\loading.py:19
msgid "程序处理中..."
msgstr "Processing..."
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\monitor.py:21
msgid "关闭监听"
msgstr "Close"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\monitor.py:33
msgid "已启动监听剪贴板模式"
msgstr "Currently in monitoring clipboard mode"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\monitor.py:35
msgid "退出监听剪贴板模式"
msgstr "Exit monitoring clipboard mode"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\record.py:23
msgid "请输入待删除的小红书作品链接或作品 ID"
msgstr "Please enter the link or ID of the RedNote notes to be deleted"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\record.py:26
msgid ""
"支持输入作品 ID 或包含作品 ID 的作品链接,多个链接或 ID 之间使用空格分隔"
msgstr ""
"Support input of notes ID or links containing notes ID, with multiple links "
"or IDs separated by spaces"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\record.py:32
msgid "删除指定作品 ID"
msgstr "Delete specified notes ID"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\record.py:46
msgid "删除下载记录成功"
msgstr "Successfully deleted download record"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:30
msgid "作品数据 / 文件保存根路径"
msgstr "Root path for saving notes data / files"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:35
msgid "程序根路径"
msgstr "Program root path"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:64
msgid "内置 Chrome User Agent"
msgstr "Chrome User Agent"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:69
msgid "小红书网页版 Cookie"
msgstr "RedNote Web Cookie"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:83
msgid "不使用代理"
msgstr "No proxy"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:120
msgid "记录作品详细数据"
msgstr "Record notes data"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:125
msgid "作品归档保存模式"
msgstr "notes Archiving Mode"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:130
msgid "视频作品下载开关"
msgstr "Video download switch"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:135
msgid "图文作品下载开关"
msgstr "Image download switch"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:144
msgid "动图文件下载开关"
msgstr "LivePhoto download switch"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:154
msgid "作者归档保存模式"
msgstr "Author Archiving Mode"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:159
msgid "更新文件修改时间"
msgstr "Update File Modification Time"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:168
msgid "脚本服务器开关"
msgstr "Script server switch"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:176
msgid "图片下载格式"
msgstr "Image download format"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:180
msgid "程序语言"
msgstr "Program language"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:184
msgid "视频下载偏好"
msgstr "Video preferences"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:213
msgid "保存配置"
msgstr "Save settings"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:217
msgid "放弃更改"
msgstr "Discard changes"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:227
msgid "小红书网页版 Cookie,无需登录,参数已设置"
msgstr ""
"RedNote web version cookie, no login required, parameters have been set"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:228
msgid "小红书网页版 Cookie,无需登录,参数未设置"
msgstr "RedNote web version cookie, no login required, parameters not set"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\update.py:26
msgid "正在检查新版本,请稍等..."
msgstr "Checking for new version, please wait..."
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\update.py:45
#, python-brace-format
msgid "检测到新版本:{0}.{1}"
msgstr "Detected new version: {0} {1}"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\update.py:53
msgid "当前版本为开发版, 可更新至正式版"
msgstr "Detected a new official version"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\update.py:58
msgid "当前已是最新开发版"
msgstr "You are already using the latest development version"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\update.py:63
msgid "当前已是最新正式版"
msgstr "You are already using the latest official version"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\update.py:70
msgid "检测新版本失败"
msgstr "Failed to check for a new version"
================================================
FILE: locale/generate_path.py
================================================
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
def find_python_files(dir_, file):
with open(file, "w", encoding="utf-8") as f:
for py_file in dir_.rglob("*.py"): # 递归查找所有 .py 文件
f.write(str(py_file) + "\n") # 写入文件路径
# 设置源目录和输出文件
source_directory = ROOT.joinpath("source") # 源目录
output_file = "py_files.txt" # 输出文件名
find_python_files(source_directory, output_file)
print(f"所有 .py 文件路径已保存到 {output_file}")
================================================
FILE: locale/po_to_mo.py
================================================
from pathlib import Path
from subprocess import run
ROOT = Path(__file__).resolve().parent
def scan_directory():
return [
item.joinpath("LC_MESSAGES/xhs.po") for item in ROOT.iterdir() if item.is_dir()
]
def generate_map(files: list[Path]):
return [(i, i.with_suffix(".mo")) for i in files]
def generate_mo(maps: list[tuple[Path, Path]]):
for i, j in maps:
command = f'msgfmt --check -o "{j}" "{i}"'
print(run(command, shell=True, text=True))
if __name__ == "__main__":
generate_mo(generate_map(scan_directory()))
================================================
FILE: locale/xhs.pot
================================================
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR , YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: XHS-Downloader 2.7\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-02-06 16:45+0800\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME \n"
"Language-Team: LANGUAGE \n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:223
#, python-brace-format
msgid "作品 {0} 存在下载记录,跳过下载"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:247
msgid "提取作品文件下载地址失败"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:280
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:328
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:743
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:930
msgid "提取小红书作品链接失败"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:288
#, python-brace-format
msgid "共 {0} 个小红书作品待处理..."
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:309
#, python-brace-format
msgid "共处理 {0} 个作品,成功 {1} 个,失败 {2} 个,跳过 {3} 个"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:400
#, python-brace-format
msgid "作品 {0} 存在下载记录,跳过处理"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:404
#, python-brace-format
msgid "开始处理作品:{0}"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:412
#, python-brace-format
msgid "{0} 获取数据失败"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:425
#, python-brace-format
msgid "{0} 提取数据失败"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:439
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\download.py:82
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\explore.py:81
msgid "视频"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:442
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\download.py:89
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\explore.py:82
msgid "图文"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:443
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\download.py:90
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\explore.py:81
msgid "图集"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:447
#, python-brace-format
msgid "未知的作品类型:{0}"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:505
#, python-brace-format
msgid "作品处理完成:{0}"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:611
msgid ""
"程序会自动读取并提取剪贴板中的小红书作品链接,并自动下载链接对应的作品文件,"
"如需关闭,请点击关闭按钮,或者向剪贴板写入 “close” 文本!"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:712
msgid "跳转至项目 GitHub 仓库"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:713
msgid "重定向至项目 GitHub 仓库主页"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:721
msgid "获取作品数据及下载地址"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:753
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:937
msgid "获取小红书作品数据成功"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:755
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:939
msgid "获取小红书作品数据失败"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:825
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:870
msgid "小红书作品链接"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:873
msgid "指定需要下载的图文作品序号"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:877
msgid "是否需要返回作品信息数据"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:891
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:896
msgid "作品文件下载任务执行完毕"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:901
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:906
msgid "作品文件下载任务未执行"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\download.py:131
msgid "视频作品下载功能已关闭,跳过下载"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\download.py:150
msgid "图文作品下载功能已关闭,跳过下载"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\download.py:182
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\download.py:192
#, python-brace-format
msgid "{0} 文件已存在,跳过下载"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\download.py:221
#, python-brace-format
msgid "文件 {0} 缓存异常,重新下载"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\download.py:248
#, python-brace-format
msgid "文件 {0} 下载成功"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\download.py:254
#, python-brace-format
msgid "网络异常,{0} 下载失败,错误信息: {1}"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\download.py:332
#, python-brace-format
msgid "文件 {0} 格式判断失败,错误信息:{1}"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\explore.py:53
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\explore.py:58
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\explore.py:79
msgid "未知"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\request.py:66
#, python-brace-format
msgid "网络异常,{0} 请求失败: {1}"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:129
msgid "小红书作品链接,多个链接使用空格分隔"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:136
msgid ""
"下载指定序号的图片文件,仅对图文/图集作品生效;多个序号输入示例:\"1 3 5 7\""
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:141
msgid "作品数据/文件保存根路径"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:142
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:40
msgid "作品文件储存文件夹名称"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:143
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:49
msgid "作品文件名称格式"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:145
msgid "小红书网页版 Cookie,无需登录"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:146
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:78
msgid "网络代理"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:147
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:88
msgid "请求数据超时限制,单位:秒"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:153
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:98
msgid "下载文件时,每次从服务器获取的数据块大小,单位:字节"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:156
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:108
msgid "请求数据失败时,重试的最大次数"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:157
msgid "是否记录作品数据至文件"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:162
msgid "图文作品文件下载格式,支持:PNG、WEBP、JPEG、HEIC、AUTO"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:164
msgid "动态图片下载开关"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:165
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:149
msgid "作品下载记录开关"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:170
msgid "是否将每个作品的文件储存至单独的文件夹"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:176
msgid "是否将每个作者的作品储存至单独的文件夹"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:183
msgid "是否将作品文件的修改时间属性修改为作品的发布时间"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:187
msgid "设置程序语言,目前支持:zh_CN、en_US"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:188
msgid "读取指定配置文件"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:190
#, python-brace-format
msgid "从指定的浏览器读取小红书网页版 Cookie,支持:{0}; 输入浏览器名称或序号"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:203
msgid "是否更新配置文件"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:209
msgid "查看详细参数说明"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:210
msgid "查看 XHS-Downloader 版本"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\expansion\browser.py:53
#, python-brace-format
msgid ""
"读取指定浏览器的 Cookie 并写入配置文件\n"
"Windows 系统需要以管理员身份运行程序才能读取 Chromium、Chrome、Edge 浏览器 "
"Cookie!\n"
"{options}\n"
"请输入浏览器名称或序号:"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\expansion\browser.py:63
msgid "未选择浏览器!"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\expansion\browser.py:75
msgid "浏览器名称或序号输入错误!"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\expansion\browser.py:81
msgid "获取 Cookie 失败,未找到 Cookie 数据!"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\expansion\browser.py:119
msgid "从浏览器读取 Cookie 功能不支持当前平台!"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\expansion\cleaner.py:45
msgid "不受支持的操作系统类型,可能无法正常去除非法字符!"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\module\manager.py:240
#, python-brace-format
msgid "代理 {0} 测试成功"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\module\manager.py:244
#, python-brace-format
msgid "代理 {0} 测试超时"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\module\manager.py:252
#, python-brace-format
msgid "代理 {0} 测试失败:{1}"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\module\mapping.py:55
#, python-brace-format
msgid "{old_folder} 文件夹不存在,跳过处理"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\module\mapping.py:81
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\module\mapping.py:101
msgid "文件夹"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\module\mapping.py:85
#, python-brace-format
msgid "文件夹 {old_folder} 已重命名为 {new_folder}"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\module\mapping.py:105
#, python-brace-format
msgid "文件夹 {old_} 重命名为 {new_}"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\module\mapping.py:171
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\module\mapping.py:185
msgid "文件"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\module\mapping.py:175
#, python-brace-format
msgid "文件 {old_file} 重命名为 {new_file}"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\module\mapping.py:193
#, python-brace-format
msgid "{type} {old}被占用,重命名失败: {error}"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\module\mapping.py:202
#, python-brace-format
msgid "{type} {new}名称重复,重命名失败: {error}"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\module\mapping.py:211
#, python-brace-format
msgid "处理{type} {old}时发生预期之外的错误: {error}"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\module\tools.py:32
msgid ""
"如需重新尝试处理该对象,请关闭所有正在访问该对象的窗口或程序,然后直接按下回"
"车键!\n"
"如需跳过处理该对象,请输入任意字符后按下回车键!"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\about.py:20
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\index.py:29
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\monitor.py:20
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:15
msgid "退出程序"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\about.py:21
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\index.py:30
msgid "检查更新"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\about.py:22
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\record.py:35
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:16
msgid "返回首页"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\about.py:35
msgid "如果 XHS-Downloader 对您有帮助,请考虑为它点个 Star,感谢您的支持!"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\about.py:42
msgid "Discord 社区"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\about.py:46
msgid "邀请链接:"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\about.py:48
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\about.py:61
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\about.py:70
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\index.py:56
msgid "点击访问"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\about.py:51
msgid "作者的其他开源项目"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\index.py:31
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:231
msgid "程序设置"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\index.py:32
msgid "下载记录"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\index.py:33
msgid "开启监听"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\index.py:34
msgid "关于项目"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\index.py:49
msgid "开源协议: "
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\index.py:52
msgid "项目地址: "
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\index.py:59
msgid "请输入小红书图文/视频作品链接"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\index.py:62
msgid "多个链接之间使用空格分隔"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\index.py:64
msgid "下载作品文件"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\index.py:65
msgid "读取剪贴板"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\index.py:66
msgid "清空输入框"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\index.py:82
msgid "免责声明\n"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\index.py:93
msgid "未输入任何小红书作品链接"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\index.py:122
msgid "下载小红书作品文件失败"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\loading.py:19
msgid "程序处理中..."
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\monitor.py:21
msgid "关闭监听"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\monitor.py:33
msgid "已启动监听剪贴板模式"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\monitor.py:35
msgid "退出监听剪贴板模式"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\record.py:23
msgid "请输入待删除的小红书作品链接或作品 ID"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\record.py:26
msgid ""
"支持输入作品 ID 或包含作品 ID 的作品链接,多个链接或 ID 之间使用空格分隔"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\record.py:32
msgid "删除指定作品 ID"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\record.py:46
msgid "删除下载记录成功"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:30
msgid "作品数据 / 文件保存根路径"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:35
msgid "程序根路径"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:64
msgid "内置 Chrome User Agent"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:69
msgid "小红书网页版 Cookie"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:83
msgid "不使用代理"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:120
msgid "记录作品详细数据"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:125
msgid "作品归档保存模式"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:130
msgid "视频作品下载开关"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:135
msgid "图文作品下载开关"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:144
msgid "动图文件下载开关"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:154
msgid "作者归档保存模式"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:159
msgid "更新文件修改时间"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:168
msgid "脚本服务器开关"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:176
msgid "图片下载格式"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:180
msgid "程序语言"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:184
msgid "视频下载偏好"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:213
msgid "保存配置"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:217
msgid "放弃更改"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:227
msgid "小红书网页版 Cookie,无需登录,参数已设置"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:228
msgid "小红书网页版 Cookie,无需登录,参数未设置"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\update.py:26
msgid "正在检查新版本,请稍等..."
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\update.py:45
#, python-brace-format
msgid "检测到新版本:{0}.{1}"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\update.py:53
msgid "当前版本为开发版, 可更新至正式版"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\update.py:58
msgid "当前已是最新开发版"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\update.py:63
msgid "当前已是最新正式版"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\update.py:70
msgid "检测新版本失败"
msgstr ""
================================================
FILE: locale/zh_CN/LC_MESSAGES/xhs.po
================================================
# Chinese translations for XHS-Downloader package
# Copyright (C) 2024 THE XHS-Downloader'S COPYRIGHT HOLDER
# This file is distributed under the same license as the XHS-Downloader package.
# FIRST AUTHOR , 2024.
#
msgid ""
msgstr ""
"Project-Id-Version: XHS-Downloader 2.7\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-02-06 16:45+0800\n"
"PO-Revision-Date: 2024-12-22 14:14+0800\n"
"Last-Translator: \n"
"Language-Team: Chinese (simplified)\n"
"Language: zh_CN\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:223
#, python-brace-format
msgid "作品 {0} 存在下载记录,跳过下载"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:247
msgid "提取作品文件下载地址失败"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:280
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:328
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:743
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:930
msgid "提取小红书作品链接失败"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:288
#, python-brace-format
msgid "共 {0} 个小红书作品待处理..."
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:309
#, python-brace-format
msgid "共处理 {0} 个作品,成功 {1} 个,失败 {2} 个,跳过 {3} 个"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:400
#, python-brace-format
msgid "作品 {0} 存在下载记录,跳过处理"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:404
#, python-brace-format
msgid "开始处理作品:{0}"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:412
#, python-brace-format
msgid "{0} 获取数据失败"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:425
#, python-brace-format
msgid "{0} 提取数据失败"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:439
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\download.py:82
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\explore.py:81
msgid "视频"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:442
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\download.py:89
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\explore.py:82
msgid "图文"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:443
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\download.py:90
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\explore.py:81
msgid "图集"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:447
#, python-brace-format
msgid "未知的作品类型:{0}"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:505
#, python-brace-format
msgid "作品处理完成:{0}"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:611
msgid ""
"程序会自动读取并提取剪贴板中的小红书作品链接,并自动下载链接对应的作品文件,"
"如需关闭,请点击关闭按钮,或者向剪贴板写入 “close” 文本!"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:712
msgid "跳转至项目 GitHub 仓库"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:713
msgid "重定向至项目 GitHub 仓库主页"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:721
msgid "获取作品数据及下载地址"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:753
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:937
msgid "获取小红书作品数据成功"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:755
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:939
msgid "获取小红书作品数据失败"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:825
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:870
msgid "小红书作品链接"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:873
msgid "指定需要下载的图文作品序号"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:877
msgid "是否需要返回作品信息数据"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:891
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:896
msgid "作品文件下载任务执行完毕"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:901
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:906
msgid "作品文件下载任务未执行"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\download.py:131
msgid "视频作品下载功能已关闭,跳过下载"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\download.py:150
msgid "图文作品下载功能已关闭,跳过下载"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\download.py:182
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\download.py:192
#, python-brace-format
msgid "{0} 文件已存在,跳过下载"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\download.py:221
#, python-brace-format
msgid "文件 {0} 缓存异常,重新下载"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\download.py:248
#, python-brace-format
msgid "文件 {0} 下载成功"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\download.py:254
#, python-brace-format
msgid "网络异常,{0} 下载失败,错误信息: {1}"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\download.py:332
#, python-brace-format
msgid "文件 {0} 格式判断失败,错误信息:{1}"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\explore.py:53
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\explore.py:58
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\explore.py:79
msgid "未知"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\request.py:66
#, python-brace-format
msgid "网络异常,{0} 请求失败: {1}"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:129
msgid "小红书作品链接,多个链接使用空格分隔"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:136
msgid ""
"下载指定序号的图片文件,仅对图文/图集作品生效;多个序号输入示例:\"1 3 5 7\""
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:141
msgid "作品数据/文件保存根路径"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:142
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:40
msgid "作品文件储存文件夹名称"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:143
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:49
msgid "作品文件名称格式"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:145
msgid "小红书网页版 Cookie,无需登录"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:146
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:78
msgid "网络代理"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:147
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:88
msgid "请求数据超时限制,单位:秒"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:153
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:98
msgid "下载文件时,每次从服务器获取的数据块大小,单位:字节"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:156
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:108
msgid "请求数据失败时,重试的最大次数"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:157
msgid "是否记录作品数据至文件"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:162
msgid "图文作品文件下载格式,支持:PNG、WEBP、JPEG、HEIC、AUTO"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:164
msgid "动态图片下载开关"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:165
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:149
msgid "作品下载记录开关"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:170
msgid "是否将每个作品的文件储存至单独的文件夹"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:176
msgid "是否将每个作者的作品储存至单独的文件夹"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:183
msgid "是否将作品文件的修改时间属性修改为作品的发布时间"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:187
msgid "设置程序语言,目前支持:zh_CN、en_US"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:188
msgid "读取指定配置文件"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:190
#, python-brace-format
msgid "从指定的浏览器读取小红书网页版 Cookie,支持:{0}; 输入浏览器名称或序号"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:203
msgid "是否更新配置文件"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:209
msgid "查看详细参数说明"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:210
msgid "查看 XHS-Downloader 版本"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\expansion\browser.py:53
#, python-brace-format
msgid ""
"读取指定浏览器的 Cookie 并写入配置文件\n"
"Windows 系统需要以管理员身份运行程序才能读取 Chromium、Chrome、Edge 浏览器 "
"Cookie!\n"
"{options}\n"
"请输入浏览器名称或序号:"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\expansion\browser.py:63
msgid "未选择浏览器!"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\expansion\browser.py:75
msgid "浏览器名称或序号输入错误!"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\expansion\browser.py:81
msgid "获取 Cookie 失败,未找到 Cookie 数据!"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\expansion\browser.py:119
msgid "从浏览器读取 Cookie 功能不支持当前平台!"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\expansion\cleaner.py:45
msgid "不受支持的操作系统类型,可能无法正常去除非法字符!"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\module\manager.py:240
#, python-brace-format
msgid "代理 {0} 测试成功"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\module\manager.py:244
#, python-brace-format
msgid "代理 {0} 测试超时"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\module\manager.py:252
#, python-brace-format
msgid "代理 {0} 测试失败:{1}"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\module\mapping.py:55
#, python-brace-format
msgid "{old_folder} 文件夹不存在,跳过处理"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\module\mapping.py:81
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\module\mapping.py:101
msgid "文件夹"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\module\mapping.py:85
#, python-brace-format
msgid "文件夹 {old_folder} 已重命名为 {new_folder}"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\module\mapping.py:105
#, python-brace-format
msgid "文件夹 {old_} 重命名为 {new_}"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\module\mapping.py:171
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\module\mapping.py:185
msgid "文件"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\module\mapping.py:175
#, python-brace-format
msgid "文件 {old_file} 重命名为 {new_file}"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\module\mapping.py:193
#, python-brace-format
msgid "{type} {old}被占用,重命名失败: {error}"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\module\mapping.py:202
#, python-brace-format
msgid "{type} {new}名称重复,重命名失败: {error}"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\module\mapping.py:211
#, python-brace-format
msgid "处理{type} {old}时发生预期之外的错误: {error}"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\module\tools.py:32
msgid ""
"如需重新尝试处理该对象,请关闭所有正在访问该对象的窗口或程序,然后直接按下回"
"车键!\n"
"如需跳过处理该对象,请输入任意字符后按下回车键!"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\about.py:20
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\index.py:29
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\monitor.py:20
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:15
msgid "退出程序"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\about.py:21
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\index.py:30
msgid "检查更新"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\about.py:22
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\record.py:35
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:16
msgid "返回首页"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\about.py:35
msgid "如果 XHS-Downloader 对您有帮助,请考虑为它点个 Star,感谢您的支持!"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\about.py:42
msgid "Discord 社区"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\about.py:46
msgid "邀请链接:"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\about.py:48
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\about.py:61
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\about.py:70
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\index.py:56
msgid "点击访问"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\about.py:51
msgid "作者的其他开源项目"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\index.py:31
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:231
msgid "程序设置"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\index.py:32
msgid "下载记录"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\index.py:33
msgid "开启监听"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\index.py:34
msgid "关于项目"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\index.py:49
msgid "开源协议: "
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\index.py:52
msgid "项目地址: "
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\index.py:59
msgid "请输入小红书图文/视频作品链接"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\index.py:62
msgid "多个链接之间使用空格分隔"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\index.py:64
msgid "下载作品文件"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\index.py:65
msgid "读取剪贴板"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\index.py:66
msgid "清空输入框"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\index.py:82
msgid "免责声明\n"
msgstr ""
"关于 XHS-Downloader 的 免责声明:\n"
"\n"
"1.使用者对本项目的使用由使用者自行决定,并自行承担风险。作者对使用者使用本项"
"目所产生的任何损失、责任、或风险概不负责。\n"
"2.本项目的作者提供的代码和功能是基于现有知识和技术的开发成果。作者按现有技术"
"水平努力确保代码的正确性和安全性,但不保证代码完全没有错误或缺陷。\n"
"3.本项目依赖的所有第三方库、插件或服务各自遵循其原始开源或商业许可,使用者需"
"自行查阅并遵守相应协议,作者不对第三方组件的稳定性、安全性及合规性承担任何责"
"任。\n"
"4.使用者在使用本项目时必须严格遵守 GNU General Public License v3.0 的要求,并"
"在适当的地方注明使用了 GNU General Public License v3.0 的代码。\n"
"5.使用者在使用本项目的代码和功能时,必须自行研究相关法律法规,并确保其使用行"
"为合法合规。任何因违反法律法规而导致的法律责任和风险,均由使用者自行承担。\n"
"6.使用者不得使用本工具从事任何侵犯知识产权的行为,包括但不限于未经授权下载、"
"传播受版权保护的内容,开发者不参与、不支持、不认可任何非法内容的获取或分"
"发。\n"
"7.本项目不对使用者涉及的数据收集、存储、传输等处理活动的合规性承担责任。使用"
"者应自行遵守相关法律法规,确保处理行为合法正当;因违规操作导致的法律责任由使"
"用者自行承担。\n"
"8.使用者在任何情况下均不得将本项目的作者、贡献者或其他相关方与使用者的使用行"
"为联系起来,或要求其对使用者使用本项目所产生的任何损失或损害负责。\n"
"9.本项目的作者不会提供 XHS-Downloader 项目的付费版本,也不会提供与 XHS-"
"Downloader 项目相关的任何商业服务。\n"
"10.基于本项目进行的任何二次开发、修改或编译的程序与原创作者无关,原创作者不承"
"担与二次开发行为或其结果相关的任何责任,使用者应自行对因二次开发可能带来的各"
"种情况负全部责任。\n"
"11.本项目不授予使用者任何专利许可;若使用本项目导致专利纠纷或侵权,使用者自行"
"承担全部风险和责任。未经作者或权利人书面授权,不得使用本项目进行任何商业宣"
"传、推广或再授权。\n"
"12.作者保留随时终止向任何违反本声明的使用者提供服务的权利,并可能要求其销毁已"
"获取的代码及衍生作品。\n"
"13.作者保留在不另行通知的情况下更新本声明的权利,使用者持续使用即视为接受修订"
"后的条款。\n"
"\n"
"在使用本项目的代码和功能之前,请您认真考虑并接受以上免责声明。如果您对上述声"
"明有任何疑问或不同意,请不要使用本项目的代码和功能。如果您使用了本项目的代码"
"和功能,则视为您已完全理解并接受上述免责声明,并自愿承担使用本项目的一切风险"
"和后果。\n"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\index.py:93
msgid "未输入任何小红书作品链接"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\index.py:122
msgid "下载小红书作品文件失败"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\loading.py:19
msgid "程序处理中..."
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\monitor.py:21
msgid "关闭监听"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\monitor.py:33
msgid "已启动监听剪贴板模式"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\monitor.py:35
msgid "退出监听剪贴板模式"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\record.py:23
msgid "请输入待删除的小红书作品链接或作品 ID"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\record.py:26
msgid ""
"支持输入作品 ID 或包含作品 ID 的作品链接,多个链接或 ID 之间使用空格分隔"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\record.py:32
msgid "删除指定作品 ID"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\record.py:46
msgid "删除下载记录成功"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:30
msgid "作品数据 / 文件保存根路径"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:35
msgid "程序根路径"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:64
msgid "内置 Chrome User Agent"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:69
msgid "小红书网页版 Cookie"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:83
msgid "不使用代理"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:120
msgid "记录作品详细数据"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:125
msgid "作品归档保存模式"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:130
msgid "视频作品下载开关"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:135
msgid "图文作品下载开关"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:144
msgid "动图文件下载开关"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:154
msgid "作者归档保存模式"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:159
msgid "更新文件修改时间"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:168
msgid "脚本服务器开关"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:176
msgid "图片下载格式"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:180
msgid "程序语言"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:184
msgid "视频下载偏好"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:213
msgid "保存配置"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:217
msgid "放弃更改"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:227
msgid "小红书网页版 Cookie,无需登录,参数已设置"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:228
msgid "小红书网页版 Cookie,无需登录,参数未设置"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\update.py:26
msgid "正在检查新版本,请稍等..."
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\update.py:45
#, python-brace-format
msgid "检测到新版本:{0}.{1}"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\update.py:53
msgid "当前版本为开发版, 可更新至正式版"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\update.py:58
msgid "当前已是最新开发版"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\update.py:63
msgid "当前已是最新正式版"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\update.py:70
msgid "检测新版本失败"
msgstr ""
================================================
FILE: main.py
================================================
from asyncio import run
from asyncio.exceptions import CancelledError
from contextlib import suppress
from sys import argv
from source import Settings
from source import XHS
from source import XHSDownloader
from source import cli
async def app():
async with XHSDownloader() as xhs:
await xhs.run_async()
async def api_server(
host="0.0.0.0",
port=5556,
log_level="info",
):
async with XHS(**Settings().run()) as xhs:
await xhs.run_api_server(
host,
port,
log_level,
)
async def mcp_server(
transport="streamable-http",
host="0.0.0.0",
port=5556,
log_level="INFO",
):
async with XHS(**Settings().run()) as xhs:
await xhs.run_mcp_server(
transport=transport,
host=host,
port=port,
log_level=log_level,
)
if __name__ == "__main__":
with suppress(
KeyboardInterrupt,
CancelledError,
):
# TODO: 重构优化
if len(argv) == 1:
run(app())
elif argv[1].upper() == "API":
run(api_server())
elif argv[1].upper() == "MCP":
run(mcp_server())
# run(mcp_server("stdio"))
else:
cli()
================================================
FILE: pyproject.toml
================================================
[project]
name = "XHS-Downloader"
version = "2.8"
description = "小红书(XiaoHongShu、RedNote)链接提取/作品采集工具:提取账号发布、收藏、点赞、专辑作品链接;提取搜索结果作品、用户链接;采集小红书作品信息;提取小红书作品下载地址;下载小红书无水印作品文件"
authors = [
{ name = "JoeanAmier", email = "yonglelolu@foxmail.com" },
]
readme = "README.md"
license = "GPL-3.0"
requires-python = ">=3.12"
dependencies = [
"aiofiles>=25.1.0",
"aiosqlite>=0.22.1",
"click>=8.3.1",
"emoji>=2.15.0",
"fastapi>=0.128.5",
"fastmcp>=2.14.5",
"httpx[http2,socks]>=0.28.1",
"lxml>=6.0.2",
"pyperclip>=1.11.0",
"pyyaml>=6.0.3",
"textual>=7.5.0",
"uvicorn>=0.40.0",
"websockets>=16.0",
]
[project.urls]
Repository = "https://github.com/JoeanAmier/XHS-Downloader"
[tool.uv.pip]
index-url = "https://mirrors.ustc.edu.cn/pypi/simple"
[tool.ruff]
# Exclude a variety of commonly ignored directories.
exclude = [
".bzr",
".direnv",
".eggs",
".git",
".git-rewrite",
".hg",
".ipynb_checkpoints",
".mypy_cache",
".nox",
".pants.d",
".pyenv",
".pytest_cache",
".pytype",
".ruff_cache",
".svn",
".tox",
".venv",
".vscode",
"__pypackages__",
"_build",
"buck-out",
"build",
"dist",
"node_modules",
"site-packages",
"venv",
]
# Same as Black.
line-length = 88
indent-width = 4
# Assume Python 3.12
target-version = "py312"
[tool.ruff.lint]
# Enable Pyflakes (`F`) and a subset of the pycodestyle (`E`) codes by default.
# Unlike Flake8, Ruff doesn't enable pycodestyle warnings (`W`) or
# McCabe complexity (`C901`) by default.
select = ["E4", "E7", "E9", "F"]
ignore = []
# Allow fix for all enabled rules (when `--fix`) is provided.
fixable = ["ALL"]
unfixable = []
# Allow unused variables when underscore-prefixed.
dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
[tool.ruff.format]
# Like Black, use double quotes for strings.
quote-style = "double"
# Like Black, indent with spaces, rather than tabs.
indent-style = "space"
# Like Black, respect magic trailing commas.
skip-magic-trailing-comma = false
# Like Black, automatically detect the appropriate line ending.
line-ending = "auto"
# Enable auto-formatting of code examples in docstrings. Markdown,
# reStructuredText code/literal blocks and doctests are all supported.
#
# This is currently disabled by default, but it is planned for this
# to be opt-out in the future.
docstring-code-format = false
# Set the line length limit used when formatting code snippets in
# docstrings.
#
# This only has an effect when the `docstring-code-format` setting is
# enabled.
docstring-code-line-length = "dynamic"
[dependency-groups]
dev = [
"nuitka>=4.0",
"pycryptodome>=3.23.0",
"pyinstaller>=6.17.0",
"textual-dev>=1.7.0",
]
================================================
FILE: requirements.txt
================================================
# This file was autogenerated by uv via the following command:
# uv pip compile pyproject.toml --no-deps --no-strip-extras -o requirements.txt
aiofiles==25.1.0
# via xhs-downloader (pyproject.toml)
aiosqlite==0.22.1
# via xhs-downloader (pyproject.toml)
click==8.3.1
# via xhs-downloader (pyproject.toml)
emoji==2.15.0
# via xhs-downloader (pyproject.toml)
fastapi==0.135.3
# via xhs-downloader (pyproject.toml)
fastmcp>=3.1.0
# via xhs-downloader (pyproject.toml)
httpx[http2,socks]==0.28.1
# via xhs-downloader (pyproject.toml)
lxml==6.0.2
# via xhs-downloader (pyproject.toml)
pyperclip==1.11.0
# via xhs-downloader (pyproject.toml)
pyyaml==6.0.3
# via xhs-downloader (pyproject.toml)
textual==8.2.0
# via xhs-downloader (pyproject.toml)
uvicorn==0.42.0
# via xhs-downloader (pyproject.toml)
websockets==16.0
# via xhs-downloader (pyproject.toml)
================================================
FILE: source/CLI/__init__.py
================================================
from .main import cli
__all__ = ["cli"]
================================================
FILE: source/CLI/main.py
================================================
from asyncio import run
from contextlib import suppress
from pathlib import Path as Root
from textwrap import fill
from click import Context
from click import (
command,
option,
Path,
Choice,
pass_context,
echo,
)
from rich import print
from rich.panel import Panel
from rich.table import Table
from source.application import XHS
# from source.expansion import BrowserCookie
from source.module import (
ROOT,
PROJECT,
)
from source.module import Settings
from source.translation import switch_language, _
__all__ = ["cli"]
def check_value(function):
def inner(ctx: Context, param, value):
return function(ctx, param, value) if value else None
return inner
class CLI:
def __init__(self, ctx: Context, **kwargs):
self.ctx = ctx
self.url = ctx.params.pop("url")
self.index = self.__format_index(ctx.params.pop("index"))
self.path = ctx.params.pop("settings")
self.update = ctx.params.pop("update_settings")
self.settings = Settings(self.__check_settings_path())
self.parameter = (
self.settings.run()
| self.__clean_params(ctx.params)
| {"script_server": False}
)
self.APP = XHS(**self.parameter)
async def __aenter__(self):
await self.APP.__aenter__()
return self
async def __aexit__(self, exc_type, exc_value, traceback):
await self.APP.__aexit__(exc_type, exc_value, traceback)
async def run(self):
if self.url:
await self.APP.extract_cli(self.url, index=self.index)
self.__update_settings()
def __update_settings(self):
if self.update:
self.settings.update(self.parameter)
def __check_settings_path(self) -> Path:
if not self.path:
return ROOT
return s.parent if (s := Root(self.path)).is_file() else ROOT
@staticmethod
def __merge_cookie(data: dict) -> None:
if not data["cookie"] and (bc := data["browser_cookie"]):
data["cookie"] = bc
data.pop("browser_cookie")
def __clean_params(self, data: dict) -> dict:
# self.__merge_cookie(data)
return {k: v for k, v in data.items() if v != None}
@staticmethod
def __format_index(index: str) -> list:
if index:
result = []
values = index.split()
for i in values:
with suppress(ValueError):
result.append(int(i))
return result
return []
@staticmethod
@check_value
def version(ctx: Context, param, value) -> None:
echo(PROJECT)
ctx.exit()
# @staticmethod
# @check_value
# def read_cookie(ctx: Context, param, value) -> str:
# return BrowserCookie.get(
# value,
# domains=[
# "xiaohongshu.com",
# ],
# )
@staticmethod
@check_value
def help_(ctx: Context, param, value) -> None:
table = Table(highlight=True, box=None, show_header=True)
# 添加表格的列名
table.add_column("parameter", no_wrap=True, style="bold")
table.add_column("abbreviation", no_wrap=True, style="bold")
table.add_column("type", no_wrap=True, style="bold")
table.add_column(
"description",
no_wrap=True,
)
options = (
("--url", "-u", "str", _("小红书作品链接,多个链接使用空格分隔")),
(
"--index",
"-i",
"str",
fill(
_(
'下载指定序号的图片文件,仅对图文/图集作品生效;多个序号输入示例:"1 3 5 7"'
),
width=55,
),
),
("--work_path", "-wp", "str", _("作品数据/文件保存根路径")),
("--folder_name", "-fn", "str", _("作品文件储存文件夹名称")),
("--name_format", "-nf", "str", _("作品文件名称格式")),
("--user_agent", "-ua", "str", "User-Agent"),
("--cookie", "-ck", "str", _("小红书网页版 Cookie,无需登录")),
("--proxy", "-p", "str", _("网络代理")),
("--timeout", "-t", "int", _("请求数据超时限制,单位:秒")),
(
"--chunk",
"-c",
"int",
fill(
_("下载文件时,每次从服务器获取的数据块大小,单位:字节"), width=55
),
),
("--max_retry", "-mr", "int", _("请求数据失败时,重试的最大次数")),
("--record_data", "-rd", "bool", _("是否记录作品数据至文件")),
(
"--image_format",
"-if",
"choice",
_("图文作品文件下载格式,支持:PNG、WEBP、JPEG、HEIC、AUTO"),
),
("--live_download", "-ld", "bool", _("动态图片下载开关")),
("--download_record", "-dr", "bool", _("作品下载记录开关")),
(
"--folder_mode",
"-fm",
"bool",
_("是否将每个作品的文件储存至单独的文件夹"),
),
(
"--author_archive",
"-aa",
"bool",
_("是否将每个作者的作品储存至单独的文件夹"),
),
(
"--write_mtime",
"-wm",
"bool",
fill(
_("是否将作品文件的修改时间属性修改为作品的发布时间"),
width=55,
),
),
("--language", "-l", "choice", _("设置程序语言,目前支持:zh_CN、en_US")),
("--settings", "-s", "str", _("读取指定配置文件")),
# (
# "--browser_cookie",
# "-bc",
# "choice",
# fill(
# _(
# "从指定的浏览器读取小红书网页版 Cookie,支持:{0}; 输入浏览器名称或序号"
# ).format(
# ", ".join(
# f"{i}: {j}"
# for i, j in enumerate(
# BrowserCookie.SUPPORT_BROWSER.keys(),
# start=1,
# )
# )
# ),
# width=55,
# ),
# ),
("--update_settings", "-us", "flag", _("是否更新配置文件")),
("--help", "-h", "flag", _("查看详细参数说明")),
("--version", "-v", "flag", _("查看 XHS-Downloader 版本")),
)
for option in options:
table.add_row(*option)
print(
Panel(
table,
border_style="bold",
title="XHS-Downloader CLI Parameters",
title_align="left",
)
)
@command(name="XHS-Downloader", help=PROJECT)
@option(
"--url",
"-u",
)
@option(
"--index",
"-i",
)
@option(
"--work_path",
"-wp",
type=Path(file_okay=False),
)
@option(
"--folder_name",
"-fn",
)
@option(
"--name_format",
"-nf",
)
@option(
"--user_agent",
"-ua",
)
@option(
"--cookie",
"-ck",
)
@option(
"--proxy",
"-p",
)
@option(
"--timeout",
"-t",
type=int,
)
@option(
"--chunk",
"-c",
type=int,
)
@option(
"--max_retry",
"-mr",
type=int,
)
@option(
"--record_data",
"-rd",
type=bool,
)
@option(
"--image_format",
"-if",
type=Choice(
["png", "PNG", "webp", "WEBP", "jpeg", "JPEG", "heic", "HEIC", "auto", "AUTO"]
),
)
@option(
"--live_download",
"-ld",
type=bool,
)
@option(
"--video_preference",
"-vp",
type=Choice(["resolution", "bitrate", "size"]),
)
@option(
"--download_record",
"-dr",
type=bool,
)
@option(
"--folder_mode",
"-fm",
type=bool,
)
@option(
"--author_archive",
"-aa",
type=bool,
)
@option(
"--write_mtime",
"-wm",
type=bool,
)
@option(
"--language",
"-l",
type=Choice(["zh_CN", "en_US"]),
)
@option(
"--settings",
"-s",
type=Path(dir_okay=False),
)
# @option(
# "--browser_cookie",
# "-bc",
# type=Choice(
# list(BrowserCookie.SUPPORT_BROWSER.keys())
# + [str(i) for i in range(1, len(BrowserCookie.SUPPORT_BROWSER) + 1)]
# ),
# callback=CLI.read_cookie,
# )
@option(
"--update_settings",
"-us",
type=bool,
is_flag=True,
)
@option(
"-h",
"--help",
is_flag=True,
)
@option(
"--version",
"-v",
is_flag=True,
is_eager=True,
expose_value=False,
callback=CLI.version,
)
@pass_context
def cli(ctx, help, language, **kwargs):
# Step 1: 切换语言
if language:
switch_language(language)
# Step 2: 如果请求了帮助信息,则显示帮助并退出
if help:
ctx.obj = kwargs # 保留当前上下文的参数
CLI.help_(ctx, None, help)
return
# Step 3: 主逻辑
async def main():
async with CLI(ctx, **kwargs) as xhs:
await xhs.run()
run(main())
if __name__ == "__main__":
from click.testing import CliRunner
runner = CliRunner()
result = runner.invoke(cli, ["-l", "en_US", "-u", ""])
================================================
FILE: source/TUI/__init__.py
================================================
from .app import XHSDownloader
__all__ = ["XHSDownloader"]
================================================
FILE: source/TUI/about.py
================================================
from rich.text import Text
from textual.app import ComposeResult
from textual.binding import Binding
from textual.screen import Screen
from textual.widgets import Footer, Header, Label, Link
from ..module import (
INFO,
MASTER,
PROJECT,
PROMPT,
)
from ..translation import _
__all__ = ["About"]
class About(Screen):
BINDINGS = [
Binding(key="Q", action="quit", description=_("退出程序")),
Binding(key="U", action="update", description=_("检查更新")),
Binding(key="B", action="back", description=_("返回首页")),
]
def __init__(
self,
):
super().__init__()
def compose(self) -> ComposeResult:
yield Header()
yield Label(
Text(
_(
"如果 XHS-Downloader 对您有帮助,请考虑为它点个 Star,感谢您的支持!"
),
style=INFO,
),
classes="prompt",
)
yield Label(
Text(_("Discord 社区"), style=PROMPT),
classes="prompt",
)
yield Link(
_("邀请链接:") + "https://discord.com/invite/ZYtmgKud9Y",
url="https://discord.com/invite/ZYtmgKud9Y",
tooltip=_("点击访问"),
)
yield Label(
Text(_("作者的其他开源项目"), style=PROMPT),
classes="prompt",
)
yield Label(
Text("DouK-Downloader (抖音 / TikTok)", style=MASTER),
classes="prompt",
)
yield Link(
"https://github.com/JoeanAmier/TikTokDownloader",
url="https://github.com/JoeanAmier/TikTokDownloader",
tooltip=_("点击访问"),
)
yield Label(
Text("KS-Downloader (快手)", style=MASTER),
classes="prompt",
)
yield Link(
"https://github.com/JoeanAmier/KS-Downloader",
url="https://github.com/JoeanAmier/KS-Downloader",
tooltip=_("点击访问"),
)
yield Footer()
def on_mount(self) -> None:
self.title = PROJECT
async def action_quit(self) -> None:
await self.app.action_quit()
async def action_back(self) -> None:
await self.app.action_back()
async def action_update(self):
await self.app.run_action("update")
================================================
FILE: source/TUI/app.py
================================================
from textual.app import App
from ..application import XHS
from ..module import (
ROOT,
Settings,
)
from .about import About
from .index import Index
from .loading import Loading
from .record import Record
from .setting import Setting
from .update import Update
__all__ = ["XHSDownloader"]
class XHSDownloader(App):
CSS_PATH = ROOT.parent.joinpath("static/XHS-Downloader.tcss")
SETTINGS = Settings(ROOT)
def __init__(self):
super().__init__()
self.parameter: dict
self.APP: XHS
self.__initialization()
async def __aenter__(self):
await self.APP.__aenter__()
return self
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.APP = XHS(
**self.parameter,
_print=False,
)
async def on_mount(self) -> None:
self.theme = "nord"
self.install_screen(
Setting(
self.parameter,
),
name="setting",
)
self.install_screen(
Index(
self.APP,
),
name="index",
)
self.install_screen(Loading(), name="loading")
self.install_screen(About(), name="about")
self.install_screen(
Record(
self.APP,
),
name="record",
)
await self.push_screen("index")
async def action_settings(self):
async def save_settings(data: dict) -> None:
self.SETTINGS.update(data)
await self.refresh_screen()
await self.push_screen("setting", save_settings)
async def refresh_screen(self):
await self.action_back()
await self.close_database()
await self.APP.close()
self.__initialization()
await self.__aenter__()
await self.APP.switch_script_server()
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,
),
name="index",
)
self.install_screen(
Setting(
self.parameter,
),
name="setting",
)
self.install_screen(Loading(), name="loading")
self.install_screen(About(), name="about")
self.install_screen(
Record(
self.APP,
),
name="record",
)
await self.push_screen("index")
def update_result(self, args: tuple[str, str]) -> None:
self.notify(
args[0],
severity=args[1],
)
async def action_update(self):
await self.push_screen(
Update(
self.APP,
),
callback=self.update_result,
)
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()
================================================
FILE: source/TUI/index.py
================================================
from pyperclip import paste
from rich.text import Text
from textual import on, work
from textual.app import ComposeResult
from textual.binding import Binding
from textual.containers import HorizontalScroll, ScrollableContainer
from textual.screen import Screen
from textual.widgets import Button, Footer, Header, Input, Label, Link, RichLog
from ..application import XHS
from ..module import (
ERROR,
GENERAL,
LICENCE,
MASTER,
PROJECT,
PROMPT,
REPOSITORY,
WARNING,
)
from ..translation import _
from .monitor import Monitor
__all__ = ["Index"]
class Index(Screen):
BINDINGS = [
Binding(key="Q", action="quit", description=_("退出程序")),
Binding(key="U", action="update", description=_("检查更新")),
Binding(key="S", action="settings", description=_("程序设置")),
Binding(key="R", action="record", description=_("下载记录")),
Binding(key="M", action="monitor", description=_("开启监听")),
Binding(key="A", action="about", description=_("关于项目")),
]
def __init__(
self,
app: XHS,
):
super().__init__()
self.xhs = app
self.url = None
self.tip = None
def compose(self) -> ComposeResult:
yield Header()
yield ScrollableContainer(
Label(Text(_("开源协议: ") + LICENCE, style=MASTER)),
Link(
Text(
_("项目地址: ") + REPOSITORY,
style=MASTER,
),
url=REPOSITORY,
tooltip=_("点击访问"),
),
Label(
Text(_("请输入小红书图文/视频作品链接"), style=PROMPT),
classes="prompt",
),
Input(placeholder=_("多个链接之间使用空格分隔")),
HorizontalScroll(
Button(_("下载作品文件"), id="deal"),
Button(_("读取剪贴板"), id="paste"),
Button(_("清空输入框"), id="reset"),
),
)
yield RichLog(
markup=True,
wrap=True,
auto_scroll=True,
)
yield Footer()
def on_mount(self) -> None:
self.title = PROJECT
self.url = self.query_one(Input)
self.tip = self.query_one(RichLog)
self.xhs.print.func = self.tip
self.tip.write(
Text(_("免责声明\n") + f"\n{'>' * 50}", style=MASTER),
scroll_end=True,
)
self.xhs.manager.print_proxy_tip()
@on(Button.Pressed, "#deal")
async def deal_button(self):
if self.url.value:
self.deal()
else:
self.tip.write(
Text(_("未输入任何小红书作品链接"), style=WARNING),
scroll_end=True,
)
self.tip.write(
Text(">" * 50, style=GENERAL),
scroll_end=True,
)
@on(Button.Pressed, "#reset")
def reset_button(self):
self.query_one(Input).value = ""
@on(Button.Pressed, "#paste")
def paste_button(self):
self.query_one(Input).value = paste()
@work(exclusive=True)
async def deal(self):
await self.app.push_screen("loading")
if any(
await self.xhs.extract(
self.url.value,
True,
data=False,
)
):
self.url.value = ""
else:
self.tip.write(
Text(_("下载小红书作品文件失败"), style=ERROR),
animate=True,
scroll_end=True,
)
self.tip.write(
Text(">" * 50, style=GENERAL),
scroll_end=True,
)
await self.app.action_back()
async def action_quit(self) -> None:
await self.app.action_quit()
async def action_update(self) -> None:
await self.app.run_action("update")
async def action_settings(self):
await self.app.run_action("settings")
async def action_monitor(self):
await self.app.push_screen(
Monitor(
self.xhs,
)
)
async def action_about(self):
await self.app.push_screen("about")
async def action_record(self):
await self.app.push_screen("record")
================================================
FILE: source/TUI/loading.py
================================================
from textual.app import ComposeResult
from textual.containers import Grid
from textual.screen import ModalScreen
from textual.widgets import Label, LoadingIndicator
from ..translation import _
__all__ = ["Loading"]
class Loading(ModalScreen):
def __init__(
self,
):
super().__init__()
def compose(self) -> ComposeResult:
yield Grid(
Label(_("程序处理中...")),
LoadingIndicator(),
classes="loading",
)
================================================
FILE: source/TUI/monitor.py
================================================
from rich.text import Text
from textual import on, work
from textual.app import ComposeResult
from textual.binding import Binding
from textual.screen import Screen
from textual.widgets import Button, Footer, Header, Label, RichLog
from ..application import XHS
from ..module import (
INFO,
PROJECT,
)
from ..translation import _
__all__ = ["Monitor"]
class Monitor(Screen):
BINDINGS = [
Binding(key="Q", action="quit", description=_("退出程序")),
Binding(key="C", action="close", description=_("关闭监听")),
]
def __init__(
self,
app: XHS,
):
super().__init__()
self.xhs = app
def compose(self) -> ComposeResult:
yield Header()
yield Label(Text(_("已启动监听剪贴板模式"), style=INFO), classes="prompt")
yield RichLog(markup=True, wrap=True)
yield Button(_("退出监听剪贴板模式"), id="close")
yield Footer()
@on(Button.Pressed, "#close")
async def close_button(self):
await self.action_close()
@work(exclusive=True)
async def run_monitor(self):
await self.xhs.monitor()
await self.action_close()
def on_mount(self) -> None:
self.title = PROJECT
self.xhs.print.func = self.query_one(RichLog)
self.run_monitor()
async def action_close(self):
self.xhs.stop_monitor()
await self.app.action_back()
async def action_quit(self) -> None:
await self.action_close()
await self.app.action_quit()
================================================
FILE: source/TUI/progress.py
================================================
from textual.app import ComposeResult
from textual.screen import Screen
__all__ = ["Progress"]
class Progress(Screen):
def compose(self) -> ComposeResult:
pass
================================================
FILE: source/TUI/record.py
================================================
from textual import on
from textual.app import ComposeResult
from textual.containers import Grid, HorizontalScroll
from textual.screen import ModalScreen
from textual.widgets import Button, Input, Label
from ..application import XHS
from ..translation import _
__all__ = ["Record"]
class Record(ModalScreen):
def __init__(
self,
app: XHS,
):
super().__init__()
self.xhs = app
def compose(self) -> ComposeResult:
yield Grid(
Label(_("请输入待删除的小红书作品链接或作品 ID"), classes="prompt"),
Input(
placeholder=_(
"支持输入作品 ID 或包含作品 ID 的作品链接,多个链接或 ID 之间使用空格分隔"
),
id="id",
),
HorizontalScroll(
Button(
_("删除指定作品 ID"),
id="enter",
),
Button(_("返回首页"), id="close"),
),
id="record",
)
async def delete(self, text: str):
text = await self.xhs.extract_links(
text,
)
text = self.xhs.extract_id(text)
await self.xhs.id_recorder.delete(text)
self.app.notify(_("删除下载记录成功"))
@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()
================================================
FILE: source/TUI/setting.py
================================================
from textual import on
from textual.app import ComposeResult
from textual.binding import Binding
from textual.containers import Container, ScrollableContainer
from textual.screen import Screen
from textual.widgets import Button, Checkbox, Footer, Header, Input, Label, Select
from ..translation import _
__all__ = ["Setting"]
class Setting(Screen):
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 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(
_("作品文件名称格式"),
classes="params",
),
Input(
self.data["name_format"],
placeholder="发布时间 作者昵称 作品标题",
valid_empty=True,
id="name_format",
),
Label(
"User-Agent",
classes="params",
),
Input(
self.data["user_agent"],
placeholder=_("内置 Chrome User Agent"),
valid_empty=True,
id="user_agent",
),
Label(
_("小红书网页版 Cookie"),
classes="params",
),
Input(
placeholder=self.__check_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",
),
Label(),
Container(
Checkbox(
_("记录作品详细数据"),
id="record_data",
value=self.data["record_data"],
),
Checkbox(
_("作品归档保存模式"),
id="folder_mode",
value=self.data["folder_mode"],
),
Checkbox(
_("视频作品下载开关"),
id="video_download",
value=self.data["video_download"],
),
Checkbox(
_("图文作品下载开关"),
id="image_download",
value=self.data["image_download"],
),
classes="horizontal-layout",
),
Label(),
Container(
Checkbox(
_("动图文件下载开关"),
id="live_download",
value=self.data["live_download"],
),
Checkbox(
_("作品下载记录开关"),
id="download_record",
value=self.data["download_record"],
),
Checkbox(
_("作者归档保存模式"),
id="author_archive",
value=self.data["author_archive"],
),
Checkbox(
_("更新文件修改时间"),
id="write_mtime",
value=self.data["write_mtime"],
),
classes="horizontal-layout",
),
Label(),
Container(
Checkbox(
_("脚本服务器开关"),
id="script_server",
value=self.data["script_server"],
),
classes="horizontal-layout",
),
Container(
Label(
_("图片下载格式"),
classes="params",
),
Label(
_("程序语言"),
classes="params",
),
Label(
_("视频下载偏好"),
classes="params",
),
classes="horizontal-layout",
),
Label(),
Container(
Select.from_values(
("AUTO", "PNG", "WEBP", "JPEG", "HEIC"),
value=self.data["image_format"].upper(),
allow_blank=False,
id="image_format",
),
Select.from_values(
["zh_CN", "en_US"],
value=self.data["language"],
allow_blank=False,
id="language",
),
Select.from_values(
["resolution", "bitrate", "size"],
value=self.data["video_preference"],
allow_blank=False,
id="video_preference",
),
classes="horizontal-layout",
),
Container(
Button(
_("保存配置"),
id="save",
),
Button(
_("放弃更改"),
id="abandon",
),
classes="settings_button",
),
)
yield Footer()
def __check_cookie(self) -> str:
if self.data["cookie"]:
return _("小红书网页版 Cookie,无需登录,参数已设置")
return _("小红书网页版 Cookie,无需登录,参数未设置")
def on_mount(self) -> None:
self.title = _("程序设置")
@on(Button.Pressed, "#save")
def save_settings(self):
self.dismiss(
{
"mapping_data": self.data.get("mapping_data", {}),
"work_path": self.query_one("#work_path").value,
"folder_name": self.query_one("#folder_name").value,
"name_format": self.query_one("#name_format").value,
"user_agent": self.query_one("#user_agent").value,
"cookie": self.query_one("#cookie").value or self.data["cookie"],
"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.lower(),
"folder_mode": self.query_one("#folder_mode").value,
"language": self.query_one("#language").value,
"image_download": self.query_one("#image_download").value,
"video_download": self.query_one("#video_download").value,
"live_download": self.query_one("#live_download").value,
"download_record": self.query_one("#download_record").value,
"author_archive": self.query_one("#author_archive").value,
"write_mtime": self.query_one("#write_mtime").value,
"script_server": self.query_one("#script_server").value,
"video_preference": self.query_one("#video_preference").value,
}
)
@on(Button.Pressed, "#abandon")
def reset(self):
self.dismiss(self.data)
async def action_quit(self) -> None:
await self.app.action_quit()
async def action_index(self):
await self.app.action_back()
================================================
FILE: source/TUI/update.py
================================================
from textual import work
from textual.app import ComposeResult
from textual.containers import Grid
from textual.screen import ModalScreen
from textual.widgets import Label, LoadingIndicator
from ..application import XHS
from ..module import (
RELEASES,
)
from ..translation import _
__all__ = ["Update"]
class Update(ModalScreen):
def __init__(
self,
app: XHS,
):
super().__init__()
self.xhs = app
def compose(self) -> ComposeResult:
yield Grid(
Label(_("正在检查新版本,请稍等...")),
LoadingIndicator(),
classes="loading",
)
@work(exclusive=True)
async def check_update(self) -> None:
try:
url = await self.xhs.html.request_url(
RELEASES,
False,
timeout=5,
)
version = url.split("/")[-1]
match self.compare_versions(
f"{XHS.VERSION_MAJOR}.{XHS.VERSION_MINOR}", version, XHS.VERSION_BETA
):
case 4:
args = (
_("检测到新版本:{0}.{1}").format(
XHS.VERSION_MAJOR,
XHS.VERSION_MINOR,
),
"warning",
)
case 3:
args = (
_("当前版本为开发版, 可更新至正式版"),
"warning",
)
case 2:
args = (
_("当前已是最新开发版"),
"warning",
)
case 1:
args = (
_("当前已是最新正式版"),
"information",
)
case _:
raise ValueError
except ValueError:
args = (
_("检测新版本失败"),
"error",
)
self.dismiss(args)
def on_mount(self) -> None:
self.check_update()
@staticmethod
def compare_versions(
current_version: str, target_version: str, is_development: bool
) -> int:
current_major, current_minor = map(int, current_version.split("."))
target_major, target_minor = map(int, target_version.split("."))
if target_major > current_major:
return 4
if target_major == current_major:
if target_minor > current_minor:
return 4
if target_minor == current_minor:
return 3 if is_development else 1
return 2
================================================
FILE: source/__init__.py
================================================
from .CLI import cli
from .TUI import XHSDownloader
from .application import XHS
from .module import Settings
__all__ = [
"XHS",
"XHSDownloader",
"cli",
"Settings",
]
================================================
FILE: source/application/__init__.py
================================================
from .app import XHS
__all__ = ["XHS"]
================================================
FILE: source/application/app.py
================================================
from asyncio import (
Event,
Queue,
QueueEmpty,
create_task,
gather,
sleep,
Future,
CancelledError,
)
from contextlib import suppress
from datetime import datetime
from re import compile
from urllib.parse import urlparse
from textwrap import dedent
from fastapi import FastAPI
from fastapi.responses import RedirectResponse
from fastmcp import FastMCP
from typing import Annotated
from pydantic import Field
from types import SimpleNamespace
from pyperclip import copy, paste
from uvicorn import Config, Server
from typing import Callable
from ..expansion import (
# BrowserCookie,
Cleaner,
Converter,
Namespace,
beautify_string,
)
from ..module import (
__VERSION__,
ERROR,
MASTER,
REPOSITORY,
ROOT,
VERSION_BETA,
VERSION_MAJOR,
VERSION_MINOR,
WARNING,
DataRecorder,
ExtractData,
ExtractParams,
IDRecorder,
Manager,
MapRecorder,
logging,
# sleep_time,
ScriptServer,
INFO,
)
from ..translation import _, switch_language
from ..module import Mapping
from .download import Download
from .explore import Explore
from .image import Image
from .request import Html
from .video import Video
from rich import print
__all__ = ["XHS"]
def data_cache(function):
async def inner(
self,
data: dict,
):
if self.manager.record_data:
download = data["下载地址"]
lives = data["动图地址"]
await function(
self,
data,
)
data["下载地址"] = download
data["动图地址"] = lives
return inner
class Print:
def __init__(
self,
func: Callable = print,
):
self.func = func
def __call__(
self,
):
return self.func
class XHS:
VERSION_MAJOR = VERSION_MAJOR
VERSION_MINOR = VERSION_MINOR
VERSION_BETA = VERSION_BETA
LINK = compile(r"(?:https?://)?www\.xiaohongshu\.com/explore/\S+")
USER = compile(r"(?:https?://)?www\.xiaohongshu\.com/user/profile/[a-z0-9]+/\S+")
SHARE = compile(r"(?:https?://)?www\.xiaohongshu\.com/discovery/item/\S+")
SHORT = compile(r"(?:https?://)?xhslink\.com/[^\s\"<>\\^`{|},。;!?、【】《》]+")
ID = compile(r"(?:explore|item)/(\S+)?\?")
ID_USER = compile(r"user/profile/[a-z0-9]+/(\S+)?\?")
__INSTANCE = None
CLEANER = Cleaner()
def __new__(cls, *args, **kwargs):
if not cls.__INSTANCE:
cls.__INSTANCE = super().__new__(cls)
return cls.__INSTANCE
def __init__(
self,
mapping_data: dict = None,
work_path="",
folder_name="Download",
name_format="发布时间 作者昵称 作品标题",
user_agent: str = None,
cookie: str = "",
proxy: str | dict = None,
timeout=10,
chunk=1024 * 1024,
max_retry=5,
record_data=False,
image_format="JPEG",
image_download=True,
video_download=True,
live_download=False,
video_preference="resolution",
folder_mode=False,
download_record=True,
author_archive=False,
write_mtime=False,
language="zh_CN",
# read_cookie: int | str = None,
script_server: bool = False,
script_host="0.0.0.0",
script_port=5558,
**kwargs,
):
switch_language(language)
self.print = Print()
self.manager = Manager(
ROOT,
work_path,
folder_name,
name_format,
chunk,
user_agent,
cookie,
# self.read_browser_cookie(read_cookie) or cookie,
proxy,
timeout,
max_retry,
record_data,
image_format,
image_download,
video_download,
live_download,
video_preference,
download_record,
folder_mode,
author_archive,
write_mtime,
script_server,
self.CLEANER,
self.print,
)
self.mapping_data = mapping_data or {}
self.map_recorder = MapRecorder(
self.manager,
)
self.mapping = Mapping(self.manager, self.map_recorder)
self.html = Html(self.manager)
self.image = Image()
self.video = Video()
self.explore = Explore()
self.convert = Converter()
self.download = Download(self.manager)
self.id_recorder = IDRecorder(self.manager)
self.data_recorder = DataRecorder(self.manager)
self.clipboard_cache: str = ""
self.queue = Queue()
self.event = Event()
self.script = None
self.init_script_server(
script_host,
script_port,
)
def __extract_image(self, container: dict, data: Namespace):
container["下载地址"], container["动图地址"] = self.image.get_image_link(
data, self.manager.image_format
)
def __extract_video(
self,
container: dict,
data: Namespace,
):
container["下载地址"] = self.video.deal_video_link(
data,
self.manager.video_preference,
)
container["动图地址"] = [
None,
]
async def __download_files(
self,
container: dict,
download: bool,
index,
count: SimpleNamespace,
):
name = self.__naming_rules(container)
if (u := container["下载地址"]) and download:
if await self.skip_download(i := container["作品ID"]):
self.logging(_("作品 {0} 存在下载记录,跳过下载").format(i))
count.skip += 1
else:
__, result = await self.download.run(
u,
container["动图地址"],
index,
container["作者ID"]
+ "_"
+ self.CLEANER.filter_name(container["作者昵称"]),
name,
container["作品类型"],
container["时间戳"],
)
if not result:
count.skip += 1
elif all(result):
count.success += 1
await self.__add_record(
i,
)
else:
count.fail += 1
elif not u:
self.logging(_("提取作品文件下载地址失败"), ERROR)
count.fail += 1
await self.save_data(container)
@data_cache
async def save_data(
self,
data: dict,
):
data["采集时间"] = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
data["下载地址"] = " ".join(data["下载地址"])
data["动图地址"] = " ".join(i or "NaN" for i in data["动图地址"])
data.pop("时间戳", None)
await self.data_recorder.add(**data)
async def __add_record(
self,
id_: str,
) -> None:
await self.id_recorder.add(id_)
async def extract(
self,
url: str,
download=False,
index: list | tuple = None,
data=True,
) -> list[dict]:
if not (
urls := await self.extract_links(
url,
)
):
self.logging(_("提取小红书作品链接失败"), WARNING)
return []
statistics = SimpleNamespace(
all=len(urls),
success=0,
fail=0,
skip=0,
)
self.logging(_("共 {0} 个小红书作品待处理...").format(statistics.all))
result = [
await self.__deal_extract(
i,
download,
index,
data,
count=statistics,
)
for i in urls
]
self.show_statistics(
statistics,
)
return result
def show_statistics(
self,
statistics: SimpleNamespace,
) -> None:
self.logging(
_("共处理 {0} 个作品,成功 {1} 个,失败 {2} 个,跳过 {3} 个").format(
statistics.all,
statistics.success,
statistics.fail,
statistics.skip,
),
)
async def extract_cli(
self,
url: str,
download=True,
index: list | tuple = None,
data=False,
) -> None:
url = await self.extract_links(
url,
)
if not url:
self.logging(_("提取小红书作品链接失败"), WARNING)
return
if index:
await self.__deal_extract(
url[0],
download,
index,
data,
)
else:
statistics = SimpleNamespace(
all=len(url),
success=0,
fail=0,
skip=0,
)
[
await self.__deal_extract(
u,
download,
index,
data,
count=statistics,
)
for u in url
]
self.show_statistics(
statistics,
)
async def extract_links(
self,
url: str,
) -> list:
urls = []
for i in url.split():
if u := self.SHORT.search(i):
i = await self.html.request_url(
u.group(),
False,
)
if u := self.SHARE.search(i):
urls.append(u.group())
elif u := self.LINK.search(i):
urls.append(u.group())
elif u := self.USER.search(i):
urls.append(u.group())
return urls
def extract_id(self, links: list[str]) -> list[str]:
ids = []
for i in links:
if j := self.ID.search(i):
ids.append(j.group(1))
elif j := self.ID_USER.search(i):
ids.append(j.group(1))
return ids
async def _get_html_data(
self,
url: str,
data: bool,
cookie: str = None,
proxy: str = None,
count=SimpleNamespace(
all=0,
success=0,
fail=0,
skip=0,
),
) -> tuple[str, Namespace | dict]:
if await self.skip_download(id_ := self.__extract_link_id(url)) and not data:
msg = _("作品 {0} 存在下载记录,跳过处理").format(id_)
self.logging(msg)
count.skip += 1
return id_, {"message": msg}
self.logging(_("开始处理作品:{0}").format(id_))
html = await self.html.request_url(
url,
cookie=cookie,
proxy=proxy,
)
namespace = self.__generate_data_object(html)
if not namespace:
self.logging(_("{0} 获取数据失败").format(id_), ERROR)
count.fail += 1
return id_, {}
return id_, namespace
def _extract_data(
self,
namespace: Namespace,
id_: str,
count,
):
data = self.explore.run(namespace)
if not data:
self.logging(_("{0} 提取数据失败").format(id_), ERROR)
count.fail += 1
return {}
return data
async def _deal_download_tasks(
self,
data: dict,
namespace: Namespace,
id_: str,
download: bool,
index: list | tuple | None,
count: SimpleNamespace,
):
if data["作品类型"] == _("视频"):
self.__extract_video(data, namespace)
elif data["作品类型"] in {
_("图文"),
_("图集"),
}:
self.__extract_image(data, namespace)
else:
self.logging(_("未知的作品类型:{0}").format(id_), WARNING)
data["下载地址"] = []
data["动图地址"] = []
await self.update_author_nickname(
data,
)
await self.__download_files(
data,
download,
index,
count,
)
# await sleep_time()
return data
async def __deal_extract(
self,
url: str,
download: bool,
index: list | tuple | None,
data: bool,
cookie: str = None,
proxy: str = None,
count=SimpleNamespace(
all=0,
success=0,
fail=0,
skip=0,
),
):
id_, namespace = await self._get_html_data(
url,
data,
cookie,
proxy,
count,
)
if not isinstance(namespace, Namespace):
return namespace
if not (
data := self._extract_data(
namespace,
id_,
count,
)
):
return data
data = await self._deal_download_tasks(
data
| {
"作品链接": url,
},
namespace,
id_,
download,
index,
count,
)
self.logging(_("作品处理完成:{0}").format(id_))
return data
async def deal_script_tasks(
self,
data: dict,
index: list | tuple | None,
count=SimpleNamespace(
all=0,
success=0,
fail=0,
skip=0,
),
):
namespace = self.json_to_namespace(data)
id_ = namespace.safe_extract("noteId", "")
if not (
data := self._extract_data(
namespace,
id_,
count,
)
):
return data
return await self._deal_download_tasks(
data,
namespace,
id_,
True,
index,
count,
)
@staticmethod
def json_to_namespace(data: dict) -> Namespace:
return Namespace(data)
async def update_author_nickname(
self,
container: dict,
):
if a := self.CLEANER.filter_name(
self.mapping_data.get(i := container["作者ID"], "")
):
container["作者昵称"] = a
else:
container["作者昵称"] = self.manager.filter_name(container["作者昵称"]) or i
await self.mapping.update_cache(
i,
container["作者昵称"],
)
@staticmethod
def __extract_link_id(url: str) -> str:
link = urlparse(url)
return link.path.split("/")[-1]
def __generate_data_object(self, html: str) -> Namespace:
data = self.convert.run(html)
return Namespace(data)
def __naming_rules(self, data: dict) -> str:
keys = self.manager.name_format.split()
values = []
for key in keys:
match key:
case "发布时间":
values.append(self.__get_name_time(data))
case "作品标题":
values.append(self.__get_name_title(data))
case _:
values.append(data[key])
return beautify_string(
self.CLEANER.filter_name(
self.manager.SEPARATE.join(values),
default=self.manager.SEPARATE.join(
(
data["作者ID"],
data["作品ID"],
)
),
),
length=128,
)
@staticmethod
def __get_name_time(data: dict) -> str:
return data["发布时间"].replace(":", ".")
def __get_name_title(self, data: dict) -> str:
return (
beautify_string(
self.manager.filter_name(data["作品标题"]),
64,
)
or data["作品ID"]
)
async def monitor(
self,
delay=1,
download=True,
data=False,
) -> None:
self.logging(
_(
"程序会自动读取并提取剪贴板中的小红书作品链接,并自动下载链接对应的作品文件,如需关闭,请点击关闭按钮,或者向剪贴板写入 “close” 文本!"
),
style=MASTER,
)
self.event.clear()
copy("")
await gather(
self.__get_link(delay),
self.__receive_link(delay, download=download, index=None, data=data),
)
async def __get_link(self, delay: int):
while not self.event.is_set():
if (t := paste()).lower() == "close":
self.stop_monitor()
elif t != self.clipboard_cache:
self.clipboard_cache = t
create_task(self.__push_link(t))
await sleep(delay)
async def __push_link(
self,
content: str,
):
await gather(
*[
self.queue.put(i)
for i in await self.extract_links(
content,
)
]
)
async def __receive_link(self, delay: int, *args, **kwargs):
while not self.event.is_set() or self.queue.qsize() > 0:
with suppress(QueueEmpty):
await self.__deal_extract(self.queue.get_nowait(), *args, **kwargs)
await sleep(delay)
def stop_monitor(self):
self.event.set()
async def skip_download(self, id_: str) -> bool:
return bool(await self.id_recorder.select(id_))
async def __aenter__(self):
await self.id_recorder.__aenter__()
await self.data_recorder.__aenter__()
await self.map_recorder.__aenter__()
return self
async def __aexit__(self, exc_type, exc_value, traceback):
await self.id_recorder.__aexit__(exc_type, exc_value, traceback)
await self.data_recorder.__aexit__(exc_type, exc_value, traceback)
await self.map_recorder.__aexit__(exc_type, exc_value, traceback)
await self.close()
async def close(self):
await self.stop_script_server()
await self.manager.close()
# @staticmethod
# def read_browser_cookie(value: str | int) -> str:
# return (
# BrowserCookie.get(
# value,
# domains=[
# "xiaohongshu.com",
# ],
# )
# if value
# else ""
# )
async def run_api_server(
self,
host="0.0.0.0",
port=5556,
log_level="info",
):
api = FastAPI(
debug=self.VERSION_BETA,
title="XHS-Downloader",
version=__VERSION__,
)
self.setup_routes(api)
config = Config(
api,
host=host,
port=port,
log_level=log_level,
)
server = Server(config)
await server.serve()
def setup_routes(
self,
server: FastAPI,
):
@server.get(
"/",
summary=_("跳转至项目 GitHub 仓库"),
description=_("重定向至项目 GitHub 仓库主页"),
tags=["API"],
)
async def index():
return RedirectResponse(url=REPOSITORY)
@server.post(
"/xhs/detail",
summary=_("获取作品数据及下载地址"),
description=_(
dedent("""
**参数**:
- **url**: 小红书作品链接,自动提取,不支持多链接;必需参数
- **download**: 是否下载作品文件;设置为 true 将会耗费更多时间;可选参数
- **index**: 下载指定序号的图片文件,仅对图文作品生效;download 参数设置为 false 时不生效;可选参数
- **cookie**: 请求数据时使用的 Cookie;可选参数
- **proxy**: 请求数据时使用的代理;可选参数
- **skip**: 是否跳过存在下载记录的作品;设置为 true 将不会返回存在下载记录的作品数据;可选参数
""")
),
tags=["API"],
response_model=ExtractData,
)
async def handle(extract: ExtractParams):
data = None
url = await self.extract_links(
extract.url,
)
if not url:
msg = _("提取小红书作品链接失败")
else:
if data := await self.__deal_extract(
url[0],
extract.download,
extract.index,
not extract.skip,
extract.cookie,
extract.proxy,
):
msg = _("获取小红书作品数据成功")
else:
msg = _("获取小红书作品数据失败")
return ExtractData(message=msg, params=extract, data=data)
async def run_mcp_server(
self,
transport="streamable-http",
host="0.0.0.0",
port=5556,
log_level="INFO",
):
mcp = FastMCP(
"XHS-Downloader",
instructions=dedent("""
本服务器提供两个 MCP 接口,分别用于获取小红书作品信息数据和下载小红书作品文件,二者互不依赖,可独立调用。
支持的作品链接格式:
- https://www.xiaohongshu.com/explore/...
- https://www.xiaohongshu.com/discovery/item/...
- https://xhslink.com/...
get_detail_data
功能:输入小红书作品链接,返回该作品的信息数据,不会下载文件。
参数:
- url(必填):小红书作品链接
返回:
- message:结果提示
- data:作品信息数据
download_detail
功能:输入小红书作品链接,下载作品文件,默认不返回作品信息数据。
参数:
- url(必填):小红书作品链接
- index(选填):根据用户指定的图片序号(如用户说“下载第1和第3张”时,index应为 [1, 3]),生成由所需图片序号组成的列表;如果用户未指定序号,则该字段为 None
- return_data(可选):是否返回作品信息数据;如需返回作品信息数据,设置此参数为 true,默认值为 false
返回:
- message:结果提示
- data:作品信息数据,不需要返回作品信息数据时固定为 None
"""),
version=__VERSION__,
)
@mcp.tool(
name="get_detail_data",
description=dedent("""
功能:输入小红书作品链接,返回该作品的信息数据,不会下载文件。
参数:
url(必填):小红书作品链接,格式如:
- https://www.xiaohongshu.com/explore/...
- https://www.xiaohongshu.com/discovery/item/...
- https://xhslink.com/...
返回:
- message:结果提示
- data:作品信息数据
"""),
tags={
"小红书",
"XiaoHongShu",
"RedNote",
},
annotations={
"title": "获取小红书作品信息数据",
"readOnlyHint": False,
"destructiveHint": False,
"idempotentHint": True,
"openWorldHint": True,
},
)
async def get_detail_data(
url: Annotated[str, Field(description=_("小红书作品链接"))],
) -> dict:
msg, data = await self.deal_detail_mcp(
url,
False,
None,
)
return {
"message": msg,
"data": data,
}
@mcp.tool(
name="download_detail",
description=dedent("""
功能:输入小红书作品链接,下载作品文件,默认不返回作品信息数据。
参数:
url(必填):小红书作品链接,格式如:
- https://www.xiaohongshu.com/explore/...
- https://www.xiaohongshu.com/discovery/item/...
- https://xhslink.com/...
index(选填):根据用户指定的图片序号(如用户说“下载第1和第3张”时,index应为 [1, 3]),生成由所需图片序号组成的列表;如果用户未指定序号,则该字段为 None
return_data(可选):是否返回作品信息数据;如需返回作品信息数据,设置此参数为 true,默认值为 false
返回:
- message:结果提示
- data:作品信息数据,不需要返回作品信息数据时固定为 None
"""),
tags={
"小红书",
"XiaoHongShu",
"RedNote",
"Download",
"下载",
},
annotations={
"title": "下载小红书作品文件,可以返回作品信息数据",
"readOnlyHint": False,
"destructiveHint": False,
"idempotentHint": True,
"openWorldHint": True,
},
)
async def download_detail(
url: Annotated[str, Field(description=_("小红书作品链接"))],
index: Annotated[
list[str | int] | None,
Field(default=None, description=_("指定需要下载的图文作品序号")),
],
return_data: Annotated[
bool,
Field(default=False, description=_("是否需要返回作品信息数据")),
],
) -> dict:
msg, data = await self.deal_detail_mcp(
url,
True,
index,
)
match (
bool(data),
return_data,
):
case (True, True):
return {
"message": msg + ", " + _("作品文件下载任务执行完毕"),
"data": data,
}
case (True, False):
return {
"message": _("作品文件下载任务执行完毕"),
"data": None,
}
case (False, True):
return {
"message": msg + ", " + _("作品文件下载任务未执行"),
"data": None,
}
case (False, False):
return {
"message": msg + ", " + _("作品文件下载任务未执行"),
"data": None,
}
case _:
raise ValueError
await mcp.run_async(
transport=transport,
host=host,
port=port,
log_level=log_level,
)
async def deal_detail_mcp(
self,
url: str,
download: bool,
index: list[str | int] | None,
):
data = None
url = await self.extract_links(
url,
)
if not url:
msg = _("提取小红书作品链接失败")
elif data := await self.__deal_extract(
url[0],
download,
index,
True,
):
msg = _("获取小红书作品数据成功")
else:
msg = _("获取小红书作品数据失败")
return msg, data
def init_script_server(
self,
host="0.0.0.0",
port=5558,
):
if self.manager.script_server:
self.run_script_server(host, port)
async def switch_script_server(
self,
host="0.0.0.0",
port=5558,
switch: bool = None,
):
if switch is None:
switch = self.manager.script_server
if switch:
self.run_script_server(
host,
port,
)
else:
await self.stop_script_server()
def run_script_server(
self,
host="0.0.0.0",
port=5558,
):
if not self.script:
self.script = create_task(self._run_script_server(host, port))
async def _run_script_server(
self,
host="0.0.0.0",
port=5558,
):
async with ScriptServer(self, host, port):
await Future()
async def stop_script_server(self):
if self.script:
self.script.cancel()
with suppress(CancelledError):
await self.script
self.script = None
async def _script_server_debug(self):
await self.switch_script_server(
switch=self.manager.script_server,
)
def logging(self, text, style=INFO):
logging(
self.print,
text,
style,
)
================================================
FILE: source/application/download.py
================================================
from asyncio import Semaphore, gather
from pathlib import Path
from typing import TYPE_CHECKING, Any
from aiofiles import open
from httpx import HTTPError
from ..expansion import CacheError
# from ..module import WARNING
from ..module import (
ERROR,
FILE_SIGNATURES,
FILE_SIGNATURES_LENGTH,
MAX_WORKERS,
logging,
# sleep_time,
)
from ..module import retry as re_download
from ..translation import _
if TYPE_CHECKING:
from httpx import AsyncClient
from ..module import Manager
__all__ = ["Download"]
class Download:
SEMAPHORE = Semaphore(MAX_WORKERS)
CONTENT_TYPE_MAP = {
"image/png": "png",
"image/jpeg": "jpeg",
"image/webp": "webp",
"video/mp4": "mp4",
"video/quicktime": "mov",
"audio/mp4": "m4a",
"audio/mpeg": "mp3",
}
def __init__(
self,
manager: "Manager",
):
self.manager = manager
self.print = manager.print
self.folder = manager.folder
self.temp = manager.temp
self.chunk = manager.chunk
self.client: "AsyncClient" = manager.download_client
self.headers = manager.blank_headers
self.retry = manager.retry
self.folder_mode = manager.folder_mode
self.video_format = "mp4"
self.live_format = "mp4"
self.image_format = manager.image_format
self.image_format_list = (
"jpeg",
"png",
"webp",
"avif",
"heic",
)
self.image_download = manager.image_download
self.video_download = manager.video_download
self.live_download = manager.live_download
self.author_archive = manager.author_archive
self.write_mtime = manager.write_mtime
async def run(
self,
urls: list,
lives: list,
index: list | tuple | None,
nickname: str,
filename: str,
type_: str,
mtime: int,
) -> tuple[Path, list[Any]]:
path = self.__generate_path(nickname, filename)
if type_ == _("视频"):
tasks = self.__ready_download_video(
urls,
path,
filename,
)
elif type_ in {
_("图文"),
_("图集"),
}:
tasks = self.__ready_download_image(
urls,
lives,
index,
path,
filename,
)
else:
raise ValueError
tasks = [
self.__download(
url,
path,
name,
format_,
mtime,
)
for url, name, format_ in tasks
]
tasks = await gather(*tasks)
return path, tasks # 未解之谜
def __generate_path(self, nickname: str, filename: str):
if self.author_archive:
folder = self.folder.joinpath(nickname)
folder.mkdir(exist_ok=True)
else:
folder = self.folder
path = self.manager.archive(folder, filename, self.folder_mode)
path.mkdir(exist_ok=True)
return path
def __ready_download_video(
self,
urls: list[str],
path: Path,
name: str,
) -> list:
if not self.video_download:
logging(self.print, _("视频作品下载功能已关闭,跳过下载"))
return []
if self.__check_exists_path(
path,
f"{name}.{self.video_format}",
):
return []
return [(urls[0], name, self.video_format)]
def __ready_download_image(
self,
urls: list[str],
lives: list[str],
index: list | tuple | None,
path: Path,
name: str,
) -> list:
tasks = []
if not self.image_download:
logging(self.print, _("图文作品下载功能已关闭,跳过下载"))
return tasks
for i, j in enumerate(zip(urls, lives), start=1):
if index and i not in index:
continue
file = f"{name}_{i}"
if not any(
self.__check_exists_path(
path,
f"{file}.{s}",
)
for s in self.image_format_list
):
tasks.append([j[0], file, self.image_format])
if (
not self.live_download
or not j[1]
or self.__check_exists_path(
path,
f"{file}.{self.live_format}",
)
):
continue
tasks.append([j[1], file, self.live_format])
return tasks
def __check_exists_glob(
self,
path: Path,
name: str,
) -> bool:
if any(path.glob(name)):
logging(self.print, _("{0} 文件已存在,跳过下载").format(name))
return True
return False
def __check_exists_path(
self,
path: Path,
name: str,
) -> bool:
if path.joinpath(name).exists():
logging(self.print, _("{0} 文件已存在,跳过下载").format(name))
return True
return False
@re_download
async def __download(
self,
url: str,
path: Path,
name: str,
format_: str,
mtime: int,
):
async with self.SEMAPHORE:
headers = self.headers.copy()
temp = self.temp.joinpath(f"{name}.{format_}")
self.__update_headers_range(
headers,
temp,
)
try:
async with self.client.stream(
"GET",
url,
headers=headers,
) as response:
# await sleep_time()
if response.status_code == 416:
raise CacheError(
_("文件 {0} 缓存异常,重新下载").format(temp.name),
)
response.raise_for_status()
# self.__create_progress(
# bar,
# int(
# response.headers.get(
# 'content-length', 0)) or None,
# )
async with open(temp, "ab") as f:
async for chunk in response.aiter_bytes(self.chunk):
await f.write(chunk)
# self.__update_progress(bar, len(chunk))
real = await self.__suffix_with_file(
temp,
path,
name,
# suffix,
format_,
)
self.manager.move(
temp,
real,
mtime,
self.write_mtime,
)
# self.__create_progress(bar, None)
logging(self.print, _("文件 {0} 下载成功").format(real.name))
return True
except HTTPError as error:
# self.__create_progress(bar, None)
logging(
self.print,
_("网络异常,{0} 下载失败,错误信息: {1}").format(
name, repr(error)
),
ERROR,
)
return False
except CacheError as error:
self.manager.delete(temp)
logging(
self.print,
str(error),
ERROR,
)
return False
@staticmethod
def __create_progress(
bar,
total: int | None,
completed=0,
):
if bar:
bar.update(total=total, completed=completed)
@staticmethod
def __update_progress(bar, advance: int):
if bar:
bar.advance(advance)
@classmethod
def __extract_type(cls, content: str) -> str:
return cls.CONTENT_TYPE_MAP.get(content, "")
async def __head_file(
self,
url: str,
headers: dict[str, str],
suffix: str,
) -> tuple[int, str]:
"""未使用"""
response = await self.client.head(
url,
headers=headers,
)
# await sleep_time()
response.raise_for_status()
suffix = self.__extract_type(response.headers.get("Content-Type")) or suffix
length = response.headers.get("Content-Length", 0)
return int(length), suffix
@staticmethod
def __get_resume_byte_position(file: Path) -> int:
return file.stat().st_size if file.is_file() else 0
def __update_headers_range(
self,
headers: dict[str, str],
file: Path,
) -> int:
headers["Range"] = f"bytes={(p := self.__get_resume_byte_position(file))}-"
return p
async def __suffix_with_file(
self,
temp: Path,
path: Path,
name: str,
default_suffix: str,
) -> Path:
try:
async with open(temp, "rb") as f:
file_start = await f.read(FILE_SIGNATURES_LENGTH)
for offset, signature, suffix in FILE_SIGNATURES:
if file_start[offset : offset + len(signature)] == signature:
return path.joinpath(f"{name}.{suffix}")
except Exception as error:
logging(
self.print,
_("文件 {0} 格式判断失败,错误信息:{1}").format(
temp.name, repr(error)
),
ERROR,
)
return path.joinpath(f"{name}.{default_suffix}")
================================================
FILE: source/application/explore.py
================================================
from datetime import datetime
from ..expansion import Namespace
from ..translation import _
__all__ = ["Explore"]
class Explore:
time_format = "%Y-%m-%d_%H:%M:%S"
def run(self, data: Namespace) -> dict:
return self.__extract_data(data)
def __extract_data(self, data: Namespace) -> dict:
result = {}
if data:
self.__extract_interact_info(result, data)
self.__extract_tags(result, data)
self.__extract_info(result, data)
self.__extract_time(result, data)
self.__extract_user(result, data)
return result
@staticmethod
def __extract_interact_info(container: dict, data: Namespace) -> None:
container["收藏数量"] = data.safe_extract("interactInfo.collectedCount", "-1")
container["评论数量"] = data.safe_extract("interactInfo.commentCount", "-1")
container["分享数量"] = data.safe_extract("interactInfo.shareCount", "-1")
container["点赞数量"] = data.safe_extract("interactInfo.likedCount", "-1")
@staticmethod
def __extract_tags(container: dict, data: Namespace):
tags = data.safe_extract("tagList", [])
container["作品标签"] = " ".join(
Namespace.object_extract(i, "name") for i in tags
)
def __extract_info(self, container: dict, data: Namespace):
container["作品ID"] = data.safe_extract("noteId")
container["作品链接"] = (
f"https://www.xiaohongshu.com/explore/{container['作品ID']}"
)
container["作品标题"] = data.safe_extract("title")
container["作品描述"] = data.safe_extract("desc")
container["作品类型"] = self.__classify_works(data)
# container["IP归属地"] = data.safe_extract("ipLocation")
def __extract_time(self, container: dict, data: Namespace):
container["发布时间"] = (
datetime.fromtimestamp(time / 1000).strftime(self.time_format)
if (time := data.safe_extract("time"))
else _("未知")
)
container["最后更新时间"] = (
datetime.fromtimestamp(last / 1000).strftime(self.time_format)
if (last := data.safe_extract("lastUpdateTime"))
else _("未知")
)
container["时间戳"] = (
(time / 1000) if (time := data.safe_extract("time")) else None
)
@staticmethod
def __extract_user(container: dict, data: Namespace):
container["作者昵称"] = data.safe_extract("user.nickname") or data.safe_extract(
"user.nickName"
)
container["作者ID"] = data.safe_extract("user.userId")
container["作者链接"] = (
f"https://www.xiaohongshu.com/user/profile/{container['作者ID']}"
)
@staticmethod
def __classify_works(data: Namespace) -> str:
type_ = data.safe_extract("type")
list_ = data.safe_extract("imageList", [])
if type_ not in {"video", "normal"} or len(list_) == 0:
return _("未知")
if type_ == "video":
return _("视频") if len(list_) == 1 else _("图集")
return _("图文")
================================================
FILE: source/application/image.py
================================================
from source.expansion import Namespace
from .request import Html
__all__ = ["Image"]
class Image:
@classmethod
def get_image_link(cls, data: Namespace, format_: str) -> tuple[list, list]:
images = data.safe_extract("imageList", [])
live_link = cls.__get_live_link(images)
if not any(
token_list := [
cls.__extract_image_token(Namespace.object_extract(i, "urlDefault"))
for i in images
]
):
token_list = [
cls.__extract_image_token(Namespace.object_extract(i, "url"))
for i in images
]
match format_:
case "png" | "webp" | "jpeg" | "heic" | "avif":
return [
Html.format_url(
cls.__generate_fixed_link(
i,
format_,
)
)
for i in token_list
], live_link
case "auto":
return [
Html.format_url(cls.__generate_auto_link(i)) for i in token_list
], live_link
case _:
raise ValueError
@staticmethod
def __generate_auto_link(token: str) -> str:
return f"https://sns-img-bd.xhscdn.com/{token}"
@staticmethod
def __generate_fixed_link(
token: str,
format_: str,
) -> str:
return f"https://ci.xiaohongshu.com/{token}?imageView2/format/{format_}"
@staticmethod
def __extract_image_token(url: str) -> str:
return "/".join(url.split("/")[5:]).split("!")[0]
@staticmethod
def __get_live_link(items: list) -> list:
return [
(
Html.format_url(
Namespace.object_extract(item, "stream.h264[0].masterUrl")
)
or None
)
for item in items
]
================================================
FILE: source/application/request.py
================================================
from typing import TYPE_CHECKING
from httpx import HTTPError
from httpx import get
from ..module import ERROR, Manager, logging, retry, sleep_time
from ..translation import _
if TYPE_CHECKING:
from ..module import Manager
__all__ = ["Html"]
class Html:
def __init__(
self,
manager: "Manager",
):
self.print = manager.print
self.retry = manager.retry
self.client = manager.request_client
self.headers = manager.blank_headers
self.timeout = manager.timeout
@retry
async def request_url(
self,
url: str,
content=True,
cookie: str = None,
proxy: str = None,
**kwargs,
) -> str:
if not url.startswith("http"):
url = f"https://{url}"
headers = self.update_cookie(
cookie,
)
try:
match bool(proxy):
case False:
response = await self.__request_url_get(
url,
headers,
**kwargs,
)
await sleep_time()
response.raise_for_status()
return response.text if content else str(response.url)
case True:
response = await self.__request_url_get_proxy(
url,
headers,
proxy,
**kwargs,
)
await sleep_time()
response.raise_for_status()
return response.text if content else str(response.url)
case _:
raise ValueError
except HTTPError as error:
logging(
self.print,
_("网络异常,{0} 请求失败: {1}").format(url, repr(error)),
ERROR,
)
return ""
@staticmethod
def format_url(url: str) -> str:
return bytes(url, "utf-8").decode("unicode_escape")
def update_cookie(
self,
cookie: str = None,
) -> dict:
return self.headers | {"Cookie": cookie} if cookie else self.headers.copy()
async def __request_url_head(
self,
url: str,
headers: dict,
**kwargs,
):
return await self.client.head(
url,
headers=headers,
**kwargs,
)
async def __request_url_head_proxy(
self,
url: str,
headers: dict,
proxy: str,
**kwargs,
):
return await self.client.head(
url,
headers=headers,
proxy=proxy,
follow_redirects=True,
verify=False,
timeout=self.timeout,
**kwargs,
)
async def __request_url_get(
self,
url: str,
headers: dict,
**kwargs,
):
return await self.client.get(
url,
headers=headers,
**kwargs,
)
async def __request_url_get_proxy(
self,
url: str,
headers: dict,
proxy: str,
**kwargs,
):
return get(
url,
headers=headers,
proxy=proxy,
follow_redirects=True,
verify=False,
timeout=self.timeout,
**kwargs,
)
================================================
FILE: source/application/user_posted.py
================================================
from xhshow import Xhshow
from typing import TYPE_CHECKING
from ..module import retry, sleep_time
from httpx import get
if TYPE_CHECKING:
from ..module import Manager
class UserPosted:
encipher = Xhshow()
def __init__(
self,
manager: "Manager",
url: str,
params: dict,
cookies: str = None,
proxy: str = None,
):
self.url = url
self.params = params
self.headers = manager.blank_headers.copy()
self.client = manager.request_client
self.cookies = self.get_cookie(cookies)
self.print = manager.print
self.retry = manager.retry
self.timeout = manager.timeout
self.proxy = proxy
def get_cookie(self, cookies: str = None) -> dict | str:
if cookies:
self.headers["cookie"] = cookies
return cookies
return dict(self.client.cookies)
def run(
self,
verify=True,
): ...
@retry
async def get_data(self):
headers = self.get_headers()
if self.proxy:
response = get(
self.url,
params=self.params,
headers=headers,
proxy=self.proxy,
follow_redirects=True,
verify=False,
timeout=self.timeout,
)
else:
response = await self.client.get(
self.url,
params=self.params,
headers=headers,
)
await sleep_time()
response.raise_for_status()
return response.json()
def get_headers(self):
headers = self.encipher.sign_headers_get(
uri=self.url,
cookies=self.cookies,
params=self.params,
)
return headers | self.headers
================================================
FILE: source/application/video.py
================================================
from source.expansion import Namespace
from .request import Html
__all__ = ["Video"]
class Video:
VIDEO_LINK = (
"video",
"consumer",
"originVideoKey",
)
@classmethod
def deal_video_link(
cls,
data: Namespace,
preference="resolution",
):
return cls.generate_video_link(data) or cls.get_video_link(data, preference)
@classmethod
def generate_video_link(cls, data: Namespace) -> list:
return (
[Html.format_url(f"https://sns-video-bd.xhscdn.com/{t}")]
if (t := data.safe_extract(".".join(cls.VIDEO_LINK)))
else []
)
@classmethod
def get_video_link(
cls,
data: Namespace,
preference="resolution",
) -> list:
if not (items := cls.get_video_items(data)):
return []
match preference:
case "resolution":
items.sort(key=lambda x: x.height)
case "bitrate":
items.sort(key=lambda x: x.videoBitrate)
case "size":
items.sort(key=lambda x: x.size)
case _:
raise ValueError(f"Invalid video preference value: {preference}")
return [b[0]] if (b := items[-1].backupUrls) else [items[-1].masterUrl]
@staticmethod
def get_video_items(data: Namespace) -> list:
h264 = data.safe_extract("video.media.stream.h264", [])
h265 = data.safe_extract("video.media.stream.h265", [])
return [*h264, *h265]
================================================
FILE: source/expansion/__init__.py
================================================
# from .browser import BrowserCookie
from .cleaner import Cleaner
from .converter import Converter
from .error import CacheError
from .file_folder import file_switch
from .file_folder import remove_empty_directories
from .namespace import Namespace
from .truncate import beautify_string
from .truncate import trim_string
from .truncate import truncate_string
================================================
FILE: source/expansion/browser.py
================================================
from contextlib import suppress
from sys import platform
from rich.console import Console
from rookiepy import (
arc,
brave,
chrome,
chromium,
edge,
firefox,
librewolf,
opera,
opera_gx,
vivaldi,
)
try:
from source.translation import _
except ImportError:
_ = lambda s: s
__all__ = ["BrowserCookie"]
class BrowserCookie:
SUPPORT_BROWSER = {
"Arc": (arc, "Linux, macOS, Windows"),
"Chrome": (chrome, "Linux, macOS, Windows"),
"Chromium": (chromium, "Linux, macOS, Windows"),
"Opera": (opera, "Linux, macOS, Windows"),
"OperaGX": (opera_gx, "macOS, Windows"),
"Brave": (brave, "Linux, macOS, Windows"),
"Edge": (edge, "Linux, macOS, Windows"),
"Vivaldi": (vivaldi, "Linux, macOS, Windows"),
"Firefox": (firefox, "Linux, macOS, Windows"),
"LibreWolf": (librewolf, "Linux, macOS, Windows"),
}
@classmethod
def run(
cls,
domains: list[str],
console: Console = None,
) -> str | None:
console = console or Console()
options = "\n".join(
f"{i}. {k}: {v[1]}"
for i, (k, v) in enumerate(cls.SUPPORT_BROWSER.items(), start=1)
)
if browser := console.input(
_(
"读取指定浏览器的 Cookie 并写入配置文件\n"
"Windows 系统需要以管理员身份运行程序才能读取 Chromium、Chrome、Edge 浏览器 Cookie!\n"
"{options}\n请输入浏览器名称或序号:"
).format(options=options),
):
return cls.get(
browser,
domains,
console,
)
console.print(_("未选择浏览器!"))
return None
@classmethod
def get(
cls,
browser: str | int,
domains: list[str],
console: Console = None,
) -> str:
console = console or Console()
if not (browser := cls.__browser_object(browser)):
console.print(_("浏览器名称或序号输入错误!"))
return ""
try:
cookies = browser(domains=domains)
return "; ".join(f"{i['name']}={i['value']}" for i in cookies)
except RuntimeError:
console.print(_("获取 Cookie 失败,未找到 Cookie 数据!"))
return ""
@classmethod
def __browser_object(cls, browser: str | int):
with suppress(ValueError):
browser = int(browser) - 1
if isinstance(browser, int):
try:
return list(cls.SUPPORT_BROWSER.values())[browser][0]
except IndexError:
return None
if isinstance(browser, str):
try:
return cls.__match_browser(browser)
except KeyError:
return None
raise TypeError
@classmethod
def __match_browser(cls, browser: str):
for i, j in cls.SUPPORT_BROWSER.items():
if i.lower() == browser.lower():
return j[0]
match platform:
case "darwin":
from rookiepy import safari
BrowserCookie.SUPPORT_BROWSER |= {
"Safari": (safari, "macOS"),
}
case "linux":
BrowserCookie.SUPPORT_BROWSER.pop("OperaGX")
case "win32":
pass
case _:
print(_("从浏览器读取 Cookie 功能不支持当前平台!"))
================================================
FILE: source/expansion/cleaner.py
================================================
from platform import system
from re import compile
from string import whitespace
from warnings import warn
from emoji import replace_emoji
try:
from source.translation import _
except ImportError:
_ = lambda s: s
class Cleaner:
CONTROL_CHARACTERS = compile(r"[\x00-\x1F\x7F]")
def __init__(self):
"""
替换字符串中包含的非法字符,默认根据系统类型生成对应的非法字符字典,也可以自行设置非法字符字典
"""
self.rule = self.default_rule() # 默认非法字符字典
@staticmethod
def default_rule():
"""根据系统类型生成默认非法字符字典"""
if (s := system()) in ("Windows", "Darwin"):
rule = {
"/": "",
"\\": "",
"|": "",
"<": "",
">": "",
'"': "",
"?": "",
":": "",
"*": "",
"\x00": "",
} # Windows 系统和 Mac 系统
elif s == "Linux":
rule = {
"/": "",
"\x00": "",
} # Linux 系统
else:
warn(_("不受支持的操作系统类型,可能无法正常去除非法字符!"))
rule = {}
cache = {i: "" for i in whitespace[1:]} # 补充换行符等非法字符
return rule | cache
def set_rule(self, rule: dict[str, str], update=True):
"""
设置非法字符字典
:param rule: 替换规则,字典格式,键为非法字符,值为替换后的内容
:param update: 如果是 True,则与原有规则字典合并,否则替换原有规则字典
"""
self.rule = {**self.rule, **rule} if update else rule
def filter(self, text: str) -> str:
"""
去除非法字符
:param text: 待处理的字符串
:return: 替换后的字符串,如果替换后字符串为空,则返回 None
"""
for i in self.rule:
text = text.replace(i, self.rule[i])
return text
def filter_name(
self,
text: str,
replace: str = "",
default: str = "",
) -> str:
"""过滤文件夹名称中的非法字符"""
text = text.replace(":", ".")
text = self.remove_control_characters(text)
text = self.filter(text)
text = replace_emoji(
text,
replace,
)
text = self.clear_spaces(text)
text = text.strip().strip(".").strip("_")
return text or default
@staticmethod
def clear_spaces(string: str):
"""将连续的空格转换为单个空格"""
return " ".join(string.split())
@classmethod
def remove_control_characters(
cls,
text,
replace="",
):
# 使用正则表达式匹配所有控制字符
return cls.CONTROL_CHARACTERS.sub(
replace,
text,
)
if __name__ == "__main__":
demo = Cleaner()
print(demo.rule)
print(demo.filter_name(""))
print(demo.remove_control_characters("hello \x08world"))
================================================
FILE: source/expansion/converter.py
================================================
from typing import Union
from re import compile
from lxml.etree import HTML
from yaml import safe_load
__all__ = ["Converter"]
class Converter:
YAML_ILLEGAL = compile(r"[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]")
INITIAL_STATE = "//script/text()"
PC_KEYS_LINK = (
"note",
"noteDetailMap",
"[-1]",
"note",
)
PHONE_KEYS_LINK = (
"noteData",
"data",
"noteData",
)
def run(self, content: str) -> dict:
return self._filter_object(self._convert_object(self._extract_object(content)))
def _extract_object(self, html: str) -> str:
if not html:
return ""
html_tree = HTML(html)
scripts = html_tree.xpath(self.INITIAL_STATE)
return self.get_script(scripts)
@classmethod
def _convert_object(cls, text: str) -> dict:
cleaned = cls.YAML_ILLEGAL.sub("", text.lstrip("window.__INITIAL_STATE__="))
return safe_load(cleaned)
@classmethod
def _filter_object(cls, data: dict) -> dict:
return (
cls.deep_get(data, cls.PHONE_KEYS_LINK)
or cls.deep_get(data, cls.PC_KEYS_LINK)
or {}
)
@classmethod
def deep_get(cls, data: dict, keys: list | tuple, default=None):
if not data:
return default
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, TypeError):
return default
@staticmethod
def safe_get(data: Union[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
@staticmethod
def get_script(scripts: list) -> str:
scripts.reverse()
return next(
(
script
for script in scripts
if script.startswith("window.__INITIAL_STATE__")
),
"",
)
================================================
FILE: source/expansion/error.py
================================================
class CacheError(Exception):
def __init__(self, message: str):
super().__init__(message)
self.message = message
def __str__(self):
return self.message
================================================
FILE: source/expansion/file_folder.py
================================================
from contextlib import suppress
from pathlib import Path
def file_switch(path: Path) -> None:
if path.exists():
path.unlink()
else:
path.touch()
def remove_empty_directories(path: Path) -> None:
exclude = {
"\\.",
"\\_",
"\\__",
}
for dir_path, dir_names, file_names in path.walk(
top_down=False,
):
if any(i in str(dir_path) for i in exclude):
continue
if not dir_names and not file_names:
with suppress(OSError):
dir_path.rmdir()
================================================
FILE: source/expansion/namespace.py
================================================
from copy import deepcopy
from types import SimpleNamespace
from typing import Union
__all__ = ["Namespace"]
class Namespace:
def __init__(self, data: dict) -> None:
self.data: SimpleNamespace = self.generate_data_object(data)
@staticmethod
def generate_data_object(data: dict) -> SimpleNamespace:
def depth_conversion(element):
if isinstance(element, dict):
return SimpleNamespace(
**{k: depth_conversion(v) for k, v in element.items()}
)
elif isinstance(element, list):
return [depth_conversion(item) for item in element]
else:
return element
return depth_conversion(data)
def safe_extract(
self,
attribute_chain: str,
default: Union[str, int, list, dict, SimpleNamespace] = "",
):
return self.__safe_extract(self.data, attribute_chain, default)
@staticmethod
def __safe_extract(
data_object: SimpleNamespace,
attribute_chain: str,
default: Union[str, int, list, dict, SimpleNamespace] = "",
):
data = deepcopy(data_object)
attributes = attribute_chain.split(".")
for attribute in attributes:
if "[" in attribute:
parts = attribute.split("[", 1)
attribute = parts[0]
index = parts[1][:-1]
try:
index = int(index)
data = getattr(data, attribute, None)[index]
except (IndexError, TypeError, ValueError):
return default
else:
data = getattr(data, attribute, None)
if not data:
return default
return data or default
@classmethod
def object_extract(
cls,
data_object: SimpleNamespace,
attribute_chain: str,
default: Union[str, int, list, dict, SimpleNamespace] = "",
):
return cls.__safe_extract(
data_object,
attribute_chain,
default,
)
@property
def __dict__(self):
return self.convert_to_dict(self.data)
@classmethod
def convert_to_dict(cls, data) -> dict:
return {
key: cls.convert_to_dict(value)
if isinstance(value, SimpleNamespace)
else value
for key, value in vars(data).items()
}
def __bool__(self):
return bool(vars(self.data))
================================================
FILE: source/expansion/pyi_rth_beartype.py
================================================
"""
PyInstaller runtime hook for beartype compatibility.
This runtime hook runs BEFORE any user code and patches beartype's import hook
system to be compatible with PyInstaller's frozen import mechanism.
The Problem:
-----------
Beartype's `beartype.claw` module installs a custom path hook into
`sys.path_hooks` that uses `SourceFileLoader` to load and transform Python
source files. In PyInstaller's frozen environment:
1. There are no source .py files - only compiled bytecode in a PYZ archive
2. Beartype's hook is prepended to `sys.path_hooks`, taking precedence over
PyInstaller's `PyiFrozenFinder`
3. Beartype clears `sys.path_importer_cache`, invalidating PyInstaller's
cached finders
This causes `ModuleNotFoundError` for any module imported AFTER beartype's
hook is installed.
The Fix:
--------
This runtime hook monkey-patches beartype's `add_beartype_pathhook` function
to detect PyInstaller's frozen environment and skip installing the problematic
path hook.
See Also:
---------
- https://github.com/beartype/beartype/issues/599
- https://github.com/pyinstaller/pyinstaller/issues/9324
"""
import sys
def _is_pyinstaller_frozen():
"""Check if running in a PyInstaller frozen environment."""
return getattr(sys, "frozen", False) or hasattr(sys, "_MEIPASS")
def _patch_beartype_claw():
"""
Patch beartype's add_beartype_pathhook to skip in frozen environments.
This patches the function at import time before any user code can call it.
"""
# Only patch if we're actually frozen
if not _is_pyinstaller_frozen():
return
try:
# Import the module containing the function to patch
from beartype.claw._importlib import clawimpmain
# Store the original function
_original_add_beartype_pathhook = clawimpmain.add_beartype_pathhook
def _patched_add_beartype_pathhook():
"""
Patched version of add_beartype_pathhook that skips in frozen env.
"""
if _is_pyinstaller_frozen():
# In frozen environment, skip installing the path hook entirely
# This prevents breaking PyInstaller's frozen import system
return
# Otherwise, call the original
return _original_add_beartype_pathhook()
# Replace the function in clawimpmain module
clawimpmain.add_beartype_pathhook = _patched_add_beartype_pathhook
# CRITICAL: Also patch clawpkgmain which does `from ... import add_beartype_pathhook`
# and thus has its own local reference to the original function
from beartype.claw._package import clawpkgmain
clawpkgmain.add_beartype_pathhook = _patched_add_beartype_pathhook
except ImportError:
# beartype not installed or not using claw module
pass
except Exception:
# Don't crash on patch failure
pass
# Apply the patch when this runtime hook is loaded
_patch_beartype_claw()
================================================
FILE: source/expansion/truncate.py
================================================
from unicodedata import name
def is_chinese_char(char: str) -> bool:
return "CJK" in name(char, "")
def truncate_string(s: str, length: int = 64) -> str:
count = 0
result = ""
for char in s:
count += 2 if is_chinese_char(char) else 1
if count > length:
break
result += char
return result
def trim_string(s: str, length: int = 64) -> str:
length = length // 2 - 2
return f"{s[:length]}...{s[-length:]}" if len(s) > length else s
def beautify_string(s: str, length: int = 64) -> str:
count = 0
for char in s:
count += 2 if is_chinese_char(char) else 1
if count > length:
break
else:
return s
length //= 2
start = truncate_string(s, length)
end = truncate_string(s[::-1], length)[::-1]
return f"{start}...{end}"
================================================
FILE: source/module/__init__.py
================================================
from .extend import Account
from .manager import Manager
from .model import (
ExtractData,
ExtractParams,
)
from .recorder import DataRecorder
from .recorder import IDRecorder
from .recorder import MapRecorder
from .mapping import Mapping
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,
HEADERS,
PROJECT,
USERAGENT,
FILE_SIGNATURES,
FILE_SIGNATURES_LENGTH,
MAX_WORKERS,
__VERSION__,
)
from .tools import (
retry,
logging,
sleep_time,
retry_limited,
)
from .script import ScriptServer
================================================
FILE: source/module/extend.py
================================================
__all__ = ["Account"]
class Account:
pass
================================================
FILE: source/module/manager.py
================================================
from pathlib import Path
from re import compile, sub
from shutil import move, rmtree
from os import utime
from http.cookies import SimpleCookie
from httpx import (
AsyncClient,
AsyncHTTPTransport,
HTTPStatusError,
RequestError,
TimeoutException,
get,
)
from source.expansion import remove_empty_directories
from ..translation import _
from .static import HEADERS, USERAGENT, WARNING
from .tools import logging
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from ..expansion import Cleaner
__all__ = ["Manager"]
class Manager:
NAME = compile(r"[^\u4e00-\u9fffa-zA-Z0-9-_!?,。;:“”()《》]")
NAME_KEYS = (
"收藏数量",
"评论数量",
"分享数量",
"点赞数量",
"作品标签",
"作品ID",
"作品标题",
"作品描述",
"作品类型",
"发布时间",
"最后更新时间",
"作者昵称",
"作者ID",
)
NO_PROXY = {
"http://": None,
"https://": None,
}
SEPARATE = "_"
WEB_ID = r"(?:^|; )webId=[^;]+"
WEB_SESSION = r"(?:^|; )web_session=[^;]+"
def __init__(
self,
root: Path,
path: str,
folder: str,
name_format: str,
chunk: int,
user_agent: str,
cookie: str,
proxy: str | dict,
timeout: int,
retry: int,
record_data: bool,
image_format: str,
image_download: bool,
video_download: bool,
live_download: bool,
video_preference: str,
download_record: bool,
folder_mode: bool,
author_archive: bool,
write_mtime: bool,
script_server: bool,
cleaner: "Cleaner",
print_object,
):
self.print = print_object
self.root = root
self.cleaner = cleaner
self.temp = root.joinpath("Temp")
self.path = self.__check_path(path)
self.folder = self.__check_folder(folder)
self.compatible()
self.blank_headers = HEADERS | {
"user-agent": user_agent or USERAGENT,
}
self.retry = retry
self.chunk = chunk
self.name_format = self.__check_name_format(name_format)
self.record_data = self.check_bool(record_data, False)
self.image_format = self.__check_image_format(image_format)
self.folder_mode = self.check_bool(folder_mode, False)
self.download_record = self.check_bool(download_record, True)
self.proxy_tip = None
self.proxy = self.__check_proxy(proxy)
self.print_proxy_tip()
self.timeout = timeout
self.request_client = AsyncClient(
headers=self.blank_headers
| {
"referer": "https://www.xiaohongshu.com/",
},
cookies=self.cookie_str_to_dict(cookie),
timeout=timeout,
verify=False,
http2=True,
follow_redirects=True,
mounts={
"http://": AsyncHTTPTransport(proxy=self.proxy),
"https://": AsyncHTTPTransport(proxy=self.proxy),
},
)
self.download_client = AsyncClient(
headers=self.blank_headers,
timeout=timeout,
verify=False,
follow_redirects=True,
mounts={
"http://": AsyncHTTPTransport(proxy=self.proxy),
"https://": AsyncHTTPTransport(proxy=self.proxy),
},
)
self.image_download = self.check_bool(image_download, True)
self.video_download = self.check_bool(video_download, True)
self.video_preference = self.check_video_preference(video_preference)
self.live_download = self.check_bool(live_download, True)
self.author_archive = self.check_bool(author_archive, False)
self.write_mtime = self.check_bool(write_mtime, False)
self.script_server = self.check_bool(script_server, False)
self.create_folder()
def __check_path(self, path: str) -> Path:
if not path:
return self.root
if (r := Path(path)).is_dir():
return r
return r if (r := self.__check_root_again(r)) else self.root
def __check_folder(self, folder: str) -> Path:
folder = self.cleaner.filter_name(folder, default="Download")
return self.path.joinpath(folder)
@staticmethod
def __check_root_again(root: Path) -> bool | Path:
if root.parent.is_dir():
root.mkdir(exist_ok=True)
return root
return False
@staticmethod
def __check_image_format(image_format) -> str:
if (i := image_format.lower()) in {
"auto",
"png",
"webp",
"jpeg",
"heic",
"avif",
}:
return i
return "jpeg"
@staticmethod
def is_exists(path: Path) -> bool:
return path.exists()
@staticmethod
def delete(path: Path):
if path.exists():
path.unlink()
@staticmethod
def archive(root: Path, name: str, folder_mode: bool) -> Path:
return root.joinpath(name) if folder_mode else root
@classmethod
def move(
cls,
temp: Path,
path: Path,
mtime: int = None,
rewrite: bool = False,
):
move(temp.resolve(), path.resolve())
if rewrite and mtime:
cls.update_mtime(path.resolve(), mtime)
@staticmethod
def update_mtime(file: Path, mtime: int):
utime(file, (mtime, mtime))
def __clean(self):
rmtree(self.temp.resolve())
def filter_name(self, name: str) -> str:
name = self.NAME.sub("_", name)
return sub(r"_+", "_", name).strip("_")
@staticmethod
def check_bool(value: bool, default: bool) -> bool:
return value if isinstance(value, bool) else default
async def close(self):
await self.request_client.aclose()
await self.download_client.aclose()
# self.__clean()
remove_empty_directories(self.root)
remove_empty_directories(self.folder)
def __check_name_format(self, format_: str) -> str:
keys = format_.split()
return next(
("发布时间 作者昵称 作品标题" for key in keys if key not in self.NAME_KEYS),
format_,
)
@staticmethod
def check_video_preference(preference: str) -> str:
if preference in {"resolution", "bitrate", "size"}:
return preference
return "resolution"
def __check_proxy(
self,
proxy: str,
url="https://www.xiaohongshu.com/explore",
) -> str | None:
if proxy:
try:
response = get(
url,
proxy=proxy,
timeout=10,
headers={
"User-Agent": USERAGENT,
},
)
response.raise_for_status()
self.proxy_tip = (_("代理 {0} 测试成功").format(proxy),)
return proxy
except TimeoutException:
self.proxy_tip = (
_("代理 {0} 测试超时").format(proxy),
WARNING,
)
except (
RequestError,
HTTPStatusError,
) as e:
self.proxy_tip = (
_("代理 {0} 测试失败:{1}").format(
proxy,
e,
),
WARNING,
)
return None
def print_proxy_tip(
self,
) -> None:
if self.proxy_tip:
logging(self.print, *self.proxy_tip)
@classmethod
def clean_cookie(cls, cookie_string: str) -> str:
return cls.delete_cookie(
cookie_string,
(
cls.WEB_ID,
cls.WEB_SESSION,
),
)
@classmethod
def delete_cookie(cls, cookie_string: str, patterns: list | tuple) -> str:
for pattern in patterns:
# 使用空字符串替换匹配到的部分
cookie_string = sub(pattern, "", cookie_string)
# 去除多余的分号和空格
cookie_string = sub(r";\s*$", "", cookie_string) # 删除末尾的分号和空格
cookie_string = sub(r";\s*;", ";", cookie_string) # 删除中间多余分号后的空格
return cookie_string.strip("; ")
def create_folder(
self,
):
self.folder.mkdir(exist_ok=True)
self.temp.mkdir(exist_ok=True)
def compatible(
self,
):
if (
self.path == self.root
and (old := self.path.parent.joinpath(self.folder.name)).exists()
and not self.folder.exists()
):
move(old, self.folder)
@staticmethod
def cookie_str_to_dict(cookie_str: str) -> dict:
cookie = SimpleCookie()
cookie.load(cookie_str)
return {key: morsel.value for key, morsel in cookie.items()}
================================================
FILE: source/module/mapping.py
================================================
from pathlib import Path
from typing import TYPE_CHECKING
from ..translation import _
from .static import ERROR
from .tools import logging
if TYPE_CHECKING:
from manager import Manager
from recorder import MapRecorder
__all__ = ["Mapping"]
class Mapping:
def __init__(
self,
manager: "Manager",
mapping: "MapRecorder",
):
self.root = manager.folder
self.folder_mode = manager.folder_mode
self.database = mapping
self.switch = manager.author_archive
self.print = manager.print
async def update_cache(
self,
id_: str,
alias: str,
):
if not self.switch:
return
if (a := await self.has_mapping(id_)) and a != alias:
self.__check_file(
id_,
alias,
a,
)
await self.database.add(id_, alias)
async def has_mapping(self, id_: str) -> str:
return d[0] if (d := await self.database.select(id_)) else ""
def __check_file(
self,
id_: str,
alias: str,
old_alias: str,
):
if not (old_folder := self.root.joinpath(f"{id_}_{old_alias}")).is_dir():
logging(
self.print,
_("{old_folder} 文件夹不存在,跳过处理").format(
old_folder=old_folder.name
),
)
return
self.__rename_folder(
old_folder,
id_,
alias,
)
self.__scan_file(
id_,
alias,
old_alias,
)
def __rename_folder(
self,
old_folder: Path,
id_: str,
alias: str,
):
new_folder = self.root.joinpath(f"{id_}_{alias}")
self.__rename(
old_folder,
new_folder,
_("文件夹"),
)
logging(
self.print,
_("文件夹 {old_folder} 已重命名为 {new_folder}").format(
old_folder=old_folder.name, new_folder=new_folder.name
),
)
def __rename_works_folder(
self,
old_: Path,
alias: str,
old_alias: str,
) -> Path:
if old_alias in old_.name:
new_ = old_.parent / old_.name.replace(old_alias, alias, 1)
self.__rename(
old_,
new_,
_("文件夹"),
)
logging(
self.print,
_("文件夹 {old_} 重命名为 {new_}").format(
old_=old_.name, new_=new_.name
),
)
return new_
return old_
def __scan_file(
self,
id_: str,
alias: str,
old_alias: str,
):
root = self.root.joinpath(f"{id_}_{alias}")
item_list = root.iterdir()
if self.folder_mode:
for f in item_list:
if f.is_dir():
f = self.__rename_works_folder(
f,
alias,
old_alias,
)
files = f.iterdir()
self.__batch_rename(
f,
files,
alias,
old_alias,
)
else:
self.__batch_rename(
root,
item_list,
alias,
old_alias,
)
def __batch_rename(
self,
root: Path,
files,
alias: str,
old_alias: str,
):
for old_file in files:
if old_alias not in old_file.name:
break
self.__rename_file(
root,
old_file,
alias,
old_alias,
)
def __rename_file(
self,
root: Path,
old_file: Path,
alias: str,
old_alias: str,
):
new_file = root.joinpath(old_file.name.replace(old_alias, alias, 1))
self.__rename(
old_file,
new_file,
_("文件"),
)
logging(
self.print,
_("文件 {old_file} 重命名为 {new_file}").format(
old_file=old_file.name, new_file=new_file.name
),
)
return True
def __rename(
self,
old_: Path,
new_: Path,
type_=_("文件"),
) -> bool:
try:
old_.rename(new_)
return True
except PermissionError as e:
logging(
self.print,
_("{type} {old}被占用,重命名失败: {error}").format(
type=type_, old=old_.name, error=e
),
ERROR,
)
return False
except FileExistsError as e:
logging(
self.print,
_("{type} {new}名称重复,重命名失败: {error}").format(
type=type_, new=new_.name, error=e
),
ERROR,
)
return False
except OSError as e:
logging(
self.print,
_("处理{type} {old}时发生预期之外的错误: {error}").format(
type=type_, old=old_.name, error=e
),
ERROR,
)
return True
================================================
FILE: source/module/model.py
================================================
from pydantic import BaseModel
class ExtractParams(BaseModel):
url: str
download: bool = False
index: list[str | int] | None = None
cookie: str = None
proxy: str = None
skip: bool = False
class ExtractData(BaseModel):
message: str
params: ExtractParams
data: dict | None
================================================
FILE: source/module/recorder.py
================================================
from asyncio import CancelledError
from contextlib import suppress
from typing import TYPE_CHECKING
from shutil import move
from aiosqlite import connect
if TYPE_CHECKING:
from ..module import Manager
__all__ = ["IDRecorder", "DataRecorder", "MapRecorder"]
class IDRecorder:
def __init__(self, manager: "Manager"):
self.name = "ExploreID.db"
self.file = manager.root.joinpath(self.name)
self.changed = False
self.switch = manager.download_record
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.database.execute(
"CREATE TABLE IF NOT EXISTS explore_id (ID TEXT PRIMARY KEY);"
)
await self.database.commit()
async def select(self, id_: str):
if self.switch:
await self.cursor.execute("SELECT ID FROM explore_id WHERE ID=?", (id_,))
return await self.cursor.fetchone()
async def add(
self,
id_: str,
name: str = None,
*args,
**kwargs,
) -> None:
if self.switch:
await self.database.execute("REPLACE INTO explore_id VALUES (?);", (id_,))
await self.database.commit()
async def __delete(self, id_: str) -> None:
if id_:
await self.database.execute("DELETE FROM explore_id WHERE ID=?", (id_,))
await self.database.commit()
async def delete(self, ids: list[str]):
if self.switch:
[await self.__delete(i) for i in ids]
async def all(self):
if self.switch:
await self.cursor.execute("SELECT ID FROM explore_id")
return [i[0] for i in await self.cursor.fetchmany()]
async def __aenter__(self):
self.compatible()
await self._connect_database()
return self
async def __aexit__(self, exc_type, exc_value, traceback):
with suppress(CancelledError):
await self.cursor.close()
await self.database.close()
def compatible(
self,
):
if (
not self.changed
and (old := self.file.parent.parent.joinpath(self.name)).exists()
and not self.file.exists()
):
move(old, self.file)
class DataRecorder(IDRecorder):
DATA_TABLE = (
("采集时间", "TEXT"),
("作品ID", "TEXT PRIMARY KEY"),
("作品类型", "TEXT"),
("作品标题", "TEXT"),
("作品描述", "TEXT"),
("作品标签", "TEXT"),
("发布时间", "TEXT"),
("最后更新时间", "TEXT"),
("收藏数量", "TEXT"),
("评论数量", "TEXT"),
("分享数量", "TEXT"),
("点赞数量", "TEXT"),
("作者昵称", "TEXT"),
("作者ID", "TEXT"),
("作者链接", "TEXT"),
("作品链接", "TEXT"),
("下载地址", "TEXT"),
("动图地址", "TEXT"),
)
def __init__(self, manager: "Manager"):
super().__init__(manager)
self.name = "ExploreData.db"
self.file = manager.folder.joinpath(self.name)
self.changed = True
self.switch = manager.record_data
async def _connect_database(self):
self.database = await connect(self.file)
self.cursor = await self.database.cursor()
await self.database.execute(f"""CREATE TABLE IF NOT EXISTS explore_data (
{",".join(" ".join(i) for i in self.DATA_TABLE)}
);""")
await self.database.commit()
async def select(self, id_: str):
pass
async def add(self, **kwargs) -> None:
if self.switch:
await self.database.execute(
f"""REPLACE INTO explore_data (
{", ".join(i[0] for i in self.DATA_TABLE)}
) VALUES (
{", ".join("?" for _ in kwargs)}
);""",
self.__generate_values(kwargs),
)
await self.database.commit()
async def __delete(self, id_: str) -> None:
pass
async def delete(self, ids: list | tuple):
pass
async def all(self):
pass
def __generate_values(self, data: dict) -> tuple:
return tuple(data[i] for i, _ in self.DATA_TABLE)
class MapRecorder(IDRecorder):
def __init__(self, manager: "Manager"):
super().__init__(manager)
self.name = "MappingData.db"
self.file = manager.root.joinpath(self.name)
self.switch = manager.author_archive
async def _connect_database(self):
self.database = await connect(self.file)
self.cursor = await self.database.cursor()
await self.database.execute(
"CREATE TABLE IF NOT EXISTS mapping_data ("
"ID TEXT PRIMARY KEY,"
"NAME TEXT NOT NULL"
");"
)
await self.database.commit()
async def select(self, id_: str):
if self.switch:
await self.cursor.execute(
"SELECT NAME FROM mapping_data WHERE ID=?", (id_,)
)
return await self.cursor.fetchone()
async def add(self, id_: str, name: str, *args, **kwargs) -> None:
if self.switch:
await self.database.execute(
"REPLACE INTO mapping_data VALUES (?, ?);",
(
id_,
name,
),
)
await self.database.commit()
async def __delete(self, id_: str) -> None:
pass
async def delete(self, ids: list[str]):
pass
async def all(self):
if self.switch:
await self.cursor.execute("SELECT ID, NAME FROM mapping_data")
return [i[0] for i in await self.cursor.fetchmany()]
================================================
FILE: source/module/script.py
================================================
from contextlib import suppress
from json import loads
from websockets import ConnectionClosed, serve
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from ..application import XHS
class ScriptServer:
def __init__(
self,
core: "XHS",
host="0.0.0.0",
port=5558,
):
self.core = core
self.host = host
self.port = port
self.server = None
async def handler(self, websocket):
with suppress(ConnectionClosed):
async for message in websocket:
data = loads(message)
await self.core.deal_script_tasks(**data)
async def start(self):
"""启动服务器"""
self.server = await serve(
self.handler,
self.host,
self.port,
)
async def stop(self):
"""停止服务器"""
if self.server:
self.server.close()
await self.server.wait_closed()
async def __aenter__(self):
await self.start()
return self
async def __aexit__(self, exc_type, exc_val, exc_tb):
await self.stop()
================================================
FILE: source/module/settings.py
================================================
from json import dump, load
from pathlib import Path
from platform import system
from shutil import move
from .static import ROOT, USERAGENT
__all__ = ["Settings"]
class Settings:
# 默认配置参数
default = {
"mapping_data": {}, # 账号备注映射数据
"work_path": "", # 工作目录路径
"folder_name": "Download", # 下载文件夹名称
"name_format": "发布时间 作者昵称 作品标题", # 文件命名格式
"user_agent": USERAGENT, # 请求头
# "a_user_agent": USERAGENT, # 请求头
# "b_user_agent": USERAGENT, # 请求头
"cookie": "", # Cookie
"proxy": None, # 代理设置
"timeout": 10, # 超时时间(秒)
"chunk": 1024 * 1024 * 2, # 下载块大小(字节)
"max_retry": 5, # 最大重试次数
"record_data": False, # 是否记录作品数据
"image_format": "JPEG", # 图文作品格式
"image_download": True, # 是否下载图文
"video_download": True, # 是否下载视频
"live_download": False, # 是否下载动图
"video_preference": "resolution", # 视频文件偏好
"folder_mode": False, # 文件夹归档模式
"download_record": True, # 是否记录下载历史
"author_archive": False, # 是否按作者归档
"write_mtime": False, # 是否写入修改时间
"language": "zh_CN", # 语言设置
"script_server": False, # 是否启用脚本服务器
}
# 根据操作系统设置编码格式
encode = "UTF-8-SIG" if system() == "Windows" else "UTF-8"
def __init__(self, root: Path = ROOT):
"""初始化Settings类
Args:
root: 设置文件的根目录路径,默认为ROOT
"""
# 设置文件路径
self.name = "settings.json"
self.root = root
self.path = root.joinpath(self.name)
def run(self):
"""运行设置管理
Returns:
dict: 设置参数字典
"""
self.migration_file()
# 如果文件存在则读取,否则创建新文件
return self.read() if self.path.is_file() else self.create()
def read(self) -> dict:
"""读取设置文件
Returns:
dict: 读取的设置参数字典
"""
# 读取设置文件
with self.path.open("r", encoding=self.encode) as f:
return self.compatible(load(f))
def create(self) -> dict:
"""创建新的设置文件
Returns:
dict: 默认设置参数字典
"""
# 创建新的设置文件
with self.path.open("w", encoding=self.encode) as f:
dump(self.default, f, indent=4, ensure_ascii=False)
return self.default
def update(self, data: dict):
"""更新设置文件内容
Args:
data: 要更新的设置参数字典
"""
# 更新设置文件
with self.path.open("w", encoding=self.encode) as f:
dump(data, f, indent=4, ensure_ascii=False)
def compatible(
self,
data: dict,
) -> dict:
"""兼容性检查,确保所有默认配置都存在
Args:
data: 要检查的设置参数字典
Returns:
dict: 经过兼容性检查后的设置参数字典
"""
# 兼容性检查: 确保所有默认配置都存在
update = False
for i, j in self.default.items():
if i not in data:
data[i] = j
update = True
if update:
self.update(data)
return data
def migration_file(self):
"""迁移设置文件
如果旧的设置文件存在且新路径下不存在,则移动旧文件到新路径
"""
if (
old := self.root.parent.joinpath(self.name)
).exists() and not self.path.exists():
move(old, self.path)
================================================
FILE: source/module/static.py
================================================
from pathlib import Path
VERSION_MAJOR = 2
VERSION_MINOR = 8
VERSION_BETA = True
__VERSION__ = f"{VERSION_MAJOR}.{VERSION_MINOR}.{'beta' if VERSION_BETA else 'stable'}"
ROOT = Path(__file__).resolve().parent.parent.parent.joinpath("Volume")
ROOT.mkdir(exist_ok=True)
PROJECT = f"XHS-Downloader V{VERSION_MAJOR}.{VERSION_MINOR} {
'Beta' if VERSION_BETA else 'Stable'
}"
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/143.0.0.0 "
"Safari/537.36 Edg/143.0.0.0"
)
HEADERS = {
"accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,"
"application/signed-exchange;v=b3;q=0.7",
"referer": "https://www.xiaohongshu.com/explore",
"user-agent": USERAGENT,
}
MASTER = "#fff200"
PROMPT = "turquoise2"
GENERAL = "bright_white"
PROGRESS = "bright_magenta"
ERROR = "bright_red"
WARNING = "bright_yellow"
INFO = "bright_green"
FILE_SIGNATURES: tuple[
tuple[
int,
bytes,
str,
],
...,
] = (
# 分别为偏移量(字节)、十六进制签名、后缀
# 参考:https://en.wikipedia.org/wiki/List_of_file_signatures
# 参考:https://www.garykessler.net/library/file_sigs.html
(0, b"\xff\xd8\xff", "jpeg"),
(0, b"\x89\x50\x4e\x47\x0d\x0a\x1a\x0a", "png"),
(4, b"\x66\x74\x79\x70\x61\x76\x69\x66", "avif"),
(4, b"\x66\x74\x79\x70\x68\x65\x69\x63", "heic"),
(8, b"\x57\x45\x42\x50", "webp"),
(4, b"\x66\x74\x79\x70\x4d\x53\x4e\x56", "mp4"),
(4, b"\x66\x74\x79\x70\x69\x73\x6f\x6d", "mp4"),
(4, b"\x66\x74\x79\x70\x6d\x70\x34\x32", "m4v"),
(4, b"\x66\x74\x79\x70\x71\x74\x20\x20", "mov"),
(0, b"\x1a\x45\xdf\xa3", "mkv"),
(0, b"\x00\x00\x01\xb3", "mpg"),
(0, b"\x00\x00\x01\xba", "mpg"),
(0, b"\x46\x4c\x56\x01", "flv"),
(8, b"\x41\x56\x49\x20", "avi"),
)
FILE_SIGNATURES_LENGTH = max(
offset + len(signature) for offset, signature, _ in FILE_SIGNATURES
)
MAX_WORKERS: int = 4
if __name__ == "__main__":
print(__VERSION__)
================================================
FILE: source/module/tools.py
================================================
from asyncio import sleep
from random import lognormvariate
from math import log
from typing import Callable
from rich import print
from rich.text import Text
from ..translation import _
from .static import INFO
def retry(function):
async def inner(self, *args, **kwargs):
if result := await function(self, *args, **kwargs):
return result
for __ in range(self.retry):
if result := await function(self, *args, **kwargs):
return result
return result
return inner
def retry_limited(function):
# TODO: 不支持 TUI
def inner(self, *args, **kwargs):
while True:
if function(self, *args, **kwargs):
return
if self.console.input(
_(
"如需重新尝试处理该对象,请关闭所有正在访问该对象的窗口或程序,然后直接按下回车键!\n"
"如需跳过处理该对象,请输入任意字符后按下回车键!"
),
):
return
return inner
def logging(log: Callable, text, style=INFO):
string = Text(text, style=style)
func = log()
if func is print:
func(string)
else:
func.write(
string,
scroll_end=True,
)
def get_wait_time(
avg_delay: float | int = 6.0,
sigma: float = 0.6,
) -> float:
mu = log(avg_delay) - (sigma**2 / 2)
return max(0.5, lognormvariate(mu, sigma))
async def sleep_time():
await sleep(get_wait_time())
================================================
FILE: source/translation/__init__.py
================================================
from .translate import switch_language, _
================================================
FILE: source/translation/translate.py
================================================
from gettext import translation
from locale import getlocale
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent.parent
class TranslationManager:
"""管理gettext翻译的类"""
_instance = None # 单例实例
def __new__(cls, *args, **kwargs):
if not cls._instance:
cls._instance = super(TranslationManager, cls).__new__(cls)
return cls._instance
def __init__(self, domain="xhs", localedir=None):
self.domain = domain
if not localedir:
localedir = ROOT.joinpath("locale")
self.localedir = Path(localedir)
self.current_translator = self.setup_translation(
self.get_language_code(),
)
@staticmethod
def get_language_code() -> str:
# 获取当前系统的语言和区域设置
language_code, __ = getlocale()
if not language_code:
return "en_US"
return (
"zh_CN"
if any(
s in language_code.upper()
for s in (
"CHINESE",
"ZH",
"CHINA",
)
)
else "en_US"
)
def setup_translation(self, language: str = "zh_CN"):
"""设置gettext翻译环境"""
try:
return translation(
self.domain,
localedir=self.localedir,
languages=[language],
fallback=True,
)
except FileNotFoundError as e:
print(
f"Warning: Translation files for '{self.domain}' not found. Error: {e}"
)
return translation(self.domain, fallback=True)
def switch_language(self, language: str = "en_US"):
"""切换当前使用的语言"""
self.current_translator = self.setup_translation(language)
def gettext(self, message):
"""提供gettext方法"""
return self.current_translator.gettext(message)
# 初始化TranslationManager单例实例
translation_manager = TranslationManager()
def _translate(message):
"""辅助函数来简化翻译调用"""
return translation_manager.gettext(message)
def switch_language(language: str = "en_US"):
"""切换语言并刷新翻译函数"""
global _
translation_manager.switch_language(language)
_ = translation_manager.gettext
# 设置默认翻译函数
_ = _translate
================================================
FILE: static/20250619.js
================================================
function L(h, b) {
var C = F();
L = function (f, v) {
f = f - 0x1aa;
var t = C[f];
return t;
}
;
return L(h, b);
}
(function (h, b) {
var C6 = {
h: 0x210,
b: 0x31f
};
var Fn = L;
var C = h();
while (!![]) {
try {
var f = parseInt(Fn(0x314)) / 0x1 + parseInt(Fn(0x23e)) / 0x2 + parseInt(Fn(0x220)) / 0x3 * (-parseInt(Fn(0x26e)) / 0x4) + -parseInt(Fn(0x319)) / 0x5 * (parseInt(Fn(0x305)) / 0x6) + -parseInt(Fn(C6.h)) / 0x7 + parseInt(Fn(0x2b0)) / 0x8 * (parseInt(Fn(0x1c2)) / 0x9) + -parseInt(Fn(C6.b)) / 0xa * (-parseInt(Fn(0x2ce)) / 0xb);
if (f === b) {
break;
} else {
C['push'](C['shift']());
}
} catch (v) {
C['push'](C['shift']());
}
}
}(F, 0xbce69));
function F() {
var RC = ['JZLvj', 'oahov', 'gPDFl', 'DaRdU', 'eGCPD', 'pMsaX', 'vQQUk', 'SssvC', 'AMBCI', 'vSFPC', 'wSOnE', 'NIOdi', 'mXOEa', 'YAvpt', '3956hsoIXl', 'UPxpr', 'hEIFf', 'oNTxv', 'PTNqV', 'qPPwK', 'oXgav', 'XEyPA', 'sRVor', 'FsXNX', 'oRxVE', 'GrgCd', 'ALpNt', 'kEqSg', 'VHkPt', 'GlBWa', 'yAZPc', 'yOvzN', 'AWvMp', 'kRdje', 'zCPbZ', 'fIDlR', 'QmRpf', 'lGvVL', 'kgeUA', 'NcKvK', 'MRQGa', 'reduce', 'zXemr', 'xCAJf', 'xnNmv', 'bHZSF', 'pop', 'nwOLN', 'iZTGz', 'mjdDi', 'lOrOp', 'aSIKX', 'GTnDt', 'Vdmrg', 'vpgNZ', 'sNnEf', 'nMMxv', 'ikzvH', 'GKtmp', 'JiTri', 'pCEKQ', 'CGaSJ', 'JSON', 'XWoOH', 'eqyFf', 'quyBc', 'hfEPg', 'ijFaB', 'Zfoeu', 'MrzJy', '_sabo_95cb2', 'CScej', 'oBnDc', 'odVfd', 'XjCEE', 'kPfVf', 'SobNN', 'zzwfi', 'hXCqG', 'YqyTm', '8XPIkOX', 'rpzig', 'vfuFM', 'qyZqB', 'WwqmJ', 'WqSKX', 'grXOT', 'TnYHa', 'HIswS', 'TByEj', 'DBWWn', 'eOFNg', 'WzxBd', 'WHsCZ', 'SEoiV', 'svkqH', 'lbZOY', 'bind', 'OMccf', '4|1|2|0|5|9|6|3|8|7', 'epJNI', 'VLqYs', 'gSDNx', '_sabo_5b836', '_sabo_3088c', 'map', 'ZWORk', 'indexOf', 'hoXih', 'OChTg', '5235241KFSZcx', 'aMltr', 'nDlQf', 'PHGCq', 'wWSUD', 'odYLt', 'tXtzn', 'PGxhU', 'uEfBT', 'AfBYF', 'sVuBy', 'fRDpO', 'JbvFu', 'KdPNG', 'DinAD', 'jYhqS', 'zQsUh', 'aiVyO', 'GnKMQ', 'Lkyuv', 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/', 'bOaOU', 'goMdg', 'aLwwx', 'prototype', 'AryJI', 'tRAuT', 'eXpUz', 'atDry', 'charCodeAt', 'JBdED', 'KAEds', 'qMPgm', 'tGZGI', 'xRcPH', 'zNAiV', 'wBccl', 'APjhA', 'vfvwF', 'buGvd', 'qehkP', 'sioDi', 'Uiemt', 'dOPfp', 'vsGkJ', 'WbHfy', 'Horkt', 'jXFeA', 'OIQJh', 'IvpwU', 'FdoKy', 'orFnF', 'ZvPja', 'zHUzN', 'CZeXt', '722346TUWvEl', 'kweEL', 'PxhoI', 'RRNkR', 'ysQKL', 'YqfES', 'ibqQa', 'PUCdd', 'BWpPZ', 'hhbCq', 'fJlXY', 'OGgZX', 'fromCharCode', 'tRJfx', 'dBKVS', '890714JAyMWZ', 'FrWRh', 'ptgek', 'nlBkp', 'nzYNo', '45oABSvD', 'hxqUd', 'TwqJu', 'eMmdn', 'jHxlb', 'nHhGK', '10BSxYuf', 'HRfcg', 'gUbAP', 'FGzOP', 'tItQY', 'jTwuf', 'CFtvf', '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', 'rCmpM', 'OoaGy', 'MpRAu', 'deFSN', 'GKBtV', 'XDorZ', 'nVIbO', 'IdiaC', 'vWruB', 'OzdlV', 'CcUsi', 'join', 'dAAVb', 'djLSf', 'LjgZY', 'HLABO', 'lGKwN', 'PXTBo', 'zGaFk', 'FAcyL', 'sMIQH', 'HozPp', 'oJfRr', 'WTylz', 'UGTAB', 'qlpXf', 'JcTTq', 'mMONc', 'AKXKb', 'MmaEG', 'bZSXv', 'LJyjj', 'ckdoI', 'oeJmh', 'PPfJp', 'icctW', 'NNfMe', 'Mzwla', 'NJFpJ', 'KHzzB', 'OMgMD', 'ENgsj', 'ImoXf', 'kmutF', 'zUOBa', '_sabo_57ecb', 'xblso', 'GkjFS', 'BllIK', 'wCzTn', 'mTPIJ', 'cIovd', 'ZRZmq', 'hakVh', 'IWYXF', 'gFinC', 'vqEeQ', 'HeuYr', 'dJeju', 'zaGpL', 'bEIcG', 'split', 'hyZoA', 'ijCbw', 'YULRE', 'ZgtSl', 'GNxQH', 'Ufprf', 'SmgXY', 'uDAFK', 'hkMoL', '_sabo_c097b', '7229619GDXiCt', 'SOakV', '_sabo_e2c0a', 'dReHp', 'XNrHe', 'uLSjZ', 'lUoXu', 'ECpKE', 'lDXhj', 'MLjuz', 'isNaN', 'TNAaC', 'Error', 'YVUIV', 'GRZrF', 'Dqqnv', 'SROLQ', 'svANh', 'vyrbl', 'apply', 'zryFJ', 'WIitv', '_sabo_10b1e', 'NVYmF', 'ZavHO', 'Uint8Array', 'EFeRo', 'FqByk', 'QgjFq', 'Ofenh', 'snVSB', 'kcAaU', 'MGiPt', 'kSUUe', 'YIEGq', 'SvIPY', 'SKkEN', 'slice', '_sabo_6da36', 'QSewX', 'ziuYy', 'gnqzd', 'qfOel', 'hZcaT', 'CnNTH', 'XwdTh', 'KhWSm', 'ljzHB', 'KjZAP', 'blfSb', 'iEAsQ', 'oWFrJ', 'RGcqc', 'uMofr', 'CDGuO', 'bdltS', 'pRDMG', 'rKNGe', 'zJdLz', '_sabo_c724', 'sHzOh', 'watuf', 'AleBJ', '_sabo_d4818', 'rbpGg', 'IgyrF', 'qbJjB', 'encodeURI', 'hgTCW', 'ivsOQ', 'NEMiU', 'ciJml', 'RRAzt', 'EUTZE', 'byFQy', 'charAt', 'PlApi', 'afgyW', '966651pmXOPB', 'bApKG', 'GYyhS', 'JYyAi', 'yMgIH', 'ahuAk', 'CcYre', 'GstgQ', 'eDqxI', 'zCLPb', 'EhpkL', 'nAdsq', 'ShWNE', 'Efzgp', 'ngarZ', 'uLFqi', '4458qRqukd', 'VgmAq', 'UEaJS', 'cLzzq', 'prjDE', 'pisTm', 'vfeYb', 'lIxby', 'QOeak', 'wQhwE', 'ODGrw', 'cAlyE', 'vEcMv', 'ldnrr', 'jqmtI', 'DGWOU', 'length', 'MqBmP', 'Wxdrg', 'ZHwyQ', 'NTrbI', 'SeLDj', 'OoSWq', 'tVHze', 'TYkNO', 'yHiCc', 'KECwk', 'sqlMq', 'QImRg', 'YsXRR', '2590134kfnprK', 'VRBVo', 'XvVud', 'NLzhS', 'tBAUu', 'unshift', 'rtvbE', 'uQaTs', 'yrndN', 'QNaSf', 'uWAZn', 'awHpc', 'LLEzS', 'DiKZc', 'SIhNg', 'push', 'kHKev', 'fVHLl', 'ERaGW', 'splice', 'QcqJP', 'ucaVm', 'huAES', 'mhMSh', 'mNQYE', 'lAWcZ', 'dvFSE', 'GGXmC', 'YqUuj', 'umXpK', 'eTZYs', 'RlxEZ', 'BNqwn', 'MMTae'];
F = function () {
return RC;
}
;
return F();
}
(function () {
var Rb = {
h: 0x227,
b: 0x1f0,
C: 0x223,
f: 0x2a0,
v: 0x334,
t: 0x1bb,
c: 0x1ed,
W: 0x273,
R: 0x250,
S: 0x29f,
E: 0x25f,
i: 0x353,
N: 0x316,
g: 0x2fb,
x: 0x300,
r: 0x1da,
m: 0x30a,
e: 0x297,
B: 0x1cc,
y: 0x265
};
var Rh = {
h: 0x224,
b: 0x330,
C: 0x1ab,
f: 0x24a,
v: 0x1fa,
t: 0x265,
c: 0x247,
W: 0x249,
R: 0x338,
S: 0x1bc,
E: 0x2c3
};
var vA = {
h: 0x29a
};
var vB = {
h: 0x2c4
};
var vE = {
h: 0x25a
};
var fD = {
h: 0x1ff
};
var ft = {
h: 0x304
};
var fb = {
h: 0x350
};
var f7 = {
h: 0x218
};
var f2 = {
h: 0x31d
};
var CO = {
h: 0x350
};
var CI = {
h: 0x2cf
};
var FQ = L;
var h = {
'aMltr': function (C, f, v, t, c) {
return C(f, v, t, c);
},
'MmaEG': function (C, f) {
return C + f;
},
'GKtmp': function (C, f, v) {
return C(f, v);
},
'eDqxI': function (C, f) {
return C < f;
},
'ENgsj': function (C, f) {
return C === f;
},
'wWSUD': FQ(0x202),
'AryJI': function (C, f) {
return C & f;
},
'prjDE': FQ(Rb.h),
'kgeUA': function (C, f) {
return C << f;
},
'jHxlb': function (C, f) {
return C & f;
},
'XDorZ': function (C, f) {
return C | f;
},
'hgTCW': function (C, f) {
return C & f;
},
'kRdje': function (C, f) {
return C !== f;
},
'OzdlV': 'ZRZmq',
'vpgNZ': FQ(0x1d0),
'BNqwn': function (C, f) {
return C >> f;
},
'wCzTn': FQ(Rb.b),
'vQQUk': function (C, f) {
return C != f;
},
'cAlyE': function (C, f) {
return C(f);
},
'rKKzw': FQ(0x31b),
'LLEzS': FQ(0x28a),
'mjdDi': FQ(0x24f),
'umXpK': FQ(Rb.C),
'jTwuf': function (C, f, v, t, c) {
return C(f, v, t, c);
},
'CZeXt': function (C, f, v, t, c) {
return C(f, v, t, c);
},
'xblso': FQ(Rb.f),
'qehkP': FQ(0x31e),
'pRDMG': FQ(0x267),
'AWvMp': function (C, f, v, t, c) {
return C(f, v, t, c);
},
'bEIcG': function (C, f) {
return C >>> f;
},
'nMMxv': FQ(0x27b),
'NLzhS': FQ(0x2e0),
'kcAaU': FQ(Rb.v),
'lUoXu': function (C, f) {
return C == f;
},
'DaRdU': function (C, f) {
return C !== f;
},
'NEMiU': 'DLZuo',
'TNZwf': 'bNhav',
'cIovd': function (C, f) {
return C - f;
},
'VkNzq': FQ(Rb.t),
'poQoR': FQ(0x2a7),
'KHzzB': function (C, f) {
return C * f;
},
'NTrbI': function (C, f) {
return C - f;
},
'watuf': function (C, f) {
return C ^ f;
},
'tBAUu': function (C, f, v) {
return C(f, v);
},
'nAdsq': FQ(Rb.c),
'vfuFM': function (C, f) {
return C in f;
},
'ikzvH': FQ(Rb.W),
'mTPIJ': FQ(0x207),
'AENql': function (C, f) {
return C !== f;
},
'QOeak': function (C, f, v) {
return C(f, v);
},
'QSewX': function (C, f) {
return C - f;
},
'pMsaX': 'undefined',
'wurWF': function (C, f, v) {
return C(f, v);
},
'eOFNg': function (C, f, v, t, c) {
return C(f, v, t, c);
},
'oBnDc': function (C, f, v, t, c) {
return C(f, v, t, c);
},
'cbnsj': function (C, f) {
return C % f;
},
'qlpXf': FQ(0x1f7),
'CcYre': function (C, f, v) {
return C(f, v);
},
'ySQYi': function (C, f, v) {
return C(f, v);
},
'GTknR': FQ(Rb.R),
'dJeju': function (C, f, v) {
return C(f, v);
},
'MrzJy': function (C, f, v) {
return C(f, v);
},
'piLdu': 'SFzDm',
'qMPgm': FQ(Rb.S),
'zzwfi': function (C, f) {
return C == f;
},
'EsoLm': FQ(0x31c),
'QNaSf': FQ(0x27f),
'awHpc': FQ(Rb.E),
'uCZeD': function (C, f) {
return C === f;
},
'IvpwU': 'gNlYm',
'qCvbJ': FQ(0x32a),
'JYyAi': FQ(0x1f1),
'YpQLH': FQ(0x264),
'YqUuj': function (C, f, v) {
return C(f, v);
},
'hXCqG': FQ(Rb.i),
'PXTBo': FQ(Rb.N),
'tItQY': function (C, f, v, t, c) {
return C(f, v, t, c);
},
'GkjFS': function (C, f) {
return C > f;
},
'jkiDJ': FQ(Rb.g),
'PPfJp': FQ(0x29d),
'epJNI': function (C, f) {
return C !== f;
},
'YckUU': FQ(Rb.x),
'iEKgt': FQ(Rb.r),
'LTTNI': function (C) {
return C();
},
'gSDNx': function (C, f, v, t, c) {
return C(f, v, t, c);
},
'dBKVS': function (C, f) {
return C instanceof f;
},
'GNxQH': FQ(0x2b6),
'ucOWm': function (C, f, v) {
return C(f, v);
},
'ucaVm': FQ(Rb.m),
'zHUzN': function (C, f) {
return C(f);
},
'sVuBy': FQ(0x325),
'dAAVb': FQ(0x24b),
'sNnEf': FQ(0x326),
'FsXNX': 'window',
'GstgQ': FQ(0x1ce),
'PxhoI': FQ(0x1db),
'ZkQes': FQ(0x29e),
'lAWcZ': FQ(0x205)
};
function b() {
var RL = {
h: 0x28d,
b: 0x23a,
C: 0x256,
f: 0x2ea,
v: 0x2d6,
t: 0x21e
};
var R9 = {
h: 0x2a6
};
var WO = {
h: 0x1d7
};
var WU = {
h: 0x309
};
var WV = {
h: 0x2f3,
b: 0x32b,
C: 0x343,
f: 0x2a6
};
var Wk = {
h: 0x2f7
};
var Wl = {
h: 0x1cf
};
var WD = {
h: 0x284,
b: 0x26a,
C: 0x1fd,
f: 0x209
};
var Wg = {
h: 0x1b9
};
var WN = {
h: 0x1d5,
b: 0x1fd
};
var WS = {
h: 0x1fc
};
var Wc = {
h: 0x1b3,
b: 0x274
};
var Wb = {
h: 0x2d5
};
var Wh = {
h: 0x1e7,
b: 0x346
};
var W5 = {
h: 0x1bf
};
var W1 = {
h: 0x21d
};
var cI = {
h: 0x2c7,
b: 0x2c8,
C: 0x1b8,
f: 0x24d,
v: 0x2e5,
t: 0x2eb
};
var cq = {
h: 0x2c5,
b: 0x1d3
};
var cU = {
h: 0x230,
b: 0x20e
};
var cd = {
h: 0x1d8
};
var ce = {
h: 0x28e
};
var cm = {
h: 0x24d
};
var cE = {
h: 0x2fa,
b: 0x2c8,
C: 0x2e9
};
var cv = {
h: 0x30c,
b: 0x1d9,
C: 0x1ae,
f: 0x24d,
v: 0x2e2,
t: 0x2cb,
c: 0x20d,
W: 0x29c,
R: 0x29c,
S: 0x20f,
E: 0x311,
i: 0x33a,
N: 0x24d,
g: 0x332
};
var ta = {
h: 0x341
};
var tD = {
h: 0x287
};
var vQ = {
h: 0x2cf
};
var vl = {
h: 0x2c6
};
var vy = {
h: 0x218
};
var v9 = {
h: 0x280
};
var fq = {
h: 0x242
};
var fT = {
h: 0x2a8
};
var fo = {
h: 0x29a
};
var fY = {
h: 0x234
};
var fR = {
h: 0x1b6
};
var fC = {
h: 0x2cf
};
var Cz = {
h: 0x218
};
var Cs = {
h: 0x344
};
var FK = FQ;
var C = {
'nwOLN': function (R, S, E, i, N) {
return R(S, E, i, N);
},
'kSUUe': function (R, S, E, i, N) {
var FZ = L;
return h[FZ(CI.h)](R, S, E, i, N);
},
'eTZYs': function (R, S) {
var FV = L;
return h[FV(Cs.h)](R, S);
},
'lGvVL': function (R, S, E) {
return h['GKtmp'](R, S, E);
},
'SROLQ': function (R, S) {
var FM = L;
return h[FM(Cz.h)](R, S);
},
'fJlXY': function (R, S) {
var FU = L;
return h[FU(CO.h)](R, S);
},
'PUCdd': h[FK(0x2d2)],
'FGzOP': function (R, S) {
return R >> S;
},
'afgyW': function (R, S) {
var FT = FK;
return h[FT(0x2e7)](R, S);
},
'kHKev': h[FK(Rh.h)],
'zGaFk': '2|0|4|3|1',
'RRNkR': function (R, S) {
var FP = FK;
return h[FP(0x286)](R, S);
},
'fAOHV': function (R, S) {
var Fp = FK;
return h[Fp(f2.h)](R, S);
},
'pCEKQ': function (R, S) {
var Fq = FK;
return h[Fq(0x32c)](R, S);
},
'sHzOh': function (R, S) {
var FI = FK;
return h[FI(0x2e7)](R, S);
},
'GGXmC': function (R, S) {
var Fs = FK;
return h[Fs(0x206)](R, S);
},
'NVYmF': function (R, S) {
var Fw = FK;
return h[Fw(0x281)](R, S);
},
'XwdTh': h[FK(Rh.b)],
'PHsGL': function (R, S) {
var Fz = FK;
return h[Fz(f7.h)](R, S);
},
'hxqUd': h[FK(0x296)],
'GrgCd': function (R, S) {
return h['BNqwn'](R, S);
},
'kzPnv': function (R, S) {
var FO = FK;
return h[FO(0x32c)](R, S);
},
'FrWRh': function (R, S) {
return R !== S;
},
'TByEj': h[FK(Rh.C)],
'FAcyL': function (R, S) {
return h['vQQUk'](R, S);
},
'AleBJ': function (R, S) {
var FJ = FK;
return h[FJ(0x22b)](R, S);
},
'huAES': h['rKKzw'],
'DGWOU': function (R, S) {
var L0 = FK;
return h[L0(fb.h)](R, S);
},
'jXFeA': h[FK(Rh.f)],
'uLSjZ': h[FK(0x291)],
'zaGpL': function (R, S, E, i, N) {
var L1 = FK;
return h[L1(fC.h)](R, S, E, i, N);
},
'OChTg': function (R, S) {
return R(S);
},
'zCLPb': h[FK(0x25b)],
'WHsCZ': function (R, S, E, i, N) {
var L2 = FK;
return h[L2(0x324)](R, S, E, i, N);
},
'quyBc': function (R, S, E, i, N) {
var L3 = FK;
return h[L3(ft.h)](R, S, E, i, N);
},
'DBWWn': h[FK(0x355)],
'rCmpM': h[FK(0x2f6)],
'HRfcg': function (R, S) {
return h['ENgsj'](R, S);
},
'bdltS': FK(0x27d),
'wMDXH': h[FK(Rh.v)],
'WwqmJ': function (R, S, E, i, N) {
return h['AWvMp'](R, S, E, i, N);
},
'BWpPZ': function (R, S) {
var L4 = FK;
return h[L4(fR.h)](R, S);
},
'snVSB': h[FK(0x298)],
'RlxEZ': h[FK(0x241)],
'SmgXY': function (R, S, E, i, N, g, x, r) {
return R(S, E, i, N, g, x, r);
},
'zCPbZ': function (R, S, E) {
var L5 = FK;
return h[L5(0x29a)](R, S, E);
},
'UEaJS': h[FK(0x1e1)],
'YqyTm': function (R, S) {
var L6 = FK;
return h[L6(0x1c8)](R, S);
},
'aSIKX': function (R, S) {
var L7 = FK;
return h[L7(0x263)](R, S);
},
'xnNmv': FK(0x34b),
'QgjFq': h[FK(0x208)],
'ldnrr': h['TNZwf'],
'ZvPja': function (R, S, E, i, N, g, x, r) {
return R(S, E, i, N, g, x, r);
},
'uLFqi': function (R, S) {
var L8 = FK;
return h[L8(0x1ad)](R, S);
},
'MRQGa': function (R, S) {
return R === S;
},
'dfxVY': h['VkNzq'],
'MBgMf': h['poQoR'],
'hyZoA': function (R, S) {
return R - S;
},
'LJyjj': function (R, S) {
var L9 = FK;
return h[L9(0x344)](R, S);
},
'aLwwx': function (R, S) {
var LF = FK;
return h[LF(0x34e)](R, S);
},
'WqSKX': function (R, S, E, i, N) {
var LL = FK;
return h[LL(0x2cf)](R, S, E, i, N);
},
'gPDFl': function (R, S) {
var Lh = FK;
return h[Lh(fY.h)](R, S);
},
'iZTGz': FK(0x1e4),
'TNAaC': function (R, S) {
var Lb = FK;
return h[Lb(fD.h)](R, S);
},
'odVfd': function (R, S, E) {
var LC = FK;
return h[LC(0x242)](R, S, E);
},
'yMgIH': function (R, S, E) {
return R(S, E);
},
'PGxhU': FK(0x211),
'SvIPY': h[FK(0x21b)],
'AfBYF': function (R, S) {
var Lf = FK;
return h[Lf(0x2b2)](R, S);
},
'RGcqc': function (R) {
return R();
},
'zJdLz': function (R, S, E) {
var Lv = FK;
return h[Lv(fo.h)](R, S, E);
},
'oXgav': h[FK(0x299)],
'sMIQH': function (R, S, E, i, N) {
return h['AWvMp'](R, S, E, i, N);
},
'gFinC': h[FK(0x1ac)],
'orFnF': function (R, S) {
return R !== S;
},
'HlDqZ': function (R, S, E, i, N) {
return R(S, E, i, N);
},
'rKNGe': function (R, S) {
return R != S;
},
'gUbAP': function (R, S) {
return h['AENql'](R, S);
},
'OMccf': FK(0x1e6),
'oeJmh': function (R, S) {
return h['BNqwn'](R, S);
},
'WIitv': function (R, S, E) {
var Lt = FK;
return h[Lt(0x29a)](R, S, E);
},
'XNrHe': function (R, S, E) {
var Lc = FK;
return h[Lc(0x228)](R, S, E);
},
'lHsxx': function (R, S) {
var LW = FK;
return h[LW(0x1e9)](R, S);
},
'ckdoI': function (R, S) {
var LR = FK;
return h[LR(0x22b)](R, S);
},
'IgyrF': h[FK(Rh.t)],
'zryFJ': function (R, S, E) {
var LS = FK;
return h[LS(0x228)](R, S, E);
},
'SOakV': function (R, S, E) {
return h['wurWF'](R, S, E);
},
'QcqJP': function (R, S, E, i, N) {
return h['eOFNg'](R, S, E, i, N);
},
'IdiaC': function (R, S, E, i, N) {
var LE = FK;
return h[LE(fT.h)](R, S, E, i, N);
},
'sioDi': function (R, S, E) {
return R(S, E);
},
'DHUPP': function (R, S) {
return h['cbnsj'](R, S);
},
'NJFpJ': function (R, S, E) {
var Li = FK;
return h[Li(fq.h)](R, S, E);
},
'yHiCc': function (R, S, E) {
return h['GKtmp'](R, S, E);
},
'uNwcj': h[FK(0x340)],
'ciJml': function (R, S, E) {
var LN = FK;
return h[LN(0x216)](R, S, E);
},
'NcKvK': function (R, S, E, i, N) {
return R(S, E, i, N);
},
'YVUIV': function (R, S) {
return R / S;
},
'qfltV': function (R, S) {
return R - S;
},
'yzhnN': function (R, S, E) {
return h['ySQYi'](R, S, E);
},
'sRVor': function (R, S) {
return R === S;
},
'ODGrw': function (R, S, E) {
return R(S, E);
},
'APjhA': function (R, S) {
var Lg = FK;
return h[Lg(0x263)](R, S);
},
'GKBtV': h['GTknR'],
'AKXKb': function (R, S, E) {
var Lx = FK;
return h[Lx(0x216)](R, S, E);
},
'FJREI': function (R, S, E, i, N) {
return R(S, E, i, N);
},
'gnqzd': function (R, S, E) {
var Lr = FK;
return h[Lr(0x1b4)](R, S, E);
},
'atDry': function (R, S, E, i, N) {
return R(S, E, i, N);
},
'ELHrH': function (R, S) {
return R - S;
},
'vQqZP': function (R, S, E) {
var Lm = FK;
return h[Lm(0x2a5)](R, S, E);
},
'ocRxu': function (R, S, E, i, N) {
var Le = FK;
return h[Le(v9.h)](R, S, E, i, N);
},
'dvFSE': function (R, S) {
return R !== S;
},
'ABTJb': h['piLdu'],
'MGiPt': h[FK(0x2ee)],
'gQkLB': function (R, S, E, i, N) {
return R(S, E, i, N);
},
'ahuAk': function (R, S) {
return R > S;
},
'AMBCI': function (R, S, E) {
return R(S, E);
},
'RRAzt': function (R, S, E) {
return h['ySQYi'](R, S, E);
},
'MqBmP': function (R, S) {
var LB = FK;
return h[LB(0x2ad)](R, S);
},
'tGZGI': function (R, S, E, i, N) {
var Ly = FK;
return h[Ly(0x2bb)](R, S, E, i, N);
},
'JZLvj': h['EsoLm'],
'vEcMv': h[FK(Rh.c)],
'fIDlR': function (R, S, E, i, N) {
return h['CZeXt'](R, S, E, i, N);
},
'HAyBd': function (R, S, E) {
return R(S, E);
},
'xCAJf': h[FK(Rh.W)],
'kmutF': function (R, S) {
return h['uCZeD'](R, S);
},
'wQhwE': h[FK(0x2ff)],
'hVFPT': h['qCvbJ'],
'bHZSF': h[FK(0x213)],
'JmIJf': h['YpQLH'],
'tXtzn': function (R, S, E) {
var LY = FK;
return h[LY(0x29a)](R, S, E);
},
'HIswS': function (R, S) {
var LD = FK;
return h[LD(0x266)](R, S);
},
'vtWFF': function (R, S, E) {
var Lj = FK;
return h[Lj(vE.h)](R, S, E);
},
'WTylz': function (R, S) {
return R - S;
},
'ImoXf': h[FK(0x2ae)],
'ljrRl': h[FK(Rh.R)],
'CcUsi': function (R, S, E, i, N) {
var Ll = FK;
return h[Ll(0x323)](R, S, E, i, N);
},
'hoXih': function (R, S) {
var LA = FK;
return h[LA(0x356)](R, S);
},
'Qkaka': h['jkiDJ'],
'Ufprf': function (R, S, E, i, N) {
return h['CZeXt'](R, S, E, i, N);
},
'TcDxS': function (R, S, E) {
var LG = FK;
return h[LG(0x242)](R, S, E);
},
'Uiemt': h[FK(0x349)],
'tRJfx': function (R, S) {
return R + S;
},
'jksMW': function (R, S, E) {
return h['QOeak'](R, S, E);
},
'OIQJh': function (R, S) {
var Lo = FK;
return h[Lo(vB.h)](R, S);
},
'WAmru': function (R, S) {
var Lk = FK;
return h[Lk(vy.h)](R, S);
},
'Wxdrg': h['YckUU'],
'QImRg': h['iEKgt'],
'rpzig': function (R, S, E, i, N) {
var La = FK;
return h[La(0x2bb)](R, S, E, i, N);
},
'FjvPb': function (R) {
return h['LTTNI'](R);
},
'EhpkL': function (R, S, E, i, N) {
return h['aMltr'](R, S, E, i, N);
},
'ngarZ': function (R, S, E, i, N) {
var Ld = FK;
return h[Ld(vl.h)](R, S, E, i, N);
},
'JcTTq': function (R, S, E) {
var LX = FK;
return h[LX(vA.h)](R, S, E);
},
'KdPNG': function (R, S) {
var LH = FK;
return h[LH(0x313)](R, S);
},
'MLjuz': h[FK(Rh.S)],
'tVHze': function (R, S, E, i, N) {
var Lu = FK;
return h[Lu(0x2a8)](R, S, E, i, N);
},
'ybgJn': function (R, S, E) {
return h['ucOWm'](R, S, E);
},
'bRryP': h[FK(0x253)],
'vSFPC': function (R, S) {
var Ln = FK;
return h[Ln(0x303)](R, S);
},
'KECwk': h[FK(0x2d8)],
'fSmTE': function (R, S, E, i, N) {
return R(S, E, i, N);
},
'UPxpr': FK(Rh.E),
'aiVyO': function (R, S) {
var LQ = FK;
return h[LQ(0x2e7)](R, S);
},
'mMONc': function (R, S) {
var LZ = FK;
return h[LZ(0x25e)](R, S);
},
'mNQYE': h[FK(0x333)],
'goMdg': function (R, S, E, i, N) {
return R(S, E, i, N);
},
'uEfBT': function (R, S, E, i, N) {
var LV = FK;
return h[LV(0x2bb)](R, S, E, i, N);
},
'kweEL': function (R, S, E, i, N) {
var LM = FK;
return h[LM(vQ.h)](R, S, E, i, N);
}
};
var f = 0x7fffffff
, v = 0x1
, t = 0x0
, c = !!v
, W = !!t;
return function (R, S, E) {
var RF = {
h: 0x233
};
var R6 = {
h: 0x1d4
};
var R5 = {
h: 0x255,
b: 0x29b,
C: 0x2cb,
f: 0x311,
v: 0x20d,
t: 0x238
};
var Wz = {
h: 0x215,
b: 0x20a
};
var WZ = {
h: 0x1af,
b: 0x245
};
var WQ = {
h: 0x30e
};
var Wu = {
h: 0x233
};
var WH = {
h: 0x27a
};
var WX = {
h: 0x310
};
var Wa = {
h: 0x34d,
b: 0x22a
};
var WB = {
h: 0x2da
};
var Wr = {
h: 0x1d6,
b: 0x2a6
};
var Wi = {
h: 0x2c2
};
var WE = {
h: 0x285
};
var WR = {
h: 0x315,
b: 0x1f2
};
var Wt = {
h: 0x1fc
};
var Wf = {
h: 0x24d
};
var WC = {
h: 0x26d,
b: 0x23b,
C: 0x230
};
var W7 = {
h: 0x32f,
b: 0x1ec
};
var W2 = {
h: 0x262,
b: 0x1fd
};
var W0 = {
h: 0x282
};
var cJ = {
h: 0x230,
b: 0x2aa,
C: 0x2f2,
f: 0x243,
v: 0x2e6,
t: 0x2c1
};
var cw = {
h: 0x2d9
};
var cT = {
h: 0x226,
b: 0x293,
C: 0x2c8,
f: 0x302
};
var cu = {
h: 0x23d
};
var cX = {
h: 0x2a1
};
var ck = {
h: 0x23f,
b: 0x1df
};
var cD = {
h: 0x22f,
b: 0x219
};
var cy = {
h: 0x1f3
};
var cx = {
h: 0x2c7,
b: 0x2c8
};
var cR = {
h: 0x1fd,
b: 0x2a6,
C: 0x1c4
};
var cW = {
h: 0x221
};
var cf = {
h: 0x24d,
b: 0x2d3,
C: 0x1b7,
f: 0x27c,
v: 0x311,
t: 0x235,
c: 0x1dc,
W: 0x1f8,
R: 0x1fd,
S: 0x2a6
};
var ch = {
h: 0x308
};
var cL = {
h: 0x29c
};
var c5 = {
h: 0x20f
};
var tz = {
h: 0x285
};
var tq = {
h: 0x2e4
};
var tu = {
h: 0x237
};
var vq = {
h: 0x288
};
var LT = FK;
var i = {
'eXpUz': function (p, q, I, s, w) {
return p(q, I, s, w);
},
'GTnDt': function (p, q, I) {
var LU = L;
return C[LU(0x1d6)](p, q, I);
},
'FqByk': function (p, q, I, s, w) {
return p(q, I, s, w);
},
'tRAuT': function (p, q) {
var LK = L;
return C[LK(0x301)](p, q);
},
'JNcQS': C[LT(0x260)],
'nzYNo': 'ByABT',
'vsGkJ': function (p, q, I) {
return C['NJFpJ'](p, q, I);
},
'OoSWq': 'XrElB',
'XEyPA': C[LT(0x22c)],
'VLqYs': function (p, q, I, s, w) {
var LP = LT;
return C[LP(0x283)](p, q, I, s, w);
},
'kPfVf': function (p, q) {
var Lp = LT;
return C[Lp(0x200)](p, q);
},
'ECpKE': function (p, q, I) {
return C['HAyBd'](p, q, I);
},
'ALpNt': function (p, q) {
var Lq = LT;
return C[Lq(vq.h)](p, q);
},
'blfSb': C[LT(0x28b)],
'hakVh': function (p, q, I, s, w) {
var LI = LT;
return C[LI(0x1b5)](p, q, I, s, w);
},
'BllIK': function (p, q) {
var Ls = LT;
return C[Ls(0x352)](p, q);
},
'jqmtI': C[LT(0x229)],
'VRBVo': C['hVFPT'],
'qbJjB': C[LT(RL.h)],
'OoaGy': function (p, q, I, s, w) {
return p(q, I, s, w);
},
'dOPfp': function (p, q) {
return p === q;
},
'YsXRR': C['JmIJf'],
'QmRpf': function (p, q, I) {
var Lw = LT;
return C[Lw(0x2d4)](p, q, I);
},
'uWAZn': function (p, q) {
var Lz = LT;
return C[Lz(0x2b8)](p, q);
},
'mXOEa': function (p, q, I) {
return C['vtWFF'](p, q, I);
},
'sqlMq': function (p, q) {
var LO = LT;
return C[LO(0x33e)](p, q);
},
'hMwnk': function (p, q) {
var LJ = LT;
return C[LJ(0x2b8)](p, q);
},
'lbZOY': function (p, q) {
return p === q;
},
'LjgZY': C[LT(0x351)],
'Vdmrg': C['ljrRl'],
'YqBZK': function (p, q, I, s, w) {
return p(q, I, s, w);
},
'XvVud': function (p, q, I, s, w) {
var h0 = LT;
return C[h0(0x32e)](p, q, I, s, w);
},
'svANh': function (p, q, I, s, w) {
var h1 = LT;
return C[h1(0x331)](p, q, I, s, w);
},
'HLABO': function (p, q) {
var h2 = LT;
return C[h2(0x2cc)](p, q);
},
'fRDpO': function (p, q, I) {
var h3 = LT;
return C[h3(0x1fc)](p, q, I);
},
'ZWORk': function (p) {
var h4 = LT;
return C[h4(0x1f6)](p);
},
'CnNTH': function (p, q) {
return p < q;
},
'XjCEE': 'vnGmT',
'wBccl': C['Qkaka'],
'ysQKL': function (p, q) {
return p - q;
},
'SEoiV': function (p, q, I, s, w) {
var h5 = LT;
return C[h5(0x1bd)](p, q, I, s, w);
},
'Efzgp': function (p, q) {
var h6 = LT;
return C[h6(0x308)](p, q);
},
'ijFaB': function (p, q, I) {
return p(q, I);
},
'PVQkv': function (p, q) {
return p <= q;
},
'TnYHa': function (p, q, I, s, w) {
return p(q, I, s, w);
},
'uDAFK': function (p, q, I) {
return p(q, I);
},
'oahov': function (p, q, I) {
return C['TcDxS'](p, q, I);
},
'ERHkq': function (p, q, I) {
return p(q, I);
},
'hEIFf': C[LT(0x2f8)],
'YULRE': function (p, q) {
var h7 = LT;
return C[h7(0x312)](p, q);
},
'OGgZX': function (p, q, I) {
return C['jksMW'](p, q, I);
},
'qfOel': function (p, q, I) {
return p(q, I);
},
'oJfRr': function (p, q, I, s, w, z) {
return p(q, I, s, w, z);
},
'HozPp': function (p) {
var h8 = LT;
return C[h8(0x1f6)](p);
},
'BvKJu': function (p, q) {
var h9 = LT;
return C[h9(0x2fe)](p, q);
},
'pisTm': LT(0x1b2),
'JbvFu': function (p) {
return p();
},
'PHGCq': function (p, q) {
return C['WAmru'](p, q);
},
'oWFrJ': function (p) {
return C['RGcqc'](p);
},
'oxiGH': function (p, q, I, s, w, z, O, J) {
return p(q, I, s, w, z, O, J);
},
'DinAD': function (p, q) {
var hF = LT;
return C[hF(0x2cd)](p, q);
},
'YAvpt': C[LT(0x232)],
'yAZPc': C[LT(0x23c)],
'QnhQH': function (p, q, I, s, w) {
var hL = LT;
return C[hL(0x2b1)](p, q, I, s, w);
},
'hhbCq': function (p, q, I, s, w) {
return C['WHsCZ'](p, q, I, s, w);
},
'ijCbw': function (p, q, I, s, w) {
var hh = LT;
return C[hh(tD.h)](p, q, I, s, w);
},
'rtvbE': function (p, q) {
return p(q);
},
'icctW': function (p) {
return C['FjvPb'](p);
},
'iEAsQ': function (p, q, I) {
return p(q, I);
},
'wSOnE': function (p, q, I, s, w) {
var hb = LT;
return C[hb(0x21a)](p, q, I, s, w);
},
'IWYXF': function (p, q, I, s, w) {
var hC = LT;
return C[hC(0x21e)](p, q, I, s, w);
},
'Lkyuv': function (p, q, I) {
var hf = LT;
return C[hf(0x1eb)](p, q, I);
},
'twOao': function (p, q, I) {
var hv = LT;
return C[hv(ta.h)](p, q, I);
},
'QMMxd': function (p, q, I) {
var ht = LT;
return C[ht(0x209)](p, q, I);
},
'yrndN': function (p, q, I, s, w) {
return p(q, I, s, w);
},
'ShWNE': function (p, q) {
var hc = LT;
return C[hc(0x2db)](p, q);
},
'SIhNg': C[LT(0x1cb)],
'dMFQI': function (p, q, I, s, w) {
var hW = LT;
return C[hW(tu.h)](p, q, I, s, w);
},
'KAEds': function (p, q, I) {
return C['ybgJn'](p, q, I);
},
'byFQy': C['bRryP'],
'ZHwyQ': function (p, q) {
var hR = LT;
return C[hR(0x269)](p, q);
},
'Dqqnv': C[LT(RL.b)],
'uQaTs': function (p, q, I) {
return C['gnqzd'](p, q, I);
},
'udbjY': function (p, q, I, s, w) {
return C['fSmTE'](p, q, I, s, w);
},
'lGKwN': function (p, q) {
return p >>> q;
},
'Mzwla': function (p, q, I) {
return p(q, I);
},
'mhMSh': C[LT(0x26f)],
'RjPbU': function (p, q) {
return p | q;
},
'JBdED': function (p, q) {
var hS = LT;
return C[hS(0x2df)](p, q);
},
'oNTxv': function (p, q) {
var hE = LT;
return C[hE(0x342)](p, q);
},
'qyZqB': function (p, q) {
var hi = LT;
return C[hi(0x352)](p, q);
},
'JiTri': C[LT(RL.C)],
'vyrbl': function (p, q, I, s, w) {
var hN = LT;
return C[hN(tq.h)](p, q, I, s, w);
}
};
var N = []
, g = []
, x = {}
, r = []
, m = {
'_sabo_c097b': R
}
, e = {}
, B = t
, y = [];
var Y = function (I) {
var he = LT;
var w = {
'ziuYy': function (F9, FF, FL, Fh, Fb) {
return C['nwOLN'](F9, FF, FL, Fh, Fb);
},
'wBqAO': function (F9, FF, FL, Fh, Fb) {
return C['kSUUe'](F9, FF, FL, Fh, Fb);
},
'Horkt': function (F9, FF) {
var hg = L;
return C[hg(0x25c)](F9, FF);
},
'hfEPg': function (F9, FF, FL) {
var hx = L;
return C[hx(tz.h)](F9, FF, FL);
},
'dReHp': function (F9, FF, FL) {
return F9(FF, FL);
},
'SobNN': function (F9, FF) {
var hr = L;
return C[hr(0x1d2)](F9, FF);
},
'jYhqS': function (F9, FF) {
var hm = L;
return C[hm(0x30f)](F9, FF);
},
'MpRAu': C[he(cv.h)],
'svkqH': function (F9, FF) {
return F9 == FF;
},
'oRxVE': function (F9, FF) {
var hB = he;
return C[hB(0x322)](F9, FF);
},
'Zfoeu': function (F9, FF) {
var hy = he;
return C[hy(0x20f)](F9, FF);
},
'lDXhj': C[he(0x24e)],
'nDlQf': C[he(0x339)],
'VHkPt': function (F9, FF) {
var hY = he;
return C[hY(0x308)](F9, FF);
},
'odYLt': function (F9, FF) {
var hD = he;
return C[hD(c5.h)](F9, FF);
},
'PTNqV': function (F9, FF) {
return F9 == FF;
},
'nVIbO': function (F9, FF) {
return C['fAOHV'](F9, FF);
},
'qiPlA': function (F9, FF) {
var hj = he;
return C[hj(0x29c)](F9, FF);
},
'EFeRo': function (F9, FF) {
var hl = he;
return C[hl(0x1fe)](F9, FF);
},
'OMgMD': function (F9, FF) {
return F9 >> FF;
},
'SeLDj': function (F9, FF) {
var hA = he;
return C[hA(cL.h)](F9, FF);
},
'CDGuO': function (F9, FF) {
var hG = he;
return C[hG(ch.h)](F9, FF);
},
'ZMIOq': function (F9, FF) {
return F9 & FF;
},
'zNAiV': function (F9, FF) {
var ho = he;
return C[ho(0x259)](F9, FF);
}
};
if (C[he(cv.b)](C[he(0x1ef)], he(cv.C))) {
c[he(cv.f)](w[he(0x1ea)](W, R, S, E, F6));
} else {
if (!I) {
return '';
}
var z = function (FF) {
var hk = he;
var FL = []
, Fh = FF[hk(0x230)];
var Fb = 0x0;
for (var Fb = 0x0; w[hk(0x2ac)](Fb, Fh); Fb++) {
if (w[hk(0x2dd)](w['MpRAu'], w[hk(0x329)])) {
var FC = FF[hk(0x2eb)](Fb);
if (w[hk(0x2bf)](w[hk(0x278)](FC, 0x7) & 0xff, 0x0)) {
FL[hk(cf.h)](FF['charAt'](Fb));
} else {
if (w[hk(0x2a4)](w[hk(0x278)](FC, 0x5), 0xff) == 0x6) {
if (w['jYhqS'](w[hk(0x1ca)], w[hk(0x1ca)])) {
var Ff = w[hk(0x2d0)][hk(0x1b7)]('|');
var Fv = 0x0;
while (!![]) {
switch (Ff[Fv++]) {
case '0':
var Ft = w[hk(0x27c)](w[hk(0x2d3)](FC, 0x1f), 0x6);
continue;
case '1':
FL[hk(0x24d)](String[hk(0x311)](FW));
continue;
case '2':
var Fc = FF[hk(0x2eb)](++Fb);
continue;
case '3':
var FW = Ft | FR;
continue;
case '4':
var FR = w[hk(cf.b)](Fc, 0x3f);
continue;
}
break;
}
} else {
w['wBqAO'](E, w[hk(0x2fc)](w[hk(0x2a2)](F6, N, F8), w[hk(0x1c5)](x, F3, F1)), FF, B, 0x0);
return ++y;
}
} else {
if (w[hk(0x272)](w[hk(0x32d)](w['oRxVE'](FC, 0x4), 0xff), 0xe)) {
var FS = '1|2|0|5|4|3'[hk(cf.C)]('|');
var FE = 0x0;
while (!![]) {
switch (FS[FE++]) {
case '0':
var Ft = w['qiPlA'](w[hk(cf.f)](FC, 0x4), w['EFeRo'](w[hk(0x34f)](Fc, 0x2), 0xf));
continue;
case '1':
var Fc = FF['charCodeAt'](++Fb);
continue;
case '2':
var Fi = FF['charCodeAt'](++Fb);
continue;
case '3':
FL[hk(0x24d)](String[hk(cf.v)](FW));
continue;
case '4':
var FW = w[hk(cf.t)](w['VHkPt'](w[hk(cf.c)](Ft, 0xff), 0x8), FR);
continue;
case '5':
var FR = w['SeLDj'](w[hk(cf.W)](w['ZMIOq'](Fc, 0x3), 0x6), w[hk(0x2f1)](Fi, 0x3f));
continue;
}
break;
}
}
}
}
} else {
return FW[hk(0x201)] ? Fh[hk(cf.R)][FR[hk(cf.S)]] : W[hk(0x1c4)];
}
}
return FL[hk(0x332)]('');
};
var O = he(cv.v)[he(0x1b7)]('');
var J = I['length'];
var F0 = 0x0;
var F1 = [];
while (C['PHsGL'](F0, J)) {
if ('GRZrF' === C[he(0x31a)]) {
var F2 = O[he(cv.t)](I[he(cv.c)](F0++));
var F3 = O['indexOf'](I['charAt'](F0++));
var F4 = O[he(cv.t)](I[he(0x20d)](F0++));
var F5 = O[he(0x2cb)](I[he(0x20d)](F0++));
var F6 = C[he(cv.W)](F2 << 0x2, C[he(0x322)](F3, 0x4));
var F7 = C[he(cv.R)](C['RRNkR'](C[he(cv.S)](F3, 0xf), 0x4), C[he(0x279)](F4, 0x2));
var F8 = C['kzPnv'](C[he(0x259)](F4, 0x3) << 0x6, F5);
F1[he(0x24d)](String[he(cv.E)](F6));
if (F4 != 0x40) {
if (C[he(0x315)](C[he(0x2b9)], C[he(0x2b9)])) {
v[t] = c;
return ++W;
} else {
F1[he(0x24d)](String['fromCharCode'](F7));
}
}
if (C[he(cv.i)](F5, 0x40)) {
F1[he(cv.N)](String[he(0x311)](F8));
}
} else {
return b;
}
}
return C['AleBJ'](z, F1[he(cv.g)](''));
}
};
var D = function (p, q, I, s) {
var ha = LT;
var w = {
'VgmAq': function (z, O, J) {
return z(O, J);
},
'WzxBd': function (z, O, J, F0, F1) {
return z(O, J, F0, F1);
}
};
if (C['NVYmF'](C[ha(0x254)], C[ha(0x254)])) {
var O = w[ha(cW.h)](E, i, N)
, J = g(x, r);
w[ha(0x2bc)](m, J++, e, B, 0x0);
O['_sabo_c724'][O[ha(0x2a6)]] = J;
return ++y;
} else {
return {
'_sabo_e2c0a': p,
'_sabo_c724': q,
'_sabo_95cb2': I,
'_sabo_d4818': s
};
}
};
var j = function (p) {
var hd = LT;
return p['_sabo_d4818'] ? p[hd(cR.h)][p[hd(cR.b)]] : p[hd(cR.C)];
};
var l = function (p, q) {
return q['hasOwnProperty'](p) ? c : W;
};
var A = function (p, q) {
var hX = LT;
if (i[hX(0x294)](l, p, q)) {
return i[hX(0x1dd)](D, t, q, p, v);
}
var I;
if (q[hX(0x1e8)]) {
I = A(p, q[hX(0x1e8)]);
if (I) {
if (i['tRAuT'](i['JNcQS'], i[hX(0x318)])) {
return I;
} else {
return '';
}
}
}
if (q[hX(0x2c8)]) {
I = i[hX(cE.h)](A, p, q[hX(cE.b)]);
if (I) {
if (i[hX(0x236)] === i[hX(0x275)]) {
var z = R(S, E)
, O = {};
i[hX(cE.C)](i, i[hX(0x294)](N, z, O), g, x, 0x0);
return ++r;
} else {
return I;
}
}
}
return W;
};
var G = function (p) {
var hH = LT;
var q = i['GTnDt'](A, p, x);
if (q) {
return q;
}
return i[hH(0x2c5)](D, t, x, p, v);
};
var o = function () {
var hQ = LT;
var p = {
'buGvd': function (q, I, s, w, z) {
var hu = L;
return C[hu(0x1e3)](q, I, s, w, z);
},
'lOrOp': function (q, I) {
var hn = L;
return C[hn(0x308)](q, I);
}
};
if (C['DGWOU'](C[hQ(0x2fd)], C[hQ(0x1c7)])) {
p[hQ(0x2f5)](E, p[hQ(0x292)](i(N, g), x(r, m)), e, B, 0x0);
return ++y;
} else {
N = x[hQ(cx.h)] ? x['_sabo_5b836'] : r;
x = x[hQ(cx.b)] ? x[hQ(0x2c8)] : x;
B--;
}
};
var k = function (p) {
x = {
'_sabo_3088c': x,
'_sabo_6da36': p,
'_sabo_5b836': N
};
N = [];
B++;
};
var a = function () {
var hZ = LT;
y[hZ(cm.h)](C[hZ(0x1b5)](D, B, t, t, t));
};
var d = function () {
var hV = LT;
return C[hV(0x2cd)](j, y[hV(ce.h)]());
};
var X = function (p, q) {
return e[p] = q;
};
var H = function (p) {
var hM = LT;
if (i[hM(0x27a)](i[hM(0x1f3)], i[hM(cy.h)])) {
return e[p];
} else {
return i[hM(0x2ab)](v, i[hM(0x1c9)](t, c, W));
}
};
var u = [C[LT(RL.f)](D, t, t, t, t), C[LT(RL.v)](D, t, t, t, t), C[LT(RL.t)](D, t, t, t, t), C[LT(0x2a1)](D, t, t, t, t), C[LT(0x306)](D, t, t, t, t)];
var n = [E, function p(q) {
return u[q];
}
, function (q) {
var hU = LT;
if (C[hU(cD.h)]('cLzzq', C[hU(cD.b)])) {
return C[hU(0x2bd)](D, t, m[hU(0x1d8)], q, v);
} else {
return f(v, t);
}
}
, function (q) {
return G(q);
}
, function (q) {
var hK = LT;
return i[hK(0x2c5)](D, t, R, S['d'][q], v);
}
, function (q) {
var hP = LT;
var I = {
'Ofenh': function (s, w, z, O, J) {
var hT = L;
return i[hT(0x1af)](s, w, z, O, J);
},
'AuyZZ': function (s, w, z) {
return s(w, z);
},
'NIOdi': function (s, w, z) {
return s(w, z);
}
};
if (i['BllIK'](i[hP(0x22e)], i[hP(ck.h)])) {
I[hP(ck.b)](E, I['AuyZZ'](i, N, g) * I[hP(0x26b)](x, r, m), e, B, 0x0);
return ++y;
} else {
return D(m[hP(0x1c1)], t, t, t);
}
}
, function (q) {
return D(t, S['d'], q, v);
}
, function (q) {
var hp = LT;
return C[hp(0x2a1)](D, m[hp(cd.h)], E, E, t);
}
, function (q) {
var hq = LT;
if (C['DGWOU'](C[hq(0x2ba)], C[hq(0x327)])) {
return ++b;
} else {
return C[hq(cX.h)](D, t, e, q, t);
}
}
];
var Q = function (q, I) {
var hI = LT;
if (i[hI(0x27a)](i[hI(0x204)], 'ljzHB')) {
return n[q] ? n[q](I) : i[hI(0x328)](D, t, t, t, t);
} else {
b['push']([]);
}
};
var Z = function (q, I) {
var hs = LT;
if (i[hs(0x2f9)](i['YsXRR'], i[hs(cu.h)])) {
return i['kPfVf'](j, i['QmRpf'](Q, q, I));
} else {
c = {
'_sabo_3088c': W,
'_sabo_6da36': R,
'_sabo_5b836': S
};
E = [];
i++;
}
};
var V = function (q, I, s, w) {
var hw = LT;
if (C[hw(0x320)](C[hw(0x1f9)], C['wMDXH'])) {
f['push'](v[0x0]);
return ++t;
} else {
u[t] = C[hw(0x2b4)](D, q, I, s, w);
}
};
var M = function (q) {
var hO = LT;
var I = {
'xAbsm': function (O, J, F0, F1, F2) {
return O(J, F0, F1, F2);
},
'hkMoL': function (O, J) {
var hz = L;
return C[hz(0x30d)](O, J);
},
'zMwEL': function (O, J, F0) {
return O(J, F0);
},
'PlApi': function (O, J, F0) {
return O(J, F0);
}
};
if (C[hO(0x315)](C[hO(0x1e0)], C[hO(0x25d)])) {
var s = t;
while (C['SROLQ'](s, q[hO(cU.h)])) {
var w = q[s];
var z = P[w[t]];
s = C[hO(0x1be)](z, w[0x1], w[0x2], w[0x3], w[0x4], s, T, q);
}
} else {
I['xAbsm'](E, I[hO(0x1c0)](I['zMwEL'](i, N, g), I[hO(cU.b)](x, r, m)), e, B, 0x0);
return ++y;
}
};
var U = function (q, I, s, w) {
var b0 = LT;
var z = {
'boEzQ': function (F2, F3, F4) {
var hJ = L;
return C[hJ(0x282)](F2, F3, F4);
}
};
if (C['FrWRh'](C[b0(0x222)], b0(cT.h))) {
var O = j(q);
var J = j(I);
if (C[b0(0x2af)](O, 0x7fffffff)) {
if (C[b0(cT.b)](C[b0(0x28c)], C[b0(0x1de)])) {
return s;
} else {
c = z['boEzQ'](W, R, S[b0(cT.C)]);
if (E) {
return N;
}
}
}
while (C['SROLQ'](O, J)) {
if ('AsWrn' !== C[b0(0x22d)]) {
var F0 = w[O];
var F1 = P[F0[t]];
O = C[b0(cT.f)](F1, F0[0x1], F0[0x2], F0[0x3], F0[0x4], O, T, w);
} else {
return b;
}
}
return O;
} else {
var F5 = i[b0(0x2fa)](W, R, S);
E(delete F5[b0(0x1fd)][F5['_sabo_95cb2']], i, N, 0x0);
return ++g;
}
};
var K = function (q, I) {
var b3 = LT;
var s = {
'dsPCH': function (O, J) {
var b1 = L;
return i[b1(0x248)](O, J);
},
'GYyhS': function (O, J, F0) {
var b2 = L;
return i[b2(0x26c)](O, J, F0);
}
};
var w = N[b3(0x251)](i['sqlMq'](N[b3(0x230)], 0x6), 0x6);
var z = i['hMwnk'](w[0x4][b3(0x1c4)], 0x7fffffff);
try {
if (i['lbZOY'](i[b3(0x335)], i[b3(0x295)])) {
E(s['dsPCH'](s[b3(0x212)](i, N, g), x(r, m)), e, B, 0x0);
return ++y;
} else {
q = U(w[0x0], w[0x1], q, I);
}
} catch (J) {
u[0x2] = i['YqBZK'](D, J, t, t, t);
q = i['XvVud'](U, w[0x2], w[0x3], q, I);
u[0x2] = i[b3(cq.h)](D, t, t, t, t);
} finally {
q = i[b3(cq.b)](U, w[0x4], w[0x5], q, I);
}
return i[b3(0x336)](w[0x5]['_sabo_e2c0a'], q) ? w[0x5]['_sabo_e2c0a'] : q;
};
var T = C[LT(0x347)](Y, S['b'])[LT(0x1b7)]('')[LT(0x289)](function (q, I) {
var b4 = LT;
if (!q[b4(0x230)] || C['YqyTm'](q[C[b4(0x21f)](q[b4(0x230)], v)][b4(0x230)], 0x5)) {
if (C[b4(0x288)](C['dfxVY'], C['MBgMf'])) {
S = E['_sabo_5b836'] ? i[b4(cI.h)] : N;
g = x[b4(0x2c8)] ? r[b4(cI.b)] : m;
e--;
} else {
q[b4(0x24d)]([]);
}
}
q[C[b4(cI.C)](q[b4(0x230)], v)][b4(cI.f)](C[b4(0x346)](C[b4(cI.v)](-v, 0x1), I[b4(cI.t)]()));
return q;
}, []);
var P = [function (q, I, s, w, z, O, J) {
var b8 = LT;
var F0 = {
'ibqQa': function (F5, F6, F7, F8, F9) {
var b5 = L;
return i[b5(0x240)](F5, F6, F7, F8, F9);
},
'KWsvb': function (F5, F6, F7) {
var b6 = L;
return i[b6(cw.h)](F5, F6, F7);
},
'DqXoc': function (F5) {
var b7 = L;
return i[b7(0x2ca)](F5);
},
'nlBkp': function (F5, F6) {
return F5 < F6;
}
};
var F1 = Z(q, I);
if (i[b8(0x1ee)](N[b8(cJ.h)], F1)) {
if (i[b8(cJ.b)] !== i[b8(cJ.C)]) {
return ++z;
} else {
F0[b8(0x30b)](E, F0['KWsvb'](i, N, g), x, r, 0x0);
var F6 = F0['DqXoc'](m);
while (F0[b8(0x317)](F6, e)) {
Y();
}
return y;
}
}
var F2 = N[b8(0x251)](i[b8(0x309)](N[b8(0x230)], F1), F1)['map'](j)
, F3 = N[b8(0x28e)]()
, F4 = j(F3);
F2[b8(cJ.f)](null);
i[b8(0x2be)](V, new (Function[b8(cJ.v)][b8(cJ.t)][b8(0x1d5)](F4, F2))(), E, E, 0x0);
return ++z;
}
, function (q, I, s, w, z, O, J) {
var b9 = LT;
C['WqSKX'](V, Z(q, I) & C[b9(W0.h)](Z, s, w), E, E, 0x0);
return ++z;
}
, function (q, I, s, w, z, O, J) {
var bF = LT;
V(i[bF(W1.h)](i[bF(0x284)](Z, q, I), i['ijFaB'](Z, s, w)), E, E, 0x0);
return ++z;
}
, function (q, I, s, w, z, O, J) {
var bL = LT;
var F0 = C['lGvVL'](Q, q, I)
, F1 = C[bL(W2.h)](C[bL(0x282)](Z, q, I), 0x1);
F0[bL(W2.b)][F0['_sabo_95cb2']] = F1;
V(F1, E, E, 0x0);
return ++z;
}
, function (q, I, s, w, z, O, J) {
throw N['pop']();
}
, function (q, I, s, w, z, O, J) {
var bh = LT;
i[bh(0x2c5)](V, i['PVQkv'](i['QmRpf'](Z, q, I), i[bh(0x1c9)](Z, s, w)), E, E, 0x0);
return ++z;
}
, function (q, I, s, w, z, O, J) {
var bb = LT;
i[bb(0x2b7)](V, i['vsGkJ'](Z, q, I) | i[bb(W5.h)](Z, s, w), E, E, 0x0);
return ++z;
}
, function (q, I, s, w, z, O, J) {
u[0x4] = g['pop']();
return ++z;
}
, function (q, I, s, w, z, O, J) {
var bC = LT;
if (i[bC(0x270)] === bC(W7.h)) {
i[bC(0x2c5)](E, i[bC(0x261)](i, N, g) > i['ERHkq'](x, r, m), e, B, 0x0);
return ++y;
} else {
i[bC(0x2be)](V, i[bC(0x1ba)](i[bC(0x310)](Z, q, I), i[bC(W7.b)](Z, s, w)), E, E, 0x0);
return ++z;
}
}
, function (q, I, s, w, z, O, J) {
var bf = LT;
if (i['BvKJu'](i[bf(0x225)], 'jgodB')) {
V(i[bf(0x2a3)](Z, q, I), E, E, 0x0);
var F0 = i['JbvFu'](d);
while (i[bf(0x2d1)](F0, B)) {
i[bf(0x1f5)](o);
}
return Infinity;
} else {
W();
i[bf(0x33d)](R, S, E, i, 0x0, 0x0);
i['HozPp'](N);
return g;
}
}
, function (q, I, s, w, z, O, J) {
var bv = LT;
if (C['MRQGa'](C['iZTGz'], C[bv(0x290)])) {
V(C[bv(0x1cd)](Z(q, I), C[bv(0x2a9)](Z, s, w)), E, E, 0x0);
return ++z;
} else {
var F1 = R[S];
var F2 = E[F1[i]];
N = i['oxiGH'](F2, F1[0x1], F1[0x2], F1[0x3], F1[0x4], g, F1, r);
}
}
, function (q, I, s, w, z, O, J) {
return ++z;
}
, function (q, I, s, w, z, O, J) {
var WL = {
h: 0x2dc
};
var bt = LT;
var F0 = T[bt(Wh.h)](C[bt(0x285)](Z, q, I), C[bt(Wh.b)](C[bt(0x214)](Z, s, w), 0x1))
, F1 = x;
C[bt(0x2b5)](V, function () {
var bc = bt;
m = {
'_sabo_c097b': this || R,
'_sabo_57ecb': m,
'_sabo_10b1e': arguments,
'_sabo_6da36': F1
};
M(F0);
m = m[bc(0x354)];
return i[bc(WL.h)](j, u[0x0]);
}, E, E, 0x0);
return ++z;
}
, function (q, I, s, w, z, O, J) {
var bW = LT;
if (C[bW(0x22f)](C[bW(Wb.h)], C[bW(0x1e5)])) {
return f[v] = t;
} else {
C[bW(0x2b4)](V, C[bW(0x2d7)](C[bW(0x282)](Z, q, I), Z(s, w)), E, E, 0x0);
return ++z;
}
}
, function (q, I, s, w, z, O, J) {
var bR = LT;
if (i[bR(0x2e8)](i[bR(WC.h)], i[bR(0x27e)])) {
i['QnhQH'](V, ~i['GTnDt'](Z, q, I), E, E, 0x0);
return ++z;
} else {
v[0x4] = t[i[bR(WC.b)](c[bR(WC.C)], 0x1)];
return ++W;
}
}
, , function (q, I, s, w, z, O, J) {
var bS = LT;
g[bS(Wf.h)](u[0x0]);
return ++z;
}
, function (q, I, s, w, z, O, J) {
var bE = LT;
C[bE(0x1f6)](a);
C[bE(0x200)](k, m[bE(0x1e8)]);
return ++z;
}
, function (q, I, s, w, z, O, J) {
var bi = LT;
return C[bi(Wt.h)](Z, q, I);
}
, function (q, I, s, w, z, O, J) {
var bN = LT;
if (bN(Wc.h) === C[bN(Wc.b)]) {
i[bN(0x30e)](E, i[bN(0x310)](i, N, g) && x(r, m), e, B, 0x0);
return ++y;
} else {
var F0 = C[bN(0x282)](Q, q, I)
, F1 = Z(q, I);
V(F1++, E, E, 0x0);
F0[bN(0x1fd)][F0[bN(0x2a6)]] = F1;
return ++z;
}
}
, function (q, I, s, w, z, O, J) {
var bx = LT;
var F0 = {
'KjZAP': function (F1, F2, F3, F4, F5) {
var bg = L;
return C[bg(0x33b)](F1, F2, F3, F4, F5);
}
};
if (C[bx(WR.h)](C[bx(0x1b1)], bx(0x207))) {
B[0x2] = F0[bx(0x1f2)](y, Y, D, j, l);
A = F0[bx(WR.b)](G, o[0x2], k[0x3], a, d);
X[0x2] = F0[bx(0x1f2)](H, u, n, Q, Z);
} else {
C[bx(0x28f)](V, C[bx(0x301)](C[bx(0x285)](Z, q, I), Z(s, w)), E, E, 0x0);
return ++z;
}
}
, function (q, I, s, w, z, O, J) {
var br = LT;
V(typeof C[br(WS.h)](Z, q, I), E, E, 0x0);
return ++z;
}
, function (q, I, s, w, z, O, J) {
var bm = LT;
C['HlDqZ'](V, C[bm(0x1fb)](C[bm(0x285)](Z, q, I), C[bm(WE.h)](Z, s, w)), E, E, 0x0);
return ++z;
}
, function (q, I, s, w, z, O, J) {
var be = LT;
if (C[be(0x321)](C['OMccf'], C[be(Wi.h)])) {
throw b['pop']();
} else {
V(C[be(0x348)](C['WIitv'](Z, q, I), C[be(0x1d7)](Z, s, w)), E, E, 0x0);
return ++z;
}
}
, function (q, I, s, w, z, O, J) {
var bB = LT;
var F0 = C[bB(0x1c6)](Z, q, I);
if (C[bB(0x1d2)](N['length'], F0)) {
return ++z;
}
var F1 = N[bB(0x251)](C['lHsxx'](N[bB(0x230)], F0), F0)[bB(0x2c9)](j)
, F2 = N['pop']()
, F3 = C['ckdoI'](j, F2);
V(F3[bB(WN.h)](typeof F2['_sabo_c724'] == C[bB(0x203)] ? R : F2[bB(WN.b)], F1), E, E, 0x0);
return ++z;
}
, function (q, I, s, w, z, O, J) {
var by = LT;
i[by(Wg.h)](V, 0x0, i[by(0x244)](j, Q(q, I)), Z(s, w), 0x1);
return ++z;
}
, function (q, I, s, w, z, O, J) {
var bY = LT;
i[bY(0x34a)](o);
return ++z;
}
, function (q, I, s, w, z, O, J) {
var bD = LT;
var F0 = C[bD(Wr.h)](Q, q, I)
, F1 = C[bD(0x25c)](C[bD(0x1c3)](Z, q, I), 0x1);
F0[bD(0x1fd)][F0[bD(Wr.b)]] = F1;
C[bD(0x252)](V, F1, E, E, 0x0);
return ++z;
}
, function (q, I, s, w, z, O, J) {
var bj = LT;
N[bj(0x24d)](u[0x0]);
return ++z;
}
, function (q, I, s, w, z, O, J) {
var bl = LT;
C['IdiaC'](V, C['aLwwx'](C[bl(0x2f7)](Z, q, I), C[bl(0x1d6)](Z, s, w)), E, E, 0x0);
return ++z;
}
, function (q, I, s, w, z, O, J) {
var bA = LT;
i[bA(0x33c)](o);
i[bA(0x33d)](V, E, E, E, 0x0, 0x0);
i[bA(WB.h)](d);
return Infinity;
}
, function (q, I, s, w, z, O, J) {
var bG = LT;
V(C['DHUPP'](C[bG(0x34d)](Z, q, I), C[bG(0x239)](Z, s, w)), E, E, 0x0);
return ++z;
}
, function (q, I, s, w, z, O, J) {
return ++z;
}
, function (q, I, s, w, z, O, J) {
var bo = LT;
if (C[bo(0x30f)](C['uNwcj'], bo(0x2de))) {
var F1 = i[bo(0x1f4)](E, i, N)
, F2 = i[bo(WD.h)](g, x, r);
i[bo(WD.b)](m, F2--, e, B, 0x0);
F1[bo(WD.C)][F1['_sabo_95cb2']] = F2;
return ++y;
} else {
return C[bo(WD.f)](K, z, J);
}
}
, function (q, I, s, w, z, O, J) {
x[I] = undefined;
return ++z;
}
, function (q, I, s, w, z, O, J) {
var bk = LT;
C[bk(0x287)](V, C[bk(Wl.h)](C[bk(0x1d6)](Z, q, I), Z(s, w)), E, E, 0x0);
return ++z;
}
, function (q, I, s, w, z, O, J) {
var ba = LT;
V(C['qfltV'](C[ba(0x1c3)](Z, q, I), C[ba(0x285)](Z, s, w)), E, E, 0x0);
return ++z;
}
, function (q, I, s, w, z, O, J) {
var bd = LT;
i[bd(0x1b0)](V, {}, E, E, 0x0);
return ++z;
}
, function (q, I, s, w, z, O, J) {
var bX = LT;
i[bX(0x2be)](V, !i[bX(0x2e1)](Z, q, I), E, E, 0x0);
return ++z;
}
, function (q, I, s, w, z, O, J) {
var bH = LT;
var F0 = C[bH(Wk.h)](Z, q, I)
, F1 = {};
C['kSUUe'](V, C['yzhnN'](X, F0, F1), E, E, 0x0);
return ++z;
}
, function (q, I, s, w, z, O, J) {
var bu = LT;
V(C[bu(0x276)](C[bu(Wa.h)](Z, q, I), C[bu(Wa.b)](Z, s, w)), E, E, 0x0);
return ++z;
}
, function (q, I, s, w, z, O, J) {
V(i['twOao'](Z, q, I) >= i['QMMxd'](Z, s, w), E, E, 0x0);
return ++z;
}
, function (q, I, s, w, z, O, J) {
var bn = LT;
i[bn(0x246)](V, i[bn(0x21c)](Z(q, I), i[bn(WX.h)](Z, s, w)), E, E, 0x0);
return ++z;
}
, function (q, I, s, w, z, O, J) {
var bQ = LT;
if (i[bQ(WH.h)](i[bQ(0x24c)], 'grXOT')) {
i['dMFQI'](V, -i[bQ(0x2ed)](Z, q, I), E, E, 0x0);
return ++z;
} else {
return C[f];
}
}
, function (q, I, s, w, z, O, J) {
var bZ = LT;
if (i[bZ(0x1aa)](i[bZ(0x20c)], i[bZ(0x20c)])) {
return !i[bZ(Wu.h)](j, u[0x0]) ? i['mXOEa'](Z, q, I) : ++z;
} else {
return b;
}
}
, function (q, I, s, w, z, O, J) {
var bV = LT;
if (i[bV(0x2c0)]('CFtvf', i[bV(0x1d1)])) {
u[0x3] = i[bV(0x1af)](D, N[bV(0x230)], 0x0, 0x0, 0x0);
return ++z;
} else {
debugger;
return ++b;
}
}
, function (q, I, s, w, z, O, J) {
var bM = LT;
i[bM(WQ.h)](V, i[bM(0x1c9)](Z, q, I), E, E, 0x0);
return ++z;
}
, function (q, I, s, w, z, O, J) {
var bU = LT;
i[bU(WZ.h)](V, Z(q, I) && i[bU(WZ.b)](Z, s, w), E, E, 0x0);
return ++z;
}
, function (q, I, s, w, z, O, J) {
var bK = LT;
if (C[bK(WV.h)](C[bK(WV.b)], bK(0x250))) {
return ++b;
} else {
var F0 = C[bK(WV.C)](Q, q, I);
C['FJREI'](V, delete F0['_sabo_c724'][F0[bK(WV.f)]], E, E, 0x0);
return ++z;
}
}
, function (q, I, s, w, z, O, J) {
var bT = LT;
var F0 = C[bT(0x1eb)](Z, q, I);
C[bT(0x2ea)](V, N['splice'](C['ELHrH'](N[bT(0x230)], F0), F0)[bT(0x2c9)](j), E, E, 0x0);
return ++z;
}
, function (q, I, s, w, z, O, J) {
var bP = LT;
u[0x4] = g[i[bP(WU.h)](g[bP(0x230)], 0x1)];
return ++z;
}
, function (q, I, s, w, z, O, J) {
var bp = LT;
i['udbjY'](V, i[bp(0x337)](i[bp(0x34c)](Z, q, I), Z(s, w)), E, E, 0x0);
return ++z;
}
, function (q, I, s, w, z, O, J) {
var bq = LT;
var F0 = C['yMgIH'](Q, q, I)
, F1 = C['vQqZP'](Z, q, I);
C['ocRxu'](V, F1--, E, E, 0x0);
F0['_sabo_c724'][F0[bq(0x2a6)]] = F1;
return ++z;
}
, function (q, I, s, w, z, O, J) {
return ++z;
}
, function (q, I, s, w, z, O, J) {
var bI = LT;
if (C[bI(0x258)](C['ABTJb'], C[bI(0x1e2)])) {
u[0x1] = N[bI(0x28e)]();
return ++z;
} else {
return i[bI(0x2d9)](f, v, t);
}
}
, function (q, I, s, w, z, O, J) {
u[0x0] = N[C['gPDFl'](N['length'], 0x1)];
return ++z;
}
, function (q, I, s, w, z, O, J) {
return f;
}
, function (q, I, s, w, z, O, J) {
var bs = LT;
V(i[bs(0x1bf)](Z, q, I) || Z(s, w), E, E, 0x0);
return ++z;
}
, function (q, I, s, w, z, O, J) {
var bw = LT;
V(+C[bw(0x343)](Z, q, I), E, E, 0x0);
return ++z;
}
, function (q, I, s, w, z, O, J) {
var bz = LT;
C['gQkLB'](V, C[bz(Wz.h)](C[bz(0x268)](Z, q, I), C[bz(Wz.b)](Z, s, w)), E, E, 0x0);
return ++z;
}
, function (q, I, s, w, z, O, J) {
var bO = LT;
V(C[bO(0x231)](C['RRAzt'](Z, q, I), C[bO(WO.h)](Z, s, w)), E, E, 0x0);
return ++z;
}
, function (q, I, s, w, z, O, J) {
var bJ = LT;
var F0 = {
'xRcPH': i[bJ(R5.h)],
'TYkNO': function (F1, F2) {
return i['RjPbU'](F1, F2);
},
'bZSXv': function (F1, F2) {
return F1 << F2;
},
'bOaOU': function (F1, F2) {
return F1 << F2;
},
'vfvwF': function (F1, F2) {
var C0 = bJ;
return i[C0(0x2ec)](F1, F2);
},
'EUTZE': function (F1, F2) {
return F1 != F2;
},
'UGTAB': function (F1, F2) {
var C1 = bJ;
return i[C1(0x271)](F1, F2);
}
};
if (i[bJ(0x2b3)](bJ(0x24b), i[bJ(R5.b)])) {
return i['kPfVf'](j, u[0x0]) ? i[bJ(0x284)](Z, q, I) : ++z;
} else {
var F2 = F0[bJ(0x2f0)][bJ(0x1b7)]('|');
var F3 = 0x0;
while (!![]) {
switch (F2[F3++]) {
case '0':
var F4 = k[bJ(R5.C)](a[bJ(0x20d)](d++));
continue;
case '1':
var F5 = D[bJ(0x2cb)](j['charAt'](l++));
continue;
case '2':
var F6 = A['indexOf'](G[bJ(0x20d)](F4++));
continue;
case '3':
X[bJ(0x24d)](H[bJ(R5.f)](F8));
continue;
case '4':
var F7 = B[bJ(0x2cb)](y[bJ(R5.v)](Y++));
continue;
case '5':
var F8 = F0['TYkNO'](F0[bJ(0x345)](F7, 0x2), F5 >> 0x4);
continue;
case '6':
var F9 = F0[bJ(R5.t)](F0[bJ(0x2e3)](F0['vfvwF'](F6, 0x3), 0x6), F4);
continue;
case '7':
if (F0['EUTZE'](F4, 0x40)) {
U['push'](K['fromCharCode'](F9));
}
continue;
case '8':
if (F0[bJ(0x20b)](F6, 0x40)) {
V[bJ(0x24d)](M[bJ(0x311)](FF));
}
continue;
case '9':
var FF = F0[bJ(0x2e3)](F0[bJ(0x2f4)](F5, 0xf), 0x4) | F0[bJ(0x33f)](F6, 0x2);
continue;
}
break;
}
}
}
, function (q, I, s, w, z, O, J) {
var C2 = LT;
var F0 = Z(q, I);
i[C2(R6.h)](V, i[C2(0x244)](H, F0), E, E, 0x0);
return ++z;
}
, function (q, I, s, w, z, O, J) {
debugger;
return ++z;
}
, function (q, I, s, w, z, O, J) {
var C3 = LT;
i['dMFQI'](V, i[C3(0x2ed)](Z, q, I) < Z(s, w), E, E, 0x0);
return ++z;
}
, function (q, I, s, w, z, O, J) {
var C4 = LT;
var F0 = Q(q, I)
, F1 = Z(s, w);
C[C4(0x2ef)](V, F0[C4(0x1fd)][F0[C4(R9.h)]] = F1, E, E, 0x0);
return ++z;
}
, function (q, I, s, w, z, O, J) {
var C5 = LT;
i[C5(RF.h)](k, null);
return ++z;
}
];
return C[LT(0x2cd)](M, T);
}
;
}
;b()(window, {
'b': h[FQ(Rb.e)],
'd': ['Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P', 'A', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L', 'Z', 'X', 'C', 'V', 'B', 'N', 'M', 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', 'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', 'z', 'x', 'c', 'v', 'b', 'n', 'm', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '$', '_', '[', ']', 0x4f, 0x6e2, 0x0, 0x6e3, 0x722, 0x723, 0x869, 0x953, 0xa2c, 0xa2d, 0xa39, 0xa3a, 0xa76, 0xa77, 0x14d5, 0x18bf, 0x1c17, 0x1c18, 0x1c90, 0x1c91, 0x1d08, 0x1d09, 0x1da2, 0x1da3, 0x1e27, 0x1e28, 0x1ed7, 0x1ed8, 0x1f86, 0x1f87, 0x1fe8, 0x1fe9, 0x2098, 0x2099, 0x20fe, 0x20ff, 0x21d5, 0x21d6, 0x221d, 0x221e, 0x228f, 0x2290, 0x22fd, 0x22fe, 0x23b2, 0x23b3, 0x2404, 0x2405, 0x2441, 0x2442, 0x248b, 0x248c, 0x2511, 0x2512, 0x2a3d, 0x2a3e, 0x2a7b, 0x2a7c, 0x2a9d, 0x2a9e, 0x2adb, 0x3287, 0x331f, 0x3320, 0x33ef, h[FQ(0x277)], 0x1, '', 0x2, ![], 0x341f, 0x3487, 0x3488, 0x34f0, 0x34f1, 0x35ae, null, 0x40, 0x9, 0x86a, 0x952, '+', '/', '=', 0x135, 0x7c, 0x93, 0xaa, 0xd1, 0x3, 0x4, 0xf, 0x6, 0x3f, FQ(Rb.B), 0xf4, 0xff, 0x6f, 0xd7, 0x43, 0x4b, 0x80, 0x64, 0xd3, 0x7f, 0x67, 0x800, 0x94, 0xc0, 0xc, 0xe0, 0x17, 0x1d, 0x59, 0x56, 0x37, 0x55, 0x7fffffff, 0x9c, 0x95, h[FQ(0x217)], '\x20', ':', h[FQ(0x307)], 0xac, 0xb6, '-', 0x15, 0x39, 0x2c, 0x2f, 0x35, 0x19, !![], 0x14d6, 0x14e1, 0x14e2, 0x1539, 0x153a, 0x1573, 0x1574, 0x1771, 0x1772, 0x17c4, 0x17c5, 0x17cc, 0x63, 0x77, 0x7b, 0xf2, 0x6b, 0xc5, 0x30, 0x2b, 0xfe, 0xab, 0x76, 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0xa4, 0x72, 0xb7, 0xfd, 0x26, 0x36, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x5, 0x9a, 0x7, 0x12, 0xe2, 0xeb, 0x27, 0xb2, 0x75, 0x83, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x84, 0x53, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x4a, 0x4c, 0x58, 0xcf, 0xd0, 0xef, 0xfb, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x50, 0x3c, 0x9f, 0xa8, 0x51, 0xa3, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xda, 0x21, 0x10, 0xf3, 0xd2, 0xcd, 0x13, 0xec, 0x5f, 0x97, 0x44, 0xc4, 0xa7, 0x7e, 0x3d, 0x5d, 0x73, 0x60, 0x81, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0xb, 0xdb, 0x32, 0x3a, 0xa, 0x49, 0x24, 0x5c, 0xc2, 0x62, 0x91, 0xe4, 0x79, 0xe7, 0xc8, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0xea, 0x65, 0x7a, 0xae, 0x8, 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0xbd, 0x8b, 0x8a, 0x70, 0x3e, 0xb5, 0x66, 0x48, 0xf6, 0xe, 0x61, 0x57, 0xb9, 0x86, 0xc1, 0x9e, 0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x28, 0xdf, 0x8c, 0xa1, 0x89, 0xd, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0xb0, 0x54, 0xbb, 0x16, 0x100, 0xc66363a5, 0xf87c7c84, 0xee777799, 0xf67b7b8d, 0xfff2f20d, 0xd66b6bbd, 0xde6f6fb1, 0x91c5c554, 0x60303050, 0x2010103, 0xce6767a9, 0x562b2b7d, 0xe7fefe19, 0xb5d7d762, 0x4dababe6, 0xec76769a, 0x8fcaca45, 0x1f82829d, 0x89c9c940, 0xfa7d7d87, 0xeffafa15, 0xb25959eb, 0x8e4747c9, 0xfbf0f00b, 0x41adadec, 0xb3d4d467, 0x5fa2a2fd, 0x45afafea, 0x239c9cbf, 0x53a4a4f7, 0xe4727296, 0x9bc0c05b, 0x75b7b7c2, 0xe1fdfd1c, 0x3d9393ae, 0x4c26266a, 0x6c36365a, 0x7e3f3f41, 0xf5f7f702, 0x83cccc4f, 0x6834345c, 0x51a5a5f4, 0xd1e5e534, 0xf9f1f108, 0xe2717193, 0xabd8d873, 0x62313153, 0x2a15153f, 0x804040c, 0x95c7c752, 0x46232365, 0x9dc3c35e, 0x30181828, 0x379696a1, 0xa05050f, 0x2f9a9ab5, 0xe070709, 0x24121236, 0x1b80809b, 0xdfe2e23d, 0xcdebeb26, 0x4e272769, 0x7fb2b2cd, 0xea75759f, 0x1209091b, 0x1d83839e, 0x582c2c74, 0x341a1a2e, 0x361b1b2d, 0xdc6e6eb2, 0xb45a5aee, 0x5ba0a0fb, 0xa45252f6, 0x763b3b4d, 0xb7d6d661, 0x7db3b3ce, 0x5229297b, 0xdde3e33e, 0x5e2f2f71, 0x13848497, 0xa65353f5, 0xb9d1d168, 0xc1eded2c, 0x40202060, 0xe3fcfc1f, 0x79b1b1c8, 0xb65b5bed, 0xd46a6abe, 0x8dcbcb46, 0x67bebed9, 0x7239394b, 0x944a4ade, 0x984c4cd4, 0xb05858e8, 0x85cfcf4a, 0xbbd0d06b, 0xc5efef2a, 0x4faaaae5, 0xedfbfb16, 0x864343c5, 0x9a4d4dd7, 0x66333355, 0x11858594, 0x8a4545cf, 0xe9f9f910, 0x4020206, 0xfe7f7f81, 0xa05050f0, 0x783c3c44, 0x259f9fba, 0x4ba8a8e3, 0xa25151f3, 0x5da3a3fe, 0x804040c0, 0x58f8f8a, 0x3f9292ad, 0x219d9dbc, 0x70383848, 0xf1f5f504, 0x63bcbcdf, 0x77b6b6c1, 0xafdada75, 0x42212163, 0x20101030, 0xe5ffff1a, 0xfdf3f30e, 0xbfd2d26d, 0x81cdcd4c, 0x180c0c14, 0x26131335, 0xc3ecec2f, 0xbe5f5fe1, 0x359797a2, 0x884444cc, 0x2e171739, 0x93c4c457, 0x55a7a7f2, 0xfc7e7e82, 0x7a3d3d47, 0xc86464ac, 0xba5d5de7, 0x3219192b, 0xe6737395, 0xc06060a0, 0x19818198, 0x9e4f4fd1, 0xa3dcdc7f, 0x44222266, 0x542a2a7e, 0x3b9090ab, 0xb888883, 0x8c4646ca, 0xc7eeee29, 0x6bb8b8d3, 0x2814143c, 0xa7dede79, 0xbc5e5ee2, 0x160b0b1d, 0xaddbdb76, 0xdbe0e03b, 0x64323256, 0x743a3a4e, 0x140a0a1e, 0x924949db, 0xc06060a, 0x4824246c, 0xb85c5ce4, 0x9fc2c25d, 0xbdd3d36e, 0x43acacef, 0xc46262a6, 0x399191a8, 0x319595a4, 0xd3e4e437, 0xf279798b, 0xd5e7e732, 0x8bc8c843, 0x6e373759, 0xda6d6db7, 0x18d8d8c, 0xb1d5d564, 0x9c4e4ed2, 0x49a9a9e0, 0xd86c6cb4, 0xac5656fa, 0xf3f4f407, 0xcfeaea25, 0xca6565af, 0xf47a7a8e, 0x47aeaee9, 0x10080818, 0x6fbabad5, 0xf0787888, 0x4a25256f, 0x5c2e2e72, 0x381c1c24, 0x57a6a6f1, 0x73b4b4c7, 0x97c6c651, 0xcbe8e823, 0xa1dddd7c, 0xe874749c, 0x3e1f1f21, 0x964b4bdd, 0x61bdbddc, 0xd8b8b86, 0xf8a8a85, 0xe0707090, 0x7c3e3e42, 0x71b5b5c4, 0xcc6666aa, 0x904848d8, 0x6030305, 0xf7f6f601, 0x1c0e0e12, 0xc26161a3, 0x6a35355f, 0xae5757f9, 0x69b9b9d0, 0x17868691, 0x99c1c158, 0x3a1d1d27, 0x279e9eb9, 0xd9e1e138, 0xebf8f813, 0x2b9898b3, 0x22111133, 0xd26969bb, 0xa9d9d970, 0x78e8e89, 0x339494a7, 0x2d9b9bb6, 0x3c1e1e22, 0x15878792, 0xc9e9e920, 0x87cece49, 0xaa5555ff, 0x50282878, 0xa5dfdf7a, 0x38c8c8f, 0x59a1a1f8, 0x9898980, 0x1a0d0d17, 0x65bfbfda, 0xd7e6e631, 0x844242c6, 0xd06868b8, 0x824141c3, 0x299999b0, 0x5a2d2d77, 0x1e0f0f11, 0x7bb0b0cb, 0xa85454fc, 0x6dbbbbd6, 0x2c16163a, 0xa5c66363, 0x84f87c7c, 0x99ee7777, 0x8df67b7b, 0xdfff2f2, 0xbdd66b6b, 0xb1de6f6f, 0x5491c5c5, 0x50603030, 0x3020101, 0xa9ce6767, 0x7d562b2b, 0x19e7fefe, 0x62b5d7d7, 0xe64dabab, 0x9aec7676, 0x458fcaca, 0x9d1f8282, 0x4089c9c9, 0x87fa7d7d, 0x15effafa, 0xebb25959, 0xc98e4747, 0xbfbf0f0, 0xec41adad, 0x67b3d4d4, 0xfd5fa2a2, 0xea45afaf, 0xbf239c9c, 0xf753a4a4, 0x96e47272, 0x5b9bc0c0, 0xc275b7b7, 0x1ce1fdfd, 0xae3d9393, 0x6a4c2626, 0x5a6c3636, 0x417e3f3f, 0x2f5f7f7, 0x4f83cccc, 0x5c683434, 0xf451a5a5, 0x34d1e5e5, 0x8f9f1f1, 0x93e27171, 0x73abd8d8, 0x53623131, 0x3f2a1515, 0xc080404, 0x5295c7c7, 0x65462323, 0x5e9dc3c3, 0x28301818, 0xa1379696, 0xf0a0505, 0xb52f9a9a, 0x90e0707, 0x36241212, 0x9b1b8080, 0x3ddfe2e2, 0x26cdebeb, 0x694e2727, 0xcd7fb2b2, 0x9fea7575, 0x1b120909, 0x9e1d8383, 0x74582c2c, 0x2e341a1a, 0x2d361b1b, 0xb2dc6e6e, 0xeeb45a5a, 0xfb5ba0a0, 0xf6a45252, 0x4d763b3b, 0x61b7d6d6, 0xce7db3b3, 0x7b522929, 0x3edde3e3, 0x715e2f2f, 0x97138484, 0xf5a65353, 0x68b9d1d1, 0x2cc1eded, 0x60402020, 0x1fe3fcfc, 0xc879b1b1, 0xedb65b5b, 0xbed46a6a, 0x468dcbcb, 0xd967bebe, 0x4b723939, 0xde944a4a, 0xd4984c4c, 0xe8b05858, 0x4a85cfcf, 0x6bbbd0d0, 0x2ac5efef, 0xe54faaaa, 0x16edfbfb, 0xc5864343, 0xd79a4d4d, 0x55663333, 0x94118585, 0xcf8a4545, 0x10e9f9f9, 0x6040202, 0x81fe7f7f, 0xf0a05050, 0x44783c3c, 0xba259f9f, 0xe34ba8a8, 0xf3a25151, 0xfe5da3a3, 0xc0804040, 0x8a058f8f, 0xad3f9292, 0xbc219d9d, 0x48703838, 0x4f1f5f5, 0xdf63bcbc, 0xc177b6b6, 0x75afdada, 0x63422121, 0x30201010, 0x1ae5ffff, 0xefdf3f3, 0x6dbfd2d2, 0x4c81cdcd, 0x14180c0c, 0x35261313, 0x2fc3ecec, 0xe1be5f5f, 0xa2359797, 0xcc884444, 0x392e1717, 0x5793c4c4, 0xf255a7a7, 0x82fc7e7e, 0x477a3d3d, 0xacc86464, 0xe7ba5d5d, 0x2b321919, 0x95e67373, 0xa0c06060, 0x98198181, 0xd19e4f4f, 0x7fa3dcdc, 0x66442222, 0x7e542a2a, 0xab3b9090, 0x830b8888, 0xca8c4646, 0x29c7eeee, 0xd36bb8b8, 0x3c281414, 0x79a7dede, 0xe2bc5e5e, 0x1d160b0b, 0x76addbdb, 0x3bdbe0e0, 0x56643232, 0x4e743a3a, 0x1e140a0a, 0xdb924949, 0xa0c0606, 0x6c482424, 0xe4b85c5c, 0x5d9fc2c2, 0x6ebdd3d3, 0xef43acac, 0xa6c46262, 0xa8399191, 0xa4319595, 0x37d3e4e4, 0x8bf27979, 0x32d5e7e7, 0x438bc8c8, 0x596e3737, 0xb7da6d6d, 0x8c018d8d, 0x64b1d5d5, 0xd29c4e4e, 0xe049a9a9, 0xb4d86c6c, 0xfaac5656, 0x7f3f4f4, 0x25cfeaea, 0xafca6565, 0x8ef47a7a, 0xe947aeae, 0x18100808, 0xd56fbaba, 0x88f07878, 0x6f4a2525, 0x725c2e2e, 0x24381c1c, 0xf157a6a6, 0xc773b4b4, 0x5197c6c6, 0x23cbe8e8, 0x7ca1dddd, 0x9ce87474, 0x213e1f1f, 0xdd964b4b, 0xdc61bdbd, 0x860d8b8b, 0x850f8a8a, 0x90e07070, 0x427c3e3e, 0xc471b5b5, 0xaacc6666, 0xd8904848, 0x5060303, 0x1f7f6f6, 0x121c0e0e, 0xa3c26161, 0x5f6a3535, 0xf9ae5757, 0xd069b9b9, 0x91178686, 0x5899c1c1, 0x273a1d1d, 0xb9279e9e, 0x38d9e1e1, 0x13ebf8f8, 0xb32b9898, 0x33221111, 0xbbd26969, 0x70a9d9d9, 0x89078e8e, 0xa7339494, 0xb62d9b9b, 0x223c1e1e, 0x92158787, 0x20c9e9e9, 0x4987cece, 0xffaa5555, 0x78502828, 0x7aa5dfdf, 0x8f038c8c, 0xf859a1a1, 0x80098989, 0x171a0d0d, 0xda65bfbf, 0x31d7e6e6, 0xc6844242, 0xb8d06868, 0xc3824141, 0xb0299999, 0x775a2d2d, 0x111e0f0f, 0xcb7bb0b0, 0xfca85454, 0xd66dbbbb, 0x3a2c1616, 0x63a5c663, 0x7c84f87c, 0x7799ee77, 0x7b8df67b, 0xf20dfff2, 0x6bbdd66b, 0x6fb1de6f, 0xc55491c5, 0x30506030, 0x1030201, 0x67a9ce67, 0x2b7d562b, 0xfe19e7fe, 0xd762b5d7, 0xabe64dab, 0x769aec76, 0xca458fca, 0x829d1f82, 0xc94089c9, 0x7d87fa7d, 0xfa15effa, 0x59ebb259, 0x47c98e47, 0xf00bfbf0, 0xadec41ad, 0xd467b3d4, 0xa2fd5fa2, 0xafea45af, 0x9cbf239c, 0xa4f753a4, 0x7296e472, 0xc05b9bc0, 0xb7c275b7, 0xfd1ce1fd, 0x93ae3d93, 0x266a4c26, 0x365a6c36, 0x3f417e3f, 0xf702f5f7, 0xcc4f83cc, 0x345c6834, 0xa5f451a5, 0xe534d1e5, 0xf108f9f1, 0x7193e271, 0xd873abd8, 0x31536231, 0x153f2a15, 0x40c0804, 0xc75295c7, 0x23654623, 0xc35e9dc3, 0x18283018, 0x96a13796, 0x50f0a05, 0x9ab52f9a, 0x7090e07, 0x12362412, 0x809b1b80, 0xe23ddfe2, 0xeb26cdeb, 0x27694e27, 0xb2cd7fb2, 0x759fea75, 0x91b1209, 0x839e1d83, 0x2c74582c, 0x1a2e341a, 0x1b2d361b, 0x6eb2dc6e, 0x5aeeb45a, 0xa0fb5ba0, 0x52f6a452, 0x3b4d763b, 0xd661b7d6, 0xb3ce7db3, 0x297b5229, 0xe33edde3, 0x2f715e2f, 0x84971384, 0x53f5a653, 0xd168b9d1, 0xed2cc1ed, 0x20604020, 0xfc1fe3fc, 0xb1c879b1, 0x5bedb65b, 0x6abed46a, 0xcb468dcb, 0xbed967be, 0x394b7239, 0x4ade944a, 0x4cd4984c, 0x58e8b058, 0xcf4a85cf, 0xd06bbbd0, 0xef2ac5ef, 0xaae54faa, 0xfb16edfb, 0x43c58643, 0x4dd79a4d, 0x33556633, 0x85941185, 0x45cf8a45, 0xf910e9f9, 0x2060402, 0x7f81fe7f, 0x50f0a050, 0x3c44783c, 0x9fba259f, 0xa8e34ba8, 0x51f3a251, 0xa3fe5da3, 0x40c08040, 0x8f8a058f, 0x92ad3f92, 0x9dbc219d, 0x38487038, 0xf504f1f5, 0xbcdf63bc, 0xb6c177b6, 0xda75afda, 0x21634221, 0x10302010, 0xff1ae5ff, 0xf30efdf3, 0xd26dbfd2, 0xcd4c81cd, 0xc14180c, 0x13352613, 0xec2fc3ec, 0x5fe1be5f, 0x97a23597, 0x44cc8844, 0x17392e17, 0xc45793c4, 0xa7f255a7, 0x7e82fc7e, 0x3d477a3d, 0x64acc864, 0x5de7ba5d, 0x192b3219, 0x7395e673, 0x60a0c060, 0x81981981, 0x4fd19e4f, 0xdc7fa3dc, 0x22664422, 0x2a7e542a, 0x90ab3b90, 0x88830b88, 0x46ca8c46, 0xee29c7ee, 0xb8d36bb8, 0x143c2814, 0xde79a7de, 0x5ee2bc5e, 0xb1d160b, 0xdb76addb, 0xe03bdbe0, 0x32566432, 0x3a4e743a, 0xa1e140a, 0x49db9249, 0x60a0c06, 0x246c4824, 0x5ce4b85c, 0xc25d9fc2, 0xd36ebdd3, 0xacef43ac, 0x62a6c462, 0x91a83991, 0x95a43195, 0xe437d3e4, 0x798bf279, 0xe732d5e7, 0xc8438bc8, 0x37596e37, 0x6db7da6d, 0x8d8c018d, 0xd564b1d5, 0x4ed29c4e, 0xa9e049a9, 0x6cb4d86c, 0x56faac56, 0xf407f3f4, 0xea25cfea, 0x65afca65, 0x7a8ef47a, 0xaee947ae, 0x8181008, 0xbad56fba, 0x7888f078, 0x256f4a25, 0x2e725c2e, 0x1c24381c, 0xa6f157a6, 0xb4c773b4, 0xc65197c6, 0xe823cbe8, 0xdd7ca1dd, 0x749ce874, 0x1f213e1f, 0x4bdd964b, 0xbddc61bd, 0x8b860d8b, 0x8a850f8a, 0x7090e070, 0x3e427c3e, 0xb5c471b5, 0x66aacc66, 0x48d89048, 0x3050603, 0xf601f7f6, 0xe121c0e, 0x61a3c261, 0x355f6a35, 0x57f9ae57, 0xb9d069b9, 0x86911786, 0xc15899c1, 0x1d273a1d, 0x9eb9279e, 0xe138d9e1, 0xf813ebf8, 0x98b32b98, 0x11332211, 0x69bbd269, 0xd970a9d9, 0x8e89078e, 0x94a73394, 0x9bb62d9b, 0x1e223c1e, 0x87921587, 0xe920c9e9, 0xce4987ce, 0x55ffaa55, 0x28785028, 0xdf7aa5df, 0x8c8f038c, 0xa1f859a1, 0x89800989, 0xd171a0d, 0xbfda65bf, 0xe631d7e6, 0x42c68442, 0x68b8d068, 0x41c38241, 0x99b02999, 0x2d775a2d, 0xf111e0f, 0xb0cb7bb0, 0x54fca854, 0xbbd66dbb, 0x163a2c16, 0x6363a5c6, 0x7c7c84f8, 0x777799ee, 0x7b7b8df6, 0xf2f20dff, 0x6b6bbdd6, 0x6f6fb1de, 0xc5c55491, 0x30305060, 0x1010302, 0x6767a9ce, 0x2b2b7d56, 0xfefe19e7, 0xd7d762b5, 0xababe64d, 0x76769aec, 0xcaca458f, 0x82829d1f, 0xc9c94089, 0x7d7d87fa, 0xfafa15ef, 0x5959ebb2, 0x4747c98e, 0xf0f00bfb, 0xadadec41, 0xd4d467b3, 0xa2a2fd5f, 0xafafea45, 0x9c9cbf23, 0xa4a4f753, 0x727296e4, 0xc0c05b9b, 0xb7b7c275, 0xfdfd1ce1, 0x9393ae3d, 0x26266a4c, 0x36365a6c, 0x3f3f417e, 0xf7f702f5, 0xcccc4f83, 0x34345c68, 0xa5a5f451, 0xe5e534d1, 0xf1f108f9, 0x717193e2, 0xd8d873ab, 0x31315362, 0x15153f2a, 0x4040c08, 0xc7c75295, 0x23236546, 0xc3c35e9d, 0x18182830, 0x9696a137, 0x5050f0a, 0x9a9ab52f, 0x707090e, 0x12123624, 0x80809b1b, 0xe2e23ddf, 0xebeb26cd, 0x2727694e, 0xb2b2cd7f, 0x75759fea, 0x9091b12, 0x83839e1d, 0x2c2c7458, 0x1a1a2e34, 0x1b1b2d36, 0x6e6eb2dc, 0x5a5aeeb4, 0xa0a0fb5b, 0x5252f6a4, 0x3b3b4d76, 0xd6d661b7, 0xb3b3ce7d, 0x29297b52, 0xe3e33edd, 0x2f2f715e, 0x84849713, 0x5353f5a6, 0xd1d168b9, 0xeded2cc1, 0x20206040, 0xfcfc1fe3, 0xb1b1c879, 0x5b5bedb6, 0x6a6abed4, 0xcbcb468d, 0xbebed967, 0x39394b72, 0x4a4ade94, 0x4c4cd498, 0x5858e8b0, 0xcfcf4a85, 0xd0d06bbb, 0xefef2ac5, 0xaaaae54f, 0xfbfb16ed, 0x4343c586, 0x4d4dd79a, 0x33335566, 0x85859411, 0x4545cf8a, 0xf9f910e9, 0x2020604, 0x7f7f81fe, 0x5050f0a0, 0x3c3c4478, 0x9f9fba25, 0xa8a8e34b, 0x5151f3a2, 0xa3a3fe5d, 0x4040c080, 0x8f8f8a05, 0x9292ad3f, 0x9d9dbc21, 0x38384870, 0xf5f504f1, 0xbcbcdf63, 0xb6b6c177, 0xdada75af, 0x21216342, 0x10103020, 0xffff1ae5, 0xf3f30efd, 0xd2d26dbf, 0xcdcd4c81, 0xc0c1418, 0x13133526, 0xecec2fc3, 0x5f5fe1be, 0x9797a235, 0x4444cc88, 0x1717392e, 0xc4c45793, 0xa7a7f255, 0x7e7e82fc, 0x3d3d477a, 0x6464acc8, 0x5d5de7ba, 0x19192b32, 0x737395e6, 0x6060a0c0, 0x81819819, 0x4f4fd19e, 0xdcdc7fa3, 0x22226644, 0x2a2a7e54, 0x9090ab3b, 0x8888830b, 0x4646ca8c, 0xeeee29c7, 0xb8b8d36b, 0x14143c28, 0xdede79a7, 0x5e5ee2bc, 0xb0b1d16, 0xdbdb76ad, 0xe0e03bdb, 0x32325664, 0x3a3a4e74, 0xa0a1e14, 0x4949db92, 0x6060a0c, 0x24246c48, 0x5c5ce4b8, 0xc2c25d9f, 0xd3d36ebd, 0xacacef43, 0x6262a6c4, 0x9191a839, 0x9595a431, 0xe4e437d3, 0x79798bf2, 0xe7e732d5, 0xc8c8438b, 0x3737596e, 0x6d6db7da, 0x8d8d8c01, 0xd5d564b1, 0x4e4ed29c, 0xa9a9e049, 0x6c6cb4d8, 0x5656faac, 0xf4f407f3, 0xeaea25cf, 0x6565afca, 0x7a7a8ef4, 0xaeaee947, 0x8081810, 0xbabad56f, 0x787888f0, 0x25256f4a, 0x2e2e725c, 0x1c1c2438, 0xa6a6f157, 0xb4b4c773, 0xc6c65197, 0xe8e823cb, 0xdddd7ca1, 0x74749ce8, 0x1f1f213e, 0x4b4bdd96, 0xbdbddc61, 0x8b8b860d, 0x8a8a850f, 0x707090e0, 0x3e3e427c, 0xb5b5c471, 0x6666aacc, 0x4848d890, 0x3030506, 0xf6f601f7, 0xe0e121c, 0x6161a3c2, 0x35355f6a, 0x5757f9ae, 0xb9b9d069, 0x86869117, 0xc1c15899, 0x1d1d273a, 0x9e9eb927, 0xe1e138d9, 0xf8f813eb, 0x9898b32b, 0x11113322, 0x6969bbd2, 0xd9d970a9, 0x8e8e8907, 0x9494a733, 0x9b9bb62d, 0x1e1e223c, 0x87879215, 0xe9e920c9, 0xcece4987, 0x5555ffaa, 0x28287850, 0xdfdf7aa5, 0x8c8c8f03, 0xa1a1f859, 0x89898009, 0xd0d171a, 0xbfbfda65, 0xe6e631d7, 0x4242c684, 0x6868b8d0, 0x4141c382, 0x9999b029, 0x2d2d775a, 0xf0f111e, 0xb0b0cb7b, 0x5454fca8, 0xbbbbd66d, 0x16163a2c, 0x1886, 0x18be, 0x5c7389a, 0x7a15a75b, 0x63a31db5, 0x18ef9a44, 0x37f40bae, 0x569067cf, 0x60f11eff, 0x57c12f89, 0x4de1acf5, 0x1b71cb3a, 0x7b80d5c5, 0x2c41fa4c, 0x7c2d582f, 0x5d64804d, 0x18b06e2c, 0x2ddce040, 0x39e517e, 0x565c35fb, 0x2fdfab32, 0x5c413cd5, 0x5fdf6dab, 0x9835852, 0x265cf364, 0x4c5c4621, 0x7d818408, 0x21a0e53e, 0x5201db5e, 0x67be1727, 0x51bfeedd, 0x583f0105, 0x288bc1d1, 0x1e21f31e, 0x2b35f452, 0x3f811624, 0x79342f0c, 0x1d340dac, 0x3601f9fa, 0x980efda, 0x70b4c0d6, 0x3dbe72fe, 0xf2fff90, 0x75da9be3, 0x76fe3e37, 0x35337a7c, 0x2e42b142, 0x55c26485, 0x79839ec9, 0x32a45bae, 0x53c037cf, 0x66a14eff, 0x51917f89, 0x4e890381, 0x1d493450, 0x7be87ab1, 0x2a79053a, 0xea4b784, 0x13ed83cc, 0x6805f97d, 0x427cfc45, 0x1614d9b0, 0x5f95a64, 0x6dfca31f, 0x2f805f5a, 0x34249845, 0x31ddc227, 0x5c21613a, 0x73a13e64, 0x76bfe2e0, 0x29608f4b, 0x20e3d71b, 0x6bf2479, 0x4895834c, 0x3514074c, 0x14b4e272, 0x46b53930, 0x68873e04, 0x7f6ee5c0, 0x4864e91f, 0x79d1c1b9, 0x4dff68b2, 0x32918d72, 0x7af5646d, 0x324a5d6, 0x3e89548f, 0x17e9dbc4, 0x370a0cdf, 0x31b528a8, 0x13c, 0x126, 0x19f, 0x192, 0x187, 0x189, 0x148, 0x140, 0x1fa, 0x1ad, 0x17cd, 0x1885, 0x335, 0x334, 0x357, 0x15c, 0x158, 0x143, 0x144, 0x157, '|', 0x125, 0x1f6, ';', 0x1ce, 0x288, '{', '}', h['ZkQes'], '.', '\x22', '\x27', ',', 0x190, '*', 0.02, '!', 0x526, 0x525, 0x528, 0x114, 0x132, 0x15a, 0x180, 0x1a5, 0x1d2, 0x1fb, 0x222, 0x247, 0x26b, 0x292, 0x2b5, 0x2db, 0x301, 0x31c, 0x331, 0x35e, 0x364, 0x391, 0x3bf, 0x3ea, '\x5c', 0x406, 0x42b, 0x403, 0x487, 0x460, 0x468, 0x47b, 0x483, 0x441, 0x497, 0x4cd, 0x4f4, 0x51f, 0x2adc, 0x2afd, 0x2afe, 0x2b0c, 0x2b0d, 0x2b3c, 0x2b3d, 0x2b63, 0x2b64, 0x2b8a, 0x2b8b, 0x2bad, 0x2bae, 0x2bd1, 0x2bd2, 0x3144, 0x3145, 0x3180, 0x3181, 0x31e2, 0x31e3, 0x31fe, 0x31ff, 0x325f, 0x3260, 0x3276, 0x3277, 0x3286, 0xffff, 0x67452301, 0x10325477, 0x67452302, 0x10325476, 0x567, 0x28955b88, 0x173848aa, 0x242070db, 0x3e423112, 0xa83f051, 0x4787c62a, 0x57cfb9ed, 0x2b96aff, 0x698098d8, 0x74bb0851, 0xa44f, 0x76a32842, 0x6b901122, 0x2678e6d, 0x5986bc72, 0x49b40821, 0x9e1da9e, 0x3fbf4cc0, 0x265e5a51, 0x16493856, 0x29d0efa3, 0x2441453, 0x275e197f, 0x182c0438, 0x21e1cde6, 0x3cc8f82a, 0xb2af279, 0x455a14ed, 0x561c16fb, 0x3105c08, 0x676f02d9, 0x72d5b376, 0x5c6be, 0x788e097f, 0x6d9d6122, 0x21ac7f4, 0x5b4115bc, 0x4bdecfa9, 0x944b4a0, 0x41404390, 0x289b7ec6, 0x155ed806, 0x2b10cf7b, 0x4881d05, 0x262b2fc7, 0x1924661b, 0x1fa27cf8, 0x3b53a99b, 0xbd6ddbc, 0x432aff97, 0x546bdc59, 0x36c5fc7, 0x655b59c3, 0x70f3336e, 0x100b83, 0x7a7ba22f, 0x6fa87e4f, 0x1d31920, 0x5cfebcec, 0x4e0811a1, 0x8ac817e, 0x42c50dcb, 0x2ad7d2bb, 0x14792c6f, h[FQ(Rb.y)], 0x33f0, 0x33fb, 0x33fc, 0x3416, 0x3417, 0x341e, h[FQ(0x257)]]
});
}());
================================================
FILE: static/Release_Notes.md
================================================
**项目更新内容:**
1. 修复 CLI 模式 KeyError 异常
2. 修复控制字符导致程序报错的问题
3. 重构项目内置请求延时机制
*****
**用户脚本更新内容:**
**版本号:2.3.1**
================================================
FILE: static/XHS-Downloader.js
================================================
// ==UserScript==
// @name XHS-Downloader
// @namespace xhs_downloader
// @homepage https://github.com/JoeanAmier/XHS-Downloader
// @version 2.3.1
// @tag 小红书
// @tag RedNote
// @tag XiaoHongShu
// @description 提取小红书作品/用户链接,下载小红书图文/视频作品文件
// @description:en Extract RedNote works/user links, Download images/videos files
// @author JoeanAmier
// @match http*://www.xiaohongshu.com/explore*
// @match http*://www.xiaohongshu.com/discovery/item/*
// @match http*://www.xiaohongshu.com/user/profile/*
// @match http*://www.xiaohongshu.com/search_result*
// @match http*://www.xiaohongshu.com/board/*
// @icon64 data:image/png;base64,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
// @grant GM_getValue
// @grant GM_setValue
// @grant unsafeWindow
// @grant GM_setClipboard
// @grant GM_registerMenuCommand
// @grant GM_unregisterMenuCommand
// @license GNU General Public License v3.0
// @run-at document-end
// @updateURL https://raw.githubusercontent.com/JoeanAmier/XHS-Downloader/master/static/XHS-Downloader.js
// @downloadURL https://raw.githubusercontent.com/JoeanAmier/XHS-Downloader/master/static/XHS-Downloader.js
// @supportURL https://github.com/JoeanAmier/XHS-Downloader/issues
// @require https://cdnjs.cloudflare.com/ajax/libs/jszip/3.9.1/jszip.min.js
// ==/UserScript==
(function () {
'use strict';
const i18n = {
'CN': {
instructionsText: `功能清单:
1. 下载小红书作品文件
2. 提取推荐页面作品链接
3. 提取账号发布作品链接
4. 提取账号收藏作品链接
5. 提取账号专辑作品链接
6. 提取账号点赞作品链接
7. 提取搜索结果作品链接
8. 提取搜索结果用户链接
注意事项:
1. 下载小红书作品文件时,脚本需要花费时间处理文件,请等待片刻,请勿多次点击下载按钮
2. 提取账号发布、收藏、点赞、专辑作品链接时,脚本可以自动滚动页面直至加载全部作品
3. 提取推荐作品链接、搜索作品、用户链接时,脚本可以自动滚动指定次数加载更多内容,默认滚动次数:50 次
4. 自动滚动页面功能默认关闭;用户可以自由开启,并修改滚动页面次数,修改后立即生效
5. 如果未开启自动滚动页面功能,用户需要手动滚动页面以便加载更多内容后再进行其他操作
6. 支持作品文件打包下载;该功能默认开启,多个文件的作品将会以压缩包格式下载
7. 向服务器推送下载任务时,文件格式、名称规则等设置以服务器配置文件中的设置为准
8. 使用全局代理工具可能会导致脚本下载文件失败,如有异常,请尝试关闭代理工具,必要时向作者反馈
9. XHS-Downloader 用户脚本仅实现可见即可得的数据采集功能,无任何收费功能和破解功能
项目开源地址:https://github.com/JoeanAmier/XHS-Downloader
`,
disclaimerText: `1. 使用者对本项目的使用由使用者自行决定,并自行承担风险。作者对使用者使用本项目所产生的任何损失、责任、或风险概不负责。
2. 本项目的作者提供的代码和功能是基于现有知识和技术的开发成果。作者按现有技术水平努力确保代码的正确性和安全性,但不保证代码完全没有错误或缺陷。
3. 本项目依赖的所有第三方库、插件或服务各自遵循其原始开源或商业许可,使用者需自行查阅并遵守相应协议,作者不对第三方组件的稳定性、安全性及合规性承担任何责任。
4. 使用者在使用本项目时必须严格遵守 GNU General Public License v3.0 的要求,并在适当的地方注明使用了 GNU General Public License v3.0 的代码。
5. 使用者在使用本项目的代码和功能时,必须自行研究相关法律法规,并确保其使用行为合法合规。任何因违反法律法规而导致的法律责任和风险,均由使用者自行承担。
6. 使用者不得使用本工具从事任何侵犯知识产权的行为,包括但不限于未经授权下载、传播受版权保护的内容,开发者不参与、不支持、不认可任何非法内容的获取或分发。
7. 本项目不对使用者涉及的数据收集、存储、传输等处理活动的合规性承担责任。使用者应自行遵守相关法律法规,确保处理行为合法正当;因违规操作导致的法律责任由使用者自行承担。
8. 使用者在任何情况下均不得将本项目的作者、贡献者或其他相关方与使用者的使用行为联系起来,或要求其对使用者使用本项目所产生的任何损失或损害负责。
9. 本项目的作者不会提供 XHS-Downloader 项目的付费版本,也不会提供与 XHS-Downloader 项目相关的任何商业服务。
10. 基于本项目进行的任何二次开发、修改或编译的程序与原创作者无关,原创作者不承担与二次开发行为或其结果相关的任何责任,使用者应自行对因二次开发可能带来的各种情况负全部责任。
11. 本项目不授予使用者任何专利许可;若使用本项目导致专利纠纷或侵权,使用者自行承担全部风险和责任。未经作者或权利人书面授权,不得使用本项目进行任何商业宣传、推广或再授权。
12. 作者保留随时终止向任何违反本声明的使用者提供服务的权利,并可能要求其销毁已获取的代码及衍生作品。
13. 作者保留在不另行通知的情况下更新本声明的权利,使用者持续使用即视为接受修订后的条款。
在使用本项目的代码和功能之前,请您认真考虑并接受以上免责声明。如果您对上述声明有任何疑问或不同意,请不要使用本项目的代码和功能。如果您使用了本项目的代码和功能,则视为您已完全理解并接受上述免责声明,并自愿承担使用本项目的一切风险和后果。
`,
readmeTitle: 'XHS-Downloader 脚本说明',
disclaimerTitle: 'XHS-Downloader 免责声明',
disclaimerConfirm: '我已知晓',
readmeMenuTitle: "阅读脚本说明和免责声明",
aboutText: `项目开源协议:GNU General Public License v3.0
项目开源地址:https://github.com/JoeanAmier/XHS-Downloader
如果 XHS-Downloader 对您有帮助,请考虑为它点个 Star ⭐,感谢您的支持!
✨ 作者的其他开源项目:
DouK-Downloader(抖音、DouYin、TikTok):https://github.com/JoeanAmier/TikTokDownloader
KS-Downloader(快手、KuaiShou):https://github.com/JoeanAmier/KS-Downloader
项目 Discord 社区:https://discord.com/invite/ZYtmgKud9Y
`,
aboutTitle: '关于 XHS-Downloader',
errorTitle: '发生异常',
errorText: (text) => `${text}请向作者反馈!\n项目开源地址:https://github.com/JoeanAmier/XHS-Downloader`,
imageExtractError: "解析图文作品数据发生异常!",
downloadLinkError: "处理下载链接发生异常!",
downloadTips: "正在下载文件,请稍等...",
downloadError: "下载作品文件发生异常!",
extractError: "读取作品数据发生异常!",
linkExtractSuccess: '作品/用户链接已复制到剪贴板!',
linkExtractError: "未提取到任何作品/用户链接!",
videoDownloadError: "下载视频作品文件发生异常!",
imageDownloadError: "下载图文作品文件发生异常!",
signInPrompt: "提取作品链接失败!受平台限制,未登录状态下无法通过账号主页浏览作品详情!请登录后重试!",
jsZipError: "XHS-Downloader 用户脚本依赖库 JSZip 加载失败,作品文件打包下载功能无法使用,请尝试刷新网页或者向作者反馈!",
tipsTitle: '脚本提示',
confirmButton: '确认',
closeButton: '关闭',
autoScrollLabel: '自动滚动页面',
autoScrollDesc: '启用后,页面将根据规则自动滚动以便加载更多内容',
filePackLabel: '文件打包下载',
filePackDesc: '启用后,多个文件的作品将会以压缩包格式下载',
scrollCountLabel: '自动滚动次数',
scrollCountDesc: '自动滚动页面的次数(仅在启用自动滚动页面时可用)',
linkCheckboxSwitchLabel: '链接提取选择模式',
linkCheckboxSwitchDesc: '关闭后,提取作品链接时无需确认直接提取全部链接',
imageCheckboxSwitchLabel: '图片下载选择模式',
imageCheckboxSwitchDesc: '关闭后,下载图文作品时无需确认直接下载全部文件',
keepMenuVisibleLabel: '菜单保持显示',
keepMenuVisibleDesc: '启用后,功能菜单无需鼠标悬停始终保持显示',
scriptServerURLLabel: 'WebSocket 服务器地址',
scriptServerURLDesc: '用户脚本连接的 WebSocket 服务器',
scriptServerSwitchLabel: '连接服务器',
scriptServerSwitchDesc: '启用后,可以把作品下载任务推送至服务器',
imageDownloadFormatLabel: '图片下载格式',
imageDownloadFormatDesc: '图文作品文件下载格式',
saveSettingsButton: '保存设置',
cancelSettingsButton: '放弃修改',
selectAllButton: '全部选中',
deselectAllButton: '全部取消',
startDownloadButton: '开始下载',
closeDownloadButton: '取消下载',
imageSelectionTip: '请至少选择一张图片!',
itemsExtractTip: '请选择需要提取的项目',
itemsExtractConfirm: '提取链接',
itemsExtractCancel: '放弃',
extractRecommendLinksText: '提取推荐作品链接',
extractRecommendLinksDescription: '提取推荐页面的作品链接至剪贴板',
downloadNoteFilesText: '下载作品文件',
downloadNoteFilesDescription: '下载当前作品文件',
pushDownloadTaskText: '推送下载任务',
pushDownloadTaskDescription: '向服务器发送下载请求',
extractPublishedLinksText: '提取发布作品链接',
extractPublishedLinksDescription: '提取账号发布作品链接至剪贴板',
extractLikedLinksText: '提取点赞作品链接',
extractLikedLinksDescription: '提取账号点赞作品链接至剪贴板',
extractSavedLinksText: '提取收藏作品链接',
extractSavedLinksDescription: '提取账号收藏作品链接至剪贴板',
extractSearchNoteLinksText: '提取作品链接',
extractSearchNoteLinksDescription: '提取搜索结果的作品链接至剪贴板',
extractSearchUsersLinksText: '提取用户链接',
extractSearchUsersLinksDescription: '提取搜索结果的用户链接至剪贴板',
extractAlbumNotesLinksText: '提取专辑作品链接',
extractAlbumNotesLinksDescription: '提取当前专辑的作品链接至剪贴板',
modifyScriptSettingsText: '修改用户脚本设置',
modifyScriptSettingsDescription: '修改用户脚本设置',
aboutXHSText: '关于 XHS-Downloader',
aboutXHSDescription: '查看 XHS-Downloader 更多信息',
imageCheckboxTitle: '请选中需要下载的图片',
scriptServerError: '脚本服务器连接出错,请检查网络连接或脚本服务器状态是否正常!',
pushTaskError: '脚本服务器未连接,请检查网络连接或脚本服务器状态是否正常!',
pushTaskSuccess: "已向服务器发送下载请求",
settingsTitle: '用户脚本设置',
}, 'EN': {
instructionsText: `Features:
1. Download RedNote note files
2. Extract note links from the Recommendation page
3. Extract note links from an account's Published tab
4. Extract note links from an account's Collections tab
5. Extract note links from an account's Albums
6. Extract note links from an account's Liked tab
7. Extract note links from search results
8. Extract user links from search results
Notes:
1. When downloading note files, the script needs time to process. Please wait a moment and do not click the download button repeatedly.
2. When extracting links from Published, Collections, Liked, or Albums, the script can automatically scroll the page until all notes are loaded.
3. When extracting Recommendation, Search Notes, or User links, the script can automatically scroll a specified number of times. Default: 50 times.
4. Auto-scroll is disabled by default; users can enable it and modify the scroll count. Changes take effect immediately.
5. If auto-scroll is disabled, users must manually scroll the page to load more content before performing extractions.
6. Supports batch downloading (ZIP format); this feature is enabled by default. Notes with multiple files will be downloaded as a compressed package.
7. When pushing tasks to a server, settings such as file format and naming rules are determined by the server's configuration file.
8. Using global proxy tools may cause download failures. If issues occur, try disabling the proxy and provide feedback to the author if necessary.
9. The XHS-Downloader userscript only provides "what you see is what you get" data collection; it contains no paid features or decryption/cracking functions.
Open Source: https://github.com/JoeanAmier/XHS-Downloader
`,
disclaimerText: `1. The use of this project is at the user's own discretion and risk. The author is not responsible for any loss, liability, or risk arising from its use.
2. The code and functions provided are based on existing knowledge and technology. While efforts are made to ensure correctness and security, the author does not guarantee that the code is entirely error-free.
3. All third-party libraries, plugins, or services relied upon by this project follow their own original open-source or commercial licenses. Users must consult and comply with those agreements.
4. Users must strictly adhere to the GNU General Public License v3.0 requirements and credit the use of GPL v3.0 code where appropriate.
5. Users must research relevant laws and regulations to ensure their use of this project is legal and compliant. Any legal liability arising from violations is borne solely by the user.
6. Users must not use this tool for any acts that infringe on intellectual property rights, including but not limited to unauthorized downloading or distribution of copyrighted content.
7. This project assumes no responsibility for the compliance of data collection, storage, or transmission activities performed by the user.
8. Under no circumstances shall the author or contributors be held liable for any damages or losses related to the user's actions.
9. The author will not provide a paid version of XHS-Downloader, nor any commercial services related to the project.
10. Any secondary development, modification, or compilation of this program is unrelated to the original author. The user is solely responsible for any consequences of such actions.
11. This project does not grant any patent licenses. The user assumes all risks regarding patent disputes. Commercial promotion or sub-licensing without written authorization is prohibited.
12. The author reserves the right to terminate service to any user violating this disclaimer and may request the destruction of obtained code.
13. The author reserves the right to update this disclaimer without notice. Continued use constitutes acceptance of the revised terms.
Before using this project, please carefully consider and accept the above disclaimer. If you have any doubts or disagree, do not use the code or functions. Use of the project implies full understanding and acceptance of these terms.
`,
readmeTitle: 'XHS-Downloader Instructions',
disclaimerTitle: 'XHS-Downloader Disclaimer',
disclaimerConfirm: 'I acknowledge',
readmeMenuTitle: "Read Instructions and Disclaimer",
aboutText: `License: GNU General Public License v3.0
GitHub: https://github.com/JoeanAmier/XHS-Downloader
If XHS-Downloader helps you, please consider giving it a Star ⭐. Thanks for your support!
✨ Other Projects by the Author:
DouK-Downloader (DouYin, TikTok): https://github.com/JoeanAmier/TikTokDownloader
KS-Downloader (KuaiShou): https://github.com/JoeanAmier/KS-Downloader
Discord Community: https://discord.com/invite/ZYtmgKud9Y
`,
aboutTitle: 'About XHS-Downloader',
errorTitle: 'Exception Occurred',
errorText: (text) => `${text} Please report this to the author!\nGitHub: https://github.com/JoeanAmier/XHS-Downloader`,
imageExtractError: "Error parsing image note data!",
downloadLinkError: "Error processing download links!",
downloadTips: "Downloading file, please wait...",
downloadError: "Error downloading note files!",
extractError: "Error reading note data!",
linkExtractSuccess: 'Note/User links copied to clipboard!',
linkExtractError: "No Note/User links extracted!",
videoDownloadError: "Error downloading video note!",
imageDownloadError: "Error downloading image note!",
signInPrompt: "Failed to extract links! Due to platform restrictions, note details cannot be viewed via account pages without logging in. Please log in and try again!",
jsZipError: "JSZip library failed to load. ZIP packaging is unavailable. Please refresh or contact the author!",
tipsTitle: 'Script Tips',
confirmButton: 'Confirm',
closeButton: 'Close',
autoScrollLabel: 'Auto-scroll Page',
autoScrollDesc: 'When enabled, the page will automatically scroll to load more content',
filePackLabel: 'Package Files for Download',
filePackDesc: 'When enabled, notes with multiple files will be downloaded as a ZIP archive',
scrollCountLabel: 'Auto-scroll Count',
scrollCountDesc: 'Number of times to scroll (only active when Auto-scroll is enabled)',
linkCheckboxSwitchLabel: 'Link Extraction Selection Mode',
linkCheckboxSwitchDesc: 'If disabled, all links will be extracted immediately without confirmation',
imageCheckboxSwitchLabel: 'Image Download Selection Mode',
imageCheckboxSwitchDesc: 'If disabled, all images will be downloaded immediately without confirmation',
keepMenuVisibleLabel: 'Keep Menu Visible',
keepMenuVisibleDesc: 'When enabled, the menu stays visible without needing a mouse hover',
scriptServerURLLabel: 'WebSocket Server URL',
scriptServerURLDesc: 'The WebSocket server address the script connects to',
scriptServerSwitchLabel: 'Connect to Server',
scriptServerSwitchDesc: 'When enabled, download tasks can be pushed to the server',
imageDownloadFormatLabel: 'Image Download Format',
imageDownloadFormatDesc: 'Preferred file format for downloading images',
saveSettingsButton: 'Save Settings',
cancelSettingsButton: 'Discard Changes',
selectAllButton: 'Select All',
deselectAllButton: 'Deselect All',
startDownloadButton: 'Start Download',
closeDownloadButton: 'Cancel Download',
imageSelectionTip: 'Please select at least one image!',
itemsExtractTip: 'Please select items to extract',
itemsExtractConfirm: 'Extract Links',
itemsExtractCancel: 'Cancel',
extractRecommendLinksText: 'Extract Recommended Note Links',
extractRecommendLinksDescription: '',
downloadNoteFilesText: 'Download Note Files',
downloadNoteFilesDescription: '',
pushDownloadTaskText: 'Push Download Task',
pushDownloadTaskDescription: 'Send download request to the server',
extractPublishedLinksText: 'Extract Published Note Links',
extractPublishedLinksDescription: '',
extractLikedLinksText: 'Extract Liked Note Links',
extractLikedLinksDescription: '',
extractSavedLinksText: 'Extract Collected Note Links',
extractSavedLinksDescription: '',
extractSearchNoteLinksText: 'Extract Note Links',
extractSearchNoteLinksDescription: 'Extract note links from search results',
extractSearchUsersLinksText: 'Extract User Links',
extractSearchUsersLinksDescription: 'Extract user links from search results',
extractAlbumNotesLinksText: 'Extract Album Note Links',
extractAlbumNotesLinksDescription: 'Extract note links from the current album',
modifyScriptSettingsText: 'Modify Script Settings',
modifyScriptSettingsDescription: '',
aboutXHSText: 'About XHS-Downloader',
aboutXHSDescription: '',
imageCheckboxTitle: 'Please select images to download',
scriptServerError: 'Server connection error. Please check your network or server status!',
pushTaskError: 'Server not connected. Please check your network or server status!',
pushTaskSuccess: "Download request sent to server successfully",
settingsTitle: 'Script Settings',
},
};
let lang = GM_getValue("language", undefined);
if (!lang) {
lang = navigator.language.toLowerCase().includes('zh') ? 'CN' : 'EN';
GM_setValue("language", lang);
}
let t = i18n[lang];
const iconBase64 = "data:image/png;base64,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";
const defaultsWebSocketURL = "ws://127.0.0.1:5558";
let config = {
disclaimer: GM_getValue("disclaimer", false),
packageDownloadFiles: GM_getValue("packageDownloadFiles", true),
autoScrollSwitch: GM_getValue("autoScrollSwitch", false),
maxScrollCount: GM_getValue("maxScrollCount", 50),
keepMenuVisible: GM_getValue("keepMenuVisible", false),
linkCheckboxSwitch: GM_getValue("linkCheckboxSwitch", true),
imageCheckboxSwitch: GM_getValue("imageCheckboxSwitch", true),
imageDownloadFormat: GM_getValue("imageDownloadFormat", "jpeg"),
scriptServerURL: GM_getValue("scriptServerURL", defaultsWebSocketURL),
scriptServerSwitch: GM_getValue("scriptServerSwitch", false),
fileNameFormat: undefined,
icon: {
type: 'image', // 可选: image/svg/font
image: {
url: iconBase64, // 图片URL或Base64
size: 64, // 图标尺寸
borderRadius: '50%' // 形状(50%为圆形)
},
}, // 位置配置
position: {
bottom: '6rem', left: '1rem'
}, // 动画配置
animation: {
duration: 0.25, // 动画时长(s)
easing: 'cubic-bezier(0.4, 0, 0.2, 1)'
}
};
const readme = async () => {
await showTextModal({
title: t.readmeTitle, text: t.instructionsText, mode: 'info', closeText: t.closeButton
});
if (!config.disclaimer) {
showTextModal({
title: t.disclaimerTitle,
text: t.disclaimerText,
mode: 'confirm',
confirmText: t.disclaimerConfirm,
closeText: t.closeButton
}).then(answer => {
GM_setValue("disclaimer", answer);
config.disclaimer = answer;
});
}
};
if (!config.disclaimer) {
readme().then();
}
console.info("用户接受 XHS-Downloader 免责声明", config.disclaimer)
GM_registerMenuCommand(t.readmeMenuTitle, function () {
readme().then();
});
GM_registerMenuCommand("切换语言/Switch language", function () {
lang = lang === "CN" ? "EN" : "CN";
GM_setValue("language", lang);
t = i18n[lang];
});
const updatePackageDownloadFiles = (value) => {
config.packageDownloadFiles = value;
GM_setValue("packageDownloadFiles", config.packageDownloadFiles);
};
const updateAutoScrollSwitch = (value) => {
config.autoScrollSwitch = value;
GM_setValue("autoScrollSwitch", config.autoScrollSwitch);
};
const updateMaxScrollCount = (value) => {
config.maxScrollCount = parseInt(value) || 50;
GM_setValue("maxScrollCount", config.maxScrollCount);
};
const updateKeepMenuVisible = (value) => {
config.keepMenuVisible = value;
GM_setValue("keepMenuVisible", config.keepMenuVisible);
if (config.keepMenuVisible) {
showMenu();
} else {
hideMenu();
}
};
const updateLinkCheckboxSwitch = (value) => {
config.linkCheckboxSwitch = value;
GM_setValue("linkCheckboxSwitch", config.linkCheckboxSwitch);
}
const updateImageCheckboxSwitch = (value) => {
config.imageCheckboxSwitch = value;
GM_setValue("imageCheckboxSwitch", config.imageCheckboxSwitch);
}
const updateScriptServerURL = (value) => {
config.scriptServerURL = value;
GM_setValue("scriptServerURL", config.scriptServerURL);
}
const updateScriptServerSwitch = (value) => {
webSocket.disconnect();
if (value) {
webSocket.url = config.scriptServerURL;
webSocket.connect();
}
config.scriptServerSwitch = value;
GM_setValue("scriptServerSwitch", config.scriptServerSwitch);
}
const updateImageDownloadFormat = (value) => {
config.imageDownloadFormat = value.toLowerCase();
GM_setValue("imageDownloadFormat", config.imageDownloadFormat);
}
const updateFileNameFormat = (value) => {
config.fileNameFormat = value;
GM_setValue("fileNameFormat", config.fileNameFormat);
};
const about = () => {
showTextModal({
title: t.aboutTitle, text: t.aboutText, mode: 'info', closeText: t.closeButton
}).then();
}
const abnormal = (text) => {
showTextModal({
title: t.errorTitle, text: t.errorText(text), mode: 'info', closeText: t.closeButton
}).then();
};
const runTips = (text) => {
showTextModal({
title: t.tipsTitle, text: text, mode: 'info', closeText: t.closeButton
}).then();
}
const generateVideoUrl = note => {
try {
const key = note.video?.consumer?.originVideoKey;
if (key) return [`https://sns-video-bd.xhscdn.com/${key}`];
const video = note.video.media.stream.h265;
return [video[video.length - 1].masterUrl];
} catch (error) {
console.error("Error deal video URL:", error);
return [];
}
};
const generateImageUrl = note => {
let images = note.imageList;
const regex = /http:\/\/sns-webpic-qc\.xhscdn.com\/\d+\/[0-9a-z]+\/(\S+)!/;
let urls = [];
try {
images.forEach((item) => {
const url = item.urlDefault || item.url;
let match = url.match(regex);
if (match && match[1]) {
urls.push(
`https://ci.xiaohongshu.com/${match[1]}?imageView2/format/${GM_getValue(
"imageDownloadFormat",
"jpeg"
)}`);
}
})
return urls
} catch (error) {
console.error("Error generating image URLs:", error);
return [];
}
};
const extractImageWebpUrls = (note, urls,) => {
try {
let items = []
let {imageList} = note;
if (urls.length !== imageList.length) {
console.error("图片数量不一致!")
return []
}
for (const [index, item] of imageList.entries()) {
const url = item.urlDefault || item.url;
if (url) {
items.push({
webp: url, index: index + 1, url: urls[index],
})
} else {
console.error("提取图片预览链接失败", item)
break
}
}
return items;
} catch (error) {
console.error("Error occurred in generating image object:", error);
return []
}
};
const download = async (urls, note, server = false,) => {
const name = extractName();
if (server) {
let data = {data: note, index: null,};
if (note.type === "normal") {
let items = extractImageWebpUrls(note, urls);
if (items.length === 0) {
console.error("解析图文作品数据失败", note)
abnormal(t.imageExtractError)
} else if (urls.length > 1 && config.imageCheckboxSwitch) {
data.index = await showImageSelectionModal(items, name, server,);
}
}
webSocket.send(JSON.stringify(data));
} else {
console.debug(`文件名称 ${name}`);
if (note.type === "video") {
showToast(t.downloadTips);
await downloadVideo(urls[0], name);
} else {
let items = extractImageWebpUrls(note, urls);
if (items.length === 0) {
console.error("解析图文作品数据失败", note)
abnormal(t.imageExtractError)
} else if (urls.length > 1 && config.imageCheckboxSwitch) {
await showImageSelectionModal(items, name,);
} else {
showToast(t.downloadTips);
await downloadImage(items, name);
}
}
}
};
const exploreDeal = async (note, server = false,) => {
try {
let links;
if (note.type === "normal") {
links = generateImageUrl(note);
} else {
links = generateVideoUrl(note);
}
if (links.length > 0) {
// console.debug("下载链接", links);
await download(links, note, server,);
} else {
abnormal(t.downloadLinkError)
}
} catch (error) {
console.error("Error in exploreDeal function:", error);
abnormal(t.downloadError);
}
};
const extractNoteInfo = () => {
const data = unsafeWindow.__INITIAL_STATE__?.noteData?.data?.noteData;
if (data) return data;
const regex = /\/explore\/([^?]+)/;
const match = currentUrl.match(regex);
if (match) {
return unsafeWindow.__INITIAL_STATE__.note.noteDetailMap[match[1]].note;
} else {
console.error("从链接提取作品 ID 失败", currentUrl,);
}
};
const extractDownloadLinks = async (server = false) => {
if (currentUrl.includes("https://www.xiaohongshu.com/explore/") || currentUrl.includes(
"https://www.xiaohongshu.com/discovery/item/")) {
let note = extractNoteInfo();
if (note) {
await exploreDeal(note, server,);
} else {
abnormal(t.extractError);
}
}
};
const triggerDownload = (name, blob) => {
// 创建 Blob 对象的 URL
const blobUrl = URL.createObjectURL(blob);
// 创建一个临时链接元素
const tempLink = document.createElement("a");
tempLink.href = blobUrl;
tempLink.download = name;
// 将链接添加到 DOM 并模拟点击
document.body.appendChild(tempLink); // 避免某些浏览器安全限制
tempLink.click();
// 清理临时链接元素
document.body.removeChild(tempLink); // 从 DOM 中移除临时链接
URL.revokeObjectURL(blobUrl); // 释放 URL
// console.debug(`文件已成功下载: ${name}`);
}
const downloadFile = async (link, name, trigger = true, retries = 5) => {
for (let attempt = 1; attempt <= retries; attempt++) {
try {
// 使用 fetch 获取文件数据
const response = await fetch(link, {
"headers": {
"accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
"accept-language": "zh-SG,zh;q=0.9",
}, "method": "GET",
});
// 检查响应状态码
if (!response.ok) {
console.error(`下载失败,状态码: ${response.status},URL: ${link},尝试次数: ${attempt}`);
continue; // 继续下一次尝试
}
const blob = await response.blob();
if (trigger) {
triggerDownload(name, blob);
return true;
} else {
return blob;
}
} catch (error) {
console.error(`下载失败 (${name}),错误信息:`, error, `尝试次数: ${attempt}`);
if (attempt === retries) {
return false; // 如果达到最大重试次数,返回失败
}
}
}
return false; // 如果所有尝试都失败,返回失败
};
const downloadFiles = async (items, name,) => {
const downloadResults = []; // 用于存储下载结果
const downloadPromises = items.map(async (item) => {
let fileName;
if (item.index) {
fileName = `${name}_${item.index}.${GM_getValue("imageDownloadFormat", "jpeg")}`; // 根据索引生成文件名
} else {
fileName = `${name}.${GM_getValue("imageDownloadFormat", "jpeg")}`;
}
const result = await downloadFile(item.url, fileName, false); // 调用单个文件下载方法
if (result) {
downloadResults.push({name: fileName, file: result});
return true; // 成功
} else {
return false; // 失败
}
});
// 等待所有下载操作完成
const results = await Promise.all(downloadPromises);
if (results.every(result => result === true)) {
try {
const zip = new JSZip();
downloadResults.forEach((item) => {
zip.file(item.name, item.file);
});
const content = await zip.generateAsync({type: "blob", compression: "STORE"});
triggerDownload(`${name}.zip`, content,)
return true;
} catch (error) {
console.error('生成 ZIP 文件或保存失败,错误信息:', error);
return false;
}
} else {
return false;
}
};
const truncateString = (str, maxLength) => {
if (str.length > maxLength) {
const halfLength = Math.floor(maxLength / 2) - 1; // 减去 1 留出省略号的空间
return str.slice(0, halfLength) + '...' + str.slice(-halfLength);
}
return str;
};
const extractName = () => {
let name = document.title.replace(/ - 小红书$/, "")
.replace(/[^\u4e00-\u9fa5a-zA-Z0-9 ~!@#$%&()_\-+=\[\];"',.!()【】:“”,。《》?]/g, "");
name = truncateString(name, 64,);
let match = currentUrl.match(/\/([0-9a-z]+?)\?/);
let id = match ? match[1] : null;
return name === "" ? id : name
};
const downloadVideo = async (url, name) => {
if (!await downloadFile(url, `${name}.mp4`)) {
abnormal(t.videoDownloadError);
}
};
const downloadImage = async (items, name) => {
let success;
if (!config.packageDownloadFiles && items.length > 1) {
let result = [];
for (let item of items) {
result.push(await downloadFile(
item.url,
`${name}_${item.index}.${GM_getValue("imageDownloadFormat", "jpeg")}`
));
}
success = result.every(item => item === true);
} else if (items.length === 1) {
success = await downloadFile(items[0].url, `${name}.${GM_getValue("imageDownloadFormat", "jpeg")}`);
} else {
success = await downloadFiles(items, name,);
}
if (!success) {
abnormal(t.imageDownloadError);
}
};
const window_scrollBy = (x, y,) => {
window.scrollBy(x, y,);
}
// 随机整数生成函数
const getRandomInt = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min;
// 判断是否需要暂停,模拟用户的停顿行为
const shouldPause = () => Math.random() < 0.2; // 20%几率停顿
// 执行一次增量滚动
const scrollOnce = () => {
const scrollDistanceMin = 100; // 最小滚动距离
const scrollDistanceMax = 300; // 最大滚动距离
const scrollDistance = getRandomInt(scrollDistanceMin, scrollDistanceMax);
window_scrollBy(0, scrollDistance); // 增量滚动
};
// 检查是否已经滚动到底部
const isAtBottom = () => {
const docHeight = document.documentElement.scrollHeight;
const winHeight = window.innerHeight;
const scrollPos = window.scrollY;
return (docHeight - winHeight - scrollPos <= 10); // 如果距离底部小于10px,认为滚动到底部
};
// 自动滚动主函数
const scrollScreen = (callback, endless = false, scrollCount = 0,) => {
const timeoutMin = 250; // 最小滚动间隔
const timeoutMax = 500; // 最大滚动间隔
const scrollInterval = setInterval(() => {
if (shouldPause()) {
// 停顿,模拟用户的休息
clearInterval(scrollInterval);
setTimeout(() => {
scrollScreen(callback, endless, scrollCount,); // 重新启动滚动
}, getRandomInt(timeoutMin, timeoutMax,)); // 随机停顿时间
} else if (endless) {
// 无限滚动至底部模式
if (!isAtBottom()) {
scrollOnce(); // 执行一次滚动
} else {
// 到达底部,停止滚动
clearInterval(scrollInterval);
callback(); // 调用回调函数
}
} else if (scrollCount < config.maxScrollCount && !isAtBottom()) {
scrollOnce(); // 执行一次滚动
scrollCount++;
} else {
// 如果到达底部或滚动次数已满,停止滚动
clearInterval(scrollInterval);
callback(); // 调用回调函数
}
}, getRandomInt(timeoutMin, timeoutMax)); // 随机滚动间隔
};
const scrollScreenEvent = (callback, endless = false) => {
if (config.autoScrollSwitch) {
scrollScreen(callback, endless,);
} else {
callback();
}
};
const extractNotesInfo = order => {
const notesRawValue = unsafeWindow.__INITIAL_STATE__.user.notes._rawValue[order];
return notesRawValue.filter(item => item?.noteCard).map(
item => [item.id, item.xsecToken, item.noteCard.cover.urlDefault, item.noteCard.user.nickName,
item.noteCard.displayTitle,]);
};
const extractBoardInfo = () => {
// 定义正则表达式来匹配 URL 中的 ID
const regex = /\/board\/([a-z0-9]+)\?/;
// 使用 exec 方法执行正则表达式
const match = regex.exec(currentUrl);
// 检查是否有匹配
if (match) {
// 提取 ID
const id = match[1]; // match[0] 是整个匹配的字符串,match[1] 是第一个括号内的匹配
const notesRawValue = unsafeWindow.__INITIAL_STATE__.board.boardFeedsMap._rawValue[id].notes;
return notesRawValue.map(
item => [item.noteId, item.xsecToken, item.cover.urlDefault, item.user.nickName, item.displayTitle,]);
} else {
console.error("从链接提取专辑 ID 失败", currentUrl,);
return [];
}
};
const extractFeedInfo = () => {
const notesRawValue = unsafeWindow.__INITIAL_STATE__.feed.feeds._rawValue;
return notesRawValue.filter(item => item?.noteCard).map(
item => [item.id, item.xsecToken, item.noteCard.cover.urlDefault, item.noteCard.user.nickName,
item.noteCard.displayTitle,]);
};
const extractSearchNotes = () => {
const notesRawValue = unsafeWindow.__INITIAL_STATE__.search.feeds._rawValue;
return notesRawValue.filter(item => item?.noteCard).map(
item => [item.id, item.xsecToken, item.noteCard.cover.urlDefault, item.noteCard.user.nickName,
item.noteCard.displayTitle,]);
}
const extractSearchUsers = () => {
const notesRawValue = unsafeWindow.__INITIAL_STATE__.search.userLists._rawValue;
return notesRawValue.map(item => item.id);
}
const generateNoteUrls = data => data.map(
([id, token,]) => `https://www.xiaohongshu.com/discovery/item/${id}?source=webshare&xhsshare=pc_web&xsec_token=${token}&xsec_source=pc_share`)
.join(" ");
const generateUserUrls = data => data.map(id => `https://www.xiaohongshu.com/user/profile/${id}`).join(" ");
const invalidDetection = data => data.every(([first]) => Boolean(first));
const extractAllLinks = (callback, order) => {
scrollScreenEvent(() => {
let data;
if (order >= 0 && order <= 2) {
data = extractNotesInfo(order);
if (!invalidDetection(data)) {
runTips(t.signInPrompt);
return;
}
} else if (order === 3) {
data = extractSearchNotes();
} else if (order === 4) {
data = extractSearchUsers();
} else if (order === -1) {
data = extractFeedInfo()
} else if (order === 5) {
data = extractBoardInfo()
} else {
data = []
}
if (data.length === 0) {
callback("");
return;
}
let urlsString;
if (order === 4) {
urlsString = generateUserUrls(data);
callback(urlsString);
} else if (config.linkCheckboxSwitch) {
showListSelectionModal(data.map(([id, token, cover, author, title,]) => ({
id: id, token: token, image: cover, author: author, title: title,
})),).then((selected) => {
if (selected.length > 0) {
urlsString = generateNoteUrls(selected.map(item => [item.id, item.token]));
callback(urlsString);
}
});
} else {
urlsString = generateNoteUrls(data.map(item => item.slice(0, 2)))
callback(urlsString);
}
}, [0, 1, 2, 5].includes(order))
};
const extractAllLinksEvent = (order = 0) => {
extractAllLinks(urlsString => {
if (urlsString) {
GM_setClipboard(urlsString, "text", () => {
showToast(t.linkExtractSuccess);
});
} else {
showToast(t.linkExtractError)
}
}, order);
};
if (typeof JSZip === 'undefined') {
runTips(t.jsZipError);
}
let style = document.createElement('style');
style.textContent = `
/* 通用 Overlay(三个弹窗共用) */
#SettingsOverlay,
#imageSelectionOverlay,
#textGenericOverlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.32);
backdrop-filter: blur(4px);
display: flex;
justify-content: center;
align-items: center;
z-index: 10000;
animation: fadeIn 0.3s;
}
/* Settings 容器,仅本块特有尺寸 */
.optimized-scroll-modal {
background: white;
border-radius: 16px;
width: 380px; /* 缩小窗口宽度 */
max-width: 95vw;
max-height: 95vh;
box-shadow: 0 10px 20px rgba(0,0,0,0.19), 0 6px 6px rgba(0,0,0,0.23);
overflow: hidden;
animation: scaleUp 0.3s;
display: flex;
flex-direction: column;
}
/* 通用头部/内容/底部/按钮(三个弹窗共用) */
.modal-header {
padding: 1rem;
border-bottom: 1px solid #eee;
text-align: center;
}
.modal-header span {
font-size: 1.25rem;
font-weight: 500;
color: #212121;
}
.modal-body {
flex: 1;
padding: 1rem;
overflow-y: auto;
}
.modal-footer {
padding: 1rem;
border-top: 1px solid #eee;
display: flex;
justify-content: flex-end;
gap: 12px;
}
.primary-btn {
background: #2196F3;
color: white;
padding: 8px 24px;
border-radius: 24px;
cursor: pointer;
transition: all 0.2s;
}
.secondary-btn {
background: #f0f0f0;
color: #666;
padding: 8px 24px;
border-radius: 24px;
cursor: pointer;
transition: all 0.2s;
}
/* Settings 专用的设置项样式(保持不变) */
.setting-item {
margin: 0.5rem 0;
padding: 10px;
border-radius: 8px;
transition: background 0.2s;
}
.setting-item:hover { background: #f0f0f0; }
.setting-item label {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
}
.setting-item label span {
font-size: 1rem;
font-weight: 500;
color: #333;
}
.toggle-switch {
position: relative;
width: 40px;
height: 20px;
}
.toggle-switch input { opacity: 0; width: 0; height: 0; }
.slider {
position: absolute;
cursor: pointer;
top: 0; left: 0; right: 0; bottom: 0;
background: #ccc;
transition: 0.4s;
border-radius: 34px;
}
.slider:before {
content: "";
position: absolute;
height: 16px; width: 16px;
left: 2px; bottom: 2px;
background: white;
border-radius: 50%;
transition: 0.4s;
}
input:checked + .slider { background: #2196F3; }
input:checked + .slider:before { transform: translateX(20px); }
.number-input {
display: flex;
align-items: center;
border: 1px solid #ddd;
border-radius: 8px;
overflow: hidden;
margin: 6px 0;
}
.number-input input {
width: 60px;
text-align: center;
border: none;
}
.number-button {
padding: 4px 8px;
background: #f0f0f0;
border: none;
cursor: pointer;
transition: all 0.2s;
}
.text-input {
width: 100%;
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 0.9rem;
margin-top: 8px;
transition: border-color 0.2s;
}
.text-input:focus {
outline: none;
border-color: #2196F3;
box-shadow: 0 0 4px rgba(33, 150, 243, 0.3);
}
.select-input {
width: 100px;
padding: 8px 12px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 0.9rem;
margin-top: 8px;
background: #fff;
transition: border-color 0.2s, box-shadow 0.2s;
appearance: none;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%23666' d='M3 4.5L6 7.5L9 4.5H3Z'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: right 12px center;
padding-right: 32px;
}
.select-input:focus {
outline: none;
border-color: #2196F3;
box-shadow: 0 0 4px rgba(33, 150, 243, 0.3);
}
.select-input:disabled {
background-color: #f5f5f5;
color: #999;
cursor: not-allowed;
}
.setting-description {
font-size: 0.875rem;
color: #757575;
margin-top: 4px;
line-height: 1.4;
text-align: left;
}
/* 通用动画:统一定义一次 */
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
@keyframes scaleUp { from { transform: scale(0.98); } to { transform: scale(1); } }
@keyframes fadeOut { from { opacity: 1; } to { opacity: 0; } }
`;
document.head.appendChild(style);
// 覆盖修改:文本弹窗样式
(() => {
if (!document.getElementById('textModalStyle')) {
const style = document.createElement('style');
style.id = 'textModalStyle';
style.textContent = `
/* 仅文本弹窗容器特有的尺寸与外观 */
.text-generic-modal {
background: #fff;
border-radius: 16px;
width: 80%;
max-width: 700px;
max-height: 80vh;
box-shadow: 0 10px 20px rgba(0,0,0,0.19), 0 6px 6px rgba(0,0,0,0.23);
overflow: hidden;
animation: scaleUp 0.3s;
display: flex;
flex-direction: column;
}
/* 仅该弹窗使用的文本内容样式 */
.text-content {
white-space: pre-wrap;
word-break: break-word;
color: #1e272e;
line-height: 1.6;
font-size: 0.95rem;
user-select: text;
}
`;
document.head.appendChild(style);
}
})();
/**
* 显示文本弹窗(手动调用)
* @param {Object} opts
* @param {string} opts.title 标题
* @param {string} opts.text 文本内容
* @param {'confirm'|'info'} opts.mode 模式:confirm=确认+关闭;info=仅关闭
* @param {string} [opts.confirmText='确认'] 确认按钮文案(仅 confirm 模式生效)
* @param {string} [opts.closeText='关闭'] 关闭按钮文案
* @returns {Promise} confirm 返回 true;关闭/点遮罩返回 false
*/
function showTextModal(opts) {
const {
title = t.tipsTitle, text = '', mode = 'info', confirmText = t.confirmButton, closeText = t.closeButton,
} = opts || {};
if (document.getElementById('textGenericOverlay')) {
return Promise.resolve(false);
}
return new Promise((resolve) => {
const overlay = document.createElement('div');
overlay.id = 'textGenericOverlay';
const modal = document.createElement('div');
modal.className = 'text-generic-modal';
const header = document.createElement('div');
header.className = 'modal-header';
header.innerHTML = `${title}`;
const body = document.createElement('div');
body.className = 'modal-body';
const content = document.createElement('div');
content.className = 'text-content';
content.textContent = text ?? '';
body.appendChild(content);
const footer = document.createElement('div');
footer.className = 'modal-footer';
if (mode === 'confirm') {
const okBtn = document.createElement('button');
okBtn.className = 'primary-btn';
okBtn.textContent = confirmText;
okBtn.addEventListener('click', () => close(true));
footer.appendChild(okBtn);
}
const closeBtn = document.createElement('button');
closeBtn.className = 'secondary-btn';
closeBtn.textContent = closeText;
closeBtn.addEventListener('click', () => close(false));
footer.appendChild(closeBtn);
modal.appendChild(header);
modal.appendChild(body);
modal.appendChild(footer);
overlay.appendChild(modal);
document.body.appendChild(overlay);
function close(result) {
overlay.style.animation = 'fadeOut 0.2s';
setTimeout(() => {
overlay.remove();
resolve(result);
}, 200);
}
overlay.addEventListener('click', (e) => {
if (e.target === overlay) close(false);
});
});
}
// 创建开关项
const createSwitchItem = ({label, description, checked, disabled = false}) => {
const item = document.createElement('div');
item.className = 'setting-item';
item.style.opacity = disabled ? 0.6 : 1;
item.innerHTML = `