您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
🎯 专业动画追番神器!一键搜索动画资源,智能收藏管理,个性化追番体验。快捷键说明:[Shift+F]呼出搜索 | [Shift+C]收藏当前动画 | [Shift+D]管理收藏夹
// ==UserScript== // @name 全局动画搜索与追番助手 // @namespace http://tampermonkey.net/ // @version 0.1.1 // @description 🎯 专业动画追番神器!一键搜索动画资源,智能收藏管理,个性化追番体验。快捷键说明:[Shift+F]呼出搜索 | [Shift+C]收藏当前动画 | [Shift+D]管理收藏夹 // @author Aomine // @match *://*/* // @icon data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='32' height='32' viewBox='0 0 32 32'><text x='0' y='24' font-size='24'>🔍 </text></svg> // @license GPL License // @grant GM_setValue // @grant GM_getValue // ==/UserScript== (function() { 'use strict'; // 创建搜索框HTML结构 const searchHTML = ` <div id="search-overlay"></div> <div id="global-search-container"> <div class="search-header"> <h2 class="search-title">动画资源搜索</h2> <button class="close-btn">×</button> </div> <div class="search-input-group"> <input type="text" id="search-input" placeholder="输入动画名称..." autocomplete="off"> <button id="search-btn"> <svg class="search-icon" viewBox="0 0 24 24"> <path d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"/> </svg> </button> </div> <div class="engine-selector"> <label class="engine-label">选择搜索引擎:</label> <select id="engine-select"> <option value="0">次元城</option> <option value="1">稀饭动漫</option> <option value="2">MuteFun</option> <option value="3">咕咕番</option> <option value="4">NT动漫</option> <option value="5">风铃动漫</option> <option value="6">喵物次元</option> <option value="7">Bangumi评分</option> </select> </div> <div class="search-footer"> 按 <span class="search-hotkey">ESC</span> 关闭 | 按 <span class="search-hotkey">Enter</span> 搜索 </div> </div> `; // 创建CSS样式 const css = ` #global-search-container { position: fixed; top: 20%; left: 50%; transform: translateX(-50%); z-index: 999999; background: rgba(255, 255, 255, 0.95); border-radius: 12px; box-shadow: 0 8px 32px rgba(31, 38, 135, 0.37); backdrop-filter: blur(10px); border: 1px solid rgba(255, 255, 255, 0.18); padding: 20px; width: 550px; max-width: 90%; display: none; animation: pop-in 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275); font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; } #search-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.5); backdrop-filter: blur(3px); z-index: 999998; display: none; } @keyframes pop-in { 0% { opacity: 0; transform: translate(-50%, -20px); } 100% { opacity: 1; transform: translate(-50%, 0); } } .search-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px; } .search-title { font-size: 20px; font-weight: 600; color: #2c3e50; margin: 0; } .close-btn { background: none; border: none; font-size: 24px; cursor: pointer; color: #7f8c8d; transition: color 0.2s; } .close-btn:hover { color: #e74c3c; } .search-input-group { display: flex; margin-bottom: 15px; border-radius: 30px; overflow: hidden; box-shadow: 0 4px 6px rgba(50, 50, 93, 0.11), 0 1px 3px rgba(0, 0, 0, 0.08); } #search-input { flex: 1; padding: 15px 20px; border: none; outline: none; font-size: 16px; background: #f8f9fa; color: #000000 !important; caret-color: #3498db !important; } #search-btn { background: #3498db; border: none; padding: 0 25px; cursor: pointer; transition: background 0.3s; display: flex; align-items: center; justify-content: center; } #search-btn:hover { background: #2980b9; } .search-icon { width: 22px; height: 22px; fill: white; } .engine-selector { display: flex; flex-direction: column; margin-top: 15px; } .engine-label { font-size: 14px; margin-bottom: 8px; color: #34495e; font-weight: 500; } #engine-select { padding: 12px 15px; border-radius: 8px; border: 1px solid #ddd; background: #f8f9fa; font-size: 15px; outline: none; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05); cursor: pointer; width: 100%; } #engine-select:focus { border-color: #3498db; box-shadow: 0 0 0 2px rgba(52, 152, 219, 0.2); } .search-footer { margin-top: 15px; font-size: 13px; color: #7f8c8d; text-align: center; padding-top: 10px; border-top: 1px solid #eee; } .search-hotkey { background: #f1f2f6; padding: 2px 6px; border-radius: 4px; font-weight: 600; } `; // 将样式和HTML添加到文档 document.head.insertAdjacentHTML('beforeend', `<style>${css}</style>`); document.body.insertAdjacentHTML('beforeend', searchHTML); // 搜索引擎列表 const searchEngines = [ { name: "次元城动画", url: "https://www.cycani.org/search.html?wd=${name}" }, { name: "稀饭动漫", url: "https://dm.xifanacg.com/search.html?wd=${name}" }, { name: "MuteFun", url: "https://www.mutean.com/vodsearch/${name}-------------.html" }, { name: "咕咕番", url: "https://www.gugu3.com/index.php/vod/search.html?wd=${name}" }, { name: "NT动漫", url: "http://www.ntdm8.com/search/-------------.html?wd=${name}&page=1" }, { name: "风铃动漫", url: "https://www.bbfun.cc/#/search?wd=${name}" }, { name: "喵物次元", url: "https://www.mwcy.net/search.html?wd=${name}" }, { name: "Bangumi评分", url: "https://bangumi.tv/subject_search/${name}?cat=2" } ]; // 获取DOM元素 const searchContainer = document.getElementById('global-search-container'); const searchOverlay = document.getElementById('search-overlay'); const searchInput = document.getElementById('search-input'); const searchBtn = document.getElementById('search-btn'); const engineSelect = document.getElementById('engine-select'); const closeBtn = document.querySelector('.close-btn'); // 显示搜索框 function showSearch() { searchContainer.style.display = 'block'; searchOverlay.style.display = 'block'; searchInput.focus(); document.body.style.overflow = 'hidden'; } // 隐藏搜索框 function hideSearch() { searchContainer.style.display = 'none'; searchOverlay.style.display = 'none'; searchInput.value = ''; document.body.style.overflow = ''; } // 执行搜索 function performSearch() { const searchTerm = searchInput.value.trim(); if (!searchTerm) return; const selectedEngine = searchEngines[engineSelect.value]; const encodedTerm = encodeURIComponent(searchTerm); const searchUrl = selectedEngine.url.replace('${name}', encodedTerm); window.open(searchUrl, '_blank'); hideSearch(); } // 事件监听 document.addEventListener('keydown', function(e) { // Shift + F 打开搜索框 if (e.shiftKey && e.key === 'F') { e.preventDefault(); showSearch(); } // ESC 关闭搜索框 if (e.key === 'Escape' && searchContainer.style.display === 'block') { hideSearch(); } // 在搜索框中按Enter搜索 if (e.key === 'Enter' && document.activeElement === searchInput && searchContainer.style.display === 'block') { performSearch(); } }); searchBtn.addEventListener('click', performSearch); closeBtn.addEventListener('click', hideSearch); searchOverlay.addEventListener('click', hideSearch); })(); (function () { 'use strict'; /* ========================= 白名单配置 ========================= */ const whitelist = [ "https://www.gugu3.com/index.php/vod/play/id", "https://www.ntdm8.com/play", "https://www.cycani.org/watch", "https://dm.xifanacg.com/watch", "https://www.aafun.cc/f", "https://www.mwcy.net/play", "https://www.mutean.com/vodplay", ]; const STORAGE_KEY = 'anime_favorites_v2'; const POS_KEY = 'anime_fav_panel_pos_v2'; const NOISE_WORDS = [ '免费在线观看','在线观看','高清','超清','原声','全集','无广告','在线播放', '高清版','未删减','官方','官网','弹幕','字幕','BT','迅雷','下载','观看' ]; /* ========================= Title 捕获 ========================= */ let currentTitle = (function() { try { let tnode = document.querySelector && document.querySelector('title'); return (tnode && tnode.textContent || document.title || '').trim(); } catch(e) { return document.title || ''; } })(); const titleObserver = new MutationObserver(() => { currentTitle = document.title; }); const tNode = document.querySelector('title'); if(tNode) { titleObserver.observe(tNode, { subtree: true, characterData: true, childList: true }); } /* ========================= 数据存储 ========================= */ function getFavorites() { try { return JSON.parse(GM_getValue(STORAGE_KEY, '[]')); } catch(e) { return []; } } function saveFavorites(list) { GM_setValue(STORAGE_KEY, JSON.stringify(list)); } function savePanelPos(pos) { GM_setValue(POS_KEY, JSON.stringify(pos)); } function loadPanelPos() { try { return JSON.parse(GM_getValue(POS_KEY, 'null')); } catch(e) { return null; } } /* ========================= 标题处理 ========================= */ function cleanRawTitle(t) { if(!t) return ''; t = t.replace(/【.*?】|\[.*?\]|\(.*?\)|(.*?)/g, ''); NOISE_WORDS.forEach(w => { t = t.replace(new RegExp(w,'gi'), ''); }); t = t.replace(/\s*[-|_|—|–|_]\s*[^-_|—–_]{1,50}$/g, ''); t = t.replace(/\s+/g, ' ').trim(); return t; } function extractSeriesAndEpisode(rawTitle) { const t = cleanRawTitle(rawTitle || ''); let m = t.match(/(.+?)\s*(第[\d一二三四五六七八九十百千]+[集话回])/i); if(m) return { full: t, series: m[1].trim(), episode: m[2].trim() }; return { full: t, series: t.trim(), episode: '' }; } function normalizeSeriesName(name) { return (name||'').replace(/\s+/g,'').replace(/[^\w\u4e00-\u9fa5]/g,'').toLowerCase(); } function getCurrentTitle() { return currentTitle || document.title || ''; } /* ========================= 创建UI ========================= */ const panel = document.createElement('div'); panel.id = 'animeFavPanel'; panel.style.cssText = ` position: fixed; top: 60px; right: 20px; width: 320px; max-height: 72vh; overflow: hidden; background: rgba(255,255,255,0.96); border: 1px solid rgba(0,0,0,0.12); border-radius: 12px; box-shadow: 0 6px 20px rgba(0,0,0,0.12); padding: 8px; font-family: system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial; font-size: 13px; color: #111; display: none; z-index: 2147483646; user-select: none; `; document.body.appendChild(panel); // header const header = document.createElement('div'); header.style.cssText = ` display: flex; align-items: center; justify-content: space-between; padding: 6px 4px; cursor: grab; `; const titleText = document.createElement('strong'); titleText.textContent = '🎞 我的收藏'; header.appendChild(titleText); const closeBtn = document.createElement('button'); closeBtn.textContent = '✖'; closeBtn.style.cssText = ` border: none; background: transparent; color: #888; font-size: 14px; cursor: pointer; padding: 2px 6px; `; closeBtn.onclick = () => { panel.style.display = 'none'; }; header.appendChild(closeBtn); panel.appendChild(header); // list const listWrap = document.createElement('div'); listWrap.style.cssText = ` overflow: auto; max-height: 56vh; padding-right: 6px; `; const list = document.createElement('div'); list.id = 'animeFavList'; list.style.cssText = ` display: flex; flex-direction: column; gap: 6px; padding: 6px; `; listWrap.appendChild(list); panel.appendChild(listWrap); // footer const footer = document.createElement('div'); footer.style.cssText = ` padding: 6px 4px; border-top: 1px solid rgba(0,0,0,0.04); font-size: 12px; color: #666; `; footer.innerHTML = ` <div>快捷键:</div> <ul style="margin:4px 0;padding-left:18px;"> <li>Shift+C 显示/隐藏收藏栏</li> <li>Shift+D 收藏当前番剧(仅白名单页)</li> </ul> `; panel.appendChild(footer); /* ========================= 拖动功能 ========================= */ (function makeDraggable(handle, target) { let dragging = false; let startX = 0; let startY = 0; let startLeft = 0; let startTop = 0; handle.addEventListener('mousedown', e => { if (e.button !== 0) return; dragging = true; startX = e.clientX; startY = e.clientY; if (!target.style.left) target.style.left = target.getBoundingClientRect().left + 'px'; startLeft = parseFloat(target.style.left); startTop = parseFloat(target.style.top || target.getBoundingClientRect().top); target.style.right = 'auto'; //在document上强制设置鼠标样式 document.body.style.cursor = 'grabbing'; handle.style.cursor = 'grabbing'; document.addEventListener('mousemove', onMove); document.addEventListener('mouseup', onUp); e.preventDefault(); }); function onMove(e) { if (!dragging) return; const dx = e.clientX - startX; const dy = e.clientY - startY; const newLeft = Math.max(6, Math.min(window.innerWidth - target.offsetWidth - 6, startLeft + dx)); const newTop = Math.max(6, Math.min(window.innerHeight - target.offsetHeight - 6, startTop + dy)); target.style.left = newLeft + 'px'; target.style.top = newTop + 'px'; } function onUp() { dragging = false; //拖动结束时恢复默认鼠标样式 document.body.style.cursor = ''; handle.style.cursor = 'grab'; document.removeEventListener('mousemove', onMove); document.removeEventListener('mouseup', onUp); savePanelPos({ left: parseFloat(target.style.left), top: parseFloat(target.style.top) }); } })(header, panel); /* ========================= 渲染收藏列表 ========================= */ // 获取网站名称的辅助函数 function getSiteNameByUrl(url) { const defaultName = "未知来源"; const engines = [ { name: "次元城动画", url: "https://www.cycani.org" }, { name: "稀饭动漫", url: "https://dm.xifanacg.com" }, { name: "MuteFun", url: "https://www.mutean.com" }, { name: "咕咕番", url: "https://www.gugu3.com" }, { name: "NT动漫", url: "http://www.ntdm8.com" }, { name: "风铃动漫", url: "https://www.bbfun.cc" }, { name: "喵物次元", url: "https://www.mwcy.net" } ]; for (const engine of engines) { if (url.startsWith(engine.url)) { return engine.name; } } return defaultName; } function renderList() { list.innerHTML = ''; const data = getFavorites(); data.sort((a, b) => (b.ts || 0) - (a.ts || 0)); data.forEach((item, idx) => { const row = document.createElement('div'); row.style.cssText = ` display: flex; align-items: center; justify-content: space-between; padding: 6px; border-radius: 8px; `; const left = document.createElement('div'); left.style.cssText = ` flex: 1; min-width: 0; `; const a = document.createElement('a'); a.textContent = item.title; a.href = item.url; a.target = '_blank'; a.title = item.title; a.style.cssText = ` display: block; text-decoration: none; color: #111; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; `; const meta = document.createElement('div'); const siteName = getSiteNameByUrl(item.url); const timestamp = item.ts ? (new Date(item.ts)).toLocaleString() : ''; meta.textContent = `[${siteName}] ${timestamp}`; meta.style.cssText = ` font-size: 11px; color: #777; margin-top: 4px; `; left.appendChild(a); left.appendChild(meta); const btns = document.createElement('div'); btns.style.cssText = ` display: flex; flex-direction: column; gap: 6px; margin-left: 8px; `; const del = document.createElement('button'); del.textContent = '删除'; del.style.cssText = ` border: none; background: #e24; color: #fff; padding: 4px 8px; border-radius: 6px; cursor: pointer; font-size: 12px; `; del.onclick = () => { const arr = getFavorites(); arr.splice(idx, 1); saveFavorites(arr); renderList(); }; btns.appendChild(del); row.appendChild(left); row.appendChild(btns); list.appendChild(row); }); } /* ========================= 收藏逻辑 ========================= */ function urlInWhitelist(url) { return whitelist.some(w => url.startsWith(w)); } function collectCurrentPage() { if (!urlInWhitelist(location.href)) { alert('当前页面不在收藏白名单,无法收藏'); return; } const url = location.href; const parsed = extractSeriesAndEpisode(getCurrentTitle()); const newTitle = parsed.episode ? `${parsed.series} ${parsed.episode}` : parsed.full; const newSeriesNorm = normalizeSeriesName(parsed.series); let arr = getFavorites(); // 尝试通过番剧名查找已存在的收藏 let idx = arr.findIndex(it => normalizeSeriesName(extractSeriesAndEpisode(it.title).series) === newSeriesNorm); // 如果未找到,尝试通过 URL 的相似性来查找 if (idx === -1) { // 提取 URL 的主干部分进行比较,例如: // "https://dm.xifanacg.com/watch/3272/1/" const baseUrl = url.replace(/\/\d+\/\d+\.html$/, '/'); idx = arr.findIndex(it => it.url.startsWith(baseUrl)); } if (idx > -1) { const existing = arr[idx]; if (existing.url === url) { alert('该页面已收藏,无需重复添加:' + newTitle); return; } if (confirm(`检测到已有该番剧收藏:\n${existing.title}\n是否用当前页面更新为:\n${newTitle} ?`)) { arr.splice(idx, 1, { title: newTitle, url, ts: Date.now() }); saveFavorites(arr); renderList(); alert('已更新收藏并置顶:' + newTitle); } return; // 取消则不操作 } // 新收藏,防止重复 URL if (!arr.some(it => it.url === url)) { arr.unshift({ title: newTitle, url, ts: Date.now() }); saveFavorites(arr); renderList(); alert('已收藏:' + newTitle); } } /* ========================= 快捷键 ========================= */ document.addEventListener('keydown', e => { const tgt = e.target; const isTyping = tgt && (tgt.tagName === 'INPUT' || tgt.tagName === 'TEXTAREA' || tgt.isContentEditable); if(isTyping) return; if(e.shiftKey && e.code === 'KeyC') { panel.style.display = (panel.style.display === 'none' || panel.style.display === '') ? 'block' : 'none'; renderList(); return; } if(e.shiftKey && e.code === 'KeyD') { e.preventDefault(); collectCurrentPage(); return; } }); })();