您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
在演员列表页,显示每部影片是否已看,就难得点进去看了
// ==UserScript== // @name JavDB列表页显示是否已看 // @namespace http://tampermonkey.net/ // @version 2025.06.28.0325 // @description 在演员列表页,显示每部影片是否已看,就难得点进去看了 // @author Ryen // @match https://javdb.com/* // @icon https://www.google.com/s2/favicons?sz=64&domain=javdb.com // @grant GM_xmlhttpRequest // @grant GM_setValue // @grant GM_getValue // @grant GM_deleteValue // @grant GM_listValues // @license GPL-3.0-only // ==/UserScript== // --- Script Configuration --- const CONFIG = { DEBUG: false, PERFORMANCE_MODE: true, // 性能日志开关 - 设置为 true 可以显示加载时间 VERSION: '2025.06.28.0325', // --- UI & Animation --- MESSAGE_FADE_DURATION: 500, // 消息渐变持续时间 (ms) MESSAGE_DISPLAY_DURATION: 3000, // 消息显示持续时间 (ms) MAX_MESSAGES: 3, // 最大显示消息数量 CIRCLE_BUTTON_POSITION: { left: -40, top: 60 }, // 悬浮按钮位置 // --- Feature Toggles & Storage Keys --- HIDE_WATCHED_VIDEOS_KEY: 'hideWatchedVideos', HIDE_VIEWED_VIDEOS_KEY: 'hideViewedVideos', HIDE_VR_VIDEOS_KEY: 'hideVRVideos', STORED_IDS_KEY: 'myIds', BROWSE_HISTORY_KEY: 'videoBrowseHistory', LAST_UPLOAD_TIME_KEY: 'lastUploadTime', // --- Data Export --- VIDEOS_PER_PAGE: 20, // 列表页每页视频数量 EXPORT_PAUSE_DELAY: 3000, // 导出时翻页暂停时间 (ms) // --- Content Processing --- NEW_ITEM_CHECK_INTERVAL: 1000, // 检查新加载项目的间隔 (ms) VIDEO_ID_RECORD_DELAY_MIN: 3, // 记录番号前最小延迟 (s) VIDEO_ID_RECORD_DELAY_MAX: 5, // 记录番号前最大延迟 (s) VIDEO_ID_RECORD_RETRIES: 5, // 记录番号失败后重试次数 VIDEO_ID_RECORD_RETRY_DELAY: 3000, // 记录番号重试前的等待时间 (ms) // --- Video Status Indicator --- STATUS_CHECK_INTERVAL: 2000, // 详情页检查状态间隔 (ms) STATUS_WATCHED_COLOR: '#2ed573', // 已看/已浏览状态颜色 STATUS_DEFAULT_COLOR: '#ed0085', // 默认状态颜色 CUSTOM_FAVICON_URL: 'https://res.cloudinary.com/djsqosyyr/image/upload/v1749926977/picgo/d43f78db1a3c01c621580b4a55b06bd7.png', // 已看/已浏览状态的图标 }; // --- End of Script Configuration --- // 调试开关 const DEBUG = CONFIG.DEBUG; // 版本 const VERSION = CONFIG.VERSION; const fadeDuration = CONFIG.MESSAGE_FADE_DURATION; const displayDuration = CONFIG.MESSAGE_DISPLAY_DURATION; const maxMessages = CONFIG.MAX_MESSAGES; let counter = 0; // 初始化计数器 let lastItemCount = 0; // 存储上一次的电影项目数量 let storedIds = new Set(); // 使用 Set 存储唯一 ID const styleMap = { '我看過這部影片': 'tag is-success is-light', '我想看這部影片': 'tag is-info is-light', '未看过': 'tag is-gray', '已浏览': 'tag is-warning is-light', }; // 在这里定义 hideWatchedVideos let hideWatchedVideos = GM_getValue(CONFIG.HIDE_WATCHED_VIDEOS_KEY, false); console.log('初始化 hideWatchedVideos:', hideWatchedVideos); // 在 styleMap 下面添加新的变量 let hideViewedVideos = GM_getValue(CONFIG.HIDE_VIEWED_VIDEOS_KEY, false); console.log('初始化 hideViewedVideos:', hideViewedVideos); // 在 styleMap 下面添加新的变量 let hideVRVideos = GM_getValue(CONFIG.HIDE_VR_VIDEOS_KEY, false); console.log('初始化 hideVRVideos:', hideVRVideos); const indicatorTexts = ['我看過這部影片', '我想看這部影片']; const validUrlPatterns = [ /https:\/\/javdb\.com\/users\/want_watch_videos.*/, /https:\/\/javdb\.com\/users\/watched_videos.*/, /https:\/\/javdb\.com\/users\/list_detail.*/, /https:\/\/javdb\.com\/lists.*/ ]; // 消息容器 const messageContainer = document.createElement('div'); messageContainer.style.position = 'fixed'; messageContainer.style.bottom = '20px'; messageContainer.style.right = '20px'; messageContainer.style.zIndex = '9999'; messageContainer.style.pointerEvents = 'none'; messageContainer.style.maxWidth = '500px'; messageContainer.style.display = 'flex'; messageContainer.style.flexDirection = 'column'; document.body.appendChild(messageContainer); // 渐入效果 function fadeIn(el) { el.style.opacity = 0; el.style.display = 'block'; const startTime = performance.now(); function animate(time) { const elapsed = time - startTime; el.style.opacity = Math.min((elapsed / fadeDuration), 1); if (elapsed < fadeDuration) { requestAnimationFrame(animate); } } requestAnimationFrame(animate); } // 渐出效果 function fadeOut(el) { const startTime = performance.now(); function animate(time) { const elapsed = time - startTime; el.style.opacity = 1 - Math.min((elapsed / fadeDuration), 1); if (elapsed < fadeDuration) { requestAnimationFrame(animate); } else { el.remove(); } } requestAnimationFrame(animate); } // 显示信息 function logToScreen(message, bgColor = 'rgba(169, 169, 169, 0.8)', textColor = 'white') { const messageBox = document.createElement('div'); messageBox.style.padding = '10px'; messageBox.style.borderRadius = '5px'; messageBox.style.backgroundColor = bgColor; messageBox.style.color = textColor; messageBox.style.fontSize = '12px'; messageBox.style.marginBottom = '10px'; messageBox.style.pointerEvents = 'none'; messageBox.style.wordWrap = 'break-word'; messageBox.style.maxWidth = '100%'; messageBox.innerHTML = message; messageContainer.appendChild(messageBox); fadeIn(messageBox); setTimeout(() => { fadeOut(messageBox); }, displayDuration); if (messageContainer.childElementCount > maxMessages) { fadeOut(messageContainer.firstChild); } } async function sleep(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); } // 创建调试日志函数 function debugLog(...args) { if (DEBUG) { console.log(...args); } } // 添加全局变量声明 let exportButton = null; let stopButton = null; let isExporting = false; let exportState = { allowExport: false, currentPage: 1, maxPage: null }; let uploadTimeDisplay; let idCountDisplay; // 将 updateCountDisplay 函数移到全局作用域 function updateCountDisplay() { if (!idCountDisplay) { return; // 如果 idCountDisplay 还没有初始化,直接返回 } const watchedCount = storedIds.size; const browseHistory = new Set(GM_getValue(CONFIG.BROWSE_HISTORY_KEY, [])); const browseCount = browseHistory.size; idCountDisplay.innerHTML = ` <div style="margin-bottom: 5px;">已看番号总数: ${watchedCount}</div> <div>已浏览番号总数: ${browseCount}</div> `; } (function () { 'use strict'; debugLog('开始初始化面板...'); const isSearchPage = window.location.href.startsWith('https://javdb.com/search?'); let panelVisible = false; const circlePosition = CONFIG.CIRCLE_BUTTON_POSITION; let lastUploadTime = ""; // 面板样式优化 const panel = document.createElement('div'); debugLog('创建面板元素'); panel.style.position = 'fixed'; panel.style.border = 'none'; panel.style.backgroundColor = 'rgba(255, 255, 255, 0.98)'; panel.style.padding = '20px'; panel.style.boxShadow = '0 4px 12px rgba(0, 0, 0, 0.15)'; panel.style.maxWidth = '340px'; panel.style.width = '90vw'; panel.style.maxHeight = '90vh'; panel.style.overflowY = 'auto'; panel.style.overflowX = 'hidden'; panel.style.borderRadius = '12px'; panel.style.display = 'none'; panel.style.zIndex = 10001; panel.style.fontFamily = '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif'; // 确保面板被添加到文档中 document.body.appendChild(panel); debugLog('面板已添加到文档中'); // 添加滚动条样式 panel.style.scrollbarWidth = 'thin'; // Firefox panel.style.scrollbarColor = '#ccc transparent'; // Firefox // Webkit浏览器的滚动条样式 const styleSheet = document.createElement('style'); styleSheet.textContent = ` #${panel.id}::-webkit-scrollbar { width: 6px; } #${panel.id}::-webkit-scrollbar-track { background: transparent; } #${panel.id}::-webkit-scrollbar-thumb { background-color: #ccc; border-radius: 3px; } `; document.head.appendChild(styleSheet); // 修改面板位置计算 function updatePanelPosition(panel, top) { debugLog('更新面板位置'); const windowHeight = window.innerHeight; const panelHeight = panel.offsetHeight; debugLog('窗口高度:', windowHeight, '面板高度:', panelHeight); let finalTop = top; if (top + panelHeight > windowHeight - 20) { finalTop = Math.max(20, windowHeight - panelHeight - 20); } finalTop = Math.max(20, finalTop); panel.style.top = `${finalTop}px`; panel.style.left = '10px'; debugLog('面板最终位置:', finalTop); } // 创建标题 const title = document.createElement('div'); title.textContent = '番号数据上传与搜索'; title.style.display = 'flex'; title.style.alignItems = 'center'; title.style.fontWeight = '600'; title.style.fontSize = '16px'; title.style.marginBottom = '15px'; title.style.color = '#333'; title.style.borderBottom = '1px solid #eee'; title.style.paddingBottom = '10px'; debugLog('创建标题'); // 说明按钮 const helpButton = document.createElement('span'); helpButton.textContent = '❓'; helpButton.style.cursor = 'pointer'; helpButton.style.marginLeft = '10px'; helpButton.style.fontSize = '16px'; helpButton.title = '帮助与说明'; title.appendChild(helpButton); // 帮助符号 /* const titleHelpIcon = document.createElement('span'); titleHelpIcon.textContent = 'ℹ️'; titleHelpIcon.style.cursor = 'pointer'; titleHelpIcon.style.marginLeft = '10px'; titleHelpIcon.title = '目前只过滤"看过",更新脚本数据会被清空'; title.appendChild(titleHelpIcon); */ // 创建一个容器来包含上传按钮和帮助图标 const uploadContainer = document.createElement('div'); uploadContainer.style.cssText = ` position: relative; margin-bottom: 15px; display: flex; align-items: center; gap: 8px; width: 100%; `; // 创建自定义的文件上传按钮 const customUploadButton = document.createElement('label'); customUploadButton.style.cssText = ` display: inline-flex; align-items: center; padding: 8px 16px; background-color: #4a9eff; color: white; border-radius: 6px; cursor: pointer; font-size: 14px; font-weight: 500; transition: all 0.3s ease; border: none; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); `; // 添加图标 const uploadIcon = document.createElement('span'); uploadIcon.innerHTML = '📁'; // 使用 emoji 作为图标 uploadIcon.style.marginRight = '8px'; customUploadButton.appendChild(uploadIcon); // 添加文本 const buttonText = document.createElement('span'); buttonText.textContent = '上传已看番号JSON'; customUploadButton.appendChild(buttonText); // 创建实际的文件输入框但隐藏它 const uploadButton = document.createElement('input'); uploadButton.type = 'file'; uploadButton.accept = '.json'; uploadButton.style.cssText = ` position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); border: 0; `; // 添加文件名显示区域 const fileNameDisplay = document.createElement('span'); fileNameDisplay.style.cssText = ` margin-left: 10px; color: #666; font-size: 14px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 200px; `; // 帮助图标 const uploadHelpIcon = document.createElement('a'); uploadHelpIcon.href = 'https://javdb.com/users/watched_videos'; uploadHelpIcon.target = '_blank'; uploadHelpIcon.textContent = 'ℹ️'; uploadHelpIcon.style.cssText = ` cursor: pointer; padding: 4px; flex-shrink: 0; text-decoration: none; color: inherit; `; uploadHelpIcon.title = '点击前往"看过"页面进行导出文件 (在新标签页中打开)'; // 将所有元素添加到容器中 customUploadButton.appendChild(uploadButton); uploadContainer.appendChild(customUploadButton); uploadContainer.appendChild(fileNameDisplay); uploadContainer.appendChild(uploadHelpIcon); // 添加悬停效果 customUploadButton.addEventListener('mouseover', function() { this.style.backgroundColor = '#3d8ae5'; this.style.transform = 'translateY(-1px)'; }); customUploadButton.addEventListener('mouseout', function() { this.style.backgroundColor = '#4a9eff'; this.style.transform = 'translateY(0)'; }); // 更新文件名显示 uploadButton.addEventListener('change', function() { if (this.files.length > 0) { fileNameDisplay.textContent = this.files[0].name; } else { fileNameDisplay.textContent = ''; } }); // 开关按钮样式优化 const toggleContainer = document.createElement('div'); toggleContainer.style.display = 'flex'; toggleContainer.style.alignItems = 'center'; toggleContainer.style.gap = '8px'; toggleContainer.style.marginTop = '10px'; toggleContainer.style.marginBottom = '15px'; // 优化按钮样式的函数 function styleButton(button, isActive) { button.style.flex = '1'; button.style.padding = '8px 12px'; button.style.color = 'white'; button.style.border = 'none'; button.style.borderRadius = '6px'; button.style.cursor = 'pointer'; button.style.transition = 'all 0.3s ease'; button.style.backgroundColor = isActive ? '#808080' : '#2ed573'; // 修改为:隐藏为灰色(#808080),显示为绿色 } // 修改开关按钮样式优化和事件处理 const toggleHideButton = document.createElement('button'); styleButton(toggleHideButton, hideWatchedVideos); toggleHideButton.textContent = hideWatchedVideos ? '当前:隐藏已看的番号' : '当前:显示已看的番号'; // 帮助符号 /*const toggleHelpIcon = document.createElement('span'); toggleHelpIcon.textContent = 'ℹ️'; toggleHelpIcon.style.cursor = 'pointer'; toggleHelpIcon.style.padding = '4px'; toggleHelpIcon.title = '点击切换'; */ // 将按钮和帮助符号添加到容器 toggleContainer.appendChild(toggleHideButton); //toggleContainer.appendChild(toggleHelpIcon); // 添加按钮悬停效果 toggleHideButton.addEventListener('mouseover', function () { this.style.opacity = '0.9'; }); toggleHideButton.addEventListener('mouseout', function () { this.style.opacity = '1'; }); // 添加开关按钮点击事件 toggleHideButton.addEventListener('click', function () { debugLog('开关按钮被点击'); hideWatchedVideos = !hideWatchedVideos; debugLog('切换 hideWatchedVideos 为:', hideWatchedVideos); // 更新按钮文本和颜色 this.textContent = hideWatchedVideos ? '当前:隐藏已看的番号' : '当前:显示已看的番号'; styleButton(this, hideWatchedVideos); // 更新按钮样式 // 保存设置 GM_setValue(CONFIG.HIDE_WATCHED_VIDEOS_KEY, hideWatchedVideos); if (!hideWatchedVideos) { // 如果切换到显示模式,自动刷新页面 debugLog('切换到显示模式,准备刷新页面'); location.reload(); } else { // 如果切换到隐藏模式,直接处理当前页面 processLoadedItems(); } }); // 添加开关按钮到面板 panel.appendChild(toggleContainer); // 在创建 toggleContainer 后添加新的容器和按钮 const toggleViewedContainer = document.createElement('div'); toggleViewedContainer.style.display = 'flex'; toggleViewedContainer.style.alignItems = 'center'; toggleViewedContainer.style.gap = '8px'; toggleViewedContainer.style.marginTop = '10px'; toggleViewedContainer.style.marginBottom = '15px'; const toggleViewedButton = document.createElement('button'); styleButton(toggleViewedButton, hideViewedVideos); toggleViewedButton.textContent = hideViewedVideos ? '当前:隐藏已浏览的番号' : '当前:显示已浏览的番号'; /*const toggleViewedHelpIcon = document.createElement('span'); toggleViewedHelpIcon.textContent = 'ℹ️'; toggleViewedHelpIcon.style.cursor = 'pointer'; toggleViewedHelpIcon.style.padding = '4px'; toggleViewedHelpIcon.title = '点击切换'; */ toggleViewedContainer.appendChild(toggleViewedButton); //toggleViewedContainer.appendChild(toggleViewedHelpIcon); // 添加按钮事件 toggleViewedButton.addEventListener('click', function() { hideViewedVideos = !hideViewedVideos; this.textContent = hideViewedVideos ? '当前:隐藏已浏览的番号' : '当前:显示已浏览的番号'; styleButton(this, hideViewedVideos); // 更新按钮样式 GM_setValue(CONFIG.HIDE_VIEWED_VIDEOS_KEY, hideViewedVideos); if (!hideViewedVideos) { location.reload(); } else { processLoadedItems(); } }); // 在 panel.appendChild(toggleContainer) 后添加 panel.appendChild(toggleViewedContainer); // 在面板中添加切换 VR 视频的按钮 const toggleVRContainer = document.createElement('div'); toggleVRContainer.style.display = 'flex'; toggleVRContainer.style.alignItems = 'center'; toggleVRContainer.style.gap = '8px'; toggleVRContainer.style.marginTop = '10px'; toggleVRContainer.style.marginBottom = '15px'; const toggleVRButton = document.createElement('button'); styleButton(toggleVRButton, hideVRVideos); toggleVRButton.textContent = hideVRVideos ? '当前:隐藏VR番号' : '当前:显示VR番号'; // 添加按钮点击事件 toggleVRButton.addEventListener('click', function () { hideVRVideos = !hideVRVideos; this.textContent = hideVRVideos ? '当前:隐藏VR番号' : '当前:显示VR番号'; styleButton(this, hideVRVideos); // 更新按钮样式 GM_setValue(CONFIG.HIDE_VR_VIDEOS_KEY, hideVRVideos); // 切换到显示模式时刷新页面 if (!hideVRVideos) { location.reload(); } else { processLoadedItems(); // 处理当前页面以应用更改 } }); // 添加到面板 toggleVRContainer.appendChild(toggleVRButton); panel.appendChild(toggleVRContainer); // 在搜索页面,禁用切换按钮 if (isSearchPage) { [toggleHideButton, toggleViewedButton, toggleVRButton].forEach(button => { button.disabled = true; button.style.backgroundColor = '#b0b0b0'; // 禁用的灰色 button.textContent = '搜索页禁用'; button.style.cursor = 'not-allowed'; button.title = '此功能在搜索页面禁用,以显示所有结果。'; }); } // 创建按钮容器 const buttonContainer = document.createElement('div'); buttonContainer.style.display = 'flex'; buttonContainer.style.gap = '10px'; buttonContainer.style.marginTop = '15px'; buttonContainer.style.marginBottom = '15px'; debugLog('创建按钮容器'); // 导出按钮样式优化 const exportButton = document.createElement('button'); exportButton.innerHTML = '💾 导出存储番号'; // 添加下载图标 exportButton.style.cssText = ` display: inline-flex; align-items: center; padding: 8px 16px; background-color: #4a9eff; color: white; border-radius: 6px; cursor: pointer; font-size: 14px; font-weight: 500; transition: all 0.3s ease; border: none; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); white-space: nowrap; `; // 清除按钮样式优化 const clearButton = document.createElement('button'); clearButton.innerHTML = '🗑️ 清空存储番号'; // 添加垃圾桶图标 clearButton.style.cssText = ` display: inline-flex; align-items: center; padding: 8px 16px; background-color: #ff4a4a; color: white; border-radius: 6px; cursor: pointer; font-size: 14px; font-weight: 500; transition: all 0.3s ease; border: none; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); white-space: nowrap; `; // 定义导出函数 function exportStoredData() { debugLog('开始导出存储的番号...'); try { const myIds = GM_getValue(CONFIG.STORED_IDS_KEY, []); const videoBrowseHistory = GM_getValue(CONFIG.BROWSE_HISTORY_KEY, []); if (myIds.length === 0 && videoBrowseHistory.length === 0) { logToScreen('没有存储任何数据,已停止导出。', 'rgba(255, 193, 7, 0.8)', 'white'); return; } const dataToExport = { myIds: myIds, videoBrowseHistory: videoBrowseHistory, }; const json = JSON.stringify(dataToExport, null, 2); const jsonBlob = new Blob([json], { type: 'application/json' }); const jsonUrl = URL.createObjectURL(jsonBlob); const downloadLink = document.createElement('a'); const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19); downloadLink.download = `javdb-backup_${timestamp}.json`; downloadLink.href = jsonUrl; document.body.appendChild(downloadLink); downloadLink.click(); document.body.removeChild(downloadLink); URL.revokeObjectURL(jsonUrl); logToScreen('存储的数据已成功导出。', 'rgba(76, 175, 80, 0.8)', 'white'); debugLog('存储的数据导出成功。'); } catch (error) { console.error('导出存储数据时出错:', error); logToScreen('导出存储数据时出错,请查看控制台。', 'rgba(244, 67, 54, 0.8)', 'white'); } } // 定义清除函数 function handleClear(e) { e.preventDefault(); e.stopPropagation(); debugLog('开始清除数据...'); if (confirm('确定要清除所有存储的番号吗?')) { try { // 清除内存中的数据 storedIds = new Set(); // 定义要从 GM 存储中清除的键 const keysToClear = [ CONFIG.STORED_IDS_KEY, CONFIG.LAST_UPLOAD_TIME_KEY, CONFIG.HIDE_WATCHED_VIDEOS_KEY, CONFIG.HIDE_VIEWED_VIDEOS_KEY, CONFIG.HIDE_VR_VIDEOS_KEY, CONFIG.BROWSE_HISTORY_KEY, 'storedIds', // for legacy cleanup ]; // 清除所有 GM 存储 keysToClear.forEach(key => { if (GM_getValue(key) !== undefined) { debugLog('清除 GM 存储:', key); GM_deleteValue(key); } }); // 清除所有相关的 localStorage const localStorageKeys = ['allVideosInfo', 'exportState']; localStorageKeys.forEach(key => { debugLog('清除 localStorage:', key); localStorage.removeItem(key); }); // 重新初始化必要的值 GM_setValue(CONFIG.STORED_IDS_KEY, []); GM_setValue(CONFIG.LAST_UPLOAD_TIME_KEY, ''); lastUploadTime = ''; // 更新显示 idCountDisplay.textContent = `已存储 0 个番号`; uploadTimeDisplay.textContent = ''; debugLog('数据清除完成'); debugLog('当前 storedIds size:', storedIds.size); debugLog('当前 GM storage:', GM_getValue(CONFIG.STORED_IDS_KEY, []).length); // 强制刷新页面 alert('已清除所有存储的番号,页面将刷新'); window.location.href = window.location.href; } catch (error) { console.error('清除数据时出错:', error); alert('清除数据时出错,请查看控制台'); } } return false; } // 添加按钮事件监听器 exportButton.addEventListener('click', exportStoredData); clearButton.addEventListener('click', handleClear); // 修改这段代码 [exportButton, clearButton].forEach(button => { button.addEventListener('mouseover', function() { this.style.transform = 'translateY(-1px)'; if (this === exportButton) { this.style.backgroundColor = '#3d8ae5'; } else { this.style.backgroundColor = '#e54a4a'; } }); button.addEventListener('mouseout', function() { this.style.transform = 'translateY(0)'; if (this === exportButton) { this.style.backgroundColor = '#4a9eff'; } else { this.style.backgroundColor = '#ff4a4a'; } }); // 修改禁用状态的处理方式 const updateDisabledStyle = function() { if (this.disabled) { this.style.opacity = '0.6'; this.style.cursor = 'not-allowed'; } else { this.style.opacity = '1'; this.style.cursor = 'pointer'; } }; // 监听 disabled 属性变化 const observer = new MutationObserver((mutations) => { mutations.forEach((mutation) => { if (mutation.attributeName === 'disabled') { updateDisabledStyle.call(button); } }); }); observer.observe(button, { attributes: true, attributeFilter: ['disabled'] }); // 初始化禁用状态样式 updateDisabledStyle.call(button); }); // 将按钮添加到按钮容器 buttonContainer.appendChild(exportButton); buttonContainer.appendChild(clearButton); debugLog('按钮已添加到容器'); // 创建搜索框和结果容器 const searchContainer = document.createElement('div'); searchContainer.style.marginBottom = '15px'; const searchBox = document.createElement('input'); searchBox.type = 'text'; searchBox.placeholder = '搜索已看的番号'; searchBox.style.width = '100%'; searchBox.style.padding = '8px 12px'; searchBox.style.border = '1px solid #ddd'; searchBox.style.borderRadius = '6px'; searchBox.style.marginBottom = '10px'; searchBox.style.boxSizing = 'border-box'; // 创建搜索结果容器 const resultContainer = document.createElement('div'); resultContainer.style.maxHeight = '200px'; resultContainer.style.overflowY = 'auto'; resultContainer.style.border = '1px solid #eee'; resultContainer.style.borderRadius = '6px'; resultContainer.style.padding = '10px'; resultContainer.style.display = 'none'; resultContainer.style.backgroundColor = '#f9f9f9'; // 搜索处理函数 function handleSearch() { const searchTerm = searchBox.value.trim().toLowerCase(); const storedIdsArray = Array.from(storedIds); if (searchTerm === '') { resultContainer.style.display = 'none'; return; } const results = storedIdsArray.filter(id => id.toLowerCase().includes(searchTerm) ); resultContainer.innerHTML = ''; if (results.length > 0) { results.forEach(id => { const resultItem = document.createElement('div'); resultItem.style.display = 'flex'; resultItem.style.justifyContent = 'space-between'; resultItem.style.alignItems = 'center'; resultItem.style.padding = '5px'; resultItem.style.borderBottom = '1px solid #eee'; const idText = document.createElement('span'); idText.textContent = id; idText.style.cursor = 'pointer'; const deleteBtn = document.createElement('button'); deleteBtn.textContent = '删除'; deleteBtn.style.cssText = ` padding: 2px 8px; background-color: #ff4757; color: white; border: none; border-radius: 3px; cursor: pointer; font-size: 12px; transition: all 0.2s ease; `; deleteBtn.addEventListener('mouseover', () => { deleteBtn.style.opacity = '0.8'; }); deleteBtn.addEventListener('mouseout', () => { deleteBtn.style.opacity = '1'; }); deleteBtn.addEventListener('click', (e) => { e.stopPropagation(); if (confirm(`确定要删除 ${id} 吗?`)) { storedIds.delete(id); GM_setValue(CONFIG.STORED_IDS_KEY, Array.from(storedIds)); resultItem.style.opacity = '0'; setTimeout(() => { resultItem.remove(); if (resultContainer.children.length === 0) { const noResult = document.createElement('div'); noResult.textContent = '未找到匹配的番号'; noResult.style.padding = '5px'; noResult.style.color = '#666'; resultContainer.appendChild(noResult); } updateCountDisplay(); }, 300); logToScreen(`已删除: ${id}`, 'rgba(255, 71, 87, 0.8)', 'white'); } }); resultItem.appendChild(idText); resultItem.appendChild(deleteBtn); resultContainer.appendChild(resultItem); resultItem.addEventListener('mouseover', () => { resultItem.style.backgroundColor = '#eee'; }); resultItem.addEventListener('mouseout', () => { resultItem.style.backgroundColor = 'transparent'; }); }); resultContainer.style.display = 'block'; } else { const noResult = document.createElement('div'); noResult.textContent = '未找到匹配的番号'; noResult.style.padding = '5px'; noResult.style.color = '#666'; resultContainer.appendChild(noResult); resultContainer.style.display = 'block'; } } // 添加搜索事件监听 searchBox.addEventListener('input', handleSearch); // 添加点击外部关闭搜索结果的功能 document.addEventListener('click', (e) => { if (!searchContainer.contains(e.target)) { resultContainer.style.display = 'none'; } }); // 将搜索框和结果容器添加到搜索容器 searchContainer.appendChild(searchBox); searchContainer.appendChild(resultContainer); // 创建ID计数显示 idCountDisplay = document.createElement('div'); idCountDisplay.style.marginTop = '15px'; idCountDisplay.style.color = '#666'; idCountDisplay.style.fontSize = '13px'; idCountDisplay.style.textAlign = 'center'; // 在加载存储的 ID 和上传时间时更新显示 const rawData = GM_getValue(CONFIG.STORED_IDS_KEY); const savedUploadTime = GM_getValue(CONFIG.LAST_UPLOAD_TIME_KEY); if (rawData) { storedIds = new Set(rawData); // 如果之前存过数据,加载到 Set updateCountDisplay(); // 使用新的更新函数 } if (savedUploadTime) { lastUploadTime = savedUploadTime; // 恢复最新上传时间 if (uploadTimeDisplay) { uploadTimeDisplay.textContent = `上次上传时间:${lastUploadTime}`; } } // 创建上传时间显示 uploadTimeDisplay = document.createElement('div'); uploadTimeDisplay.style.marginTop = '5px'; uploadTimeDisplay.style.color = '#666'; uploadTimeDisplay.style.fontSize = '13px'; uploadTimeDisplay.style.textAlign = 'center'; uploadTimeDisplay.textContent = lastUploadTime ? `上次上传时间:${lastUploadTime}` : ''; debugLog('创建上传时间显示'); // 创建浏览记录查询功能 const browseHistoryContainer = document.createElement('div'); browseHistoryContainer.style.marginBottom = '15px'; const browseHistoryBox = document.createElement('input'); browseHistoryBox.type = 'text'; browseHistoryBox.placeholder = '查询浏览记录'; browseHistoryBox.style.width = '100%'; browseHistoryBox.style.padding = '8px 12px'; browseHistoryBox.style.border = '1px solid #ddd'; browseHistoryBox.style.borderRadius = '6px'; browseHistoryBox.style.marginBottom = '10px'; browseHistoryBox.style.boxSizing = 'border-box'; // 创建浏览记录结果容器 const browseHistoryResultContainer = document.createElement('div'); browseHistoryResultContainer.style.maxHeight = '200px'; browseHistoryResultContainer.style.overflowY = 'auto'; browseHistoryResultContainer.style.border = '1px solid #eee'; browseHistoryResultContainer.style.borderRadius = '6px'; browseHistoryResultContainer.style.padding = '10px'; browseHistoryResultContainer.style.display = 'none'; browseHistoryResultContainer.style.backgroundColor = '#f9f9f9'; // 查询处理函数 function handleBrowseHistorySearch() { const searchTerm = browseHistoryBox.value.trim().toLowerCase(); const storedVideoIds = Array.from(new Set(GM_getValue(CONFIG.BROWSE_HISTORY_KEY, []))); if (searchTerm === '') { browseHistoryResultContainer.style.display = 'none'; return; } const results = storedVideoIds.filter(id => id.toLowerCase().includes(searchTerm)); browseHistoryResultContainer.innerHTML = ''; if (results.length > 0) { results.forEach(id => { const resultItem = document.createElement('div'); resultItem.style.display = 'flex'; resultItem.style.justifyContent = 'space-between'; resultItem.style.alignItems = 'center'; resultItem.style.padding = '5px'; resultItem.style.borderBottom = '1px solid #eee'; const idText = document.createElement('span'); idText.textContent = id; idText.style.cursor = 'pointer'; const deleteBtn = document.createElement('button'); deleteBtn.textContent = '删除'; deleteBtn.style.cssText = ` padding: 2px 8px; background-color: #ff4757; color: white; border: none; border-radius: 3px; cursor: pointer; font-size: 12px; transition: all 0.2s ease; `; deleteBtn.addEventListener('mouseover', () => { deleteBtn.style.opacity = '0.8'; }); deleteBtn.addEventListener('mouseout', () => { deleteBtn.style.opacity = '1'; }); deleteBtn.addEventListener('click', (e) => { e.stopPropagation(); if (confirm(`确定要删除浏览记录 ${id} 吗?`)) { const browseHistory = new Set(GM_getValue(CONFIG.BROWSE_HISTORY_KEY, [])); browseHistory.delete(id); GM_setValue(CONFIG.BROWSE_HISTORY_KEY, Array.from(browseHistory)); resultItem.style.opacity = '0'; setTimeout(() => { resultItem.remove(); if (browseHistoryResultContainer.children.length === 0) { const noResult = document.createElement('div'); noResult.textContent = '未找到匹配的浏览记录'; noResult.style.padding = '5px'; noResult.style.color = '#666'; browseHistoryResultContainer.appendChild(noResult); } updateCountDisplay(); }, 300); logToScreen(`已删除浏览记录: ${id}`, 'rgba(255, 71, 87, 0.8)', 'white'); } }); resultItem.appendChild(idText); resultItem.appendChild(deleteBtn); browseHistoryResultContainer.appendChild(resultItem); resultItem.addEventListener('mouseover', () => { resultItem.style.backgroundColor = '#eee'; }); resultItem.addEventListener('mouseout', () => { resultItem.style.backgroundColor = 'transparent'; }); }); browseHistoryResultContainer.style.display = 'block'; } else { const noResult = document.createElement('div'); noResult.textContent = '未找到匹配的浏览记录'; noResult.style.padding = '5px'; noResult.style.color = '#666'; browseHistoryResultContainer.appendChild(noResult); browseHistoryResultContainer.style.display = 'block'; } } // 添加搜索事件监听 browseHistoryBox.addEventListener('input', handleBrowseHistorySearch); // 将浏览记录查询框和结果容器添加到面板 browseHistoryContainer.appendChild(browseHistoryBox); browseHistoryContainer.appendChild(browseHistoryResultContainer); // 在面板的最后添加版本和作者信息 const versionAuthorInfo = document.createElement('div'); versionAuthorInfo.style.cssText = ` margin-top: 15px; padding-top: 10px; border-top: 1px solid #eee; color: #999; font-size: 12px; text-align: center; `; versionAuthorInfo.innerHTML = `Version: ${VERSION}<br>Author: Ryen`; // 将所有元素添加到面板 panel.appendChild(title); panel.appendChild(uploadContainer); panel.appendChild(toggleContainer); panel.appendChild(toggleViewedContainer); panel.appendChild(toggleVRContainer); panel.appendChild(buttonContainer); panel.appendChild(searchContainer); panel.appendChild(browseHistoryContainer); // 添加浏览记录查询功能 panel.appendChild(idCountDisplay); panel.appendChild(uploadTimeDisplay); // 创建帮助面板 const helpPanel = document.createElement('div'); helpPanel.style.display = 'none'; helpPanel.style.position = 'absolute'; helpPanel.style.top = '0'; helpPanel.style.left = '0'; helpPanel.style.width = '100%'; helpPanel.style.height = '100%'; helpPanel.style.backgroundColor = 'rgba(255, 255, 255, 1)'; helpPanel.style.zIndex = '10002'; helpPanel.style.overflowY = 'auto'; helpPanel.style.padding = '20px'; helpPanel.style.boxSizing = 'border-box'; panel.appendChild(helpPanel); const helpContent = ` <div style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; color: #333; font-size: 14px; line-height: 1.6;"> <div style="display: flex; justify-content: space-between; align-items: center; position: sticky; top: -20px; background: white; z-index: 1; padding: 10px 20px; margin: -20px -20px 15px -20px; border-bottom: 1px solid #eee;"> <h2 style="font-size: 18px; font-weight: 600; margin: 0;">功能说明</h2> <span id="closeHelpButton" style="cursor: pointer; font-size: 24px; font-weight: bold; line-height: 1; color: #999;">×</span> </div> <h3 style="font-size: 16px; font-weight: 600; margin-top: 20px; margin-bottom: 10px;">主面板功能</h3> <ul style="padding-left: 20px;"> <li style="margin-bottom: 8px;"><strong>上传番号备份:</strong> 点击选择一个 previously exported <code>javdb-backup_...json</code> 文件进行导入。文件中的"已看"和"已浏览"记录都会被合并到当前脚本的"已看"列表中。同时,文件中的"已浏览"记录也会被合并到脚本的"已浏览"历史中。此操作用于数据恢复和同步。</li> <li style="margin-bottom: 8px;"><strong>隐藏/显示开关:</strong> <ul style="padding-left: 20px; margin-top: 8px;"> <li style="margin-bottom: 5px;"><strong>已看的番号:</strong> 切换是否在列表页隐藏您已标记为"看过"的影片。</li> <li style="margin-bottom: 5px;"><strong>已浏览的番号:</strong> 切换是否隐藏您仅访问过详情页但未标记为"看过"的影片。</li> <li style="margin-bottom: 5px;"><strong>VR番号:</strong> 切换是否隐藏所有VR影片。</li> <li style="margin-top: 8px; color: #666; font-size: 12px;">注意:为了保证搜索结果的完整性,所有隐藏功能在搜索页面会自动禁用。</li> </ul> </li> <li style="margin-bottom: 8px;"><strong>导出存储番号:</strong> 将当前脚本中存储的所有"已看"和"已浏览"番号数据导出为一个 <code>javdb-backup_...json</code> 文件,用于备份和迁移。</li> <li style="margin-bottom: 8px;"><strong>清空存储番号:</strong> <strong style="color: #ff4a4a;">删除所有存储在脚本中的数据</strong>(包括"已看"、"已浏览"记录及所有设置)。此操作不可逆,请谨慎使用!</li> <li style="margin-bottom: 8px;"><strong>搜索与管理:</strong> <ul style="padding-left: 20px; margin-top: 8px;"> <li style="margin-bottom: 5px;"><strong>搜索已看的番号:</strong> 在"已看"列表中快速查找特定番号,并可以单独删除记录。</li> <li><strong>查询浏览记录:</strong> 在"已浏览"历史中快速查找特定番号,并可以单独删除记录。</li> </ul> </li> <li style="margin-bottom: 8px;"><strong>数据显示:</strong> 面板底部会实时显示已存储的"已看"和"已浏览"番号总数,以及上次导入文件的时间。</li> </ul> <h3 style="font-size: 16px; font-weight: 600; margin-top: 20px; margin-bottom: 10px;">列表页功能</h3> <ul style="padding-left: 20px;"> <li style="margin-bottom: 8px;"><strong>状态标记:</strong> 在影片列表(如演员页、热门影片等)中,脚本会自动为影片添加状态标签: <ul style="padding-left: 20px; margin-top: 8px;"> <li style="margin-bottom: 5px;"> <span class="tag is-success is-light">我看過這部影片</span>: 表示该影片在您的"已看"列表中。</li> <li style="margin-bottom: 5px;"> <span class="tag is-warning is-light">已浏览</span>: 表示您曾访问过该影片的详情页。</li> </ul> </li> <li style="margin-bottom: 8px;"><strong>自动隐藏:</strong> 根据您在主面板中的开关设置,自动隐藏对应的"已看"、"已浏览"或"VR"影片。</li> <li style="margin-bottom: 8px;"><strong>懒加载与翻页插件支持:</strong> 无论您是向下滚动无限加载,还是使用自动翻页插件,新加载的影片都会被自动处理(添加标签或隐藏)。</li> <li style="margin-bottom: 8px;"><strong>页面数据导出 (特定页面):</strong> 在"看过"、"想看"、"收藏夹"等特定列表页面,顶部会出现一个导出工具。您可以输入希望导出的页数(例如输入5,即从当前页开始导出5页),将这些页面上的影片信息(番号、发行日期)导出为JSON文件。如果留空,则会导出所有页面的数据。</li> </ul> <h3 style="font-size: 16px; font-weight: 600; margin-top: 20px; margin-bottom: 10px;">详情页功能</h3> <ul style="padding-left: 20px;"> <li style="margin-bottom: 8px;"><strong>自动记录浏览:</strong> 当您访问一部影片的详情页时,脚本会在3-5秒的随机延迟后,自动将其番号记录到"已浏览"列表中。这可以避免误操作,并模拟真实浏览行为。如果记录失败,脚本会自动重试最多5次。</li> <li style="margin-bottom: 8px;"><strong>状态提示:</strong> <ul style="padding-left: 20px; margin-top: 8px;"> <li style="margin-bottom: 5px;"><strong>悬浮按钮变色:</strong> 如果当前影片处于"已看"或"已浏览"状态,左侧的悬浮球按钮会由默认的粉色变为<span style="color: #2ed573; font-weight: bold;">绿色</span>,给您最直观的提示。</li> <li><strong>网页标签页图标 (Favicon) 变更:</strong> 同时,浏览器标签页的图标也会改变为一个特殊的"R"图标,方便您在多个标签页中快速识别已处理过的影片。</li> </ul> </li> </ul> </div> `; helpPanel.innerHTML = helpContent; // 帮助按钮事件 helpButton.addEventListener('click', () => { helpPanel.style.display = 'block'; }); const closeHelpBtn = helpPanel.querySelector('#closeHelpButton'); closeHelpBtn.addEventListener('click', () => { helpPanel.style.display = 'none'; }); closeHelpBtn.addEventListener('mouseover', () => { closeHelpBtn.style.color = '#333'; }); closeHelpBtn.addEventListener('mouseout', () => { closeHelpBtn.style.color = '#999'; }); panel.appendChild(versionAuthorInfo); // 将版本和作者信息放在最下面 // 处理文件上传 uploadButton.addEventListener('change', handleFileUpload); const circle = createCircle(circlePosition.left, circlePosition.top); function handleFileUpload(event) { const file = event.target.files[0]; if (!file) { return; } const reader = new FileReader(); reader.onload = function (e) { try { const jsonData = JSON.parse(e.target.result); // 检查导入的数据格式 if (Array.isArray(jsonData)) { // 旧格式处理 jsonData.forEach(item => { if (item.id) { storedIds.add(item.id); } }); } else if (jsonData.videoBrowseHistory && jsonData.myIds) { // 新格式处理 jsonData.videoBrowseHistory.forEach(id => { storedIds.add(id); // 添加到 storedIds }); jsonData.myIds.forEach(id => { storedIds.add(id); // 添加到 storedIds }); // 更新 videoBrowseHistory const existingVideoBrowseHistory = GM_getValue(CONFIG.BROWSE_HISTORY_KEY, []); const updatedVideoBrowseHistory = new Set([...existingVideoBrowseHistory, ...jsonData.videoBrowseHistory]); GM_setValue(CONFIG.BROWSE_HISTORY_KEY, Array.from(updatedVideoBrowseHistory)); // 更新存储 } GM_setValue(CONFIG.STORED_IDS_KEY, Array.from(storedIds)); lastUploadTime = new Date().toLocaleString(); GM_setValue(CONFIG.LAST_UPLOAD_TIME_KEY, lastUploadTime); uploadTimeDisplay.textContent = `最新上传时间: ${lastUploadTime}`; alert('数据已保存'); updateCountDisplay(); // 使用新的更新函数 } catch (error) { console.error('解析 JSON 失败:', error); alert('解析 JSON 失败'); } }; reader.readAsText(file); } function createCircle(left, top) { debugLog('创建圆形按钮...'); const existingCircle = document.getElementById('unique-circle'); if (existingCircle) { debugLog('移除已存在的圆形按钮'); existingCircle.remove(); } const circle = document.createElement('div'); circle.id = 'unique-circle'; circle.style.position = 'fixed'; circle.style.width = '60px'; circle.style.height = '60px'; circle.style.borderRadius = '50%'; circle.style.backgroundColor = CONFIG.STATUS_DEFAULT_COLOR; circle.style.cursor = 'pointer'; circle.style.zIndex = 10000; circle.style.left = `${left}px`; circle.style.top = `${top}px`; circle.style.boxShadow = '0 4px 8px rgba(0, 0, 0, 0.2)'; circle.style.transition = 'all 0.3s ease'; document.body.appendChild(circle); // 创建内部文字容器 const label = document.createElement('div'); label.textContent = '番'; label.style.fontSize = '20px'; label.style.color = 'white'; label.style.textAlign = 'center'; label.style.lineHeight = '60px'; label.style.fontWeight = 'bold'; label.style.textShadow = '1px 1px 2px rgba(0, 0, 0, 0.2)'; circle.appendChild(label); // 悬停效果 circle.addEventListener('mouseenter', function () { circle.style.transition = 'all 0.3s ease'; circle.style.left = '0px'; circle.style.backgroundColor = CONFIG.STATUS_DEFAULT_COLOR; circle.style.transform = 'scale(1.05)'; circle.style.boxShadow = '0 6px 12px rgba(0, 0, 0, 0.3)'; }); // Helper to convert hex to rgba function hexToRgba(hex, alpha) { const r = parseInt(hex.slice(1, 3), 16); const g = parseInt(hex.slice(3, 5), 16); const b = parseInt(hex.slice(5, 7), 16); return `rgba(${r}, ${g}, ${b}, ${alpha})`; } circle.addEventListener('mouseleave', function () { circle.style.transition = 'all 0.3s ease'; circle.style.left = '-40px'; circle.style.backgroundColor = hexToRgba(CONFIG.STATUS_DEFAULT_COLOR, 0.7); circle.style.transform = 'scale(1)'; circle.style.boxShadow = '0 4px 8px rgba(0, 0, 0, 0.2)'; }); // 点击效果 circle.addEventListener('mousedown', function() { circle.style.transform = 'scale(0.95)'; }); circle.addEventListener('mouseup', function() { circle.style.transform = 'scale(1.05)'; }); // 修改圆形按钮点击事件处理 circle.addEventListener('click', function(e) { e.stopPropagation(); // 阻止事件冒泡 debugLog('圆形按钮被点击'); panel.style.display = 'block'; panelVisible = true; debugLog('面板显示状态:', panelVisible); setTimeout(() => { updatePanelPosition(panel, parseInt(circle.style.top)); }, 0); setupMouseLeave(panel); }); return circle; } // 修改 setupMouseLeave 函数 function setupMouseLeave(panel) { let timeoutId = null; panel.addEventListener('mouseenter', () => { debugLog('鼠标进入面板'); if (timeoutId) { clearTimeout(timeoutId); timeoutId = null; } }); panel.addEventListener('mouseleave', (e) => { debugLog('鼠标离开面板'); if (!panel.contains(e.relatedTarget)) { timeoutId = setTimeout(() => { debugLog('关闭面板'); panel.style.display = 'none'; panelVisible = false; }, 1000); } }); document.addEventListener('click', (e) => { if (!panel.contains(e.target) && e.target.id !== 'unique-circle') { debugLog('点击面板外部,关闭面板'); panel.style.display = 'none'; panelVisible = false; } }); } // 添加调试信息 const observer = new MutationObserver((mutations) => { mutations.forEach((mutation) => { if (mutation.type === 'attributes' && mutation.attributeName === 'style') { debugLog('面板样式变化:', panel.style.cssText); } }); }); observer.observe(panel, { attributes: true }); })(); (function () { const url = window.location.href; const validUrlPatterns = [ /https:\/\/javdb\.com\/users\/want_watch_videos.*/, /https:\/\/javdb\.com\/users\/watched_videos.*/, /https:\/\/javdb\.com\/users\/list_detail.*/, /https:\/\/javdb\.com\/lists.*/ ]; const isValidUrl = validUrlPatterns.some(pattern => pattern.test(url)); if (!isValidUrl) { return; } let allVideosInfo = JSON.parse(localStorage.getItem('allVideosInfo')) || []; let exportState = { allowExport: false, currentPage: 1, maxPage: null }; function getVideosInfo() { const videoElements = document.querySelectorAll('.item'); return Array.from(videoElements).map((element) => { const title = element.querySelector('.video-title').textContent.trim(); const [id, ...titleWords] = title.split(' '); const releaseDate = element.querySelector('.meta').textContent.replace(/[^0-9-]/g, ''); return { id, releaseDate }; }); } // 获取总视频数量 function getTotalVideoCount() { const activeLink = document.querySelector('a.is-active'); if (activeLink) { const text = activeLink.textContent; const match = text.match(/\((\d+)\)/); if (match) { console.log(`总视频数量: ${match[1]}`); return parseInt(match[1], 10); } } return 0; // 默认返回0 } // 计算最大页数 function calculateMaxPages(totalCount, itemsPerPage) { const maxPages = Math.ceil(totalCount / itemsPerPage); console.log(`总视频数量: ${totalCount}, 每页视频数量: ${itemsPerPage},最大页数: ${maxPages}`); return maxPages; } // 修改翻页逻辑 function scrapeAllPages() { const itemsPerPage = CONFIG.VIDEOS_PER_PAGE; // 每页视频数量 const totalCount = getTotalVideoCount(); // 获取总视频数量 const maxPages = calculateMaxPages(totalCount, itemsPerPage); // 计算最大页数 if (exportState.currentPage > maxPages) { exportScrapedData(); return; } const videosInfo = getVideosInfo(); allVideosInfo = allVideosInfo.concat(videosInfo); // 更新 URL if (exportState.currentPage > 1) { const newUrl = `https://javdb.com/users/watched_videos?page=${exportState.currentPage}`; location.href = newUrl; // 通过 URL 变更翻页 } else { exportState.currentPage++; // 只在第一页时增加页码 localStorage.setItem('exportState', JSON.stringify(exportState)); scrapeAllPages(); // 继续抓取下一页 } // 每导出5页后暂停3秒 if (exportState.currentPage % 5 === 0) { setTimeout(() => { scrapeAllPages(); // 继续抓取 }, CONFIG.EXPORT_PAUSE_DELAY); // 暂停3秒 } } function exportScrapedData() { debugLog('开始导出抓取的番号...'); try { const allVideosInfo = JSON.parse(localStorage.getItem('allVideosInfo') || '[]'); if (allVideosInfo.length === 0) { logToScreen('没有抓取到任何数据,已停止导出。', 'rgba(255, 193, 7, 0.8)', 'white'); return; } const json = JSON.stringify(allVideosInfo, null, 2); const jsonBlob = new Blob([json], { type: 'application/json' }); const jsonUrl = URL.createObjectURL(jsonBlob); const downloadLink = document.createElement('a'); const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19); let fileName = 'javdb-export'; const url = window.location.href; if (url.includes('/watched_videos')) { fileName = 'watched-videos'; } else if (url.includes('/want_watch_videos')) { fileName = 'want-watch-videos'; } else if (url.includes('/list_detail')) { const listTitle = document.querySelector('.title.is-4'); if (listTitle) fileName = listTitle.textContent.trim(); } else if (url.includes('/lists')) { const listTitle = document.querySelector('.title.is-4'); if (listTitle) fileName = listTitle.textContent.trim(); } downloadLink.download = `${fileName}_${timestamp}.json`; downloadLink.href = jsonUrl; document.body.appendChild(downloadLink); downloadLink.click(); document.body.removeChild(downloadLink); URL.revokeObjectURL(jsonUrl); logToScreen('抓取的数据已成功导出。', 'rgba(76, 175, 80, 0.8)', 'white'); debugLog('抓取的数据导出成功。'); } catch (error) { console.error('导出抓取数据时出错:', error); logToScreen('导出抓取数据时出错,请查看控制台。', 'rgba(244, 67, 54, 0.8)', 'white'); } finally { localStorage.removeItem('allVideosInfo'); localStorage.removeItem('exportState'); } } // 添加获取当前页码的函数 function getCurrentPage() { // 从 URL 中获取页码 const urlParams = new URLSearchParams(window.location.search); const page = urlParams.get('page'); // 如果 URL 中没有页码参数,则返回 1 return page ? parseInt(page) : 1; } function startExport() { const maxPageInput = document.getElementById('maxPageInput'); if (!maxPageInput) { console.error('找不到页数输入框'); return; } const itemsPerPage = CONFIG.VIDEOS_PER_PAGE; const totalCount = getTotalVideoCount(); const maxPages = calculateMaxPages(totalCount, itemsPerPage); // 如果用户输入了页数,使用用户输入的值,否则使用最大页数 const pagesToExport = maxPageInput.value ? parseInt(maxPageInput.value) : maxPages; // 确保不超过最大页数 const currentPage = getCurrentPage(); const targetPage = Math.min(currentPage + pagesToExport - 1, maxPages); if (targetPage < currentPage) { alert('请输入有效的页数'); return; } exportState.currentPage = currentPage; exportState.maxPage = targetPage; exportState.allowExport = true; localStorage.setItem('exportState', JSON.stringify(exportState)); localStorage.setItem('allVideosInfo', JSON.stringify([])); // 清空之前的数据 exportButton.textContent = `导出中...(${currentPage}/${targetPage})`; exportButton.disabled = true; stopButton.disabled = false; isExporting = true; // 开始从当前页抓取数据 scrapeCurrentPage(); } // 新增函数:抓取当前页面数据 function scrapeCurrentPage() { if (!isExporting || exportState.currentPage > exportState.maxPage) { finishExport(); return; } const startTime = performance.now(); const videosInfo = getVideosInfo(); const currentAllVideos = JSON.parse(localStorage.getItem('allVideosInfo') || '[]'); const newAllVideos = currentAllVideos.concat(videosInfo); localStorage.setItem('allVideosInfo', JSON.stringify(newAllVideos)); if (CONFIG.PERFORMANCE_MODE) { const endTime = performance.now(); const processingTime = (endTime - startTime).toFixed(2); console.log(`成功获取到 ${videosInfo.length} 个视频数据,当前页: ${exportState.currentPage},处理时间: ${processingTime}ms`); } // 更新进度显示 exportButton.textContent = `导出中...(${exportState.currentPage}/${exportState.maxPage})`; // 前往下一页 exportState.currentPage++; localStorage.setItem('exportState', JSON.stringify(exportState)); if (exportState.currentPage <= exportState.maxPage) { const newUrl = `${window.location.pathname}?page=${exportState.currentPage}`; window.location.href = newUrl; } else { finishExport(); } } // 新增函数:完成导出 function finishExport() { const allVideosInfo = JSON.parse(localStorage.getItem('allVideosInfo') || '[]'); if (allVideosInfo.length > 0) { exportScrapedData(); } // 重置状态 isExporting = false; exportState.allowExport = false; exportState.currentPage = 1; exportState.maxPage = null; localStorage.setItem('exportState', JSON.stringify(exportState)); exportButton.textContent = '导出完成'; exportButton.disabled = false; stopButton.disabled = true; } function createExportButton() { const maxPageInput = document.createElement('input'); maxPageInput.type = 'number'; maxPageInput.id = 'maxPageInput'; maxPageInput.placeholder = '当前页往后导出的页数,留空导全部'; // 修改提示文本 maxPageInput.style.cssText = ` margin-right: 10px; padding: 6px 12px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px; width: auto; min-width: 50px; box-sizing: border-box; transition: all 0.3s ease; outline: none; background-color: white; `; maxPageInput.min = '1'; // 获取最大页数并设置为 max 属性 const itemsPerPage = CONFIG.VIDEOS_PER_PAGE; const totalCount = getTotalVideoCount(); const maxPages = calculateMaxPages(totalCount, itemsPerPage); maxPageInput.max = maxPages; // 创建一个容器来包含上传按钮和帮助图标 const uploadContainer = document.createElement('div'); uploadContainer.style.position = 'relative'; uploadContainer.style.marginBottom = '15px'; uploadContainer.style.display = 'flex'; // 使用flex布局 uploadContainer.style.alignItems = 'center'; // 垂直居中 uploadContainer.style.gap = '8px'; // 元素之间的间距 uploadContainer.style.width = '100%'; // 创建一个包装上传按钮的容器 const uploadButtonWrapper = document.createElement('div'); uploadButtonWrapper.style.flex = '1'; // 占据剩余空间 uploadButtonWrapper.style.minWidth = '0'; // 防止内容溢出 uploadContainer.appendChild(uploadButtonWrapper); uploadButtonWrapper.appendChild(maxPageInput); // 初始化输入框宽度 setTimeout(() => { const tempSpan = document.createElement('span'); tempSpan.style.visibility = 'hidden'; tempSpan.style.position = 'absolute'; tempSpan.style.whiteSpace = 'nowrap'; tempSpan.style.font = window.getComputedStyle(maxPageInput).font; tempSpan.textContent = maxPageInput.placeholder; document.body.appendChild(tempSpan); // 设置输入框宽度为占位符文本宽度加上内边距 const width = tempSpan.offsetWidth; maxPageInput.style.width = (width + 50) + 'px'; document.body.removeChild(tempSpan); }, 0); // 输入框焦点样式 maxPageInput.addEventListener('focus', function() { this.style.borderColor = '#4a9eff'; this.style.boxShadow = '0 0 0 2px rgba(74, 158, 255, 0.2)'; }); maxPageInput.addEventListener('blur', function() { this.style.borderColor = '#ddd'; this.style.boxShadow = 'none'; }); exportButton = document.createElement('button'); exportButton.textContent = '导出 json'; exportButton.className = 'button is-small'; exportButton.style.cssText = ` padding: 6px 16px; background-color: #4a9eff; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 14px; font-weight: 500; transition: all 0.3s ease; outline: none; height: auto; `; stopButton = document.createElement('button'); stopButton.textContent = '停止导出'; stopButton.className = 'button is-small'; stopButton.style.cssText = ` margin-left: 10px; padding: 6px 16px; background-color: #ff4757; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 14px; font-weight: 500; transition: all 0.3s ease; outline: none; height: auto; `; stopButton.disabled = true; // 按钮悬停效果 [exportButton, stopButton].forEach(button => { button.addEventListener('mouseover', function() { if (!this.disabled) { this.style.opacity = '0.9'; this.style.transform = 'translateY(-1px)'; } }); button.addEventListener('mouseout', function() { if (!this.disabled) { this.style.opacity = '1'; this.style.transform = 'translateY(0)'; } }); // 修改禁用状态的处理方式 const updateDisabledStyle = function() { if (this.disabled) { this.style.opacity = '0.6'; this.style.cursor = 'not-allowed'; } else { this.style.opacity = '1'; this.style.cursor = 'pointer'; } }; // 监听 disabled 属性变化 const observer = new MutationObserver((mutations) => { mutations.forEach((mutation) => { if (mutation.attributeName === 'disabled') { updateDisabledStyle.call(button); } }); }); observer.observe(button, { attributes: true, attributeFilter: ['disabled'] }); // 初始化禁用状态样式 updateDisabledStyle.call(button); }); // 添加按钮点击效果 [exportButton, stopButton].forEach(button => { button.addEventListener('mousedown', function() { if (!this.disabled) { this.style.transform = 'translateY(1px)'; } }); button.addEventListener('mouseup', function() { if (!this.disabled) { this.style.transform = 'translateY(-1px)'; } }); }); stopButton.addEventListener('click', () => { isExporting = false; stopButton.disabled = true; exportButton.disabled = false; exportButton.textContent = '导出已停止'; localStorage.removeItem('allVideosInfo'); localStorage.removeItem('exportState'); }); const buttonContainer = document.createElement('div'); buttonContainer.style.display = 'flex'; buttonContainer.style.gap = '8px'; buttonContainer.style.alignItems = 'center'; buttonContainer.appendChild(exportButton); buttonContainer.appendChild(stopButton); // 创建最终的容器 const exportContainer = document.createElement('div'); exportContainer.style.cssText = ` display: flex; align-items: center; gap: 8px; padding: 5px; border-radius: 6px; background-color: rgba(255, 255, 255, 0.8); `; exportContainer.appendChild(maxPageInput); exportContainer.appendChild(buttonContainer); if (url.includes('/list_detail')) { document.querySelector('.breadcrumb').querySelector('ul').appendChild(exportContainer); } else { document.querySelector('.toolbar').appendChild(exportContainer); } exportButton.addEventListener('click', () => { if (!isExporting) { startExport(); } }); } function checkExportState() { const savedExportState = localStorage.getItem('exportState'); if (savedExportState) { exportState = JSON.parse(savedExportState); if (exportState.allowExport) { exportButton.textContent = '导出中...'; exportButton.disabled = true; stopButton.disabled = false; isExporting = true; // 继续抓取当前页面 waitForDOMAndScrape(); } } } function waitForDOMAndScrape() { // 使用更智能的DOM检测机制 function checkAndScrape() { const videoElements = document.querySelectorAll('.item'); if (videoElements.length > 0) { // 如果找到视频元素,立即开始处理 scrapeCurrentPage(); } else { // 如果还没找到,继续等待 setTimeout(checkAndScrape, 200); // 每200ms检查一次 } } // 检查页面是否已经加载完成 if (document.readyState === 'complete') { checkAndScrape(); } else { window.addEventListener('load', checkAndScrape); } } if (url.includes('/watched_videos') || url.includes('/want_watch_videos') || url.includes('/list_detail') || url.includes('/lists') ) { createExportButton(); checkExportState(); } })(); // 修改 modifyItemAtCurrentPage 函数 function modifyItemAtCurrentPage(itemToModify) { const isSearchPage = window.location.href.startsWith('https://javdb.com/search?'); // 从 GM 存储中读取最新的隐藏设置,以确保对动态加载的内容也有效 let hideWatchedVideos = GM_getValue(CONFIG.HIDE_WATCHED_VIDEOS_KEY, false); let hideViewedVideos = GM_getValue(CONFIG.HIDE_VIEWED_VIDEOS_KEY, false); let hideVRVideos = GM_getValue(CONFIG.HIDE_VR_VIDEOS_KEY, false); // 如果是搜索页面,则禁用隐藏功能 if (isSearchPage) { hideWatchedVideos = false; hideViewedVideos = false; hideVRVideos = false; } // 获取番号 const videoTitle = itemToModify.querySelector('div.video-title > strong')?.textContent.trim(); // 获取 data-title const dataTitle = itemToModify.querySelector('div.video-title > span.x-btn')?.getAttribute('data-title'); if (!videoTitle) { debugLog('未找到视频标题'); return; } debugLog('处理番号:', videoTitle); const browseHistory = new Set(GM_getValue(CONFIG.BROWSE_HISTORY_KEY, [])); const watchedVideos = new Set(GM_getValue(CONFIG.STORED_IDS_KEY, [])); debugLog('浏览历史数量:', browseHistory.size); debugLog('已看番号数量:', watchedVideos.size); debugLog(`${videoTitle} 是否在浏览历史中:`, browseHistory.has(videoTitle)); debugLog(`${videoTitle} 是否在已看列表中:`, watchedVideos.has(videoTitle)); // 检查是否需要隐藏VR番号 if (hideVRVideos && dataTitle.includes('【VR】')) { debugLog(`${videoTitle} 是VR番号,hideVRVideos=${hideVRVideos},准备隐藏`); const itemContainer = itemToModify.closest('.item'); if (itemContainer) { itemContainer.style.transition = 'opacity 0.3s'; itemContainer.style.opacity = '0'; setTimeout(() => { itemContainer.remove(); debugLog(`${videoTitle} 已隐藏并移除`); }, 300); } return; } // 检查是否需要隐藏已浏览的番号 if (hideViewedVideos && browseHistory.has(videoTitle)) { debugLog(`${videoTitle} 在浏览历史中,hideViewedVideos=${hideViewedVideos},准备隐藏`); const itemContainer = itemToModify.closest('.item'); if (itemContainer) { itemContainer.style.transition = 'opacity 0.3s'; itemContainer.style.opacity = '0'; setTimeout(() => { itemContainer.remove(); debugLog(`${videoTitle} 已隐藏并移除`); }, 300); } return; } // 检查是否是已看的番号并需要隐藏 if (watchedVideos.has(videoTitle) && hideWatchedVideos) { debugLog(`${videoTitle} 在已看列表中,hideWatchedVideos=${hideWatchedVideos},准备隐藏`); const itemContainer = itemToModify.closest('.item'); if (itemContainer) { itemContainer.style.transition = 'opacity 0.3s'; itemContainer.style.opacity = '0'; setTimeout(() => { itemContainer.remove(); debugLog(`${videoTitle} 已隐藏并移除`); }, 300); } return; } // 只处理真正已看的番号的标签 if (watchedVideos.has(videoTitle)) { debugLog(`${videoTitle} 在已看列表中,准备添加标签`); let tags = itemToModify.closest('.item').querySelector('.tags.has-addons'); if (!tags) { debugLog(`${videoTitle} 未找到标签容器`); return; } let tagClass = styleMap['我看過這部影片']; // 检查是否已经存在相同的标记 let existingTags = Array.from(tags.querySelectorAll('span')); let tagExists = existingTags.some(tag => tag.textContent === '我看過這部影片'); debugLog(`${videoTitle} 是否已有标签:`, tagExists); // 如果不存在对应的标签,则添加新的标签 if (!tagExists) { let newTag = document.createElement('span'); newTag.className = tagClass; newTag.textContent = '我看過這部影片'; tags.appendChild(newTag); debugLog(`成功为 ${videoTitle} 添加已看标签`); logToScreen(`我看過這部影片: ${videoTitle}`, 'rgba(76, 175, 80, 0.8)', 'white'); } else { debugLog(`${videoTitle} 已有标签,跳过添加`); } } else if (browseHistory.has(videoTitle)) { debugLog(`${videoTitle} 在浏览历史中,准备添加已浏览标签`); let tags = itemToModify.closest('.item').querySelector('.tags.has-addons'); if (!tags) { debugLog(`${videoTitle} 未找到标签容器`); return; } let tagClass = styleMap['已浏览']; if (!tagClass) { debugLog('未找到已浏览标签的样式'); return; } // 检查是否已存在标签 let existingTags = Array.from(tags.querySelectorAll('span')); let tagExists = existingTags.some(tag => ['我看過這部影片', '已浏览'].includes(tag.textContent)); debugLog(`${videoTitle} 是否已有标签:`, tagExists); if (!tagExists) { let newTag = document.createElement('span'); newTag.className = tagClass; newTag.textContent = '已浏览'; tags.appendChild(newTag); debugLog(`成功为 ${videoTitle} 添加已浏览标签`); logToScreen(`已浏览: ${videoTitle}`, 'rgba(255, 159, 67, 0.8)', 'white'); } else { debugLog(`${videoTitle} 已有标签,跳过添加`); } } else { debugLog(`${videoTitle} 不在已看或已浏览列表中,不添加标签`); } } async function processLoadedItems() { debugLog('开始处理页面项目'); debugLog('当前 hideWatchedVideos 状态:', GM_getValue(CONFIG.HIDE_WATCHED_VIDEOS_KEY, false)); debugLog('当前 hideViewedVideos 状态:', GM_getValue(CONFIG.HIDE_VIEWED_VIDEOS_KEY, false)); debugLog('当前 hideVRVideos 状态:', GM_getValue(CONFIG.HIDE_VR_VIDEOS_KEY, false)); const url = window.location.href; debugLog('当前URL:', url); const isValidUrl = validUrlPatterns.some(pattern => pattern.test(url)); if (isValidUrl) { debugLog('URL匹配特殊模式,跳过处理'); return; } let items = Array.from(document.querySelectorAll('.movie-list .item a')); debugLog('找到项目数量:', items.length); // 从每个 item 的 <a> 中找到对应的 <strong> 内容 items.forEach((item, index) => { debugLog(`处理第 ${index + 1} 个项目`); let strongElement = item.querySelector('div.video-title > strong'); if (strongElement) { let title = strongElement.textContent.trim(); if (title) { debugLog(`处理番号: ${title}`); modifyItemAtCurrentPage(item); } else { debugLog('项目标题为空'); } } else { debugLog('未找到标题元素'); } }); } // 使用 MutationObserver 监听由"自动翻页"等插件动态添加的项目,这比 setInterval 更高效 const targetNode = document.querySelector('.movie-list'); if (targetNode) { // 初始加载时处理页面上已有的项目 processLoadedItems(); const observer = new MutationObserver((mutationsList) => { mutationsList.forEach((mutation) => { if (mutation.type === 'childList' && mutation.addedNodes.length > 0) { let newItemsCount = 0; mutation.addedNodes.forEach(node => { if (node.nodeType === Node.ELEMENT_NODE) { const itemsToProcess = []; // 检查节点本身是否为 .item if (node.matches && node.matches('.item')) { itemsToProcess.push(node); } // 检查节点是否为包含 .item 的容器 else if (node.querySelectorAll) { node.querySelectorAll('.item').forEach(item => itemsToProcess.push(item)); } if (itemsToProcess.length > 0) { newItemsCount += itemsToProcess.length; itemsToProcess.forEach(itemNode => { const itemLink = itemNode.querySelector('a'); if (itemLink) { modifyItemAtCurrentPage(itemLink); } }); } } }); if (newItemsCount > 0) { logToScreen(`通过翻页插件加载了 ${newItemsCount} 个新项目`, 'rgba(0, 255, 255, 0.8)', 'white'); } } }); }); observer.observe(targetNode, { childList: true, subtree: true }); debugLog('MutationObserver 已启动,正在监视 .movie-list 的变化。'); } else { // 如果没有 .movie-list,仍然像以前一样处理项目 processLoadedItems(); debugLog("未找到 '.movie-list' 元素,MutationObserver 未启动。"); } // 监听窗口大小变化 window.addEventListener('resize', () => { if (panelVisible) { updatePanelPosition(panel, parseInt(panel.style.top)); } }); // 修改随机延迟函数的参数范围为3-5秒 function getRandomDelay(min = CONFIG.VIDEO_ID_RECORD_DELAY_MIN, max = CONFIG.VIDEO_ID_RECORD_DELAY_MAX) { return Math.floor(Math.random() * (max - min + 1) + min) * 1000; } // 修改记录番号浏览记录函数 async function recordVideoId() { const videoIdPattern = /<strong>番號:<\/strong>\s* <span class="value"><a href="\/video_codes\/([A-Z]+)">([A-Z]+)<\/a>-(\d+)<\/span>/; const panelBlock = document.querySelector('.panel-block.first-block'); if (panelBlock) { const match = panelBlock.innerHTML.match(videoIdPattern); if (match) { const videoId = `${match[1]}-${match[3]}`; // 随机等待3-5秒 const delay = getRandomDelay(); debugLog(`等待 ${delay/1000} 秒后开始验证记录...`); await sleep(delay); // 最大重试次数 const maxRetries = CONFIG.VIDEO_ID_RECORD_RETRIES; let retryCount = 0; let recordSuccess = false; while (!recordSuccess && retryCount < maxRetries) { try { // 获取当前存储的浏览记录 const storedVideoIds = new Set(GM_getValue(CONFIG.BROWSE_HISTORY_KEY, [])); // 如果已经存在,直接返回 if (storedVideoIds.has(videoId)) { debugLog(`番号已存在: ${videoId}`); return; } // 添加新的番号 storedVideoIds.add(videoId); GM_setValue(CONFIG.BROWSE_HISTORY_KEY, Array.from(storedVideoIds)); // 等待一小段时间后验证 await sleep(500); // 验证是否成功保存 const verifyStorage = new Set(GM_getValue(CONFIG.BROWSE_HISTORY_KEY, [])); if (verifyStorage.has(videoId)) { debugLog(`成功记录番号: ${videoId}`); logToScreen(`成功记录番号: ${videoId}`, 'rgba(76, 175, 80, 0.8)', 'white'); updateCountDisplay(); recordSuccess = true; break; } else { throw new Error('验证失败'); } } catch (error) { retryCount++; debugLog(`第 ${retryCount} 次记录失败: ${videoId}, 错误: ${error.message}`); logToScreen(`第 ${retryCount} 次记录失败,将在3秒后重试...`, 'rgba(255, 193, 7, 0.8)', 'white'); if (retryCount < maxRetries) { await sleep(CONFIG.VIDEO_ID_RECORD_RETRY_DELAY); // 失败后等待3秒再重试 } } } if (!recordSuccess) { debugLog(`达到最大重试次数(${maxRetries}),番号记录失败: ${videoId}`); logToScreen(`番号记录失败,请刷新页面重试`, 'rgba(244, 67, 54, 0.8)', 'white'); } } else { debugLog('未找到匹配的番号格式'); } } else { debugLog('未找到包含番号的元素'); } } // 在页面加载时调用记录函数 if (window.location.href.startsWith('https://javdb.com/v/')) { recordVideoId(); } // -- 新增功能:定时检查当前影片状态并更新图标颜色 -- (function() { 'use strict'; // 定义常量 const GREEN_COLOR = CONFIG.STATUS_WATCHED_COLOR; // 绿色,用于标记已看/已浏览 const ORIGINAL_COLOR = CONFIG.STATUS_DEFAULT_COLOR; // 原始颜色 let originalFaviconUrl = null; function getFaviconLink() { let link = document.querySelector("link[rel~='icon']"); if (!link) { link = document.createElement('link'); link.rel = 'icon'; document.head.appendChild(link); } return link; } function setFavicon(url) { const head = document.head; const existingFavicons = document.querySelectorAll("link[rel~='icon']"); existingFavicons.forEach(icon => icon.remove()); const newFavicon = document.createElement('link'); newFavicon.rel = 'icon'; if (url.endsWith('.png')) { newFavicon.type = 'image/png'; } newFavicon.href = url; head.appendChild(newFavicon); } originalFaviconUrl = getFaviconLink().href; /** * 检查当前视频页面的观看状态,并更新圆形按钮的颜色。 */ function checkCurrentVideoStatus() { const currentFaviconHref = getFaviconLink().href; // 此功能仅在视频详情页执行 (e.g., /v/xxxx) if (!window.location.href.startsWith('https://javdb.com/v/')) { if (originalFaviconUrl && currentFaviconHref !== originalFaviconUrl) { setFavicon(originalFaviconUrl); } return; } // 查找包含番号信息的元素 const panelBlock = document.querySelector('.panel-block.first-block'); if (!panelBlock) { return; // 如果元素未加载,则稍后重试 } // 使用正则表达式从页面提取番号 const videoIdPattern = /<strong>番號:<\/strong>\s* <span class="value"><a href="\/video_codes\/([A-Z]+)">[A-Z]+<\/a>-(\d+)<\/span>/; const match = panelBlock.innerHTML.match(videoIdPattern); if (!match) { return; // 未匹配到番号 } const videoId = `${match[1]}-${match[2]}`; // 从 GM 存储中获取"已看"和"已浏览"列表 const watchedVideos = new Set(GM_getValue(CONFIG.STORED_IDS_KEY, [])); const browseHistory = new Set(GM_getValue(CONFIG.BROWSE_HISTORY_KEY, [])); // 判断当前视频是否在任一列表中 const isWatchedOrViewed = watchedVideos.has(videoId) || browseHistory.has(videoId); // 获取圆形按钮元素 const circle = document.getElementById('unique-circle'); if (circle) { const targetColor = isWatchedOrViewed ? GREEN_COLOR : ORIGINAL_COLOR; // 仅在颜色不同时才更新,以减少不必要的DOM操作 if (circle.style.backgroundColor !== targetColor) { circle.style.backgroundColor = targetColor; } } if (isWatchedOrViewed) { if (currentFaviconHref !== CONFIG.CUSTOM_FAVICON_URL) { setFavicon(CONFIG.CUSTOM_FAVICON_URL); } } else { if (originalFaviconUrl && currentFaviconHref !== originalFaviconUrl) { setFavicon(originalFaviconUrl); } } } // 每2秒执行一次检查 setInterval(checkCurrentVideoStatus, CONFIG.STATUS_CHECK_INTERVAL); })();