mirror of
https://github.com/JoeanAmier/XHS-Downloader.git
synced 2025-12-26 04:48:05 +08:00
refactor(script): 重构用户脚本代码
1. 重构用户脚本交互界面 2. 重构用户脚本交互效果 3. 新增选择图片下载功能 4. 重构用户脚本设置面板
This commit is contained in:
parent
8420685019
commit
d972420ab3
@ -197,19 +197,21 @@ async def example_api():
|
||||
<li>程序下载记录数据储存至 <code>./ExploreID.db</code> 文件</li>
|
||||
</ul>
|
||||
<h1 id="user-scripts">🕹 用户脚本</h1>
|
||||
<p>如果您的浏览器安装了 <a href="https://www.tampermonkey.net/">Tampermonkey</a> 浏览器扩展程序,可以添加 <a href="https://raw.githubusercontent.com/JoeanAmier/XHS-Downloader/master/static/XHS-Downloader.js">用户脚本</a>,无需下载安装即可体验项目功能!</p>
|
||||
<p>如果您的浏览器安装了 <a href="https://www.tampermonkey.net/">Tampermonkey</a> 浏览器扩展程序,可以添加 <a href="https://raw.githubusercontent.com/JoeanAmier/XHS-Downloader/master/static/XHS-Downloader.js">用户脚本</a>(右键单击复制链接),无需下载安装即可体验项目功能!</p>
|
||||
<img src="static/screenshot/脚本安装教程.png" alt="">
|
||||
<p>脚本安装成功后,打开小红书页面,查看脚本说明,并根据提示操作。</p>
|
||||
<hr>
|
||||
<img src="static/screenshot/用户脚本截图1.png" alt="">
|
||||
<hr>
|
||||
<img src="static/screenshot/用户脚本截图2.png" alt="">
|
||||
<hr>
|
||||
<img src="static/screenshot/用户脚本截图3.png" alt="">
|
||||
<p>提示:使用 XHS-Downloader 用户脚本批量提取作品链接,搭配 XHS-Downloader 程序可以实现批量下载无水印作品文件!</p>
|
||||
<h2>📜 脚本说明</h2>
|
||||
<ul>
|
||||
<li>下载小红书无水印作品文件时,脚本需要花费时间处理文件,请等待片刻,请勿多次点击下载按钮</li>
|
||||
<li>无水印图片文件为 PNG 格式;无水印视频文件较大,可能需要较长的时间处理,页面跳转可能会导致下载失败</li>
|
||||
<li>提取账号发布、收藏、点赞、专辑作品链接时,脚本可以自动滚动页面直至加载全部作品</li>
|
||||
<li>提取推荐作品链接、搜索作品、用户链接时,脚本可以自动滚动指定次数加载更多内容,默认滚动次数:10 次</li>
|
||||
<li>提取推荐作品链接、搜索作品、用户链接时,脚本可以自动滚动指定次数加载更多内容,默认滚动次数:50 次</li>
|
||||
<li>自动滚动页面功能默认关闭;用户可以自由开启,并修改滚动页面次数,修改后立即生效</li>
|
||||
<li>如果未开启自动滚动页面功能,用户需要手动滚动页面以便加载更多内容后再进行其他操作</li>
|
||||
<li>支持作品文件打包下载;该功能默认开启,多个文件的作品将会以压缩包格式下载</li>
|
||||
|
||||
@ -201,19 +201,23 @@ async def example_api():
|
||||
<li>The program's download records will be stored in the <code>./ExploreID.db</code> file</li>
|
||||
</ul>
|
||||
<h1 id="user-scripts">🕹 User Script</h1>
|
||||
<p>If your browser has the <a href="https://www.tampermonkey.net/">Tampermonkey</a> browser extension installed, you can add the <a href="https://raw.githubusercontent.com/JoeanAmier/XHS-Downloader/master/static/XHS-Downloader.js">user script</a> to experience the project features without needing to download or install anything!</p>
|
||||
<p>If your browser has the <a href="https://www.tampermonkey.net/">Tampermonkey</a> browser extension installed, you can add the <a href="https://raw.githubusercontent.com/JoeanAmier/XHS-Downloader/master/static/XHS-Downloader.js">user script</a>(Right click to copy link) to experience the project features without needing to download or install anything!</p>
|
||||
<img src="static/screenshot/脚本安装教程.png" alt="">
|
||||
<p>After successfully installing the script, open the RedNote page, check the script instructions, and follow the prompts to operate.</p>
|
||||
<img src="static/screenshot/脚本安装教程.png" alt="">
|
||||
<hr>
|
||||
<img src="static/screenshot/用户脚本截图1.png" alt="">
|
||||
<hr>
|
||||
<img src="static/screenshot/用户脚本截图2.png" alt="">
|
||||
<hr>
|
||||
<img src="static/screenshot/用户脚本截图3.png" alt="">
|
||||
<p>Note: Using the XHS-Downloader user script to batch extract works links, in combination with the XHS-Downloader program, can achieve batch downloading of watermark-free works files!</p>
|
||||
<h2>📜 Script Instructions</h2>
|
||||
<ul>
|
||||
<li>When downloading watermark-free works from Xiaohongshu, the script requires time to process the files. Please wait for a moment and do not click the download button multiple times.</li>
|
||||
<li>Watermark-free image files are in PNG format; watermark-free video files are larger and may take longer to process. Page redirects may cause download failures.</li>
|
||||
<li>When extracting links for posts, collects, likes, and board from an account, the script can automatically scroll the page until all works are loaded.</li>
|
||||
<li>When extracting recommended works links, search works, and user links, the script can automatically scroll a specified number of times to load more content. The default number of page scrolls is 10.</li>
|
||||
<li>When extracting recommended works links, search works, 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.</li>
|
||||
<li>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.</li>
|
||||
<li>If the automatic page scroll feature is not enabled, users need to manually scroll the page to load more content before performing other actions.</li>
|
||||
<li>Support packaging and downloading of work files; This feature is enabled by default, and works from multiple files will be downloaded in compressed file format</li>
|
||||
|
||||
@ -19,8 +19,10 @@
|
||||
|
||||
**用户脚本更新内容:**
|
||||
|
||||
**版本号:1.8.8**
|
||||
**版本号:2.0.0**
|
||||
|
||||
1. 更新脚本功能说明
|
||||
2. 移除无效功能菜单
|
||||
3. 优化功能状态显示
|
||||
1. 重构用户脚本交互界面
|
||||
2. 重构用户脚本交互效果
|
||||
3. 新增选择图片下载功能
|
||||
4. 重构用户脚本设置面板
|
||||
5. 更新用户脚本功能说明
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
674
static/XHS-Downloader_旧版.js
Normal file
674
static/XHS-Downloader_旧版.js
Normal file
@ -0,0 +1,674 @@
|
||||
// ==UserScript==
|
||||
// @name XHS-Downloader
|
||||
// @namespace https://github.com/JoeanAmier/XHS-Downloader
|
||||
// @version 1.8.8
|
||||
// @description 提取小红书作品/用户链接,下载小红书无水印图文/视频作品文件
|
||||
// @author JoeanAmier
|
||||
// @match http*://xhslink.com/*
|
||||
// @match http*://www.xiaohongshu.com/explore*
|
||||
// @match http*://www.xiaohongshu.com/user/profile/*
|
||||
// @match http*://www.xiaohongshu.com/search_result*
|
||||
// @match http*://www.xiaohongshu.com/board/*
|
||||
// @icon data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBzdGFuZGFsb25lPSJubyI/PjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMjAwMTA5MDQvL0VOIiAiaHR0cDovL3d3dy53My5vcmcvVFIvMjAwMS9SRUMtU1ZHLTIwMDEwOTA0L0RURC9zdmcxMC5kdGQiPjxzdmcgdmVyc2lvbj0iMS4wIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNTYuMDAwMDAwcHQiIGhlaWdodD0iMjU2LjAwMDAwMHB0IiB2aWV3Qm94PSIwIDAgMjU2LjAwMDAwMCAyNTYuMDAwMDAwIiBwcmVzZXJ2ZUFzcGVjdFJhdGlvPSJ4TWlkWU1pZCBtZWV0Ij48ZyB0cmFuc2Zvcm09InRyYW5zbGF0ZSgwLjAwMDAwMCwyNTYuMDAwMDAwKSBzY2FsZSgwLjEwMDAwMCwtMC4xMDAwMDApIiBmaWxsPSIjZTgzYjM1IiBzdHJva2U9Im5vbmUiPjxwYXRoIGQ9Ik0xMTI1IDI1NDMgYy02MyAtNyAtMjIxIC00NiAtMjgyIC02OSAtMTc3IC02NyAtMzIxIC0xNjAgLTQ1OSAtMjk4IC0xOTQgLTE5NCAtMzEzIC00MjIgLTM1OSAtNjgzIC0xOSAtMTExIC0xOSAtMzI2IDAgLTQzOSA3NCAtNDIzIDM3OCAtNzk4IDc3NiAtOTU4IDQ4OSAtMTk3IDEwMzUgLTgwIDEzOTkgMzAwIDEzOCAxNDUgMjM2IDMwNyAyOTQgNDg3IDg1IDI2MCA4MSA1NjYgLTEwIDgxOSAtMTU2IDQzNCAtNTM4IDc1NCAtOTkzIDgzMiAtODYgMTQgLTI4MiAyMCAtMzY2IDl6IG01MCAtODEwIGw4MCAtMzAgMyAtMzg5IDIgLTM5MCAtODcgMjkgYy0xMzIgNDQgLTMwMiA0OCAtNDQwIDExIGwtMjMgLTYgMCAzOTEgYzAgMjE1IDMgMzkxIDggMzkxIDQgMSAzMCA3IDU3IDE1IDM2IDExIDg5IDE0IDE4NSAxMSAxMjEgLTIgMTQzIC02IDIxNSAtMzN6IG02MzggMTggbDM3IC0xMiAwIC0zODkgYzAgLTIxNSAtMiAtMzkwIC00IC0zOTAgLTMgMCAtMzUgNyAtNzMgMTYgLTEwOCAyNSAtMjc0IDE1IC0zODUgLTIzIGwtODggLTMxIDAgMzg4IDAgMzg3IDM5IDE4IGMxMDAgNDQgMTYwIDU1IDMwMSA1MSA3NCAtMiAxNTIgLTkgMTczIC0xNXogbS0xMTQzIC00NTggYzAgLTE5NyA0IC0zNjQgOCAtMzcxIDcgLTkgMjUgLTggODcgOCAxNDUgMzcgMzE1IDI0IDQ1NiAtMzUgbDYxIC0yNiA3MSAzMCBjNDAgMTYgMTAwIDM1IDEzNSA0MSA4MSAxNiAyNzEgOCAzMzIgLTE0IDI0IC05IDUwIC0xNCA1NyAtMTIgMTAgNCAxMyA4MSAxMyAzNzEgbDAgMzY1IDQ1IDAgNDUgMCAwIC00MjkgYzAgLTIzNyAtMyAtNDMyIC04IC00MzUgLTQgLTIgLTU5IDExIC0xMjEgMzAgLTYzIDE5IC0xNDYgMzcgLTE4NSA0MCAtODcgOCAtMjE0IC0xNCAtMjkzIC01MSAtNzEgLTMyIC0xMDUgLTMyIC0xOTYgNSAtMTUzIDYxIC0yODEgNjMgLTQ3MCA1IC02MCAtMTkgLTExNCAtMzIgLTExOSAtMjkgLTQgMyAtOCAxOTggLTggNDM1IGwwIDQyOSA0NSAwIDQ1IDAgMCAtMzU3eiIvPjxwYXRoIGQ9Ik04NTcgMTYwMyBjLTI0IC0yMyAtMSAtMzEgMTA2IC0zNiA2MSAtNCAxMjMgLTggMTM3IC05IDQ1IC01IDI5IDI2IC0yMCAzOSAtNDkgMTIgLTIxMiAxNyAtMjIzIDZ6Ii8+PHBhdGggZD0iTTg1NyAxNDYzIGMtMjQgLTIzIC0yIC0zMSAxMDEgLTM3IDU5IC0zIDEyMSAtNyAxMzcgLTggMjEgLTIgMzAgMSAzMCAxMiAwIDEwIC0xOCAxOSAtNTAgMjYgLTU1IDEzIC0yMDggMTggLTIxOCA3eiIvPjxwYXRoIGQ9Ik04NjMgMTMyNCBjLTcgLTMgLTEzIC0xMSAtMTMgLTE5IDAgLTkgMzAgLTE0IDEwOCAtMTkgNTkgLTMgMTE4IC03IDEzMiAtOSAzNiAtMyAzOSAxNiA2IDMxIC0yOCAxNCAtMjExIDI2IC0yMzMgMTZ6Ii8+PHBhdGggZD0iTTg2OCAxMTgzIGMtMTAgLTIgLTE4IC0xMSAtMTggLTE5IDAgLTExIDE3IC0xNCA4MSAtMTQgNDQgMCAxMDAgLTUgMTI0IC0xMSA1MiAtMTMgODMgLTYgNjYgMTUgLTIyIDI2IC0xOTEgNDYgLTI1MyAyOXoiLz48cGF0aCBkPSJNMTQ4OCAxNTk4IGMtMzggLTggLTQ4IC0xNCAtNDggLTMwIDAgLTE3IDQgLTE5IDI4IC0xMyAxNSAzIDc2IDkgMTM1IDEyIDkzIDUgMTA4IDggMTA1IDIyIC0zIDEzIC0xOCAxNiAtODggMTcgLTQ3IDEgLTEwNiAtMyAtMTMyIC04eiIvPjxwYXRoIGQ9Ik0xNDkwIDE0NTcgYy0zMCAtOCAtNDYgLTE4IC00OCAtMzEgLTMgLTE2IDAgLTE4IDI1IC0xMiAxNSA0IDc3IDEwIDEzNiAxMyA5MyA1IDEwOCA4IDEwNSAyMiAtMyAxMyAtMTggMTYgLTg4IDE4IC00NyAxIC0xMDUgLTQgLTEzMCAtMTB6Ii8+PHBhdGggZD0iTTE0OTUgMTMxNiBjLTM1IC04IC01MSAtMTcgLTUzIC0zMCAtMyAtMTYgMCAtMTggMjUgLTEyIDE1IDQgNzYgMTAgMTM2IDEzIDc2IDQgMTA3IDkgMTA3IDE4IDAgNyAtNyAxNiAtMTYgMTkgLTI1IDEwIC0xNDQgNSAtMTk5IC04eiIvPjxwYXRoIGQ9Ik0xNTAwIDExNzcgYy00MCAtOSAtNTYgLTE3IC01OCAtMzEgLTMgLTE2IDAgLTE4IDMwIC0xMiAxOCAzIDc2IDkgMTI4IDEyIDExOSA3IDEzMiAxMCAxMDYgMzAgLTI0IDE3IC0xMjYgMTcgLTIwNiAxeiIvPjwvZz48L3N2Zz4=
|
||||
// @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
|
||||
// @require https://cdnjs.cloudflare.com/ajax/libs/jszip/3.9.1/jszip.min.js
|
||||
// ==/UserScript==
|
||||
|
||||
(function () {
|
||||
let config = {
|
||||
disclaimer: GM_getValue("disclaimer", false),
|
||||
packageDownloadFiles: GM_getValue("packageDownloadFiles", true),
|
||||
autoScrollSwitch: GM_getValue("autoScrollSwitch", false), // scrollCheckTime: GM_getValue("scrollCheckTime", 2500),
|
||||
maxScrollCount: GM_getValue("maxScrollCount", 10),
|
||||
};
|
||||
|
||||
let menu = {
|
||||
disclaimer: undefined, packageDownloadFiles: undefined, autoScrollSwitch: undefined, // scrollCheckTime: undefined,
|
||||
maxScrollCount: undefined,
|
||||
};
|
||||
|
||||
const readme = () => {
|
||||
const instructions = `
|
||||
XHS-Downloader 用户脚本 功能清单:
|
||||
1. 下载小红书无水印作品文件
|
||||
2. 提取推荐页面作品链接
|
||||
3. 提取账号发布作品链接
|
||||
4. 提取账号收藏作品链接
|
||||
5. 提取账号专辑作品链接
|
||||
6. 提取账号点赞作品链接
|
||||
7. 提取搜索结果作品链接
|
||||
8. 提取搜索结果用户链接
|
||||
|
||||
XHS-Downloader 用户脚本 详细说明:
|
||||
1. 下载小红书无水印作品文件时,脚本需要花费时间处理文件,请等待片刻,请勿多次点击下载按钮
|
||||
2. 无水印作品文件较大,可能需要较长的时间处理,页面跳转可能会导致下载失败
|
||||
3. 提取账号发布、收藏、点赞、专辑作品链接时,脚本可以自动滚动页面直至加载全部作品
|
||||
4. 提取推荐作品链接、搜索作品、用户链接时,脚本可以自动滚动指定次数加载更多内容,默认滚动次数:10 次
|
||||
5. 自动滚动页面功能默认关闭;用户可以自由开启,并修改滚动页面次数,修改后立即生效
|
||||
6. 如果未开启自动滚动页面功能,用户需要手动滚动页面以便加载更多内容后再进行其他操作
|
||||
7. 支持作品文件打包下载;该功能默认开启,多个文件的作品将会以压缩包格式下载
|
||||
|
||||
项目开源地址:https://github.com/JoeanAmier/XHS-Downloader
|
||||
`
|
||||
const disclaimer_content = `
|
||||
关于 XHS-Downloader 的 免责声明:
|
||||
|
||||
1. 使用者对本项目的使用由使用者自行决定,并自行承担风险。作者对使用者使用本项目所产生的任何损失、责任、或风险概不负责。
|
||||
2. 本项目的作者提供的代码和功能是基于现有知识和技术的开发成果。作者尽力确保代码的正确性和安全性,但不保证代码完全没有错误或缺陷。
|
||||
3. 使用者在使用本项目时必须严格遵守 GNU General Public License v3.0 的要求,并在适当的地方注明使用了 GNU General Public License v3.0 的代码。
|
||||
4. 使用者在任何情况下均不得将本项目的作者、贡献者或其他相关方与使用者的使用行为联系起来,或要求其对使用者使用本项目所产生的任何损失或损害负责。
|
||||
5. 使用者在使用本项目的代码和功能时,必须自行研究相关法律法规,并确保其使用行为合法合规。任何因违反法律法规而导致的法律责任和风险,均由使用者自行承担。
|
||||
6. 本项目的作者不会提供 XHS-Downloader 项目的付费版本,也不会提供与 XHS-Downloader 项目相关的任何商业服务。
|
||||
7. 基于本项目进行的任何二次开发、修改或编译的程序与原创作者无关,原创作者不承担与二次开发行为或其结果相关的任何责任,使用者应自行对因二次开发可能带来的各种情况负全部责任。
|
||||
|
||||
在使用本项目的代码和功能之前,请您认真考虑并接受以上免责声明。如果您对上述声明有任何疑问或不同意,请不要使用本项目的代码和功能。如果您使用了本项目的代码和功能,则视为您已完全理解并接受上述免责声明,并自愿承担使用本项目的一切风险和后果。
|
||||
|
||||
是否已阅读 XHS-Downloader 功能说明与免责声明(YES/NO)
|
||||
`
|
||||
alert(instructions);
|
||||
alert(`自动滚动页面功能代码已重构,该功能默认关闭!
|
||||
启用该功能可能会被小红书检测为自动化操作,从而导致账号受到风控或封禁!
|
||||
该功能在使用过程中遇到任何问题请及时向开发者反馈!
|
||||
`);
|
||||
if (!config.disclaimer) {
|
||||
const answer = prompt(disclaimer_content, "");
|
||||
if (answer === null) {
|
||||
GM_setValue("disclaimer", false);
|
||||
config.disclaimer = false;
|
||||
} else {
|
||||
GM_setValue("disclaimer", answer.toUpperCase() === "YES" || answer.toUpperCase() === "Y");
|
||||
config.disclaimer = GM_getValue("disclaimer");
|
||||
location.reload();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (!config.disclaimer) {
|
||||
readme();
|
||||
}
|
||||
|
||||
menu.disclaimer = GM_registerMenuCommand("关于 XHS-Downloader", function () {
|
||||
readme();
|
||||
});
|
||||
|
||||
const packageDownloadFilesMenu = () => {
|
||||
menu.packageDownloadFiles = GM_registerMenuCommand(`文件打包下载功能 ${config.packageDownloadFiles ? '✔️' : '❌'}`, function () {
|
||||
config.packageDownloadFiles = !config.packageDownloadFiles;
|
||||
GM_setValue("packageDownloadFiles", config.packageDownloadFiles);
|
||||
GM_unregisterMenuCommand(menu.packageDownloadFiles);
|
||||
packageDownloadFilesMenu();
|
||||
}, {title: "单击切换功能状态",});
|
||||
};
|
||||
|
||||
packageDownloadFilesMenu();
|
||||
|
||||
const autoScrollSwitchMenu = () => {
|
||||
menu.autoScrollSwitch = GM_registerMenuCommand(`自动滚动页面功能 ${config.autoScrollSwitch ? '✔️' : '❌'}`, function () {
|
||||
config.autoScrollSwitch = !config.autoScrollSwitch;
|
||||
GM_setValue("autoScrollSwitch", config.autoScrollSwitch);
|
||||
GM_unregisterMenuCommand(menu.autoScrollSwitch);
|
||||
autoScrollSwitchMenu();
|
||||
}, {title: "单击切换功能状态",});
|
||||
};
|
||||
|
||||
autoScrollSwitchMenu();
|
||||
|
||||
// menu.scrollCheckTime = GM_registerMenuCommand("修改滚动检测间隔", function () {
|
||||
// let data;
|
||||
// data = prompt("请输入自动滚动页面检测间隔:\n如果网络环境不佳导致脚本未能加载全部作品,可以设置较大的检测间隔!", config.scrollCheckTime / 1000);
|
||||
// if (data === null) {
|
||||
// return
|
||||
// }
|
||||
// data = parseFloat(data) || 2.5
|
||||
// config.scrollCheckTime = data * 1000;
|
||||
// GM_setValue("scrollCheckTime", config.scrollCheckTime);
|
||||
// alert(`修改自动滚动页面检测间隔成功,当前值:${data} 秒`);
|
||||
// });
|
||||
|
||||
menu.maxScrollCount = GM_registerMenuCommand("修改滚动页面次数", function () {
|
||||
let data;
|
||||
data = prompt("请输入自动滚动页面次数:\n仅对提取推荐作品、搜索作品、搜索用户链接功能生效!", config.maxScrollCount);
|
||||
if (data === null) {
|
||||
return
|
||||
}
|
||||
config.maxScrollCount = parseInt(data) || 10;
|
||||
GM_setValue("maxScrollCount", config.maxScrollCount);
|
||||
alert(`修改自动滚动页面次数成功,当前值:${config.maxScrollCount} 次`);
|
||||
});
|
||||
|
||||
const icon = "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBzdGFuZGFsb25lPSJubyI/PjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMjAwMTA5MDQvL0VOIiAiaHR0cDovL3d3dy53My5vcmcvVFIvMjAwMS9SRUMtU1ZHLTIwMDEwOTA0L0RURC9zdmcxMC5kdGQiPjxzdmcgdmVyc2lvbj0iMS4wIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNTYuMDAwMDAwcHQiIGhlaWdodD0iMjU2LjAwMDAwMHB0IiB2aWV3Qm94PSIwIDAgMjU2LjAwMDAwMCAyNTYuMDAwMDAwIiBwcmVzZXJ2ZUFzcGVjdFJhdGlvPSJ4TWlkWU1pZCBtZWV0Ij48ZyB0cmFuc2Zvcm09InRyYW5zbGF0ZSgwLjAwMDAwMCwyNTYuMDAwMDAwKSBzY2FsZSgwLjEwMDAwMCwtMC4xMDAwMDApIiBmaWxsPSIjZTgzYjM1IiBzdHJva2U9Im5vbmUiPjxwYXRoIGQ9Ik0xMTI1IDI1NDMgYy02MyAtNyAtMjIxIC00NiAtMjgyIC02OSAtMTc3IC02NyAtMzIxIC0xNjAgLTQ1OSAtMjk4IC0xOTQgLTE5NCAtMzEzIC00MjIgLTM1OSAtNjgzIC0xOSAtMTExIC0xOSAtMzI2IDAgLTQzOSA3NCAtNDIzIDM3OCAtNzk4IDc3NiAtOTU4IDQ4OSAtMTk3IDEwMzUgLTgwIDEzOTkgMzAwIDEzOCAxNDUgMjM2IDMwNyAyOTQgNDg3IDg1IDI2MCA4MSA1NjYgLTEwIDgxOSAtMTU2IDQzNCAtNTM4IDc1NCAtOTkzIDgzMiAtODYgMTQgLTI4MiAyMCAtMzY2IDl6IG01MCAtODEwIGw4MCAtMzAgMyAtMzg5IDIgLTM5MCAtODcgMjkgYy0xMzIgNDQgLTMwMiA0OCAtNDQwIDExIGwtMjMgLTYgMCAzOTEgYzAgMjE1IDMgMzkxIDggMzkxIDQgMSAzMCA3IDU3IDE1IDM2IDExIDg5IDE0IDE4NSAxMSAxMjEgLTIgMTQzIC02IDIxNSAtMzN6IG02MzggMTggbDM3IC0xMiAwIC0zODkgYzAgLTIxNSAtMiAtMzkwIC00IC0zOTAgLTMgMCAtMzUgNyAtNzMgMTYgLTEwOCAyNSAtMjc0IDE1IC0zODUgLTIzIGwtODggLTMxIDAgMzg4IDAgMzg3IDM5IDE4IGMxMDAgNDQgMTYwIDU1IDMwMSA1MSA3NCAtMiAxNTIgLTkgMTczIC0xNXogbS0xMTQzIC00NTggYzAgLTE5NyA0IC0zNjQgOCAtMzcxIDcgLTkgMjUgLTggODcgOCAxNDUgMzcgMzE1IDI0IDQ1NiAtMzUgbDYxIC0yNiA3MSAzMCBjNDAgMTYgMTAwIDM1IDEzNSA0MSA4MSAxNiAyNzEgOCAzMzIgLTE0IDI0IC05IDUwIC0xNCA1NyAtMTIgMTAgNCAxMyA4MSAxMyAzNzEgbDAgMzY1IDQ1IDAgNDUgMCAwIC00MjkgYzAgLTIzNyAtMyAtNDMyIC04IC00MzUgLTQgLTIgLTU5IDExIC0xMjEgMzAgLTYzIDE5IC0xNDYgMzcgLTE4NSA0MCAtODcgOCAtMjE0IC0xNCAtMjkzIC01MSAtNzEgLTMyIC0xMDUgLTMyIC0xOTYgNSAtMTUzIDYxIC0yODEgNjMgLTQ3MCA1IC02MCAtMTkgLTExNCAtMzIgLTExOSAtMjkgLTQgMyAtOCAxOTggLTggNDM1IGwwIDQyOSA0NSAwIDQ1IDAgMCAtMzU3eiIvPjxwYXRoIGQ9Ik04NTcgMTYwMyBjLTI0IC0yMyAtMSAtMzEgMTA2IC0zNiA2MSAtNCAxMjMgLTggMTM3IC05IDQ1IC01IDI5IDI2IC0yMCAzOSAtNDkgMTIgLTIxMiAxNyAtMjIzIDZ6Ii8+PHBhdGggZD0iTTg1NyAxNDYzIGMtMjQgLTIzIC0yIC0zMSAxMDEgLTM3IDU5IC0zIDEyMSAtNyAxMzcgLTggMjEgLTIgMzAgMSAzMCAxMiAwIDEwIC0xOCAxOSAtNTAgMjYgLTU1IDEzIC0yMDggMTggLTIxOCA3eiIvPjxwYXRoIGQ9Ik04NjMgMTMyNCBjLTcgLTMgLTEzIC0xMSAtMTMgLTE5IDAgLTkgMzAgLTE0IDEwOCAtMTkgNTkgLTMgMTE4IC03IDEzMiAtOSAzNiAtMyAzOSAxNiA2IDMxIC0yOCAxNCAtMjExIDI2IC0yMzMgMTZ6Ii8+PHBhdGggZD0iTTg2OCAxMTgzIGMtMTAgLTIgLTE4IC0xMSAtMTggLTE5IDAgLTExIDE3IC0xNCA4MSAtMTQgNDQgMCAxMDAgLTUgMTI0IC0xMSA1MiAtMTMgODMgLTYgNjYgMTUgLTIyIDI2IC0xOTEgNDYgLTI1MyAyOXoiLz48cGF0aCBkPSJNMTQ4OCAxNTk4IGMtMzggLTggLTQ4IC0xNCAtNDggLTMwIDAgLTE3IDQgLTE5IDI4IC0xMyAxNSAzIDc2IDkgMTM1IDEyIDkzIDUgMTA4IDggMTA1IDIyIC0zIDEzIC0xOCAxNiAtODggMTcgLTQ3IDEgLTEwNiAtMyAtMTMyIC04eiIvPjxwYXRoIGQ9Ik0xNDkwIDE0NTcgYy0zMCAtOCAtNDYgLTE4IC00OCAtMzEgLTMgLTE2IDAgLTE4IDI1IC0xMiAxNSA0IDc3IDEwIDEzNiAxMyA5MyA1IDEwOCA4IDEwNSAyMiAtMyAxMyAtMTggMTYgLTg4IDE4IC00NyAxIC0xMDUgLTQgLTEzMCAtMTB6Ii8+PHBhdGggZD0iTTE0OTUgMTMxNiBjLTM1IC04IC01MSAtMTcgLTUzIC0zMCAtMyAtMTYgMCAtMTggMjUgLTEyIDE1IDQgNzYgMTAgMTM2IDEzIDc2IDQgMTA3IDkgMTA3IDE4IDAgNyAtNyAxNiAtMTYgMTkgLTI1IDEwIC0xNDQgNSAtMTk5IC04eiIvPjxwYXRoIGQ9Ik0xNTAwIDExNzcgYy00MCAtOSAtNTYgLTE3IC01OCAtMzEgLTMgLTE2IDAgLTE4IDMwIC0xMiAxOCAzIDc2IDkgMTI4IDEyIDExOSA3IDEzMiAxMCAxMDYgMzAgLTI0IDE3IC0xMjYgMTcgLTIwNiAxeiIvPjwvZz48L3N2Zz4=";
|
||||
|
||||
const about = () => {
|
||||
window.open('https://github.com/JoeanAmier/XHS-Downloader', '_blank');
|
||||
}
|
||||
|
||||
const abnormal = () => {
|
||||
alert("下载无水印作品文件失败!请向作者反馈!\n项目地址:https://github.com/JoeanAmier/XHS-Downloader");
|
||||
};
|
||||
|
||||
const generateVideoUrl = note => {
|
||||
try {
|
||||
return [`https://sns-video-bd.xhscdn.com/${note.video.consumer.originVideoKey}`];
|
||||
} catch (error) {
|
||||
console.error("Error generating 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) => {
|
||||
let match = item.urlDefault.match(regex);
|
||||
if (match && match[1]) {
|
||||
urls.push(`https://ci.xiaohongshu.com/${match[1]}?imageView2/format/png`);
|
||||
}
|
||||
})
|
||||
return urls
|
||||
} catch (error) {
|
||||
console.error("Error generating image URLs:", error);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
const download = async (urls, type_) => {
|
||||
const name = extractName();
|
||||
console.info(`文件名称 ${name}`);
|
||||
if (type_ === "video") {
|
||||
await downloadVideo(urls[0], name);
|
||||
} else {
|
||||
await downloadImage(urls, name);
|
||||
}
|
||||
};
|
||||
|
||||
const exploreDeal = async note => {
|
||||
try {
|
||||
let links;
|
||||
if (note.type === "normal") {
|
||||
links = generateImageUrl(note);
|
||||
} else {
|
||||
links = generateVideoUrl(note);
|
||||
}
|
||||
if (links.length > 0) {
|
||||
console.info("下载链接", links);
|
||||
await download(links, note.type);
|
||||
} else {
|
||||
abnormal()
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error in exploreDeal function:", error);
|
||||
abnormal();
|
||||
}
|
||||
};
|
||||
|
||||
const extractNoteInfo = () => {
|
||||
const regex = /\/explore\/([^?]+)/;
|
||||
const match = currentUrl.match(regex);
|
||||
if (match) {
|
||||
return unsafeWindow.__INITIAL_STATE__.note.noteDetailMap[match[1]]
|
||||
} else {
|
||||
console.error("从链接提取作品 ID 失败", currentUrl,);
|
||||
}
|
||||
};
|
||||
|
||||
const extractDownloadLinks = async () => {
|
||||
if (currentUrl.includes("https://www.xiaohongshu.com/explore/")) {
|
||||
let note = extractNoteInfo();
|
||||
if (note.note) {
|
||||
await exploreDeal(note.note);
|
||||
} else {
|
||||
abnormal();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
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.info(`文件已成功下载: ${name}`);
|
||||
}
|
||||
|
||||
const downloadFile = async (link, name, trigger = true, retries = 5) => {
|
||||
for (let attempt = 1; attempt <= retries; attempt++) {
|
||||
try {
|
||||
// 使用 fetch 获取文件数据
|
||||
const response = await fetch(link, {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 (urls, name,) => {
|
||||
const downloadResults = []; // 用于存储下载结果
|
||||
|
||||
const downloadPromises = urls.map(async (url, index) => {
|
||||
const fileName = `${name}_${index + 1}.png`; // 根据索引生成文件名
|
||||
const result = await downloadFile(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(/\/([^\/]+)$/);
|
||||
let id = match ? match[1] : null;
|
||||
return name === "" ? id : name
|
||||
};
|
||||
|
||||
const downloadVideo = async (url, name) => {
|
||||
if (!await downloadFile(url, `${name}.mp4`)) {
|
||||
abnormal();
|
||||
}
|
||||
};
|
||||
|
||||
const downloadImage = async (urls, name) => {
|
||||
let success;
|
||||
if (!config.packageDownloadFiles) {
|
||||
let result = [];
|
||||
for (const [index, url] of urls.entries()) {
|
||||
result.push(await downloadFile(url, `${name}_${index + 1}.png`));
|
||||
}
|
||||
success = result.every(item => item === true);
|
||||
} else if (urls.length > 1) {
|
||||
success = await downloadFiles(urls, name,);
|
||||
} else {
|
||||
success = await downloadFile(urls[0], `${name}.png`);
|
||||
}
|
||||
if (!success) {
|
||||
abnormal();
|
||||
}
|
||||
};
|
||||
|
||||
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.map(item => [item.id, item.xsecToken,]);
|
||||
};
|
||||
|
||||
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,]);
|
||||
} else {
|
||||
console.error("从链接提取专辑 ID 失败", currentUrl,);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
const extractFeedInfo = () => {
|
||||
const notesRawValue = unsafeWindow.__INITIAL_STATE__.feed.feeds._rawValue;
|
||||
return notesRawValue.map(item => [item.id, item.xsecToken,]);
|
||||
};
|
||||
|
||||
const extractSearchNotes = () => {
|
||||
const notesRawValue = unsafeWindow.__INITIAL_STATE__.search.feeds._rawValue;
|
||||
return notesRawValue.map(item => [item.id, item.xsecToken,]);
|
||||
}
|
||||
|
||||
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 extractAllLinks = (callback, order) => {
|
||||
scrollScreenEvent(() => {
|
||||
let data;
|
||||
if (order >= 0 && order <= 2) {
|
||||
data = extractNotesInfo(order);
|
||||
} 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 = [];
|
||||
}
|
||||
let urlsString = order !== 4 ? generateNoteUrls(data) : generateUserUrls(data);
|
||||
callback(urlsString);
|
||||
}, [0, 1, 2, 5].includes(order))
|
||||
};
|
||||
|
||||
const extractAllLinksEvent = (order = 0) => {
|
||||
extractAllLinks(urlsString => {
|
||||
if (urlsString) {
|
||||
GM_setClipboard(urlsString, "text", () => {
|
||||
alert('作品/用户链接已复制到剪贴板!');
|
||||
});
|
||||
} else {
|
||||
alert("未提取到任何作品/用户链接!")
|
||||
}
|
||||
}, order);
|
||||
};
|
||||
|
||||
const createContainer = () => {
|
||||
let container = document.createElement('div');
|
||||
container.id = 'xhsFunctionContainer';
|
||||
|
||||
let imgTextContainer = document.createElement('div');
|
||||
imgTextContainer.id = 'xhsImgTextContainer';
|
||||
|
||||
let img = new Image(48, 48); // 确保 icon 变量已定义
|
||||
img.src = icon;
|
||||
img.style.borderRadius = '50%';
|
||||
img.style.objectFit = 'cover';
|
||||
|
||||
let textDiv = document.createElement('div');
|
||||
textDiv.id = 'xhsImgTextContainer__text'
|
||||
textDiv.textContent = 'XHS-Downloader';
|
||||
|
||||
imgTextContainer.appendChild(img);
|
||||
imgTextContainer.appendChild(textDiv);
|
||||
|
||||
container.appendChild(imgTextContainer);
|
||||
|
||||
document.body.appendChild(container);
|
||||
return container;
|
||||
};
|
||||
|
||||
const createButton = (id, text, onClick, ...args) => {
|
||||
let button = document.createElement('button');
|
||||
button.id = id;
|
||||
button.textContent = text;
|
||||
button.addEventListener('click', () => onClick(...args));
|
||||
return button;
|
||||
};
|
||||
|
||||
const exclusionButton = ["xhsImgTextContainer", "About"];
|
||||
|
||||
const updateContainer = buttons => {
|
||||
let container = document.getElementById('xhsFunctionContainer');
|
||||
if (!container) {
|
||||
container = createContainer();
|
||||
}
|
||||
|
||||
// 移除除了 imgTextContainer 以外的所有子元素
|
||||
Array.from(container.children).forEach(child => {
|
||||
if (!exclusionButton.includes(child.id)) {
|
||||
child.remove();
|
||||
}
|
||||
});
|
||||
|
||||
// 添加有效按钮
|
||||
buttons.forEach(button => {
|
||||
container.appendChild(button);
|
||||
});
|
||||
};
|
||||
|
||||
const buttons = [
|
||||
createButton("Download", "下载无水印作品文件", extractDownloadLinks),
|
||||
createButton("Post", "提取发布作品链接", extractAllLinksEvent, 0),
|
||||
createButton("Collection", "提取收藏作品链接", extractAllLinksEvent, 1),
|
||||
createButton("Favorite", "提取点赞作品链接", extractAllLinksEvent, 2),
|
||||
createButton("Feed", "提取推荐作品链接", extractAllLinksEvent, -1),
|
||||
createButton("Search", "提取搜索作品链接", extractAllLinksEvent, 3),
|
||||
createButton("User", "提取搜索用户链接", extractAllLinksEvent, 4),
|
||||
createButton("Board", "提取专辑作品链接", extractAllLinksEvent, 5),
|
||||
createButton("Disclaimer", "脚本说明及免责声明", readme,),
|
||||
createButton("About", "关于 XHS-Downloader", about,),];
|
||||
|
||||
const run = url => {
|
||||
setTimeout(function () {
|
||||
if (!config.disclaimer) {
|
||||
} else if (url === "https://www.xiaohongshu.com/explore" || url.includes("https://www.xiaohongshu.com/explore?")) {
|
||||
updateContainer(buttons.slice(4, 5));
|
||||
} else if (url.includes("https://www.xiaohongshu.com/explore/")) {
|
||||
updateContainer(buttons.slice(0, 1));
|
||||
} else if (url.includes("https://www.xiaohongshu.com/user/profile/")) {
|
||||
updateContainer(buttons.slice(1, 4));
|
||||
} else if (url.includes("https://www.xiaohongshu.com/search_result")) {
|
||||
updateContainer(buttons.slice(5, 7));
|
||||
} else if (url.includes("https://www.xiaohongshu.com/board/")) {
|
||||
updateContainer(buttons.slice(7, 8));
|
||||
}
|
||||
}, 500)
|
||||
}
|
||||
|
||||
let currentUrl = window.location.href;
|
||||
|
||||
updateContainer(buttons.slice(8));
|
||||
|
||||
// 初始化容器
|
||||
run(currentUrl)
|
||||
|
||||
// 设置 MutationObserver 来监听 URL 变化
|
||||
let observer
|
||||
if (config.disclaimer) {
|
||||
observer = new MutationObserver(function () {
|
||||
if (currentUrl !== window.location.href) {
|
||||
currentUrl = window.location.href;
|
||||
run(currentUrl);
|
||||
}
|
||||
});
|
||||
const config = {childList: true, subtree: true};
|
||||
observer.observe(document.body, config);
|
||||
}
|
||||
|
||||
const buttonStyle = `
|
||||
#xhsFunctionContainer {
|
||||
position: fixed;
|
||||
bottom: 15%;
|
||||
background-color: #fff;
|
||||
color: #2f3542;
|
||||
padding: 5px 10px;
|
||||
border-radius: 0 32px 32px 0;
|
||||
box-shadow: 0 3.2px 12px #00000014, 0 5px 24px #0000000a;
|
||||
transition: width 0.25s ease-in-out, border-radius 0.25s ease-in-out, height 0.25s ease-in-out;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
width: 65px; /* 初始宽度 */
|
||||
height: 60px;
|
||||
text-align: center;
|
||||
font-size: 16px;
|
||||
display: flex;
|
||||
flex-direction: column-reverse;
|
||||
z-index: 99999;
|
||||
}
|
||||
|
||||
#xhsFunctionContainer:hover {
|
||||
padding: 10px 10px 5px 10px;
|
||||
width: 210px; /* hover时的宽度 */
|
||||
height: auto;
|
||||
}
|
||||
|
||||
#xhsFunctionContainer button {
|
||||
cursor: pointer;
|
||||
height: 48px;
|
||||
color: #ff4757;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
border-radius: 32px;
|
||||
margin-bottom: 14px;
|
||||
border: 3px #ff4757 solid;
|
||||
}
|
||||
|
||||
#xhsFunctionContainer button:active {
|
||||
background-color: #ff4757; /* 点击时的背景颜色 */
|
||||
}
|
||||
|
||||
#xhsImgTextContainer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
#xhsImgTextContainer__text {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
}
|
||||
`;
|
||||
|
||||
const head = document.head || document.getElementsByTagName('head')[0];
|
||||
const style = document.createElement('style');
|
||||
head.appendChild(style);
|
||||
|
||||
style.type = 'text/css';
|
||||
style.appendChild(document.createTextNode(buttonStyle));
|
||||
console.info("用户接受 XHS-Downloader 免责声明", config.disclaimer)
|
||||
|
||||
if (typeof JSZip === 'undefined') {
|
||||
alert("XHS-Downloader 用户脚本依赖库 JSZip 加载失败,作品文件打包下载功能无法使用,请尝试刷新网页或者向作者反馈!");
|
||||
}
|
||||
})();
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 100 KiB After Width: | Height: | Size: 138 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 84 KiB After Width: | Height: | Size: 814 KiB |
BIN
static/screenshot/用户脚本截图3.png
Normal file
BIN
static/screenshot/用户脚本截图3.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 766 KiB |
Loading…
x
Reference in New Issue
Block a user