From 94d33d56e1fc8141f9588a07124c6a3ebd4c0d79 Mon Sep 17 00:00:00 2001 From: NekoAria Date: Sat, 9 Aug 2025 19:07:28 +0800 Subject: [PATCH 01/14] fix(ui): adapt `bangumi_related_subject_enhance` to new UI and enhance UX --- .../bangumi_related_subject_enhance.user.js | 345 +++++++++++++----- 1 file changed, 260 insertions(+), 85 deletions(-) diff --git a/liaune/bangumi_related_subject_enhance.user.js b/liaune/bangumi_related_subject_enhance.user.js index 7f688b76..8b63d003 100644 --- a/liaune/bangumi_related_subject_enhance.user.js +++ b/liaune/bangumi_related_subject_enhance.user.js @@ -1,7 +1,7 @@ // ==UserScript== // @name Bangumi Related Subject Enhance // @namespace https://github.com/bangumi/scripts/liaune -// @version 0.6.1 +// @version 0.6.2 // @description 显示条目页面关联条目的收藏情况,显示关联条目的排名,单行本设为全部已读/取消全部已读 // @author Liaune // @include /^https?:\/\/((bangumi|bgm)\.tv|chii.in)\/subject\/\d+$/ @@ -102,7 +102,7 @@ background: no-repeat url(/img/ico/ico_eye.png) 50% top; let objectStore = transaction.objectStore(tableName); let reqInfo = objectStore.get(itemId); reqInfo.onsuccess = evt => { - let result = evt.target.result; + let {result} = evt.target; if(!!result) { cacheLists.push(itemId); callback(true, result.value.content); @@ -161,74 +161,165 @@ background: no-repeat url(/img/ico/ico_eye.png) 50% top; } let collectStatus,securitycode,privacy,update=0,count=0,count1=0,flag = 0; - let itemsList1 = document.querySelectorAll('#columnSubjectHomeB ul.browserCoverMedium li'); - let itemsList2 = document.querySelectorAll('#columnSubjectHomeB ul.coversSmall li'); - let itemsList3 = document.querySelectorAll('#columnSubjectHomeB ul.browserCoverSmall li'); + + // 修正选择器以适配新 UI + let itemsList1 = document.querySelectorAll('.subject_section ul.browserCoverMedium li'); + let itemsList2 = document.querySelectorAll('.subject_section ul.coversSmall li'); + let itemsList3 = document.querySelectorAll('.subject_section ul.browserCoverSmall li'); + let itemsList = []; for(let i=0;i { - if(elem.href.match(/logout/)) securitycode = elem.href.split('/logout/')[1].toString(); + badgeUserPanel.forEach( (elem) => { + if (elem.href.match(/logout/)) { + securitycode = elem.href.split('/logout/')[1].toString(); + } }); + // 找到所有相关的单行本区域 + let targetSections = document.querySelectorAll('.subject_section'); + let mangaSection = null; + + // 查找包含单行本的区域 + targetSections.forEach(section => { + let subtitle = section.querySelector('.subtitle'); + if (subtitle && (subtitle.textContent.includes('单行本'))) { + mangaSection = section; + } + }); + + // 如果没找到,使用第一个包含列表的区域 + if (!mangaSection) { + targetSections.forEach(section => { + if (section.querySelector('ul.browserCoverMedium')) { + mangaSection = section; + } + }); + } + //更新缓存数据 const updateBtn = createElement('a','chiiBtn','javascript:;','更新'); updateBtn.addEventListener('click', updateInfo); - if(itemsList3.length) document.querySelectorAll('#columnSubjectHomeB .subject_section .clearit')[1].append(updateBtn); - else document.querySelectorAll('#columnSubjectHomeB .subject_section .clearit')[0].append(updateBtn); + updateBtn.style.marginRight = '10px'; - getInfo(update); - function updateInfo(){ - count=0; - update=1; - getInfo(update); - } + // 创建控制面板容器 + let controlPanel = document.createElement('div'); + controlPanel.style.cssText = 'margin: 10px 0; padding: 10px; background: #f8f8f8; border-radius: 4px; border: 1px solid #ddd;'; + + // 私密选项 + let privateLabel = document.createElement('label'); + privateLabel.style.cssText = 'margin-right: 15px; cursor: pointer;'; + let checkbox = document.createElement('input'); + checkbox.type = 'checkbox'; + checkbox.style.marginRight = '5px'; + privateLabel.appendChild(checkbox); + privateLabel.appendChild(document.createTextNode('私密收藏')); - let privatebox = document.createElement('a'); privatebox.textContent = '私密'; - let checkbox = document.createElement('input'); checkbox.type = 'checkbox'; - privatebox.appendChild(checkbox); - if(itemsList3.length) $(privatebox).insertAfter(document.querySelectorAll('#columnSubjectHomeB .subject_section .clearit')[0]); checkbox.onclick = function (){ - if (checkbox.checked) privacy = 1; - else privacy = 0; + privacy = checkbox.checked ? 1 : 0; }; + // 全部标为已读按钮 let allCollect = createElement('a','chiiBtn','javascript:;','全部标为已读'); + allCollect.style.marginRight = '10px'; allCollect.onclick = function (){ - if (!confirm("确定要"+allCollect.textContent+"吗?")) return; + if (!confirm(`确定要${allCollect.textContent}吗?\n\n${privacy ? "将以私密方式收藏" : "将以公开方式收藏"}`)) { + return; + } + + let targetItems = itemsList3.length ? itemsList3 : itemsList; + if (targetItems.length === 0) { + alert('没有找到可操作的条目'); + return; + } + let i = 0; flag = (flag==1)?0:1; - allCollect.textContent =(flag==1)? '全部取消已读':'全部标为已读'; - let getitemsList3= setInterval(function(){ - let elem = itemsList3[i]; - let href = elem.querySelector('a.avatar').href; + allCollect.textContent = (flag==1) ? `全部取消已读 (${targetItems.length})` : `全部标为已读 (${targetItems.length})`; + allCollect.style.backgroundColor = flag ? '#dc3545' : '#28a745'; + + let processInterval = setInterval(function(){ + if (i >= targetItems.length) { + clearInterval(processInterval); + allCollect.textContent = flag ? '全部标为已读' : '全部取消已读'; + allCollect.style.backgroundColor = ''; + alert('操作完成!'); + return; + } + + let elem = targetItems[i]; + let avatarLink = elem.querySelector('a.avatar') || elem.querySelector('a'); + if (!avatarLink) { + i++; + return; + } + + let {href} = avatarLink; let ID = href.split('/subject/')[1]; let avatarNeue = elem.querySelector('span.avatarNeue'); + if(flag){ collectStatus[ID] = 'collect'; - avatarNeue.classList.add('relate_collect'); - $.post('/subject/' + ID + '/interest/update?gh=' + securitycode, { status: 'collect',privacy:privacy}); - } - else{ + if (avatarNeue) { + avatarNeue.classList.add('relate_collect'); + } + $.post(`/subject/${ID}/interest/update?gh=${securitycode}`, { + status: 'collect', + privacy: privacy || 0 + }); + } else { delete collectStatus[ID]; - avatarNeue.classList.remove('relate_collect'); - $.post('/subject/' + ID + '/remove?gh=' + securitycode); + if (avatarNeue) { + avatarNeue.classList.remove('relate_collect'); + } + $.post(`/subject/${ID}/remove?gh=${securitycode}`); } + + // 更新按钮文本显示进度 + allCollect.textContent = `${(flag ? '标记中... ' : '取消中... ') + (i + 1)}/${targetItems.length}`; + i++; localStorage.setItem('bangumi_subject_collectStatus',JSON.stringify(collectStatus)); - if(i >= itemsList3.length){ - clearInterval(getitemsList3); - } - },300); + }, 300); }; - if(itemsList3.length) - $(allCollect).insertAfter(document.querySelectorAll('#columnSubjectHomeB .subject_section .clearit')[0]); + + // 组装控制面板 + controlPanel.appendChild(updateBtn); + controlPanel.appendChild(privateLabel); + controlPanel.appendChild(allCollect); + + // 添加说明文字 + let helpText = document.createElement('div'); + helpText.style.cssText = 'margin-top: 8px; font-size: 12px; color: #666;'; + helpText.textContent = '提示:批量操作会将当前页面所有条目标记为"已收藏"状态,请谨慎使用'; + controlPanel.appendChild(helpText); + + // 插入控制面板 + if (mangaSection) { + let subtitle = mangaSection.querySelector('.subtitle'); + if (subtitle) { + subtitle.parentNode.insertBefore(controlPanel, subtitle.nextSibling); + } else { + mangaSection.insertBefore(controlPanel, mangaSection.firstChild); + } + } + + getInfo(update); + function updateInfo(){ + count=0; + update=1; + updateBtn.textContent = '更新中...'; + updateBtn.disabled = true; + getInfo(update); + } function createElement(type,className,href,textContent){ let Element = document.createElement(type); @@ -239,6 +330,17 @@ background: no-repeat url(/img/ico/ico_eye.png) 50% top; } function showCheckIn(elem,ID){ + // 检查是否已经存在 checkIn 按钮,避免重复添加 + let avatarLink = elem.querySelector('a.avatar') || elem.querySelector('a'); + if (!avatarLink) { + return; + } + + let existingCheckIn = avatarLink.querySelector('.subCheckIn'); + if (existingCheckIn) { + return; // 如果已经存在,直接返回 + } + let checkIn = createElement('a','subCheckIn','javascript:;'); let flag = 0; let avatarNeue = elem.querySelector('span.avatarNeue'); @@ -247,26 +349,36 @@ background: no-repeat url(/img/ico/ico_eye.png) 50% top; if(flag){ checkIn.style.backgroundPosition= "bottom left"; collectStatus[ID] = 'collect'; - avatarNeue.classList.add('relate_collect'); - $.post('/subject/' + ID + '/interest/update?gh=' + securitycode, { status: 'collect',privacy:privacy}); + if (avatarNeue) { + avatarNeue.classList.add('relate_collect'); + } + $.post(`/subject/${ID}/interest/update?gh=${securitycode}`, { status: 'collect',privacy:privacy || 0}); } else{ checkIn.style.backgroundPosition= "top left"; delete collectStatus[ID]; - avatarNeue.classList.remove('relate_collect'); - $.post('/subject/' + ID + '/remove?gh=' + securitycode); + if (avatarNeue) { + avatarNeue.classList.remove('relate_collect'); + } + $.post(`/subject/${ID}/remove?gh=${securitycode}`); } localStorage.setItem('bangumi_subject_collectStatus',JSON.stringify(collectStatus)); }); - elem.querySelector('a.avatar').append(checkIn); + + avatarLink.appendChild(checkIn); } function getInfo(update){ if(itemsList.length){ let fetchList = [],fetchList1 = []; - itemsList.forEach( (elem, index) => { + itemsList.forEach( (elem) => { elem.style.height="150px"; - let href = elem.querySelector('a.avatar').href; + let avatarLink = elem.querySelector('a.avatar') || elem.querySelector('a'); + if (!avatarLink) { + return; + } + + let {href} = avatarLink; let href1 = href.replace(/subject/,"update"); let ID = href.split('/subject/')[1]; getCache(ID, function(success, result) { @@ -277,19 +389,26 @@ background: no-repeat url(/img/ico/ico_eye.png) 50% top; fetchList.push(elem); } }); - if(collectStatus[ID]!='collect') - showCheckIn(elem,ID); - if(collectStatus[ID] && !update) - displayCollect(collectStatus[ID],elem); - else fetchList1.push(elem); + if (collectStatus[ID]!='collect') { + showCheckIn(elem,ID); + } + if (collectStatus[ID] && !update) { + displayCollect(collectStatus[ID],elem); + } else { + fetchList1.push(elem); + } }); let i=0,j=0; let getitemsList= setInterval(function(){ let elem = fetchList[i]; - if(!elem) console.log(i); - else{ - let href = elem.querySelector('a.avatar').href; - showRank(href,elem); + if(!elem) { + console.log(i); + } else { + let avatarLink = elem.querySelector('a.avatar') || elem.querySelector('a'); + if (avatarLink) { + let {href} = avatarLink; + showRank(href,elem); + } i++; //console.log(i); } @@ -299,11 +418,15 @@ background: no-repeat url(/img/ico/ico_eye.png) 50% top; },500); let getitemsList1= setInterval(function(){ let elem = fetchList1[j]; - if(!elem) console.log(j); - else{ - let href = elem.querySelector('a.avatar').href; - let href1 = href.replace(/subject/,"update"); - showCollect(href1,elem); + if(!elem) { + console.log(j); + } else { + let avatarLink = elem.querySelector('a.avatar') || elem.querySelector('a'); + if (avatarLink) { + let {href} = avatarLink; + let href1 = href.replace(/subject/,"update"); + showCollect(href1,elem); + } j++; //console.log(j); } @@ -313,13 +436,19 @@ background: no-repeat url(/img/ico/ico_eye.png) 50% top; },500); } if(itemsList3.length){ - itemsList3.forEach( (elem, index) => { - let href = elem.querySelector('a').href; + itemsList3.forEach( (elem) => { + let avatarLink = elem.querySelector('a.avatar') || elem.querySelector('a'); + if (!avatarLink) { + return; + } + + let {href} = avatarLink; let ID = href.split('/subject/')[1]; - if(collectStatus[ID]) + if (collectStatus[ID]) { displayCollect(collectStatus[ID],elem); - else if(collectStatus[ID]!='collect') + } else if (collectStatus[ID]!='collect') { showCheckIn(elem,ID); + } }); } @@ -335,8 +464,11 @@ background: no-repeat url(/img/ico/ico_eye.png) 50% top; let Match = targetStr.match(/"GenInterestBox\('(\S+?)'\)" checked="checked"/); let interest = Match ? Match[1] : null; let ID = thisItem.split('/update/')[1]; - if(interest) collectStatus[ID] = interest; - else if(collectStatus[ID]) delete collectStatus[ID]; + if (interest) { + collectStatus[ID] = interest; + } else if (collectStatus[ID]) { + delete collectStatus[ID]; + } localStorage.setItem('bangumi_subject_collectStatus',JSON.stringify(collectStatus)); }); } @@ -357,17 +489,32 @@ background: no-repeat url(/img/ico/ico_eye.png) 50% top; collectStatus[ID] = 'collect'; localStorage.setItem('bangumi_subject_collectStatus',JSON.stringify(collectStatus)); } - if(!update) displayCollect(interest,elem); + if (!update) { + displayCollect(interest,elem); + } }); } function displayCollect(interest,elem){ let avatarNeue = elem.querySelector('span.avatarNeue'); - if(interest=='wish') avatarNeue.classList.add('relate_wish'); - else if(interest=='collect') avatarNeue.classList.add('relate_collect'); - else if(interest=='do') avatarNeue.classList.add('relate_do'); - else if(interest=='on_hold') avatarNeue.classList.add('relate_on_hold'); - else if(interest=='dropped') avatarNeue.classList.add('relate_dropped'); + if (!avatarNeue) { + return; + } + + // 先清除所有收藏状态的 CSS 类,避免重复添加 + avatarNeue.classList.remove('relate_wish', 'relate_collect', 'relate_do', 'relate_on_hold', 'relate_dropped'); + + if (interest=='wish') { + avatarNeue.classList.add('relate_wish'); + } else if (interest=='collect') { + avatarNeue.classList.add('relate_collect'); + } else if (interest=='do') { + avatarNeue.classList.add('relate_do'); + } else if (interest=='on_hold') { + avatarNeue.classList.add('relate_on_hold'); + } else if (interest=='dropped') { + avatarNeue.classList.add('relate_dropped'); + } count1++; } @@ -380,13 +527,15 @@ background: no-repeat url(/img/ico/ico_eye.png) 50% top; xhr.onload = function(){ let d = xhr.responseXML; let nameinfo = d.querySelector('#infobox li'); - let name_cn = nameinfo.innerText.match(/中文名: (\.*)/)?nameinfo.innerText.match(/中文名: (\.*)/)[1]:null; + let name_cn = nameinfo && nameinfo.innerText.match(/中文名: (\.*)/)?nameinfo.innerText.match(/中文名: (\.*)/)[1]:null; //获取排名 let ranksp = d.querySelector('#panelInterestWrapper .global_score small.alarm'); let rank = ranksp ? ranksp.innerText.match(/\d+/)[0]:null; //获取站内评分和评分人数 - let score = d.querySelector('#panelInterestWrapper .global_score span.number').innerText; - let votes = d.querySelector('#ChartWarpper small.grey span').innerText; + let scoreElement = d.querySelector('#panelInterestWrapper .global_score span.number'); + let score = scoreElement ? scoreElement.innerText : null; + let votesElement = d.querySelector('#ChartWarpper small.grey span'); + let votes = votesElement ? votesElement.innerText : null; //获取好友评分和评分人数 let frdScore = d.querySelector('#panelInterestWrapper .frdScore'); let score_f = frdScore ? frdScore.querySelector('span.num').innerText:null; @@ -395,25 +544,51 @@ background: no-repeat url(/img/ico/ico_eye.png) 50% top; let info = {"name_cn":name_cn,"rank":rank,"score":score,"votes":votes,"score_f":score_f,"votes_f":votes_f,"score_u":score_u}; let ID = href.split('/subject/')[1]; setCache(ID,info); - if(!update) displayRank(rank,elem); - else{ + if (!update) { + displayRank(rank,elem); + } else { count+=1; - updateBtn.textContent='更新中... (' + count + '/' + itemsList.length +')'; - if(count==itemsList.length){ updateBtn.textContent='更新完毕!';} + updateBtn.textContent=`更新中... (${count}/${itemsList.length})`; + if (count==itemsList.length) { + updateBtn.textContent='更新完毕!'; + updateBtn.disabled = false; + setTimeout(() => { + updateBtn.textContent = '更新'; + }, 2000); + } } }; } function displayRank(rank,elem){ + // 检查是否已经存在排名标签,避免重复添加 + let existingRank = elem.querySelector('.rank'); + if (existingRank) { + // 如果已经存在,更新内容而不是添加新的 + if (rank) { + existingRank.className = 'rank'; + if (rank<=1500) { + existingRank.classList.add('relate_rank_1'); + } else { + existingRank.classList.add('relate_rank'); + } + existingRank.innerHTML = `Rank ${rank}`; + } + count++; + return; + } + let rankSp = createElement('span','rank'); if (rank) { - if(rank<=1500) rankSp.classList.add('relate_rank_1'); - else rankSp.classList.add('relate_rank'); + if (rank<=1500) { + rankSp.classList.add('relate_rank_1'); + } else { + rankSp.classList.add('relate_rank'); + } rankSp.innerHTML = `Rank ${rank}`; - elem.append(rankSp); + elem.appendChild(rankSp); } count++; } })(); - From 48dcaa36b5e3f924475fccac765e01c2fef67b7e Mon Sep 17 00:00:00 2001 From: NekoAria Date: Sat, 9 Aug 2025 19:08:54 +0800 Subject: [PATCH 02/14] style(format): apply code formatting and standardize style --- .../bangumi_related_subject_enhance.user.js | 1039 +++++++++-------- 1 file changed, 560 insertions(+), 479 deletions(-) diff --git a/liaune/bangumi_related_subject_enhance.user.js b/liaune/bangumi_related_subject_enhance.user.js index 8b63d003..96f1aa4a 100644 --- a/liaune/bangumi_related_subject_enhance.user.js +++ b/liaune/bangumi_related_subject_enhance.user.js @@ -7,8 +7,8 @@ // @include /^https?:\/\/((bangumi|bgm)\.tv|chii.in)\/subject\/\d+$/ // @grant GM_addStyle // ==/UserScript== -(function() { - GM_addStyle(` +(function () { + GM_addStyle(` .relate_rank{ padding: 2px 5px 1px 5px; background: #b4b020; @@ -75,520 +75,601 @@ background: no-repeat url(/img/ico/ico_eye.png) 50% top; } `); - // 检测 indexedDB 兼容性,因为只有新版本浏览器支持 - let indexedDB = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB; - // 初始化 indexedDB - const dbName = 'Bangumi_Subject_Info'; - const tableName = 'info'; - const indexName = 'id'; - if (indexedDB) { - let request = indexedDB.open(dbName, 1); - request.onupgradeneeded = evt => { - let db = evt.target.result; - let objectStore = db.createObjectStore(tableName, {keyPath: indexName}); - } - request.onsuccess = evt => { - removeCache(); + // 检测 indexedDB 兼容性,因为只有新版本浏览器支持 + let indexedDB = + window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB; + // 初始化 indexedDB + const dbName = "Bangumi_Subject_Info"; + const tableName = "info"; + const indexName = "id"; + if (indexedDB) { + let request = indexedDB.open(dbName, 1); + request.onupgradeneeded = (evt) => { + let db = evt.target.result; + let objectStore = db.createObjectStore(tableName, { keyPath: indexName }); + }; + request.onsuccess = (evt) => { + removeCache(); + }; + } + // 用来记录已经被使用的缓存列表 + let cacheLists = []; + // 获取本地缓存 + function getCache(itemId, callback) { + let request = indexedDB.open(dbName, 1); + request.onsuccess = (evt) => { + let db = evt.target.result; + let transaction = db.transaction([tableName], "readonly"); + let objectStore = transaction.objectStore(tableName); + let reqInfo = objectStore.get(itemId); + reqInfo.onsuccess = (evt) => { + let { result } = evt.target; + if (!!result) { + cacheLists.push(itemId); + callback(true, result.value.content); + } else { + callback(false); } - } - // 用来记录已经被使用的缓存列表 - let cacheLists = []; - // 获取本地缓存 - function getCache(itemId, callback) { - let request = indexedDB.open(dbName, 1); - request.onsuccess = evt => { - let db = evt.target.result; - let transaction = db.transaction([tableName], 'readonly'); - let objectStore = transaction.objectStore(tableName); - let reqInfo = objectStore.get(itemId); - reqInfo.onsuccess = evt => { - let {result} = evt.target; - if(!!result) { - cacheLists.push(itemId); - callback(true, result.value.content); - } else { - callback(false); - } - } - reqInfo.onerror = evt => { - callback(false); - } - }; - } - // 记录到本地缓存 - function setCache(itemId, data) { - let request = indexedDB.open(dbName, 1); - request.onsuccess = evt => { - let db = evt.target.result; - let transaction = db.transaction([tableName], 'readwrite'); - let objectStore = transaction.objectStore(tableName); - let cache = { - content: data, - created: new Date() - }; - let reqInfo = objectStore.put({id: itemId, value: cache}) - reqInfo.onerror = evt => { - // //console.log('Error', evt.target.error.name); - } - reqInfo.onsuccess = evt => {} - }; - } - // 清除和更新缓存 - function removeCache() { - let request = indexedDB.open(dbName, 1); - request.onsuccess = evt => { - let db = evt.target.result; - let transaction = db.transaction([tableName], 'readwrite'), - store = transaction.objectStore(tableName), - twoWeek = 1209600000; - store.openCursor().onsuccess = evt => { - let cursor = evt.target.result; - if (cursor) { - if (cacheLists.indexOf(cursor.value.name) !== -1) { - cursor.value.created = new Date(); - cursor.update(cursor.value); - } else { - let now = new Date(), - last = cursor.value.created; - if (now - last > twoWeek) { - cursor.delete(); - } - } - cursor.continue(); - } + }; + reqInfo.onerror = (evt) => { + callback(false); + }; + }; + } + // 记录到本地缓存 + function setCache(itemId, data) { + let request = indexedDB.open(dbName, 1); + request.onsuccess = (evt) => { + let db = evt.target.result; + let transaction = db.transaction([tableName], "readwrite"); + let objectStore = transaction.objectStore(tableName); + let cache = { + content: data, + created: new Date(), + }; + let reqInfo = objectStore.put({ id: itemId, value: cache }); + reqInfo.onerror = (evt) => { + // //console.log('Error', evt.target.error.name); + }; + reqInfo.onsuccess = (evt) => {}; + }; + } + // 清除和更新缓存 + function removeCache() { + let request = indexedDB.open(dbName, 1); + request.onsuccess = (evt) => { + let db = evt.target.result; + let transaction = db.transaction([tableName], "readwrite"), + store = transaction.objectStore(tableName), + twoWeek = 1209600000; + store.openCursor().onsuccess = (evt) => { + let cursor = evt.target.result; + if (cursor) { + if (cacheLists.indexOf(cursor.value.name) !== -1) { + cursor.value.created = new Date(); + cursor.update(cursor.value); + } else { + let now = new Date(), + last = cursor.value.created; + if (now - last > twoWeek) { + cursor.delete(); } - }; + } + cursor.continue(); + } + }; + }; + } + + let collectStatus, + securitycode, + privacy, + update = 0, + count = 0, + count1 = 0, + flag = 0; + + // 修正选择器以适配新 UI + let itemsList1 = document.querySelectorAll( + ".subject_section ul.browserCoverMedium li" + ); + let itemsList2 = document.querySelectorAll( + ".subject_section ul.coversSmall li" + ); + let itemsList3 = document.querySelectorAll( + ".subject_section ul.browserCoverSmall li" + ); + + let itemsList = []; + for (let i = 0; i < itemsList1.length; i++) itemsList.push(itemsList1[i]); + for (let i = 0; i < itemsList2.length; i++) itemsList.push(itemsList2[i]); + + if (localStorage.getItem("bangumi_subject_collectStatus")) { + collectStatus = JSON.parse( + localStorage.getItem("bangumi_subject_collectStatus") + ); + } else { + collectStatus = {}; + } + + let badgeUserPanel = document.querySelectorAll("#badgeUserPanel a"); + badgeUserPanel.forEach((elem) => { + if (elem.href.match(/logout/)) { + securitycode = elem.href.split("/logout/")[1].toString(); } + }); - let collectStatus,securitycode,privacy,update=0,count=0,count1=0,flag = 0; + // 找到所有相关的单行本区域 + let targetSections = document.querySelectorAll(".subject_section"); + let mangaSection = null; - // 修正选择器以适配新 UI - let itemsList1 = document.querySelectorAll('.subject_section ul.browserCoverMedium li'); - let itemsList2 = document.querySelectorAll('.subject_section ul.coversSmall li'); - let itemsList3 = document.querySelectorAll('.subject_section ul.browserCoverSmall li'); - - let itemsList = []; - for(let i=0;i { + let subtitle = section.querySelector(".subtitle"); + if (subtitle && subtitle.textContent.includes("单行本")) { + mangaSection = section; } - - let badgeUserPanel=document.querySelectorAll('#badgeUserPanel a'); - badgeUserPanel.forEach( (elem) => { - if (elem.href.match(/logout/)) { - securitycode = elem.href.split('/logout/')[1].toString(); - } + }); + + // 如果没找到,使用第一个包含列表的区域 + if (!mangaSection) { + targetSections.forEach((section) => { + if (section.querySelector("ul.browserCoverMedium")) { + mangaSection = section; + } }); + } + + //更新缓存数据 + const updateBtn = createElement("a", "chiiBtn", "javascript:;", "更新"); + updateBtn.addEventListener("click", updateInfo); + updateBtn.style.marginRight = "10px"; + + // 创建控制面板容器 + let controlPanel = document.createElement("div"); + controlPanel.style.cssText = + "margin: 10px 0; padding: 10px; background: #f8f8f8; border-radius: 4px; border: 1px solid #ddd;"; + + // 私密选项 + let privateLabel = document.createElement("label"); + privateLabel.style.cssText = "margin-right: 15px; cursor: pointer;"; + let checkbox = document.createElement("input"); + checkbox.type = "checkbox"; + checkbox.style.marginRight = "5px"; + privateLabel.appendChild(checkbox); + privateLabel.appendChild(document.createTextNode("私密收藏")); + + checkbox.onclick = function () { + privacy = checkbox.checked ? 1 : 0; + }; + + // 全部标为已读按钮 + let allCollect = createElement( + "a", + "chiiBtn", + "javascript:;", + "全部标为已读" + ); + allCollect.style.marginRight = "10px"; + allCollect.onclick = function () { + if ( + !confirm( + `确定要${allCollect.textContent}吗?\n\n${ + privacy ? "将以私密方式收藏" : "将以公开方式收藏" + }` + ) + ) { + return; + } - // 找到所有相关的单行本区域 - let targetSections = document.querySelectorAll('.subject_section'); - let mangaSection = null; + let targetItems = itemsList3.length ? itemsList3 : itemsList; + if (targetItems.length === 0) { + alert("没有找到可操作的条目"); + return; + } - // 查找包含单行本的区域 - targetSections.forEach(section => { - let subtitle = section.querySelector('.subtitle'); - if (subtitle && (subtitle.textContent.includes('单行本'))) { - mangaSection = section; + let i = 0; + flag = flag == 1 ? 0 : 1; + allCollect.textContent = + flag == 1 + ? `全部取消已读 (${targetItems.length})` + : `全部标为已读 (${targetItems.length})`; + allCollect.style.backgroundColor = flag ? "#dc3545" : "#28a745"; + + let processInterval = setInterval(function () { + if (i >= targetItems.length) { + clearInterval(processInterval); + allCollect.textContent = flag ? "全部标为已读" : "全部取消已读"; + allCollect.style.backgroundColor = ""; + alert("操作完成!"); + return; + } + + let elem = targetItems[i]; + let avatarLink = + elem.querySelector("a.avatar") || elem.querySelector("a"); + if (!avatarLink) { + i++; + return; + } + + let { href } = avatarLink; + let ID = href.split("/subject/")[1]; + let avatarNeue = elem.querySelector("span.avatarNeue"); + + if (flag) { + collectStatus[ID] = "collect"; + if (avatarNeue) { + avatarNeue.classList.add("relate_collect"); } - }); - - // 如果没找到,使用第一个包含列表的区域 - if (!mangaSection) { - targetSections.forEach(section => { - if (section.querySelector('ul.browserCoverMedium')) { - mangaSection = section; - } + $.post(`/subject/${ID}/interest/update?gh=${securitycode}`, { + status: "collect", + privacy: privacy || 0, }); + } else { + delete collectStatus[ID]; + if (avatarNeue) { + avatarNeue.classList.remove("relate_collect"); + } + $.post(`/subject/${ID}/remove?gh=${securitycode}`); + } + + // 更新按钮文本显示进度 + allCollect.textContent = `${ + (flag ? "标记中... " : "取消中... ") + (i + 1) + }/${targetItems.length}`; + + i++; + localStorage.setItem( + "bangumi_subject_collectStatus", + JSON.stringify(collectStatus) + ); + }, 300); + }; + + // 组装控制面板 + controlPanel.appendChild(updateBtn); + controlPanel.appendChild(privateLabel); + controlPanel.appendChild(allCollect); + + // 添加说明文字 + let helpText = document.createElement("div"); + helpText.style.cssText = "margin-top: 8px; font-size: 12px; color: #666;"; + helpText.textContent = + '提示:批量操作会将当前页面所有条目标记为"已收藏"状态,请谨慎使用'; + controlPanel.appendChild(helpText); + + // 插入控制面板 + if (mangaSection) { + let subtitle = mangaSection.querySelector(".subtitle"); + if (subtitle) { + subtitle.parentNode.insertBefore(controlPanel, subtitle.nextSibling); + } else { + mangaSection.insertBefore(controlPanel, mangaSection.firstChild); + } + } + + getInfo(update); + function updateInfo() { + count = 0; + update = 1; + updateBtn.textContent = "更新中..."; + updateBtn.disabled = true; + getInfo(update); + } + + function createElement(type, className, href, textContent) { + let Element = document.createElement(type); + Element.className = className; + Element.href = href; + Element.textContent = textContent; + return Element; + } + + function showCheckIn(elem, ID) { + // 检查是否已经存在 checkIn 按钮,避免重复添加 + let avatarLink = elem.querySelector("a.avatar") || elem.querySelector("a"); + if (!avatarLink) { + return; } - //更新缓存数据 - const updateBtn = createElement('a','chiiBtn','javascript:;','更新'); - updateBtn.addEventListener('click', updateInfo); - updateBtn.style.marginRight = '10px'; - - // 创建控制面板容器 - let controlPanel = document.createElement('div'); - controlPanel.style.cssText = 'margin: 10px 0; padding: 10px; background: #f8f8f8; border-radius: 4px; border: 1px solid #ddd;'; - - // 私密选项 - let privateLabel = document.createElement('label'); - privateLabel.style.cssText = 'margin-right: 15px; cursor: pointer;'; - let checkbox = document.createElement('input'); - checkbox.type = 'checkbox'; - checkbox.style.marginRight = '5px'; - privateLabel.appendChild(checkbox); - privateLabel.appendChild(document.createTextNode('私密收藏')); - - checkbox.onclick = function (){ - privacy = checkbox.checked ? 1 : 0; - }; + let existingCheckIn = avatarLink.querySelector(".subCheckIn"); + if (existingCheckIn) { + return; // 如果已经存在,直接返回 + } - // 全部标为已读按钮 - let allCollect = createElement('a','chiiBtn','javascript:;','全部标为已读'); - allCollect.style.marginRight = '10px'; - allCollect.onclick = function (){ - if (!confirm(`确定要${allCollect.textContent}吗?\n\n${privacy ? "将以私密方式收藏" : "将以公开方式收藏"}`)) { - return; + let checkIn = createElement("a", "subCheckIn", "javascript:;"); + let flag = 0; + let avatarNeue = elem.querySelector("span.avatarNeue"); + checkIn.addEventListener("click", function () { + flag = flag == 1 ? 0 : 1; + if (flag) { + checkIn.style.backgroundPosition = "bottom left"; + collectStatus[ID] = "collect"; + if (avatarNeue) { + avatarNeue.classList.add("relate_collect"); } - - let targetItems = itemsList3.length ? itemsList3 : itemsList; - if (targetItems.length === 0) { - alert('没有找到可操作的条目'); - return; + $.post(`/subject/${ID}/interest/update?gh=${securitycode}`, { + status: "collect", + privacy: privacy || 0, + }); + } else { + checkIn.style.backgroundPosition = "top left"; + delete collectStatus[ID]; + if (avatarNeue) { + avatarNeue.classList.remove("relate_collect"); } + $.post(`/subject/${ID}/remove?gh=${securitycode}`); + } + localStorage.setItem( + "bangumi_subject_collectStatus", + JSON.stringify(collectStatus) + ); + }); - let i = 0; - flag = (flag==1)?0:1; - allCollect.textContent = (flag==1) ? `全部取消已读 (${targetItems.length})` : `全部标为已读 (${targetItems.length})`; - allCollect.style.backgroundColor = flag ? '#dc3545' : '#28a745'; - - let processInterval = setInterval(function(){ - if (i >= targetItems.length) { - clearInterval(processInterval); - allCollect.textContent = flag ? '全部标为已读' : '全部取消已读'; - allCollect.style.backgroundColor = ''; - alert('操作完成!'); - return; - } - - let elem = targetItems[i]; - let avatarLink = elem.querySelector('a.avatar') || elem.querySelector('a'); - if (!avatarLink) { - i++; - return; - } - - let {href} = avatarLink; - let ID = href.split('/subject/')[1]; - let avatarNeue = elem.querySelector('span.avatarNeue'); - - if(flag){ - collectStatus[ID] = 'collect'; - if (avatarNeue) { - avatarNeue.classList.add('relate_collect'); - } - $.post(`/subject/${ID}/interest/update?gh=${securitycode}`, { - status: 'collect', - privacy: privacy || 0 - }); - } else { - delete collectStatus[ID]; - if (avatarNeue) { - avatarNeue.classList.remove('relate_collect'); - } - $.post(`/subject/${ID}/remove?gh=${securitycode}`); - } - - // 更新按钮文本显示进度 - allCollect.textContent = `${(flag ? '标记中... ' : '取消中... ') + (i + 1)}/${targetItems.length}`; - - i++; - localStorage.setItem('bangumi_subject_collectStatus',JSON.stringify(collectStatus)); - }, 300); - }; + avatarLink.appendChild(checkIn); + } + + function getInfo(update) { + if (itemsList.length) { + let fetchList = [], + fetchList1 = []; + itemsList.forEach((elem) => { + elem.style.height = "150px"; + let avatarLink = + elem.querySelector("a.avatar") || elem.querySelector("a"); + if (!avatarLink) { + return; + } - // 组装控制面板 - controlPanel.appendChild(updateBtn); - controlPanel.appendChild(privateLabel); - controlPanel.appendChild(allCollect); - - // 添加说明文字 - let helpText = document.createElement('div'); - helpText.style.cssText = 'margin-top: 8px; font-size: 12px; color: #666;'; - helpText.textContent = '提示:批量操作会将当前页面所有条目标记为"已收藏"状态,请谨慎使用'; - controlPanel.appendChild(helpText); - - // 插入控制面板 - if (mangaSection) { - let subtitle = mangaSection.querySelector('.subtitle'); - if (subtitle) { - subtitle.parentNode.insertBefore(controlPanel, subtitle.nextSibling); + let { href } = avatarLink; + let href1 = href.replace(/subject/, "update"); + let ID = href.split("/subject/")[1]; + getCache(ID, function (success, result) { + if (success && !update) { + displayRank(result.rank, elem); + } else { + fetchList.push(elem); + } + }); + if (collectStatus[ID] != "collect") { + showCheckIn(elem, ID); + } + if (collectStatus[ID] && !update) { + displayCollect(collectStatus[ID], elem); } else { - mangaSection.insertBefore(controlPanel, mangaSection.firstChild); + fetchList1.push(elem); } + }); + let i = 0, + j = 0; + let getitemsList = setInterval(function () { + let elem = fetchList[i]; + if (!elem) { + console.log(i); + } else { + let avatarLink = + elem.querySelector("a.avatar") || elem.querySelector("a"); + if (avatarLink) { + let { href } = avatarLink; + showRank(href, elem); + } + i++; + //console.log(i); + } + if (count >= itemsList.length) { + clearInterval(getitemsList); + } + }, 500); + let getitemsList1 = setInterval(function () { + let elem = fetchList1[j]; + if (!elem) { + console.log(j); + } else { + let avatarLink = + elem.querySelector("a.avatar") || elem.querySelector("a"); + if (avatarLink) { + let { href } = avatarLink; + let href1 = href.replace(/subject/, "update"); + showCollect(href1, elem); + } + j++; + //console.log(j); + } + if (count1 >= itemsList.length) { + clearInterval(getitemsList1); + } + }, 500); } - - getInfo(update); - function updateInfo(){ - count=0; - update=1; - updateBtn.textContent = '更新中...'; - updateBtn.disabled = true; - getInfo(update); - } - - function createElement(type,className,href,textContent){ - let Element = document.createElement(type); - Element.className = className; - Element.href = href; - Element.textContent = textContent; - return Element; - } - - function showCheckIn(elem,ID){ - // 检查是否已经存在 checkIn 按钮,避免重复添加 - let avatarLink = elem.querySelector('a.avatar') || elem.querySelector('a'); + if (itemsList3.length) { + itemsList3.forEach((elem) => { + let avatarLink = + elem.querySelector("a.avatar") || elem.querySelector("a"); if (!avatarLink) { return; } - let existingCheckIn = avatarLink.querySelector('.subCheckIn'); - if (existingCheckIn) { - return; // 如果已经存在,直接返回 + let { href } = avatarLink; + let ID = href.split("/subject/")[1]; + if (collectStatus[ID]) { + displayCollect(collectStatus[ID], elem); + } else if (collectStatus[ID] != "collect") { + showCheckIn(elem, ID); } - - let checkIn = createElement('a','subCheckIn','javascript:;'); - let flag = 0; - let avatarNeue = elem.querySelector('span.avatarNeue'); - checkIn.addEventListener('click', function(){ - flag = (flag==1)?0:1; - if(flag){ - checkIn.style.backgroundPosition= "bottom left"; - collectStatus[ID] = 'collect'; - if (avatarNeue) { - avatarNeue.classList.add('relate_collect'); - } - $.post(`/subject/${ID}/interest/update?gh=${securitycode}`, { status: 'collect',privacy:privacy || 0}); - } - else{ - checkIn.style.backgroundPosition= "top left"; - delete collectStatus[ID]; - if (avatarNeue) { - avatarNeue.classList.remove('relate_collect'); - } - $.post(`/subject/${ID}/remove?gh=${securitycode}`); - } - localStorage.setItem('bangumi_subject_collectStatus',JSON.stringify(collectStatus)); - }); - - avatarLink.appendChild(checkIn); + }); } - function getInfo(update){ - if(itemsList.length){ - let fetchList = [],fetchList1 = []; - itemsList.forEach( (elem) => { - elem.style.height="150px"; - let avatarLink = elem.querySelector('a.avatar') || elem.querySelector('a'); - if (!avatarLink) { - return; - } - - let {href} = avatarLink; - let href1 = href.replace(/subject/,"update"); - let ID = href.split('/subject/')[1]; - getCache(ID, function(success, result) { - if (success && !update) { - displayRank(result.rank,elem); - } - else{ - fetchList.push(elem); - } - }); - if (collectStatus[ID]!='collect') { - showCheckIn(elem,ID); - } - if (collectStatus[ID] && !update) { - displayCollect(collectStatus[ID],elem); - } else { - fetchList1.push(elem); - } - }); - let i=0,j=0; - let getitemsList= setInterval(function(){ - let elem = fetchList[i]; - if(!elem) { - console.log(i); - } else { - let avatarLink = elem.querySelector('a.avatar') || elem.querySelector('a'); - if (avatarLink) { - let {href} = avatarLink; - showRank(href,elem); - } - i++; - //console.log(i); - } - if(count >= itemsList.length){ - clearInterval(getitemsList); - } - },500); - let getitemsList1= setInterval(function(){ - let elem = fetchList1[j]; - if(!elem) { - console.log(j); - } else { - let avatarLink = elem.querySelector('a.avatar') || elem.querySelector('a'); - if (avatarLink) { - let {href} = avatarLink; - let href1 = href.replace(/subject/,"update"); - showCollect(href1,elem); - } - j++; - //console.log(j); - } - if(count1 >= itemsList.length){ - clearInterval(getitemsList1); - } - },500); - } - if(itemsList3.length){ - itemsList3.forEach( (elem) => { - let avatarLink = elem.querySelector('a.avatar') || elem.querySelector('a'); - if (!avatarLink) { - return; - } - - let {href} = avatarLink; - let ID = href.split('/subject/')[1]; - if (collectStatus[ID]) { - displayCollect(collectStatus[ID],elem); - } else if (collectStatus[ID]!='collect') { - showCheckIn(elem,ID); - } - }); - } - - let thisItem = window.location.href.replace(/subject/,"update"); - fetch(thisItem,{credentials: "include"}) - .then(data => { - return new Promise(function (resovle, reject) { - let targetStr = data.text(); - resovle(targetStr); - }); - }) - .then(targetStr => { - let Match = targetStr.match(/"GenInterestBox\('(\S+?)'\)" checked="checked"/); - let interest = Match ? Match[1] : null; - let ID = thisItem.split('/update/')[1]; - if (interest) { - collectStatus[ID] = interest; - } else if (collectStatus[ID]) { - delete collectStatus[ID]; - } - localStorage.setItem('bangumi_subject_collectStatus',JSON.stringify(collectStatus)); + let thisItem = window.location.href.replace(/subject/, "update"); + fetch(thisItem, { credentials: "include" }) + .then((data) => { + return new Promise(function (resovle, reject) { + let targetStr = data.text(); + resovle(targetStr); }); - } - - function showCollect(href,elem){ - fetch(href,{credentials: "include"}) - .then(data => { - return new Promise(function (resovle, reject) { - let targetStr = data.text(); - resovle(targetStr); - }); - }) - .then(targetStr => { - let Match = targetStr.match(/"GenInterestBox\('(\S+?)'\)" checked="checked"/); - let interest = Match ? Match[1] : null; - let ID = href.split('/update/')[1]; - if(Match){ - collectStatus[ID] = 'collect'; - localStorage.setItem('bangumi_subject_collectStatus',JSON.stringify(collectStatus)); - } - if (!update) { - displayCollect(interest,elem); - } + }) + .then((targetStr) => { + let Match = targetStr.match( + /"GenInterestBox\('(\S+?)'\)" checked="checked"/ + ); + let interest = Match ? Match[1] : null; + let ID = thisItem.split("/update/")[1]; + if (interest) { + collectStatus[ID] = interest; + } else if (collectStatus[ID]) { + delete collectStatus[ID]; + } + localStorage.setItem( + "bangumi_subject_collectStatus", + JSON.stringify(collectStatus) + ); + }); + } + + function showCollect(href, elem) { + fetch(href, { credentials: "include" }) + .then((data) => { + return new Promise(function (resovle, reject) { + let targetStr = data.text(); + resovle(targetStr); }); - } - - function displayCollect(interest,elem){ - let avatarNeue = elem.querySelector('span.avatarNeue'); - if (!avatarNeue) { - return; + }) + .then((targetStr) => { + let Match = targetStr.match( + /"GenInterestBox\('(\S+?)'\)" checked="checked"/ + ); + let interest = Match ? Match[1] : null; + let ID = href.split("/update/")[1]; + if (Match) { + collectStatus[ID] = "collect"; + localStorage.setItem( + "bangumi_subject_collectStatus", + JSON.stringify(collectStatus) + ); } - - // 先清除所有收藏状态的 CSS 类,避免重复添加 - avatarNeue.classList.remove('relate_wish', 'relate_collect', 'relate_do', 'relate_on_hold', 'relate_dropped'); - - if (interest=='wish') { - avatarNeue.classList.add('relate_wish'); - } else if (interest=='collect') { - avatarNeue.classList.add('relate_collect'); - } else if (interest=='do') { - avatarNeue.classList.add('relate_do'); - } else if (interest=='on_hold') { - avatarNeue.classList.add('relate_on_hold'); - } else if (interest=='dropped') { - avatarNeue.classList.add('relate_dropped'); + if (!update) { + displayCollect(interest, elem); } - count1++; - } + }); + } - function showRank(href,elem){ - let xhr = new XMLHttpRequest(); - xhr.open( "GET", href ); - xhr.withCredentials = true; - xhr.responseType = "document"; - xhr.send(); - xhr.onload = function(){ - let d = xhr.responseXML; - let nameinfo = d.querySelector('#infobox li'); - let name_cn = nameinfo && nameinfo.innerText.match(/中文名: (\.*)/)?nameinfo.innerText.match(/中文名: (\.*)/)[1]:null; - //获取排名 - let ranksp = d.querySelector('#panelInterestWrapper .global_score small.alarm'); - let rank = ranksp ? ranksp.innerText.match(/\d+/)[0]:null; - //获取站内评分和评分人数 - let scoreElement = d.querySelector('#panelInterestWrapper .global_score span.number'); - let score = scoreElement ? scoreElement.innerText : null; - let votesElement = d.querySelector('#ChartWarpper small.grey span'); - let votes = votesElement ? votesElement.innerText : null; - //获取好友评分和评分人数 - let frdScore = d.querySelector('#panelInterestWrapper .frdScore'); - let score_f = frdScore ? frdScore.querySelector('span.num').innerText:null; - let votes_f = frdScore ? frdScore.querySelector('a.l').innerText.match(/\d+/)[0]:null; - let score_u=0; - let info = {"name_cn":name_cn,"rank":rank,"score":score,"votes":votes,"score_f":score_f,"votes_f":votes_f,"score_u":score_u}; - let ID = href.split('/subject/')[1]; - setCache(ID,info); - if (!update) { - displayRank(rank,elem); - } else { - count+=1; - updateBtn.textContent=`更新中... (${count}/${itemsList.length})`; - if (count==itemsList.length) { - updateBtn.textContent='更新完毕!'; - updateBtn.disabled = false; - setTimeout(() => { - updateBtn.textContent = '更新'; - }, 2000); - } - } - }; + function displayCollect(interest, elem) { + let avatarNeue = elem.querySelector("span.avatarNeue"); + if (!avatarNeue) { + return; } - function displayRank(rank,elem){ - // 检查是否已经存在排名标签,避免重复添加 - let existingRank = elem.querySelector('.rank'); - if (existingRank) { - // 如果已经存在,更新内容而不是添加新的 - if (rank) { - existingRank.className = 'rank'; - if (rank<=1500) { - existingRank.classList.add('relate_rank_1'); - } else { - existingRank.classList.add('relate_rank'); - } - existingRank.innerHTML = `Rank ${rank}`; - } - count++; - return; + // 先清除所有收藏状态的 CSS 类,避免重复添加 + avatarNeue.classList.remove( + "relate_wish", + "relate_collect", + "relate_do", + "relate_on_hold", + "relate_dropped" + ); + + if (interest == "wish") { + avatarNeue.classList.add("relate_wish"); + } else if (interest == "collect") { + avatarNeue.classList.add("relate_collect"); + } else if (interest == "do") { + avatarNeue.classList.add("relate_do"); + } else if (interest == "on_hold") { + avatarNeue.classList.add("relate_on_hold"); + } else if (interest == "dropped") { + avatarNeue.classList.add("relate_dropped"); + } + count1++; + } + + function showRank(href, elem) { + let xhr = new XMLHttpRequest(); + xhr.open("GET", href); + xhr.withCredentials = true; + xhr.responseType = "document"; + xhr.send(); + xhr.onload = function () { + let d = xhr.responseXML; + let nameinfo = d.querySelector("#infobox li"); + let name_cn = + nameinfo && nameinfo.innerText.match(/中文名: (\.*)/) + ? nameinfo.innerText.match(/中文名: (\.*)/)[1] + : null; + //获取排名 + let ranksp = d.querySelector( + "#panelInterestWrapper .global_score small.alarm" + ); + let rank = ranksp ? ranksp.innerText.match(/\d+/)[0] : null; + //获取站内评分和评分人数 + let scoreElement = d.querySelector( + "#panelInterestWrapper .global_score span.number" + ); + let score = scoreElement ? scoreElement.innerText : null; + let votesElement = d.querySelector("#ChartWarpper small.grey span"); + let votes = votesElement ? votesElement.innerText : null; + //获取好友评分和评分人数 + let frdScore = d.querySelector("#panelInterestWrapper .frdScore"); + let score_f = frdScore + ? frdScore.querySelector("span.num").innerText + : null; + let votes_f = frdScore + ? frdScore.querySelector("a.l").innerText.match(/\d+/)[0] + : null; + let score_u = 0; + let info = { + name_cn: name_cn, + rank: rank, + score: score, + votes: votes, + score_f: score_f, + votes_f: votes_f, + score_u: score_u, + }; + let ID = href.split("/subject/")[1]; + setCache(ID, info); + if (!update) { + displayRank(rank, elem); + } else { + count += 1; + updateBtn.textContent = `更新中... (${count}/${itemsList.length})`; + if (count == itemsList.length) { + updateBtn.textContent = "更新完毕!"; + updateBtn.disabled = false; + setTimeout(() => { + updateBtn.textContent = "更新"; + }, 2000); } - - let rankSp = createElement('span','rank'); - if (rank) { - if (rank<=1500) { - rankSp.classList.add('relate_rank_1'); - } else { - rankSp.classList.add('relate_rank'); - } - rankSp.innerHTML = `Rank ${rank}`; - elem.appendChild(rankSp); + } + }; + } + + function displayRank(rank, elem) { + // 检查是否已经存在排名标签,避免重复添加 + let existingRank = elem.querySelector(".rank"); + if (existingRank) { + // 如果已经存在,更新内容而不是添加新的 + if (rank) { + existingRank.className = "rank"; + if (rank <= 1500) { + existingRank.classList.add("relate_rank_1"); + } else { + existingRank.classList.add("relate_rank"); } - count++; + existingRank.innerHTML = `Rank ${rank}`; + } + count++; + return; } + let rankSp = createElement("span", "rank"); + if (rank) { + if (rank <= 1500) { + rankSp.classList.add("relate_rank_1"); + } else { + rankSp.classList.add("relate_rank"); + } + rankSp.innerHTML = `Rank ${rank}`; + elem.appendChild(rankSp); + } + count++; + } })(); From ecbbb8fb1f5c7037d70f27b449eb7409476bb281 Mon Sep 17 00:00:00 2001 From: NekoAria Date: Sat, 9 Aug 2025 21:32:08 +0800 Subject: [PATCH 03/14] fix(style): adjust `bangumi_related_subject_enhance` 's CSS styles to resolve element overlapping issues --- liaune/bangumi_related_subject_enhance.user.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/liaune/bangumi_related_subject_enhance.user.js b/liaune/bangumi_related_subject_enhance.user.js index 96f1aa4a..70670f9d 100644 --- a/liaune/bangumi_related_subject_enhance.user.js +++ b/liaune/bangumi_related_subject_enhance.user.js @@ -1,7 +1,7 @@ // ==UserScript== // @name Bangumi Related Subject Enhance // @namespace https://github.com/bangumi/scripts/liaune -// @version 0.6.2 +// @version 0.6.3 // @description 显示条目页面关联条目的收藏情况,显示关联条目的排名,单行本设为全部已读/取消全部已读 // @author Liaune // @include /^https?:\/\/((bangumi|bgm)\.tv|chii.in)\/subject\/\d+$/ @@ -18,7 +18,9 @@ color: #FFF; box-shadow: 0 1px 2px #EEE,inset 0 1px 1px #FFF; -moz-border-radius: 4px; -webkit-border-radius: 4px; -border-radius: 4px +border-radius: 4px; +position: relative; +top: 10px; } .relate_rank_1{ padding: 2px 5px 1px 5px; @@ -415,7 +417,7 @@ background: no-repeat url(/img/ico/ico_eye.png) 50% top; let fetchList = [], fetchList1 = []; itemsList.forEach((elem) => { - elem.style.height = "150px"; + elem.style.height = "200px"; let avatarLink = elem.querySelector("a.avatar") || elem.querySelector("a"); if (!avatarLink) { From 611b5d87405afba7534299773090bbb120b89a4b Mon Sep 17 00:00:00 2001 From: NekoAria Date: Sat, 9 Aug 2025 21:59:01 +0800 Subject: [PATCH 04/14] fix(style): adjust `bangumi_related_subject_enhance` styles for dark mode --- .../bangumi_related_subject_enhance.user.js | 29 +++++++++++++++++-- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/liaune/bangumi_related_subject_enhance.user.js b/liaune/bangumi_related_subject_enhance.user.js index 70670f9d..aa763465 100644 --- a/liaune/bangumi_related_subject_enhance.user.js +++ b/liaune/bangumi_related_subject_enhance.user.js @@ -76,6 +76,30 @@ height: 18px; background: no-repeat url(/img/ico/ico_eye.png) 50% top; } +/* 控制面板样式 */ +.bangumi-control-panel { +margin: 10px 0; +padding: 10px; +background: #f8f8f8; +border-radius: 4px; +border: 1px solid #ddd; +} + +.bangumi-help-text { +margin-top: 8px; +font-size: 12px; +color: #666; +} + +/* 关灯模式支持 */ +[data-theme="dark"] .bangumi-control-panel { +background: #2a2a2a; +border-color: #444; +} + +[data-theme="dark"] .bangumi-help-text { +color: #aaa; +} `); // 检测 indexedDB 兼容性,因为只有新版本浏览器支持 let indexedDB = @@ -229,8 +253,7 @@ background: no-repeat url(/img/ico/ico_eye.png) 50% top; // 创建控制面板容器 let controlPanel = document.createElement("div"); - controlPanel.style.cssText = - "margin: 10px 0; padding: 10px; background: #f8f8f8; border-radius: 4px; border: 1px solid #ddd;"; + controlPanel.className = "bangumi-control-panel"; // 私密选项 let privateLabel = document.createElement("label"); @@ -336,7 +359,7 @@ background: no-repeat url(/img/ico/ico_eye.png) 50% top; // 添加说明文字 let helpText = document.createElement("div"); - helpText.style.cssText = "margin-top: 8px; font-size: 12px; color: #666;"; + helpText.className = "bangumi-help-text"; helpText.textContent = '提示:批量操作会将当前页面所有条目标记为"已收藏"状态,请谨慎使用'; controlPanel.appendChild(helpText); From b308c080d658740a8de139d7fc83d5658f9462cd Mon Sep 17 00:00:00 2001 From: NekoAria Date: Sat, 9 Aug 2025 22:13:26 +0800 Subject: [PATCH 05/14] fix(style): adjust `bangumi_related_subject_enhance` styling in manga section --- .../bangumi_related_subject_enhance.user.js | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/liaune/bangumi_related_subject_enhance.user.js b/liaune/bangumi_related_subject_enhance.user.js index aa763465..2b25f6c1 100644 --- a/liaune/bangumi_related_subject_enhance.user.js +++ b/liaune/bangumi_related_subject_enhance.user.js @@ -1,8 +1,8 @@ // ==UserScript== // @name Bangumi Related Subject Enhance // @namespace https://github.com/bangumi/scripts/liaune -// @version 0.6.3 -// @description 显示条目页面关联条目的收藏情况,显示关联条目的排名,单行本设为全部已读/取消全部已读 +// @version 0.6.4 +// @description 显示条目页面关联条目的收藏情况,显示关联条目的排名,单行本设为全部已读/取消全部已读 // @author Liaune // @include /^https?:\/\/((bangumi|bgm)\.tv|chii.in)\/subject\/\d+$/ // @grant GM_addStyle @@ -440,7 +440,12 @@ color: #aaa; let fetchList = [], fetchList1 = []; itemsList.forEach((elem) => { - elem.style.height = "200px"; + // 检查元素是否在单行本区域内,如果不是才设置固定高度 + let isInMangaSection = mangaSection && mangaSection.contains(elem); + if (!isInMangaSection) { + elem.style.height = "200px"; + } + let avatarLink = elem.querySelector("a.avatar") || elem.querySelector("a"); if (!avatarLink) { @@ -680,6 +685,10 @@ color: #aaa; existingRank.classList.add("relate_rank"); } existingRank.innerHTML = `Rank ${rank}`; + let isInMangaSection = mangaSection && mangaSection.contains(elem); + if (isInMangaSection) { + existingRank.style.top = "0"; + } } count++; return; @@ -694,6 +703,10 @@ color: #aaa; } rankSp.innerHTML = `Rank ${rank}`; elem.appendChild(rankSp); + let isInMangaSection = mangaSection && mangaSection.contains(elem); + if (isInMangaSection) { + rankSp.style.top = "0"; + } } count++; } From 2c77b38c762e2b1e819b2a106dcebd0623592029 Mon Sep 17 00:00:00 2001 From: NekoAria Date: Wed, 13 Aug 2025 07:38:17 +0800 Subject: [PATCH 06/14] feat(ui): enhance manga control panel and improve user experience - Optimize rank badge positioning with better CSS layout - Prevent duplicate operations with loading states --- .../bangumi_related_subject_enhance.user.js | 429 ++++++------------ 1 file changed, 150 insertions(+), 279 deletions(-) diff --git a/liaune/bangumi_related_subject_enhance.user.js b/liaune/bangumi_related_subject_enhance.user.js index 2b25f6c1..e1a33522 100644 --- a/liaune/bangumi_related_subject_enhance.user.js +++ b/liaune/bangumi_related_subject_enhance.user.js @@ -20,7 +20,8 @@ box-shadow: 0 1px 2px #EEE,inset 0 1px 1px #FFF; -webkit-border-radius: 4px; border-radius: 4px; position: relative; -top: 10px; +top: -12px; +display: inline-block; } .relate_rank_1{ padding: 2px 5px 1px 5px; @@ -31,37 +32,35 @@ color: #FFF; box-shadow: 0 1px 2px #EEE,inset 0 1px 1px #FFF; -moz-border-radius: 4px; -webkit-border-radius: 4px; -border-radius: 4px +border-radius: 4px; +position: relative; +top: -12px; +display: inline-block; } .relate_wish{ -border-color: #fd59a9; -border-style: solid; -border-width:2px; -border-radius: 4px +border: 2px solid #fd59a9; +border-radius: 4px; +box-sizing: border-box; } .relate_collect{ -border-color: #3838e6; -border-style: solid; -border-width:2px; -border-radius: 4px +border: 2px solid #3838e6; +border-radius: 4px; +box-sizing: border-box; } .relate_do{ -border-color: #15d748; -border-style: solid; -border-width:2px; -border-radius: 4px +border: 2px solid #15d748; +border-radius: 4px; +box-sizing: border-box; } .relate_on_hold{ -border-color: #f6af45; -border-style: solid; -border-width:2px; -border-radius: 4px +border: 2px solid #f6af45; +border-radius: 4px; +box-sizing: border-box; } .relate_dropped{ -border-color: #5a5855; -border-style: solid; -border-width:2px; -border-radius: 4px +border: 2px solid #5a5855; +border-radius: 4px; +box-sizing: border-box; } .subCheckIn{ @@ -76,30 +75,26 @@ height: 18px; background: no-repeat url(/img/ico/ico_eye.png) 50% top; } -/* 控制面板样式 */ -.bangumi-control-panel { +/* 单行本控制面板样式 */ +.manga-control-panel { margin: 10px 0; padding: 10px; background: #f8f8f8; border-radius: 4px; border: 1px solid #ddd; -} - -.bangumi-help-text { -margin-top: 8px; -font-size: 12px; -color: #666; +width: 205px; } /* 关灯模式支持 */ -[data-theme="dark"] .bangumi-control-panel { +[data-theme="dark"] .manga-control-panel { background: #2a2a2a; border-color: #444; } -[data-theme="dark"] .bangumi-help-text { -color: #aaa; +ul.coversSmall li { +height: 100%; } + `); // 检测 indexedDB 兼容性,因为只有新版本浏览器支持 let indexedDB = @@ -195,15 +190,14 @@ color: #aaa; count1 = 0, flag = 0; - // 修正选择器以适配新 UI let itemsList1 = document.querySelectorAll( - ".subject_section ul.browserCoverMedium li" + "#columnSubjectHomeB ul.browserCoverMedium li:not(:has(a.thumbTipSmall))" ); let itemsList2 = document.querySelectorAll( - ".subject_section ul.coversSmall li" + "#columnSubjectHomeB ul.coversSmall li" ); let itemsList3 = document.querySelectorAll( - ".subject_section ul.browserCoverSmall li" + "#columnSubjectHomeB ul.browserCoverMedium li:has(a.thumbTipSmall)" ); let itemsList = []; @@ -225,162 +219,113 @@ color: #aaa; } }); - // 找到所有相关的单行本区域 - let targetSections = document.querySelectorAll(".subject_section"); - let mangaSection = null; + // 更新排名数据 + let isUpdating = false; - // 查找包含单行本的区域 - targetSections.forEach((section) => { - let subtitle = section.querySelector(".subtitle"); - if (subtitle && subtitle.textContent.includes("单行本")) { - mangaSection = section; + const updateBtn = createElement("a", "chiiBtn", "javascript:;", "更新排名"); + updateBtn.style.margin = "10px 0"; + updateBtn.addEventListener("click", () => { + if (isUpdating) { + return; } + + updateInfo(); }); - // 如果没找到,使用第一个包含列表的区域 - if (!mangaSection) { - targetSections.forEach((section) => { - if (section.querySelector("ul.browserCoverMedium")) { - mangaSection = section; - } - }); + if (itemsList3.length) { + document + .querySelectorAll("#columnSubjectHomeB .subject_section .clearit")[1] + .append(updateBtn); + } else { + document + .querySelectorAll("#columnSubjectHomeB .subject_section .clearit")[0] + .append(updateBtn); } - //更新缓存数据 - const updateBtn = createElement("a", "chiiBtn", "javascript:;", "更新"); - updateBtn.addEventListener("click", updateInfo); - updateBtn.style.marginRight = "10px"; - - // 创建控制面板容器 - let controlPanel = document.createElement("div"); - controlPanel.className = "bangumi-control-panel"; - - // 私密选项 - let privateLabel = document.createElement("label"); - privateLabel.style.cssText = "margin-right: 15px; cursor: pointer;"; - let checkbox = document.createElement("input"); - checkbox.type = "checkbox"; - checkbox.style.marginRight = "5px"; - privateLabel.appendChild(checkbox); - privateLabel.appendChild(document.createTextNode("私密收藏")); - - checkbox.onclick = function () { - privacy = checkbox.checked ? 1 : 0; - }; - - // 全部标为已读按钮 - let allCollect = createElement( - "a", - "chiiBtn", - "javascript:;", - "全部标为已读" - ); - allCollect.style.marginRight = "10px"; - allCollect.onclick = function () { - if ( - !confirm( - `确定要${allCollect.textContent}吗?\n\n${ - privacy ? "将以私密方式收藏" : "将以公开方式收藏" - }` - ) - ) { - return; - } + getInfo(update); - let targetItems = itemsList3.length ? itemsList3 : itemsList; - if (targetItems.length === 0) { - alert("没有找到可操作的条目"); + function updateInfo() { + if (isUpdating) { return; } - let i = 0; - flag = flag == 1 ? 0 : 1; - allCollect.textContent = - flag == 1 - ? `全部取消已读 (${targetItems.length})` - : `全部标为已读 (${targetItems.length})`; - allCollect.style.backgroundColor = flag ? "#dc3545" : "#28a745"; - - let processInterval = setInterval(function () { - if (i >= targetItems.length) { - clearInterval(processInterval); - allCollect.textContent = flag ? "全部标为已读" : "全部取消已读"; - allCollect.style.backgroundColor = ""; - alert("操作完成!"); - return; - } + count = 0; + update = 1; + isUpdating = true; + updateBtn.style.opacity = "0.5"; + updateBtn.style.pointerEvents = "none"; + getInfo(update); + } - let elem = targetItems[i]; - let avatarLink = - elem.querySelector("a.avatar") || elem.querySelector("a"); - if (!avatarLink) { - i++; + if (itemsList3.length) { + let mangaControlPanel = document.createElement("div"); + mangaControlPanel.className = "manga-control-panel"; + + let privateLabel = document.createElement("label"); + privateLabel.style.cssText = "margin-right: 15px; cursor: pointer;"; + let checkbox = document.createElement("input"); + checkbox.type = "checkbox"; + checkbox.style.marginRight = "5px"; + privateLabel.appendChild(checkbox); + privateLabel.appendChild(document.createTextNode("私密收藏")); + + checkbox.onclick = function () { + privacy = checkbox.checked ? 1 : 0; + }; + + let allCollect = createElement( + "a", + "chiiBtn", + "javascript:;", + "全部标为已读" + ); + allCollect.onclick = function () { + if (!confirm(`确定要${allCollect.textContent}吗?`)) { return; } - let { href } = avatarLink; - let ID = href.split("/subject/")[1]; - let avatarNeue = elem.querySelector("span.avatarNeue"); + let i = 0; + flag = flag == 1 ? 0 : 1; + allCollect.textContent = flag == 1 ? "全部取消已读" : "全部标为已读"; - if (flag) { - collectStatus[ID] = "collect"; - if (avatarNeue) { + let getitemsList3 = setInterval(function () { + let elem = itemsList3[i]; + let { href } = elem.querySelector("a.avatar"); + let ID = href.split("/subject/")[1]; + let avatarNeue = elem.querySelector("span.avatarNeue"); + + if (flag) { + collectStatus[ID] = "collect"; avatarNeue.classList.add("relate_collect"); - } - $.post(`/subject/${ID}/interest/update?gh=${securitycode}`, { - status: "collect", - privacy: privacy || 0, - }); - } else { - delete collectStatus[ID]; - if (avatarNeue) { + $.post(`/subject/${ID}/interest/update?gh=${securitycode}`, { + status: "collect", + privacy: privacy, + }); + } else { + delete collectStatus[ID]; avatarNeue.classList.remove("relate_collect"); + $.post(`/subject/${ID}/remove?gh=${securitycode}`); } - $.post(`/subject/${ID}/remove?gh=${securitycode}`); - } - // 更新按钮文本显示进度 - allCollect.textContent = `${ - (flag ? "标记中... " : "取消中... ") + (i + 1) - }/${targetItems.length}`; + i++; + localStorage.setItem( + "bangumi_subject_collectStatus", + JSON.stringify(collectStatus) + ); + if (i >= itemsList3.length) { + clearInterval(getitemsList3); + } + }, 300); + }; - i++; - localStorage.setItem( - "bangumi_subject_collectStatus", - JSON.stringify(collectStatus) - ); - }, 300); - }; - - // 组装控制面板 - controlPanel.appendChild(updateBtn); - controlPanel.appendChild(privateLabel); - controlPanel.appendChild(allCollect); - - // 添加说明文字 - let helpText = document.createElement("div"); - helpText.className = "bangumi-help-text"; - helpText.textContent = - '提示:批量操作会将当前页面所有条目标记为"已收藏"状态,请谨慎使用'; - controlPanel.appendChild(helpText); - - // 插入控制面板 - if (mangaSection) { - let subtitle = mangaSection.querySelector(".subtitle"); - if (subtitle) { - subtitle.parentNode.insertBefore(controlPanel, subtitle.nextSibling); - } else { - mangaSection.insertBefore(controlPanel, mangaSection.firstChild); - } - } + mangaControlPanel.appendChild(privateLabel); + mangaControlPanel.appendChild(allCollect); - getInfo(update); - function updateInfo() { - count = 0; - update = 1; - updateBtn.textContent = "更新中..."; - updateBtn.disabled = true; - getInfo(update); + $(mangaControlPanel).insertBefore( + document.querySelectorAll( + "#columnSubjectHomeB .subject_section .clearit" + )[0] + ); } function createElement(type, className, href, textContent) { @@ -392,38 +337,27 @@ color: #aaa; } function showCheckIn(elem, ID) { - // 检查是否已经存在 checkIn 按钮,避免重复添加 - let avatarLink = elem.querySelector("a.avatar") || elem.querySelector("a"); - if (!avatarLink) { + if (elem.querySelector("a.subCheckIn")) { return; } - let existingCheckIn = avatarLink.querySelector(".subCheckIn"); - if (existingCheckIn) { - return; // 如果已经存在,直接返回 - } - let checkIn = createElement("a", "subCheckIn", "javascript:;"); - let flag = 0; + let flag = (collectStatus[ID] === "collect") ? 1 : 0; let avatarNeue = elem.querySelector("span.avatarNeue"); checkIn.addEventListener("click", function () { flag = flag == 1 ? 0 : 1; if (flag) { checkIn.style.backgroundPosition = "bottom left"; collectStatus[ID] = "collect"; - if (avatarNeue) { - avatarNeue.classList.add("relate_collect"); - } + avatarNeue.classList.add("relate_collect"); $.post(`/subject/${ID}/interest/update?gh=${securitycode}`, { status: "collect", - privacy: privacy || 0, + privacy: privacy, }); } else { checkIn.style.backgroundPosition = "top left"; delete collectStatus[ID]; - if (avatarNeue) { - avatarNeue.classList.remove("relate_collect"); - } + avatarNeue.classList.remove("relate_collect"); $.post(`/subject/${ID}/remove?gh=${securitycode}`); } localStorage.setItem( @@ -431,8 +365,7 @@ color: #aaa; JSON.stringify(collectStatus) ); }); - - avatarLink.appendChild(checkIn); + elem.querySelector("a.avatar").append(checkIn); } function getInfo(update) { @@ -440,19 +373,7 @@ color: #aaa; let fetchList = [], fetchList1 = []; itemsList.forEach((elem) => { - // 检查元素是否在单行本区域内,如果不是才设置固定高度 - let isInMangaSection = mangaSection && mangaSection.contains(elem); - if (!isInMangaSection) { - elem.style.height = "200px"; - } - - let avatarLink = - elem.querySelector("a.avatar") || elem.querySelector("a"); - if (!avatarLink) { - return; - } - - let { href } = avatarLink; + let { href } = elem.querySelector("a.avatar"); let href1 = href.replace(/subject/, "update"); let ID = href.split("/subject/")[1]; getCache(ID, function (success, result) { @@ -462,9 +383,9 @@ color: #aaa; fetchList.push(elem); } }); - if (collectStatus[ID] != "collect") { - showCheckIn(elem, ID); - } + + showCheckIn(elem, ID); + if (collectStatus[ID] && !update) { displayCollect(collectStatus[ID], elem); } else { @@ -476,14 +397,10 @@ color: #aaa; let getitemsList = setInterval(function () { let elem = fetchList[i]; if (!elem) { - console.log(i); + // console.log(i); } else { - let avatarLink = - elem.querySelector("a.avatar") || elem.querySelector("a"); - if (avatarLink) { - let { href } = avatarLink; - showRank(href, elem); - } + let { href } = elem.querySelector("a.avatar"); + showRank(href, elem); i++; //console.log(i); } @@ -494,15 +411,11 @@ color: #aaa; let getitemsList1 = setInterval(function () { let elem = fetchList1[j]; if (!elem) { - console.log(j); + // console.log(j); } else { - let avatarLink = - elem.querySelector("a.avatar") || elem.querySelector("a"); - if (avatarLink) { - let { href } = avatarLink; - let href1 = href.replace(/subject/, "update"); - showCollect(href1, elem); - } + let { href } = elem.querySelector("a.avatar"); + let href1 = href.replace(/subject/, "update"); + showCollect(href1, elem); j++; //console.log(j); } @@ -513,13 +426,7 @@ color: #aaa; } if (itemsList3.length) { itemsList3.forEach((elem) => { - let avatarLink = - elem.querySelector("a.avatar") || elem.querySelector("a"); - if (!avatarLink) { - return; - } - - let { href } = avatarLink; + let { href } = elem.querySelector("a"); let ID = href.split("/subject/")[1]; if (collectStatus[ID]) { displayCollect(collectStatus[ID], elem); @@ -584,19 +491,6 @@ color: #aaa; function displayCollect(interest, elem) { let avatarNeue = elem.querySelector("span.avatarNeue"); - if (!avatarNeue) { - return; - } - - // 先清除所有收藏状态的 CSS 类,避免重复添加 - avatarNeue.classList.remove( - "relate_wish", - "relate_collect", - "relate_do", - "relate_on_hold", - "relate_dropped" - ); - if (interest == "wish") { avatarNeue.classList.add("relate_wish"); } else if (interest == "collect") { @@ -620,22 +514,19 @@ color: #aaa; xhr.onload = function () { let d = xhr.responseXML; let nameinfo = d.querySelector("#infobox li"); - let name_cn = - nameinfo && nameinfo.innerText.match(/中文名: (\.*)/) - ? nameinfo.innerText.match(/中文名: (\.*)/)[1] - : null; + let name_cn = nameinfo.innerText.match(/中文名: (\.*)/) + ? nameinfo.innerText.match(/中文名: (\.*)/)[1] + : null; //获取排名 let ranksp = d.querySelector( "#panelInterestWrapper .global_score small.alarm" ); let rank = ranksp ? ranksp.innerText.match(/\d+/)[0] : null; //获取站内评分和评分人数 - let scoreElement = d.querySelector( + let score = d.querySelector( "#panelInterestWrapper .global_score span.number" - ); - let score = scoreElement ? scoreElement.innerText : null; - let votesElement = d.querySelector("#ChartWarpper small.grey span"); - let votes = votesElement ? votesElement.innerText : null; + ).innerText; + let votes = d.querySelector("#ChartWarpper small.grey span").innerText; //获取好友评分和评分人数 let frdScore = d.querySelector("#panelInterestWrapper .frdScore"); let score_f = frdScore @@ -663,37 +554,18 @@ color: #aaa; updateBtn.textContent = `更新中... (${count}/${itemsList.length})`; if (count == itemsList.length) { updateBtn.textContent = "更新完毕!"; - updateBtn.disabled = false; + isUpdating = false; + updateBtn.style.opacity = "1"; + updateBtn.style.pointerEvents = "auto"; setTimeout(() => { - updateBtn.textContent = "更新"; - }, 2000); + updateBtn.textContent = "更新排名"; + }, 1000); } } }; } function displayRank(rank, elem) { - // 检查是否已经存在排名标签,避免重复添加 - let existingRank = elem.querySelector(".rank"); - if (existingRank) { - // 如果已经存在,更新内容而不是添加新的 - if (rank) { - existingRank.className = "rank"; - if (rank <= 1500) { - existingRank.classList.add("relate_rank_1"); - } else { - existingRank.classList.add("relate_rank"); - } - existingRank.innerHTML = `Rank ${rank}`; - let isInMangaSection = mangaSection && mangaSection.contains(elem); - if (isInMangaSection) { - existingRank.style.top = "0"; - } - } - count++; - return; - } - let rankSp = createElement("span", "rank"); if (rank) { if (rank <= 1500) { @@ -702,11 +574,10 @@ color: #aaa; rankSp.classList.add("relate_rank"); } rankSp.innerHTML = `Rank ${rank}`; - elem.appendChild(rankSp); - let isInMangaSection = mangaSection && mangaSection.contains(elem); - if (isInMangaSection) { - rankSp.style.top = "0"; - } + const subjectLink = [...elem.querySelectorAll('a[href^="/subject/"]')].at( + -1 + ); + subjectLink.parentNode.insertBefore(rankSp, subjectLink); } count++; } From cdbc7eb84255cc6815d7aab3f2fbe30bc4294709 Mon Sep 17 00:00:00 2001 From: NekoAria Date: Wed, 13 Aug 2025 07:52:39 +0800 Subject: [PATCH 07/14] refactor: migrate from jQuery/XHR to native fetch API and DOM methods --- .../bangumi_related_subject_enhance.user.js | 149 +++++++++--------- 1 file changed, 78 insertions(+), 71 deletions(-) diff --git a/liaune/bangumi_related_subject_enhance.user.js b/liaune/bangumi_related_subject_enhance.user.js index e1a33522..61d5bf53 100644 --- a/liaune/bangumi_related_subject_enhance.user.js +++ b/liaune/bangumi_related_subject_enhance.user.js @@ -297,14 +297,19 @@ height: 100%; if (flag) { collectStatus[ID] = "collect"; avatarNeue.classList.add("relate_collect"); - $.post(`/subject/${ID}/interest/update?gh=${securitycode}`, { - status: "collect", - privacy: privacy, + fetch(`/subject/${ID}/interest/update?gh=${securitycode}`, { + method: "POST", + body: new URLSearchParams({ + status: "collect", + privacy: privacy, + }), }); } else { delete collectStatus[ID]; avatarNeue.classList.remove("relate_collect"); - $.post(`/subject/${ID}/remove?gh=${securitycode}`); + fetch(`/subject/${ID}/remove?gh=${securitycode}`, { + method: "POST", + }); } i++; @@ -321,11 +326,10 @@ height: 100%; mangaControlPanel.appendChild(privateLabel); mangaControlPanel.appendChild(allCollect); - $(mangaControlPanel).insertBefore( - document.querySelectorAll( - "#columnSubjectHomeB .subject_section .clearit" - )[0] + const clearitElement = document.querySelector( + "#columnSubjectHomeB .subject_section .clearit" ); + clearitElement.parentNode.insertBefore(mangaControlPanel, clearitElement); } function createElement(type, className, href, textContent) { @@ -342,7 +346,7 @@ height: 100%; } let checkIn = createElement("a", "subCheckIn", "javascript:;"); - let flag = (collectStatus[ID] === "collect") ? 1 : 0; + let flag = collectStatus[ID] === "collect" ? 1 : 0; let avatarNeue = elem.querySelector("span.avatarNeue"); checkIn.addEventListener("click", function () { flag = flag == 1 ? 0 : 1; @@ -350,15 +354,20 @@ height: 100%; checkIn.style.backgroundPosition = "bottom left"; collectStatus[ID] = "collect"; avatarNeue.classList.add("relate_collect"); - $.post(`/subject/${ID}/interest/update?gh=${securitycode}`, { - status: "collect", - privacy: privacy, + fetch(`/subject/${ID}/interest/update?gh=${securitycode}`, { + method: "POST", + body: new URLSearchParams({ + status: "collect", + privacy: privacy, + }), }); } else { checkIn.style.backgroundPosition = "top left"; delete collectStatus[ID]; avatarNeue.classList.remove("relate_collect"); - $.post(`/subject/${ID}/remove?gh=${securitycode}`); + fetch(`/subject/${ID}/remove?gh=${securitycode}`, { + method: "POST", + }); } localStorage.setItem( "bangumi_subject_collectStatus", @@ -437,7 +446,7 @@ height: 100%; } let thisItem = window.location.href.replace(/subject/, "update"); - fetch(thisItem, { credentials: "include" }) + fetch(thisItem) .then((data) => { return new Promise(function (resovle, reject) { let targetStr = data.text(); @@ -463,7 +472,7 @@ height: 100%; } function showCollect(href, elem) { - fetch(href, { credentials: "include" }) + fetch(href) .then((data) => { return new Promise(function (resovle, reject) { let targetStr = data.text(); @@ -506,63 +515,61 @@ height: 100%; } function showRank(href, elem) { - let xhr = new XMLHttpRequest(); - xhr.open("GET", href); - xhr.withCredentials = true; - xhr.responseType = "document"; - xhr.send(); - xhr.onload = function () { - let d = xhr.responseXML; - let nameinfo = d.querySelector("#infobox li"); - let name_cn = nameinfo.innerText.match(/中文名: (\.*)/) - ? nameinfo.innerText.match(/中文名: (\.*)/)[1] - : null; - //获取排名 - let ranksp = d.querySelector( - "#panelInterestWrapper .global_score small.alarm" - ); - let rank = ranksp ? ranksp.innerText.match(/\d+/)[0] : null; - //获取站内评分和评分人数 - let score = d.querySelector( - "#panelInterestWrapper .global_score span.number" - ).innerText; - let votes = d.querySelector("#ChartWarpper small.grey span").innerText; - //获取好友评分和评分人数 - let frdScore = d.querySelector("#panelInterestWrapper .frdScore"); - let score_f = frdScore - ? frdScore.querySelector("span.num").innerText - : null; - let votes_f = frdScore - ? frdScore.querySelector("a.l").innerText.match(/\d+/)[0] - : null; - let score_u = 0; - let info = { - name_cn: name_cn, - rank: rank, - score: score, - votes: votes, - score_f: score_f, - votes_f: votes_f, - score_u: score_u, - }; - let ID = href.split("/subject/")[1]; - setCache(ID, info); - if (!update) { - displayRank(rank, elem); - } else { - count += 1; - updateBtn.textContent = `更新中... (${count}/${itemsList.length})`; - if (count == itemsList.length) { - updateBtn.textContent = "更新完毕!"; - isUpdating = false; - updateBtn.style.opacity = "1"; - updateBtn.style.pointerEvents = "auto"; - setTimeout(() => { - updateBtn.textContent = "更新排名"; - }, 1000); + fetch(href) + .then((response) => response.text()) + .then((html) => { + const parser = new DOMParser(); + const d = parser.parseFromString(html, "text/html"); + let nameinfo = d.querySelector("#infobox li"); + let name_cn = nameinfo.innerText.match(/中文名: (\.*)/) + ? nameinfo.innerText.match(/中文名: (\.*)/)[1] + : null; + //获取排名 + let ranksp = d.querySelector( + "#panelInterestWrapper .global_score small.alarm" + ); + let rank = ranksp ? ranksp.innerText.match(/\d+/)[0] : null; + //获取站内评分和评分人数 + let score = d.querySelector( + "#panelInterestWrapper .global_score span.number" + ).innerText; + let votes = d.querySelector("#ChartWarpper small.grey span").innerText; + //获取好友评分和评分人数 + let frdScore = d.querySelector("#panelInterestWrapper .frdScore"); + let score_f = frdScore + ? frdScore.querySelector("span.num").innerText + : null; + let votes_f = frdScore + ? frdScore.querySelector("a.l").innerText.match(/\d+/)[0] + : null; + let score_u = 0; + let info = { + name_cn: name_cn, + rank: rank, + score: score, + votes: votes, + score_f: score_f, + votes_f: votes_f, + score_u: score_u, + }; + let ID = href.split("/subject/")[1]; + setCache(ID, info); + if (!update) { + displayRank(rank, elem); + } else { + count += 1; + updateBtn.textContent = `更新中... (${count}/${itemsList.length})`; + if (count == itemsList.length) { + updateBtn.textContent = "更新完毕!"; + isUpdating = false; + updateBtn.style.opacity = "1"; + updateBtn.style.pointerEvents = "auto"; + setTimeout(() => { + updateBtn.textContent = "更新排名"; + }, 1000); + } } - } - }; + }); } function displayRank(rank, elem) { From 6810f8c39880ea2126a38db43b77b05b95675711 Mon Sep 17 00:00:00 2001 From: NekoAria Date: Wed, 13 Aug 2025 08:10:09 +0800 Subject: [PATCH 08/14] refactor(css): consolidate duplicate styles and migrate to BEM naming convention --- .../bangumi_related_subject_enhance.user.js | 89 +++++++++---------- 1 file changed, 41 insertions(+), 48 deletions(-) diff --git a/liaune/bangumi_related_subject_enhance.user.js b/liaune/bangumi_related_subject_enhance.user.js index 61d5bf53..6c5b49e1 100644 --- a/liaune/bangumi_related_subject_enhance.user.js +++ b/liaune/bangumi_related_subject_enhance.user.js @@ -9,9 +9,8 @@ // ==/UserScript== (function () { GM_addStyle(` -.relate_rank{ -padding: 2px 5px 1px 5px; -background: #b4b020; +.rank-badge { +padding: 2px 5px; color: #FFF; -webkit-box-shadow: 0 1px 2px #EEE,inset 0 1px 1px #FFF; -moz-box-shadow: 0 1px 2px #EEE,inset 0 1px 1px #FFF; @@ -23,48 +22,35 @@ position: relative; top: -12px; display: inline-block; } -.relate_rank_1{ -padding: 2px 5px 1px 5px; +.rank-badge--high { background: #15d7b3; -color: #FFF; --webkit-box-shadow: 0 1px 2px #EEE,inset 0 1px 1px #FFF; --moz-box-shadow: 0 1px 2px #EEE,inset 0 1px 1px #FFF; -box-shadow: 0 1px 2px #EEE,inset 0 1px 1px #FFF; --moz-border-radius: 4px; --webkit-border-radius: 4px; -border-radius: 4px; -position: relative; -top: -12px; -display: inline-block; } -.relate_wish{ -border: 2px solid #fd59a9; -border-radius: 4px; -box-sizing: border-box; +.rank-badge--normal { +background: #b4b020; } -.relate_collect{ -border: 2px solid #3838e6; +.collect-status { +border: 2px solid; border-radius: 4px; box-sizing: border-box; } -.relate_do{ -border: 2px solid #15d748; -border-radius: 4px; -box-sizing: border-box; +.collect-status--wish { +border-color: #fd59a9; } -.relate_on_hold{ -border: 2px solid #f6af45; -border-radius: 4px; -box-sizing: border-box; +.collect-status--collect { +border-color: #3838e6; } -.relate_dropped{ -border: 2px solid #5a5855; -border-radius: 4px; -box-sizing: border-box; +.collect-status--do { +border-color: #15d748; +} +.collect-status--on-hold { +border-color: #f6af45; +} +.collect-status--dropped { +border-color: #5a5855; } -.subCheckIn{ -display:block; +.collect-toggle { +display: block; top: -20px; left: 5px; opacity: 0.5; @@ -296,7 +282,7 @@ height: 100%; if (flag) { collectStatus[ID] = "collect"; - avatarNeue.classList.add("relate_collect"); + avatarNeue.classList.add("collect-status", "collect-status--collect"); fetch(`/subject/${ID}/interest/update?gh=${securitycode}`, { method: "POST", body: new URLSearchParams({ @@ -306,7 +292,10 @@ height: 100%; }); } else { delete collectStatus[ID]; - avatarNeue.classList.remove("relate_collect"); + avatarNeue.classList.remove( + "collect-status", + "collect-status--collect" + ); fetch(`/subject/${ID}/remove?gh=${securitycode}`, { method: "POST", }); @@ -341,11 +330,11 @@ height: 100%; } function showCheckIn(elem, ID) { - if (elem.querySelector("a.subCheckIn")) { + if (elem.querySelector("a.collect-toggle")) { return; } - let checkIn = createElement("a", "subCheckIn", "javascript:;"); + let checkIn = createElement("a", "collect-toggle", "javascript:;"); let flag = collectStatus[ID] === "collect" ? 1 : 0; let avatarNeue = elem.querySelector("span.avatarNeue"); checkIn.addEventListener("click", function () { @@ -353,7 +342,7 @@ height: 100%; if (flag) { checkIn.style.backgroundPosition = "bottom left"; collectStatus[ID] = "collect"; - avatarNeue.classList.add("relate_collect"); + avatarNeue.classList.add("collect-status", "collect-status--collect"); fetch(`/subject/${ID}/interest/update?gh=${securitycode}`, { method: "POST", body: new URLSearchParams({ @@ -364,7 +353,10 @@ height: 100%; } else { checkIn.style.backgroundPosition = "top left"; delete collectStatus[ID]; - avatarNeue.classList.remove("relate_collect"); + avatarNeue.classList.remove( + "collect-status", + "collect-status--collect" + ); fetch(`/subject/${ID}/remove?gh=${securitycode}`, { method: "POST", }); @@ -501,15 +493,15 @@ height: 100%; function displayCollect(interest, elem) { let avatarNeue = elem.querySelector("span.avatarNeue"); if (interest == "wish") { - avatarNeue.classList.add("relate_wish"); + avatarNeue.classList.add("collect-status", "collect-status--wish"); } else if (interest == "collect") { - avatarNeue.classList.add("relate_collect"); + avatarNeue.classList.add("collect-status", "collect-status--collect"); } else if (interest == "do") { - avatarNeue.classList.add("relate_do"); + avatarNeue.classList.add("collect-status", "collect-status--do"); } else if (interest == "on_hold") { - avatarNeue.classList.add("relate_on_hold"); + avatarNeue.classList.add("collect-status", "collect-status--on-hold"); } else if (interest == "dropped") { - avatarNeue.classList.add("relate_dropped"); + avatarNeue.classList.add("collect-status", "collect-status--dropped"); } count1++; } @@ -575,10 +567,11 @@ height: 100%; function displayRank(rank, elem) { let rankSp = createElement("span", "rank"); if (rank) { + rankSp.classList.add("rank-badge"); if (rank <= 1500) { - rankSp.classList.add("relate_rank_1"); + rankSp.classList.add("rank-badge--high"); } else { - rankSp.classList.add("relate_rank"); + rankSp.classList.add("rank-badge--normal"); } rankSp.innerHTML = `Rank ${rank}`; const subjectLink = [...elem.querySelectorAll('a[href^="/subject/"]')].at( From 45bc4ca57501a6d07ac3c04aa0fe9f0f4ba12d0f Mon Sep 17 00:00:00 2001 From: NekoAria Date: Wed, 13 Aug 2025 08:31:05 +0800 Subject: [PATCH 09/14] refactor(core): improve variable naming and iteration patterns --- .../bangumi_related_subject_enhance.user.js | 72 +++++++++---------- 1 file changed, 34 insertions(+), 38 deletions(-) diff --git a/liaune/bangumi_related_subject_enhance.user.js b/liaune/bangumi_related_subject_enhance.user.js index 6c5b49e1..dbbf9195 100644 --- a/liaune/bangumi_related_subject_enhance.user.js +++ b/liaune/bangumi_related_subject_enhance.user.js @@ -176,34 +176,28 @@ height: 100%; count1 = 0, flag = 0; - let itemsList1 = document.querySelectorAll( + let relatedSubjects = document.querySelectorAll( "#columnSubjectHomeB ul.browserCoverMedium li:not(:has(a.thumbTipSmall))" ); - let itemsList2 = document.querySelectorAll( + let recommendedSubjects = document.querySelectorAll( "#columnSubjectHomeB ul.coversSmall li" ); - let itemsList3 = document.querySelectorAll( + let volumeSubjects = document.querySelectorAll( "#columnSubjectHomeB ul.browserCoverMedium li:has(a.thumbTipSmall)" ); - let itemsList = []; - for (let i = 0; i < itemsList1.length; i++) itemsList.push(itemsList1[i]); - for (let i = 0; i < itemsList2.length; i++) itemsList.push(itemsList2[i]); + let itemsList = [...relatedSubjects, ...recommendedSubjects]; - if (localStorage.getItem("bangumi_subject_collectStatus")) { - collectStatus = JSON.parse( - localStorage.getItem("bangumi_subject_collectStatus") - ); - } else { - collectStatus = {}; - } + collectStatus = JSON.parse( + localStorage.getItem("bangumi_subject_collectStatus") || "{}" + ); let badgeUserPanel = document.querySelectorAll("#badgeUserPanel a"); - badgeUserPanel.forEach((elem) => { + for (const elem of badgeUserPanel) { if (elem.href.match(/logout/)) { securitycode = elem.href.split("/logout/")[1].toString(); } - }); + } // 更新排名数据 let isUpdating = false; @@ -218,7 +212,7 @@ height: 100%; updateInfo(); }); - if (itemsList3.length) { + if (volumeSubjects.length) { document .querySelectorAll("#columnSubjectHomeB .subject_section .clearit")[1] .append(updateBtn); @@ -243,7 +237,7 @@ height: 100%; getInfo(update); } - if (itemsList3.length) { + if (volumeSubjects.length) { let mangaControlPanel = document.createElement("div"); mangaControlPanel.className = "manga-control-panel"; @@ -270,12 +264,12 @@ height: 100%; return; } - let i = 0; + let volumeIndex = 0; flag = flag == 1 ? 0 : 1; allCollect.textContent = flag == 1 ? "全部取消已读" : "全部标为已读"; - let getitemsList3 = setInterval(function () { - let elem = itemsList3[i]; + let getVolumeSubjects = setInterval(function () { + let elem = volumeSubjects[volumeIndex]; let { href } = elem.querySelector("a.avatar"); let ID = href.split("/subject/")[1]; let avatarNeue = elem.querySelector("span.avatarNeue"); @@ -301,13 +295,13 @@ height: 100%; }); } - i++; + volumeIndex++; localStorage.setItem( "bangumi_subject_collectStatus", JSON.stringify(collectStatus) ); - if (i >= itemsList3.length) { - clearInterval(getitemsList3); + if (volumeIndex >= volumeSubjects.length) { + clearInterval(getVolumeSubjects); } }, 300); }; @@ -373,7 +367,7 @@ height: 100%; if (itemsList.length) { let fetchList = [], fetchList1 = []; - itemsList.forEach((elem) => { + for (const elem of itemsList) { let { href } = elem.querySelector("a.avatar"); let href1 = href.replace(/subject/, "update"); let ID = href.split("/subject/")[1]; @@ -392,41 +386,43 @@ height: 100%; } else { fetchList1.push(elem); } - }); - let i = 0, - j = 0; + } + + let rankFetchIndex = 0, + collectFetchIndex = 0; let getitemsList = setInterval(function () { - let elem = fetchList[i]; + let elem = fetchList[rankFetchIndex]; if (!elem) { - // console.log(i); + // console.log(rankFetchIndex); } else { let { href } = elem.querySelector("a.avatar"); showRank(href, elem); - i++; - //console.log(i); + rankFetchIndex++; + //console.log(rankFetchIndex); } if (count >= itemsList.length) { clearInterval(getitemsList); } }, 500); let getitemsList1 = setInterval(function () { - let elem = fetchList1[j]; + let elem = fetchList1[collectFetchIndex]; if (!elem) { - // console.log(j); + // console.log(collectFetchIndex); } else { let { href } = elem.querySelector("a.avatar"); let href1 = href.replace(/subject/, "update"); showCollect(href1, elem); - j++; - //console.log(j); + collectFetchIndex++; + //console.log(collectFetchIndex); } if (count1 >= itemsList.length) { clearInterval(getitemsList1); } }, 500); } - if (itemsList3.length) { - itemsList3.forEach((elem) => { + + if (volumeSubjects.length) { + for (const elem of volumeSubjects) { let { href } = elem.querySelector("a"); let ID = href.split("/subject/")[1]; if (collectStatus[ID]) { @@ -434,7 +430,7 @@ height: 100%; } else if (collectStatus[ID] != "collect") { showCheckIn(elem, ID); } - }); + } } let thisItem = window.location.href.replace(/subject/, "update"); From 42630edb535e654e0d0f1ece673e49fea5b70e75 Mon Sep 17 00:00:00 2001 From: NekoAria Date: Wed, 13 Aug 2025 08:54:49 +0800 Subject: [PATCH 10/14] chore(modernize): upgrade codebase to ES6 arrow function syntax --- .../bangumi_related_subject_enhance.user.js | 549 +++++++++--------- 1 file changed, 276 insertions(+), 273 deletions(-) diff --git a/liaune/bangumi_related_subject_enhance.user.js b/liaune/bangumi_related_subject_enhance.user.js index dbbf9195..b8aeed54 100644 --- a/liaune/bangumi_related_subject_enhance.user.js +++ b/liaune/bangumi_related_subject_enhance.user.js @@ -7,7 +7,7 @@ // @include /^https?:\/\/((bangumi|bgm)\.tv|chii.in)\/subject\/\d+$/ // @grant GM_addStyle // ==/UserScript== -(function () { +(() => { GM_addStyle(` .rank-badge { padding: 2px 5px; @@ -101,8 +101,9 @@ height: 100%; } // 用来记录已经被使用的缓存列表 let cacheLists = []; + // 获取本地缓存 - function getCache(itemId, callback) { + const getCache = (itemId, callback) => { let request = indexedDB.open(dbName, 1); request.onsuccess = (evt) => { let db = evt.target.result; @@ -122,9 +123,10 @@ height: 100%; callback(false); }; }; - } + }; + // 记录到本地缓存 - function setCache(itemId, data) { + const setCache = (itemId, data) => { let request = indexedDB.open(dbName, 1); request.onsuccess = (evt) => { let db = evt.target.result; @@ -140,9 +142,10 @@ height: 100%; }; reqInfo.onsuccess = (evt) => {}; }; - } + }; + // 清除和更新缓存 - function removeCache() { + const removeCache = () => { let request = indexedDB.open(dbName, 1); request.onsuccess = (evt) => { let db = evt.target.result; @@ -166,164 +169,17 @@ height: 100%; } }; }; - } - - let collectStatus, - securitycode, - privacy, - update = 0, - count = 0, - count1 = 0, - flag = 0; - - let relatedSubjects = document.querySelectorAll( - "#columnSubjectHomeB ul.browserCoverMedium li:not(:has(a.thumbTipSmall))" - ); - let recommendedSubjects = document.querySelectorAll( - "#columnSubjectHomeB ul.coversSmall li" - ); - let volumeSubjects = document.querySelectorAll( - "#columnSubjectHomeB ul.browserCoverMedium li:has(a.thumbTipSmall)" - ); - - let itemsList = [...relatedSubjects, ...recommendedSubjects]; - - collectStatus = JSON.parse( - localStorage.getItem("bangumi_subject_collectStatus") || "{}" - ); - - let badgeUserPanel = document.querySelectorAll("#badgeUserPanel a"); - for (const elem of badgeUserPanel) { - if (elem.href.match(/logout/)) { - securitycode = elem.href.split("/logout/")[1].toString(); - } - } - - // 更新排名数据 - let isUpdating = false; - - const updateBtn = createElement("a", "chiiBtn", "javascript:;", "更新排名"); - updateBtn.style.margin = "10px 0"; - updateBtn.addEventListener("click", () => { - if (isUpdating) { - return; - } - - updateInfo(); - }); - - if (volumeSubjects.length) { - document - .querySelectorAll("#columnSubjectHomeB .subject_section .clearit")[1] - .append(updateBtn); - } else { - document - .querySelectorAll("#columnSubjectHomeB .subject_section .clearit")[0] - .append(updateBtn); - } - - getInfo(update); - - function updateInfo() { - if (isUpdating) { - return; - } - - count = 0; - update = 1; - isUpdating = true; - updateBtn.style.opacity = "0.5"; - updateBtn.style.pointerEvents = "none"; - getInfo(update); - } - - if (volumeSubjects.length) { - let mangaControlPanel = document.createElement("div"); - mangaControlPanel.className = "manga-control-panel"; - - let privateLabel = document.createElement("label"); - privateLabel.style.cssText = "margin-right: 15px; cursor: pointer;"; - let checkbox = document.createElement("input"); - checkbox.type = "checkbox"; - checkbox.style.marginRight = "5px"; - privateLabel.appendChild(checkbox); - privateLabel.appendChild(document.createTextNode("私密收藏")); - - checkbox.onclick = function () { - privacy = checkbox.checked ? 1 : 0; - }; - - let allCollect = createElement( - "a", - "chiiBtn", - "javascript:;", - "全部标为已读" - ); - allCollect.onclick = function () { - if (!confirm(`确定要${allCollect.textContent}吗?`)) { - return; - } - - let volumeIndex = 0; - flag = flag == 1 ? 0 : 1; - allCollect.textContent = flag == 1 ? "全部取消已读" : "全部标为已读"; - - let getVolumeSubjects = setInterval(function () { - let elem = volumeSubjects[volumeIndex]; - let { href } = elem.querySelector("a.avatar"); - let ID = href.split("/subject/")[1]; - let avatarNeue = elem.querySelector("span.avatarNeue"); - - if (flag) { - collectStatus[ID] = "collect"; - avatarNeue.classList.add("collect-status", "collect-status--collect"); - fetch(`/subject/${ID}/interest/update?gh=${securitycode}`, { - method: "POST", - body: new URLSearchParams({ - status: "collect", - privacy: privacy, - }), - }); - } else { - delete collectStatus[ID]; - avatarNeue.classList.remove( - "collect-status", - "collect-status--collect" - ); - fetch(`/subject/${ID}/remove?gh=${securitycode}`, { - method: "POST", - }); - } - - volumeIndex++; - localStorage.setItem( - "bangumi_subject_collectStatus", - JSON.stringify(collectStatus) - ); - if (volumeIndex >= volumeSubjects.length) { - clearInterval(getVolumeSubjects); - } - }, 300); - }; + }; - mangaControlPanel.appendChild(privateLabel); - mangaControlPanel.appendChild(allCollect); - - const clearitElement = document.querySelector( - "#columnSubjectHomeB .subject_section .clearit" - ); - clearitElement.parentNode.insertBefore(mangaControlPanel, clearitElement); - } - - function createElement(type, className, href, textContent) { + const createElement = (type, className, href, textContent) => { let Element = document.createElement(type); Element.className = className; Element.href = href; Element.textContent = textContent; return Element; - } + }; - function showCheckIn(elem, ID) { + const showCheckIn = (elem, ID) => { if (elem.querySelector("a.collect-toggle")) { return; } @@ -331,7 +187,7 @@ height: 100%; let checkIn = createElement("a", "collect-toggle", "javascript:;"); let flag = collectStatus[ID] === "collect" ? 1 : 0; let avatarNeue = elem.querySelector("span.avatarNeue"); - checkIn.addEventListener("click", function () { + checkIn.addEventListener("click", () => { flag = flag == 1 ? 0 : 1; if (flag) { checkIn.style.backgroundPosition = "bottom left"; @@ -361,9 +217,128 @@ height: 100%; ); }); elem.querySelector("a.avatar").append(checkIn); - } + }; + + const displayCollect = (interest, elem) => { + let avatarNeue = elem.querySelector("span.avatarNeue"); + if (interest == "wish") { + avatarNeue.classList.add("collect-status", "collect-status--wish"); + } else if (interest == "collect") { + avatarNeue.classList.add("collect-status", "collect-status--collect"); + } else if (interest == "do") { + avatarNeue.classList.add("collect-status", "collect-status--do"); + } else if (interest == "on_hold") { + avatarNeue.classList.add("collect-status", "collect-status--on-hold"); + } else if (interest == "dropped") { + avatarNeue.classList.add("collect-status", "collect-status--dropped"); + } + count1++; + }; + + const displayRank = (rank, elem) => { + let rankSp = createElement("span", "rank"); + if (rank) { + rankSp.classList.add("rank-badge"); + if (rank <= 1500) { + rankSp.classList.add("rank-badge--high"); + } else { + rankSp.classList.add("rank-badge--normal"); + } + rankSp.innerHTML = `Rank ${rank}`; + const subjectLink = [...elem.querySelectorAll('a[href^="/subject/"]')].at( + -1 + ); + subjectLink.parentNode.insertBefore(rankSp, subjectLink); + } + count++; + }; + + const showCollect = (href, elem) => { + fetch(href) + .then((data) => { + return new Promise((resovle, reject) => { + let targetStr = data.text(); + resovle(targetStr); + }); + }) + .then((targetStr) => { + let Match = targetStr.match( + /"GenInterestBox\('(\S+?)'\)" checked="checked"/ + ); + let interest = Match ? Match[1] : null; + let ID = href.split("/update/")[1]; + if (Match) { + collectStatus[ID] = "collect"; + localStorage.setItem( + "bangumi_subject_collectStatus", + JSON.stringify(collectStatus) + ); + } + if (!update) { + displayCollect(interest, elem); + } + }); + }; + + const showRank = (href, elem) => { + fetch(href) + .then((response) => response.text()) + .then((html) => { + const parser = new DOMParser(); + const d = parser.parseFromString(html, "text/html"); + let nameinfo = d.querySelector("#infobox li"); + let name_cn = nameinfo.innerText.match(/中文名: (\.*)/) + ? nameinfo.innerText.match(/中文名: (\.*)/)[1] + : null; + //获取排名 + let ranksp = d.querySelector( + "#panelInterestWrapper .global_score small.alarm" + ); + let rank = ranksp ? ranksp.innerText.match(/\d+/)[0] : null; + //获取站内评分和评分人数 + let score = d.querySelector( + "#panelInterestWrapper .global_score span.number" + ).innerText; + let votes = d.querySelector("#ChartWarpper small.grey span").innerText; + //获取好友评分和评分人数 + let frdScore = d.querySelector("#panelInterestWrapper .frdScore"); + let score_f = frdScore + ? frdScore.querySelector("span.num").innerText + : null; + let votes_f = frdScore + ? frdScore.querySelector("a.l").innerText.match(/\d+/)[0] + : null; + let score_u = 0; + let info = { + name_cn: name_cn, + rank: rank, + score: score, + votes: votes, + score_f: score_f, + votes_f: votes_f, + score_u: score_u, + }; + let ID = href.split("/subject/")[1]; + setCache(ID, info); + if (!update) { + displayRank(rank, elem); + } else { + count += 1; + updateBtn.textContent = `更新中... (${count}/${itemsList.length})`; + if (count == itemsList.length) { + updateBtn.textContent = "更新完毕!"; + isUpdating = false; + updateBtn.style.opacity = "1"; + updateBtn.style.pointerEvents = "auto"; + setTimeout(() => { + updateBtn.textContent = "更新排名"; + }, 1000); + } + } + }); + }; - function getInfo(update) { + const getInfo = (update) => { if (itemsList.length) { let fetchList = [], fetchList1 = []; @@ -371,7 +346,7 @@ height: 100%; let { href } = elem.querySelector("a.avatar"); let href1 = href.replace(/subject/, "update"); let ID = href.split("/subject/")[1]; - getCache(ID, function (success, result) { + getCache(ID, (success, result) => { if (success && !update) { displayRank(result.rank, elem); } else { @@ -390,7 +365,7 @@ height: 100%; let rankFetchIndex = 0, collectFetchIndex = 0; - let getitemsList = setInterval(function () { + let getitemsList = setInterval(() => { let elem = fetchList[rankFetchIndex]; if (!elem) { // console.log(rankFetchIndex); @@ -404,7 +379,7 @@ height: 100%; clearInterval(getitemsList); } }, 500); - let getitemsList1 = setInterval(function () { + let getitemsList1 = setInterval(() => { let elem = fetchList1[collectFetchIndex]; if (!elem) { // console.log(collectFetchIndex); @@ -436,7 +411,7 @@ height: 100%; let thisItem = window.location.href.replace(/subject/, "update"); fetch(thisItem) .then((data) => { - return new Promise(function (resovle, reject) { + return new Promise((resovle, reject) => { let targetStr = data.text(); resovle(targetStr); }); @@ -457,124 +432,152 @@ height: 100%; JSON.stringify(collectStatus) ); }); - } + }; - function showCollect(href, elem) { - fetch(href) - .then((data) => { - return new Promise(function (resovle, reject) { - let targetStr = data.text(); - resovle(targetStr); - }); - }) - .then((targetStr) => { - let Match = targetStr.match( - /"GenInterestBox\('(\S+?)'\)" checked="checked"/ - ); - let interest = Match ? Match[1] : null; - let ID = href.split("/update/")[1]; - if (Match) { - collectStatus[ID] = "collect"; - localStorage.setItem( - "bangumi_subject_collectStatus", - JSON.stringify(collectStatus) - ); - } - if (!update) { - displayCollect(interest, elem); - } - }); + const updateInfo = () => { + if (isUpdating) { + return; + } + + count = 0; + update = 1; + isUpdating = true; + updateBtn.style.opacity = "0.5"; + updateBtn.style.pointerEvents = "none"; + getInfo(update); + }; + + let collectStatus, + securitycode, + privacy, + update = 0, + count = 0, + count1 = 0, + flag = 0; + + let relatedSubjects = document.querySelectorAll( + "#columnSubjectHomeB ul.browserCoverMedium li:not(:has(a.thumbTipSmall))" + ); + let recommendedSubjects = document.querySelectorAll( + "#columnSubjectHomeB ul.coversSmall li" + ); + let volumeSubjects = document.querySelectorAll( + "#columnSubjectHomeB ul.browserCoverMedium li:has(a.thumbTipSmall)" + ); + + let itemsList = [...relatedSubjects, ...recommendedSubjects]; + + collectStatus = JSON.parse( + localStorage.getItem("bangumi_subject_collectStatus") || "{}" + ); + + let badgeUserPanel = document.querySelectorAll("#badgeUserPanel a"); + for (const elem of badgeUserPanel) { + if (elem.href.match(/logout/)) { + securitycode = elem.href.split("/logout/")[1].toString(); + } } - function displayCollect(interest, elem) { - let avatarNeue = elem.querySelector("span.avatarNeue"); - if (interest == "wish") { - avatarNeue.classList.add("collect-status", "collect-status--wish"); - } else if (interest == "collect") { - avatarNeue.classList.add("collect-status", "collect-status--collect"); - } else if (interest == "do") { - avatarNeue.classList.add("collect-status", "collect-status--do"); - } else if (interest == "on_hold") { - avatarNeue.classList.add("collect-status", "collect-status--on-hold"); - } else if (interest == "dropped") { - avatarNeue.classList.add("collect-status", "collect-status--dropped"); + // 更新排名数据 + let isUpdating = false; + + const updateBtn = createElement("a", "chiiBtn", "javascript:;", "更新排名"); + updateBtn.style.margin = "10px 0"; + updateBtn.addEventListener("click", () => { + if (isUpdating) { + return; } - count1++; + + updateInfo(); + }); + + if (volumeSubjects.length) { + document + .querySelectorAll("#columnSubjectHomeB .subject_section .clearit")[1] + .append(updateBtn); + } else { + document + .querySelectorAll("#columnSubjectHomeB .subject_section .clearit")[0] + .append(updateBtn); } - function showRank(href, elem) { - fetch(href) - .then((response) => response.text()) - .then((html) => { - const parser = new DOMParser(); - const d = parser.parseFromString(html, "text/html"); - let nameinfo = d.querySelector("#infobox li"); - let name_cn = nameinfo.innerText.match(/中文名: (\.*)/) - ? nameinfo.innerText.match(/中文名: (\.*)/)[1] - : null; - //获取排名 - let ranksp = d.querySelector( - "#panelInterestWrapper .global_score small.alarm" - ); - let rank = ranksp ? ranksp.innerText.match(/\d+/)[0] : null; - //获取站内评分和评分人数 - let score = d.querySelector( - "#panelInterestWrapper .global_score span.number" - ).innerText; - let votes = d.querySelector("#ChartWarpper small.grey span").innerText; - //获取好友评分和评分人数 - let frdScore = d.querySelector("#panelInterestWrapper .frdScore"); - let score_f = frdScore - ? frdScore.querySelector("span.num").innerText - : null; - let votes_f = frdScore - ? frdScore.querySelector("a.l").innerText.match(/\d+/)[0] - : null; - let score_u = 0; - let info = { - name_cn: name_cn, - rank: rank, - score: score, - votes: votes, - score_f: score_f, - votes_f: votes_f, - score_u: score_u, - }; + getInfo(update); + + if (volumeSubjects.length) { + let mangaControlPanel = document.createElement("div"); + mangaControlPanel.className = "manga-control-panel"; + + let privateLabel = document.createElement("label"); + privateLabel.style.cssText = "margin-right: 15px; cursor: pointer;"; + let checkbox = document.createElement("input"); + checkbox.type = "checkbox"; + checkbox.style.marginRight = "5px"; + privateLabel.appendChild(checkbox); + privateLabel.appendChild(document.createTextNode("私密收藏")); + + checkbox.onclick = () => { + privacy = checkbox.checked ? 1 : 0; + }; + + let allCollect = createElement( + "a", + "chiiBtn", + "javascript:;", + "全部标为已读" + ); + allCollect.onclick = () => { + if (!confirm(`确定要${allCollect.textContent}吗?`)) { + return; + } + + let volumeIndex = 0; + flag = flag == 1 ? 0 : 1; + allCollect.textContent = flag == 1 ? "全部取消已读" : "全部标为已读"; + + let getVolumeSubjects = setInterval(() => { + let elem = volumeSubjects[volumeIndex]; + let { href } = elem.querySelector("a.avatar"); let ID = href.split("/subject/")[1]; - setCache(ID, info); - if (!update) { - displayRank(rank, elem); + let avatarNeue = elem.querySelector("span.avatarNeue"); + + if (flag) { + collectStatus[ID] = "collect"; + avatarNeue.classList.add("collect-status", "collect-status--collect"); + fetch(`/subject/${ID}/interest/update?gh=${securitycode}`, { + method: "POST", + body: new URLSearchParams({ + status: "collect", + privacy: privacy, + }), + }); } else { - count += 1; - updateBtn.textContent = `更新中... (${count}/${itemsList.length})`; - if (count == itemsList.length) { - updateBtn.textContent = "更新完毕!"; - isUpdating = false; - updateBtn.style.opacity = "1"; - updateBtn.style.pointerEvents = "auto"; - setTimeout(() => { - updateBtn.textContent = "更新排名"; - }, 1000); - } + delete collectStatus[ID]; + avatarNeue.classList.remove( + "collect-status", + "collect-status--collect" + ); + fetch(`/subject/${ID}/remove?gh=${securitycode}`, { + method: "POST", + }); } - }); - } - function displayRank(rank, elem) { - let rankSp = createElement("span", "rank"); - if (rank) { - rankSp.classList.add("rank-badge"); - if (rank <= 1500) { - rankSp.classList.add("rank-badge--high"); - } else { - rankSp.classList.add("rank-badge--normal"); - } - rankSp.innerHTML = `Rank ${rank}`; - const subjectLink = [...elem.querySelectorAll('a[href^="/subject/"]')].at( - -1 - ); - subjectLink.parentNode.insertBefore(rankSp, subjectLink); - } - count++; + volumeIndex++; + localStorage.setItem( + "bangumi_subject_collectStatus", + JSON.stringify(collectStatus) + ); + if (volumeIndex >= volumeSubjects.length) { + clearInterval(getVolumeSubjects); + } + }, 300); + }; + + mangaControlPanel.appendChild(privateLabel); + mangaControlPanel.appendChild(allCollect); + + const clearitElement = document.querySelector( + "#columnSubjectHomeB .subject_section .clearit" + ); + clearitElement.parentNode.insertBefore(mangaControlPanel, clearitElement); } })(); From 111558b95483350eb14473b141879c9875552010 Mon Sep 17 00:00:00 2001 From: NekoAria Date: Wed, 13 Aug 2025 12:05:51 +0800 Subject: [PATCH 11/14] refactor(core): extract constants, improve readability and restructure state management --- .../bangumi_related_subject_enhance.user.js | 833 ++++++++++-------- 1 file changed, 461 insertions(+), 372 deletions(-) diff --git a/liaune/bangumi_related_subject_enhance.user.js b/liaune/bangumi_related_subject_enhance.user.js index b8aeed54..4ac1f1bb 100644 --- a/liaune/bangumi_related_subject_enhance.user.js +++ b/liaune/bangumi_related_subject_enhance.user.js @@ -1,12 +1,13 @@ // ==UserScript== // @name Bangumi Related Subject Enhance // @namespace https://github.com/bangumi/scripts/liaune -// @version 0.6.4 +// @version 0.6.5 // @description 显示条目页面关联条目的收藏情况,显示关联条目的排名,单行本设为全部已读/取消全部已读 // @author Liaune // @include /^https?:\/\/((bangumi|bgm)\.tv|chii.in)\/subject\/\d+$/ // @grant GM_addStyle // ==/UserScript== + (() => { GM_addStyle(` .rank-badge { @@ -28,6 +29,7 @@ background: #15d7b3; .rank-badge--normal { background: #b4b020; } + .collect-status { border: 2px solid; border-radius: 4px; @@ -52,10 +54,9 @@ border-color: #5a5855; .collect-toggle { display: block; top: -20px; -left: 5px; opacity: 0.5; position: relative; -padding: 0 2px; +padding: 0 5px; width: 16px; height: 18px; background: no-repeat url(/img/ico/ico_eye.png) 50% top; @@ -82,502 +83,590 @@ height: 100%; } `); - // 检测 indexedDB 兼容性,因为只有新版本浏览器支持 - let indexedDB = + + // 常量定义 + const CONSTANTS = { + COLLECT_STATUS_KEY: "bangumi_subject_collectStatus", + DB_NAME: "Bangumi_Subject_Info", + FETCH_INTERVAL: 500, + HIGH_RANK_THRESHOLD: 1500, + INDEX_NAME: "id", + TABLE_NAME: "info", + TWO_WEEKS_MS: 1209600000, + }; + + // 状态管理 + const state = { + accessedCacheItems: [], + collectFetchCount: 0, + collectStatus: {}, + isAllCollected: false, + isUpdating: false, + privacy: 0, + rankFetchCount: 0, + securityCode: null, + }; + + // 获取 IndexedDB 实例 + const indexedDB = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB; + // 初始化 indexedDB - const dbName = "Bangumi_Subject_Info"; - const tableName = "info"; - const indexName = "id"; if (indexedDB) { - let request = indexedDB.open(dbName, 1); + const request = indexedDB.open(CONSTANTS.DB_NAME, 1); request.onupgradeneeded = (evt) => { - let db = evt.target.result; - let objectStore = db.createObjectStore(tableName, { keyPath: indexName }); + const db = evt.target.result; + db.createObjectStore(CONSTANTS.TABLE_NAME, { + keyPath: CONSTANTS.INDEX_NAME, + }); }; - request.onsuccess = (evt) => { - removeCache(); + request.onsuccess = () => { + cleanExpiredCache(); }; } - // 用来记录已经被使用的缓存列表 - let cacheLists = []; - - // 获取本地缓存 - const getCache = (itemId, callback) => { - let request = indexedDB.open(dbName, 1); - request.onsuccess = (evt) => { - let db = evt.target.result; - let transaction = db.transaction([tableName], "readonly"); - let objectStore = transaction.objectStore(tableName); - let reqInfo = objectStore.get(itemId); - reqInfo.onsuccess = (evt) => { - let { result } = evt.target; - if (!!result) { - cacheLists.push(itemId); + + // 获取缓存数据 + const getCachedData = (subjectId, callback) => { + const request = indexedDB.open(CONSTANTS.DB_NAME, 1); + request.onsuccess = (event) => { + const db = event.target.result; + const transaction = db.transaction([CONSTANTS.TABLE_NAME], "readonly"); + const store = transaction.objectStore(CONSTANTS.TABLE_NAME); + const getRequest = store.get(subjectId); + + getRequest.onsuccess = (event) => { + const { result } = event.target; + if (result) { + state.accessedCacheItems.push(subjectId); callback(true, result.value.content); } else { callback(false); } }; - reqInfo.onerror = (evt) => { - callback(false); - }; + + getRequest.onerror = () => callback(false); }; }; - // 记录到本地缓存 - const setCache = (itemId, data) => { - let request = indexedDB.open(dbName, 1); - request.onsuccess = (evt) => { - let db = evt.target.result; - let transaction = db.transaction([tableName], "readwrite"); - let objectStore = transaction.objectStore(tableName); - let cache = { + // 保存缓存数据 + const setCachedData = (subjectId, data) => { + const request = indexedDB.open(CONSTANTS.DB_NAME, 1); + request.onsuccess = (event) => { + const db = event.target.result; + const transaction = db.transaction([CONSTANTS.TABLE_NAME], "readwrite"); + const store = transaction.objectStore(CONSTANTS.TABLE_NAME); + const cacheData = { content: data, created: new Date(), }; - let reqInfo = objectStore.put({ id: itemId, value: cache }); - reqInfo.onerror = (evt) => { - // //console.log('Error', evt.target.error.name); - }; - reqInfo.onsuccess = (evt) => {}; + store.put({ id: subjectId, value: cacheData }); }; }; - // 清除和更新缓存 - const removeCache = () => { - let request = indexedDB.open(dbName, 1); - request.onsuccess = (evt) => { - let db = evt.target.result; - let transaction = db.transaction([tableName], "readwrite"), - store = transaction.objectStore(tableName), - twoWeek = 1209600000; - store.openCursor().onsuccess = (evt) => { - let cursor = evt.target.result; - if (cursor) { - if (cacheLists.indexOf(cursor.value.name) !== -1) { - cursor.value.created = new Date(); - cursor.update(cursor.value); - } else { - let now = new Date(), - last = cursor.value.created; - if (now - last > twoWeek) { - cursor.delete(); - } + // 清理过期缓存 + const cleanExpiredCache = () => { + const request = indexedDB.open(CONSTANTS.DB_NAME, 1); + request.onsuccess = (event) => { + const db = event.target.result; + const transaction = db.transaction([CONSTANTS.TABLE_NAME], "readwrite"); + const store = transaction.objectStore(CONSTANTS.TABLE_NAME); + + store.openCursor().onsuccess = (event) => { + const cursor = event.target.result; + if (!cursor) { + return; + } + + const cacheItem = cursor.value; + if (state.accessedCacheItems.includes(cacheItem.id)) { + cacheItem.value.created = new Date(); + cursor.update(cacheItem); + } else { + const now = new Date(); + const cacheAge = now - new Date(cacheItem.value.created); + if (cacheAge > CONSTANTS.TWO_WEEKS_MS) { + cursor.delete(); } - cursor.continue(); } + cursor.continue(); }; }; }; - const createElement = (type, className, href, textContent) => { - let Element = document.createElement(type); - Element.className = className; - Element.href = href; - Element.textContent = textContent; - return Element; + // 创建带属性的 DOM 元素 + const createElement = (tagName, className, href, textContent) => { + const element = document.createElement(tagName); + if (className) { + element.className = className; + } + if (href) { + element.href = href; + } + if (textContent) { + element.textContent = textContent; + } + return element; }; - const showCheckIn = (elem, ID) => { - if (elem.querySelector("a.collect-toggle")) { + // 添加收藏切换按钮 + const addCollectToggleButton = (element, subjectId) => { + if (element.querySelector("a.collect-toggle")) { return; } - let checkIn = createElement("a", "collect-toggle", "javascript:;"); - let flag = collectStatus[ID] === "collect" ? 1 : 0; - let avatarNeue = elem.querySelector("span.avatarNeue"); - checkIn.addEventListener("click", () => { - flag = flag == 1 ? 0 : 1; - if (flag) { - checkIn.style.backgroundPosition = "bottom left"; - collectStatus[ID] = "collect"; - avatarNeue.classList.add("collect-status", "collect-status--collect"); - fetch(`/subject/${ID}/interest/update?gh=${securitycode}`, { - method: "POST", - body: new URLSearchParams({ - status: "collect", - privacy: privacy, - }), - }); - } else { - checkIn.style.backgroundPosition = "top left"; - delete collectStatus[ID]; - avatarNeue.classList.remove( - "collect-status", - "collect-status--collect" - ); - fetch(`/subject/${ID}/remove?gh=${securitycode}`, { - method: "POST", - }); - } - localStorage.setItem( - "bangumi_subject_collectStatus", - JSON.stringify(collectStatus) - ); + const toggleButton = createElement("a", "collect-toggle", "javascript:;"); + let isCollected = state.collectStatus[subjectId] === "collect"; + const avatarNeue = element.querySelector("span.avatarNeue"); + + toggleButton.style.backgroundPosition = isCollected + ? "bottom left" + : "top left"; + + toggleButton.addEventListener("click", () => { + isCollected = !isCollected; + updateCollectStatus(subjectId, isCollected, avatarNeue, toggleButton); }); - elem.querySelector("a.avatar").append(checkIn); + + element.querySelector("a.avatar").append(toggleButton); }; - const displayCollect = (interest, elem) => { - let avatarNeue = elem.querySelector("span.avatarNeue"); - if (interest == "wish") { - avatarNeue.classList.add("collect-status", "collect-status--wish"); - } else if (interest == "collect") { - avatarNeue.classList.add("collect-status", "collect-status--collect"); - } else if (interest == "do") { - avatarNeue.classList.add("collect-status", "collect-status--do"); - } else if (interest == "on_hold") { - avatarNeue.classList.add("collect-status", "collect-status--on-hold"); - } else if (interest == "dropped") { - avatarNeue.classList.add("collect-status", "collect-status--dropped"); + // 更新收藏状态 + const updateCollectStatus = ( + subjectId, + isCollected, + avatarNeue, + toggleButton + ) => { + const statusClass = "collect-status--collect"; + + if (isCollected) { + toggleButton.style.backgroundPosition = "bottom left"; + state.collectStatus[subjectId] = "collect"; + avatarNeue.classList.add("collect-status", statusClass); + + fetch(`/subject/${subjectId}/interest/update?gh=${state.securityCode}`, { + method: "POST", + body: new URLSearchParams({ + status: "collect", + privacy: state.privacy, + }), + }); + } else { + toggleButton.style.backgroundPosition = "top left"; + delete state.collectStatus[subjectId]; + avatarNeue.classList.remove("collect-status", statusClass); + + fetch(`/subject/${subjectId}/remove?gh=${state.securityCode}`, { + method: "POST", + }); } - count1++; + saveCollectStatus(); }; - const displayRank = (rank, elem) => { - let rankSp = createElement("span", "rank"); - if (rank) { - rankSp.classList.add("rank-badge"); - if (rank <= 1500) { - rankSp.classList.add("rank-badge--high"); - } else { - rankSp.classList.add("rank-badge--normal"); - } - rankSp.innerHTML = `Rank ${rank}`; - const subjectLink = [...elem.querySelectorAll('a[href^="/subject/"]')].at( - -1 - ); - subjectLink.parentNode.insertBefore(rankSp, subjectLink); + // 保存收藏状态到本地存储 + const saveCollectStatus = () => { + localStorage.setItem( + CONSTANTS.COLLECT_STATUS_KEY, + JSON.stringify(state.collectStatus) + ); + }; + + // 显示收藏状态 + const renderCollectStatus = (interest, element) => { + const statusMap = { + wish: "collect-status--wish", + collect: "collect-status--collect", + do: "collect-status--do", + on_hold: "collect-status--on-hold", + dropped: "collect-status--dropped", + }; + + const statusClass = statusMap[interest]; + if (statusClass) { + const avatarNeue = element.querySelector("span.avatarNeue"); + avatarNeue.classList.add("collect-status", statusClass); } - count++; + state.collectFetchCount++; }; - const showCollect = (href, elem) => { - fetch(href) - .then((data) => { - return new Promise((resovle, reject) => { - let targetStr = data.text(); - resovle(targetStr); - }); - }) - .then((targetStr) => { - let Match = targetStr.match( + // 显示排名徽章 + const renderRankBadge = (rank, element) => { + if (!rank) { + state.rankFetchCount++; + return; + } + + const rankSpan = createElement("span", "rank"); + rankSpan.classList.add("rank-badge"); + + const badgeClass = + rank <= CONSTANTS.HIGH_RANK_THRESHOLD + ? "rank-badge--high" + : "rank-badge--normal"; + rankSpan.classList.add(badgeClass); + rankSpan.innerHTML = `Rank ${rank}`; + + const subjectLinks = element.querySelectorAll('a[href^="/subject/"]'); + const lastSubjectLink = subjectLinks[subjectLinks.length - 1]; + lastSubjectLink.parentNode.insertBefore(rankSpan, lastSubjectLink); + + state.rankFetchCount++; + }; + + // 获取收藏数据 + const fetchCollectData = (url, element) => { + fetch(url) + .then((response) => response.text()) + .then((html) => { + const interestMatch = html.match( /"GenInterestBox\('(\S+?)'\)" checked="checked"/ ); - let interest = Match ? Match[1] : null; - let ID = href.split("/update/")[1]; - if (Match) { - collectStatus[ID] = "collect"; - localStorage.setItem( - "bangumi_subject_collectStatus", - JSON.stringify(collectStatus) - ); + const interest = interestMatch ? interestMatch[1] : null; + const subjectId = url.split("/update/")[1]; + + if (interestMatch) { + state.collectStatus[subjectId] = interest; + saveCollectStatus(); } - if (!update) { - displayCollect(interest, elem); + + if (!state.isUpdating) { + renderCollectStatus(interest, element); } + }) + .catch((error) => { + console.error("Error fetching collect data:", error); + state.collectFetchCount++; }); }; - const showRank = (href, elem) => { - fetch(href) + // 获取排名数据 + const fetchRankData = (url, element) => { + fetch(url) .then((response) => response.text()) .then((html) => { - const parser = new DOMParser(); - const d = parser.parseFromString(html, "text/html"); - let nameinfo = d.querySelector("#infobox li"); - let name_cn = nameinfo.innerText.match(/中文名: (\.*)/) - ? nameinfo.innerText.match(/中文名: (\.*)/)[1] - : null; - //获取排名 - let ranksp = d.querySelector( + const doc = new DOMParser().parseFromString(html, "text/html"); + + // 解析条目信息 + const nameInfo = doc.querySelector("#infobox li"); + const nameCN = nameInfo?.innerText.match(/中文名: (.*)/)?.[1] || null; + + // 获取排名 + const rankElement = doc.querySelector( "#panelInterestWrapper .global_score small.alarm" ); - let rank = ranksp ? ranksp.innerText.match(/\d+/)[0] : null; - //获取站内评分和评分人数 - let score = d.querySelector( + const rank = rankElement?.innerText.match(/\d+/)?.[0] || null; + + // 获取站内评分和评分人数 + const scoreElement = doc.querySelector( "#panelInterestWrapper .global_score span.number" - ).innerText; - let votes = d.querySelector("#ChartWarpper small.grey span").innerText; - //获取好友评分和评分人数 - let frdScore = d.querySelector("#panelInterestWrapper .frdScore"); - let score_f = frdScore - ? frdScore.querySelector("span.num").innerText - : null; - let votes_f = frdScore - ? frdScore.querySelector("a.l").innerText.match(/\d+/)[0] - : null; - let score_u = 0; - let info = { - name_cn: name_cn, - rank: rank, - score: score, - votes: votes, - score_f: score_f, - votes_f: votes_f, - score_u: score_u, + ); + const score = scoreElement?.innerText; + + const votesElement = doc.querySelector("#ChartWarpper small.grey span"); + const votes = votesElement?.innerText; + + // 获取好友评分和评分人数 + const friendScoreElement = doc.querySelector( + "#panelInterestWrapper .frdScore" + ); + const friendScore = + friendScoreElement?.querySelector("span.num")?.innerText || null; + const friendVotes = + friendScoreElement + ?.querySelector("a.l") + ?.innerText.match(/\d+/)?.[0] || null; + + const subjectInfo = { + name_cn: nameCN, + rank, + score, + votes, + score_f: friendScore, + votes_f: friendVotes, + score_u: 0, }; - let ID = href.split("/subject/")[1]; - setCache(ID, info); - if (!update) { - displayRank(rank, elem); + + const subjectId = url.split("/subject/")[1]; + setCachedData(subjectId, subjectInfo); + + if (!state.isUpdating) { + renderRankBadge(rank, element); } else { - count += 1; - updateBtn.textContent = `更新中... (${count}/${itemsList.length})`; - if (count == itemsList.length) { - updateBtn.textContent = "更新完毕!"; - isUpdating = false; - updateBtn.style.opacity = "1"; - updateBtn.style.pointerEvents = "auto"; - setTimeout(() => { - updateBtn.textContent = "更新排名"; - }, 1000); - } + state.rankFetchCount++; + updateProgressDisplay(); } }); }; - const getInfo = (update) => { - if (itemsList.length) { - let fetchList = [], - fetchList1 = []; - for (const elem of itemsList) { - let { href } = elem.querySelector("a.avatar"); - let href1 = href.replace(/subject/, "update"); - let ID = href.split("/subject/")[1]; - getCache(ID, (success, result) => { - if (success && !update) { - displayRank(result.rank, elem); - } else { - fetchList.push(elem); - } - }); + // 更新进度显示 + const updateProgressDisplay = () => { + const updateButton = document.querySelector(".update-btn"); + if (!updateButton) { + return; + } - showCheckIn(elem, ID); + const progress = `${state.rankFetchCount}/${itemsList.length}`; + updateButton.textContent = `更新中... (${progress})`; - if (collectStatus[ID] && !update) { - displayCollect(collectStatus[ID], elem); - } else { - fetchList1.push(elem); - } - } + if (state.rankFetchCount === itemsList.length) { + updateButton.textContent = "更新完毕!"; + state.isUpdating = false; + updateButton.style.opacity = "1"; + updateButton.style.pointerEvents = "auto"; - let rankFetchIndex = 0, - collectFetchIndex = 0; - let getitemsList = setInterval(() => { - let elem = fetchList[rankFetchIndex]; - if (!elem) { - // console.log(rankFetchIndex); - } else { - let { href } = elem.querySelector("a.avatar"); - showRank(href, elem); - rankFetchIndex++; - //console.log(rankFetchIndex); - } - if (count >= itemsList.length) { - clearInterval(getitemsList); - } - }, 500); - let getitemsList1 = setInterval(() => { - let elem = fetchList1[collectFetchIndex]; - if (!elem) { - // console.log(collectFetchIndex); + setTimeout(() => { + updateButton.textContent = "更新排名"; + }, 1000); + } + }; + + // 处理主列表项目 + const processMainListItems = (isUpdating) => { + const rankFetchList = []; + const collectFetchList = []; + + for (const elem of itemsList) { + const { href } = elem.querySelector("a.avatar"); + const subjectId = href.split("/subject/")[1]; + + getCachedData(subjectId, (success, result) => { + if (success && !isUpdating) { + renderRankBadge(result.rank, elem); } else { - let { href } = elem.querySelector("a.avatar"); - let href1 = href.replace(/subject/, "update"); - showCollect(href1, elem); - collectFetchIndex++; - //console.log(collectFetchIndex); - } - if (count1 >= itemsList.length) { - clearInterval(getitemsList1); + rankFetchList.push(elem); } - }, 500); + }); + + addCollectToggleButton(elem, subjectId); + + if (state.collectStatus[subjectId] && !isUpdating) { + renderCollectStatus(state.collectStatus[subjectId], elem); + } else { + collectFetchList.push(elem); + } } - if (volumeSubjects.length) { - for (const elem of volumeSubjects) { - let { href } = elem.querySelector("a"); - let ID = href.split("/subject/")[1]; - if (collectStatus[ID]) { - displayCollect(collectStatus[ID], elem); - } else if (collectStatus[ID] != "collect") { - showCheckIn(elem, ID); - } + let rankFetchIndex = 0; + let collectFetchIndex = 0; + + const rankFetchInterval = setInterval(() => { + const elem = rankFetchList[rankFetchIndex]; + if (elem) { + const { href } = elem.querySelector("a.avatar"); + fetchRankData(href, elem); + rankFetchIndex++; + } + if (state.rankFetchCount >= itemsList.length) { + clearInterval(rankFetchInterval); + } + }, CONSTANTS.FETCH_INTERVAL); + + const collectFetchInterval = setInterval(() => { + const elem = collectFetchList[collectFetchIndex]; + if (elem) { + const { href } = elem.querySelector("a.avatar"); + const collectHref = href.replace(/subject/, "update"); + fetchCollectData(collectHref, elem); + collectFetchIndex++; + } + if (state.collectFetchCount >= itemsList.length) { + clearInterval(collectFetchInterval); + } + }, CONSTANTS.FETCH_INTERVAL); + + for (const elem of volumeSubjects) { + const { href } = elem.querySelector("a"); + const subjectId = href.split("/subject/")[1]; + + addCollectToggleButton(elem, subjectId); + + if (state.collectStatus[subjectId]) { + renderCollectStatus(state.collectStatus[subjectId], elem); } } - let thisItem = window.location.href.replace(/subject/, "update"); + const thisItem = window.location.href.replace(/subject/, "update"); fetch(thisItem) - .then((data) => { - return new Promise((resovle, reject) => { - let targetStr = data.text(); - resovle(targetStr); - }); - }) + .then((data) => data.text()) .then((targetStr) => { - let Match = targetStr.match( + const match = targetStr.match( /"GenInterestBox\('(\S+?)'\)" checked="checked"/ ); - let interest = Match ? Match[1] : null; - let ID = thisItem.split("/update/")[1]; + const interest = match ? match[1] : null; + const subjectId = thisItem.split("/update/")[1]; if (interest) { - collectStatus[ID] = interest; - } else if (collectStatus[ID]) { - delete collectStatus[ID]; + state.collectStatus[subjectId] = interest; + } else if (state.collectStatus[subjectId]) { + delete state.collectStatus[subjectId]; } - localStorage.setItem( - "bangumi_subject_collectStatus", - JSON.stringify(collectStatus) - ); + saveCollectStatus(); + }) + .catch((error) => { + console.error("Error fetching current item status:", error); }); }; - const updateInfo = () => { - if (isUpdating) { + // 更新条目信息 + const updateSubjectInfo = () => { + if (state.isUpdating) { return; } - count = 0; - update = 1; - isUpdating = true; + state.rankFetchCount = 0; + state.isUpdating = true; updateBtn.style.opacity = "0.5"; updateBtn.style.pointerEvents = "none"; - getInfo(update); + processMainListItems(true); }; - let collectStatus, - securitycode, - privacy, - update = 0, - count = 0, - count1 = 0, - flag = 0; + let updateBtn; - let relatedSubjects = document.querySelectorAll( - "#columnSubjectHomeB ul.browserCoverMedium li:not(:has(a.thumbTipSmall))" - ); - let recommendedSubjects = document.querySelectorAll( - "#columnSubjectHomeB ul.coversSmall li" - ); - let volumeSubjects = document.querySelectorAll( - "#columnSubjectHomeB ul.browserCoverMedium li:has(a.thumbTipSmall)" - ); + // DOM 选择器 + const selectors = { + related: + "#columnSubjectHomeB ul.browserCoverMedium li:not(:has(a.thumbTipSmall))", + recommended: "#columnSubjectHomeB ul.coversSmall li", + volume: "#columnSubjectHomeB ul.browserCoverMedium li:has(a.thumbTipSmall)", + logoutLinks: "#badgeUserPanel a", + }; - let itemsList = [...relatedSubjects, ...recommendedSubjects]; + const relatedSubjects = document.querySelectorAll(selectors.related); + const recommendedSubjects = document.querySelectorAll(selectors.recommended); + const volumeSubjects = document.querySelectorAll(selectors.volume); - collectStatus = JSON.parse( - localStorage.getItem("bangumi_subject_collectStatus") || "{}" + const itemsList = [...relatedSubjects, ...recommendedSubjects]; + + // 初始化收藏状态 + state.collectStatus = JSON.parse( + localStorage.getItem(CONSTANTS.COLLECT_STATUS_KEY) || "{}" ); - let badgeUserPanel = document.querySelectorAll("#badgeUserPanel a"); - for (const elem of badgeUserPanel) { - if (elem.href.match(/logout/)) { - securitycode = elem.href.split("/logout/")[1].toString(); + // 获取安全码 + const logoutLinks = document.querySelectorAll(selectors.logoutLinks); + for (const link of logoutLinks) { + if (link.href.includes("/logout/")) { + state.securityCode = link.href.split("/logout/")[1]; + break; } } - // 更新排名数据 - let isUpdating = false; - - const updateBtn = createElement("a", "chiiBtn", "javascript:;", "更新排名"); + // 初始化更新按钮 + updateBtn = createElement( + "a", + "chiiBtn update-btn", + "javascript:;", + "更新排名" + ); updateBtn.style.margin = "10px 0"; updateBtn.addEventListener("click", () => { - if (isUpdating) { - return; + if (!state.isUpdating) { + updateSubjectInfo(); } - - updateInfo(); }); - if (volumeSubjects.length) { - document - .querySelectorAll("#columnSubjectHomeB .subject_section .clearit")[1] - .append(updateBtn); - } else { - document - .querySelectorAll("#columnSubjectHomeB .subject_section .clearit")[0] - .append(updateBtn); + // 添加更新按钮到页面 + if (itemsList.length > 0) { + const clearitElements = document.querySelectorAll( + "#columnSubjectHomeB .subject_section .clearit" + ); + const targetElement = volumeSubjects.length + ? clearitElements[1] + : clearitElements[0]; + targetElement?.append(updateBtn); } - getInfo(update); + // 初始化加载数据 + processMainListItems(false); + // 初始化单行本控制面板 if (volumeSubjects.length) { - let mangaControlPanel = document.createElement("div"); + const mangaControlPanel = document.createElement("div"); mangaControlPanel.className = "manga-control-panel"; - let privateLabel = document.createElement("label"); + // 私密收藏复选框 + const privateLabel = document.createElement("label"); privateLabel.style.cssText = "margin-right: 15px; cursor: pointer;"; - let checkbox = document.createElement("input"); + const checkbox = document.createElement("input"); checkbox.type = "checkbox"; checkbox.style.marginRight = "5px"; - privateLabel.appendChild(checkbox); - privateLabel.appendChild(document.createTextNode("私密收藏")); + checkbox.addEventListener("click", () => { + state.privacy = checkbox.checked ? 1 : 0; + }); + privateLabel.append(checkbox, document.createTextNode("私密收藏")); - checkbox.onclick = () => { - privacy = checkbox.checked ? 1 : 0; - }; + // 检查是否全部已收藏 + const allCollected = Array.from(volumeSubjects).every((element) => { + const { href } = element.querySelector("a.avatar"); + const subjectId = href.split("/subject/")[1]; + return state.collectStatus[subjectId] === "collect"; + }); + + state.isAllCollected = allCollected; - let allCollect = createElement( + const allCollectButton = createElement( "a", "chiiBtn", "javascript:;", - "全部标为已读" + state.isAllCollected ? "全部取消已读" : "全部标为已读" ); - allCollect.onclick = () => { - if (!confirm(`确定要${allCollect.textContent}吗?`)) { + // 全部收藏/取消收藏事件 + allCollectButton.addEventListener("click", () => { + if (!confirm(`确定要${allCollectButton.textContent}吗?`)) { return; } + state.isAllCollected = !state.isAllCollected; + allCollectButton.textContent = state.isAllCollected + ? "全部取消已读" + : "全部标为已读"; + let volumeIndex = 0; - flag = flag == 1 ? 0 : 1; - allCollect.textContent = flag == 1 ? "全部取消已读" : "全部标为已读"; - - let getVolumeSubjects = setInterval(() => { - let elem = volumeSubjects[volumeIndex]; - let { href } = elem.querySelector("a.avatar"); - let ID = href.split("/subject/")[1]; - let avatarNeue = elem.querySelector("span.avatarNeue"); - - if (flag) { - collectStatus[ID] = "collect"; - avatarNeue.classList.add("collect-status", "collect-status--collect"); - fetch(`/subject/${ID}/interest/update?gh=${securitycode}`, { - method: "POST", - body: new URLSearchParams({ - status: "collect", - privacy: privacy, - }), - }); - } else { - delete collectStatus[ID]; - avatarNeue.classList.remove( - "collect-status", - "collect-status--collect" + const volumeFetchInterval = setInterval(() => { + if (volumeIndex >= volumeSubjects.length) { + clearInterval(volumeFetchInterval); + return; + } + + const element = volumeSubjects[volumeIndex]; + const { href } = element.querySelector("a.avatar"); + const subjectId = href.split("/subject/")[1]; + const avatarNeue = element.querySelector("span.avatarNeue"); + const statusClass = "collect-status--collect"; + + if (state.isAllCollected) { + state.collectStatus[subjectId] = "collect"; + avatarNeue.classList.add("collect-status", statusClass); + + fetch( + `/subject/${subjectId}/interest/update?gh=${state.securityCode}`, + { + method: "POST", + body: new URLSearchParams({ + status: "collect", + privacy: state.privacy, + }), + } ); - fetch(`/subject/${ID}/remove?gh=${securitycode}`, { + } else { + delete state.collectStatus[subjectId]; + avatarNeue.classList.remove("collect-status", statusClass); + + fetch(`/subject/${subjectId}/remove?gh=${state.securityCode}`, { method: "POST", }); } volumeIndex++; - localStorage.setItem( - "bangumi_subject_collectStatus", - JSON.stringify(collectStatus) - ); - if (volumeIndex >= volumeSubjects.length) { - clearInterval(getVolumeSubjects); - } - }, 300); - }; + saveCollectStatus(); + }, CONSTANTS.FETCH_INTERVAL); + }); - mangaControlPanel.appendChild(privateLabel); - mangaControlPanel.appendChild(allCollect); + // 组装控制面板 + mangaControlPanel.append(privateLabel, allCollectButton); const clearitElement = document.querySelector( "#columnSubjectHomeB .subject_section .clearit" ); - clearitElement.parentNode.insertBefore(mangaControlPanel, clearitElement); + clearitElement?.parentNode.insertBefore(mangaControlPanel, clearitElement); } })(); From 92986d9f4622106bd31d4324aea63f849ac4f61b Mon Sep 17 00:00:00 2001 From: NekoAria Date: Mon, 8 Sep 2025 21:19:54 +0800 Subject: [PATCH 12/14] fix(compatibility): update CSS selectors after site redesign --- .../bangumi_related_subject_enhance.user.js | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/liaune/bangumi_related_subject_enhance.user.js b/liaune/bangumi_related_subject_enhance.user.js index 4ac1f1bb..fc988e99 100644 --- a/liaune/bangumi_related_subject_enhance.user.js +++ b/liaune/bangumi_related_subject_enhance.user.js @@ -1,7 +1,7 @@ // ==UserScript== // @name Bangumi Related Subject Enhance // @namespace https://github.com/bangumi/scripts/liaune -// @version 0.6.5 +// @version 0.6.6 // @description 显示条目页面关联条目的收藏情况,显示关联条目的排名,单行本设为全部已读/取消全部已读 // @author Liaune // @include /^https?:\/\/((bangumi|bgm)\.tv|chii.in)\/subject\/\d+$/ @@ -216,7 +216,7 @@ height: 100%; const toggleButton = createElement("a", "collect-toggle", "javascript:;"); let isCollected = state.collectStatus[subjectId] === "collect"; - const avatarNeue = element.querySelector("span.avatarNeue"); + const coverNeue = element.querySelector("span.coverNeue"); toggleButton.style.backgroundPosition = isCollected ? "bottom left" @@ -224,7 +224,7 @@ height: 100%; toggleButton.addEventListener("click", () => { isCollected = !isCollected; - updateCollectStatus(subjectId, isCollected, avatarNeue, toggleButton); + updateCollectStatus(subjectId, isCollected, coverNeue, toggleButton); }); element.querySelector("a.avatar").append(toggleButton); @@ -234,7 +234,7 @@ height: 100%; const updateCollectStatus = ( subjectId, isCollected, - avatarNeue, + coverNeue, toggleButton ) => { const statusClass = "collect-status--collect"; @@ -242,7 +242,7 @@ height: 100%; if (isCollected) { toggleButton.style.backgroundPosition = "bottom left"; state.collectStatus[subjectId] = "collect"; - avatarNeue.classList.add("collect-status", statusClass); + coverNeue.classList.add("collect-status", statusClass); fetch(`/subject/${subjectId}/interest/update?gh=${state.securityCode}`, { method: "POST", @@ -254,7 +254,7 @@ height: 100%; } else { toggleButton.style.backgroundPosition = "top left"; delete state.collectStatus[subjectId]; - avatarNeue.classList.remove("collect-status", statusClass); + coverNeue.classList.remove("collect-status", statusClass); fetch(`/subject/${subjectId}/remove?gh=${state.securityCode}`, { method: "POST", @@ -283,8 +283,8 @@ height: 100%; const statusClass = statusMap[interest]; if (statusClass) { - const avatarNeue = element.querySelector("span.avatarNeue"); - avatarNeue.classList.add("collect-status", statusClass); + const coverNeue = element.querySelector("span.coverNeue"); + coverNeue.classList.add("collect-status", statusClass); } state.collectFetchCount++; }; @@ -630,12 +630,12 @@ height: 100%; const element = volumeSubjects[volumeIndex]; const { href } = element.querySelector("a.avatar"); const subjectId = href.split("/subject/")[1]; - const avatarNeue = element.querySelector("span.avatarNeue"); + const coverNeue = element.querySelector("span.coverNeue"); const statusClass = "collect-status--collect"; if (state.isAllCollected) { state.collectStatus[subjectId] = "collect"; - avatarNeue.classList.add("collect-status", statusClass); + coverNeue.classList.add("collect-status", statusClass); fetch( `/subject/${subjectId}/interest/update?gh=${state.securityCode}`, @@ -649,7 +649,7 @@ height: 100%; ); } else { delete state.collectStatus[subjectId]; - avatarNeue.classList.remove("collect-status", statusClass); + coverNeue.classList.remove("collect-status", statusClass); fetch(`/subject/${subjectId}/remove?gh=${state.securityCode}`, { method: "POST", From 53352c42b6a581fc165832592ed298f2ea6a91dd Mon Sep 17 00:00:00 2001 From: NekoAria Date: Wed, 10 Sep 2025 19:45:45 +0800 Subject: [PATCH 13/14] fix(compatibility): update CSS selectors and add guest detection after site redesign --- .../bangumi_related_subject_enhance.user.js | 37 +++++++++++-------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/liaune/bangumi_related_subject_enhance.user.js b/liaune/bangumi_related_subject_enhance.user.js index fc988e99..daafc152 100644 --- a/liaune/bangumi_related_subject_enhance.user.js +++ b/liaune/bangumi_related_subject_enhance.user.js @@ -1,7 +1,7 @@ // ==UserScript== // @name Bangumi Related Subject Enhance // @namespace https://github.com/bangumi/scripts/liaune -// @version 0.6.6 +// @version 0.6.7 // @description 显示条目页面关联条目的收藏情况,显示关联条目的排名,单行本设为全部已读/取消全部已读 // @author Liaune // @include /^https?:\/\/((bangumi|bgm)\.tv|chii.in)\/subject\/\d+$/ @@ -208,9 +208,11 @@ height: 100%; return element; }; + const isGuest = document.querySelector("div.guest"); + // 添加收藏切换按钮 const addCollectToggleButton = (element, subjectId) => { - if (element.querySelector("a.collect-toggle")) { + if (isGuest || element.querySelector("a.collect-toggle")) { return; } @@ -334,7 +336,7 @@ height: 100%; } }) .catch((error) => { - console.error("Error fetching collect data:", error); + console.error(`Error fetching collect data: ${url}`, error); state.collectFetchCount++; }); }; @@ -524,7 +526,7 @@ height: 100%; // DOM 选择器 const selectors = { related: - "#columnSubjectHomeB ul.browserCoverMedium li:not(:has(a.thumbTipSmall))", + "#columnSubjectHomeB ul.browserCoverMedium:not(.crtList) li:not(:has(a.thumbTipSmall))", recommended: "#columnSubjectHomeB ul.coversSmall li", volume: "#columnSubjectHomeB ul.browserCoverMedium li:has(a.thumbTipSmall)", logoutLinks: "#badgeUserPanel a", @@ -566,20 +568,24 @@ height: 100%; // 添加更新按钮到页面 if (itemsList.length > 0) { - const clearitElements = document.querySelectorAll( - "#columnSubjectHomeB .subject_section .clearit" - ); - const targetElement = volumeSubjects.length - ? clearitElements[1] - : clearitElements[0]; - targetElement?.append(updateBtn); + const relatedSubjectSection = Array.from( + document.querySelectorAll("#columnSubjectHomeB .subject_section .clearit") + ).find((el) => { + const subtitleText = el.querySelector("h2.subtitle")?.textContent; + return ( + subtitleText && + (subtitleText.includes("关联条目") || + subtitleText.includes("大概会喜欢")) + ); + }); + relatedSubjectSection?.append(updateBtn); } // 初始化加载数据 processMainListItems(false); // 初始化单行本控制面板 - if (volumeSubjects.length) { + if (!isGuest && volumeSubjects.length > 0) { const mangaControlPanel = document.createElement("div"); mangaControlPanel.className = "manga-control-panel"; @@ -664,9 +670,8 @@ height: 100%; // 组装控制面板 mangaControlPanel.append(privateLabel, allCollectButton); - const clearitElement = document.querySelector( - "#columnSubjectHomeB .subject_section .clearit" - ); - clearitElement?.parentNode.insertBefore(mangaControlPanel, clearitElement); + const volumeListContainer = volumeSubjects[0].parentElement; + const volumeSectionContainer = volumeListContainer.parentElement; + volumeSectionContainer.insertBefore(mangaControlPanel, volumeListContainer); } })(); From 31a39605faf40c94b26c7c62cb546a0a9b0111d0 Mon Sep 17 00:00:00 2001 From: NekoAria Date: Tue, 14 Oct 2025 01:02:04 +0800 Subject: [PATCH 14/14] fix(style): increase opacity of collect toggle for better visibility --- liaune/bangumi_related_subject_enhance.user.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/liaune/bangumi_related_subject_enhance.user.js b/liaune/bangumi_related_subject_enhance.user.js index daafc152..9279a76c 100644 --- a/liaune/bangumi_related_subject_enhance.user.js +++ b/liaune/bangumi_related_subject_enhance.user.js @@ -1,7 +1,7 @@ // ==UserScript== // @name Bangumi Related Subject Enhance // @namespace https://github.com/bangumi/scripts/liaune -// @version 0.6.7 +// @version 0.6.8 // @description 显示条目页面关联条目的收藏情况,显示关联条目的排名,单行本设为全部已读/取消全部已读 // @author Liaune // @include /^https?:\/\/((bangumi|bgm)\.tv|chii.in)\/subject\/\d+$/ @@ -54,7 +54,7 @@ border-color: #5a5855; .collect-toggle { display: block; top: -20px; -opacity: 0.5; +opacity: 0.8; position: relative; padding: 0 5px; width: 16px;