// ==UserScript==
// @name 起点小说解锁|VIP章节免费阅读|极速章节识别
// @version 1.3.5
// @description 可解锁起点小说VIP付费章节。基本还原付费效果,无需设置即可阅读。
// @author JiGuang
// @namespace www.xyde.net.cn
// @homepageURL http://www.xyde.net.cn
// @match https://www.qidian.com/chapter/*
// @require https://cdn.jsdelivr.net/npm/sweetalert2@11
// @require https://cdn.staticfile.org/jquery/2.0.3/jquery.min.js
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_xmlhttpRequest
// @grant unsafeWindow
// @grant GM_registerMenuCommand
// @connect
// @license MIT
// @downloadURL none
// ==/UserScript==
(function () {
'use strict';
//全局配置
//获取cookie值
var index = getCookie("choice");
var times = getCookie("times");
if (index == null) { index = 0 }
if (times == null) { times = 0 }
var config = {
//配置版本号
version: 1,
//支持的书源地址:
//步骤1
webSites:
[
"http://www.dushuge.com/hsdgiohsdigohsog.php?ie=gbk&q="
],
//跳转网址:用于修正脚本读取章节地址自动把起点前缀拼接起来
//步骤1
webGo: ['http://www.dushuge.com'],
//网页内容:F12查看页面元素 找到章节文字所在的标签id
webContent: ['#content'],
//书源描述
webDesc: ["读书阁"],
//正在使用的书源
webSiteIndex: index,
//搜索前缀:
//步骤2:查看书源网站搜索关键字后跳转地址 并替换
webSearch: [''],
//搜索方法 : 目前没有特别大的作用
webMethod: ["GET"],
//使用序列: 不同书源的获取章节目录的标签选择不同
//步骤5:0 代表第一个字符串
webReturn: [2],
//书源类型:0代表网页书源,1代表api请求书源
webType: [0],
//具体章节网址替换
webHref: [0],
//book:不同书源的获取作品名的标签选择不同
//步骤3:去书源网站搜索页面查找标签并替换
webBook: [
"h4.bookname > a",
],
//author
//步骤3:去书源网站搜索页面查找标签并替换
webAuthor: [
"div.author",
]
}
//注册的菜单和对应执行的函数
var menus = [
{
name: '打开设置',
event: openSetting
},
]
//增加cookie缓存
function setCookie(cName, value, datetime) {
var oDate = new Date();
if (datetime == 0) { datetime = 1 * 24 * 60 * 60 * 1000 }
oDate.setTime(oDate.getTime() + datetime);//设置过期时间
var cookieString = cName + value + ";expires='" + oDate.toGMTString() + ";path=/";
document.cookie = cookieString;//存cookie
}
//获取指定名称的cookie的值
function getCookie(cName) {
var arrStr = document.cookie.split("; ");
for (var i = 0; i < arrStr.length; i++) {
var temp = arrStr[i].split("=");
if (temp[0] == cName) {
return decodeURI(temp[1]);
}
}
}
//增加菜单
function addMenu() {
for (var menu of menus) {
GM_registerMenuCommand(menu.name, menu.event)
}
}
//添加新书源
function openSetting() {
try {
document.querySelector("#j_navSettingBtn > a").click()
} catch (e) {
notify('打开设置失败', 'warning')
}
}
//把更换书源增加到设置菜单
function hookSetting() {
let bookhtml = ``
for (var di in config.webDesc) {
bookhtml += ``
}
if (!document.querySelector(".w-359px")) {
setTimeout(hookSetting, 1000)
return
}
let e = document.createElement("div")
e.innerHTML = `
书源切换
`
document.querySelector(".w-359px").appendChild(e)
document.querySelector("#select").onchange = function () {
var index = document.querySelector("#select").value
setCookie("choice=", index, 0)
location.reload()
}
}
//自动加载本章说
async function comment() {
$("#j-readPage").removeClass("j-sectionCommentLimit")
$("#j_chapterBox > div > div").removeClass("j-sectionCommentLimit")
}
//提示用户
function notify(title = '操作成功', type = 'success', show = true) {
console.log(title)
const Toast = Swal.mixin({
toast: true,
position: 'top-end',
showConfirmButton: false,
timer: 2000,
timerProgressBar: true,
didOpen: (toast) => {
toast.addEventListener('mouseenter', Swal.stopTimer)
toast.addEventListener('mouseleave', Swal.resumeTimer)
}
})
if (show)
Toast.fire({
icon: type,
title: title
})
return Toast
}
//获取章节名
function QDgetBookChapter() {
let ele = document.querySelector("#reader-content > div.min-h-100vh.relative.z-1.bg-inherit > div > div.relative > div > h1")
if (ele) {
let res = '' + ele.innerText
res = res.replace(' ', '')
console.log(`BookChapter:${res}`)
return res
}
return undefined
}
// 获取小说名字,去掉括号内的内容
function QDgetBookName() {
const bookNameElement = document.querySelector("#r-breadcrumbs > a.text-s-gray-900");
if (bookNameElement) {
// 使用正则表达式去掉括号内的内容
const rawName = bookNameElement.innerText;
const cleanedName = rawName.replace(/\([^)]*\)/g, '').trim();
console.log(`BookName:${cleanedName}`)
return cleanedName;
} else {
return null; // 或者返回一个默认的名称,或者抛出错误,具体根据需求来定
}
}
//本章是否已被购买
function QDgetChapterOrder() {
//return false
// @ts-ignore
let not_buy_as = document.querySelector("#reader-content > div.min-h-100vh.relative.z-1.bg-inherit > div > section.sm\\:border-t.sm\\:border-outline-black-8.sm\\:pt-48px.sm\\:my-64px.sm\\:mx-64px.mb-24px.text-center.mx-20px > div:nth-child(3) > button")
if(not_buy_as) return null
else return false
}
//设置页面阅读内容
async function QDsetContent(content) {
document.querySelector("#reader-content > div.min-h-100vh.relative.z-1.bg-inherit > div > div.relative > div > main").innerHTML = content
}
//将请求的url的html内容转化成document对象
async function parseDocFromAjax(method, url, flag) {
return new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method,
url: url,
onload: (res) => {
if (config.webType[config.webSiteIndex] == 1 || flag) {
let str = res.response
// console.log(str)
str = str.replace(/\\r\\n \\r\\n /g, "
")
str = str.replace(/\\r\\n /g, "
")
let arr = eval('(' + str + ')')
const { data } = arr
console.log(data)
return resolve(data)
}
let htmldoc = document.createElement('html')
let htmlstr = res.responseText
htmlstr = htmlstr.replace(/http /g, "https")
htmlstr = htmlstr.replace(/img src/g, "a url")
htmlstr = htmlstr.replace(/onerror/g, "class")
// console.log(htmlstr)
htmldoc.innerHTML = htmlstr
console.log(url)
resolve(htmldoc)
},
onerror: (err) => {
reject(err)
}
})
})
}
//搜索小说并返回结果
async function searchBook(keywords) {
const r = await parseDocFromAjax(config.webMethod[config.webSiteIndex], config.webSites[config.webSiteIndex] + keywords + config.webSearch[config.webSiteIndex])
let resList = []
if (config.webType[config.webSiteIndex] == 1) {
r.map(item => {
resList.push({ id: item.Id, bookName: item.Name, author: item.Author, url: config.webGo[config.webSiteIndex] + item.Id + "/" })
//console.log(item)
})
//console.log(resList[0])
return resList
}
var bookList = r.querySelectorAll(config.webBook[config.webSiteIndex])
const authorList = r.querySelectorAll(config.webAuthor[config.webSiteIndex])
for (let i in bookList) {
if (bookList[i].title) {
resList.push({ bookName: bookList[i].title, author: authorList[i].innerText, url: config.webGo[config.webSiteIndex] + bookList[i].pathname })
}
resList.push({ bookName: bookList[i].innerText, author: authorList[i].innerText, url: config.webGo[config.webSiteIndex] + bookList[i].pathname })
}
// console.log(resList)
return resList
}
//获取小说目录
async function getChapterList(book) {
let resList = []
let bookUrl = book.url.replace('https://vipreader.qidian.com/', config.webGo[config.webSiteIndex])
const r = await parseDocFromAjax('GET', bookUrl)
if (config.webType[config.webSiteIndex] == 1) {
// console.log(r)
r.list.map(item => {
item.list.map(i => {
resList.push({ title: i.name, url: config.webGo[config.webSiteIndex] + book.id + "/" + i.id + ".html" })
})
})
// console.log(resList);
return resList
}
let s = ["#list > dl > dd > a", "ul.cf > li > a", "div.listmain > dl > dd > a"]
//步骤4:如书源目录标签不相同 此处添加后再在webReturn修改对应数字
const cateList = r.querySelectorAll(s[config.webReturn[config.webSiteIndex]])
console.log("cateList:", cateList)
for (let i of cateList) {
// console.log( i)
let url = i.getAttribute("href")
if (config.webHref[config.webSiteIndex] == 1) {
// console.log("Ok")
// bookUrl = bookUrl.substring(0, bookUrl.lastIndexOf("/")+1)
config.webGo[config.webSiteIndex] = ''
}
url = config.webGo[config.webSiteIndex] + url
resList.push({ title: i.innerText, url: url })
}
return resList
}
//获取章节内容
async function getContent(pageUrl) {
const res = await parseDocFromAjax('GET', pageUrl)
if (config.webType[config.webSiteIndex] == 1) {
let title = res.cname.replace(" ", '')
if (res.content.indexOf(title) == -1) return res.content
title = title + '
'
res.content = res.content.replace(title, '')
//console.log('getContent:',res.content)
return res.content
}
return res.querySelector(config.webContent[config.webSiteIndex]).innerHTML
}
//解析书源函数
async function parseMain() {
//搜索小说名字
var r = await searchBook(QDgetBookName())
let ii = 0
//优先匹配名字相同的
for (let suoyin in r) {
if (r[suoyin].bookName == QDgetBookName()) {
ii = suoyin
break;
}
}
if (r[ii] == undefined) {
console.log("搜索作者")
r = await searchBook(a)
for (let suoyin in r) {
if (r[suoyin].bookName == QDgetBookName()) {
ii = suoyin
break;
}
}
}
//获取第一项结果章节目录
if (r[ii] == undefined) {
console.log('该小说暂无资源')
}
// console.log(r[ii])
const clist = await getChapterList(r[ii])
if (QDgetBookChapter() == undefined || clist.length == 0) {
console.log('抓取目录失败')
}
console.log('抓取目录成功')
// console.log(clist)
//获取章节名
for (let i in clist) {
let tit = '' + clist[i].title
let str = tit
tit = tit.replace(' ', '')
//console.log('匹配',tit,QDgetBookChapter())
var patt1 = /[a-zA-Z\u4e00-\u9fa5]+/g
var patt2 = /[0-9]+/g
str = QDgetBookChapter()
var flag = false
//排除纯数字章节的影响
if (tit.match(patt1) == null) {
tit = tit.match(patt2)
str = str.match(patt2)
}
else if (str.match(patt1) == null) {
str = str.match(patt2)
tit = tit.match(patt2) == null ? tit.match(patt1) : tit.match(patt2)
}
else {
str = str.match(patt1)
tit = tit.match(patt1)
//有些作者喜欢加第几卷第几章 但是书源网站没有卷名
var str2 = str.join("").split(/卷|章/)
var tit2 = tit.join("").split(/卷|章/)
//模糊读取,若无法精准匹配 尝试模糊名匹配 并设置缓存默认以此方法匹配,默认是2分钟
console.log(times)
if (times >= 4 & times < 11) {
//自带数字章节名 首个字符串与书源匹配
if (str[0] == tit[0]) {
flag = true
setCookie("times=", times, 1 * 1000 * 60 * 2)//这里修改2可以改缓存时间
}
}
else if (times >= 11 & times < 17) {
//末尾名匹配
if (str2[str2.length - 1] == tit2[tit2.length - 1]) {
flag = true
setCookie("times=", times, 1 * 1000 * 60 * 2)
}
}
else if (times >= 17) {
//中间名匹配
if (str2[str2.length - 2] == tit2[tit2.length - 2]) {
flag = true
setCookie("times=", times, 1 * 1000 * 60 * 2)
}
}
}
// console.log(str[0],tit[0])
if (str.join("") == tit.join("") || flag == true) {
console.log('检查到结果')
const content = await getContent(clist[i].url)
QDsetContent(content)
console.log('写入成功')
notify('小说读取成功')
return
}
}
times++
setCookie("times=", times, 1 * 1000 * 60 * 2)
console.log('目录匹配失败')
notify('未查询到该小说内容', 'warning')
throw new Error('该书源解析失败')
}
//递归更换书源
async function mergeOne(index) {
try {
if (index) {
config.webSiteIndex = index
console.log(index)
}
notify(`正在切换到书源${config.webDesc[config.webSiteIndex]}...`, 'info')
await parseMain()
} catch (e) {
console.log(e)
config.webSiteIndex = (config.webSiteIndex + 1) % 6
mergeOne()
}
}
//MAIN-BEFORE 主程序预备函数
if (QDgetChapterOrder() != null) {
notify(`已订阅章节`)
} else {
addMenu()
//MAIN 主程序
notify(`您正在阅读${QDgetBookName()}的${QDgetBookChapter()}`)
mergeOne()
comment()
hookSetting()
}
// Your code here...
})();