// ==UserScript== // @name 视频收藏助手 - 多站联合版 // @namespace https://gitee.com/xiaobin90/jVideoCollector // @version 1.0.2 // @description 视频收藏助手 - 多站联合版,支持低端影视、厂长资源、嘀嗒影视等影视站点收藏工具 // @author jackMa // @match *://www.didahd.xyz/* // @match *://www.4kcz.com/* // @match *://czzy.top/* // @match *://www.cz4k.com/* // @match *://hantv.net/* // @match *://*.hantv.net/* // @match *://ddys.app/* // @match *://*.ddys.app/* // @match *://ddys.ai/* // @match *://*.ddys.ai/* // @match *://libvio.host/* // @match *://*.libvio.host/* // @match *://www.libvio.mov/* // @match *://*.libvio.mov/* // @match *://www.libvios.com/* // @match *://*.libvios.com/* // @match *://www.libhd.com/* // @match *://*.libhd.com/* // @match *://libviobd.com/* // @match *://*.libviobd.com/* // @match *://www.libvio.in/* // @match *://*.libvio.in/* // @match *://xl02.com.de/* // @match *://*.xl02.com.de/* // @match *://v.xl.in.ua/* // @match *://*.v.xl.in.ua/* // @match *://v.xl01.eu.cc/* // @match *://*.v.xl01.eu.cc/* // @match *://v.xl02.eu.cc/* // @match *://*.v.xl02.eu.cc/* // @match *://v.xl01.cc.ua/* // @match *://*.v.xl01.cc.ua/* // @match *://www.duse2.com/* // @match *://*.duse2.com/* // @match *://www.duse3.com/* // @match *://*.duse3.com/* // @match *://www.duse4.com/* // @match *://*.duse4.com/* // @noframes true // @license MIT // @grant GM_setValue // @grant GM_getValue // @grant GM_registerMenuCommand // @grant GM_xmlhttpRequest // @run-at document-end // ==/UserScript== (() => { // src/drama/sites/dida.js var dida_default = { id: "dida", name: "\u5600\u55D2\u5F71\u89C6", domains: ["www.didahd.xyz"], matches: ["*://www.didahd.xyz/*"], // 跨站跳转搜索路径(域名无关) searchPath: (kw) => "/search/-------------.html?wd=" + encodeURIComponent(kw), pageType() { const p = location.pathname; if (p.startsWith("/detail/")) return "detail"; if (p.startsWith("/play/")) return "play"; if (p.startsWith("/search/")) return "search"; if (document.querySelector(".myui-vodlist")) return "list"; return "other"; }, // 列表页 + 搜索页卡片悬浮收藏 listCardSelector: ".myui-vodlist__box, #searchList li", getListCards() { const cards = []; const seen = /* @__PURE__ */ new Set(); document.querySelectorAll(".myui-vodlist__box").forEach((box) => { if (seen.has(box)) return; const a = box.querySelector("a.myui-vodlist__thumb"); if (!a) return; const name = (a.getAttribute("title") || "").trim(); const cover = a.getAttribute("data-original") || ""; if (name) { seen.add(box); cards.push({ element: box, name, cover, path: a.pathname }); } }); document.querySelectorAll("#searchList li").forEach((li) => { if (seen.has(li)) return; const a = li.querySelector("a.myui-vodlist__thumb"); if (!a) return; const sk = li.querySelector("a.searchkey"); const name = ((sk == null ? void 0 : sk.textContent) || a.getAttribute("title") || "").trim(); const cover = a.getAttribute("data-original") || ""; if (name) { seen.add(li); cards.push({ element: li, name, cover, path: a.pathname }); } }); return cards; }, // 详情页:名称从 .myui-content__detail .title,图片从 img.src,跳转链接从 a.href(播放链接) getDetailInfo() { const img = document.querySelector(".myui-content__thumb img"); const titleEl = document.querySelector(".myui-content__detail .title"); const a = document.querySelector(".myui-content__thumb a"); const name = ((titleEl == null ? void 0 : titleEl.textContent) || "").trim(); if (!name) return null; const cover = (img == null ? void 0 : img.src) || ""; const path = (a == null ? void 0 : a.getAttribute("href")) || location.pathname; return { name, cover, path }; }, // 播放页:名称从 #player-sidebar h3.title(去掉集数 small),链接取当前 url getPlayInfo() { const h3 = document.querySelector("#player-sidebar .myui-panel__head h3.title"); let name = ""; if (h3) { const clone = h3.cloneNode(true); clone.querySelectorAll("small").forEach((s) => s.remove()); name = clone.textContent.trim(); } if (!name) return null; return { name, path: location.pathname }; } }; // src/drama/sites/czzy.js var czzy_default = { id: "czzy", name: "\u5382\u957F\u8D44\u6E90", domains: ["www.4kcz.com", "czzy.top", "www.cz4k.com"], matches: ["*://www.4kcz.com/*", "*://czzy.top/*", "*://www.cz4k.com/*"], searchPath: (kw) => "/boss1O1?q=" + encodeURIComponent(kw), pageType() { const p = location.pathname; if (p.startsWith("/movie/")) return "detail"; if (p.startsWith("/v_play/")) return "play"; if (p.startsWith("/boss1O1")) return "search"; if (document.querySelector(".bt_img, .bt_image")) return "list"; return "other"; }, // 列表页 + 搜索页卡片悬浮收藏 listCardSelector: ".bt_img li, .bt_image li", getListCards() { const cards = []; const seen = /* @__PURE__ */ new Set(); document.querySelectorAll(".bt_img li, .bt_image li").forEach((li) => { if (seen.has(li)) return; const a = li.querySelector("a"); const img = li.querySelector("img"); const dytit = li.querySelector(".dytit"); if (!a) return; const name = ((dytit == null ? void 0 : dytit.textContent) || (img == null ? void 0 : img.alt) || "").trim(); const cover = (img == null ? void 0 : img.getAttribute("data-original")) || (img == null ? void 0 : img.src) || ""; if (name) { seen.add(li); cards.push({ element: li, name, cover, path: a.pathname }); } }); return cards; }, // 详情页:名称从 .dytext h1,图片从 .dyimg img.src,跳转链接从 .paly_list_btn a(播放链接) getDetailInfo() { const img = document.querySelector(".dyimg img"); const h1 = document.querySelector(".dytext h1"); const playA = document.querySelector(".paly_list_btn a"); const name = ((h1 == null ? void 0 : h1.textContent) || "").trim(); if (!name) return null; const cover = (img == null ? void 0 : img.src) || ""; const path = (playA == null ? void 0 : playA.getAttribute("href")) || location.pathname; return { name, cover, path }; }, // 播放页:名称从 .ptit a,链接从 a.href getPlayInfo() { const a = document.querySelector(".ptit a"); if (!a) return null; const name = (a.textContent || "").trim(); if (!name) return null; return { name, path: a.pathname }; } }; // src/drama/util.js function stripParens(s) { return (s || "").replace(/\s*[((][^))]*[))]\s*/g, "").trim(); } function parseBgImage(style) { const m = (style || "").match(/background-image:\s*url\(["']?([^"')]+)["']?\)/i); return m ? m[1] : ""; } // src/drama/sites/hantv.js var hantv_default = { id: "hantv", name: "\u97E9\u5267tv", domains: ["hantv.net", "www.hantv.net"], matches: ["*://hantv.net/*", "*://*.hantv.net/*"], searchPath: (kw) => "/vodsearch.html?wd=" + encodeURIComponent(kw), pageType() { const p = location.pathname; if (p.startsWith("/nf-detail/")) return "detail"; if (p.startsWith("/nf-play/")) return "play"; if (p.startsWith("/vodsearch")) return "search"; if (document.querySelector(".module-items")) return "list"; return "other"; }, // 列表页(module-poster-item) + 搜索页(module-card-item) listCardSelector: "a.module-poster-item, .module-card-item", getListCards() { const cards = []; const seen = /* @__PURE__ */ new Set(); document.querySelectorAll("a.module-poster-item").forEach((a) => { var _a; if (seen.has(a)) return; const name = (((_a = a.querySelector(".module-poster-item-title")) == null ? void 0 : _a.textContent) || "").trim(); const img = a.querySelector("img"); const cover = (img == null ? void 0 : img.getAttribute("data-original")) || (img == null ? void 0 : img.src) || ""; if (name) { seen.add(a); cards.push({ element: a, name, cover, path: a.pathname }); } }); document.querySelectorAll(".module-card-item").forEach((item) => { var _a; if (seen.has(item)) return; const name = (((_a = item.querySelector(".module-card-item-title")) == null ? void 0 : _a.textContent) || "").trim(); const img = item.querySelector("img"); const a = item.querySelector("a.module-card-item-poster"); const cover = (img == null ? void 0 : img.getAttribute("data-original")) || (img == null ? void 0 : img.src) || ""; if (name && a) { seen.add(item); cards.push({ element: item, name, cover, path: a.pathname }); } }); return cards; }, // 详情页:名称从 .module-info-heading h1,图片从 img.src,链接取当前 url getDetailInfo() { const img = document.querySelector(".module-info-poster img"); const h1 = document.querySelector(".module-info-heading h1"); const name = ((h1 == null ? void 0 : h1.textContent) || "").trim(); if (!name) return null; const cover = (img == null ? void 0 : img.src) || (img == null ? void 0 : img.getAttribute("data-original")) || ""; return { name, cover, path: location.pathname }; }, // 播放页:名称从 .module-player-side .module-info-heading a,链接从 a.href getPlayInfo() { const a = document.querySelector(".module-player-side .module-info-heading a"); if (!a) return null; const name = (a.getAttribute("title") || a.textContent || "").trim(); if (!name) return null; return { name, path: a.pathname }; } }; // src/drama/sites/ddys.js var ddys_default = { id: "ddys", name: "\u4F4E\u7AEF\u5F71\u89C6", domains: ["ddys.app", "ddys.ai"], matches: ["*://ddys.app/*", "*://*.ddys.app/*", "*://ddys.ai/*", "*://*.ddys.ai/*"], searchPath: (kw) => "/?s=" + encodeURIComponent(kw) + "&post_type=post", pageType() { if (document.querySelector(".post-title")) return "detail"; if (document.querySelector(".post-box")) return "list"; return "other"; }, // 列表/搜索页卡片 listCardSelector: ".post-box", getListCards() { const cards = []; document.querySelectorAll(".post-box").forEach((box) => { const a = box.querySelector("a"); if (!a) return; const titleEl = box.querySelector(".post-box-title"); const name = stripParens((titleEl == null ? void 0 : titleEl.textContent) || ""); const imgEl = box.querySelector(".post-box-image"); const cover = parseBgImage((imgEl == null ? void 0 : imgEl.getAttribute("style")) || ""); if (name) { cards.push({ element: box, name, cover, path: a.pathname }); if (cover && !box.dataset.ddysCoverBound) { box.dataset.ddysCoverBound = "1"; box.addEventListener("click", () => { try { sessionStorage.setItem("ddys_cover_" + name, cover); } catch (e) { } }); } } }); return cards; }, // 详情页(=播放页):名称从 .post-title(去括号),图片优先用列表页缓存,否则取最后一个 doulist-item 的 img getDetailInfo() { const titleEl = document.querySelector(".post-title"); const name = stripParens((titleEl == null ? void 0 : titleEl.textContent) || ""); if (!name) return null; let cover = ""; try { cover = sessionStorage.getItem("ddys_cover_" + name) || ""; } catch (e) { } if (!cover) { const imgs = document.querySelectorAll(".doulist-item img"); if (imgs.length) cover = imgs[imgs.length - 1].src || ""; } return { name, cover, path: location.pathname }; } // 无 getPlayInfo:详情页即播放页 }; // src/drama/sites/libvio.js var libvio_default = { id: "libvio", name: "LIBVIO", domains: ["libvio.host", "www.libvio.mov", "www.libvios.com", "www.libhd.com", "libviobd.com", "www.libvio.in"], matches: [ "*://libvio.host/*", "*://*.libvio.host/*", "*://www.libvio.mov/*", "*://*.libvio.mov/*", "*://www.libvios.com/*", "*://*.libvios.com/*", "*://www.libhd.com/*", "*://*.libhd.com/*", "*://libviobd.com/*", "*://*.libviobd.com/*", "*://www.libvio.in/*", "*://*.libvio.in/*" ], searchPath: (kw) => "/search/-------------.html?wd=" + encodeURIComponent(kw), pageType() { const p = location.pathname; if (p.startsWith("/detail/")) return "detail"; if (p.startsWith("/w/")) return "play"; if (p.startsWith("/search/")) return "search"; if (document.querySelector(".stui-vodlist")) return "list"; return "other"; }, // 列表/搜索页卡片 listCardSelector: ".stui-vodlist__box", getListCards() { const cards = []; document.querySelectorAll(".stui-vodlist__box").forEach((box) => { const a = box.querySelector("a.stui-vodlist__thumb"); const titleEl = box.querySelector(".stui-vodlist__detail .title"); if (!a) return; const name = ((titleEl == null ? void 0 : titleEl.textContent) || a.getAttribute("title") || "").trim(); const cover = a.getAttribute("data-original") || ""; if (name) cards.push({ element: box, name, cover, path: a.pathname }); }); return cards; }, // 详情页:名称从 .vod-info .title,图片从 .vod-poster img.src,链接从 .vod-info a(播放链接) getDetailInfo() { const img = document.querySelector(".vod-poster img"); const titleEl = document.querySelector(".vod-info .title"); const playA = document.querySelector(".vod-info a"); const name = ((titleEl == null ? void 0 : titleEl.textContent) || "").trim(); if (!name) return null; const cover = (img == null ? void 0 : img.src) || (img == null ? void 0 : img.getAttribute("data-original")) || ""; const path = (playA == null ? void 0 : playA.getAttribute("href")) || location.pathname; return { name, cover, path }; }, // 播放页:名称从 .stui-player__detail .title,链接取当前 url getPlayInfo() { const titleEl = document.querySelector(".stui-player__detail .title"); const name = ((titleEl == null ? void 0 : titleEl.textContent) || "").trim(); if (!name) return null; return { name, path: location.pathname }; } }; // src/drama/sites/xiuluo.js var xiuluo_default = { id: "xiuluo", name: "\u4FEE\u7F57\u5F71\u89C6", domains: ["xl02.com.de", "v.xl.in.ua", "v.xl01.eu.cc", "v.xl02.eu.cc", "v.xl01.cc.ua"], matches: [ "*://xl02.com.de/*", "*://*.xl02.com.de/*", "*://v.xl.in.ua/*", "*://*.v.xl.in.ua/*", "*://v.xl01.eu.cc/*", "*://*.v.xl01.eu.cc/*", "*://v.xl02.eu.cc/*", "*://*.v.xl02.eu.cc/*", "*://v.xl01.cc.ua/*", "*://*.v.xl01.cc.ua/*" ], searchPath: (kw) => "/search/" + encodeURIComponent(kw), pageType() { const p = location.pathname; if (p.startsWith("/play/")) return "play"; if (p.startsWith("/search/")) return "search"; if (document.querySelector(".movie-header, .movie-title")) return "detail"; if (document.querySelector(".movie-card")) return "list"; return "other"; }, // 列表页卡片 listCardSelector: ".movie-card", getListCards() { const cards = []; document.querySelectorAll(".movie-card").forEach((card) => { const a = card.querySelector("a.card-img"); const img = card.querySelector("img"); const h4 = card.querySelector(".card-info h4"); if (!a) return; const name = ((h4 == null ? void 0 : h4.textContent) || "").trim(); const cover = (img == null ? void 0 : img.getAttribute("data-src")) || (img == null ? void 0 : img.src) || ""; if (name) cards.push({ element: card, name, cover, path: a.pathname }); }); return cards; }, // 详情页:名称从 .movie-title(去括号),图片从 .movie-poster img.src,链接取当前 url getDetailInfo() { const img = document.querySelector(".movie-poster img"); const titleEl = document.querySelector(".movie-title"); const name = stripParens((titleEl == null ? void 0 : titleEl.textContent) || ""); if (!name) return null; const cover = (img == null ? void 0 : img.src) || ""; return { name, cover, path: location.pathname }; }, // 播放页:名称从 .player-title a,链接从 a.href getPlayInfo() { const a = document.querySelector(".player-title a"); if (!a) return null; const name = (a.textContent || a.getAttribute("title") || "").trim(); if (!name) return null; return { name, path: a.pathname }; } }; // src/drama/sites/duse.js function pickCover(imgs) { if (!imgs.length) return ""; const real = imgs.filter((img) => img.id !== "noneCoverImg" && !/placeholder/i.test(img.src || "")); const pick = real.length ? real[real.length - 1] : imgs[imgs.length - 1]; return pick.src || pick.getAttribute("data-src") || pick.getAttribute("data-original") || ""; } var duse_default = { id: "duse", name: "\u6BD2\u86C7\u7535\u5F71", domains: ["www.duse2.com", "www.duse3.com", "www.duse4.com"], matches: [ "*://www.duse2.com/*", "*://*.duse2.com/*", "*://www.duse3.com/*", "*://*.duse3.com/*", "*://www.duse4.com/*", "*://*.duse4.com/*" ], // 跨站直达:跳转首页 + hash 传递关键词(站点有 cdndefend 保护 + 动态 t token, // 无法跨域直接构造搜索 URL,由 onPageReady 在首页提取 t 后跳转搜索页) searchPath: (kw) => "/#vc-search=" + encodeURIComponent(kw), // 判断 URL 是否指向播放页(详情页点击播放链接时重新缓存图片) isPlayUrl(href) { return /\/play\//.test(href || ""); }, // 首页加载后检测 hash 中的搜索关键词,从 DOM 提取 t token 后直达搜索页 // 兜底:若无法提取 t,则填入搜索框并模拟提交 onPageReady() { const m = location.hash.match(/vc-search=([^&]+)/); if (!m) return; const kw = decodeURIComponent(m[1]); history.replaceState(null, "", location.pathname + location.search); let attempts = 0; const trySearch = () => { if (attempts++ > 30) return; const tInput = document.querySelector('input[name="t"], input#t'); if (tInput && tInput.value) { location.href = "/search?t=" + encodeURIComponent(tInput.value) + "&k=" + encodeURIComponent(kw); return; } const scripts = document.querySelectorAll("script:not([src])"); for (const s of scripts) { const tm = s.textContent.match(/["']?t["']?\s*[:=]\s*["']([A-Za-z0-9+/=]{16,})["']/); if (tm) { location.href = "/search?t=" + encodeURIComponent(tm[1]) + "&k=" + encodeURIComponent(kw); return; } } let input = document.querySelector('input[type="search"], input[placeholder*="\u641C"], input[placeholder*="Search" i], input[name="wd"], input[name="search"], input[name="q"], input[name="k"]'); if (!input) { input = Array.from(document.querySelectorAll('input[type="text"]')).find( (i) => /search|搜索|搜|片名/.test(i.placeholder || "") || /search|wd|keyword|q|k/.test(i.name || "") ); } if (!input) { const icon = document.querySelector('[class*="search" i] i, [class*="search" i] svg, .search-icon, .icon-search, [aria-label*="search" i]'); if (icon) icon.click(); setTimeout(trySearch, 300); return; } const setter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, "value").set; setter.call(input, kw); input.dispatchEvent(new Event("input", { bubbles: true })); input.dispatchEvent(new Event("change", { bubbles: true })); input.focus(); setTimeout(() => { input.dispatchEvent(new KeyboardEvent("keydown", { key: "Enter", code: "Enter", keyCode: 13, which: 13, bubbles: true })); input.dispatchEvent(new KeyboardEvent("keyup", { key: "Enter", code: "Enter", keyCode: 13, which: 13, bubbles: true })); const btn = document.querySelector('button[type="submit"], .search-btn, [class*="search" i] button'); if (btn) btn.click(); const form = input.closest("form"); if (form) form.dispatchEvent(new Event("submit", { bubbles: true, cancelable: true })); }, 200); }; trySearch(); }, pageType() { const p = location.pathname; if (p.startsWith("/detail/")) return "detail"; if (p.startsWith("/play/")) return "play"; if (p.startsWith("/search")) return "search"; if (document.querySelector("a.v-item")) return "list"; return "other"; }, // 列表页卡片 listCardSelector: "a.v-item", getListCards() { const cards = []; document.querySelectorAll("a.v-item").forEach((a) => { let name = ""; a.querySelectorAll(".v-item-footer .v-item-title").forEach((t) => { if (name) return; if (t.style.display !== "none") name = t.textContent.trim(); }); if (!name) return; const imgs = Array.from(a.querySelectorAll(".v-item-cover img")); const cover = pickCover(imgs); cards.push({ element: a, name, cover, path: a.pathname }); }); return cards; }, // 详情页:名称从 img.alt,图片从 img.src(过滤占位图,兜底 data-src),链接取当前 url getDetailInfo() { const img = document.querySelector(".detail-pic img"); const name = ((img == null ? void 0 : img.getAttribute("alt")) || "").trim(); if (!name) return null; const src = (img == null ? void 0 : img.src) || ""; const cover = src && !/placeholder/i.test(src) ? src : (img == null ? void 0 : img.getAttribute("data-src")) || (img == null ? void 0 : img.getAttribute("data-original")) || src || ""; return { name, cover, path: location.pathname }; }, // 播放页:名称从 .detail-title a,链接从 a.href getPlayInfo() { const a = document.querySelector(".detail-title a"); if (!a) return null; const name = (a.textContent || "").trim(); if (!name) return null; return { name, path: a.pathname }; } }; // src/drama/config.js var DRAMA_STORAGE_KEY = "video_collector_drama_data"; var DRAMA_IMAGE_CACHE_KEY = "video_collector_drama_img_cache"; var DRAMA_LAST_DOMAIN_KEY = "video_collector_drama_last_domain"; var DRAMA_POSITION_KEY = "video_collector_drama_position"; var DRAMA_COLLAPSE_KEY = "video_collector_drama_collapsed"; var IMAGE_CACHE_TTL = 30 * 60 * 1e3; var PAGE_SIZE = 12; // src/drama/domains.js function norm(host) { return (host || "").replace(/^www\./, ""); } var _lastDomainMap = null; function loadMap() { if (_lastDomainMap === null) { try { _lastDomainMap = JSON.parse(GM_getValue(DRAMA_LAST_DOMAIN_KEY, "{}")); } catch (e) { _lastDomainMap = {}; } } return _lastDomainMap; } function recordWorkingDomain(siteId, host) { const map = loadMap(); if (map[siteId] === host) return; map[siteId] = host; GM_setValue(DRAMA_LAST_DOMAIN_KEY, JSON.stringify(map)); } function getLastWorking(siteId) { return loadMap()[siteId] || ""; } function pickDomain(site) { if (!site || !site.domains || !site.domains.length) return ""; const last = getLastWorking(site.id); if (last && site.domains.some((d) => norm(d) === norm(last))) return last; return site.domains[0]; } // src/drama/sites/index.js var dramaSites = [dida_default, czzy_default, hantv_default, ddys_default, libvio_default, xiuluo_default, duse_default]; var _currentSite = void 0; function getCurrentDramaSite() { if (_currentSite !== void 0) return _currentSite; const host = location.hostname; _currentSite = dramaSites.find((s) => s.domains.some((d) => norm(d) === norm(host))) || null; if (_currentSite) recordWorkingDomain(_currentSite.id, host); return _currentSite; } // src/drama/state.js var dramaState = { currentTab: "want", currentPage: { want: 1, watched: 1 }, renderedTabs: /* @__PURE__ */ new Set(), searchKeyword: "" }; // src/drama/storage.js function loadDramaData() { try { const data = GM_getValue(DRAMA_STORAGE_KEY, ""); return data ? JSON.parse(data) : { want: [], watched: [] }; } catch (e) { return { want: [], watched: [] }; } } function saveDramaData(data) { GM_setValue(DRAMA_STORAGE_KEY, JSON.stringify(data)); } function findDrama(name, list) { return list.findIndex((item) => item.name === name); } function prune(cache) { const now = Date.now(); for (const k in cache) { if (!cache[k] || typeof cache[k].ts !== "number" || now - cache[k].ts > IMAGE_CACHE_TTL) { delete cache[k]; } } return cache; } function loadImageCache() { try { return prune(JSON.parse(GM_getValue(DRAMA_IMAGE_CACHE_KEY, "{}"))); } catch (e) { return {}; } } function setImageCache(name, cover) { if (!name || !cover) return; const cache = prune(loadImageCache()); cache[name] = { cover, ts: Date.now() }; GM_setValue(DRAMA_IMAGE_CACHE_KEY, JSON.stringify(cache)); } function consumeImageCache(name) { const cache = prune(loadImageCache()); const entry = cache[name]; if (!entry) return ""; const cover = entry.cover; delete cache[name]; GM_setValue(DRAMA_IMAGE_CACHE_KEY, JSON.stringify(cache)); return cover; } function exportDramaData() { const data = loadDramaData(); const blob = new Blob([JSON.stringify(data, null, 2)], { type: "application/json" }); const url = URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; a.download = `\u5F71\u89C6\u6536\u85CF\u5907\u4EFD_${(/* @__PURE__ */ new Date()).toLocaleDateString().replace(/\//g, "-")}.json`; a.click(); URL.revokeObjectURL(url); } function importDramaData(callback) { const input = document.createElement("input"); input.type = "file"; input.accept = "application/json"; input.onchange = (e) => { const file = e.target.files[0]; if (!file) return; const reader = new FileReader(); reader.onload = (ev) => { try { const data = JSON.parse(ev.target.result); if (data.want && data.watched) { if (confirm("\u5BFC\u5165\u5C06\u8986\u76D6\u73B0\u6709\u5F71\u89C6\u6536\u85CF\u6570\u636E\uFF0C\u662F\u5426\u7EE7\u7EED\uFF1F")) { saveDramaData(data); callback && callback(); alert("\u5BFC\u5165\u6210\u529F\uFF01"); } } else { alert("\u6587\u4EF6\u683C\u5F0F\u4E0D\u6B63\u786E"); } } catch (err) { alert("\u6587\u4EF6\u89E3\u6790\u5931\u8D25"); } }; reader.readAsText(file); }; input.click(); } // src/drama/collection.js function getCurrentDramaName() { const site = getCurrentDramaSite(); if (!site) return ""; const t = site.pageType(); if (t === "detail" && site.getDetailInfo) return (site.getDetailInfo() || {}).name || ""; if (t === "play" && site.getPlayInfo) return (site.getPlayInfo() || {}).name || ""; return ""; } function cacheCurrentDetailImage() { const site = getCurrentDramaSite(); if (!site || site.pageType() !== "detail" || !site.getDetailInfo) return; const info = site.getDetailInfo(); if (info && info.name && info.cover) setImageCache(info.name, info.cover); } function isDramaFavorited(name, type) { const data = loadDramaData(); return findDrama(name, data[type]) > -1; } function toggleDramaFavorite(drama, type) { const data = loadDramaData(); const otherType = type === "want" ? "watched" : "want"; const idx = findDrama(drama.name, data[type]); const otherIdx = findDrama(drama.name, data[otherType]); if (otherIdx > -1) data[otherType].splice(otherIdx, 1); if (idx > -1) { data[type].splice(idx, 1); } else { data[type].unshift(drama); } saveDramaData(data); dramaState.renderedTabs.clear(); } function toggleDrama(type) { const site = getCurrentDramaSite(); if (!site) return; const t = site.pageType(); let info = null; if (t === "detail") info = site.getDetailInfo ? site.getDetailInfo() : null; else if (t === "play") info = site.getPlayInfo ? site.getPlayInfo() : null; if (!info || !info.name) return; const name = info.name; const data = loadDramaData(); const idx = findDrama(name, data[type]); if (idx > -1) { data[type].splice(idx, 1); saveDramaData(data); } else { let cover = info.cover || ""; if (t === "detail") setImageCache(name, cover); if (t === "play") cover = consumeImageCache(name) || cover; const otherType = type === "want" ? "watched" : "want"; const otherIdx = findDrama(name, data[otherType]); if (otherIdx > -1) data[otherType].splice(otherIdx, 1); data[type].unshift({ name, cover, path: info.path || location.pathname, siteId: site.id, addTime: Date.now() }); saveDramaData(data); } dramaState.renderedTabs.clear(); updateDramaButtonState(); } function deleteDrama(name, type) { const data = loadDramaData(); const idx = findDrama(name, data[type]); if (idx > -1) { data[type].splice(idx, 1); saveDramaData(data); dramaState.renderedTabs.clear(); renderDramaActiveList(); updateDramaButtonState(); } } function moveDrama(name, fromType, toType) { const data = loadDramaData(); const idx = findDrama(name, data[fromType]); if (idx === -1) return; const item = data[fromType].splice(idx, 1)[0]; data[toType].unshift(item); saveDramaData(data); dramaState.renderedTabs.clear(); renderDramaActiveList(); updateDramaButtonState(); } function isDramaDetailOrPlay() { const site = getCurrentDramaSite(); if (!site) return false; const t = site.pageType(); return t === "detail" || t === "play"; } // src/common/style.css var style_default = '/* \u4E3B\u9898\u53D8\u91CF\uFF08\u539F THEME \u5BF9\u8C61\uFF0C\u73B0\u4EE5 CSS \u53D8\u91CF\u7EDF\u4E00\u7BA1\u7406\uFF09 */\r\n:root {\r\n --vc-primary: #4f46e5;\r\n --vc-want: #f97316;\r\n --vc-watched: #10b981;\r\n --vc-danger: #ef4444;\r\n --vc-bg: #ffffff;\r\n --vc-text: #1f2937;\r\n --vc-text-secondary: #6b7280;\r\n --vc-border: #e5e7eb;\r\n --vc-hover: #f9fafb;\r\n --vc-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);\r\n --vc-shadow-lg: 0 20px 60px rgba(0, 0, 0, 0.15);\r\n}\r\n\r\n/* \u60AC\u6D6E\u6309\u94AE\u7EC4 */\r\n.vc-btn-group {\r\n position: fixed;\r\n z-index: 99999;\r\n display: flex;\r\n flex-direction: column;\r\n gap: 8px;\r\n font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;\r\n user-select: none;\r\n transition: right 0.2s, bottom 0.2s;\r\n}\r\n\r\n.vc-btn {\r\n padding: 6px 10px;\r\n border: 1px solid var(--vc-border);\r\n border-radius: 10px;\r\n font-size: 13px;\r\n font-weight: 500;\r\n cursor: pointer;\r\n transition: all 0.2s ease;\r\n background: rgba(255, 255, 255, 0.95);\r\n backdrop-filter: blur(10px);\r\n box-shadow: var(--vc-shadow);\r\n color: var(--vc-text-secondary);\r\n white-space: nowrap;\r\n}\r\n\r\n.vc-btn:hover {\r\n transform: translateY(-1px);\r\n color: var(--vc-text);\r\n}\r\n\r\n.vc-btn.want.active {\r\n background: var(--vc-want);\r\n color: white;\r\n border-color: var(--vc-want);\r\n}\r\n\r\n.vc-btn.watched.active {\r\n background: var(--vc-watched);\r\n color: white;\r\n border-color: var(--vc-watched);\r\n}\r\n\r\n.vc-btn.list:hover {\r\n background: var(--vc-primary);\r\n color: white;\r\n border-color: var(--vc-primary);\r\n}\r\n\r\n.vc-collapse-btn {\r\n padding: 6px 10px;\r\n font-size: 12px;\r\n text-align: center;\r\n cursor: grab;\r\n}\r\n\r\n/* \u906E\u7F69 - \u5168\u5C40\u5C45\u4E2D */\r\n.vc-mask {\r\n position: fixed;\r\n inset: 0;\r\n width: 100vw;\r\n height: 100vh;\r\n background: rgba(0, 0, 0, 0.3);\r\n z-index: 100000;\r\n display: none;\r\n align-items: center;\r\n justify-content: center;\r\n backdrop-filter: blur(2px);\r\n margin: 0;\r\n padding: 0;\r\n}\r\n\r\n.vc-mask.show {\r\n display: flex !important;\r\n}\r\n\r\n/* \u6536\u85CF\u9762\u677F - \u56FA\u5B9A\u9AD8\u5EA6\u5E03\u5C40 */\r\n.vc-panel {\r\n width: 880px;\r\n max-width: 92vw;\r\n height: 80vh;\r\n background: var(--vc-bg);\r\n border-radius: 16px;\r\n box-shadow: var(--vc-shadow-lg);\r\n display: flex;\r\n flex-direction: column;\r\n overflow: hidden;\r\n animation: vcFadeIn 0.2s ease;\r\n position: relative;\r\n border: 1px solid var(--vc-border);\r\n}\r\n\r\n@keyframes vcFadeIn {\r\n from { opacity: 0; transform: translateY(10px); }\r\n to { opacity: 1; transform: translateY(0); }\r\n}\r\n\r\n/* \u5934\u90E8 */\r\n.vc-panel-header {\r\n padding: 20px 56px 16px 24px;\r\n border-bottom: 1px solid var(--vc-border);\r\n flex-shrink: 0;\r\n}\r\n\r\n.vc-panel-title-row {\r\n display: flex;\r\n align-items: center;\r\n justify-content: space-between;\r\n margin-bottom: 16px;\r\n}\r\n\r\n.vc-panel-title {\r\n font-size: 18px;\r\n font-weight: 600;\r\n color: var(--vc-text);\r\n margin: 0;\r\n}\r\n\r\n.vc-header-actions {\r\n display: flex;\r\n gap: 8px;\r\n}\r\n\r\n.vc-icon-btn-sm {\r\n padding: 6px 12px;\r\n border: 1px solid var(--vc-border);\r\n background: white;\r\n border-radius: 8px;\r\n font-size: 12px;\r\n cursor: pointer;\r\n color: var(--vc-text-secondary);\r\n transition: all 0.15s;\r\n}\r\n\r\n.vc-icon-btn-sm:hover {\r\n border-color: var(--vc-primary);\r\n color: var(--vc-primary);\r\n}\r\n\r\n.vc-tabs {\r\n display: flex;\r\n gap: 24px;\r\n margin-bottom: 12px;\r\n}\r\n\r\n.vc-tab {\r\n padding: 6px 0;\r\n font-size: 14px;\r\n color: var(--vc-text-secondary);\r\n cursor: pointer;\r\n border-bottom: 2px solid transparent;\r\n transition: all 0.2s;\r\n}\r\n\r\n.vc-tab.active {\r\n color: var(--vc-primary);\r\n border-bottom-color: var(--vc-primary);\r\n font-weight: 500;\r\n}\r\n\r\n.vc-search {\r\n width: 100%;\r\n padding: 8px 12px;\r\n border: 1px solid var(--vc-border);\r\n border-radius: 8px;\r\n font-size: 13px;\r\n outline: none;\r\n transition: border-color 0.2s;\r\n box-sizing: border-box;\r\n background: var(--vc-bg);\r\n}\r\n\r\n.vc-search:focus {\r\n border-color: var(--vc-primary);\r\n}\r\n\r\n/* \u5217\u8868\u5BB9\u5668 */\r\n.vc-list-wrap {\r\n flex: 1;\r\n overflow: hidden;\r\n padding: 20px 24px;\r\n display: none;\r\n box-sizing: border-box;\r\n}\r\n\r\n.vc-list-wrap.active {\r\n display: block;\r\n}\r\n\r\n/* \u7F51\u683C\u5E03\u5C40 - \u56FA\u5B9A3\u884C4\u5217 */\r\n.vc-grid {\r\n display: grid;\r\n grid-template-columns: repeat(4, 1fr);\r\n grid-template-rows: repeat(3, 1fr);\r\n gap: 16px;\r\n height: 100%;\r\n width: 100%;\r\n}\r\n\r\n/* \u5361\u7247 */\r\n.vc-card {\r\n position: relative;\r\n border-radius: 10px;\r\n overflow: hidden;\r\n border: 1px solid var(--vc-border);\r\n background: white;\r\n transition: all 0.2s ease;\r\n cursor: pointer;\r\n display: flex;\r\n flex-direction: column;\r\n max-height: 260px;\r\n}\r\n\r\n.vc-card:hover {\r\n box-shadow: 0 6px 16px rgba(0, 0, 0, 0.08);\r\n transform: translateY(-2px);\r\n}\r\n\r\n/* \u5C01\u9762\u533A\u57DF */\r\n.vc-card-cover {\r\n flex: 1;\r\n min-height: 0;\r\n background: #f3f4f6;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n overflow: hidden;\r\n}\r\n\r\n.vc-card-cover img {\r\n width: 100%;\r\n height: 100%;\r\n object-fit: cover;\r\n cursor: zoom-in;\r\n transition: transform 0.2s;\r\n}\r\n\r\n.vc-card:hover .vc-card-cover img {\r\n transform: scale(1.05);\r\n}\r\n\r\n.vc-card-cover .default-cover {\r\n color: #9ca3af;\r\n font-size: 28px;\r\n}\r\n\r\n/* \u5361\u7247\u64CD\u4F5C\u6309\u94AE */\r\n.vc-card-actions {\r\n position: absolute;\r\n top: 8px;\r\n right: 8px;\r\n display: flex;\r\n gap: 4px;\r\n opacity: 0;\r\n transition: opacity 0.2s ease;\r\n z-index: 2;\r\n}\r\n\r\n.vc-card:hover .vc-card-actions {\r\n opacity: 1;\r\n}\r\n\r\n.vc-card-action-btn {\r\n padding: 4px 8px;\r\n border: none;\r\n background: rgba(255, 255, 255, 0.95);\r\n border-radius: 6px;\r\n font-size: 11px;\r\n cursor: pointer;\r\n color: var(--vc-text-secondary);\r\n backdrop-filter: blur(4px);\r\n transition: all 0.15s;\r\n}\r\n\r\n.vc-card-action-btn:hover {\r\n background: var(--vc-primary);\r\n color: white;\r\n}\r\n\r\n.vc-card-action-btn.delete:hover {\r\n background: var(--vc-danger);\r\n color: white;\r\n}\r\n\r\n/* \u5361\u7247\u4FE1\u606F\u533A */\r\n.vc-card-info {\r\n padding: 8px 10px 10px;\r\n flex-shrink: 0;\r\n}\r\n\r\n.vc-card-title {\r\n font-size: 12px;\r\n line-height: 1.4;\r\n color: var(--vc-text);\r\n display: -webkit-box;\r\n -webkit-line-clamp: 2;\r\n -webkit-box-orient: vertical;\r\n overflow: hidden;\r\n font-weight: 500;\r\n}\r\n\r\n.vc-card-site {\r\n display: inline-block;\r\n font-size: 11px;\r\n color: var(--vc-primary);\r\n background: rgba(79, 70, 229, 0.1);\r\n padding: 1px 6px;\r\n border-radius: 4px;\r\n white-space: nowrap;\r\n vertical-align: middle;\r\n margin-right: 4px;\r\n}\r\n\r\n/* \u7AD9\u70B9\u6807\u8BC6\u4E3A\u7A7A\u65F6\u4E0D\u5360\u4F4D\uFF08\u89C6\u9891\u6A21\u5757\u65E0\u7AD9\u70B9\u6765\u6E90\uFF09 */\r\n.vc-card-site:empty {\r\n display: none;\r\n}\r\n\r\n.vc-card-time {\r\n font-size: 11px;\r\n color: var(--vc-text-secondary);\r\n margin-top: 4px;\r\n white-space: nowrap;\r\n overflow: hidden;\r\n text-overflow: ellipsis;\r\n min-width: 0;\r\n}\r\n\r\n.vc-empty {\r\n grid-column: 1 / -1;\r\n grid-row: 1 / -1;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n color: var(--vc-text-secondary);\r\n font-size: 14px;\r\n}\r\n\r\n/* \u5206\u9875\u5668 */\r\n.vc-pagination {\r\n padding: 12px 24px;\r\n border-top: 1px solid var(--vc-border);\r\n display: flex;\r\n align-items: center;\r\n justify-content: space-between;\r\n font-size: 12px;\r\n color: var(--vc-text-secondary);\r\n flex-shrink: 0;\r\n}\r\n\r\n.vc-pagination-btns {\r\n display: flex;\r\n align-items: center;\r\n gap: 8px;\r\n}\r\n\r\n.vc-page-btn {\r\n padding: 4px 10px;\r\n border: 1px solid var(--vc-border);\r\n background: white;\r\n border-radius: 6px;\r\n font-size: 12px;\r\n cursor: pointer;\r\n color: var(--vc-text-secondary);\r\n transition: all 0.15s;\r\n}\r\n\r\n.vc-page-btn:hover:not(:disabled) {\r\n border-color: var(--vc-primary);\r\n color: var(--vc-primary);\r\n}\r\n\r\n.vc-page-btn:disabled {\r\n opacity: 0.4;\r\n cursor: not-allowed;\r\n}\r\n\r\n/* \u5173\u95ED\u6309\u94AE */\r\n.vc-close {\r\n position: absolute;\r\n top: 16px;\r\n right: 20px;\r\n background: none;\r\n border: none;\r\n font-size: 20px;\r\n color: var(--vc-text-secondary);\r\n cursor: pointer;\r\n line-height: 1;\r\n padding: 4px;\r\n z-index: 2;\r\n}\r\n\r\n.vc-close:hover {\r\n color: var(--vc-text);\r\n}\r\n\r\n/* \u56FE\u7247\u9884\u89C8 - \u5168\u5C40\u5C45\u4E2D */\r\n.vc-image-preview {\r\n position: fixed;\r\n inset: 0;\r\n width: 100vw;\r\n height: 100vh;\r\n background: rgba(0, 0, 0, 0.85);\r\n z-index: 100001;\r\n display: none;\r\n align-items: center;\r\n justify-content: center;\r\n cursor: zoom-out;\r\n margin: 0;\r\n padding: 0;\r\n}\r\n\r\n.vc-image-preview.show {\r\n display: flex !important;\r\n}\r\n\r\n.vc-image-preview img {\r\n max-width: 92vw;\r\n max-height: 92vh;\r\n object-fit: contain;\r\n border-radius: 4px;\r\n}\r\n\r\n/* ===== \u5217\u8868\u9875\u5361\u7247\u8FF7\u4F60\u6536\u85CF\u6309\u94AE ===== */\r\n.vc-mini-fav {\r\n position: absolute;\r\n top: 6px;\r\n left: 6px;\r\n display: flex;\r\n gap: 4px;\r\n opacity: 0;\r\n pointer-events: none;\r\n transition: opacity 0.15s ease;\r\n z-index: 10;\r\n font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;\r\n}\r\n\r\n/* \u5361\u7247\u60AC\u6D6E\u6216\u6309\u94AE\u81EA\u8EAB\u60AC\u6D6E\u65F6\u663E\u793A\u5E76\u53EF\u70B9\u51FB */\r\n.vc-fav-host:hover .vc-mini-fav,\r\n.vc-mini-fav:hover {\r\n opacity: 1;\r\n pointer-events: auto;\r\n}\r\n\r\n.vc-mini-btn {\r\n width: 26px;\r\n height: 26px;\r\n padding: 0;\r\n border: none;\r\n border-radius: 6px;\r\n background: rgba(255, 255, 255, 0.92);\r\n color: var(--vc-text-secondary);\r\n font-size: 13px;\r\n line-height: 1;\r\n cursor: pointer;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n backdrop-filter: blur(4px);\r\n box-shadow: 0 1px 4px rgba(0, 0, 0, 0.18);\r\n transition: transform 0.15s, background 0.15s, color 0.15s;\r\n}\r\n\r\n.vc-mini-btn:hover {\r\n transform: scale(1.12);\r\n}\r\n\r\n.vc-mini-want:hover {\r\n color: var(--vc-want);\r\n}\r\n\r\n.vc-mini-watched:hover {\r\n color: var(--vc-watched);\r\n}\r\n\r\n.vc-mini-want.active {\r\n background: var(--vc-want);\r\n color: #fff;\r\n}\r\n\r\n.vc-mini-watched.active {\r\n background: var(--vc-watched);\r\n color: #fff;\r\n}\r\n\r\n/* ===== JavLibrary \u8BE6\u60C5\u9875\u8DF3\u8F6C JavBus \u6309\u94AE ===== */\r\n.vc-jb-jump {\r\n display: inline-block;\r\n margin-left: 8px;\r\n padding: 2px 10px;\r\n font-size: 12px;\r\n font-weight: 500;\r\n color: #fff !important;\r\n background: var(--vc-primary);\r\n border-radius: 6px;\r\n text-decoration: none;\r\n cursor: pointer;\r\n vertical-align: middle;\r\n transition: opacity 0.15s, transform 0.15s;\r\n}\r\n\r\n.vc-jb-jump:hover {\r\n opacity: 0.88;\r\n transform: translateY(-1px);\r\n}\r\n'; // src/drama/style.css var style_default2 = '/* \u5F71\u89C6\u6A21\u5757\u4E13\u5C5E\u6837\u5F0F\uFF08\u5171\u4EAB\u6837\u5F0F\u89C1 ui/style.css\uFF0C\u6B64\u5904\u4EC5\u8DE8\u7AD9\u76F4\u8FBE widget\uFF09 */\n\n.vc-xjump {\n position: fixed;\n z-index: 99999;\n font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;\n}\n\n.vc-xjump-toggle {\n padding: 8px 14px;\n border: 1px solid var(--vc-border);\n border-radius: 10px;\n background: rgba(255, 255, 255, 0.95);\n backdrop-filter: blur(10px);\n box-shadow: var(--vc-shadow);\n color: var(--vc-text);\n font-size: 13px;\n font-weight: 500;\n cursor: grab;\n white-space: nowrap;\n user-select: none;\n transition: transform 0.15s;\n}\n\n.vc-xjump-toggle:hover {\n transform: translateY(-1px);\n}\n\n.vc-xjump-menu {\n position: absolute;\n top: calc(100% + 6px);\n right: 0;\n display: none;\n flex-direction: column;\n gap: 6px;\n background: rgba(255, 255, 255, 0.98);\n border: 1px solid var(--vc-border);\n border-radius: 10px;\n box-shadow: var(--vc-shadow-lg);\n padding: 8px;\n max-height: 60vh;\n overflow: auto;\n min-width: 102px;\n box-sizing: border-box;\n}\n\n/* \u5E95\u90E8\u7A7A\u95F4\u4E0D\u591F\u65F6\u83DC\u5355\u5411\u4E0A\u5C55\u5F00 */\n.vc-xjump.up .vc-xjump-menu {\n top: auto;\n bottom: calc(100% + 6px);\n}\n\n.vc-xjump.show .vc-xjump-menu {\n display: flex;\n}\n\n.vc-xjump-btn {\n padding: 6px 12px;\n border: none;\n border-radius: 8px;\n background: var(--vc-hover);\n color: var(--vc-text);\n font-size: 12px;\n text-decoration: none;\n cursor: pointer;\n text-align: left;\n white-space: nowrap;\n transition: background 0.15s, color 0.15s;\n}\n\n.vc-xjump-btn:hover {\n background: var(--vc-primary);\n color: #fff;\n}\n'; // src/common/templates/panel.html var panel_default = '\n
\n
\n
\u6211\u7684\u6536\u85CF
\n
\n \n \n \n
\n
\n
\n
\u60F3\u770B
\n
\u5DF2\u770B
\n
\n \n
\n\n
\n
\n
\n
\n
\n
\n\n
\n \u5171 0 \u6761\u6536\u85CF\n
\n \n 1 / 1\n \n
\n
\n'; // src/common/templates/card.html var card_default = '
\n
{{coverHtml}}
\n
\n \n \n
\n
\n
{{siteName}}{{title}}
\n
{{dateStr}}
\n
\n
\n'; // src/common/templates/cover-image.html var cover_image_default = '{{title}}\n'; // src/common/templates/default-cover.html var default_cover_default = '
\u{1F5BC}\uFE0F
\n'; // src/drama/ui.js var wantBtn; var watchedBtn; var listBtn; var btnGroup; var panelEl; var xjumpEl; function interpolate(template, data) { return template.replace(/\{\{(\w+)\}\}/g, (_, key) => data[key] ?? ""); } function formatDate(ts) { const d = new Date(ts); const pad = (n) => String(n).padStart(2, "0"); return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`; } function injectDramaStyles() { const style = document.createElement("style"); style.textContent = style_default + "\n" + style_default2; document.head.appendChild(style); } function openImagePreview(src) { let preview = document.querySelector(".vc-image-preview"); if (!preview) { preview = document.createElement("div"); preview.className = "vc-image-preview"; const img = document.createElement("img"); img.alt = "\u5C01\u9762\u9884\u89C8"; preview.appendChild(img); preview.addEventListener("click", () => preview.classList.remove("show")); document.body.appendChild(preview); } preview.querySelector("img").src = src; preview.classList.add("show"); } function initDrama() { injectDramaStyles(); createDramaFloatButtons(); enhanceDramaPage(); GM_registerMenuCommand("\u6253\u5F00\u5F71\u89C6\u6536\u85CF\u5217\u8868", openDramaPanel); let lastUrl = location.href; setInterval(() => { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(() => { enhanceDramaPage(); updateDramaButtonState(); updateDramaButtonVisibility(); }, 500); } }, 1e3); document.addEventListener("keydown", (e) => { if (e.key !== "Escape") return; const preview = document.querySelector(".vc-image-preview"); if (preview == null ? void 0 : preview.classList.contains("show")) { preview.classList.remove("show"); return; } if (xjumpEl == null ? void 0 : xjumpEl.classList.contains("show")) { xjumpEl.classList.remove("show"); return; } if (panelEl == null ? void 0 : panelEl.classList.contains("show")) closeDramaPanel(); }); } function enhanceDramaPage() { const site = getCurrentDramaSite(); if (!site) return; const t = site.pageType(); if (typeof site.onPageReady === "function") site.onPageReady(); if (t === "detail") { cacheCurrentDetailImage(); if (site.isPlayUrl && !document.body.dataset.dramaPlayClickBound) { document.body.dataset.dramaPlayClickBound = "1"; document.addEventListener("click", (e) => { const a = e.target.closest("a"); if (a && site.isPlayUrl(a.href)) cacheCurrentDetailImage(); }); } } if (t === "detail" || t === "play") injectCrossSiteJump(); injectDramaListFavoriteButtons(); } function getPositionKey() { const site = getCurrentDramaSite(); return site ? `${DRAMA_POSITION_KEY}_${site.id}` : DRAMA_POSITION_KEY; } function getXjumpPositionKey() { const site = getCurrentDramaSite(); return site ? `video_collector_drama_xjump_pos_${site.id}` : "video_collector_drama_xjump_pos"; } function createDramaFloatButtons() { btnGroup = document.createElement("div"); btnGroup.className = "vc-btn-group"; const savedPos = GM_getValue(getPositionKey(), null); if (savedPos) { btnGroup.style.right = savedPos.right + "px"; btnGroup.style.bottom = savedPos.bottom + "px"; } else { btnGroup.style.right = "20px"; btnGroup.style.bottom = "20px"; } const collapseBtn = document.createElement("button"); collapseBtn.className = "vc-btn vc-collapse-btn"; collapseBtn.textContent = "\u22EE\u22EE \u5F71\u89C6\u6536\u85CF"; collapseBtn.title = "\u62D6\u52A8\u53EF\u79FB\u52A8\u4F4D\u7F6E\uFF0C\u70B9\u51FB\u6536\u8D77/\u5C55\u5F00"; wantBtn = document.createElement("button"); wantBtn.className = "vc-btn want"; wantBtn.textContent = "\u2B50 \u60F3\u770B"; wantBtn.onclick = () => toggleDrama("want"); watchedBtn = document.createElement("button"); watchedBtn.className = "vc-btn watched"; watchedBtn.textContent = "\u2713 \u5DF2\u770B"; watchedBtn.onclick = () => toggleDrama("watched"); listBtn = document.createElement("button"); listBtn.className = "vc-btn list"; listBtn.textContent = "\u{1F4CB} \u5F71\u89C6\u5217\u8868"; listBtn.onclick = openDramaPanel; btnGroup.appendChild(collapseBtn); btnGroup.appendChild(wantBtn); btnGroup.appendChild(watchedBtn); btnGroup.appendChild(listBtn); document.body.appendChild(btnGroup); initDrag(collapseBtn); collapseBtn.addEventListener("click", () => { const collapsed = !GM_getValue(DRAMA_COLLAPSE_KEY, false); GM_setValue(DRAMA_COLLAPSE_KEY, collapsed); updateDramaButtonVisibility(); }); updateDramaButtonState(); updateDramaButtonVisibility(); document.addEventListener("visibilitychange", () => { if (document.hidden) return; updateDramaButtonState(); refreshAllDramaMiniButtons(); }); } function initDrag(handle) { let isDragging = false, hasMoved = false, startX, startY, startRight, startBottom; let dragW = 0, dragH = 0, rafId = null, pendingRight = 0, pendingBottom = 0; handle.addEventListener("mousedown", (e) => { isDragging = true; hasMoved = false; startX = e.clientX; startY = e.clientY; const rect = btnGroup.getBoundingClientRect(); startRight = window.innerWidth - rect.right; startBottom = window.innerHeight - rect.bottom; dragW = btnGroup.offsetWidth; dragH = btnGroup.offsetHeight; document.body.style.cursor = "grabbing"; e.preventDefault(); }); document.addEventListener("mousemove", (e) => { if (!isDragging) return; const dx = e.clientX - startX, dy = e.clientY - startY; if (Math.abs(dx) > 3 || Math.abs(dy) > 3) hasMoved = true; pendingRight = Math.max(0, Math.min(window.innerWidth - dragW, startRight - dx)); pendingBottom = Math.max(0, Math.min(window.innerHeight - dragH, startBottom - dy)); if (rafId === null) { rafId = requestAnimationFrame(() => { rafId = null; btnGroup.style.right = pendingRight + "px"; btnGroup.style.bottom = pendingBottom + "px"; }); } }); document.addEventListener("mouseup", () => { if (!isDragging) return; isDragging = false; document.body.style.cursor = ""; if (rafId !== null) { cancelAnimationFrame(rafId); rafId = null; btnGroup.style.right = pendingRight + "px"; btnGroup.style.bottom = pendingBottom + "px"; } GM_setValue(getPositionKey(), { right: parseInt(btnGroup.style.right), bottom: parseInt(btnGroup.style.bottom) }); }); handle.addEventListener("click", (e) => { if (hasMoved) { e.preventDefault(); e.stopPropagation(); hasMoved = false; } }, true); } function updateDramaButtonVisibility() { if (!wantBtn || !watchedBtn) return; const collapsed = GM_getValue(DRAMA_COLLAPSE_KEY, false); if (collapsed) { wantBtn.style.display = "none"; watchedBtn.style.display = "none"; listBtn.style.display = "none"; } else if (!isDramaDetailOrPlay()) { wantBtn.style.display = "none"; watchedBtn.style.display = "none"; listBtn.style.display = ""; } else { wantBtn.style.display = ""; watchedBtn.style.display = ""; listBtn.style.display = ""; } if (!collapsed) clampBtnGroupInViewport(); } function clampBtnGroupInViewport() { if (!btnGroup) return; const w = btnGroup.offsetWidth; const h = btnGroup.offsetHeight; if (!w || !h) return; let right = parseInt(btnGroup.style.right) || 0; let bottom = parseInt(btnGroup.style.bottom) || 0; right = Math.max(0, Math.min(right, window.innerWidth - w)); bottom = Math.max(0, Math.min(bottom, window.innerHeight - h)); btnGroup.style.right = right + "px"; btnGroup.style.bottom = bottom + "px"; GM_setValue(getPositionKey(), { right, bottom }); } function updateDramaButtonState() { if (!wantBtn || !watchedBtn) return; const name = getCurrentDramaName(); if (!name) { wantBtn.classList.remove("active"); watchedBtn.classList.remove("active"); return; } wantBtn.classList.toggle("active", isDramaFavorited(name, "want")); watchedBtn.classList.toggle("active", isDramaFavorited(name, "watched")); } function injectCrossSiteJump() { const site = getCurrentDramaSite(); if (!site) return; const t = site.pageType(); let name = ""; if (t === "detail" && site.getDetailInfo) name = (site.getDetailInfo() || {}).name || ""; else if (t === "play" && site.getPlayInfo) name = (site.getPlayInfo() || {}).name || ""; if (!name) return; if (xjumpEl && xjumpEl.dataset.name === name) return; if (xjumpEl) xjumpEl.remove(); xjumpEl = document.createElement("div"); xjumpEl.className = "vc-xjump"; xjumpEl.dataset.name = name; const savedPos = GM_getValue(getXjumpPositionKey(), null); if (savedPos) { xjumpEl.style.right = savedPos.right + "px"; xjumpEl.style.top = savedPos.top + "px"; } else { xjumpEl.style.right = "16px"; xjumpEl.style.top = "16px"; } const toggle = document.createElement("button"); toggle.className = "vc-xjump-toggle"; toggle.textContent = "\u{1F517} \u8DE8\u7AD9\u76F4\u8FBE"; const menu = document.createElement("div"); menu.className = "vc-xjump-menu"; dramaSites.filter((s) => s.id !== site.id).forEach((target) => { const a = document.createElement("a"); a.className = "vc-xjump-btn"; a.textContent = target.name; a.target = "_blank"; a.rel = "noopener noreferrer"; a.href = "https://" + pickDomain(target) + target.searchPath(name); menu.appendChild(a); }); xjumpEl.appendChild(toggle); xjumpEl.appendChild(menu); document.body.appendChild(xjumpEl); initXjumpDrag(toggle); const el = xjumpEl; let hideTimer = null; el.addEventListener("mouseenter", () => { if (el.classList.contains("dragging")) return; if (hideTimer) { clearTimeout(hideTimer); hideTimer = null; } el.classList.add("show"); requestAnimationFrame(() => { const r = toggle.getBoundingClientRect(); const spaceBelow = window.innerHeight - r.bottom; const menuH = menu.offsetHeight; el.classList.toggle("up", spaceBelow < menuH + 8); }); }); el.addEventListener("mouseleave", () => { if (el.classList.contains("dragging")) return; if (hideTimer) clearTimeout(hideTimer); hideTimer = setTimeout(() => el.classList.remove("show"), 150); }); } function initXjumpDrag(handle) { let isDragging = false, hasMoved = false, startX, startY, startRight, startTop; let dragW = 0, dragH = 0, rafId = null, pendingRight = 0, pendingTop = 0; handle.addEventListener("mousedown", (e) => { if (!xjumpEl) return; isDragging = true; hasMoved = false; startX = e.clientX; startY = e.clientY; const rect = xjumpEl.getBoundingClientRect(); startRight = window.innerWidth - rect.right; startTop = rect.top; dragW = xjumpEl.offsetWidth; dragH = xjumpEl.offsetHeight; xjumpEl.classList.add("dragging"); document.body.style.cursor = "grabbing"; e.preventDefault(); }); document.addEventListener("mousemove", (e) => { if (!isDragging) return; const dx = e.clientX - startX, dy = e.clientY - startY; if (Math.abs(dx) > 3 || Math.abs(dy) > 3) hasMoved = true; pendingRight = Math.max(0, Math.min(window.innerWidth - dragW, startRight - dx)); pendingTop = Math.max(0, Math.min(window.innerHeight - dragH, startTop + dy)); if (rafId === null) { rafId = requestAnimationFrame(() => { rafId = null; xjumpEl.style.right = pendingRight + "px"; xjumpEl.style.top = pendingTop + "px"; }); } }); document.addEventListener("mouseup", () => { if (!isDragging) return; isDragging = false; document.body.style.cursor = ""; if (rafId !== null) { cancelAnimationFrame(rafId); rafId = null; xjumpEl.style.right = pendingRight + "px"; xjumpEl.style.top = pendingTop + "px"; } xjumpEl.classList.remove("dragging"); GM_setValue(getXjumpPositionKey(), { right: parseInt(xjumpEl.style.right), top: parseInt(xjumpEl.style.top) }); }); handle.addEventListener("click", (e) => { if (hasMoved) { e.preventDefault(); e.stopPropagation(); hasMoved = false; } }, true); } var dObserver = null; var dTimer = null; var dMouseBound = false; function injectDramaListFavoriteButtons() { injectDramaListCards(); [500, 1500, 3e3].forEach((delay) => setTimeout(injectDramaListCards, delay)); if (!dObserver) { dObserver = new MutationObserver(() => { if (dTimer) return; dTimer = setTimeout(() => { dTimer = null; injectDramaListCards(); }, 150); }); dObserver.observe(document.body, { childList: true, subtree: true }); } if (!dMouseBound) { dMouseBound = true; document.addEventListener("mouseover", (e) => { const site = getCurrentDramaSite(); if (!site || !site.listCardSelector) return; const card = e.target.closest(site.listCardSelector); if (!card || card.querySelector(".vc-mini-fav")) return; injectDramaListCards(); }); } } function injectDramaListCards() { const site = getCurrentDramaSite(); if (!site || !site.getListCards) return; site.getListCards().forEach(({ element, name, cover, path }) => { if (!name || element.querySelector(".vc-mini-fav")) return; if (getComputedStyle(element).position === "static") element.style.position = "relative"; element.classList.add("vc-fav-host"); const drama = { name, cover, path, siteId: site.id, addTime: Date.now(), element }; const group = document.createElement("div"); group.className = "vc-mini-fav"; const refreshFns = []; group.appendChild(makeDramaMiniBtn("vc-mini-want", "\u2B50", "\u60F3\u770B", "want", drama, refreshFns)); group.appendChild(makeDramaMiniBtn("vc-mini-watched", "\u2713", "\u5DF2\u770B", "watched", drama, refreshFns)); element.appendChild(group); }); } function makeDramaMiniBtn(cls, icon, title, type, drama, refreshFns) { const btn = document.createElement("button"); btn.type = "button"; btn.className = `vc-mini-btn ${cls}`; btn.title = title; btn.textContent = icon; btn.dataset.dramaName = drama.name; btn.dataset.dramaType = type; const refresh = () => btn.classList.toggle("active", isDramaFavorited(drama.name, type)); refresh(); refreshFns.push(refresh); btn.addEventListener("click", (e) => { e.preventDefault(); e.stopPropagation(); drama.addTime = Date.now(); if (drama.element) { const s = getCurrentDramaSite(); if (s && s.getListCards) { const fresh = s.getListCards().find((c) => c.element === drama.element); if (fresh && fresh.cover) drama.cover = fresh.cover; } } toggleDramaFavorite(drama, type); refreshFns.forEach((fn) => fn()); updateDramaButtonState(); }); return btn; } function refreshAllDramaMiniButtons() { document.querySelectorAll(".vc-mini-btn[data-drama-name]").forEach((btn) => { const name = btn.dataset.dramaName; const type = btn.dataset.dramaType; if (name && type) btn.classList.toggle("active", isDramaFavorited(name, type)); }); } function createDramaPanel() { const mask = document.createElement("div"); mask.className = "vc-mask"; mask.onclick = (e) => { if (e.target === mask) closeDramaPanel(); }; const panel = document.createElement("div"); panel.className = "vc-panel"; panel.innerHTML = panel_default; mask.appendChild(panel); document.body.appendChild(mask); panelEl = mask; bindDramaPanelEvents(panel); } function bindDramaPanelEvents(panel) { panel.querySelector(".vc-close").onclick = closeDramaPanel; panel.querySelectorAll(".vc-tab").forEach((tab) => { tab.onclick = () => { panel.querySelectorAll(".vc-tab").forEach((t) => t.classList.remove("active")); tab.classList.add("active"); dramaState.currentTab = tab.dataset.type; switchDramaTab(tab.dataset.type); }; }); const searchInput = panel.querySelector(".vc-search"); searchInput.oninput = (e) => { dramaState.searchKeyword = e.target.value.trim(); dramaState.currentPage[dramaState.currentTab] = 1; dramaState.renderedTabs.clear(); renderDramaActiveList(); }; panel.querySelector("#vc-refresh").onclick = () => { dramaState.renderedTabs.clear(); renderDramaActiveList(); }; panel.querySelector("#vc-export").onclick = exportDramaData; panel.querySelector("#vc-import").onclick = () => { importDramaData(() => { dramaState.renderedTabs.clear(); renderDramaActiveList(); updateDramaButtonState(); }); }; panel.querySelector("#vc-prev").onclick = () => { if (dramaState.currentPage[dramaState.currentTab] > 1) { dramaState.currentPage[dramaState.currentTab]--; dramaState.renderedTabs.clear(); renderDramaActiveList(); } }; panel.querySelector("#vc-next").onclick = () => { const data = loadDramaData(); const list = filterDramaList(data[dramaState.currentTab]); const totalPages = Math.ceil(list.length / PAGE_SIZE) || 1; if (dramaState.currentPage[dramaState.currentTab] < totalPages) { dramaState.currentPage[dramaState.currentTab]++; dramaState.renderedTabs.clear(); renderDramaActiveList(); } }; } function switchDramaTab(type) { panelEl.querySelectorAll(".vc-list-wrap").forEach((wrap) => { wrap.classList.toggle("active", wrap.dataset.type === type); }); if (!dramaState.renderedTabs.has(type)) { renderDramaList(type); dramaState.renderedTabs.add(type); } updateDramaPagination(); } function renderDramaActiveList() { renderDramaList(dramaState.currentTab); dramaState.renderedTabs.add(dramaState.currentTab); updateDramaPagination(); } function filterDramaList(list) { if (!dramaState.searchKeyword) return list; const kw = dramaState.searchKeyword.toLowerCase(); return list.filter((item) => (item.name || "").toLowerCase().includes(kw)); } function buildDramaUrl(item) { if (!item.siteId) return ""; const site = dramaSites.find((s) => s.id === item.siteId); if (!site) return ""; const domain = pickDomain(site); if (!domain) return ""; return "https://" + domain + (item.path || ""); } function renderDramaList(type) { const data = loadDramaData(); const fullList = filterDramaList(data[type]); const page = dramaState.currentPage[type]; const start = (page - 1) * PAGE_SIZE; const pageList = fullList.slice(start, start + PAGE_SIZE); const gridEl = panelEl.querySelector(`#vc-grid-${type}`); const oppositeType = type === "want" ? "watched" : "want"; const oppositeText = type === "want" ? "\u6807\u8BB0\u5DF2\u770B" : "\u79FB\u56DE\u60F3\u770B"; if (fullList.length === 0) { gridEl.innerHTML = ""; const empty = document.createElement("div"); empty.className = "vc-empty"; empty.textContent = "\u6682\u65E0\u5F71\u89C6\u6536\u85CF"; gridEl.appendChild(empty); return; } gridEl.innerHTML = pageList.map((item) => { const coverHtml = item.cover ? interpolate(cover_image_default, { cover: item.cover, title: item.name }) : default_cover_default; const siteName = item.siteId ? (dramaSites.find((s) => s.id === item.siteId) || {}).name || "" : ""; return interpolate(card_default, { id: item.name, title: item.name, url: buildDramaUrl(item), dateStr: formatDate(item.addTime), oppositeType, oppositeText, siteName, coverHtml }); }).join(""); bindDramaCardEvents(gridEl, data, type); } function bindDramaCardEvents(gridEl, data, type) { gridEl.querySelectorAll(".vc-card").forEach((card) => { const name = card.dataset.id; const url = card.dataset.url; const item = data[type].find((i) => i.name === name); card.addEventListener("click", (e) => { if (e.target.closest(".vc-card-actions")) return; if (e.target.tagName === "IMG" && (item == null ? void 0 : item.cover)) { openImagePreview(item.cover); return; } if (url) window.open(url, "_blank"); }); card.querySelector(".move-btn").addEventListener("click", (e) => { e.stopPropagation(); moveDrama(name, type, e.target.dataset.to); }); card.querySelector(".delete").addEventListener("click", (e) => { e.stopPropagation(); if (confirm("\u786E\u5B9A\u5220\u9664\u8FD9\u6761\u6536\u85CF\u5417\uFF1F")) deleteDrama(name, type); }); }); gridEl.querySelectorAll(".vc-card-cover img").forEach((img) => { img.addEventListener("error", function() { this.onerror = null; this.parentElement.innerHTML = default_cover_default; }); }); } function updateDramaPagination() { const data = loadDramaData(); const list = filterDramaList(data[dramaState.currentTab]); const total = list.length; const totalPages = Math.max(1, Math.ceil(total / PAGE_SIZE)); const currentPage = dramaState.currentPage[dramaState.currentTab]; const tabLabel = dramaState.currentTab === "want" ? "\u60F3\u770B" : "\u5DF2\u770B"; panelEl.querySelector("#vc-count-info").textContent = `\u5171 ${total} \u6761${tabLabel}`; panelEl.querySelector("#vc-page-info").textContent = `${currentPage} / ${totalPages}`; panelEl.querySelector("#vc-prev").disabled = currentPage <= 1; panelEl.querySelector("#vc-next").disabled = currentPage >= totalPages; } function openDramaPanel() { if (!panelEl) createDramaPanel(); panelEl.classList.add("show"); renderDramaActiveList(); } function closeDramaPanel() { panelEl == null ? void 0 : panelEl.classList.remove("show"); } // src/drama/index.js function init() { if (!getCurrentDramaSite()) return; initDrama(); } if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", init); } else { init(); } })();