mirror of
https://github.com/fireinrain/JHS-enhance.git
synced 2026-10-12 12:46:37 +08:00
修复视频长预览图
This commit is contained in:
Vendored
+1189
-215
File diff suppressed because it is too large
Load Diff
@@ -451,6 +451,124 @@ const mainCss = `
|
|||||||
background: rgba(0, 0, 0, 0.3);
|
background: rgba(0, 0, 0, 0.3);
|
||||||
z-index: 999998;
|
z-index: 999998;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.jhs-preview-overlay {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.85);
|
||||||
|
z-index: 2147483647;
|
||||||
|
display: flex;
|
||||||
|
overflow: auto;
|
||||||
|
cursor: zoom-out;
|
||||||
|
backdrop-filter: blur(5px);
|
||||||
|
}
|
||||||
|
.jhs-preview-img {
|
||||||
|
border-radius: 4px;
|
||||||
|
margin: auto;
|
||||||
|
cursor: zoom-in;
|
||||||
|
max-width: 95vw;
|
||||||
|
max-height: 95vh;
|
||||||
|
object-fit: contain;
|
||||||
|
display: block;
|
||||||
|
box-shadow: 0 0 20px rgba(0, 0, 0, 0.5);
|
||||||
|
}
|
||||||
|
.jhs-preview-img.zoomed {
|
||||||
|
max-width: none;
|
||||||
|
max-height: none;
|
||||||
|
cursor: zoom-out;
|
||||||
|
}
|
||||||
|
.jhs-preview-toolbar {
|
||||||
|
position: fixed;
|
||||||
|
top: 20px;
|
||||||
|
right: 20px;
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
z-index: 2147483648;
|
||||||
|
background: #fff;
|
||||||
|
padding: 8px 12px;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid #e5e7eb;
|
||||||
|
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);
|
||||||
|
}
|
||||||
|
.jhs-preview-btn {
|
||||||
|
padding: 7px 16px;
|
||||||
|
background: #f3f4f6;
|
||||||
|
color: #374151;
|
||||||
|
border: 1px solid #d1d5db;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 500;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.jhs-preview-btn:hover {
|
||||||
|
background: #e5e7eb;
|
||||||
|
border-color: #9ca3af;
|
||||||
|
}
|
||||||
|
.jhs-preview-btn.active {
|
||||||
|
color: #fff;
|
||||||
|
background: #2ecc71;
|
||||||
|
border-color: #2ecc71;
|
||||||
|
}
|
||||||
|
.jhs-loading-overlay {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.7);
|
||||||
|
z-index: 2147483649;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 18px;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, sans-serif;
|
||||||
|
}
|
||||||
|
.jhs-loading-spinner {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.jhs-loading-spinner .spinner {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
margin: 0 auto 12px;
|
||||||
|
border: 3px solid rgba(255, 255, 255, 0.3);
|
||||||
|
border-top-color: #fff;
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: jhs-spin 0.6s linear infinite;
|
||||||
|
}
|
||||||
|
@keyframes jhs-spin {
|
||||||
|
to { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
.jhs-preview-toast {
|
||||||
|
position: fixed;
|
||||||
|
top: 80px;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -20px);
|
||||||
|
z-index: 2147483650;
|
||||||
|
padding: 10px 24px;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, sans-serif;
|
||||||
|
color: #fff;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.3s ease, transform 0.3s ease;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
.jhs-preview-toast.show {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translate(-50%, 0);
|
||||||
|
}
|
||||||
|
.jhs-preview-toast-error {
|
||||||
|
background: rgba(220, 38, 38, 0.9);
|
||||||
|
box-shadow: 0 4px 16px rgba(220, 38, 38, 0.4);
|
||||||
|
}
|
||||||
|
.jhs-preview-toast-info {
|
||||||
|
background: rgba(59, 130, 246, 0.9);
|
||||||
|
box-shadow: 0 4px 16px rgba(59, 130, 246, 0.4);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ class CoverButtonPlugin extends BasePlugin {
|
|||||||
carNum2 = carNum2.replace("FC2-", "");
|
carNum2 = carNum2.replace("FC2-", "");
|
||||||
const imgUrl = await this.getBean("ScreenShotPlugin").getScreenshot(carNum2);
|
const imgUrl = await this.getBean("ScreenShotPlugin").getScreenshot(carNum2);
|
||||||
loadObj.close();
|
loadObj.close();
|
||||||
showImageViewer(imgUrl);
|
showPreviewOverlay(imgUrl, carNum2);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("图片预览出错:", error);
|
console.error("图片预览出错:", error);
|
||||||
show.error("图片预览出错:" + error);
|
show.error("图片预览出错:" + error);
|
||||||
|
|||||||
@@ -269,7 +269,7 @@ class Fc2By123AvPlugin extends BasePlugin {
|
|||||||
$(".screen-container").on("click", (event => {
|
$(".screen-container").on("click", (event => {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
showImageViewer(event.currentTarget);
|
showPreviewOverlay(imgUrl, carNum2);
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|||||||
+1
-1
@@ -151,7 +151,7 @@ class Fc2Plugin extends BasePlugin {
|
|||||||
utils.loopDetector((() => $(".movie-gallery .image-list").length > 0), (async () => {
|
utils.loopDetector((() => $(".movie-gallery .image-list").length > 0), (async () => {
|
||||||
$(".movie-gallery .image-list").prepend(' <a class="tile-item screen-container" style="overflow:hidden;max-height: 150px;max-width:150px; text-align:center;"><div style="margin-top: 50px;color: #000;cursor: auto">正在加载缩略图</div></a> ');
|
$(".movie-gallery .image-list").prepend(' <a class="tile-item screen-container" style="overflow:hidden;max-height: 150px;max-width:150px; text-align:center;"><div style="margin-top: 50px;color: #000;cursor: auto">正在加载缩略图</div></a> ');
|
||||||
const screenShotPlugin = this.getBean("ScreenShotPlugin"), imgUrl = await screenShotPlugin.getScreenshot(carNum2);
|
const screenShotPlugin = this.getBean("ScreenShotPlugin"), imgUrl = await screenShotPlugin.getScreenshot(carNum2);
|
||||||
imgUrl && await screenShotPlugin.addImg("缩略图", imgUrl);
|
imgUrl && await screenShotPlugin.addImg("缩略图", imgUrl, carNum2);
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
handleMagnets(movieId) {
|
handleMagnets(movieId) {
|
||||||
|
|||||||
+454
-115
@@ -1,153 +1,492 @@
|
|||||||
import { BasePlugin } from '../core/base-plugin.js';
|
import { BasePlugin } from '../core/base-plugin.js';
|
||||||
import { __publicField, isJavBus, isJavDb } from '../core/constants.js';
|
import { __publicField, isJavBus, isJavDb } from '../core/constants.js';
|
||||||
import { searchFiles } from './wangpan-115-task.js';
|
|
||||||
|
const Thumbnail = {
|
||||||
|
sources: ['javfree', 'projectjav', 'javstore'],
|
||||||
|
|
||||||
|
cacheKey(code) {
|
||||||
|
return `thumb_cache_v3_${code}`;
|
||||||
|
},
|
||||||
|
|
||||||
|
lookupCode(code) {
|
||||||
|
const text = String(code || '').trim();
|
||||||
|
const fc2 = text.match(/^(?:FC2[-_\s]?(?:PPV[-_\s]?)?)?(\d{6,9})$/i);
|
||||||
|
return fc2 ? fc2[1] : text;
|
||||||
|
},
|
||||||
|
|
||||||
|
sourceOrder() {
|
||||||
|
const savedOrder = ['javfree', 'projectjav', 'javstore'];
|
||||||
|
const seen = new Set();
|
||||||
|
return [...savedOrder, ...this.sources].filter(src => {
|
||||||
|
if (seen.has(src) || typeof this[src] !== 'function') return false;
|
||||||
|
seen.add(src);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
async fetchFromSource(source, code) {
|
||||||
|
try {
|
||||||
|
return await this[source](this.lookupCode(code));
|
||||||
|
} catch (e) {
|
||||||
|
clog.debug(`Thumbnail[${source}] 异常:`, e.message);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
normalizeForCompare(text) {
|
||||||
|
return String(text || '').toLowerCase().replace(/[^a-z0-9]/g, '');
|
||||||
|
},
|
||||||
|
|
||||||
|
isCodeMatched(text, code) {
|
||||||
|
const normalizedText = this.normalizeForCompare(text);
|
||||||
|
const normalizedCode = this.normalizeForCompare(code);
|
||||||
|
return !!normalizedCode && normalizedText.includes(normalizedCode);
|
||||||
|
},
|
||||||
|
|
||||||
|
isDetailMatched(doc, url, code) {
|
||||||
|
const title = doc?.querySelector('title')?.textContent || '';
|
||||||
|
const headings = [...(doc?.querySelectorAll('h1,h2,h3,.entry-title,.movie-title,.post-title') || [])]
|
||||||
|
.map(el => el.textContent || '').join(' ');
|
||||||
|
const bodyText = (doc?.body?.textContent || '').slice(0, 5000);
|
||||||
|
return this.isCodeMatched([url, title, headings, bodyText].join(' '), code);
|
||||||
|
},
|
||||||
|
|
||||||
|
normalizePreviewUrl(url, baseUrl = '') {
|
||||||
|
if (!url) return '';
|
||||||
|
const absolute = /^https?:\/\//i.test(url)
|
||||||
|
? url : (baseUrl ? new URL(url, baseUrl).href : url);
|
||||||
|
return absolute.replace(/^http:/, 'https:');
|
||||||
|
},
|
||||||
|
|
||||||
|
isJavfreePreviewImage(url, code) {
|
||||||
|
const cleanUrl = String(url || '').split('?')[0];
|
||||||
|
const lookupCode = this.lookupCode(code);
|
||||||
|
const isFc2Numeric = /^\d{6,9}$/.test(lookupCode);
|
||||||
|
const fc2ShotPattern = isFc2Numeric
|
||||||
|
? new RegExp(`${lookupCode}_\\d+\\.(?:jpe?g|png|webp)$`, 'i')
|
||||||
|
: null;
|
||||||
|
return this.isCodeMatched(cleanUrl, code) && (
|
||||||
|
/-(?:1080p|demosaic)\.(?:jpe?g|png|webp)$/i.test(cleanUrl) ||
|
||||||
|
(isFc2Numeric && fc2ShotPattern.test(cleanUrl))
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
selectJavfreePreviewUrl(doc, detailUrl, code) {
|
||||||
|
const urls = [...doc.querySelectorAll('p > img[src]')]
|
||||||
|
.map(img => this.normalizePreviewUrl(img.getAttribute('src') || img.src || '', detailUrl))
|
||||||
|
.filter(url => this.isJavfreePreviewImage(url, code));
|
||||||
|
return urls.find(url => /-1080p\./i.test(url)) ||
|
||||||
|
urls.find(url => /-demosaic\./i.test(url)) ||
|
||||||
|
urls.find(url => /_1\.(?:jpe?g|png|webp)$/i.test(url)) || '';
|
||||||
|
},
|
||||||
|
|
||||||
|
async javfree(code) {
|
||||||
|
code = this.lookupCode(code);
|
||||||
|
const cacheKey = this.cacheKey(code);
|
||||||
|
const cached = sessionStorage.getItem(cacheKey);
|
||||||
|
if (cached) return cached;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const html = await gmHttp.get(`https://javfree.me/search/${code}`);
|
||||||
|
const doc = new DOMParser().parseFromString(html, 'text/html');
|
||||||
|
const link = [...doc.querySelectorAll('.entry-title>a')]
|
||||||
|
.find(a => this.isCodeMatched([a.href, a.textContent].join(' '), code))?.href;
|
||||||
|
if (!link) return null;
|
||||||
|
|
||||||
|
const dHtml = await gmHttp.get(link);
|
||||||
|
const dDoc = new DOMParser().parseFromString(dHtml, 'text/html');
|
||||||
|
if (!this.isDetailMatched(dDoc, link, code)) return null;
|
||||||
|
|
||||||
|
const url = this.selectJavfreePreviewUrl(dDoc, link, code);
|
||||||
|
if (url) {
|
||||||
|
sessionStorage.setItem(cacheKey, url);
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async javstore(code) {
|
||||||
|
code = this.lookupCode(code);
|
||||||
|
try {
|
||||||
|
const normalizedCode = code.replace(/^fc2-?/i, '').replace(/-/g, '').toLowerCase();
|
||||||
|
const searchUrl = `https://javstore.net/search?q=${encodeURIComponent(code)}`;
|
||||||
|
const searchHtml = await gmHttp.get(searchUrl);
|
||||||
|
const searchDoc = new DOMParser().parseFromString(searchHtml, 'text/html');
|
||||||
|
|
||||||
|
const candidateLinks = searchDoc.querySelectorAll('a[href*="/"]');
|
||||||
|
const detailUrls = [];
|
||||||
|
for (const link of candidateLinks) {
|
||||||
|
const href = link.getAttribute('href');
|
||||||
|
if (!href) continue;
|
||||||
|
if (href.startsWith('http') && !href.includes('javstore.net')) continue;
|
||||||
|
const urlObj = new URL(href, searchUrl);
|
||||||
|
if (!/javstore\.net$/i.test(urlObj.hostname)) continue;
|
||||||
|
if (/^\/search(?:[/?#]|$)/i.test(urlObj.pathname)) continue;
|
||||||
|
const fullUrl = urlObj.href;
|
||||||
|
const pathLastPart = decodeURIComponent(urlObj.pathname.split('/').pop() || '');
|
||||||
|
const normalizedPath = pathLastPart.toLowerCase().replace(/-/g, '');
|
||||||
|
const looksLikeDetail = /\.html$/i.test(urlObj.pathname) || /^\/\d+[-/]/.test(urlObj.pathname);
|
||||||
|
if (looksLikeDetail && normalizedPath.includes(normalizedCode) && !detailUrls.includes(fullUrl)) {
|
||||||
|
detailUrls.push(fullUrl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (detailUrls.length === 0) return null;
|
||||||
|
|
||||||
|
for (const detailUrl of detailUrls) {
|
||||||
|
const imgUrl = await this._extractImgFromDetail(detailUrl, code);
|
||||||
|
if (imgUrl) return imgUrl;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
} catch (e) {
|
||||||
|
clog.debug('javstore 获取失败', e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async _extractImgFromDetail(detailUrl, code) {
|
||||||
|
try {
|
||||||
|
const detailHtml = await gmHttp.get(detailUrl);
|
||||||
|
const detailDoc = new DOMParser().parseFromString(detailHtml, 'text/html');
|
||||||
|
if (!this.isDetailMatched(detailDoc, detailUrl, code)) return null;
|
||||||
|
|
||||||
|
for (const link of detailDoc.querySelectorAll('a')) {
|
||||||
|
if (link.textContent.includes('CLICK HERE')) {
|
||||||
|
const imgUrl = link.href || link.getAttribute('href') || '';
|
||||||
|
if (imgUrl) return this.normalizePreviewUrl(imgUrl, detailUrl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const img = detailDoc.querySelector('img[src*="_s.jpg"]');
|
||||||
|
if (img) {
|
||||||
|
let src = img.getAttribute('src') || '';
|
||||||
|
if (!src.startsWith('http')) src = new URL(src, detailUrl).href;
|
||||||
|
return this.normalizePreviewUrl(src.replace(/_s\.jpg$/, '_l.jpg'), detailUrl);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
} catch (e) {
|
||||||
|
clog.debug('javstore: 详情页请求失败', detailUrl, e.message);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async projectjav(code) {
|
||||||
|
code = this.lookupCode(code);
|
||||||
|
try {
|
||||||
|
const request = (url) => gmHttp.get(url, {}, {
|
||||||
|
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
|
||||||
|
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
|
||||||
|
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8'
|
||||||
|
});
|
||||||
|
|
||||||
|
const searchUrl = `https://projectjav.com/?searchTerm=${encodeURIComponent(code)}`;
|
||||||
|
const searchHtml = await request(searchUrl);
|
||||||
|
const searchDoc = new DOMParser().parseFromString(searchHtml, 'text/html');
|
||||||
|
|
||||||
|
let detailUrl = '';
|
||||||
|
const allMovieLinks = [...searchDoc.querySelectorAll('a[href*="/movie/"]')];
|
||||||
|
const firstLink = allMovieLinks[0]?.getAttribute('href') || '';
|
||||||
|
if (firstLink) {
|
||||||
|
detailUrl = firstLink.startsWith('http') ? firstLink : `https://projectjav.com${firstLink}`;
|
||||||
|
}
|
||||||
|
if (!detailUrl) return null;
|
||||||
|
|
||||||
|
const detailHtml = await request(detailUrl);
|
||||||
|
const detailDoc = new DOMParser().parseFromString(detailHtml, 'text/html');
|
||||||
|
|
||||||
|
const screenshotLink = [...detailDoc.querySelectorAll('.col-md-12.thumbnail a[data-featherlight="image"], .thumbnail a[data-featherlight="image"]')]
|
||||||
|
.find(a => this.isCodeMatched([a.outerHTML, a.closest('.thumbnail')?.outerHTML, detailUrl].join(' '), code));
|
||||||
|
|
||||||
|
if (screenshotLink) {
|
||||||
|
const thumbImg = screenshotLink.querySelector('img');
|
||||||
|
const href = screenshotLink.getAttribute('href') || '';
|
||||||
|
if (href) return this.normalizePreviewUrl(href, detailUrl);
|
||||||
|
if (thumbImg) {
|
||||||
|
const src = (thumbImg.getAttribute('src') || '').replace(/\?.*$/, '');
|
||||||
|
if (src) return this.normalizePreviewUrl(src, detailUrl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
} catch (e) {
|
||||||
|
clog.debug('[projectjav] 异常:', e.message);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async get(code) {
|
||||||
|
const cacheKey = this.cacheKey(code);
|
||||||
|
const cached = sessionStorage.getItem(cacheKey);
|
||||||
|
if (cached) return {url: cached, source: null};
|
||||||
|
|
||||||
|
let url = null, source = null;
|
||||||
|
for (const src of this.sourceOrder()) {
|
||||||
|
url = await this.fetchFromSource(src, code);
|
||||||
|
if (url) {
|
||||||
|
source = src;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (url) sessionStorage.setItem(cacheKey, url);
|
||||||
|
return {url, source};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let previewOverlay = null;
|
||||||
|
|
||||||
|
function showPreviewOverlay(imgUrl, code, source = null) {
|
||||||
|
if (previewOverlay) {
|
||||||
|
previewOverlay.close();
|
||||||
|
}
|
||||||
|
let currentBlobUrl = null;
|
||||||
|
|
||||||
|
const originalHtmlOverflow = document.documentElement.style.overflow;
|
||||||
|
const originalBodyOverflow = document.body.style.overflow;
|
||||||
|
document.documentElement.style.overflow = 'hidden';
|
||||||
|
document.body.style.overflow = 'hidden';
|
||||||
|
|
||||||
|
const container = document.createElement('div');
|
||||||
|
container.className = 'jhs-preview-overlay';
|
||||||
|
|
||||||
|
const img = document.createElement('img');
|
||||||
|
img.src = imgUrl;
|
||||||
|
img.className = 'jhs-preview-img';
|
||||||
|
img.onclick = (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
img.classList.toggle('zoomed');
|
||||||
|
};
|
||||||
|
img.onerror = () => {
|
||||||
|
img.src = imgUrl;
|
||||||
|
};
|
||||||
|
|
||||||
|
const toolbar = document.createElement('div');
|
||||||
|
toolbar.className = 'jhs-preview-toolbar';
|
||||||
|
|
||||||
|
const createButton = (text, className, onClick) => {
|
||||||
|
const btn = document.createElement('button');
|
||||||
|
btn.className = `jhs-preview-btn ${className}`;
|
||||||
|
btn.textContent = text;
|
||||||
|
btn.onclick = onClick;
|
||||||
|
return btn;
|
||||||
|
};
|
||||||
|
|
||||||
|
const setActiveSource = (activeSource) => {
|
||||||
|
toolbar.querySelectorAll('.jhs-preview-btn').forEach(b => b.classList.remove('active'));
|
||||||
|
const activeBtn = toolbar.querySelector(`.jhs-preview-btn-${activeSource}`);
|
||||||
|
if (activeBtn) activeBtn.classList.add('active');
|
||||||
|
};
|
||||||
|
|
||||||
|
const changeSource = async (src, srcName) => {
|
||||||
|
const loadingOverlay = document.createElement('div');
|
||||||
|
loadingOverlay.className = 'jhs-loading-overlay';
|
||||||
|
loadingOverlay.innerHTML = '<div class="jhs-loading-spinner"><div class="spinner"></div><div>正在加载预览图...</div></div>';
|
||||||
|
document.body.appendChild(loadingOverlay);
|
||||||
|
try {
|
||||||
|
const newUrl = src === 'javfree' ? await Thumbnail.javfree(code)
|
||||||
|
: src === 'projectjav' ? await Thumbnail.projectjav(code)
|
||||||
|
: await Thumbnail.javstore(code);
|
||||||
|
if (newUrl) {
|
||||||
|
if (currentBlobUrl) {
|
||||||
|
URL.revokeObjectURL(currentBlobUrl);
|
||||||
|
currentBlobUrl = null;
|
||||||
|
}
|
||||||
|
img.src = newUrl;
|
||||||
|
setActiveSource(src);
|
||||||
|
} else {
|
||||||
|
showToast(`${srcName} 未找到预览图`, 'error');
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
showToast(`${srcName} 加载失败: ${err.message}`, 'error');
|
||||||
|
} finally {
|
||||||
|
loadingOverlay.remove();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let toastTimer = null;
|
||||||
|
const showToast = (msg, type = 'info') => {
|
||||||
|
const existing = container.querySelector('.jhs-preview-toast');
|
||||||
|
if (existing) existing.remove();
|
||||||
|
if (toastTimer) clearTimeout(toastTimer);
|
||||||
|
|
||||||
|
const toast = document.createElement('div');
|
||||||
|
toast.className = `jhs-preview-toast jhs-preview-toast-${type}`;
|
||||||
|
toast.textContent = msg;
|
||||||
|
container.appendChild(toast);
|
||||||
|
|
||||||
|
requestAnimationFrame(() => toast.classList.add('show'));
|
||||||
|
toastTimer = setTimeout(() => {
|
||||||
|
toast.classList.remove('show');
|
||||||
|
setTimeout(() => toast.remove(), 300);
|
||||||
|
}, 3000);
|
||||||
|
};
|
||||||
|
|
||||||
|
const javfreeBtn = createButton('javfree', 'jhs-preview-btn-javfree javfree', (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
changeSource('javfree', 'javfree');
|
||||||
|
});
|
||||||
|
|
||||||
|
const projectjavBtn = createButton('projectjav', 'jhs-preview-btn-projectjav projectjav', (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
changeSource('projectjav', 'projectjav');
|
||||||
|
});
|
||||||
|
|
||||||
|
const javstoreBtn = createButton('javstore', 'jhs-preview-btn-javstore javstore', (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
changeSource('javstore', 'javstore');
|
||||||
|
});
|
||||||
|
|
||||||
|
const newWindowBtn = createButton('新窗口打开', 'jhs-preview-btn-action action', (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
window.open(img.src);
|
||||||
|
});
|
||||||
|
|
||||||
|
const downloadBtn = createButton('下载', 'jhs-preview-btn-action action', (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
const a = document.createElement('a');
|
||||||
|
a.href = img.src;
|
||||||
|
a.download = `${code}.jpg`;
|
||||||
|
a.click();
|
||||||
|
});
|
||||||
|
|
||||||
|
toolbar.appendChild(javfreeBtn);
|
||||||
|
toolbar.appendChild(projectjavBtn);
|
||||||
|
toolbar.appendChild(javstoreBtn);
|
||||||
|
toolbar.appendChild(newWindowBtn);
|
||||||
|
toolbar.appendChild(downloadBtn);
|
||||||
|
|
||||||
|
if (source === 'javfree') setActiveSource('javfree');
|
||||||
|
else if (source === 'projectjav') setActiveSource('projectjav');
|
||||||
|
else if (source === 'javstore') setActiveSource('javstore');
|
||||||
|
else setActiveSource('javfree');
|
||||||
|
|
||||||
|
container.appendChild(img);
|
||||||
|
|
||||||
|
const closeOverlay = () => {
|
||||||
|
if (container.parentNode) {
|
||||||
|
container.remove();
|
||||||
|
toolbar.remove();
|
||||||
|
document.documentElement.style.overflow = originalHtmlOverflow;
|
||||||
|
document.body.style.overflow = originalBodyOverflow;
|
||||||
|
if (currentBlobUrl) {
|
||||||
|
URL.revokeObjectURL(currentBlobUrl);
|
||||||
|
currentBlobUrl = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
container.onclick = closeOverlay;
|
||||||
|
|
||||||
|
const escHandler = (e) => {
|
||||||
|
if (e.key === 'Escape') {
|
||||||
|
closeOverlay();
|
||||||
|
document.removeEventListener('keydown', escHandler);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
document.addEventListener('keydown', escHandler);
|
||||||
|
|
||||||
|
document.body.appendChild(container);
|
||||||
|
document.body.appendChild(toolbar);
|
||||||
|
previewOverlay = {container, toolbar, close: closeOverlay};
|
||||||
|
}
|
||||||
|
|
||||||
class ScreenShotPlugin extends BasePlugin {
|
class ScreenShotPlugin extends BasePlugin {
|
||||||
getName() {
|
getName() {
|
||||||
return "ScreenShotPlugin";
|
return "ScreenShotPlugin";
|
||||||
}
|
}
|
||||||
|
|
||||||
async handle() {
|
async handle() {
|
||||||
this.loadScreenShot().then();
|
this.loadScreenShot().then();
|
||||||
}
|
}
|
||||||
|
|
||||||
async loadScreenShot() {
|
async loadScreenShot() {
|
||||||
if (!window.isDetailPage) return;
|
if (!window.isDetailPage) return;
|
||||||
if ("yes" !== await storageManager.getSetting("enableLoadScreenShot", "yes")) return;
|
if ("yes" !== await storageManager.getSetting("enableLoadScreenShot", "yes")) return;
|
||||||
let carNum2 = this.getPageInfo().carNum;
|
let carNum2 = this.getPageInfo().carNum;
|
||||||
isJavDb && $(".preview-images .tile-item").first().before(' <a class="tile-item screen-container" style="overflow:hidden;max-height: 215px;text-align:center;"><div style="margin-top: 50px;color: #000;cursor: auto">正在加载缩略图</div></a> ');
|
|
||||||
isJavBus && $("#sample-waterfall .sample-box:first").after(' <a class="sample-box screen-container" style="overflow:hidden; height: 110px; text-align:center;"><div style="margin-top: 30px;color: #000;cursor: auto">正在加载缩略图</div></a> ');
|
isJavDb && $(".preview-images .tile-item").first().before(
|
||||||
|
'<a class="tile-item screen-container" style="overflow:hidden;max-height:215px;text-align:center;">' +
|
||||||
|
'<div style="margin-top:50px;color:#000;cursor:auto">正在加载缩略图</div></a>'
|
||||||
|
);
|
||||||
|
isJavBus && $("#sample-waterfall .sample-box:first").after(
|
||||||
|
'<a class="sample-box screen-container" style="overflow:hidden;height:110px;text-align:center;">' +
|
||||||
|
'<div style="margin-top:30px;color:#000;cursor:auto">正在加载缩略图</div></a>'
|
||||||
|
);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const imgUrl = await this.getScreenshot(carNum2);
|
const result = await Thumbnail.get(carNum2);
|
||||||
this.addImg("缩略图", imgUrl);
|
if (result.url) {
|
||||||
clog.log("加载缩略图:", imgUrl);
|
this.addImg("缩略图", result.url, carNum2, result.source);
|
||||||
|
clog.log("加载缩略图:", result.url, result.source);
|
||||||
|
} else {
|
||||||
|
this.showErrorFallback(carNum2, new Error("所有来源均未找到预览图"));
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.showErrorFallback(carNum2, e);
|
this.showErrorFallback(carNum2, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async getScreenshot(carNum2) {
|
async getScreenshot(carNum2) {
|
||||||
const cacheData = localStorage.getItem("jhs_screenShot") ? JSON.parse(localStorage.getItem("jhs_screenShot")) : {};
|
const result = await Thumbnail.get(carNum2);
|
||||||
if (cacheData[carNum2]) {
|
if (result.url) return result.url;
|
||||||
clog.debug("缓存中存在缩略图:", carNum2, cacheData[carNum2]);
|
throw new Error("未找到预览图");
|
||||||
return cacheData[carNum2];
|
|
||||||
}
|
|
||||||
let imgUrl;
|
|
||||||
try {
|
|
||||||
imgUrl = await Promise.any([ this.getJavStoreScreenShot(carNum2) ]);
|
|
||||||
} catch (e) {
|
|
||||||
clog.error("获取缩略图资源失败:", imgUrl, e);
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
if (!imgUrl) {
|
|
||||||
this.showErrorFallback(carNum2, null);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const httpsIndex = imgUrl.indexOf("https://");
|
|
||||||
-1 !== httpsIndex && (imgUrl = imgUrl.substring(httpsIndex));
|
|
||||||
cacheData[carNum2] = imgUrl;
|
|
||||||
clog.log("缩略图获取成功:", imgUrl);
|
|
||||||
localStorage.setItem("jhs_screenShot", JSON.stringify(cacheData));
|
|
||||||
return imgUrl;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async getJavStoreScreenShot(carNum2) {
|
async getJavStoreScreenShot(carNum2) {
|
||||||
let url = `https://javstore.net/search/${carNum2}.html`;
|
return await Thumbnail.javstore(carNum2);
|
||||||
clog.log("正在解析缩略图:", url);
|
|
||||||
let html = await gmHttp.get(url);
|
|
||||||
const $dom = utils.htmlTo$dom(html);
|
|
||||||
let detailPageUrl = null;
|
|
||||||
$dom.find("#content_news h3 span a").each((function() {
|
|
||||||
if ($(this).attr("title").toLowerCase().includes(carNum2.toLowerCase())) {
|
|
||||||
detailPageUrl = $(this).attr("href");
|
|
||||||
return !1;
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
if (!detailPageUrl) {
|
|
||||||
clog.error("JavStore, 查询番号失败:", url);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
let detailPageHtml = await gmHttp.get(detailPageUrl);
|
|
||||||
const $detailPageDom = utils.htmlTo$dom(detailPageHtml);
|
|
||||||
let imgUrl = $detailPageDom.find("a:contains('CLICK HERE')").attr("href") || $detailPageDom.find("img[src*='_s.jpg']").attr("src");
|
|
||||||
if (!imgUrl) {
|
|
||||||
clog.error("JavStore, 解析预览图失败:", url);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return imgUrl.replace(".th", "");
|
|
||||||
}
|
|
||||||
async getJavBestScreenShot(carNum2) {
|
|
||||||
let url = `https://javbest.net/?s=${carNum2}`;
|
|
||||||
clog.log("正在解析缩略图:", url);
|
|
||||||
let html = await gmHttp.get(url);
|
|
||||||
const $dom = utils.htmlTo$dom(html), href = $dom.find(".app_loop_thumb a").first().attr("href");
|
|
||||||
if (!href) {
|
|
||||||
clog.error("解析JavBest搜索页失败:", url);
|
|
||||||
throw new Error("解析JavBest搜索页失败");
|
|
||||||
}
|
|
||||||
const title = $dom.find(".app_loop_thumb a").first().attr("title");
|
|
||||||
if (!title.toLowerCase().includes(carNum2.toLowerCase())) {
|
|
||||||
clog.error("解析JavBest搜索页失败:", title);
|
|
||||||
throw new Error("解析JavBest搜索页失败");
|
|
||||||
}
|
|
||||||
const detailPageHtml = await gmHttp.get(href);
|
|
||||||
let imgUrl = $(detailPageHtml).find('#content a img[src*="_t.jpg"]').attr("src");
|
|
||||||
if (!imgUrl) {
|
|
||||||
clog.error("解析JavBest缩略图失败:", url);
|
|
||||||
throw new Error("解析JavBest缩略图失败");
|
|
||||||
}
|
|
||||||
imgUrl = imgUrl.replace("_t", "").replace("http:", "https:");
|
|
||||||
return imgUrl;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async getJavFreeScreenShot(carNum2) {
|
async getJavFreeScreenShot(carNum2) {
|
||||||
let url = `https://javfree.me/search/${carNum2}/`, html = await gmHttp.get(url);
|
return await Thumbnail.javfree(carNum2);
|
||||||
const $itemList = utils.htmlTo$dom(html).find("article h2.entry-title a");
|
|
||||||
if (!$itemList || 0 === $itemList.length) {
|
|
||||||
clog.error("解析JavFree搜索页失败:", url);
|
|
||||||
throw new Error("解析JavFree搜索页失败");
|
|
||||||
}
|
|
||||||
let detailPageUrl = $($itemList[0]).attr("href"), detailPageHtml = await gmHttp.get(detailPageUrl);
|
|
||||||
const $imgList = utils.htmlTo$dom(detailPageHtml).find("#main > article > .entry-content > p img");
|
|
||||||
if (!$imgList || 0 === $imgList.length) {
|
|
||||||
clog.error("解析JavFree详情页失败:", detailPageUrl);
|
|
||||||
throw new Error("解析JavFree详情页失败");
|
|
||||||
}
|
|
||||||
const srcList = $imgList.filter((function() {
|
|
||||||
const src = $(this).attr("src");
|
|
||||||
return src && src.toLowerCase().endsWith(".jpeg");
|
|
||||||
})).map((function() {
|
|
||||||
return $(this).attr("src");
|
|
||||||
})).get();
|
|
||||||
console.log(srcList);
|
|
||||||
return srcList.at(-1);
|
|
||||||
}
|
}
|
||||||
addImg(title, imgUrl) {
|
|
||||||
if (imgUrl) {
|
addImg(title, imgUrl, code, source) {
|
||||||
isJavDb && $(".screen-container").html(`<img src="${imgUrl}" alt="${title}" loading="lazy" style="width: 100%;">`);
|
if (!imgUrl) return;
|
||||||
isJavBus && $(".screen-container").html(`<div class="photo-frame"><img src="${imgUrl}" style="height: inherit;width: 100%;" title="${title}" alt="${title}"></div>`);
|
isJavDb && $(".screen-container").html(
|
||||||
$(".screen-container").on("click", (event => {
|
`<img src="${imgUrl}" alt="${title}" loading="lazy" style="width:100%;">`
|
||||||
event.stopPropagation();
|
);
|
||||||
event.preventDefault();
|
isJavBus && $(".screen-container").html(
|
||||||
showImageViewer(event.currentTarget);
|
`<div class="photo-frame"><img src="${imgUrl}" style="height:inherit;width:100%;" title="${title}" alt="${title}"></div>`
|
||||||
}));
|
);
|
||||||
}
|
$(".screen-container").on("click", (event) => {
|
||||||
|
event.stopPropagation();
|
||||||
|
event.preventDefault();
|
||||||
|
showPreviewOverlay(imgUrl, code || title, source);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
showErrorFallback(carNum2, error) {
|
showErrorFallback(carNum2, error) {
|
||||||
var _a2;
|
console.error("获取缩略图失败:", error?.message?.substring?.(0, 100) || error);
|
||||||
console.error("获取缩略图失败:", null == (_a2 = null == error ? void 0 : error.message) ? void 0 : _a2.substring(0, 100));
|
let differentCss = isJavBus ? "margin-top:30px" : "margin-top:50px";
|
||||||
let differentCss = isJavBus ? "margin-top: 30px" : "margin-top: 50px";
|
$(".screen-container").html(
|
||||||
$(".screen-container").html(`<div style="${differentCss}; cursor:auto;color:#000;">获取缩略图失败</div><br/><a href='#' class='retry-link'>点击重试</a> 或 <a class="check-link" href='https://javstore.net/search/${carNum2}.html' target='_blank'>前往确认</a>`).off("click", ".retry-link").off("click", ".check-link").on("click", ".retry-link", (async e => {
|
`<div style="${differentCss};cursor:auto;color:#000;">获取缩略图失败</div>` +
|
||||||
|
`<br/><a href='#' class='retry-link'>点击重试</a> 或 ` +
|
||||||
|
`<a class="check-link" href='https://javstore.net/search?q=${encodeURIComponent(carNum2)}' target='_blank'>前往确认</a>`
|
||||||
|
).off("click", ".retry-link").off("click", ".check-link")
|
||||||
|
.on("click", ".retry-link", async (e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
$(".screen-container").html(`<div style="${differentCss};cursor:auto;color:#000;">正在重新加载...</div>`);
|
$(".screen-container").html(
|
||||||
|
`<div style="${differentCss};cursor:auto;color:#000;">正在重新加载...</div>`
|
||||||
|
);
|
||||||
try {
|
try {
|
||||||
const imgUrl = await this.getScreenshot(carNum2);
|
const result = await Thumbnail.get(carNum2);
|
||||||
this.addImg("缩略图", imgUrl);
|
if (result.url) {
|
||||||
|
this.addImg("缩略图", result.url, carNum2, result.source);
|
||||||
|
} else {
|
||||||
|
this.showErrorFallback(carNum2, new Error("所有来源均未找到预览图"));
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.showErrorFallback(carNum2, err);
|
this.showErrorFallback(carNum2, err);
|
||||||
}
|
}
|
||||||
})).on("click", ".check-link", (async e => {
|
}).on("click", ".check-link", (e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
window.open(`https://javstore.net/search/${carNum2}.html`, "_blank");
|
window.open(`https://javstore.net/search?q=${encodeURIComponent(carNum2)}`, "_blank");
|
||||||
}));
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export { ScreenShotPlugin };
|
export {ScreenShotPlugin, Thumbnail, showPreviewOverlay};
|
||||||
|
window.showPreviewOverlay = showPreviewOverlay;
|
||||||
Reference in New Issue
Block a user