import {BasePlugin} from '../core/base-plugin.js'; import {__publicField} from '../core/constants.js'; import {GM_xmlhttpRequest} from 'vite-plugin-monkey/dist/client'; function showCopiedFeedback($btn) { const originalText = $btn.text(); $btn.addClass("copied").text("已复制"); setTimeout((() => { $btn.removeClass("copied").text(originalText); }), 2e3); } function fallbackCopy(text, $btn) { const textarea = document.createElement("textarea"); textarea.value = text; textarea.style.position = "fixed"; document.body.appendChild(textarea); textarea.select(); try { document.execCommand("copy"); showCopiedFeedback($btn); } catch (err) { console.error("复制失败:", err); alert("复制失败,请手动复制链接"); } document.body.removeChild(textarea); } function parseSize(sizeStr) { if (!sizeStr || sizeStr === "未知") return 0; const match = String(sizeStr).replace(/,/g, '').match(/([\d.]+)\s*(TiB|GiB|MiB|KiB|TB|GB|MB|KB|B)?/i); if (!match) return 0; const num = parseFloat(match[1]); const unit = (match[2] || 'B').toUpperCase(); const multipliers = { TIB: 1099511627776, TB: 1099511627776, GIB: 1073741824, GB: 1073741824, MIB: 1048576, MB: 1048576, KIB: 1024, KB: 1024, B: 1 }; return Number.isFinite(num) ? num * (multipliers[unit] || 1) : 0; } function parseDate(dateStr) { if (!dateStr || dateStr === "未知") return 0; const ts = Date.parse(dateStr.replace(/\//g, '-')); return isNaN(ts) ? 0 : ts; } function formatMagnetDate(dateStr) { if (!dateStr) return ''; const match = String(dateStr).match(/(\d{4})[-/](\d{1,2})[-/](\d{1,2})/); if (match) return `${match[1]}-${match[2].padStart(2, '0')}-${match[3].padStart(2, '0')}`; const ts = parseDate(dateStr); return ts ? new Date(ts).toISOString().slice(0, 10) : dateStr; } function classifyQuality(title) { const text = String(title || '').replace(/【[^】]*(?:APP|夸克|UC搜|(?:[a-z0-9-]+\.)+(?:com|net|org|cc|sbs|top))[^】]*】/gi, ' '); const hasCJK = /[\u4e00-\u9fff]/.test(text); const hasJP = /[\u3040-\u309f\u30a0-\u30ff]/.test(text); const isChinese = /(?:[^A-Za-z]|^)FHDC(?:[^A-Za-z]|$)/i.test(text) || /[-_](?:UC|CH?)(?:[^A-Za-z]|$)/.test(text) || /(?:\u4e2d\u5b57|\u4e2d\u6587|\u5b57\u5e55|\u4e2d\u6587\u5b57\u5e55|\u7e41\u9ad4\u4e2d\u5b57|\u7e41\u4f53\u4e2d\u5b57|\u7e41\u9ad4\u4e2d\u6587|\u7e41\u4f53\u4e2d\u6587|\u7e41\u9ad4\u5b57\u5e55|\u7e41\u4f53\u5b57\u5e55|\u7e41\u4e2d|\u7e41\u5b57|\u81ea\u63d0|\u5f81\u7528|\u5fb5\u7528|\u6f22\u5316|\u6c49\u5316|\u5167\u5d4c|\u5185\u5d4c|\u5167\u5c01|\u5185\u5c01|\u96d9\u8a9e|\u53cc\u8bed)/.test(text) || (hasCJK && !hasJP); const is4K = /(?:[^A-Za-z0-9]|^)(?:4K(?:UHD)?|2160P)(?:[^A-Za-z0-9]|$)/i.test(text); const isCracked = /(?:\u7834\u89e3|\u7834\u574f|\u7834\u58de|\u7834\u58ca|\u65e0\u7801|\u7121\u78bc)/.test(text) || /\b(?:uncensored|mosaic)\b/i.test(text); return {isChinese, is4K, isCracked}; } function gmFetch(url, opts = {}) { return new Promise((resolve => { GM_xmlhttpRequest({ method: opts.method || "GET", url: url, headers: opts.headers || {}, data: opts.data, onload: r => { resolve({ loadstuts: r.status >= 200 && r.status < 300, status: r.status, responseText: r.responseText, finalUrl: r.finalUrl || url }); }, onerror: () => resolve({loadstuts: false, status: 0, responseText: '', finalUrl: url}) }); })); } function parseHTML(html) { const parser = new DOMParser(); return parser.parseFromString(html, "text/html"); } class MagnetHubPlugin extends BasePlugin { constructor() { super(...arguments); __publicField(this, "currentEngine", null); __publicField(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: "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"; } async initCss() { return ` `; } createMagnetHub(keyword) { keyword = keyword.replace("FC2-", ""); const $container = $('
'); const $tabs = $('
'); const key = "jhs_magnetHub_selectedEngine"; const savedEngineId = localStorage.getItem(key); let defaultEngineIndex = 0; const $tabBox = $('
'); this.searchEngines.forEach(((engine, index) => { const $tab = $(`
${engine.name}
`); if (savedEngineId && engine.id === savedEngineId) { $tab.addClass("active"); this.currentEngine = engine; defaultEngineIndex = index; } else if (!savedEngineId && engine.id === "Sukebei") { $tab.addClass("active"); this.currentEngine = engine; } $tabBox.append($tab); })); const sortMode = localStorage.getItem("jhs_magnetSort") || "size"; const $sortSelect = $(``); $tabBox.append($sortSelect); $tabs.append($tabBox); $tabs.append(`原网页`); $container.append($tabs); const $resultsContainer = $('
'); $container.append($resultsContainer); $container.on("click", ".magnet-tab", (e => { const engineId = $(e.target).data("engine"); this.currentEngine = this.searchEngines.find((engine => engine.id === engineId)); this.resolveUrl(this.currentEngine, keyword).then((url => { $("#targetBox").attr("href", url); })); localStorage.setItem(key, engineId); $container.find(".magnet-tab").removeClass("active"); $(e.target).addClass("active"); this.searchEngine($resultsContainer, $sortSelect, this.currentEngine, keyword); })); $sortSelect.on("change", () => { localStorage.setItem("jhs_magnetSort", $sortSelect.val()); RerenderSort($resultsContainer, $sortSelect.val()); }); $container.on("click", ".copy-btn", function () { const $btn = $(this), magnet = $btn.data("magnet"); const dnCode = $btn.data("dncode") || ''; const magWithDn = magnet + (dnCode ? `&dn=${encodeURIComponent(dnCode)}` : ''); navigator.clipboard ? navigator.clipboard.writeText(magWithDn).then((() => { showCopiedFeedback($btn); })).catch((() => { fallbackCopy(magWithDn, $btn); })) : fallbackCopy(magWithDn, $btn); }); $container.on("click", ".down-115", (async event => { event.stopPropagation(); const magnet = $(event.currentTarget).data("magnet"); let loadObj = loading(); try { await this.getBean("WangPan115TaskPlugin").handleAddTask(magnet); } catch (e) { show.error("发生错误:" + e); console.error(e); } finally { loadObj.close(); } })); $container.on("click", ".check-btn", (async event => { event.stopPropagation(); const magnet = $(event.currentTarget).data("magnet"); this.checkWhatslink(magnet); })); this.searchEngine($resultsContainer, $sortSelect, this.currentEngine || this.searchEngines[defaultEngineIndex], keyword); this.resolveUrl(this.currentEngine || this.searchEngines[defaultEngineIndex], keyword).then((url => { $("#targetBox").attr("href", url); })); return $container; } searchEngine($container, $sortSelect, engine, keyword) { this.currentEngine = engine; this.currentKeyword = keyword; $container.html(`
正在从 ${engine.name} 搜索 "${keyword}"...
`); const cacheKey = `${engine.name}_${keyword}`; sessionStorage.getItem(cacheKey); this.resolveUrl(engine, keyword).then((url => { const requestOpts = {method: "GET", url: url}; if (engine.parseHtml) { GM_xmlhttpRequest(Object.assign(requestOpts, { method: "GET", url: url, onload: async response => { try { 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); this.displayResults($container, results, engine.name); } catch (e) { this.showError($container, `解析 ${engine.name} 结果失败: ${e.message}`); } }, onerror: error => { this.showError($container, `从 ${engine.name} 获取数据失败: ${error.statusText}`); } })); } if (engine.parseJson) { engine.parseJson.call(this, $container, $sortSelect, engine, keyword, cacheKey); } })); } showError($container, message) { $container.html(`
${message} 🔄 刷新
`); const $select = $container.closest(".magnet-container").find(".magnet-sort-select"); $container.find("#retrySearch").on("click", (e => { e.preventDefault(); this.searchEngine($container, $select, this.currentEngine, this.currentKeyword); })); } displayResults($container, results, engineName) { $container.empty(); const sortMode = localStorage.getItem("jhs_magnetSort") || "size"; if (0 !== results.length) { results = this.sortMagnetData(results, sortMode); const $table = $(`
名称 大小 日期 操作
`); const $tbody = $table.find("tbody"); results.forEach((result => { const quality = classifyQuality(result.title); let badges = ''; if (quality.is4K) badges += '4K'; if (quality.isChinese) badges += '中字'; if (quality.isCracked) badges += '破解'; const displayDate = formatMagnetDate(result.date); const dnCode = extractCode(result.title); const $row = $(`
${badges} ${result.title}
${result.size || '未知'} ${displayDate || '未知'} `); $tbody.append($row); })); $container.append($table); } else { $container.append(`
无搜索结果 🔄 刷新
`); $("#retrySearch").on("click", (e => { e.preventDefault(); this.searchEngine($container, $container.closest(".magnet-container").find(".magnet-sort-select"), this.currentEngine, this.currentKeyword); })); } } sortMagnetData(data, mode) { return [...data].sort((a, b) => { const sizeDelta = parseSize(b.size) - parseSize(a.size); if (mode === 'size') return sizeDelta; const aTime = parseDate(a.date); const bTime = parseDate(b.date); if (!aTime && !bTime) return sizeDelta; if (!aTime) return 1; if (!bTime) return -1; const timeDelta = mode === 'oldest' ? aTime - bTime : bTime - aTime; return timeDelta || sizeDelta; }); } async resolveUrl(engine, keyword) { 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 = ''; document.body.appendChild(overlay); try { const url = `https://whatslink.info/api/v1/link?url=${encodeURIComponent(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.remove(); this.showWhatslinkModal({ error: e.message || '查询失败', name: '查询失败', type: '-', file_type: '-', size: 0, count: '-', screenshots: [] }, magnet); } } 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 = ``; 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 = `截图${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) { 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").first(); 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) { const $dom = utils.htmlTo$dom(html), results = []; $dom.find("tr.default, tr.success").each(((i, el) => { const $el = $(el); const title = $el.find("td:nth-child(2) a").first().attr("title") || $el.find("td:nth-child(2) a").first().text().trim(); if (!title) return; const magnet = $el.find("td:nth-child(3) a:last-child").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 parseOmag(html, keyword) { const doc = parseHTML(html); const base = "https://xn--mag-zna.net"; const entries = [...doc.querySelectorAll('tr')].map(row => { const titleA = row.querySelector('td.result-title a[href^="/!"]'); if (!titleA) return null; const href = titleA.getAttribute('href') || ''; if (!href) return null; let src; try { src = new URL(href, base).href; } catch (_) { return null; } const title = titleA.textContent.trim(); const meta = [...row.querySelectorAll('.result-meta > div')].map(item => item.textContent.trim()); return {title, size: meta[0] || '', date: meta[1] || '', src}; }).filter(Boolean); const searchUrl = `https://xn--mag-zna.net/search?q=${encodeURIComponent(keyword)}`; const results = []; for (const entry of entries) { try { const r = await gmFetch(entry.src, { headers: {Referer: searchUrl} }); if (!r.loadstuts) continue; const detailDoc = parseHTML(r.responseText); const magnetInput = detailDoc.querySelector('#input-magnet'); const magnetA = detailDoc.querySelector('a[href^="magnet:"]'); const maglink = (magnetInput?.getAttribute('value') || magnetA?.getAttribute('href') || magnetA?.href || '').trim(); if (!maglink || !/^magnet:\?xt=urn:btih:/i.test(maglink)) continue; results.push({title: entry.title, magnet: maglink, size: entry.size, date: entry.date}); } catch (_) { } } return results; } parseBTSearch($container, $sortSelect, engine, keyword, cacheKey) { const _this = this; const apiUrl = engine.url.replace("{keyword}", encodeURIComponent(keyword)); const timestamp = Math.floor(Date.now() / 1000).toString(); const nonce = randomString(8); const parts = [`timestamp=${timestamp}`, `nonce=${nonce}`, `keyword=${keyword}`, `limit=10`, `offset=0`, `sort=size`, `sort_type=desc`]; const signText = `${parts.sort().join('&')}&key=long2ice`; const sign = window.md5 ? window.md5(signText).toUpperCase() : ''; GM_xmlhttpRequest({ method: "GET", url: apiUrl, headers: { Accept: 'application/json', Referer: engine.targetPage.replace("{keyword}", encodeURIComponent(keyword)), 'x-timestamp': timestamp, 'x-nonce': nonce, 'x-sign': sign }, onload: response => { try { const json = JSON.parse(response.responseText); const items = Array.isArray(json?.data) ? json.data : Array.isArray(json?.data?.data) ? json.data.data : []; const results = items.map(item => { const title = String(item?.name || '').replace(/<[^>]+>/g, '').replace(/\s+/g, ' ').trim(); const hash = String(item?.hash || '').replace(/^magnet:\?xt=urn:btih:/i, '').replace(/[^a-z0-9]/gi, ''); if (!/^[a-f0-9]{32,40}$/i.test(hash)) return null; const size = formatBytes(item?.size); const date = item?.created_at || item?.created_time || item?.create_time || ''; return { title: title || `magnet:?xt=urn:btih:${hash}`, magnet: `magnet:?xt=urn:btih:${hash}`, size, date }; }).filter(Boolean); results.length > 0 && sessionStorage.setItem(cacheKey, JSON.stringify(results)); $container.data("_magnetData", results); $sortSelect.prop("disabled", results.length === 0); _this.displayResults($container, results, engine.name); } catch (e) { _this.showError($container, `解析 ${engine.name} 结果失败: ${e.message}`); } }, onerror: error => { _this.showError($container, `从 ${engine.name} 获取数据失败: ${error.statusText}`); } }); } parseSokitty(html, keyword) { const doc = parseHTML(html); const base = "https://w1.sokitty.me"; const kwNorm = String(keyword).toUpperCase().replace(/[-_\s]/g, ''); const results = []; doc.querySelectorAll('.panel.search-panel').forEach(panel => { const titleA = panel.querySelector('h3.panel-title > a.list-title'); if (!titleA) return; const href = titleA.getAttribute('href') || ''; if (!href.startsWith('/bt/')) return; const hash = href.replace('/bt/', ''); if (!hash) return; const title = titleA.textContent.trim(); if (!title.toUpperCase().replace(/[-_\s]/g, '').includes(kwNorm)) return; const maglink = `magnet:?xt=urn:btih:${hash}`; const infoItems = [...panel.querySelectorAll('.panel-footer .info-item')]; const size = infoItems[0]?.textContent?.trim() || ''; const date = infoItems[2]?.textContent?.trim() || ''; results.push({title, magnet: maglink, size, date}); }); return results; } } function formatBytes(bytes) { if (!bytes || bytes <= 0) return ''; const units = ['B', 'KB', 'MB', 'GB', 'TB']; let i = 0; let size = bytes; while (size >= 1024 && i < units.length - 1) { size /= 1024; i++; } return size.toFixed(1) + ' ' + units[i]; } function extractCode(text) { if (!text) return null; const patterns = [ /FC2[-\s_]?(?:PPV)?[-\s_]?(\d{6,9})/i, /([A-Z]{2,15})-(\d{2,10})(?:-(\d+))?/i, /([A-Z]{2,15})-([A-Z]{0,2}\d{2,10})/i, /^[A-Z0-9]+[-_](\d{6}[-_]\d{2,3})/i, /(\d{6}[-_]\d{2,3})[-_][A-Z0-9]+$/i, /(? { const sizeDelta = parseSize(b.size) - parseSize(a.size); if (sortMode === 'size') return sizeDelta; const aTime = parseDate(a.date), bTime = parseDate(b.date); if (!aTime && !bTime) return sizeDelta; if (!aTime) return 1; if (!bTime) return -1; const timeDelta = sortMode === 'oldest' ? aTime - bTime : bTime - aTime; return timeDelta || sizeDelta; }); $container.empty(); const $table = $(`
名称 大小 日期 操作
`); const $tbody = $table.find("tbody"); results.forEach((result => { const quality = classifyQuality(result.title); let badges = ''; if (quality.is4K) badges += '4K'; if (quality.isChinese) badges += '中字'; if (quality.isCracked) badges += '破解'; const displayDate = formatMagnetDate(result.date); const dnCode = extractCode(result.title); const $row = $(`
${badges} ${result.title}
${result.size || '未知'} ${displayDate || '未知'} `); $tbody.append($row); })); $container.append($table); } export {MagnetHubPlugin};