Greasy Fork is available in English.

起点小说优化|AI续写追更|VIP章节免费阅读|支持本章说显示|自动翻页|简洁风格

提供多功能的起点小说网站优化插件,支持多书源、本章说、翻译、净化等功能

Verzia zo dňa 12.03.2024. Pozri najnovšiu verziu.

  1. // ==UserScript==
  2. // @name 起点小说优化|AI续写追更|VIP章节免费阅读|支持本章说显示|自动翻页|简洁风格
  3. // @version 1.5.3
  4. // @description 提供多功能的起点小说网站优化插件,支持多书源、本章说、翻译、净化等功能
  5. // @author JiGuang
  6. // @namespace www.xyde.net.cn
  7. // @homepageURL https://51coolplay.com
  8. // @match https://www.qidian.com/*
  9. // @match https://51coolplay.com/service/book/*
  10. // @require https://cdn.jsdelivr.net/npm/sweetalert2@11
  11. // @require https://cdn.staticfile.org/jquery/2.0.3/jquery.min.js
  12. // @grant GM_setValue
  13. // @grant GM_getValue
  14. // @grant GM_openInTab
  15. // @grant GM_xmlhttpRequest
  16. // @grant GM_registerMenuCommand
  17. // @license MIT
  18. // ==/UserScript==
  19.  
  20. (function() {
  21. const default_config = `[]`
  22. // 取脚本版本
  23. function getVersion(){
  24. return '1.5.3'
  25. }
  26. function openSettings(){
  27. GM_openInTab('https://51coolplay.com/service/book/settings.php?v='+getVersion(), {active: !0})
  28. }
  29. // 首次更新到新版本的提示
  30. function firstTip(){
  31. if(GM_getValue('qdv_'+getVersion(),'') == ''){
  32. Swal.fire({
  33. title: "👏欢迎使用起点小说优化",
  34. text: "1.5.0新增了AI续写功能,会根据小说简介、目录剧情和当前文字生成一段后续的章节内容,可循环生成",
  35. icon: "success"
  36. })
  37. GM_setValue('qdv_'+getVersion(),'read_notice')
  38. }
  39. }
  40. // 脚本专用:读取配置到51
  41. function read51Config(){
  42. // 如果空,就默认装一下插件
  43. //console.log('config',GM_getValue('config',default_config))
  44. document.querySelector("#config").value = GM_getValue('config',default_config)
  45. }
  46. // 脚本专用:从51写配置
  47. function save51Config(){
  48. GM_setValue('config',document.querySelector("#config").value)
  49. }
  50. // 脚本专用:检查在线插件是否有更新
  51. async function check_online_plugin_update(){
  52. if([10,30].indexOf(new Date().getMinutes()) == -1){
  53. return
  54. }
  55. const flag = 'read_version_3je7s'
  56. let res = await request('https://51coolplay.com/service/book/check_plugin_update.php')
  57. let version = res.version
  58. let local_version = GM_getValue(flag,'3cc6c22a116cdce751563ffa6de3e390')
  59. //console.log(`v:${version},lv:${local_version}`)
  60. if(local_version != version){
  61. console.log('!=')
  62. // 仅展示一次更新提示,尽量不打扰用户
  63. GM_setValue(flag,version)
  64. let ele = document.createElement('div')
  65. ele.innerHTML = `<button id="_btn34" style="padding:5px;background-image: radial-gradient(circle 248px at center, #16d9e3 0%, #30c7ec 47%, #46aef7 100%);box-shadow: 5px 5px 10px rgba(0, 0, 0, 0.5);border-radius: 10px;color:white;position:fixed;z-index:99999;top:60px;right:20px;">在线插件有更新,点我查看</button>`
  66. document.body.appendChild(ele)
  67. document.querySelector("#_btn34").onclick = ()=>{
  68. openSettings()
  69. document.querySelector("#_btn34").style.display = 'none'
  70. }
  71. }
  72. }
  73. // 脚本专用:运行开启的配置
  74. async function readConfigOpen(is_read_page = true){
  75. await check_online_plugin_update()
  76. function add_float_menu(){
  77. let div = document.createElement('div')
  78. div.innerHTML = '<div style="padding:5px;z-index:99999;position:fixed;top:10px;right:20px;box-shadow: 5px 5px 10px rgba(0, 0, 0, 0.5);border-radius: 10px;background-color:black;color:white;"><button id="b56">点我进入插件设置</button></div>'
  79. document.body.appendChild(div);
  80. document.querySelector("#b56").onclick = ()=>{
  81. openSettings()
  82. }
  83. }
  84.  
  85. window.onLoad = ()=>{
  86. // 仅在阅读页面才提示没有开启插件
  87. if(location.href.indexOf('qidian.com/chapter')!= -1){
  88. notify('您在当前页面没有开启任何插件!!','error')
  89. add_float_menu()
  90. }
  91. }
  92.  
  93. let codes = ''
  94. try{
  95. const config_str = GM_getValue('config',default_config)
  96. // console.log(config_str)
  97. const config_items = JSON.parse(config_str)
  98. // console.log(config_items)
  99. //筛选插件代码
  100. if(is_read_page){
  101. codes = config_items.filter(e => e.open).map(e => e.code).join(';')
  102. }else{
  103. //全局起点页面插件需要配置global=true,然后插件里自己设计路径检测
  104. codes = config_items.filter(e => e.open).filter(e => e.global).map(e => e.code).join(';')
  105. }
  106. }catch(err){
  107. console.warn('加载配置失败0',err)
  108. notify('加载配置失败,请去设置页面重新配置','error')
  109. add_float_menu()
  110. return
  111. }
  112. //注入插件
  113. console.log(codes)
  114. try{
  115. eval(codes)
  116. //执行启动函数(书源专用)注意,设置中的自定义插件会默认添加onload函数包裹
  117. onLoad()
  118. window.loaded = true
  119. }catch(err){
  120. console.warn('加载配置失败',err)
  121. notify('加载配置失败,请去设置页面检查是否启用了不兼容的插件','error')
  122. add_float_menu()
  123. return
  124. }
  125. }
  126. // 内置函数:读取页面书名
  127. function readBookName(){
  128. const bookNameElement = document.querySelector("#r-breadcrumbs > a.text-s-gray-900");
  129. if (bookNameElement) {
  130. // 使用正则表达式去掉括号内的内容
  131. const rawName = bookNameElement.innerText;
  132. const cleanedName = rawName.replace(/\([^)]*\)/g, '').trim();
  133. console.log(`BookName:${cleanedName}`)
  134. return cleanedName;
  135. } else {
  136. return '未知'
  137. // 或者返回一个默认的名称,或者抛出错误,具体根据需求来定
  138. }
  139. }
  140. // 内置函数:读取章节名
  141. function readChapterName(){
  142. let ele = document.querySelector("#reader-content > div.min-h-100vh.relative.z-1.bg-inherit > div > div.relative > div > h1")
  143. if (ele) {
  144. let res = '' + ele.innerText
  145. res = res.replace(' ', '')
  146. console.log(`BookChapter:${res}`)
  147. return res
  148. }
  149. return '未知'
  150. }
  151. // 内置函数:读取正文
  152. function readContent(){
  153. return document.querySelector("#reader-content > div.min-h-100vh.relative.z-1.bg-inherit > div > div.relative > div > main").innerText
  154. }
  155. // 内置函数:将请求的url的html内容转化成document对象
  156. async function parseDocFromAjax(method,url){
  157. console.log('请求url:',url)
  158. return new Promise((resolve,reject) => {
  159. GM_xmlhttpRequest({
  160. method,
  161. url,
  162. onload:(res) => {
  163. //console.log('response',res)
  164. let htmldoc = document.createElement('html')
  165. let htmlstr = res.responseText
  166. // 修复 某图片自动加载的问题
  167. htmlstr = htmlstr.replace(/http /g, "https")
  168. htmlstr = htmlstr.replace(/img src/g, "a url")
  169. htmlstr = htmlstr.replace(/onerror/g, "class")
  170. htmldoc.innerHTML = htmlstr
  171. resolve(htmldoc)
  172. },
  173. onerror:(err) => {
  174. reject(err)
  175. }
  176. })
  177. })
  178. }
  179. // 内置函数:axios/fetch风格的跨域请求
  180. async function request(url,data = '',method = 'GET'){
  181. console.log('请求url1:',url)
  182. return new Promise((resolve,reject) => {
  183. GM_xmlhttpRequest({
  184. method,
  185. url,
  186. data,
  187. onload:(res) => {
  188. //console.log('response1',res.response)
  189. resolve(JSON.parse(res.response))
  190. },
  191. onerror:(err) => {
  192. reject(err)
  193. }
  194. })
  195. })
  196. }
  197. // 内置函数:加载本章说
  198. async function loadComment(){
  199. let cid = location.href.split('/')[location.href.split('/').length-2]
  200. let bid = location.href.split('/')[location.href.split('/').length-3]
  201. let res = await request(`https://www.qidian.com/ajax/chapterReview/reviewSummary?_csrfToken=${document.cookie.split(';').find(e=>e.indexOf("_csrfToken")!=-1).split('=')[1]}&bookId=${bid}&&chapterId=${cid}`, true)
  202. let content = document.querySelector("#reader-content > div.min-h-100vh.relative.z-1.bg-inherit > div > div.relative > div > main").innerHTML
  203. let txts = content.split('<br><br>')
  204. let contents = ''
  205. res.data.list.splice(0,1)
  206. txts.forEach((txt,index) =>{
  207. let review_unit = {}
  208. let segmentId = 1234
  209. try{
  210. review_unit = res.data.list.find(e=>e.segmentId == index + 1)
  211. segmentId = review_unit.segmentId
  212. }catch(err){
  213. console.warn('对应不上段落0')
  214. }
  215. let num = 0
  216. let hot_style = ''
  217. try{
  218. num = review_unit.reviewNum
  219. hot_style = review_unit.isHotSegment?'data-type="hot"':''
  220. }catch(err){
  221. console.warn('本章说异常,置为默认值')
  222. }
  223. let plus = `<p><span id="content-${segmentId}" class="content-${segmentId} content-text" data-count="${num}" data-index="${segmentId}">${txt}</span><span class="review" ${hot_style} data-index="${segmentId}"><span class="review-icon"></span><span class="review-count content-${segmentId}">${num}</span><!----></span></p>`
  224. if(num == 0){
  225. plus = `<p>${txt}</p>`
  226. }
  227. let comment_ui = `<div id="side-sheet-${segmentId}" style="display:none;box-shadow: 0 8px 16px rgba(0, 0, 0, 0.5);z-index:99999;max-height: 600px"class="bg-b-gray-50 noise-bg border-l border-outline-black-8 h-full w-400px absolute right-0"><button onclick="document.querySelector('#side-sheet-${segmentId}').style.display = 'none'"data-v-63a3e543=""class="bg-s-gray-100 w-28px h-28px rounded-1 flex items-center justify-center hover-24 active-10 p-0 absolute right-10px top-10px"><span class="icon-close text-20px text-s-gray-400"></span></button><div data-v-8a7b341d=""data-v-6c740737=""class="h-full flex flex-col"><div data-v-8a7b341d=""class="flex items-end pt-42px pb-10px px-32px <sm:px-16px <sm:py-15px <sm:border-b <sm:border-outline-black-8"><h2 data-v-8a7b341d=""class="font-medium text-rh4 text-s-gray-900 <sm:text-rh6">评论</h2><span data-v-8a7b341d=""class="text-s3 text-s-gray-500 ml-8px font-medium <sm:text-s4">${num}条</span></div><div data-v-8a7b341d=""class="flex-1 overflow-auto overscroll-contain"><div data-v-8a7b341d=""class="min-h-[calc(100%+20px)]"><!----><ul data-v-8a7b341d="">加载中...</ul><div data-v-8a7b341d=""class="h-80px"></div></div></div></div></div>`
  228. contents += plus
  229. contents += comment_ui
  230.  
  231. })
  232. document.querySelector("#reader-content > div.min-h-100vh.relative.z-1.bg-inherit > div > div.relative > div > main").innerHTML = contents
  233. txts.forEach((item,index) =>{
  234. let review_unit = {}
  235. let segmentId = 1234
  236. try{
  237. review_unit = res.data.list.find(e=>e.segmentId == index + 1)
  238. segmentId = review_unit.segmentId
  239. document.querySelectorAll(".content-"+(segmentId)).forEach((item,index)=>{
  240. item.addEventListener("click", async function() {
  241. // 显示本章说吧!(等到点击的时候再加载书评,省流)
  242. console.log('click'+(segmentId))
  243. let template_contents = ''
  244. //请求起点的书评API,自己动手,丰衣足食~
  245. let res = await request(`https://www.qidian.com/ajax/chapterReview/reviewList?bookId=${bid}&chapterId=${cid}&page=1&pageSize=20&segmentId=${segmentId}&type=2&_csrfToken=${document.cookie.split(';').find(e=>e.indexOf("_csrfToken")!=-1).split('=')[1]}`)
  246. res.data.list.forEach((item,index)=>{
  247. let template_unit = `<li data-v-8a7b341d=""class="px-32px py-16px <sm:px-16px"><div><div class="group"><div class="flex items-center py-1px"><a target="_blank"href="//my.qidian.com/user/${item.userId}/"class="flex items-center min-w-0"><img class="w-28px h-28px mr-10px rounded-1 border border-outline-black-8"alt="cartilage"src="${item.avatar}"><div class="self-start pt-1px flex items-center"><span class="font-medium text-s-gray-500 text-s3 truncate mr-4px">${item.nickName}</span><!----></div></a><a target="_blank"href="https://jubao.yuewen.com/report/report?type=0&amp;id=208188071492190208&amp;appId=10&amp;areaId=1&amp;site=10&amp;extra=9069458404256003&amp;subType=7&amp;desc=1"class="group-hover:flex hidden ml-auto text-s-gray-500 text-s4 items-center font-medium flex-shrink-0"><span class="icon-warning text-16px mr-2px"></span>举报</a></div><div class="pl-38px"><p class="text-s-gray-900 text-16px leading-24px"><span class="inline-flex w-30px h-18px items-center justify-center overflow-hidden mr-4px align-text-bottom"><span class="whitespace-nowrap text-primary-red-500 text-20px font-medium h-32px flex items-center px-8px rounded-8px border border-primary-red-300 transform scale-50">精华</span></span><!----><span class="leading-24px whitespace-pre-wrap">${item.content}</span></p><!----><!----><div class="flex items-center mt-4px text-c12 text-s-gray-400"><span class="truncate"><span>${item.level}楼·</span>${item.createTime} ${item.ipAddress}</span><button class="flex-shrink-0 ml-4px mr-auto text-secondary-blue-500 font-medium h-28px">回复</button><button class="hidden flex-shrink-0 ml-8px items-center text-c12 text-s-gray-500 group-hover:flex h-28px"><span class="icon-thumb-up text-20px mr-2px down"></span>踩</button><button class="flex items-center flex-shrink-0 text-c12 h-28px ml-12px text-s-gray-500"><span class="icon-thumb-up text-20px mr-2px"></span>${item.likeCount}</button></div></div></div></div></li>`
  248. template_contents += template_unit
  249. })
  250. document.querySelector("#side-sheet-"+(segmentId)).innerHTML = document.querySelector("#side-sheet-"+(segmentId)).innerHTML.replace('加载中...',template_contents)
  251. document.querySelector("#side-sheet-"+(segmentId)).style.display = 'block'
  252. });
  253. })
  254. }catch(err){
  255. console.warn('对应不上段落1')
  256. }
  257. })
  258. }
  259. //内置函数:与AI交流
  260. //api-key 可去https://openai-proxy.51coolplay.cc/ 或者 OpenAI官网 获取
  261. async function getAIReply(apikey = '',content = '',model = 'gpt-3.5-turbo'){
  262.  
  263. return new Promise((resolve,reject)=>{
  264. if(apikey == '' || !apikey.startsWith('sk-')){
  265. reject('apikey不正确')
  266. }
  267. if(content == ''){
  268. reject('未输入内容')
  269. }
  270. GM_xmlhttpRequest({
  271. method: "POST",
  272. url: "https://openai-proxy.51coolplay.cc/v1/chat/completions",
  273. headers: {
  274. "Content-Type": "application/json",
  275. "Authorization": "Bearer " + apikey
  276. },
  277. data: JSON.stringify({
  278. 'model': model,
  279. 'messages': [{
  280. 'role': 'user',
  281. 'content': content
  282. }],
  283. 'temperature': 0.7
  284. }),
  285. onload: (response)=>{
  286.  
  287. const obj = JSON.parse(response.responseText)
  288. console.log('ai :'+obj.choices[0].message.content)
  289. resolve(obj.choices[0].message.content)
  290. },
  291. onerror: (err)=>{
  292. reject(err)
  293. }
  294. });
  295. })
  296. }
  297. // 内置函数:写入正文
  298. async function writeContent(content = '',html = false){
  299. if(!html){
  300. document.querySelector("#reader-content > div.min-h-100vh.relative.z-1.bg-inherit > div > div.relative > div > main").innerText = content
  301. }else{
  302. document.querySelector("#reader-content > div.min-h-100vh.relative.z-1.bg-inherit > div > div.relative > div > main").innerHTML = content
  303. }
  304. // loadComment() 不要默认开启,预留给插件去开启,可能会有部分书源不支持,需要测试;我是拿读书阁测的OK
  305. }
  306. // 内置函数:是否已订阅
  307. function isBuy(){
  308. return readContent().length > 200
  309. }
  310. // 内置函数:计算文本相似度,返回0-1之间的数值,0.5以上可以采信
  311. function calculateTextSimilarity(text1, text2) {
  312. // 将文本转换成小写并去除空格
  313. text1 = text1.toLowerCase().replace(/\s/g, "");
  314. text2 = text2.toLowerCase().replace(/\s/g, "");
  315. // 计算两个文本的交集
  316. const intersection = text1.split("").filter(char => text2.includes(char));
  317. // 计算相似度
  318. const similarity = intersection.length / (text1.length + text2.length - intersection.length);
  319. return similarity;
  320. }
  321. //内置函数:提示用户
  322. function notify(title = '操作成功', type = 'success', show = true) {
  323. console.log(title)
  324. const Toast = Swal.mixin({
  325. toast: true,
  326. position: 'top-end',
  327. showConfirmButton: false,
  328. timer: 2000,
  329. timerProgressBar: true,
  330. didOpen: (toast) => {
  331. toast.addEventListener('mouseenter', Swal.stopTimer)
  332. toast.addEventListener('mouseleave', Swal.resumeTimer)
  333. }
  334. })
  335. if (show)
  336. Toast.fire({
  337. icon: type,
  338. title: title
  339. })
  340. return Toast
  341. }
  342. // 配置网站就读取配置到网站上,1秒保存一次
  343. if(location.href.indexOf('51coolplay.com')!= -1){
  344. read51Config()
  345. setInterval(()=>{ save51Config() },1000)
  346. }
  347. // 应用网站就把配置运行好
  348. if(location.href.indexOf('qidian.com/chapter')!= -1){
  349. firstTip()
  350. readConfigOpen()
  351. }
  352. // 起点其他页面预留的坑位,计划更新:全书txt解析下载、游客云书架、移动端起点适配...
  353. else if(location.href.indexOf('qidian.com')!= -1){
  354. readConfigOpen(false)
  355. }
  356. GM_registerMenuCommand('⚙️打开设置', openSettings)
  357. GM_registerMenuCommand('♻️重置设置', ()=>{GM_deleteValue('config');notify('重置成功')})
  358. })();