// ==UserScript== // @name 随手小说下载 // @namespace ythong // @version 0.4.0.8 // @description 带图形化界面的小说下载器。任意网站,自动识别任何目录列表,自动识别正文,自由下载,简单直观。 // @author ythong // @match http://*/* // @match https://*/* // @require https://greasyfork.org/scripts/450829-numberdigit/code/numberDigit.js?version=1090310 // @require https://greasyfork.org/scripts/450948-reader/code/Reader.js?version=1093077 // @require https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.32.0/codemirror.min.js // @require https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.32.0/mode/javascript/javascript.js // @resource CodeMirrorminCss https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.32.0/codemirror.min.css // @grant GM_registerMenuCommand // @grant GM_xmlhttpRequest // @grant GM_addStyle // @grant GM_listValues // @grant GM_setValue // @grant GM_getValue // @grant GM_deleteValue // @grant GM_getResourceText // @license MIT License // @downloadURL none // ==/UserScript== (function () { 'use strict'; var nextPageReg = /下一?[页頁张張章]/i; var nextListReg = /下一?[页頁]/i; var setting = {}, tempSetting = {}, customListFunc, customItemFunc, customTextFunc; var cm; //setting输入对象 var CodeMirrorSize = [293, 121]; const SETTING_KEYS = ["listSelector", "textSelector", "jammerSelector", "customItemFunc", "customTextFunc"]; var chapters = []; var chaptersIndex = []; var root; var MaxThread = 10; // 同时下载数量 var maxNextCount = 100, //最大下一页数量,总不可能无限吧 nextCountLimit = 10, //下一页限制,超过会提示 continueDownload = false; //超过下一页限制,是否继续直到到达下一页限制 var downloadedCount = 0, downloadedErr = 0, downloadedNo = 0, downloadedExceedNext = 0; var downloadIndex; var hiClass='ythHighlight'; var reader; //#region List function getElementThisSelector(ele) { var tag = ele.tagName.toLocaleLowerCase(); // if (ele.id) { // return '#' + getRightId(ele.id); // } else { var className = (typeof ele.className == 'string') ? ele.className.trim() : ""; return tag + (className ? '.' + className.replace(/\s+/g, ".") : ""); // } } function getAncestorWithMostSimilarDescendant(ele, tag) { var selector = tag; var maxEle, maxSelector, count = 0; while (true) { ele = ele.parentElement; if (!ele || ele.tagName == 'HTML') break; var aa = ele.querySelectorAll(':scope>' + selector); if (aa.length > count) { count = aa.length; maxEle = ele; maxSelector = selector; } selector = getElementThisSelector(ele) + ">" + selector; } return [maxEle, maxSelector, count]; } function getAncestorWithMostSimilarTag(doc,tag) { var eTags = [].slice.apply(doc.querySelectorAll(tag)); var mostAncestor, mostSelector, mostCount = 0; var descendants; while (eTags.length > 0) { var ele = eTags.shift(); var [ancestor, selector, count] = getAncestorWithMostSimilarDescendant(ele, tag); descendants = [].slice.apply(ancestor.querySelectorAll(':scope>' + selector)); eTags = eTags.filter(item => descendants.indexOf(item) === -1); if (count > mostCount) { [mostAncestor, mostSelector, mostCount] = [ancestor, selector, count]; } } return [mostAncestor, mostSelector, mostCount]; } function getNextList(doc) { let eles = doc.querySelectorAll("a"), nextPage = null; for (let ele of eles) { if (nextListReg.test(ele.innerText) && ele.href.indexOf("javascript") == -1) return ele; } eles = doc.querySelectorAll("span") for (let ele of eles) { if (nextListReg.test(ele.innerText)) return ele; } } function getRestList(doc){ function showComplate(){ alert("获取剩余目录结束"); } function waitGetList(doc){ var count=0; setTimeout(function(){ if(count>10) return showComplate() count+=1; var addCount=getList(doc); if(addCount){ getRestList(doc); } setTimeout(arguments.callee, 200); },200); } if(!doc)return showComplate(); var nextList=getNextList(doc); if(!nextList)return showComplate(); if(nextList.href && nextList.href.indexOf("javascript") == -1){ download(nextList.href,0,waitGetList) } else { nextList.click(); waitGetList(doc); } } function getList(doc) { var preChapterNo = 0; var preHrefNo = 0; function getChapterNo(s) { var found = s.match(/\d+|[零一壹二贰两三叁四肆五伍六陆七柒八捌九玖十拾百佰千仟万亿]+/g); if (found) { var n = parseInt(found[0]); if (isNaN(n)) { n = numberDigit(found[0]); if (n != -1) return n; } else return n; } return preChapterNo; } function getHrefNo(s) { var found = s.match(/\d+/g); return found ? parseInt(found.pop()) : preHrefNo; } function addToChapters(elements) { var count=0; for (var e of elements) { if (customItemFunc) customItemFunc(doc, e); if (!e.href || e.href.indexOf("javascript") != -1) continue; //if(isHide(window,e))continue; if (getIndexOfObjectArray(chapters, "href", e.href) != -1) continue; count+=1; var length = chapters.push({ href: e.href, title: e.text, chapterNo: getChapterNo(e.text), hrefNo: getHrefNo(e.href), text: "", //正文 nextCount: 0, //本章的下一页数量 }); preChapterNo = chapters[length - 1].chapterNo; preHrefNo = chapters[length - 1].hrefNo; chaptersIndex.push(length - 1); } return count } setting = getSetting(); if (typeof setting != 'object') return; var listSelector=setting.listSelector; if (!listSelector||listSelector=='--') { var [mostAncestor, mostSelector, mostCount] = getAncestorWithMostSimilarTag(doc,"a:not([href*='javascript'])"); listSelector = getElementSelector(mostAncestor, doc) + '>' + mostSelector; if(setting.listSelector!='--'){ setting.listSelector = listSelector; displaySetting(); } } var count=0; if (customListFunc) { var customResult = customListFunc(doc, listSelector); if (customResult[0] instanceof Document) { [doc, listSelector] = customResult; } else { count=addToChapters(customResult); if(count)createTr(); return } } listSelector=(listSelector||'').trim().replace(/\n/,','); count=addToChapters(doc.querySelectorAll(listSelector)); if(count)createTr(); return count; } function createTr() { var tableBody = root.querySelector("table tbody"); tableBody.innerHTML = ""; for (var i of chaptersIndex) { var tr = document.createElement("tr"); chapters[i].tr = tr var td = document.createElement("td"); td.className = "serial"; tr.appendChild(td); td = document.createElement("td"); var a = document.createElement("a"); a.text = chapters[i].title; a.href = chapters[i].href; a.target="_blank"; td.title = `章节号${chapters[i].chapterNo},网址号${chapters[i].hrefNo}`; td.appendChild(a); tr.appendChild(td); td = document.createElement("td"); var button = document.createElement("button"); button.className = 'getText'; button.textContent = '正'; button.addEventListener("click", getText); td.appendChild(button); button = document.createElement("button"); button.className = 'deleteRow'; button.textContent = '─'; button.addEventListener("click", deleteRow); td.appendChild(button); var span = document.createElement("span"); td.appendChild(span); tr.appendChild(td); tableBody.appendChild(tr); showState(i); } } //getList function sortList(value) { var length = chapters.length; if (!chaptersIndex) { chaptersIndex = new Array(length); for (let i = 0; i < length; i++) { chaptersIndex[i] = i; } } switch (value) { case "原始升序": for (let i = 0; i < length; i++) { chaptersIndex[i] = i; } break; case "原始降序": for (let i = 0; i < length; i++) { chaptersIndex[i] = length - i - 1; } break; case "章节号升序": chaptersIndex.sort((a, b) => chapters[a].chapterNo - chapters[b].chapterNo); break; case "章节号降序": chaptersIndex.sort((a, b) => chapters[b].chapterNo - chapters[a].chapterNo); break; case "网址升序": chaptersIndex.sort((a, b) => chapters[a].hrefNo - chapters[b].hrefNo); break; case "网址降序": chaptersIndex.sort((a, b) => chapters[b].hrefNo - chapters[a].hrefNo); break; } createTr(); } //#endregion List //#region helper function getElementSelector(element, doc) { function getRightId(id) { var firstCode = id.charCodeAt(0); if (firstCode >= 48 && firstCode <= 57) return "\\3" + id[0] + " " + id.substr(1, id.length); else return id; } let domPath = []; var e = element; while (e.nodeName.toLowerCase() !== "html") { var tag = e.tagName.toLocaleLowerCase(); if (e.id) { domPath.unshift('#' + getRightId(e.id)); break; } else if (tag == "body") { domPath.unshift(tag); } else { var index = 0; var isOneTag = true; var isOneClass = e.classList.length > 0; for (var i = 0; i < e.parentNode.childElementCount; i++) { if (e.parentNode.children[i] == e) { index = i; } else if (e.parentNode.children[i].tagName == e.tagName) { isOneTag = false; if (e.classList.length > 0 && e.parentNode.children[i].classList.toString() == e.classList.toString()) { isOneClass = false; } } } if (isOneTag) { domPath.unshift(tag); } else if (isOneClass) { var className = e.className.trim(); domPath.unshift(tag + (className ? '.' + className.replace(/\s+/g, ".") : "")); } else { domPath.unshift(tag + ':nth-child(' + (index + 1) + ')'); } } var selector = domPath.toString().replace(/,/g, '>'); var eles = doc.querySelectorAll(selector); if (eles.length == 1 && eles[0] == element) break; e = e.parentNode; } return domPath.toString().replace(/,/g, '>'); } //getElementSelector //#endregion helper //#region Text function getContentElement(doc) { //参考“怠惰小说下载器” function getEffectiveText(text) { return text.replace(/\s+/g, ''); } var largestContent, contents = doc.querySelectorAll("span,div,article,section,p,td"), largestNum = 0; for (let content of contents) { var curNum = 0; for (let item of content.childNodes) { if (item.nodeType == 3) { if (!/^\s*$/.test(item.data)) curNum += getEffectiveText(item.data).length; } else if (/^(I|A|STRONG|B|FONT|P|DL|DD|H\d)$/.test(item.tagName)) { //有这些子节点 curNum += getEffectiveText(item.innerText).length; } } if (curNum > largestNum) { largestNum = curNum; largestContent = content } } //console.log(largestContent, largestNum, getEffectiveText(largestContent.innerText)); return largestContent; } function getElementText(element) { //参考“怠惰小说下载器” let result = ""; for (let childNode of element.childNodes) { if (childNode.innerHTML) { childNode.innerHTML = childNode.innerHTML.replace(/<\s*br\s*>/gi, "\r\n").replace(/\n+/gi, "\n").replace(/\r+/gi, "\r"); } if (childNode.nodeType == 1 && !/^(I|A|STRONG|B|FONT)$/.test(childNode.tagName)) result += "\r\n"; if (childNode.textContent) { var text = childNode.textContent; text = text.replace(/[\u00A0\u2002\u2003\u2005\u200C\u200D]/g, ''); result += text.trim().replace(/ +/g, " ").replace(/([^\r]|^)\n([^\r]|$)/gi, "$1\r\n$2"); } } return result; } function download(href, index, callback) { if (typeof index == 'undefined') return; if (index < 0 || index >= chapters.length) return; if (href == null) { href = chapters[index].href; chapters[index].text = ''; chapters[index].state = ''; chapters[index].nextCount = 0; } let requestBody = { method: 'GET', url: href, headers: { referer: href, "Content-Type": "text/html;charset=" + document.charset, }, timeout: 15000, overrideMimeType: "text/html;charset=" + document.charset, onload: function (result) { var doc = getDoc(result.responseText); doc.href = href; //记下当前页面的网址,自己生成的没有网址。 deleteSomeTag(doc, 'script'); deleteSomeTag(doc, 'style'); deleteElementBySelector(doc, '*[style*="display:none"]'); deleteElementBySelector(doc, '#float_favorite'); deleteHideElement(doc); callback(doc, index); }, onerror: function () { console.warn("error:", href); callback(null, index); }, ontimeout: function () { console.warn("timeout: ", href); callback(null, index); } }; GM_xmlhttpRequest(requestBody); } //getDocByHref function deleteHideElement(doc) { if (!doc.defaultView) return; //直接下载网页的没有doc.defaultView var elements = doc.querySelectorAll("span,div,ul,li") //var elements = doc.querySelectorAll("li") for (var i = elements.length - 1; i >= 0; i--) { var ele = elements[i]; var thisStyle = doc.defaultView.getComputedStyle(ele); if (thisStyle && (thisStyle.display == "none" || (ele.tagName == "SPAN" && thisStyle.fontSize == "0px"))) ele.remove(); } } function deleteElementBySelector(doc, selector) { var elements = doc.querySelectorAll(selector); for (var i = elements.length - 1; i >= 0; i--) { elements[i].remove(); } } function deleteSomeTag(doc, tag) { var elements = doc.getElementsByTagName(tag); for (var i = elements.length - 1; i >= 0; i--) { elements[i].remove(); } } function getDoc(str) { var doc = null; try { doc = document.implementation.createHTMLDocument(''); doc.documentElement.innerHTML = str; } catch (e) { console.log('parse error'); } return doc; } //getDoc function getNextPage(doc) { let eles = doc.querySelectorAll("a"); for (let ele of eles) { if (nextPageReg.test(ele.innerText) && ele.href.indexOf("javascript") == -1) return ele; } } //获得正文,如果有下一页网址不在章节网址,继续获取,并返回Next function getTextFromDoc(doc, index, callback) { function addTexttoChapter(text) { chapters[index].text += ((doc.href == chapters[index].href) ? "" : `>>${doc.title}\n`) + text; } if (doc) { var nextPage = getNextPage(doc); var nextPagehref = nextPage ? nextPage.href : ""; var jammerSelector=(setting.jammerSelector||'').trim().replace(/\n/,','); if (jammerSelector) deleteElementBySelector(doc, jammerSelector); var content; var textSelector=setting.textSelector; if (!textSelector||textSelector=='--') { content = getContentElement(doc); textSelector = getElementSelector(content, doc); if(setting.textSelector!='--'){ setting.textSelector = textSelector; displaySetting(); } } if (customTextFunc) { var customResult = customTextFunc(doc, textSelector); if (Array.isArray(customResult)) { [doc, textSelector] = customResult; } else if (typeof customResult == 'string') { addTexttoChapter(customResult+'\n'); return 'OK'; } else { return 'No' } } textSelector=(textSelector||'').trim().replace(/\n/,','); var eTexts = doc.querySelectorAll(textSelector); if(eTexts.length==0) return 'No'; var text='' for (var eText of eTexts) { text+=getElementText(eText)+'\n'; } addTexttoChapter(text); if (nextPagehref) { var href2 = nextPagehref.slice(0, 6) == 'https:' ? 'http:' + nextPagehref.slice(6) : 'https' + nextPagehref.slice(5); if (nextPagehref == document.location.href || href2 == document.location.href) return 'OK'; // 如果a元素的href为空,返回的是目录页的地址 if (nextPagehref == doc.href || href2 == doc.href) return 'OK'; if (getIndexOfObjectArray(chapters, "href", nextPagehref) == -1 && getIndexOfObjectArray(chapters, "href", href2) == -1) { if (chapters[index].nextCount > nextCountLimit - 1) { //第一个下一页为0 if (continueDownload || confirm(`目录“${chapters[index].title}”的下一页数量超过最大值${nextCountLimit},你要让以后的下一页继续吗?\n继续可能会下载太多链接,请谨慎继续!`)) { continueDownload = true; } else { return '>N'; } } if (chapters[index].nextCount > maxNextCount - 1) return '>>N' download(nextPagehref, index, callback); return 'Next'; } else return 'OK'; } else return 'OK'; } else { return 'Er'; } } //getTextFromDoc function showChapterText(index) { function showOrDownload(index) { if (isDownloaded(index)) { showChapterText(index) } else { download(null, index, getTextCallback) } } reader.setReader(chapters[index].text, chapters[index].title, (index - 1 >= 0 && index - 1 < chapters.length) ? '<' : '', () => { showOrDownload(index - 1) }, (index + 1 >= 0 && index + 1 < chapters.length) ? '>' : '', () => { showOrDownload(index + 1) }); } function showState(index) { var span = chapters[index].tr.querySelector("td>span"); span.textContent = (chapters[index].state || '') + ((chapters[index].state != 'OK' && chapters[index].text) ? "+" : "") + (chapters[index].nextCount || ''); } function getTextCallback(doc, index) { var state = getTextFromDoc(doc, index, getTextCallback); switch (state) { case 'No': if (confirm(`${chapters[index].text ? "后续页" : ""}找不到正文选择器指定的元素,清空正文选择器重新获取。`)) { delete setting.textSelector; displaySetting(); } break; case 'Er': alert(`${chapters[index].href}下载出错`); break; case 'Next': chapters[index].nextCount += 1; return; } chapters[index].state = state; showState(index); if (chapters[index].text) showChapterText(index); } var getText = e => { setting = getSetting(); if (typeof setting != 'object') return; var tr = e.target.parentElement.parentElement; var index = getIndexOfObjectArray(chapters, "tr", tr); if (isDownloaded(index)) showChapterText(index); else download(null, index, getTextCallback); } function saveAllText() { var allText = '', a; for (var i of chaptersIndex) { allText += '\n##' + chapters[i].title + '\n' + chapters[i].text; } var blob = new Blob(['#' + document.title + '\n', document.location.href + '\n', allText], { type: "text/plain;charset=utf-8", endings: "native" }); var filename = document.title.replace(/[/\\?%*:|"<>.]/g, '-') + '.txt'; downloadFile(blob, filename); } function getAllTextCallback(doc, index) { var state = getTextFromDoc(doc, index, getAllTextCallback); switch (state) { case 'No': downloadedNo += 1; break; case 'Er': downloadedErr += 1; break; case '>N': case '>>N': downloadedExceedNext += 1; break; case 'Next': chapters[index].nextCount += 1; return; } chapters[index].state = state; showState(index); download(null, chaptersIndex[downloadIndex], getAllTextCallback); downloadIndex = getNextUndownloadIndex(downloadIndex + 1); downloadedCount += 1; root.querySelector('#downloadNumbers').textContent = `${chapters.length}/${downloadedCount}/${downloadedNo}/${downloadedErr}/${downloadedExceedNext}`; if (downloadedCount >= chapters.length) { if (downloadedNo == 0 && downloadedErr == 0) { saveAllText(); } else { if (confirm(`${downloadedNo}个找不到正文元素,${downloadedErr}个下载失败。\n是否保存已下载的文本。`)) { saveAllText(); } } } } //getAllTextCallback function isDownloaded(index) { var state = chapters[index].state; return state == 'OK' } function getNextUndownloadIndex(index) { while (index < chaptersIndex.length && isDownloaded(chaptersIndex[index])) { downloadedCount += 1; index += 1; } return index; } function getAllText() { if (chapters.length < 1) alert("没有目录,请先获取目录再下载全部文本。"); downloadedCount = 0; //已下载数量 downloadedErr = 0; //下载失败数量 downloadedNo = 0; //下载章节找不到选择器对应元素的数量 downloadIndex = 0; //当前待下载序号 downloadIndex = getNextUndownloadIndex(downloadIndex); if (downloadIndex >= chapters.length) saveAllText(); //已经获取全部文本 else { for (var i = 0; i < MaxThread; i++) { download(null, chaptersIndex[downloadIndex], getAllTextCallback); downloadIndex = getNextUndownloadIndex(downloadIndex + 1); } } } var deleteRow = e => { var tr = e.target.parentElement.parentElement; var index = getIndexOfObjectArray(chapters, "tr", tr); tr.remove(); chapters.splice(index, 1); chaptersIndex.splice(chaptersIndex.indexOf(index), 1); for (let i = 0; i < chaptersIndex.length; i++) { if (chaptersIndex[i] > index) chaptersIndex[i] -= 1; } }; function getIndexOfObjectArray(objectArray, key, value) { for (let i = 0; i < objectArray.length; i++) { if (objectArray[i][key] == value) return i; } return -1; } function displaySetting() { // eSetting.value=toTplString(JSON.stringify(setting, null, 2)); cm.setValue(settingToString(setting)); setSaveSiteSettingClass(); } function getSetting(isSetCunstomFun = true) { var result = {}, key = '', value = ''; var lines = cm.getValue().split('\n'); for (let line of lines) { if (!line.trim()) continue; if (line.slice(0, 2) == '$$') { if (key && value) result[key] = value.trim(); key = line.slice(2); value = ''; if (SETTING_KEYS.indexOf(key) == -1) { return alert(`网站配置输入框中,键值${key}不合法,请修改。`); } } else { value += (value ? '\n' : '') + line; } } if (key && value) result[key] = value.trim(); if (isSetCunstomFun) { customListFunc = setting.customListFunc ? Function("doc", "selector", setting.customListFunc + ";return [doc,selector];") : null; customItemFunc = setting.customItemFunc ? Function("doc", "item", setting.customItemFunc + ";return [doc,item];") : null; customTextFunc = setting.customTextFunc ? Function("doc", "selector", setting.customTextFunc + ";return [doc,selector];") : null; } return result; } function settingToString(setting) { var result = ''; for (var key in setting) { result += '$$' + key + '\n' result += setting[key] + '\n'; } return result; } function saveSiteSetting() { setting = getSetting(); if (typeof setting != 'object') return; if (isSameObject(setting, {})) { GM_deleteValue(location.host); alert("已删除该网站配置"); } else { GM_setValue(location.host, setting); alert("已保存该网站配置"); } setSaveSiteSettingClass(); } function isSameObject(object1, object2) { if (!object1 || !object2) return false; var ss1 = Object.entries(object1).toString(); var ss2 = Object.entries(object2).toString(); return ss1 === ss2; } function setRestListClass(){ var nextList=getNextList(document); root.querySelector("#getRestList").disabled=!nextList; } function setSaveSiteSettingClass() { tempSetting = getSetting(false); if (typeof tempSetting == 'object') { var gmSetting = GM_getValue(location.host); root.querySelector("#saveSiteSetting").className = isSameObject(tempSetting, gmSetting) ? "disabled" : "" } setRestListClass(); //设置剩余目录按钮可用性 } function enableCodeMirrow() { var CodeMirrorminCss = GM_getResourceText("CodeMirrorminCss") var style = document.createElement('style'); style.innerHTML = CodeMirrorminCss; root.appendChild(style); var ele = root.getElementById("setting"); cm = CodeMirror.fromTextArea(ele, { matchBrackets: true, mode: "javascript", }); cm.setSize(CodeMirrorSize[0], CodeMirrorSize[1]); cm.on('blur', function () { setSaveSiteSettingClass(); }); document.ytheditor = cm; window.ytheditor = cm; ytheditor = cm SETTING_KEYS.forEach(words => { //CodeMirror.resolveMode("javascript").keywords[words] = true; }); } function downloadFile(blob, fileName) { var link = document.createElement('a'); link.href = window.URL.createObjectURL(blob); link.download = fileName; link.click(); link.remove(); window.URL.revokeObjectURL(link.href); } function addSiteSettings(siteSettings) { for (var name in siteSettings) { var setting = siteSettings[name]; var tmpSetting = {}; for (var key of SETTING_KEYS) { if (setting.hasOwnProperty(key)) tmpSetting[key] = setting[key]; } if (!isSameObject(tmpSetting, {})) GM_setValue(name, tmpSetting); } } function selClick(e) { e.stopPropagation(); e.preventDefault(); document.getElementById("ythList").style.display=''; document.body.removeEventListener("mouseover", selMouseover); document.body.removeEventListener("click", selClick); var t = getFittedElement(e.target); if(!t)return; var [mostAncestor, mostSelector, mostCount] = getAncestorWithMostSimilarDescendant(t,"a:not([href*='javascript'])"); var eListSelector = getElementSelector(mostAncestor, document) + '>' + mostSelector; setting.listSelector = eListSelector; displaySetting(); root.querySelector("#clearList").click(); root.querySelector("#getList").click(); } function getFittedElement(e) { var t = e.nodeName.toLowerCase(); return t == "a"&&!/javascript/.test(e.href)?e:(t == "body"? null : getFittedElement(e.parentNode)); } function selMouseover(e) { e.stopPropagation(); var t = getFittedElement(e.target); if(!t)return; t.addEventListener("mouseout", function(o) { var n = getFittedElement(o.target); n.className = n.className.replace(new RegExp(" " + hiClass + "\\b","g"), "") }); t.className += " " + hiClass; } function getClickedElementSelector(){ setting = getSetting(); if (typeof setting != 'object') return; document.getElementById("ythList").style.display='none'; setTimeout(()=>{ document.body.addEventListener("mouseover", selMouseover); // document.body.addEventListener("mousedown", selClick); document.body.addEventListener("click", selClick); },200); } function addDiv() { GM_addStyle(` #ythList{ position:fixed; right:0px; z-index: 99999999999; background-color: #ccc; top: 0; } .ythHighlight{ background-color:#8ce!important; cursor:pointer!important; outline:4px solid #016!important; } `); var shadowCss = ` `; var div = document.createElement("div"); div.id = "ythList"; root = div.attachShadow({ mode: 'open' }); var html = shadowCss; var importSvg = ''; var exportSvg = ''; var saveSvg = ''; var clearAllSvg = ''; var shrinkSvg = ''; var expandSvg = ''; // var focusSvg = ''; var focusSvg = '' html += `
︿
序号 标题
`; root.innerHTML = html; document.body.appendChild(div); reader = (typeof Reader == 'function') ? new Reader(document, "ReaderAttached") : null; root.querySelector("#sort").addEventListener("change", (e) => { sortList(e.target.value); }); enableCodeMirrow(); setting = GM_getValue(location.host); if (!setting) setting = {}; displaySetting(); var eGetList = root.querySelector("#getList"); eGetList.onclick = () => { getList(document); }; eGetList.click(); root.querySelector("#getRestList").addEventListener("click", () => { getRestList(document); }); // setSaveSiteSettingClass(); var resize = root.querySelector("#resize"); resize.addEventListener("click", (e) => { var ele = resize; if (ele.title == '放大') { ele.title = '缩小'; ele.innerHTML = shrinkSvg; cm.setSize(CodeMirrorSize[0] * 2, CodeMirrorSize[1] * 2); } else { ele.title = '放大'; ele.innerHTML = expandSvg; cm.setSize(CodeMirrorSize[0], CodeMirrorSize[1]); } }); resize.click(); var inputFiles = root.querySelector("#inputFiles"); inputFiles.addEventListener("change", (e) => { var file = e.target.files[0]; if (!file) return; var reader = new FileReader(); reader.readAsText(file); reader.onload = function (e) { var siteSettings = JSON.parse(this.result); addSiteSettings(siteSettings); } }); root.querySelector("#focus").addEventListener("click", (e) => { getClickedElementSelector(); }); root.querySelector("#importSiteSetting").addEventListener("click", (e) => { inputFiles.click(); }); root.querySelector("#exportSiteSetting").addEventListener("click", (e) => { var names = GM_listValues(); var siteSettings = {}; for (var name of names) { if (name == 'ReaderStyle') continue; siteSettings[name] = GM_getValue(name); } var str = JSON.stringify(siteSettings, null, 2); var blob = new Blob([str], { type: "text/plain;charset=utf-8", endings: "native" }); downloadFile(blob, "AllSiteSetting.txt") }); root.querySelector("#clearAllSiteSetting").addEventListener("click", (e) => { var names = GM_listValues(); for (var name of names) { if (name == 'ReaderStyle') continue; GM_deleteValue(name); } alert("已删除现有全部网站配置信息"); }); root.querySelector("#saveSiteSetting").addEventListener("click", (e) => { saveSiteSetting(); }); var container = root.querySelector("#container") container.style.height = '100vh'; root.querySelector("#toggle").addEventListener("click", (e) => { if (e.target.textContent == '﹀') { e.target.textContent = '︿' e.target.parentElement.nextElementSibling.style.display = '' container.style.height = '100vh'; } else { e.target.textContent = '﹀' e.target.parentElement.nextElementSibling.style.display = 'none' container.style.height = ''; } }); root.querySelector("#DeleteNoText").onclick = () => { for (let i = chapters.length - 1; i >= 0; i--) { if (!chapters[i].text) { chapters.splice(i, 1); } } chaptersIndex = null; sortList(root.querySelector("#sort").value); }; root.querySelector("#clearList").onclick = () => { root.querySelector("table tbody").innerHTML = ""; chapters = []; chaptersIndex = []; }; root.querySelector("#getAllText").onclick = () => { setting = getSetting(); if (typeof setting == 'object') getAllText(); }; } GM_registerMenuCommand("开始", addDiv); })(); //addDiv