mirror of
https://github.com/fireinrain/JHS-enhance.git
synced 2026-09-28 07:36:38 +08:00
新增验车功能
This commit is contained in:
Vendored
+255
-226
@@ -11137,44 +11137,50 @@ ${err.stack}` : "");
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
__publicField2(this, "currentEngine", null);
|
||||
__publicField2(this, "searchEngines", [{
|
||||
name: "U9A9",
|
||||
id: "u9a9",
|
||||
url: "https://u9a9.com/?type=2&search={keyword}",
|
||||
targetPage: "https://u9a9.com/?type=2&search={keyword}",
|
||||
parseHtml: this.parseU9A9
|
||||
}, {
|
||||
name: "U3C3",
|
||||
id: "u3c3",
|
||||
url: "https://u3c3.com/?search2={search2}&search={keyword}",
|
||||
targetPage: "https://u3c3.com/?search2={search2}&search={keyword}",
|
||||
parseHtml: this.parseU3C3,
|
||||
dynamicSearch2: true
|
||||
}, {
|
||||
__publicField2(this, "searchEngines", [
|
||||
{
|
||||
name: "Sukebei",
|
||||
id: "Sukebei",
|
||||
url: "https://sukebei.nyaa.si/?f=0&c=0_0&q={keyword}",
|
||||
targetPage: "https://sukebei.nyaa.si/?f=0&c=0_0&q={keyword}",
|
||||
parseHtml: this.parseSukebei
|
||||
}, {
|
||||
name: "ØMagnet",
|
||||
id: "omag",
|
||||
url: "https://xn--mag-zna.net/search?q={keyword}",
|
||||
targetPage: "https://xn--mag-zna.net/search?q={keyword}",
|
||||
parseHtml: this.parseOmag
|
||||
}, {
|
||||
},
|
||||
{
|
||||
name: "U9A9",
|
||||
id: "u9a9",
|
||||
url: "https://u9a9.com/?type=2&search={keyword}",
|
||||
targetPage: "https://u9a9.com/?type=2&search={keyword}",
|
||||
parseHtml: this.parseU9A9
|
||||
},
|
||||
{
|
||||
name: "U3C3",
|
||||
id: "u3c3",
|
||||
url: "https://u3c3.com/?search={keyword}",
|
||||
targetPage: "https://u3c3.com/?search={keyword}",
|
||||
parseHtml: this.parseU3C3
|
||||
},
|
||||
{
|
||||
name: "BTSearch",
|
||||
id: "btsearch",
|
||||
url: "https://btsearch.love/api/search?keyword={keyword}&limit=10&offset=0&sort=size&sort_type=desc",
|
||||
targetPage: "https://btsearch.love/search?keyword={keyword}",
|
||||
parseJson: this.parseBTSearch
|
||||
}, {
|
||||
},
|
||||
{
|
||||
name: "SoKitty",
|
||||
id: "sokitty",
|
||||
url: "https://w1.sokitty.me/search?key={keyword}",
|
||||
targetPage: "https://w1.sokitty.me/search?key={keyword}",
|
||||
parseHtml: this.parseSokitty
|
||||
}]);
|
||||
},
|
||||
{
|
||||
name: "ØMagnet",
|
||||
id: "omag",
|
||||
url: "https://xn--mag-zna.net/search?q={keyword}",
|
||||
targetPage: "https://xn--mag-zna.net/search?q={keyword}",
|
||||
parseHtml: this.parseOmag
|
||||
}
|
||||
]);
|
||||
}
|
||||
getName() {
|
||||
return "MagnetHubPlugin";
|
||||
@@ -11224,7 +11230,7 @@ ${err.stack}` : "");
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
.magnet-sort-select {
|
||||
height: 26px;
|
||||
height: 28px;
|
||||
padding: 2px 20px 2px 6px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
@@ -11255,9 +11261,10 @@ ${err.stack}` : "");
|
||||
font-size: 12px;
|
||||
text-align: left;
|
||||
}
|
||||
.magnet-table th.col-size { width: 72px; text-align: center; }
|
||||
.magnet-table th.col-name { max-width: 0; }
|
||||
.magnet-table th.col-size { width: 70px; text-align: center; }
|
||||
.magnet-table th.col-date { width: 90px; text-align: center; }
|
||||
.magnet-table th.col-actions { width: 130px; text-align: center; }
|
||||
.magnet-table th.col-actions { width: 180px; text-align: center; }
|
||||
.magnet-table td {
|
||||
padding: 8px 6px;
|
||||
border-bottom: 1px solid #eee;
|
||||
@@ -11286,6 +11293,7 @@ ${err.stack}` : "");
|
||||
color: #243b67;
|
||||
font-weight: 650;
|
||||
text-decoration: none;
|
||||
line-height: 1;
|
||||
}
|
||||
.magnet-name-cell > a:hover {
|
||||
color: #1d4ed8;
|
||||
@@ -11299,12 +11307,12 @@ ${err.stack}` : "");
|
||||
border-radius: 3px;
|
||||
font-size: 11px;
|
||||
font-weight: 800;
|
||||
line-height: 1.4;
|
||||
line-height: 1;
|
||||
color: #fff;
|
||||
}
|
||||
.magnet-badge.subtitle { background: #16a34a; }
|
||||
.magnet-badge.four-k { background: #2563eb; }
|
||||
.magnet-badge.cracked { background: #be123c; }
|
||||
.magnet-badge.mg-subtitle { background: #16a34a; }
|
||||
.magnet-badge.mg-four-k { background: #2563eb; }
|
||||
.magnet-badge.mg-cracked { background: #be123c; }
|
||||
.magnet-loading {
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
@@ -11328,7 +11336,7 @@ ${err.stack}` : "");
|
||||
background-color: #f0f0f0;
|
||||
color: #555;
|
||||
border: 1px solid #ddd;
|
||||
padding: 3px 8px;
|
||||
padding: 5px 10px;
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
@@ -11374,7 +11382,7 @@ ${err.stack}` : "");
|
||||
.whatslink-overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 10000040;
|
||||
z-index: 2147483700;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@@ -11532,31 +11540,29 @@ ${err.stack}` : "");
|
||||
const key = "jhs_magnetHub_selectedEngine";
|
||||
const savedEngineId = localStorage.getItem(key);
|
||||
let defaultEngineIndex = 0;
|
||||
const $tabBox = $('<div style="display: flex;"></div>');
|
||||
const $tabBox = $('<div style="display: flex; align-items: center;"></div>');
|
||||
this.searchEngines.forEach(((engine, index) => {
|
||||
const $tab = $(`<div class="magnet-tab" data-engine="${engine.id}">${engine.name}</div>`);
|
||||
if (savedEngineId && engine.id === savedEngineId) {
|
||||
$tab.addClass("active");
|
||||
this.currentEngine = engine;
|
||||
defaultEngineIndex = index;
|
||||
} else if (0 === index && !savedEngineId) {
|
||||
} else if (!savedEngineId && engine.id === "Sukebei") {
|
||||
$tab.addClass("active");
|
||||
this.currentEngine = engine;
|
||||
}
|
||||
$tabBox.append($tab);
|
||||
}));
|
||||
$tabs.append($tabBox);
|
||||
$tabs.append(`<a style="margin-right: 20px;margin-top:3px" id="targetBox" href="${this.currentEngine.targetPage.replace("{keyword}", encodeURIComponent(keyword))}" target="_blank">原网页</a>`);
|
||||
$container.append($tabs);
|
||||
const $controls = $('<div class="magnet-controls"></div>');
|
||||
const sortMode = localStorage.getItem("jhs_magnetSort") || "size";
|
||||
const $sortSelect = $(`<select class="magnet-sort-select" disabled>
|
||||
const $sortSelect = $(`<select class="magnet-sort-select" disabled style="margin-left:auto;margin-right:4px;">
|
||||
<option value="size" ${sortMode === "size" ? "selected" : ""}>按大小</option>
|
||||
<option value="newest" ${sortMode === "newest" ? "selected" : ""}>按最新</option>
|
||||
<option value="oldest" ${sortMode === "oldest" ? "selected" : ""}>按最旧</option>
|
||||
</select>`);
|
||||
$controls.append($sortSelect);
|
||||
$container.append($controls);
|
||||
$tabBox.append($sortSelect);
|
||||
$tabs.append($tabBox);
|
||||
$tabs.append(`<a style="margin-right: 20px;margin-top:3px" id="targetBox" href="${this.currentEngine.targetPage.replace("{keyword}", encodeURIComponent(keyword))}" target="_blank">原网页</a>`);
|
||||
$container.append($tabs);
|
||||
const $resultsContainer = $('<div class="magnet-results"></div>');
|
||||
$container.append($resultsContainer);
|
||||
$container.on("click", ".magnet-tab", ((e) => {
|
||||
@@ -11615,17 +11621,13 @@ ${err.stack}` : "");
|
||||
sessionStorage.getItem(cacheKey);
|
||||
this.resolveUrl(engine, keyword).then(((url) => {
|
||||
const requestOpts = {method: "GET", url};
|
||||
if (engine.dynamicSearch2) {
|
||||
const cookie = sessionStorage.getItem("jhs_u3c3_cookie");
|
||||
if (cookie) requestOpts.cookie = cookie;
|
||||
}
|
||||
if (engine.parseHtml) {
|
||||
_GM_xmlhttpRequest(Object.assign(requestOpts, {
|
||||
method: "GET",
|
||||
url,
|
||||
onload: (response) => {
|
||||
onload: async (response) => {
|
||||
try {
|
||||
const results = engine.parseHtml.call(this, response.responseText, keyword);
|
||||
const results = await engine.parseHtml.call(this, response.responseText, keyword);
|
||||
results.length > 0 && sessionStorage.setItem(cacheKey, JSON.stringify(results));
|
||||
$container.data("_magnetData", results);
|
||||
$sortSelect.prop("disabled", results.length === 0);
|
||||
@@ -11651,7 +11653,7 @@ ${err.stack}` : "");
|
||||
results = this.sortMagnetData(results, sortMode);
|
||||
const $table = $(`<table class="magnet-table">
|
||||
<thead><tr>
|
||||
<th>名称</th>
|
||||
<th class="col-name">名称</th>
|
||||
<th class="col-size">大小</th>
|
||||
<th class="col-date">日期</th>
|
||||
<th class="col-actions">操作</th>
|
||||
@@ -11662,9 +11664,9 @@ ${err.stack}` : "");
|
||||
results.forEach(((result) => {
|
||||
const quality = classifyQuality(result.title);
|
||||
let badges = "";
|
||||
if (quality.is4K) badges += '<span class="magnet-badge four-k">4K</span>';
|
||||
if (quality.isChinese) badges += '<span class="magnet-badge subtitle">中字</span>';
|
||||
if (quality.isCracked) badges += '<span class="magnet-badge cracked">破解</span>';
|
||||
if (quality.is4K) badges += '<span class="magnet-badge mg-four-k">4K</span>';
|
||||
if (quality.isChinese) badges += '<span class="magnet-badge mg-subtitle">中字</span>';
|
||||
if (quality.isCracked) badges += '<span class="magnet-badge mg-cracked">破解</span>';
|
||||
const displayDate = formatMagnetDate(result.date);
|
||||
const dnCode = extractCode(result.title);
|
||||
const $row = $(`<tr>
|
||||
@@ -11679,7 +11681,7 @@ ${err.stack}` : "");
|
||||
<td class="col-actions">
|
||||
<button class="magnet-hub-btn copy-btn" data-magnet="${result.magnet}" data-dncode="${dnCode || ""}">复制</button>
|
||||
<button class="magnet-hub-btn check-btn" data-magnet="${result.magnet}">验车</button>
|
||||
<button class="magnet-hub-btn down-btn down-115" data-magnet="${result.magnet}">115</button>
|
||||
<button class="magnet-hub-btn down-btn down-115" data-magnet="${result.magnet}">115离线</button>
|
||||
</td>
|
||||
</tr>`);
|
||||
$tbody.append($row);
|
||||
@@ -11709,157 +11711,181 @@ ${err.stack}` : "");
|
||||
}
|
||||
|
||||
async resolveUrl(engine, keyword) {
|
||||
let url = engine.url.replace("{keyword}", encodeURIComponent(keyword));
|
||||
if (engine.dynamicSearch2) {
|
||||
let search2 = sessionStorage.getItem("jhs_u3c3_search2");
|
||||
if (!search2) {
|
||||
search2 = await this.fetchSearch2();
|
||||
if (search2) sessionStorage.setItem("jhs_u3c3_search2", search2);
|
||||
}
|
||||
url = url.replace("{search2}", search2 || "c2pvbmy2");
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
async fetchSearch2() {
|
||||
const result = await new Promise(((resolve) => {
|
||||
_GM_xmlhttpRequest({
|
||||
method: "GET",
|
||||
url: "https://u3c3.com/",
|
||||
onload: (r) => resolve({html: r.responseText, headers: r.responseHeaders || ""}),
|
||||
onerror: () => resolve(null)
|
||||
});
|
||||
}));
|
||||
if (!result) return null;
|
||||
const cookie = parseSetCookie(result.headers);
|
||||
if (cookie) sessionStorage.setItem("jhs_u3c3_cookie", cookie);
|
||||
const match = result.html.match(/var\s+nmefafej\s*=\s*"([^"]+)"/g);
|
||||
if (!match) return null;
|
||||
for (const m of match) {
|
||||
const val = m.match(/"([^"]+)"/)[1];
|
||||
if (val.length === 8) {
|
||||
console.log("[U3C3] search2:", val);
|
||||
return val;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
return engine.url.replace("{keyword}", encodeURIComponent(keyword));
|
||||
}
|
||||
|
||||
async checkWhatslink(magnet) {
|
||||
var _a;
|
||||
(_a = document.querySelector(".whatslink-overlay")) == null ? void 0 : _a.remove();
|
||||
const overlay = document.createElement("div");
|
||||
overlay.className = "whatslink-overlay";
|
||||
overlay.innerHTML = '<div class="whatslink-loading">正在加载验车数据…</div>';
|
||||
overlay.addEventListener("click", (e) => {
|
||||
if (e.target === overlay) overlay.remove();
|
||||
});
|
||||
overlay.innerHTML = '<div class="whatslink-modal no-shots"><div class="whatslink-loading">正在验车...</div></div>';
|
||||
document.body.appendChild(overlay);
|
||||
try {
|
||||
const url = `https://whatslink.info/api/v1/link?url=${encodeURIComponent(magnet)}`;
|
||||
const r = await gmFetch(url);
|
||||
if (!r.loadstuts) throw new Error("查询失败");
|
||||
const data = JSON.parse(r.responseText);
|
||||
this.showWhatslinkModal(overlay, data, magnet);
|
||||
const r = await gmFetch(url, {timeout: 2e4});
|
||||
if (!r.loadstuts) throw new Error("WhatsLink 请求失败");
|
||||
const data = JSON.parse(r.responseText || "{}");
|
||||
overlay.remove();
|
||||
this.showWhatslinkModal(data, magnet);
|
||||
} catch (e) {
|
||||
overlay.innerHTML = `<div class="whatslink-gallery-scene">
|
||||
<div class="whatslink-gallery-visual">
|
||||
<div class="whatslink-gallery-empty">
|
||||
<div class="whatslink-gallery-empty-icon">⚠</div>
|
||||
<div class="whatslink-gallery-empty-title">验车失败</div>
|
||||
<div class="whatslink-gallery-empty-text">${e.message || "无法获取文件信息"}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
(_a = overlay.querySelector(".whatslink-overlay")) == null ? void 0 : _a.classList.add("no-shots");
|
||||
overlay.remove();
|
||||
this.showWhatslinkModal({
|
||||
error: e.message || "查询失败",
|
||||
name: "查询失败",
|
||||
type: "-",
|
||||
file_type: "-",
|
||||
size: 0,
|
||||
count: "-",
|
||||
screenshots: []
|
||||
}, magnet);
|
||||
}
|
||||
}
|
||||
|
||||
showWhatslinkModal(overlay, data, magnet) {
|
||||
const screenshots = Array.isArray(data.screenshots) ? data.screenshots : [];
|
||||
const hasShots = screenshots.length > 0;
|
||||
const sizeStr = data.size ? formatBytes(data.size) : "-";
|
||||
const fileCount = data.count || "-";
|
||||
overlay.innerHTML = "";
|
||||
const modal = document.createElement("div");
|
||||
modal.className = "whatslink-modal" + (hasShots ? "" : " no-shots");
|
||||
const scene = document.createElement("div");
|
||||
scene.className = "whatslink-gallery-scene";
|
||||
const visual = document.createElement("div");
|
||||
visual.className = "whatslink-gallery-visual";
|
||||
const closeBtn = document.createElement("button");
|
||||
closeBtn.className = "whatslink-gallery-close";
|
||||
closeBtn.innerHTML = "×";
|
||||
closeBtn.addEventListener("click", () => overlay.remove());
|
||||
visual.appendChild(closeBtn);
|
||||
let currentIdx = 0;
|
||||
const hero = document.createElement("img");
|
||||
hero.className = "whatslink-gallery-hero";
|
||||
if (hasShots) {
|
||||
hero.src = screenshots[0];
|
||||
visual.appendChild(hero);
|
||||
const prevBtn = document.createElement("button");
|
||||
prevBtn.className = "whatslink-gallery-arrow whatslink-gallery-prev";
|
||||
prevBtn.innerHTML = "‹";
|
||||
prevBtn.addEventListener("click", (e) => {
|
||||
e.stopPropagation();
|
||||
currentIdx = (currentIdx - 1 + screenshots.length) % screenshots.length;
|
||||
hero.src = screenshots[currentIdx];
|
||||
updateThumbs();
|
||||
updateInfo();
|
||||
});
|
||||
visual.appendChild(prevBtn);
|
||||
const nextBtn = document.createElement("button");
|
||||
nextBtn.className = "whatslink-gallery-arrow whatslink-gallery-next";
|
||||
nextBtn.innerHTML = "›";
|
||||
nextBtn.addEventListener("click", (e) => {
|
||||
e.stopPropagation();
|
||||
currentIdx = (currentIdx + 1) % screenshots.length;
|
||||
hero.src = screenshots[currentIdx];
|
||||
updateThumbs();
|
||||
updateInfo();
|
||||
});
|
||||
visual.appendChild(nextBtn);
|
||||
} else {
|
||||
const emptyDiv = document.createElement("div");
|
||||
emptyDiv.className = "whatslink-gallery-empty";
|
||||
emptyDiv.innerHTML = '<div class="whatslink-gallery-empty-icon">🖼</div><div class="whatslink-gallery-empty-title">暂无预览图</div><div class="whatslink-gallery-empty-text">该磁力链接暂无截图信息</div>';
|
||||
visual.appendChild(emptyDiv);
|
||||
}
|
||||
const info = document.createElement("div");
|
||||
info.className = "whatslink-gallery-info";
|
||||
const updateInfo = () => {
|
||||
info.innerHTML = `<span>${currentIdx + 1} / ${screenshots.length}</span><span>${sizeStr} · ${fileCount} 文件</span>`;
|
||||
};
|
||||
if (hasShots) updateInfo();
|
||||
else info.innerHTML = `<span>${sizeStr} · ${fileCount} 文件</span>`;
|
||||
scene.appendChild(visual);
|
||||
scene.appendChild(info);
|
||||
if (hasShots && screenshots.length > 1) {
|
||||
const thumbs = document.createElement("div");
|
||||
thumbs.className = "whatslink-gallery-thumbs";
|
||||
const updateThumbs2 = () => {
|
||||
thumbs.innerHTML = "";
|
||||
screenshots.forEach((src, i) => {
|
||||
const thumb = document.createElement("button");
|
||||
thumb.className = "whatslink-gallery-thumb" + (i === currentIdx ? " active" : "");
|
||||
const img = document.createElement("img");
|
||||
img.src = src;
|
||||
thumb.appendChild(img);
|
||||
thumb.addEventListener("click", (e) => {
|
||||
e.stopPropagation();
|
||||
currentIdx = i;
|
||||
hero.src = src;
|
||||
updateThumbs2();
|
||||
updateInfo();
|
||||
});
|
||||
thumbs.appendChild(thumb);
|
||||
});
|
||||
};
|
||||
updateThumbs2();
|
||||
scene.appendChild(thumbs);
|
||||
}
|
||||
modal.appendChild(scene);
|
||||
showWhatslinkModal(payload, magnet) {
|
||||
var _a;
|
||||
(_a = document.querySelector(".whatslink-overlay")) == null ? void 0 : _a.remove();
|
||||
const shots = Array.isArray(payload == null ? void 0 : payload.screenshots) ? payload.screenshots.map((item) => item == null ? void 0 : item.screenshot).filter(Boolean) : [];
|
||||
let index = 0;
|
||||
const resourceName = (payload == null ? void 0 : payload.name) || "未知资源";
|
||||
const resourceCount = (payload == null ? void 0 : payload.count) ?? "-";
|
||||
const overlay = document.createElement("div");
|
||||
overlay.className = "whatslink-overlay";
|
||||
const modal = document.createElement("section");
|
||||
modal.className = `whatslink-modal${shots.length ? "" : " no-shots"}`;
|
||||
modal.innerHTML = `<div class="whatslink-gallery-scene">
|
||||
<div class="whatslink-gallery-visual">
|
||||
<button class="whatslink-gallery-close" type="button" aria-label="关闭">×</button>
|
||||
<button class="whatslink-gallery-arrow whatslink-gallery-prev" type="button" aria-label="上一张">‹</button>
|
||||
<img class="whatslink-gallery-hero" alt="截图预览">
|
||||
<button class="whatslink-gallery-arrow whatslink-gallery-next" type="button" aria-label="下一张">›</button>
|
||||
<div class="whatslink-gallery-empty">
|
||||
<div class="whatslink-gallery-empty-icon">?</div>
|
||||
<div class="whatslink-gallery-empty-title">暂无截图</div>
|
||||
<p class="whatslink-gallery-empty-text">当前资源没有可展示的截图,可以结合资源名称和文件数量判断。</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="whatslink-gallery-info">
|
||||
<span class="whatslink-gallery-name"></span>
|
||||
<span class="whatslink-gallery-count"></span>
|
||||
<span class="whatslink-gallery-index"></span>
|
||||
</div>
|
||||
<div class="whatslink-gallery-thumbs"></div>
|
||||
</div>`;
|
||||
overlay.appendChild(modal);
|
||||
document.body.appendChild(overlay);
|
||||
modal.querySelector(".whatslink-gallery-name").textContent = resourceName;
|
||||
modal.querySelector(".whatslink-gallery-count").textContent = `${resourceCount} 个文件`;
|
||||
const visual = modal.querySelector(".whatslink-gallery-visual");
|
||||
const hero = modal.querySelector(".whatslink-gallery-hero");
|
||||
const thumbs = modal.querySelector(".whatslink-gallery-thumbs");
|
||||
const currentIndex = modal.querySelector(".whatslink-gallery-index");
|
||||
const closeButton = modal.querySelector(".whatslink-gallery-close");
|
||||
const prevButton = modal.querySelector(".whatslink-gallery-prev");
|
||||
const nextButton = modal.querySelector(".whatslink-gallery-next");
|
||||
const sizeGallery = () => {
|
||||
if (!shots.length || !hero.naturalWidth || !hero.naturalHeight) return;
|
||||
const infoHeight = modal.querySelector(".whatslink-gallery-info").offsetHeight || 23;
|
||||
const thumbsHeight = thumbs.offsetHeight || 58;
|
||||
const maxHeight = Math.max(180, window.innerHeight - 36 - infoHeight - thumbsHeight - 6);
|
||||
const maxWidth = modal.clientWidth;
|
||||
const ratio = hero.naturalWidth / hero.naturalHeight;
|
||||
const height = Math.min(maxHeight, maxWidth / ratio);
|
||||
const width = height * ratio;
|
||||
visual.style.width = `${width}px`;
|
||||
visual.style.height = `${height}px`;
|
||||
};
|
||||
const positionControls = () => {
|
||||
if (!shots.length || !hero.naturalWidth || !hero.naturalHeight) return;
|
||||
const width = visual.clientWidth;
|
||||
const height = visual.clientHeight;
|
||||
const scale = Math.min(width / hero.naturalWidth, height / hero.naturalHeight);
|
||||
const imageWidth = hero.naturalWidth * scale;
|
||||
const imageHeight = hero.naturalHeight * scale;
|
||||
const imageLeft = (width - imageWidth) / 2;
|
||||
const imageTop = (height - imageHeight) / 2;
|
||||
const closeSize = closeButton.offsetWidth || 32;
|
||||
const arrowSize = prevButton.offsetWidth || 34;
|
||||
const sideGap = 12;
|
||||
closeButton.style.left = `${imageLeft + imageWidth - closeSize - sideGap}px`;
|
||||
closeButton.style.right = "auto";
|
||||
closeButton.style.top = `${imageTop + sideGap}px`;
|
||||
prevButton.style.left = `${imageLeft + sideGap}px`;
|
||||
prevButton.style.right = "auto";
|
||||
prevButton.style.top = `${imageTop + (imageHeight - arrowSize) / 2}px`;
|
||||
nextButton.style.left = "auto";
|
||||
nextButton.style.right = `${width - imageLeft - imageWidth + sideGap}px`;
|
||||
nextButton.style.top = `${imageTop + (imageHeight - arrowSize) / 2}px`;
|
||||
};
|
||||
const render = () => {
|
||||
modal.classList.toggle("has-shots", Boolean(shots.length));
|
||||
if (!shots.length) {
|
||||
currentIndex.textContent = "NO PREVIEW";
|
||||
return;
|
||||
}
|
||||
hero.src = shots[index];
|
||||
const frame = `${index + 1} / ${shots.length}`;
|
||||
currentIndex.textContent = frame;
|
||||
[...thumbs.children].forEach((btn, i) => btn.classList.toggle("active", i === index));
|
||||
if (hero.complete) {
|
||||
sizeGallery();
|
||||
positionControls();
|
||||
}
|
||||
};
|
||||
shots.forEach((url, i) => {
|
||||
const btn = document.createElement("button");
|
||||
btn.type = "button";
|
||||
btn.className = "whatslink-gallery-thumb";
|
||||
btn.innerHTML = `<img src="${url}" alt="截图${i + 1}">`;
|
||||
btn.addEventListener("click", () => {
|
||||
index = i;
|
||||
render();
|
||||
});
|
||||
thumbs.appendChild(btn);
|
||||
});
|
||||
prevButton.addEventListener("click", () => {
|
||||
if (!shots.length) return;
|
||||
index = (index + shots.length - 1) % shots.length;
|
||||
render();
|
||||
});
|
||||
nextButton.addEventListener("click", () => {
|
||||
if (!shots.length) return;
|
||||
index = (index + 1) % shots.length;
|
||||
render();
|
||||
});
|
||||
const onKeydown = (event) => {
|
||||
if (!document.body.contains(overlay)) return;
|
||||
if (event.key === "ArrowLeft" && shots.length) {
|
||||
index = (index + shots.length - 1) % shots.length;
|
||||
render();
|
||||
}
|
||||
if (event.key === "ArrowRight" && shots.length) {
|
||||
index = (index + 1) % shots.length;
|
||||
render();
|
||||
}
|
||||
if (event.key === "Escape") close();
|
||||
};
|
||||
const onResize = () => {
|
||||
sizeGallery();
|
||||
positionControls();
|
||||
};
|
||||
const removeResize = () => window.removeEventListener("resize", onResize);
|
||||
const close = () => {
|
||||
overlay.remove();
|
||||
document.removeEventListener("keydown", onKeydown);
|
||||
removeResize();
|
||||
};
|
||||
closeButton.addEventListener("click", close);
|
||||
overlay.addEventListener("click", (e) => {
|
||||
if (e.target === overlay) close();
|
||||
});
|
||||
hero.addEventListener("load", () => {
|
||||
sizeGallery();
|
||||
positionControls();
|
||||
});
|
||||
window.addEventListener("resize", onResize);
|
||||
document.addEventListener("keydown", onKeydown);
|
||||
render();
|
||||
}
|
||||
|
||||
parseU9A9(html, keyword) {
|
||||
@@ -11883,25 +11909,42 @@ ${err.stack}` : "");
|
||||
return results;
|
||||
}
|
||||
|
||||
parseU3C3(html, keyword) {
|
||||
const $dom = utils.htmlTo$dom(html), results = [];
|
||||
const kwNorm = String(keyword).toUpperCase().replace(/[^A-Z0-9]/g, "");
|
||||
$dom.find(".torrent-list tbody tr").each(((i, el) => {
|
||||
const $el = $(el);
|
||||
if ($el.text().includes("置顶")) return;
|
||||
const $titleA = $el.find("td:nth-child(2) a");
|
||||
const title = $titleA.attr("title") || $titleA.text().trim();
|
||||
if (!title) return;
|
||||
if (kwNorm) {
|
||||
const titleNorm = title.toUpperCase().replace(/[^A-Z0-9]/g, "");
|
||||
if (!titleNorm.includes(kwNorm)) return;
|
||||
}
|
||||
const magnet = $el.find("td:nth-child(3) a[href^='magnet:']").attr("href");
|
||||
const size = $el.find("td:nth-child(4)").text().trim();
|
||||
const date = $el.find("td:nth-child(5)").text().trim();
|
||||
magnet && results.push({title, magnet, size, date});
|
||||
}));
|
||||
return results;
|
||||
async parseU3C3(_html, keyword) {
|
||||
var _a;
|
||||
const base = "https://www.u3c3.com";
|
||||
const referer = base + "/";
|
||||
const home = await gmFetch(base + "/", {
|
||||
headers: {Referer: referer}
|
||||
});
|
||||
if (!home.loadstuts) return [];
|
||||
const homeDoc = parseHTML(home.responseText);
|
||||
const searchScript = [...homeDoc.scripts].map((script) => script.textContent || "").find((text) => text.includes("function search21")) || "";
|
||||
const token = ((_a = searchScript.match(/^\s*var\s+nmefafej\s*=\s*["']([^"']+)["'];?/m)) == null ? void 0 : _a[1]) || "";
|
||||
if (!token) return [];
|
||||
const searchUrl = `${base}/?search2=${encodeURIComponent(token)}&search=${encodeURIComponent(keyword)}`;
|
||||
const r = await gmFetch(searchUrl, {
|
||||
headers: {Referer: referer}
|
||||
});
|
||||
if (!r.loadstuts) return [];
|
||||
const doc = parseHTML(r.responseText);
|
||||
const normalizedKeyword = String(keyword || "").toUpperCase().replace(/[-_\s]/g, "");
|
||||
const data = [...doc.querySelectorAll("table.torrent-list tbody tr.default, table.torrent-list tbody tr.success")].map((row) => {
|
||||
var _a2, _b, _c, _d, _e, _f;
|
||||
const titleA = row.querySelector('td:nth-child(2) a[href*="/view?id="]');
|
||||
const magnetA = row.querySelector('td:nth-child(3) a[href^="magnet:"]');
|
||||
const title = ((_a2 = titleA == null ? void 0 : titleA.getAttribute("title")) == null ? void 0 : _a2.trim()) || ((_b = titleA == null ? void 0 : titleA.textContent) == null ? void 0 : _b.trim()) || "";
|
||||
if (!title || !(magnetA == null ? void 0 : magnetA.href)) return null;
|
||||
if (normalizedKeyword && !title.toUpperCase().replace(/[-_\s]/g, "").includes(normalizedKeyword)) return null;
|
||||
const href = titleA.getAttribute("href") || "";
|
||||
return {
|
||||
title,
|
||||
magnet: magnetA.href,
|
||||
size: ((_d = (_c = row.querySelector("td:nth-child(4)")) == null ? void 0 : _c.textContent) == null ? void 0 : _d.trim()) || "",
|
||||
date: ((_f = (_e = row.querySelector("td:nth-child(5)")) == null ? void 0 : _e.textContent) == null ? void 0 : _f.trim()) || "",
|
||||
src: href ? new URL(href, base).href : searchUrl
|
||||
};
|
||||
}).filter(Boolean);
|
||||
return data;
|
||||
}
|
||||
|
||||
parseSukebei(html, keyword) {
|
||||
@@ -12030,20 +12073,6 @@ ${err.stack}` : "");
|
||||
}
|
||||
}
|
||||
|
||||
function parseSetCookie(headers) {
|
||||
if (!headers) return "";
|
||||
const lines = headers.split("\n");
|
||||
const cookies = [];
|
||||
for (const line of lines) {
|
||||
const match = line.match(/^set-cookie:\s*(.+?)(?:;|$)/i);
|
||||
if (match) {
|
||||
const val = match[1].split(";")[0].trim();
|
||||
if (val) cookies.push(val);
|
||||
}
|
||||
}
|
||||
return cookies.join("; ");
|
||||
}
|
||||
|
||||
function formatBytes(bytes) {
|
||||
if (!bytes || bytes <= 0) return "";
|
||||
const units = ["B", "KB", "MB", "GB", "TB"];
|
||||
@@ -12097,7 +12126,7 @@ ${err.stack}` : "");
|
||||
$container.empty();
|
||||
const $table = $(`<table class="magnet-table">
|
||||
<thead><tr>
|
||||
<th>名称</th>
|
||||
<th class="col-name">名称</th>
|
||||
<th class="col-size">大小</th>
|
||||
<th class="col-date">日期</th>
|
||||
<th class="col-actions">操作</th>
|
||||
@@ -12108,9 +12137,9 @@ ${err.stack}` : "");
|
||||
results.forEach(((result) => {
|
||||
const quality = classifyQuality(result.title);
|
||||
let badges = "";
|
||||
if (quality.is4K) badges += '<span class="magnet-badge four-k">4K</span>';
|
||||
if (quality.isChinese) badges += '<span class="magnet-badge subtitle">中字</span>';
|
||||
if (quality.isCracked) badges += '<span class="magnet-badge cracked">破解</span>';
|
||||
if (quality.is4K) badges += '<span class="magnet-badge mg-four-k">4K</span>';
|
||||
if (quality.isChinese) badges += '<span class="magnet-badge mg-subtitle">中字</span>';
|
||||
if (quality.isCracked) badges += '<span class="magnet-badge mg-cracked">破解</span>';
|
||||
const displayDate = formatMagnetDate(result.date);
|
||||
const dnCode = extractCode(result.title);
|
||||
const $row = $(`<tr>
|
||||
|
||||
+200
-181
@@ -119,10 +119,9 @@ class MagnetHubPlugin extends BasePlugin {
|
||||
}, {
|
||||
name: "U3C3",
|
||||
id: "u3c3",
|
||||
url: "https://u3c3.com/?search2={search2}&search={keyword}",
|
||||
targetPage: "https://u3c3.com/?search2={search2}&search={keyword}",
|
||||
parseHtml: this.parseU3C3,
|
||||
dynamicSearch2: true
|
||||
url: "https://u3c3.com/?search={keyword}",
|
||||
targetPage: "https://u3c3.com/?search={keyword}",
|
||||
parseHtml: this.parseU3C3
|
||||
}, {
|
||||
name: "BTSearch",
|
||||
id: "btsearch",
|
||||
@@ -256,7 +255,7 @@ class MagnetHubPlugin extends BasePlugin {
|
||||
color: #243b67;
|
||||
font-weight: 650;
|
||||
text-decoration: none;
|
||||
line-height: 1;
|
||||
line-height: 1.5;
|
||||
}
|
||||
.magnet-name-cell > a:hover {
|
||||
color: #1d4ed8;
|
||||
@@ -270,7 +269,7 @@ class MagnetHubPlugin extends BasePlugin {
|
||||
border-radius: 3px;
|
||||
font-size: 11px;
|
||||
font-weight: 800;
|
||||
line-height: 1;
|
||||
line-height: 1.5;
|
||||
color: #fff;
|
||||
}
|
||||
.magnet-badge.mg-subtitle { background: #16a34a; }
|
||||
@@ -345,7 +344,7 @@ class MagnetHubPlugin extends BasePlugin {
|
||||
.whatslink-overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 10000040;
|
||||
z-index: 2147483700;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@@ -592,10 +591,6 @@ class MagnetHubPlugin extends BasePlugin {
|
||||
sessionStorage.getItem(cacheKey);
|
||||
this.resolveUrl(engine, keyword).then((url => {
|
||||
const requestOpts = {method: "GET", url: url};
|
||||
if (engine.dynamicSearch2) {
|
||||
const cookie = sessionStorage.getItem("jhs_u3c3_cookie");
|
||||
if (cookie) requestOpts.cookie = cookie;
|
||||
}
|
||||
if (engine.parseHtml) {
|
||||
GM_xmlhttpRequest(Object.assign(requestOpts, {
|
||||
method: "GET",
|
||||
@@ -687,156 +682,179 @@ class MagnetHubPlugin extends BasePlugin {
|
||||
}
|
||||
|
||||
async resolveUrl(engine, keyword) {
|
||||
let url = engine.url.replace("{keyword}", encodeURIComponent(keyword));
|
||||
if (engine.dynamicSearch2) {
|
||||
let search2 = sessionStorage.getItem("jhs_u3c3_search2");
|
||||
if (!search2) {
|
||||
search2 = await this.fetchSearch2();
|
||||
if (search2) sessionStorage.setItem("jhs_u3c3_search2", search2);
|
||||
}
|
||||
url = url.replace("{search2}", search2 || "c2pvbmy2");
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
async fetchSearch2() {
|
||||
const result = await new Promise((resolve => {
|
||||
GM_xmlhttpRequest({
|
||||
method: "GET",
|
||||
url: "https://u3c3.com/",
|
||||
onload: r => resolve({html: r.responseText, headers: r.responseHeaders || ""}),
|
||||
onerror: () => resolve(null)
|
||||
});
|
||||
}));
|
||||
if (!result) return null;
|
||||
const cookie = parseSetCookie(result.headers);
|
||||
if (cookie) sessionStorage.setItem("jhs_u3c3_cookie", cookie);
|
||||
const match = result.html.match(/var\s+nmefafej\s*=\s*"([^"]+)"/g);
|
||||
if (!match) return null;
|
||||
for (const m of match) {
|
||||
const val = m.match(/"([^"]+)"/)[1];
|
||||
if (val.length === 8) {
|
||||
console.log("[U3C3] search2:", val);
|
||||
return val;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
return engine.url.replace("{keyword}", encodeURIComponent(keyword));
|
||||
}
|
||||
|
||||
async checkWhatslink(magnet) {
|
||||
document.querySelector('.whatslink-overlay')?.remove();
|
||||
const overlay = document.createElement('div');
|
||||
overlay.className = 'whatslink-overlay';
|
||||
overlay.innerHTML = '<div class="whatslink-loading">正在加载验车数据…</div>';
|
||||
overlay.addEventListener('click', e => {
|
||||
if (e.target === overlay) overlay.remove();
|
||||
});
|
||||
overlay.innerHTML = '<div class="whatslink-modal no-shots"><div class="whatslink-loading">正在验车...</div></div>';
|
||||
document.body.appendChild(overlay);
|
||||
try {
|
||||
const url = `https://whatslink.info/api/v1/link?url=${encodeURIComponent(magnet)}`;
|
||||
const r = await gmFetch(url);
|
||||
if (!r.loadstuts) throw new Error('查询失败');
|
||||
const data = JSON.parse(r.responseText);
|
||||
this.showWhatslinkModal(overlay, data, magnet);
|
||||
const r = await gmFetch(url, {timeout: 20000});
|
||||
if (!r.loadstuts) throw new Error('WhatsLink 请求失败');
|
||||
const data = JSON.parse(r.responseText || '{}');
|
||||
overlay.remove();
|
||||
this.showWhatslinkModal(data, magnet);
|
||||
} catch (e) {
|
||||
overlay.innerHTML = `<div class="whatslink-gallery-scene">
|
||||
<div class="whatslink-gallery-visual">
|
||||
<div class="whatslink-gallery-empty">
|
||||
<div class="whatslink-gallery-empty-icon">⚠</div>
|
||||
<div class="whatslink-gallery-empty-title">验车失败</div>
|
||||
<div class="whatslink-gallery-empty-text">${e.message || '无法获取文件信息'}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
overlay.querySelector('.whatslink-overlay')?.classList.add('no-shots');
|
||||
overlay.remove();
|
||||
this.showWhatslinkModal({
|
||||
error: e.message || '查询失败',
|
||||
name: '查询失败',
|
||||
type: '-',
|
||||
file_type: '-',
|
||||
size: 0,
|
||||
count: '-',
|
||||
screenshots: []
|
||||
}, magnet);
|
||||
}
|
||||
}
|
||||
|
||||
showWhatslinkModal(overlay, data, magnet) {
|
||||
const screenshots = Array.isArray(data.screenshots) ? data.screenshots : [];
|
||||
const hasShots = screenshots.length > 0;
|
||||
const sizeStr = data.size ? formatBytes(data.size) : '-';
|
||||
const fileCount = data.count || '-';
|
||||
overlay.innerHTML = '';
|
||||
const modal = document.createElement('div');
|
||||
modal.className = 'whatslink-modal' + (hasShots ? '' : ' no-shots');
|
||||
const scene = document.createElement('div');
|
||||
scene.className = 'whatslink-gallery-scene';
|
||||
const visual = document.createElement('div');
|
||||
visual.className = 'whatslink-gallery-visual';
|
||||
const closeBtn = document.createElement('button');
|
||||
closeBtn.className = 'whatslink-gallery-close';
|
||||
closeBtn.innerHTML = '×';
|
||||
closeBtn.addEventListener('click', () => overlay.remove());
|
||||
visual.appendChild(closeBtn);
|
||||
let currentIdx = 0;
|
||||
const hero = document.createElement('img');
|
||||
hero.className = 'whatslink-gallery-hero';
|
||||
if (hasShots) {
|
||||
hero.src = screenshots[0];
|
||||
visual.appendChild(hero);
|
||||
const prevBtn = document.createElement('button');
|
||||
prevBtn.className = 'whatslink-gallery-arrow whatslink-gallery-prev';
|
||||
prevBtn.innerHTML = '‹';
|
||||
prevBtn.addEventListener('click', e => {
|
||||
e.stopPropagation();
|
||||
currentIdx = (currentIdx - 1 + screenshots.length) % screenshots.length;
|
||||
hero.src = screenshots[currentIdx];
|
||||
updateThumbs();
|
||||
updateInfo();
|
||||
});
|
||||
visual.appendChild(prevBtn);
|
||||
const nextBtn = document.createElement('button');
|
||||
nextBtn.className = 'whatslink-gallery-arrow whatslink-gallery-next';
|
||||
nextBtn.innerHTML = '›';
|
||||
nextBtn.addEventListener('click', e => {
|
||||
e.stopPropagation();
|
||||
currentIdx = (currentIdx + 1) % screenshots.length;
|
||||
hero.src = screenshots[currentIdx];
|
||||
updateThumbs();
|
||||
updateInfo();
|
||||
});
|
||||
visual.appendChild(nextBtn);
|
||||
} else {
|
||||
const emptyDiv = document.createElement('div');
|
||||
emptyDiv.className = 'whatslink-gallery-empty';
|
||||
emptyDiv.innerHTML = '<div class="whatslink-gallery-empty-icon">🖼</div><div class="whatslink-gallery-empty-title">暂无预览图</div><div class="whatslink-gallery-empty-text">该磁力链接暂无截图信息</div>';
|
||||
visual.appendChild(emptyDiv);
|
||||
}
|
||||
const info = document.createElement('div');
|
||||
info.className = 'whatslink-gallery-info';
|
||||
const updateInfo = () => {
|
||||
info.innerHTML = `<span>${currentIdx + 1} / ${screenshots.length}</span><span>${sizeStr} · ${fileCount} 文件</span>`;
|
||||
};
|
||||
if (hasShots) updateInfo();
|
||||
else info.innerHTML = `<span>${sizeStr} · ${fileCount} 文件</span>`;
|
||||
scene.appendChild(visual);
|
||||
scene.appendChild(info);
|
||||
if (hasShots && screenshots.length > 1) {
|
||||
const thumbs = document.createElement('div');
|
||||
thumbs.className = 'whatslink-gallery-thumbs';
|
||||
const updateThumbs = () => {
|
||||
thumbs.innerHTML = '';
|
||||
screenshots.forEach((src, i) => {
|
||||
const thumb = document.createElement('button');
|
||||
thumb.className = 'whatslink-gallery-thumb' + (i === currentIdx ? ' active' : '');
|
||||
const img = document.createElement('img');
|
||||
img.src = src;
|
||||
thumb.appendChild(img);
|
||||
thumb.addEventListener('click', e => {
|
||||
e.stopPropagation();
|
||||
currentIdx = i;
|
||||
hero.src = src;
|
||||
updateThumbs();
|
||||
updateInfo();
|
||||
});
|
||||
thumbs.appendChild(thumb);
|
||||
});
|
||||
};
|
||||
updateThumbs();
|
||||
scene.appendChild(thumbs);
|
||||
}
|
||||
modal.appendChild(scene);
|
||||
showWhatslinkModal(payload, magnet) {
|
||||
document.querySelector('.whatslink-overlay')?.remove();
|
||||
const shots = Array.isArray(payload?.screenshots) ? payload.screenshots.map(item => item?.screenshot).filter(Boolean) : [];
|
||||
let index = 0;
|
||||
const resourceName = payload?.name || '未知资源';
|
||||
const resourceCount = payload?.count ?? '-';
|
||||
const overlay = document.createElement('div');
|
||||
overlay.className = 'whatslink-overlay';
|
||||
const modal = document.createElement('section');
|
||||
modal.className = `whatslink-modal${shots.length ? '' : ' no-shots'}`;
|
||||
modal.innerHTML = `<div class="whatslink-gallery-scene">
|
||||
<div class="whatslink-gallery-visual">
|
||||
<button class="whatslink-gallery-close" type="button" aria-label="关闭">×</button>
|
||||
<button class="whatslink-gallery-arrow whatslink-gallery-prev" type="button" aria-label="上一张">‹</button>
|
||||
<img class="whatslink-gallery-hero" alt="截图预览">
|
||||
<button class="whatslink-gallery-arrow whatslink-gallery-next" type="button" aria-label="下一张">›</button>
|
||||
<div class="whatslink-gallery-empty">
|
||||
<div class="whatslink-gallery-empty-icon">?</div>
|
||||
<div class="whatslink-gallery-empty-title">暂无截图</div>
|
||||
<p class="whatslink-gallery-empty-text">当前资源没有可展示的截图,可以结合资源名称和文件数量判断。</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="whatslink-gallery-info">
|
||||
<span class="whatslink-gallery-name"></span>
|
||||
<span class="whatslink-gallery-count"></span>
|
||||
<span class="whatslink-gallery-index"></span>
|
||||
</div>
|
||||
<div class="whatslink-gallery-thumbs"></div>
|
||||
</div>`;
|
||||
overlay.appendChild(modal);
|
||||
document.body.appendChild(overlay);
|
||||
modal.querySelector('.whatslink-gallery-name').textContent = resourceName;
|
||||
modal.querySelector('.whatslink-gallery-count').textContent = `${resourceCount} 个文件`;
|
||||
const visual = modal.querySelector('.whatslink-gallery-visual');
|
||||
const hero = modal.querySelector('.whatslink-gallery-hero');
|
||||
const thumbs = modal.querySelector('.whatslink-gallery-thumbs');
|
||||
const currentIndex = modal.querySelector('.whatslink-gallery-index');
|
||||
const closeButton = modal.querySelector('.whatslink-gallery-close');
|
||||
const prevButton = modal.querySelector('.whatslink-gallery-prev');
|
||||
const nextButton = modal.querySelector('.whatslink-gallery-next');
|
||||
const sizeGallery = () => {
|
||||
if (!shots.length || !hero.naturalWidth || !hero.naturalHeight) return;
|
||||
const infoHeight = modal.querySelector('.whatslink-gallery-info').offsetHeight || 23;
|
||||
const thumbsHeight = thumbs.offsetHeight || 58;
|
||||
const maxHeight = Math.max(180, window.innerHeight - 36 - infoHeight - thumbsHeight - 6);
|
||||
const maxWidth = modal.clientWidth;
|
||||
const ratio = hero.naturalWidth / hero.naturalHeight;
|
||||
const height = Math.min(maxHeight, maxWidth / ratio);
|
||||
const width = height * ratio;
|
||||
visual.style.width = `${width}px`;
|
||||
visual.style.height = `${height}px`;
|
||||
};
|
||||
const positionControls = () => {
|
||||
if (!shots.length || !hero.naturalWidth || !hero.naturalHeight) return;
|
||||
const width = visual.clientWidth;
|
||||
const height = visual.clientHeight;
|
||||
const scale = Math.min(width / hero.naturalWidth, height / hero.naturalHeight);
|
||||
const imageWidth = hero.naturalWidth * scale;
|
||||
const imageHeight = hero.naturalHeight * scale;
|
||||
const imageLeft = (width - imageWidth) / 2;
|
||||
const imageTop = (height - imageHeight) / 2;
|
||||
const closeSize = closeButton.offsetWidth || 32;
|
||||
const arrowSize = prevButton.offsetWidth || 34;
|
||||
const sideGap = 12;
|
||||
closeButton.style.left = `${imageLeft + imageWidth - closeSize - sideGap}px`;
|
||||
closeButton.style.right = 'auto';
|
||||
closeButton.style.top = `${imageTop + sideGap}px`;
|
||||
prevButton.style.left = `${imageLeft + sideGap}px`;
|
||||
prevButton.style.right = 'auto';
|
||||
prevButton.style.top = `${imageTop + (imageHeight - arrowSize) / 2}px`;
|
||||
nextButton.style.left = 'auto';
|
||||
nextButton.style.right = `${width - imageLeft - imageWidth + sideGap}px`;
|
||||
nextButton.style.top = `${imageTop + (imageHeight - arrowSize) / 2}px`;
|
||||
};
|
||||
const render = () => {
|
||||
modal.classList.toggle('has-shots', Boolean(shots.length));
|
||||
if (!shots.length) {
|
||||
currentIndex.textContent = 'NO PREVIEW';
|
||||
return;
|
||||
}
|
||||
hero.src = shots[index];
|
||||
const frame = `${index + 1} / ${shots.length}`;
|
||||
currentIndex.textContent = frame;
|
||||
[...thumbs.children].forEach((btn, i) => btn.classList.toggle('active', i === index));
|
||||
if (hero.complete) {
|
||||
sizeGallery();
|
||||
positionControls();
|
||||
}
|
||||
};
|
||||
shots.forEach((url, i) => {
|
||||
const btn = document.createElement('button');
|
||||
btn.type = 'button';
|
||||
btn.className = 'whatslink-gallery-thumb';
|
||||
btn.innerHTML = `<img src="${url}" alt="截图${i + 1}">`;
|
||||
btn.addEventListener('click', () => {
|
||||
index = i;
|
||||
render();
|
||||
});
|
||||
thumbs.appendChild(btn);
|
||||
});
|
||||
prevButton.addEventListener('click', () => {
|
||||
if (!shots.length) return;
|
||||
index = (index + shots.length - 1) % shots.length;
|
||||
render();
|
||||
});
|
||||
nextButton.addEventListener('click', () => {
|
||||
if (!shots.length) return;
|
||||
index = (index + 1) % shots.length;
|
||||
render();
|
||||
});
|
||||
const onKeydown = event => {
|
||||
if (!document.body.contains(overlay)) return;
|
||||
if (event.key === 'ArrowLeft' && shots.length) {
|
||||
index = (index + shots.length - 1) % shots.length;
|
||||
render();
|
||||
}
|
||||
if (event.key === 'ArrowRight' && shots.length) {
|
||||
index = (index + 1) % shots.length;
|
||||
render();
|
||||
}
|
||||
if (event.key === 'Escape') close();
|
||||
};
|
||||
const onResize = () => {
|
||||
sizeGallery();
|
||||
positionControls();
|
||||
};
|
||||
const removeResize = () => window.removeEventListener('resize', onResize);
|
||||
const close = () => {
|
||||
overlay.remove();
|
||||
document.removeEventListener('keydown', onKeydown);
|
||||
removeResize();
|
||||
};
|
||||
closeButton.addEventListener('click', close);
|
||||
overlay.addEventListener('click', e => {
|
||||
if (e.target === overlay) close();
|
||||
});
|
||||
hero.addEventListener('load', () => {
|
||||
sizeGallery();
|
||||
positionControls();
|
||||
});
|
||||
window.addEventListener('resize', onResize);
|
||||
document.addEventListener('keydown', onKeydown);
|
||||
render();
|
||||
}
|
||||
|
||||
parseU9A9(html, keyword) {
|
||||
@@ -860,25 +878,40 @@ class MagnetHubPlugin extends BasePlugin {
|
||||
return results;
|
||||
}
|
||||
|
||||
parseU3C3(html, keyword) {
|
||||
const $dom = utils.htmlTo$dom(html), results = [];
|
||||
const kwNorm = String(keyword).toUpperCase().replace(/[^A-Z0-9]/g, '');
|
||||
$dom.find(".torrent-list tbody tr").each(((i, el) => {
|
||||
const $el = $(el);
|
||||
if ($el.text().includes("置顶")) return;
|
||||
const $titleA = $el.find("td:nth-child(2) a");
|
||||
const title = $titleA.attr("title") || $titleA.text().trim();
|
||||
if (!title) return;
|
||||
if (kwNorm) {
|
||||
const titleNorm = title.toUpperCase().replace(/[^A-Z0-9]/g, '');
|
||||
if (!titleNorm.includes(kwNorm)) return;
|
||||
}
|
||||
const magnet = $el.find("td:nth-child(3) a[href^='magnet:']").attr("href");
|
||||
const size = $el.find("td:nth-child(4)").text().trim();
|
||||
const date = $el.find("td:nth-child(5)").text().trim();
|
||||
magnet && results.push({title, magnet, size, date});
|
||||
}));
|
||||
return results;
|
||||
async parseU3C3(_html, keyword) {
|
||||
const base = 'https://www.u3c3.com';
|
||||
const referer = base + '/';
|
||||
const home = await gmFetch(base + '/', {
|
||||
headers: {Referer: referer},
|
||||
});
|
||||
if (!home.loadstuts) return [];
|
||||
const homeDoc = parseHTML(home.responseText);
|
||||
const searchScript = [...homeDoc.scripts].map(script => script.textContent || '').find(text => text.includes('function search21')) || '';
|
||||
const token = searchScript.match(/^\s*var\s+nmefafej\s*=\s*["']([^"']+)["'];?/m)?.[1] || '';
|
||||
if (!token) return [];
|
||||
const searchUrl = `${base}/?search2=${encodeURIComponent(token)}&search=${encodeURIComponent(keyword)}`;
|
||||
const r = await gmFetch(searchUrl, {
|
||||
headers: {Referer: referer},
|
||||
});
|
||||
if (!r.loadstuts) return [];
|
||||
const doc = parseHTML(r.responseText);
|
||||
const normalizedKeyword = String(keyword || '').toUpperCase().replace(/[-_\s]/g, '');
|
||||
const data = [...doc.querySelectorAll('table.torrent-list tbody tr.default, table.torrent-list tbody tr.success')].map(row => {
|
||||
const titleA = row.querySelector('td:nth-child(2) a[href*="/view?id="]');
|
||||
const magnetA = row.querySelector('td:nth-child(3) a[href^="magnet:"]');
|
||||
const title = titleA?.getAttribute('title')?.trim() || titleA?.textContent?.trim() || '';
|
||||
if (!title || !magnetA?.href) return null;
|
||||
if (normalizedKeyword && !title.toUpperCase().replace(/[-_\s]/g, '').includes(normalizedKeyword)) return null;
|
||||
const href = titleA.getAttribute('href') || '';
|
||||
return {
|
||||
title,
|
||||
magnet: magnetA.href,
|
||||
size: row.querySelector('td:nth-child(4)')?.textContent?.trim() || '',
|
||||
date: row.querySelector('td:nth-child(5)')?.textContent?.trim() || '',
|
||||
src: href ? new URL(href, base).href : searchUrl,
|
||||
};
|
||||
}).filter(Boolean);
|
||||
return data;
|
||||
}
|
||||
|
||||
parseSukebei(html, keyword) {
|
||||
@@ -1006,20 +1039,6 @@ class MagnetHubPlugin extends BasePlugin {
|
||||
}
|
||||
}
|
||||
|
||||
function parseSetCookie(headers) {
|
||||
if (!headers) return "";
|
||||
const lines = headers.split("\n");
|
||||
const cookies = [];
|
||||
for (const line of lines) {
|
||||
const match = line.match(/^set-cookie:\s*(.+?)(?:;|$)/i);
|
||||
if (match) {
|
||||
const val = match[1].split(";")[0].trim();
|
||||
if (val) cookies.push(val);
|
||||
}
|
||||
}
|
||||
return cookies.join("; ");
|
||||
}
|
||||
|
||||
function formatBytes(bytes) {
|
||||
if (!bytes || bytes <= 0) return '';
|
||||
const units = ['B', 'KB', 'MB', 'GB', 'TB'];
|
||||
|
||||
Reference in New Issue
Block a user