// ==UserScript==
// @name 小说下载-红袖招
// @namespace http://tampermonkey.net/
// @version 0.0.4
// @description 小说下载,个人测试使用,主要是为了熟悉js的语法
// @author You
// @match https://hongxiue.com/*
// @match https://hongxiuf.com/*
// @icon https://www.google.com/s2/favicons?sz=64&domain=ixunshu.net
// @grant GM_registerMenuCommand
// @grant GM_unregisterMenuCommand
// @grant GM_xmlhttpRequest
// @grant GM_setValue
// @grant GM_getValue
// @license MIT
// @require https://cdn.bootcdn.net/ajax/libs/jquery/3.6.3/jquery.min.js
// @require https://cdn.bootcdn.net/ajax/libs/FileSaver.js/2.0.5/FileSaver.min.js
// ==/UserScript==
var g_chapterURLList = [];//全部章节列表
var g_paragraphList = [];//段落内容列表 [临时变量] 所有的段落和在一起就是一本书
var g_chapterList = [];//章节内容列表
var g_bTestDownload = false;
var g_iTestDownloadCnt = 1;
var g_handleCnt = 0;
var g_chapterPromises = [];
var g_iMaxPromiseCount = 3;
var g_needSleep = false;
var g_replaceMap = new Map([
['\uE290','操'],['\uE291','嫩'],['\uE292','扭'],['\uE293','揉'],['\uE294','硬'],['\uE295','奸'],['\uE296','吸'],['\uE297','处'],['\uE298','道'],['\uE299','毛'],['\uE29A','捅'],['\uE29B','催'],['\uE29C','身'],['\uE29D','捏'],['\uE29E','芭'],['\uE29F','股'],['\uE2A0','搞'],['\uE2A1','喘'],['\uE2A2','翻'],['\uE2A3','握'],['\uE2A5','入'],['\uE2A7','翘'],['\uE2A8','迷'],['\uE2A9','嘴'],['\uE2AA','扒'],['\uE2AB','摸'],['\uE2AC','抽'],['\uE2AD','耻'],['\uE2AE','裸'],['\uE2AF','弄'],['\uE2B0','臀'],['\uE2B1','腹'],['\uE2B2','鸡'],['\uE2B3','肉'],['\uE2B4','粗'],['\uE2B5','肤'],['\uE2B6','挺'],['\uE2B7','流'],['\uE2B8','淫'],['\uE2B9','唇'],['\uE2BA','下'],['\uE2BB','头'],['\uE2BC','插'],['\uE2BD','舔'],['\uE2BE','湿'],['\uE2BF','屄'],['\uE2C0','纤'],['\uE2C1','阴'],['\uE2C2','脚'],['\uE2C3','射'],['\uE2C4','推'],['\uE2C5','精'],['\uE2C6','媚'],['\uE2C7','咬'],['\uE2C8','舐'],['\uE2C9','乳'],['\uE2CA','干'],['\uE2CB','抚'],['\uE2CC','欲'],['\uE2CD','钻'],['\uE2CE','潮'],['\uE2CF','做'],['\uE2D0','骚'],['\uE2D1','体'],['\uE2D2','房'],['\uE2D3','掏'],['\uE2D4','满'],['\uE2D5','阳'],['\uE2D6','叉'],['\uE2D7','性'],['\uE2D8','裤'],['\uE2D9','拔'],['\uE2DA','光'],['\uE2DB','茎'],['\uE2DC','丰'],['\uE2DD','含'],['\uE2DE','根'],['\uE2DF','浪'],['\uE2E0','色'],['\uE2E1','胸'],['\uE2E2','龟'],['\uE2E3','药'],['\uE2E4','漏'],['\uE2E5','痒'],['\uE2E6','顶'],['\uE2E7','尿'],['\uE2E8','荡'],['\uE2E9','勃'],['\uE2EA','情'],['\uE2EB','贪'],['\uE2EC','诱'],['\uE2ED','沟'],['\uE2EE','吻'],['\uE2EF','腿'],['\uE2F0','爱'],['\uE2F1','坚'],['\uE2F3','液'],['\uE2F4','女'],['\uE2F5','屁'],['\uE2F6','席'],['\uE2F7','穴'],['\uE2F8','白'],['\uE2F9','趴'],['\uE2FA','奶'],['\uE2FB','撩'],['\uE2FC','罩'],['\uE2FD','裙'],['\uE2FE','滑'],['\uE2FF','软'],['\uE300','蜜'],['\uE301','柔'],['\uE302','搓'],['\uE303','吹'],['\uE304','尻'],['\uE305','爆'],['\uE306','交'],['\uE307','吮'],['\uE308','水'],['\uE309','脱'],['\uE30A','露'],['\uE30B','口'],['\uE30C','的'],['\uE30D','袜'],['\uE30E','呻'],['\uE30F','妇'],['\uE310','逗'],['\uE311','腰'],['\uE312','洞'],['\uE313','胀'],['\uE314','啊'],['\uE315','蒂'],['\uE316','户'],['\uE317','肥'],['\uE320','共'],['\uE321','党'],['\uE322','习'],['\uE323','产']
]);
//过滤一段文本 将其中的特殊字符替换正确
function fun_filterText(txt)
{
g_replaceMap.forEach(function(value, key){
txt = txt.replaceAll(key,value);
});
return txt;
}
//睡眠一段时间
function fun_sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
//从内容中获取文本
function fun_getContentFromHTML(html)
{
// 将 HTML 字符串转换为 jQuery 对象
var $tempDiv = $(html);
// 获取 id 为 "booktxt" 的 div 元素
var $booktxtDiv = $tempDiv.find('.article-content');
// 如果未找到对应的 div,则提示错误并返回空字符串
if (!$booktxtDiv.length) {
console.error('未找到 id 为 "content" 的 div 元素');
throw new Error('error 未找到 id 为 "content" 的 div 元素');
return '';
}
let lines = [];
$booktxtDiv.find('p').each(function() {
if ($(this).attr('style'))
return;
if($(this).find('a').length != 0)
return;
var text = this.textContent.trim();
lines.push(text);
});
var txt = lines.join('\n');
return fun_filterText(txt);
}
//这里有个问题,这个框架实际上并没有被抽象出来,基本上还是得按需调整
// 获取每一章的内容 一页一页的获取
async function fun_getChapterContenPageByPage(url) {
console.log("正在获取章节内容:" + url);
try {
// 发送 HTTP 请求并等待响应
const response = await fetch(url);
const data = await response.text();
//定义一个临时变量,最终需要返回
var paragraphList = [];
var regex = /page=/;
if (regex.test(url)) {
console.log("链接中包含 page=");
} else {
console.log("链接中不包含 page=");
var sixthChild = $(data).find('.con_top').contents()[6];
var chapterTitle = "";
// 移除文本的前三个字符
if (sixthChild.nodeType === Node.TEXT_NODE && sixthChild.nodeValue.length >= 3) {
chapterTitle = sixthChild.nodeValue.substring(3);
}
if(chapterTitle.length > 0)
{
paragraphList.push("");
paragraphList.push(chapterTitle); //TODO 暂时不处理章节名称
paragraphList.push("");
}
}
// 获取当前页面的小说内容
var content = fun_getContentFromHTML(data);
// 将当前页面的小说内容存储到数组中
paragraphList.push(content);
// 检查是否有下一页按钮
var nextPageBtn = $(data).find('a[rel="prev"]:contains("下一页")');
if (nextPageBtn.length > 0) {
// 获取下一页链接
var nextPageLink = nextPageBtn.attr('href');
console.log("存在下一页,继续获取:", nextPageLink);
// 继续获取下一页的内容
var nextContentList = await fun_getChapterContenPageByPage(nextPageLink); // 使用 await 等待递归调用完成
paragraphList = paragraphList.concat(nextContentList);
} else {
console.log("已到达最后一页,停止获取内容。");
}
return paragraphList;
} catch (error) {
console.error("请求失败:", error);
g_needSleep = true;
}
}
//从指定页面获取完整的一页数据
async function fun_getChapterContenFromOnePage(url) {
console.log("正在获取章节内容:" + url);
try {
//请求
const response = await fetch(url);
const data = await response.text();
//console.log(data);
let paragraphList = [];
//章节头部
let chapterTitle = $(data).find('.article-content h1:first').text();
if (chapterTitle.length !== 0)
{
paragraphList.push("");
paragraphList.push(chapterTitle); //TODO 暂时不处理章节名称
paragraphList.push("");
}
//获取主体内容
var content = fun_getContentFromHTML(data);
paragraphList.push(content);
return paragraphList;
} catch (error) {
console.error("请求失败:", error);
g_needSleep = true;
}
}
async function fun_getChapterContentPromise(url)
{
return new Promise(async (resolve, reject) => {
try {
var contentList = await fun_getChapterContenFromOnePage(url);
const resultMap = new Map();
resultMap.set(url, contentList);
resolve(resultMap);
g_handleCnt += 1;
console.log("进度:"+g_handleCnt+"/"+g_chapterURLList.length);
} catch (error) {
reject(error);
}
});
}
//获取章节列表
async function fun_getChapterList(url)
{
console.log("正在获取章节列表: "+url);
try {
// 发送 HTTP 请求并等待响应
const response = await fetch(url);
const data = await response.text();
// 找到章节链接所在的元素
var chapterContainer = $(data).find('#content_1');
// 遍历所有章节链接
chapterContainer.find('a[rel="chapter"]').each(function() {
// 获取章节链接
var chapterLink = $(this).attr('href');
// 添加到章节列表
g_chapterURLList.push(chapterLink);
});
// 找到包含“下一页”文本的按钮
var nextPageBtn = $(data).find('.index-container-btn:contains("下一页")');
if (nextPageBtn.length > 0) {
// 获取下一页链接
var nextPageLink = nextPageBtn.attr('href');
// 继续获取下一页的章节链接
await fun_getChapterList(nextPageLink);
} else {
// 输出章节列表
console.log("所有章节链接获取完毕。");
}
} catch (error) {
console.error("请求失败:", error);
}
}
//获取章节页的地址
function fun_getChapterListUrl()
{
var chapterURL = "";
$('a[rel="chapter"] dt:contains("点击查看全部章节目录")').each(function() {
// 获取当前元素的链接地址
chapterURL = $(this).parent().attr('href');
console.log("章节目录的URL是:" + chapterURL);
});
return chapterURL;
}
//从当前页面获取所有的章节
function fun_getChapterListFromCurPage()
{
let dtCnt = 0;
//第二个dt之后的所有内容全部都是
$('.m-chapters a').each(function() {
// 获取章节链接
var chapterLink = $(this).attr('href');
// 添加到章节列表
g_chapterURLList.push(chapterLink);
});
}
//获取小说下载的名称
function fun_getNovelSaveName() {
var bookTitle = $('.m-info > h1:first').text();
var author = $('.m-info .author > a:first').text()
var originalBookName = '《' + bookTitle + '》作者:' + author;
var optimizedBookName = originalBookName.replace(/[!@#$%^&*()+\=\[\]{};':"\\|,.<>\/?]/g, 'x');
return {
originalBookName: bookTitle,
author: author,
optimizedBookName: optimizedBookName
};
}
//并行的获取一批数据
async function fun_PromiseHandle(resmap)
{
try {
const resultArray = await Promise.all(g_chapterPromises);
// console.log(resultArray);
// 这里可以继续处理resultMap
resultArray.forEach((tempMap) => {
tempMap.forEach((value,key) => {
resmap.set(key, value);
});
});
} catch (error) {
console.error('Error fetching chapter content:', error);
}
g_chapterPromises = [];
}
//遍历章节列表,逐步下载小说内容 //这里是可以调整的,使用Promise并发的进行请求
async function fun_downloadChapterUrlList(chapterList)
{
let bInterrupt = false;
for (let i = 0; i < chapterList.length; i++)
{
let url = chapterList[i];
let p = fun_getChapterContentPromise(url);
g_chapterPromises.push(p);
if(g_chapterPromises.length >=g_iMaxPromiseCount)
{
await fun_PromiseHandle(g_resmap);
}
if(g_needSleep)
{
console.log("过程中出现错误,睡眠3秒...");
await fun_sleep(3000);
g_needSleep = false;
console.log("睡眠结束!");
}
if(g_bTestDownload && i>=(g_iTestDownloadCnt-1))
{
bInterrupt = true;
break;
}
}
//需要再执行一次,保证余下的
await fun_PromiseHandle(g_resmap);
let failedList = [];
//如果中断直接退出执行
if(bInterrupt)
{
g_chapterURLList.forEach((url)=>{
const dataArray = g_resmap.get(url);
if(dataArray === undefined)
return;
dataArray.forEach((d)=>{
g_paragraphList.push(d);
});
});
return failedList;
}
g_chapterURLList.forEach((url)=>{
const dataArray = g_resmap.get(url);
if(dataArray === undefined)
{
failedList.push(url);
return;
}
dataArray.forEach((d)=>{
g_paragraphList.push(d);
});
});
if(failedList.length !=0)
g_paragraphList = [];
return failedList;
}
//下载小说
async function fun_downloadNovel()
{
//清空存储容器
g_chapterURLList = [];
g_chapterList = [];
g_paragraphList = [];
g_resmap = new Map();
g_handleCnt = 0;
let g_bookHeader = [];
console.log("正在下载小说...");
//获取保存的文件名称
let novelInfo = fun_getNovelSaveName();
console.log("书籍名称:"+novelInfo.optimizedBookName);
//插入下载信息
g_bookHeader.push("书名:" + novelInfo.originalBookName);
g_bookHeader.push("作者:" + novelInfo.author);
g_bookHeader.push("地址:" + window.location.href);
g_bookHeader.push("下载:雯饰太一");
g_bookHeader.push("形式:网页插件");
g_bookHeader.push("说明:数据为网页爬取而来,作者写作不易,请尊重正版原创");
g_bookHeader.push("");
g_bookHeader.push("");
fun_getChapterListFromCurPage();
if (g_chapterURLList.length == 0)
{
console.log("章节列表为空,取消下载任务")
return;
}
else
{
console.log("章节总数:\n"+g_chapterURLList.length);
}
failedList = g_chapterURLList;
let iDownloadBatch = 1;
while(failedList.length!=0)
{
console.log("当前下载批次:"+iDownloadBatch);
failedList = await fun_downloadChapterUrlList(failedList);
iDownloadBatch += 1;
}
//内容拼接
let allContents = g_bookHeader.join('\n') + g_paragraphList.join('\n');
// 计算内容大小
let contentSizeKB = (new Blob([allContents])).size / 1024; // 转换为 KB
let contentSizeMB = contentSizeKB / 1024; // 转换为 MB
// 输出内容大小
if (contentSizeMB >= 1) {
console.log("内容大小:", contentSizeMB.toFixed(2) + " MB");
} else {
console.log("内容大小:", contentSizeKB.toFixed(2) + " KB");
}
//将内容下载为文件
let blob = new Blob([allContents], { type: "text/plain;charset=utf-8" });
saveAs(blob, novelInfo.optimizedBookName+".txt");
}
//插入下载按钮
function fun_insertDownloadInfo() {
var newButton = $('<button id="local_download_btn">下载书籍</button>'); // 设置按钮的id为'local_download_btn'
$('.ops').append(newButton);
$('#local_download_btn').click(function() { // 使用按钮的id来绑定点击事件
fun_downloadNovel();
});
}
//判断当前url是什么类型的界面 0 不匹配 1 书籍主页 2 目录页 3 章节页
function fun_ruleMatch(url)
{
if($('.inner .m-info .author').length >=0)
{
return 1;
}
return 0;
}
(function() {
'use strict';
// Your code here...
var locUrl = window.location.href;
console.log(locUrl);
var rule_type = fun_ruleMatch(locUrl);
if(rule_type == 0)
{
console.log("不是书籍主页,脚本不生效!");
return;
}
else if(rule_type == 1)
{
console.log("脚本已激活,正在插入下载按钮...");
fun_insertDownloadInfo();
return;
}
})();