///
// ==UserScript==
// @name 69shuba auto 書簽
// @namespace Paul-16098
// @version 3.5.13.0
// @description 自動書籤,更改css,可以在看書頁找到作者連結
// @author Paul-16098
// #tag 69shux.com
// @match https://69shux.com/txt/*/*
// @match https://69shux.com/txt/*/end.html
// @match https://69shux.com/book/*.htm*
// @match https://69shux.com/modules/article/bookcase.php*
// #tag www.69shu.top
// @match https://www.69shu.top/txt/*/*
// @match https://www.69shu.top/txt/*/end.html
// @match https://www.69shu.top/book/*.htm*
// @match https://www.69shu.top/modules/article/bookcase.php*
// #tag www.69shu.cx
// @match https://www.69shu.cx/txt/*/*
// @match https://www.69shu.cx/txt/*/end.html
// @match https://www.69shu.cx/book/*.htm*
// @match https://www.69shu.cx/modules/article/bookcase.php*
// #tag 69shuba.cx
// @match https://69shuba.cx/txt/*/*
// @match https://69shuba.cx/txt/*/end.html
// @match https://69shuba.cx/book/*.htm*
// @match https://69shuba.cx/modules/article/bookcase.php*
// #tag www.69shuba.pro
// @match https://www.69shuba.pro/txt/*/*
// @match https://www.69shuba.pro/txt/*/end.html
// @match https://www.69shuba.pro/book/*.htm*
// @match https://www.69shuba.pro/modules/article/bookcase.php*
// #tag 69shuba.me
// @match https://69shu.me/txt/*/*
// @match https://69shu.me/txt/*/end.html
// @match https://69shu.me/book/*.htm*
// @match https://69shu.me/modules/article/bookcase.php*
// #tag 69shu.biz
// @match https://69shu.biz/c/*/*
// @match https://69shu.biz/c/*/end.html
// @match https://69shu.biz/b/*.htm*
// @match https://69shu.biz/modules/article/bookcase.php*
// #tag www.69yuedu.net
// @match https://www.69yuedu.net/r/*/*.html*
// @match https://www.69yuedu.net/article/*.html*
// @match https://www.69yuedu.net/modules/article/bookcase.php*
// #tag www.69shuba.com
// @match https://www.69shuba.com/txt/*/*
// @match https://www.69shuba.com/modules/article/bookcase.php*
// @match https://www.69shuba.com/book/*.htm
// #tag twkan.com
// @match https://twkan.com/txt/*/*
// @match https://twkan.com/bookcase*
// @match https://twkan.com/book/*.html
// @icon https://www.google.com/s2/favicons?sz=64&domain=69shuba.com
// @grant window.close
// @grant GM_addStyle
// @grant GM_getValue
// @grant GM_setValue
// @grant unsafeWindow
// @grant GM_registerMenuCommand
// @grant GM_openInTab
// @grant GM_getResourceText
// @run-at document-idle
//#if debug
// #@require file://C:\Users\p\Documents\git\userjs\Tools\Tools.user.js
//#else
// @require https://github.com/Paul-16098/userjs/raw/dev/Tools/Tools.user.js
//#endif
// @resource css1 https://github.com/Paul-16098/userjs/raw/refs/heads/dev/69shuba%20auto%20%E6%9B%B8%E7%B0%BD/69shuba%20auto%20%E6%9B%B8%E7%B0%BD.user.css
// @resource replace_json https://github.com/Paul-16098/userjs/raw/dev/69shuba%20auto%20%E6%9B%B8%E7%B0%BD/replace.json
// @license MIT
// @supportURL https://github.com/Paul-16098/userjs/issues/
// @homepageURL https://github.com/Paul-16098/userjs/README.md
// ==/UserScript==
// 語言選項枚舉
"use strict";var Language=/*#__PURE__*/function(Language){Language["en"]="en";Language["zh"]="zh";return Language}(Language||{});/**
* 用戶配置類,負責管理腳本的各項設置,並註冊菜單。
*/class Config{/** 是否開啟偵錯模式 */Debug=GM_getValue("Debug",false);/** 結束頁是否自動關閉 */IsEndClose=GM_getValue("IsEndClose",true);/** 是否自動加入書櫃 */AutoAddBookcase=GM_getValue("AutoAddBookcase",true);/** 自動加入書櫃的封鎖名單(書ID陣列) */AutoAddBookcaseBlockade=GM_getValue("AutoAddBookcaseBlockade",[]);/** 是否攔截alert */IsHookAlert=GM_getValue("IsHookAlert",true);/** 攔截alert的訊息封鎖名單 */HookAlertBlockade=GM_getValue("HookAlertBlockade",[["添加成功"],["刪除成功!"],["恭喜您,该章节已经加入到您的书签!"]]);/** 語言設定 */Language=GM_getValue("Language","zh");constructor(){this.set();this.registerConfigMenu();return this}/**
* 註冊所有配置項的菜單
*/registerConfigMenu(){for(const key in this){const value=this[key];let menu=undefined;// 語言切換菜單
if(Object.values(Language).includes(value)){menu=()=>{Object.values(Language).forEach(lang=>{if(lang!==value){GM_setValue("Language",lang);location.reload()}})}}setMenu(key,menu,value,{zh:"中文",en:"english",Debug:"偵錯",AutoAddBookcase:"自動添加書櫃",AutoAddBookcaseBlockade:"自動添加書櫃封鎖",Language:"語言",IsEndClose:"結束後關閉",IsHookAlert:"掛鉤Alert",HookAlertBlockade:"掛鉤Alert封鎖"})}}/**
* 將當前配置寫入GM存儲
*/set(){GM_setValue("Debug",this.Debug);GM_setValue("IsEndClose",this.IsEndClose);GM_setValue("AutoAddBookcase",this.AutoAddBookcase);GM_setValue("AutoAddBookcaseBlockade",this.AutoAddBookcaseBlockade);GM_setValue("IsHookAlert",this.IsHookAlert);GM_setValue("HookAlertBlockade",this.HookAlertBlockade);GM_setValue("Language",this.Language)}}// 配置初始化
const config=new Config;// i18n 設定
const i18nData={en:{noMatchingPattern:"No matching URL pattern found",errorOccurred:"An error occurred: ",noLabelsFound:"No labels found, retrying in 5 seconds...",maxRetriesReached:"Max retries reached. No labels found.",noUpdates:"No updates",updatesAvailable:" updates available"},zh:{noMatchingPattern:"未找到匹配的 URL 模式",errorOccurred:"發生了一些錯誤: ",noLabelsFound:"未找到標籤,5 秒後重試...",maxRetriesReached:"已達到最大重試次數。未找到標籤。",noUpdates:"沒有更新",updatesAvailable:"個更新"}};const i18nInstance=new i18n(i18nData,config.Language);const t=i18nInstance.t;/**
* `BookManager` 類別提供了各種方法來管理網頁上與書籍相關的資料並與之互動。
* 它包括偵測圖書頁面、圖書資訊頁面、結束頁面和書架頁面的功能。
* 它還提供了處理導航、將書籍添加到書架以及修改頁面元素的方法。
*
* @class
* @classdesc 此類旨在自動化並增強與圖書相關的網站的使用者體驗。
*
* @property {Object} data -包含用於偵測和處理書籍相關頁面的各種方法和模式。
* @property {Function} data.HasBookInfo -檢查是否可用書籍信息。
* @property {Function} data.IsBookshelf -檢查當前頁面是否是書架頁面。
* @property {Object} data.Book -包含與書籍操作有關的方法。
* @property {Function} data.Book.GetAid -檢索書ID。
* @property {Function} data.Book.GetCid -檢索章節ID。
* @property {RegExp} data.Book.pattern -標識書頁的模式。
* @property {Function} data.Book.Is -檢查當前頁面是否是書頁。
* @property {Object} data.Info -包含與書籍信息操作有關的方法。
* @property {RegExp} data.Info.pattern -識別圖書信息頁面的模式。
* @property {Function} data.Info.Is -檢查當前頁面是否是書籍信息頁面。
* @property {Object} data.End -包含與最終頁面操作相關的方法。
* @property {Function} data.End.Is -檢查當前頁面是否是結束頁面。
* @property {Function} data.GetNextPageUrl -檢索下一頁的URL。
* @property {Function} data.IsNextEnd -檢查下一頁是否是終點頁面。
* @property {Function} data.IsBiz -檢查當前域是否為“ 69shu.biz”。
*
* @constructor
* @description 初始化 `Bookmanager` 類的新實例。它註冊了配置菜單並處理不同類型的頁面(書籍, 書籍信息, 結束, 書架)。如果設置了 `config.debug` 標誌, 它還記錄了調試信息。
* @throws 如果發生錯誤並且未設置 `config.debug` , 將提醒用户。
*
* @method handleBookPage
* @description 透過執行各種修改和增強來處理書籍頁面。
* @private
* @returns {void}
*
* @method hookAlert
* @description 掛鈎全域「警報」功能以有條件地阻止或記錄警報訊息。
* @private
* @returns {void}
*
* @method addStyles
* @description 透過從指定資源注入 CSS 內容, 將自訂樣式新增至文件。
* @private
* @returns {void}
*
* @method modifyPageNavigation
* @description 透過刪除現有的「onkeydown」事件處理程序並新增新的「keydown」事件偵聽器來修改頁面導航。
* @private
* @returns {void}
*
* @method keydownHandler
* @description 處理鍵盤事件, 以便在按下「向右箭頭」鍵時導覽至下一頁。
* @private
* @param {KeyboardEvent} e -鍵盤事件物件。
* @returns {void}
*
* @method addBookcase
* @description 將當前的書添加到書架中。
* @private
* @returns {void}
*
* @method insertAuthorLink
* @description 插入作者鏈接並用新鏈接替換標題DIV。
* @private
* @returns {void}
*
* @method handleBookshelf
* @description 通過收集書籍數據和註冊菜單命令來處理書架。
* @private
* @returns {Promise}
*
* @method collectBookData
* @description 通過以 `book_` 開頭查詢ID, 從DOM收集書籍數據。
* @private
* @param {number} [retryCount=0] -當前的重試計數。
* @returns {Promise} -解決一系列收集的書籍數據的承諾。
*
* @method registerMenuCommand
* @description 註冊菜單命令, 其中包含收集的書籍數據。
* @private
* @param {BookData[]} bookData -收集的書籍數據。
* @returns {void}
*
* @method debugInfo
* @description 收集和返回調試信息。
* @private
* @returns {Object} -調試信息。
*
* @method registerConfigMenu
* @description 註冊配置菜單。
* @private
* @returns {void}
*/class BookManager{/** 常用選擇器集合 */SELECTORS={nextPage:["body > div.container > div.mybox > div.page1 > a:nth-child(4)","body > div.mainbox > div > div.page1 > a:nth-child(4)"],authorInfo:"body > div.container > div.mybox > div.txtnav > div.txtinfo.hide720 > span:nth-child(2)",titleDiv:"body > div.container > div.mybox > div.tools",searchInput:"body > header > div > form > div > div.inputbox > input[type=text]",searchForm:"body > header > div > form"};/**
* 各種頁面判斷與數據獲取方法集合
*/data={// 判斷是否有書籍信息
HasBookInfo:typeof bookinfo!=="undefined",// 判斷是否在書架頁面
IsBookshelf:(href=location.href)=>{if(this.data.IsTwkan){return new URL(href).pathname==="/bookcase"}else{return new URL(href).pathname==="/modules/article/bookcase.php"}},// 書籍相關操作
Book:{// 獲取書籍ID
GetAid:(href=window.location.href)=>{if(this.data.HasBookInfo){return bookinfo.articleid}return href.split("/")[4]},// 獲取章節ID
GetCid:(href=window.location.href)=>{if(this.data.HasBookInfo){return bookinfo.chapterid}return href.split("/")[5]},// 書籍URL模式
pattern:/^\/(txt|c|r)\/([0-9]|[a-z])+\/([0-9]|[a-z])+(\.html)?$/m,// 判斷是否為書籍頁面
Is:(href=window.location.href)=>{return this.data.Book.pattern.test(new URL(href).pathname)}},// 書籍信息相關操作
Info:{// 書籍信息URL模式
pattern:/^\/(book|b|article)\/([0-9]|[a-z])+\.htm(l)?$/m,// 判斷是否為書籍信息頁面
Is:(pathname=window.location.pathname)=>{return this.data.Info.pattern.test(pathname)}},// 結束頁面相關操作
End:{// 判斷是否為結束頁面
Is:(href=window.location.href)=>{if(this.data.Info.Is()){const searchParams=new URL(href).searchParams;return searchParams.get("FromBook")==="true"}if(this.data.IsTwkan){let h=new URL(href);if(/txt\/[0-9]+\/end\.html/.test(h.pathname)&&h.searchParams.get("FromBook")==="true"){return true}else{return false}}return false}},// 獲取下一頁URL
GetNextPageUrl:()=>{const nextPageEle=this.getNextPageElement();return nextPageEle?.href},// 判斷下一頁是否為結束頁面
IsNextEnd:()=>{if(this.data.Book.Is()){const nextUrl=this.data.GetNextPageUrl();if(nextUrl){return this.data.End.Is(nextUrl)||this.data.Info.Is(new URL(nextUrl).pathname)}}return false},// 判斷是否為69shu.biz域名
IsBiz:location.host==="69shu.biz",// 判斷是否為twkan.com域名
IsTwkan:location.host==="twkan.com"};/**
* 取得下一頁的a元素
*/getNextPageElement(){for(const selector of this.SELECTORS.nextPage){const element=document.querySelector(selector);if(element&&element.href)return element}// 備用方案:尋找文字為"下一章"的連結
return Array.from(document.querySelectorAll("a")).find(link=>link.textContent==="下一章")}/**
* 構造函數,根據當前頁面自動分派對應處理
*/constructor(){try{if(config.Debug){console.debug(this.debugInfo())}// #tag search
const search=new URLSearchParams(location.search).get("q");if(search)this.performSearch(search);// #tag BookEnd
if(this.data.End.Is()){if(config.Debug)console.log("End page detected");if(config.IsEndClose)window.close()}// #tag Book
if(this.data.Book.Is()){if(config.Debug)console.log("Book page detected");this.handleBookPage()}// #tag Info
if(this.data.Info.Is()){if(config.Debug)console.log("Book info page detected");let Ele=document.querySelector("body > div.container > ul > li.col-8 > div:nth-child(2) > ul > li:nth-child(2) > a");if(Ele){Ele.click()}}// #tag Bookshelf
if(this.data.IsBookshelf()){if(config.Debug)console.log("Bookshelf page detected");this.handleBookshelf()}// if not match any pattern
if(!this.data.Book.Is()&&!this.data.Info.Is()&&!this.data.End.Is()&&!this.data.IsBookshelf()){if(!config.Debug){alert(t("noMatchingPattern"))}}}catch(error){console.error(error);if(!config.Debug){alert(`${t("errorOccurred")}${String(error)}`)}}}/**
* 書頁自動化處理:樣式、導航、元素移除、書櫃、作者連結、下一頁鏈接
*/handleBookPage(){if(config.IsHookAlert)this.hookAlert();this.addStyles();this.modifyPageNavigation();removeElement(".mytitle",".top_Scroll","#pagefootermenu","body > div.container > div > div.yueduad1","#pageheadermenu",".bottom-ad2","body > div.container > div.yuedutuijian.light");if(this.data.IsTwkan){removeElement("#container > br")}if(config.AutoAddBookcase)this.autoAddToBookcase();this.insertAuthorLink();this.updateNextPageLink();if(this.data.IsTwkan){const raw_replace_json=GM_getResourceText("replace_json");let replace_json={};try{replace_json=JSON.parse(raw_replace_json)}catch(error){if(error instanceof SyntaxError){if(config.Debug){console.log(error)}else{alert(error)}}else{throw error}}if(config.Debug){console.log("replace_json: ",replace_json)}for(const key in replace_json){if(Object.prototype.hasOwnProperty.call(replace_json,key)){const element=replace_json[key];document.querySelector("#txtcontent").innerText=document.querySelector("#txtcontent")?.innerText.replaceAll(key,element)}}}}/**
* 自動加入書櫃(如未在封鎖名單)
*/autoAddToBookcase(){const aid=this.data.Book.GetAid();if(!config.AutoAddBookcaseBlockade.includes(aid)){this.addBookcase()}else{console.log("Book is in the blockade list, not auto adding to bookcase.")}}/**
* 更新下一頁鏈接,附加FromBook參數
*/updateNextPageLink(){const nextPageEle=this.getNextPageElement();if(nextPageEle){const href=new URL(nextPageEle.href);href.searchParams.set("FromBook","true");nextPageEle.href=href.toString()}}/**
* 攔截全局alert,根據封鎖名單過濾
*/hookAlert(){const _alert=alert;unsafeWindow.alert=(...message)=>{if(!config.HookAlertBlockade.some(blockade=>JSON.stringify(message)===JSON.stringify(blockade)||JSON.stringify(blockade)==="*")){_alert(...message)}if(config.Debug)console.log("Alert message:",message)}}/**
* 注入自定義CSS樣式
*/addStyles(){const css1=GM_getResourceText("css1");GM_addStyle(css1);if(config.Debug)console.log("CSS added")}/**
* 移除原有onkeydown,註冊自定義鍵盤導航
*/modifyPageNavigation(){document.onkeydown=null;addEventListener("keydown",this.keydownHandler.bind(this))}/**
* 處理右鍵導航與結束自動關閉
*/keydownHandler(e){if(!e.repeat&&e.key==="ArrowRight"){const nextPageLink=this.data.GetNextPageUrl();if(nextPageLink){let href=new URL(nextPageLink);href.searchParams.set("FromBook","true");window.location.href=href.toString()}if(this.data.IsNextEnd()){if(config.IsEndClose){window.close()}}}}/**
* 加入書櫃(根據不同站點呼叫不同API或模擬點擊)
*/addBookcase(){const aid=this.data.Book.GetAid();const cid=this.data.Book.GetCid();if(!addbookcase.toString().includes("Ajax.Tip")){addbookcase(aid,cid)}else{const addBookcaseLink=document.querySelector("#a_addbookcase");addBookcaseLink?.click()}}/**
* 替換標題div為帶有作者連結的新元素
*/insertAuthorLink(){const author=document.querySelector(this.SELECTORS.authorInfo)?.textContent?.trim().split(" ")[1]??"undefined";const authorLink=this.createAuthorLink(author);const titleDiv=document.querySelector(this.SELECTORS.titleDiv);if(titleDiv){const titleLink=this.createTitleLink();titleDiv.parentNode?.replaceChild(titleLink,titleDiv)}}/**
* 建立作者頁面連結元素
*/createAuthorLink(author){const authorLink=document.createElement("a");authorLink.href=`${window.location.origin}/modules/article/author.php?author=${encodeURIComponent(author)}`;authorLink.textContent=author;authorLink.style.color="#007ead";return authorLink}/**
* 建立書名連結元素
*/createTitleLink(){const titleLink=document.createElement("a");titleLink.innerHTML=this.data.HasBookInfo?bookinfo.articlename??document.title.split("-")[0]:document.title.split("-")[0];titleLink.classList.add("userjs_add");titleLink.id="title";titleLink.href=`${window.location.origin}/${this.data.IsBiz?"b":"book"}/${this.data.Book.GetAid()}.${this.data.IsBiz||this.data.IsTwkan?"html":"htm"}`;return titleLink}/**
* 書架頁面:收集書籍資料並註冊菜單
*/async handleBookshelf(){const bookData=await this.collectBookData();if(config.Debug)console.log("Bookshelf data collected",bookData);this.registerMenuCommand(bookData)}/**
* 搜尋功能:自動填入並提交表單
*/performSearch(search){const searchInput=document.querySelector(this.SELECTORS.searchInput);const searchForm=document.querySelector(this.SELECTORS.searchForm);if(searchInput&&searchForm){searchInput.value=search;searchForm.submit()}}/**
* 遞迴收集書架書籍資料,最多重試5次
*/async collectBookData(retryCount=0){const books=[];const labels=document.querySelectorAll("[id^='book_']");if(config.Debug)console.groupCollapsed("collectBookData");if(labels.length===0){if(retryCount<=5){console.warn(t("noLabelsFound"));await new Promise(resolve=>setTimeout(resolve,5e3));return this.collectBookData(retryCount+1)}else{console.error(t("maxRetriesReached"));return[];// 到達最大重試次數, 返回空陣列
}}if(config.Debug){console.log(labels)}labels.forEach(label=>{const bookContainer=label;const tmp=function(){if(Array.from(label.querySelectorAll("label")).find(label2=>label2.textContent==="更新")){// if (location.origin === "https://www.69yuedu.net") {
// } else {
const bookContinueEle=label.querySelector("div.newright > a.btn.btn-tp");const bookContinueLink=bookContinueEle.href;const BookName=label.querySelector("div.newnav > h3 > a > span").textContent;const bookImgEle=label.querySelector("a > img");const bookImgUrl=bookImgEle.src;// }
return{bookContinueLink,BookName,bookImgUrl}}else{return false}}();if(tmp){const{bookContinueLink,BookName,bookImgUrl}=tmp;const push_data={Updata:{url:{value:bookContinueLink,URLParams:new URLSearchParams(bookContinueLink)}},Mate:{BookName:BookName,BookHtmlObj:bookContainer,BookImgUrl:bookImgUrl}};if(config.Debug){console.group(push_data.Mate.BookName);console.log(push_data.Mate);console.table(push_data.Updata);console.groupEnd()}books.push(push_data)}});if(config.Debug)console.groupEnd();return books}/**
* 註冊菜單命令,點擊可批量打開所有更新書籍
*/registerMenuCommand(bookData){GM_registerMenuCommand(`${bookData.length===0?t("noUpdates"):`${bookData.length}${t("updatesAvailable")}`}`,()=>{bookData.forEach(data=>{GM_openInTab(data.Updata.url.value)})})}/**
* 輸出調試資訊
*/debugInfo(){return{IsBook:this.data.Book.Is(),IsInfo:this.data.Info.Is(),IsEnd:this.data.End.Is(),IsNextEnd:this.data.IsNextEnd(),IsBookshelf:this.data.IsBookshelf(),HasBookinfo:this.data.HasBookInfo,IsBiz:this.data.IsBiz,IsTwkan:this.data.IsTwkan,...config}}}// 初始化書籍管理器
const bookManager=new BookManager;
//# sourceMappingURL=69shuba auto 書簽.user.js.map