您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
为 javdb、javbus 添加视频缩略图,菜单栏可直接按番号查询
// ==UserScript== // @name JavSS // @namespace javss // @version 1.3 // @description 为 javdb、javbus 添加视频缩略图,菜单栏可直接按番号查询 // @author anonymous // @license MIT // @match https://javdb.com/* // @match https://javdb459.com/* // @match https://www.javbus.com/* // @match https://www.javsee.ink/* // @icon http://javdb459.com/favicon.ico // @require https://cdn.jsdelivr.net/npm/sweetalert2@11 // @connect * // @grant GM_registerMenuCommand // @grant GM_addStyle // @grant GM.xmlHttpRequest // ==/UserScript== (function () { 'use strict'; const DEBUG_MODE = false; const SITE_CONFIGS = { 'javdb.com': { idSelector: 'div.video-detail > h2 > strong', getId: (element) => element.textContent.trim().split(' ')[0], insertLink: ['javbus', 'avwiki', 'nyaa'], insertLinkSelector: 'div.panel-block.first-block', insertSearchSelector: '#navbar-menu-hero > .navbar-start', insertImageSelector: 'div.video-meta-panel', preCheck: { selector: 'section > div > div.movie-list', action: (movieList) => modifyMovieList(movieList) }, }, 'www.javbus.com': { idSelector: 'div.info > p:nth-child(1) > span:nth-child(2)', getId: (element) => element.textContent, insertLink: ['javdb', 'avwiki', 'nyaa'], insertSearchSelector: '#navbar', insertImageSelector: 'div.row.movie', } // 其他网站添加在这里 }; const DOMAIN_MAPPING = { 'javdb459.com': 'javdb.com', 'www.dmmbus.ink': 'www.javbus.com', 'www.seejav.ink': 'www.javbus.com', 'www.javsee.ink': 'www.javbus.com' }; const SITE_STYLES = { 'javdb.com': ` .ssc { text-align: center; padding: 2rem 0 3rem; border-top: 1px dashed #4a4a4a; } .ss { width: 95%; min-width: 9rem; min-height: 1rem; background-color: #4a4a4a; } #search-ss { color: inherit; font-size: inherit; background: none; border: none; cursor: pointer; } `, 'www.javbus.com': ` .ss { margin-top: 15px; min-width: 9rem; min-height: 1rem; background-color: #4a4a4a; } #search-ss { padding: 15px; background: none; border: none; } ` }; const SEARCH_ENGINE = { 'javdb': 'https://javdb459.com/search?q=', 'javbus': 'https://www.javbus.com/', 'nyaa': 'https://sukebei.nyaa.si/?q=', 'avwiki': 'https://av-wiki.net/?s=' }; //- 缩略图源配置 const IMAGE_SOURCES = [ { name: 'javbee', url: 'https://javbee.vip/search?keyword=', stages: [ { selector: 'div.images-description > ul > li:last-child > a', getContent: (element) => element.innerHTML.trim() }, { selector: 'div.fileviewer-file > img', getContent: (element) => element.src, }, ], }, { name: 'javstore new', url: 'https://javstore.net/search/', buildUrl: (id) => `https://javstore.net/search/${id}.html`, stages: [ { selector: '#content_news > ul > li:last-child > a', getContent: (element) => element.href, }, { selector: 'div.news > a', getContent: (element) => element.href, }, ], }, { name: '3xplanet', url: 'https://3xplanet.net/', stages: [ { selector: 'div.td-post-content > div > p:nth-child(2) > a', isRelative: true, getContent: (element) => element.getAttribute('href'), }, { selector: '#show_image', getContent: (element) => element.src, }, ], }, // { // name: 'javstore old', // url: 'https://javstore.net/search/', // buildUrl: (id) => `https://javstore.net/search/${id}.html`, // stages: [ // { // selector: '#content_news > ul > li:last-child > a', // getContent: (element) => element.href, // }, // { // selector: 'div.news > a', // getContent: (element) => element.href, // }, // { // isRedirect: true, // selector: '#embed-code-2', // getContent: (element) => element.getAttribute('value'), // } // ], // }, ]; const Swal = window.Swal; if (!Swal) console.error("SweetAlert 未加载"); //========================================================================== const createLink = (title, href) => { const link = document.createElement('a'); // link.target = '_blank'; link.href = href; link.title = title; link.textContent = ' 🔗'; link.style.color = 'inherit'; return link; }; const addLink = (target, engines, id) => { engines.forEach(engine => { const a = createLink(engine, SEARCH_ENGINE[engine] + id); target.append(a); }); }; const processItem = (item) => { const id = item.querySelector('div.video-title > strong').textContent; const tagsContainer = item.querySelector('div.tags'); const newTag = document.createElement('span'); newTag.classList.add('tag', 'is-info'); newTag.textContent = '缩略图'; newTag.addEventListener('click', async (e) => { e.stopPropagation(); e.preventDefault(); const { imgSrc, refUrl } = await getImage(id); if (imgSrc) { showImagePopup(id, imgSrc, refUrl); } }); tagsContainer.append(newTag); }; const modifyMovieList = (movieList) => { const container = document.querySelector('section > div'); if (container) { container.style.maxWidth = '100%'; } const items = Array.from(movieList.querySelectorAll('.item')); if (items.length === 0) return; let currentIndex = 0; const itemsPerRow = 4; // 每行4个元素 const scrollToCurrent = () => { if (items[currentIndex]) { items[currentIndex].scrollIntoView({ behavior: 'smooth', block: 'center' }); } }; const nextPage = () => { const nextPageLink = document.querySelector('a.pagination-next'); nextPageLink?.click(); }; const prevPage = () => { const prevPageLink = document.querySelector('a.pagination-previous'); prevPageLink?.click(); }; document.addEventListener('auxclick', (e) => { switch (e.button) { case 4: // 鼠标侧键前进 nextPage(); e.preventDefault(); break; } }); document.addEventListener('keydown', function (e) { switch (e.key) { case 'ArrowRight': nextPage(); break; case 'ArrowLeft': prevPage(); break; case 'ArrowDown': currentIndex = Math.min(currentIndex + itemsPerRow, items.length - 1); scrollToCurrent(); e.preventDefault(); break; case 'ArrowUp': currentIndex = Math.max(currentIndex - itemsPerRow, 0); scrollToCurrent(); e.preventDefault(); break; case 'Home': currentIndex = 0; break; case 'End': currentIndex = items.length - 1; break; } }); items.forEach(processItem); }; const handleResponseError = (statusCode, url) => { const errorMessages = { 400: '错误请求 - 客户端发送了无效的请求(可能是参数错误)', 401: '未授权 - 需要身份验证', 403: '禁止访问 - 服务器拒绝请求', 404: '未找到 - 请求的资源不存在', 500: '服务器内部错误 - 服务器端出现问题', 502: '网关错误 - 上游服务器无效响应', 503: '服务不可用 - 服务器暂时过载或维护', 504: '网关超时 - 上游服务器未及时响应' }; const errorType = statusCode >= 500 ? '服务器端错误' : statusCode >= 400 ? '客户端错误' : '未知错误'; const message = errorMessages[statusCode] || errorType; console.warn(`请求失败: ${url} (状态码 ${statusCode}) - ${message}`); }; const resolveUrl = (currentUrl, pathname) => { try { return pathname.startsWith('http') ? pathname : new URL(pathname, new URL(currentUrl).origin).href; } catch (error) { console.error(`URL resolution failed for currentUrl: ${currentUrl}, path: ${pathname}`); return null; } }; const getImage = async (id, fromCache = true) => { if (!id || typeof id !== 'string') { throw new Error('番号无效'); } // 默认启用尝试从缓存获取地址 if (fromCache && !DEBUG_MODE) { const cachedResult = JSON.parse(localStorage.getItem('GM_' + id)); if (cachedResult) { console.log('从缓存获取到缩略图地址:', cachedResult.imgSrc, '@', cachedResult.refUrl); return cachedResult; } } /* eslint-disable no-await-in-loop */ for (const source of IMAGE_SOURCES) { const startTime = performance.now(); const initialUrl = source.buildUrl?.(id) ?? `${source.url}${id}`; console.group('Source', source.name); try { let currentUrl = initialUrl; let currentContent = null; for (const [index, stage] of source.stages.entries()) { console.group('Stage', index + 1); console.log('请求地址:', currentUrl); try { const response = await GM.xmlHttpRequest({ method: 'GET', url: currentUrl, timeout: 5000, ...(stage.isRedirect ? { redirect: 'follow' } : {}) }); if (stage.isRedirect && response.finalUrl !== currentUrl) { console.log(`↳ 重定向到: ${response.finalUrl}`); } if (response.status !== 200) { handleResponseError(response.status, currentUrl); break; } const doc = new DOMParser().parseFromString(response.responseText, 'text/html'); const element = doc.querySelector(stage.selector); if (!element) { console.warn(`未在地址找到元素: ${currentUrl} : ${stage.selector}`); break; } currentContent = stage.getContent(element); // 如果是最后阶段则直接返回 if (index === source.stages.length - 1) { console.groupEnd(); // 结束 stage 组 console.groupEnd(); // 结束 source 组 if (DEBUG_MODE) { console.log(`${source.name} 获取到地址: ${currentContent} @ ${currentUrl}`); } else { console.log('获取到缩略图地址:', currentContent, '@', currentUrl); const result = { imgSrc: currentContent, refUrl: currentUrl }; localStorage.setItem('GM_' + id, JSON.stringify(result)); return result; } } // 处理下一阶段请求地址 URL currentUrl = stage.isRelative ? resolveUrl(currentUrl, currentContent) : currentContent; if (stage.isRedirect) { currentUrl = response.finalUrl; } if (!currentUrl) break; } catch (error) { console.error('Stage error:', error); throw error; } finally { console.groupEnd(); // 确保每个 stage 组都会关闭 } } } catch (error) { console.error('Source processing failed:', error); } finally { const duration = performance.now(); - startTime; if (DEBUG_MODE) { console.log(`[耗时统计] ${source.name}: ${duration.toFixed(2)}ms`); } console.groupEnd(); // 确保每个 source 组都会关闭 } } /* eslint-enable no-await-in-loop */ throw new Error('所有图源尝试获取失败'); }; const addImage = async (id, selector) => { console.group(id); try { const { imgSrc, refUrl } = await getImage(id); if (!imgSrc) return; const container = document.createElement('div'); container.classList.add('ssc'); const a = document.createElement('a'); a.href = refUrl; a.title = '点击跳转到来源'; const img = document.createElement('img'); img.classList.add('ss'); img.src = imgSrc; img.alt = '点击跳转到来源'; const target = document.querySelector(selector); target.append(container); container.append(a); a.append(img); } catch (error) { console.error('添加缩略图失败:', error.message); return null; } finally { console.groupEnd(); } }; const showImagePopup = (id, imgSrc, refUrl) => { Swal.fire({ theme: 'auto', width: '1224px', draggable: true, animation: false, showCancelButton: true, cancelButtonText: '关闭', imageUrl: imgSrc, imageAlt: '点击跳转到来源', imageWidth: '100%', confirmButtonText: '跳转到来源', preConfirm: () => { window.open(refUrl, '_blank'); return false; }, showDenyButton: true, denyButtonText: '清除缓存地址', preDeny: async () => { localStorage.removeItem('GM_' + id); await Swal.fire({ timer: 1500, theme: 'auto', icon: 'success', title: '操作完成', text: '缩略图缓存地址已清除', showConfirmButton: false, }); return true; } }); }; const showSearchDialog = async () => { const { value: response } = await Swal.fire({ theme: 'auto', title: '查找', width: '20.5em', input: 'text', confirmButtonText: '查找', inputPlaceholder: '输入番号', showLoaderOnConfirm: true, preConfirm: async (id) => { try { const response = await getImage(id); response.id = id; return (Swal.isVisible()) ? response : false; } catch (error) { Swal.showValidationMessage(`请求失败: ${error.message}`); } }, showDenyButton: true, denyButtonColor: '#3085d6', returnInputValueOnDeny: true, denyButtonText: '强制刷新', preDeny: async (id) => { try { const response = await getImage(id, false); response.id = id; return (Swal.isVisible()) ? response : false; } catch (error) { Swal.showValidationMessage(`请求失败: ${error.message}`); } }, showCancelButton: true, cancelButtonText: '取消', // allowOutsideClick: () => !Swal.isLoading() }); if (response) { showImagePopup(response.id, response.imgSrc, response.refUrl); } }; const init = async () => { GM_registerMenuCommand('查找缩略图', showSearchDialog); }; //========================================================================== const main = () => { init(); const hostname = window.location.hostname; const host = DOMAIN_MAPPING?.[hostname] ?? hostname; const site = SITE_CONFIGS[host]; if (!site) { console.log('需添加网站配置到 SITE_CONFIGS'); return; } GM_addStyle(SITE_STYLES[host]); if (site.insertSearchSelector) { const target = document.querySelector(site.insertSearchSelector); if (target) { const button = document.createElement("button"); button.textContent = "查找缩略图"; button.id = 'search-ss'; button.addEventListener("click", showSearchDialog); target.append(button); } } if (site.preCheck) { const target = document.querySelector(site.preCheck.selector); if (target) { site.preCheck.action(target); } } const idElement = document.querySelector(site.idSelector); if (!idElement) return; const id = site.getId(idElement); if (!id) return; addLink(site.insertLinkSelector ? document.querySelector(site.insertLinkSelector) : idElement, site.insertLink, id); addImage(id, site.insertImageSelector); }; main(); })();