// ==UserScript== // @name TMDB 影视资源查询 // @namespace http://tampermonkey.net/ // @version 0.0.4 // @description 获取页面影视名称,查询TMDB ID并获取资源链接 // @author goukey // @match *://*/* // @grant GM_xmlhttpRequest // @grant GM_setValue // @grant GM_getValue // @grant GM_addStyle // @connect api.themoviedb.org // @connect api.nullbr.com // @license MIT // @downloadURL none // ==/UserScript== (function () { 'use strict'; // 用户设置,使用GM_getValue获取存储的设置,如果没有则使用默认值 const userSettings = { enableSelectionSearch: GM_getValue('enableSelectionSearch', true), tmdbApiKey: GM_getValue('tmdbApiKey', ''), appId: GM_getValue('appId', ''), apiKey: GM_getValue('apiKey', '') }; // 获取API密钥(兼容旧配置) function getTmdbApiKey() { return userSettings.tmdbApiKey || ''; } function getAppId() { return userSettings.appId || ''; } function getApiKey() { return userSettings.apiKey || ''; } // 保存设置 function saveSettings() { GM_setValue('enableSelectionSearch', userSettings.enableSelectionSearch); GM_setValue('tmdbApiKey', userSettings.tmdbApiKey); GM_setValue('appId', userSettings.appId); GM_setValue('apiKey', userSettings.apiKey); } // 添加样式 GM_addStyle(` :root { --primary-color: #3b82f6; --primary-hover: #2563eb; --bg-color: #ffffff; --text-color: #1f2937; --text-secondary: #6b7280; --border-color: #e5e7eb; --shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05); --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); --radius-md: 0.5rem; --radius-lg: 0.75rem; } #tmdb-magnet-container { position: fixed; top: 20px; right: 20px; width: 380px; background-color: var(--bg-color); border-radius: var(--radius-lg); box-shadow: var(--shadow-lg); z-index: 10000; font-family: 'Inter', system-ui, -apple-system, sans-serif; display: none; overflow: hidden; border: 1px solid var(--border-color); transition: all 0.3s ease; } /* 顶部 Tab 栏 */ .tmdb-tabs { display: flex; border-bottom: 1px solid var(--border-color); background: #f9fafb; } .tmdb-tab { flex: 1; padding: 12px; text-align: center; cursor: pointer; font-size: 14px; font-weight: 500; color: var(--text-secondary); transition: all 0.2s; border-bottom: 2px solid transparent; } .tmdb-tab:hover { color: var(--primary-color); background: #f3f4f6; } .tmdb-tab.active { color: var(--primary-color); border-bottom-color: var(--primary-color); background: #fff; } .tmdb-close-btn { position: absolute; top: 8px; right: 8px; width: 24px; height: 24px; display: flex; align-items: center; justify-content: center; border-radius: 50%; cursor: pointer; color: #9ca3af; font-size: 18px; line-height: 1; z-index: 10; } .tmdb-close-btn:hover { background: #e5e7eb; color: #4b5563; } /* 内容区域 */ .tmdb-content-area { padding: 16px; height: 500px; overflow-y: auto; display: none; } .tmdb-content-area.active { display: block; } /* 输入框组 */ .input-group { display: flex; gap: 8px; margin-bottom: 16px; } #tmdb-magnet-input { flex: 1; padding: 8px 12px; border: 1px solid var(--border-color); border-radius: var(--radius-md); font-size: 14px; outline: none; transition: border-color 0.2s; } #tmdb-magnet-input:focus { border-color: var(--primary-color); box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.1); } .search-btn { padding: 8px 16px; background-color: var(--primary-color); color: white; border: none; border-radius: var(--radius-md); font-size: 14px; font-weight: 500; cursor: pointer; transition: background-color 0.2s; } .search-btn:hover { background-color: var(--primary-hover); } /* 筛选按钮 */ .filter-group { display: flex; gap: 8px; margin-bottom: 12px; flex-wrap: wrap; } .filter-btn { padding: 4px 10px; background: #f3f4f6; border: 1px solid transparent; border-radius: 100px; font-size: 12px; color: var(--text-secondary); cursor: pointer; transition: all 0.2s; } .filter-btn:hover { background: #e5e7eb; color: var(--text-color); } .filter-btn.active { background: #eff6ff; color: var(--primary-color); border-color: var(--primary-color); } /* 结果列表 */ .media-item { background: white; border: 1px solid var(--border-color); border-radius: var(--radius-md); padding: 12px; margin-bottom: 12px; display: flex; gap: 12px; transition: transform 0.2s, box-shadow 0.2s; } .media-item:hover { transform: translateY(-2px); box-shadow: var(--shadow-md); border-color: #bfdbfe; } .media-poster { width: 68px; height: 102px; border-radius: 4px; object-fit: cover; background-color: #f3f4f6; flex-shrink: 0; box-shadow: 0 2px 4px rgba(0,0,0,0.1); } .media-details { flex: 1; min-width: 0; } .media-title { font-size: 15px; font-weight: 600; color: var(--text-color); margin-bottom: 6px; display: inline-block; text-decoration: none; } .media-title:hover { color: var(--primary-color); } .media-info { display: flex; gap: 6px; flex-wrap: wrap; margin-bottom: 10px; } .media-tag { font-size: 11px; padding: 2px 6px; border-radius: 4px; background: #f3f4f6; color: var(--text-secondary); } .media-tag.movie { background: #e0f2fe; color: #0284c7; } .media-tag.tv { background: #f0f9ff; color: #0369a1; } .media-tag.link { cursor: pointer; text-decoration: none; transition: all 0.2s; } .media-tag.link:hover { background: var(--primary-color); color: white; } /* 链接列表 */ .magnet-list { border-top: 1px dashed var(--border-color); padding-top: 8px; margin-top: 8px; } .magnet-link { display: block; padding: 8px; background: #f9fafb; border-radius: var(--radius-md); margin-bottom: 6px; font-size: 12px; text-decoration: none; color: var(--text-color); transition: all 0.2s; border: 1px solid transparent; } .magnet-link:hover { background: #fff; border-color: var(--primary-color); box-shadow: var(--shadow-sm); } .magnet-link a { color: var(--text-color); text-decoration: none; font-weight: 500; display: block; margin-bottom: 2px; } .magnet-link a:hover { color: var(--primary-color); } .loading, .no-results, .error { text-align: center; padding: 20px; font-size: 13px; color: var(--text-secondary); } .error { color: #ef4444; } /* 设置页样式 */ .setting-item { margin-bottom: 16px; } .setting-label { font-size: 13px; font-weight: 500; color: var(--text-color); margin-bottom: 6px; display: block; } .setting-input { width: 100%; padding: 8px 12px; border: 1px solid var(--border-color); border-radius: var(--radius-md); font-size: 13px; outline: none; box-sizing: border-box; transition: border-color 0.2s; } .setting-input:focus { border-color: var(--primary-color); } .toggle-switch { position: relative; display: inline-block; width: 40px; height: 22px; } .toggle-switch input { opacity: 0; width: 0; height: 0; } .toggle-slider { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: #e5e7eb; transition: .4s; border-radius: 22px; } .toggle-slider:before { position: absolute; content: ""; height: 16px; width: 16px; left: 3px; bottom: 3px; background-color: white; transition: .4s; border-radius: 50%; box-shadow: 0 1px 2px rgba(0,0,0,0.1); } input:checked + .toggle-slider { background-color: var(--primary-color); } input:checked + .toggle-slider:before { transform: translateX(18px); } .save-btn { width: 100%; padding: 10px; background-color: var(--primary-color); color: white; border: none; border-radius: var(--radius-md); font-weight: 500; cursor: pointer; margin-top: 12px; transition: opacity 0.2s; } .save-btn:hover { opacity: 0.9; } /* 悬浮球美化 */ .tmdb-icon { position: fixed; bottom: 30px; right: 30px; width: 48px; height: 48px; background: linear-gradient(135deg, #3b82f6, #2563eb); border-radius: 50%; display: flex; justify-content: center; align-items: center; color: white; font-weight: 700; cursor: pointer; box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); z-index: 9998; transition: transform 0.2s; font-size: 14px; } .tmdb-icon:hover { transform: scale(1.1); } /* 滚动条 */ .tmdb-content-area::-webkit-scrollbar { width: 5px; } .tmdb-content-area::-webkit-scrollbar-thumb { background: #d1d5db; border-radius: 10px; } .tmdb-content-area::-webkit-scrollbar-thumb:hover { background: #9ca3af; } `); // 创建设置项输入框 function createApiKeyInput(label, key, value, placeholder) { const item = document.createElement('div'); item.style.marginBottom = '12px'; const labelDiv = document.createElement('div'); labelDiv.textContent = label; labelDiv.style.cssText = 'font-size: 12px; color: #666; margin-bottom: 4px;'; const input = document.createElement('input'); input.type = 'text'; input.value = value || ''; input.placeholder = placeholder; input.style.cssText = 'width: 100%; padding: 8px 12px; border: 1px solid #ddd; border-radius: 6px; font-size: 13px; box-sizing: border-box;'; input.addEventListener('input', (e) => { userSettings[key] = e.target.value.trim(); }); item.appendChild(labelDiv); item.appendChild(input); return item; } // 创建UI function createUI() { // 创建主容器 const container = document.createElement('div'); container.id = 'tmdb-magnet-container'; // 1. 关闭按钮 const closeBtn = document.createElement('div'); closeBtn.className = 'tmdb-close-btn'; closeBtn.innerHTML = '×'; closeBtn.title = '关闭'; closeBtn.addEventListener('click', () => { container.style.display = 'none'; }); container.appendChild(closeBtn); // 2. Tab 栏 const tabsContainer = document.createElement('div'); tabsContainer.className = 'tmdb-tabs'; const searchTab = document.createElement('div'); searchTab.className = 'tmdb-tab active'; searchTab.textContent = '影视搜索'; const settingsTab = document.createElement('div'); settingsTab.className = 'tmdb-tab'; settingsTab.textContent = '配置设置'; tabsContainer.appendChild(searchTab); tabsContainer.appendChild(settingsTab); container.appendChild(tabsContainer); // 3. 搜索面板 const searchPanel = document.createElement('div'); searchPanel.className = 'tmdb-content-area active'; searchPanel.id = 'tmdb-search-panel'; // 输入组 const inputGroup = document.createElement('div'); inputGroup.className = 'input-group'; const input = document.createElement('input'); input.id = 'tmdb-magnet-input'; input.type = 'text'; input.placeholder = '输入电影或剧集名称...'; const searchBtn = document.createElement('button'); searchBtn.className = 'search-btn'; searchBtn.textContent = '搜索'; inputGroup.appendChild(input); inputGroup.appendChild(searchBtn); searchPanel.appendChild(inputGroup); // 资源类型切换 const sourceGroup = document.createElement('div'); sourceGroup.className = 'filter-group'; const sourceTypes = [ { label: '🧲 磁力', value: 'magnet' }, { label: '🌐 115', value: '115' }, { label: '⚡ ED2K', value: 'ed2k' }, { label: '▶️ 在线', value: 'm3u8' } ]; const sourceBtns = {}; sourceTypes.forEach(st => { const btn = document.createElement('button'); btn.textContent = st.label; btn.className = 'filter-btn'; if (st.value === 'magnet') btn.classList.add('active'); btn.addEventListener('click', () => { if (currentSource === st.value) return; currentSource = st.value; Object.values(sourceBtns).forEach(b => b.classList.remove('active')); btn.classList.add('active'); // 重新显示结果 if (window._tmdbLastResults) { displayResults(window._tmdbLastResults, false); } }); sourceBtns[st.value] = btn; sourceGroup.appendChild(btn); }); searchPanel.appendChild(sourceGroup); // 类型筛选 const filterGroup = document.createElement('div'); filterGroup.className = 'filter-group'; const filterTypes = [ { label: '全部', value: 'all' }, { label: '电影', value: 'movie' }, { label: '剧集', value: 'tv' } ]; const filterBtns = {}; filterTypes.forEach(ft => { const btn = document.createElement('button'); btn.textContent = ft.label; btn.className = 'filter-btn'; if (ft.value === 'all') btn.classList.add('active'); btn.addEventListener('click', () => { currentFilter = ft.value; Object.values(filterBtns).forEach(b => b.classList.remove('active')); btn.classList.add('active'); if (window._tmdbLastResults) { const magnetCache = saveMagnetCache(); // 保存现有磁链 displayResults(window._tmdbLastResults, false); restoreMagnetCache(magnetCache); // 恢复磁链 } }); filterBtns[ft.value] = btn; filterGroup.appendChild(btn); }); searchPanel.appendChild(filterGroup); // 结果容器 const resultsContainer = document.createElement('div'); resultsContainer.id = 'tmdb-magnet-results'; searchPanel.appendChild(resultsContainer); container.appendChild(searchPanel); // 4. 设置面板 const settingsPanel = document.createElement('div'); settingsPanel.className = 'tmdb-content-area'; settingsPanel.id = 'tmdb-settings-panel'; // 选中搜索开关 const selectionSearchSetting = document.createElement('div'); selectionSearchSetting.className = 'setting-item'; const selectionLabel = document.createElement('span'); selectionLabel.className = 'setting-label'; selectionLabel.textContent = '启用划词搜索 (选中文字弹出搜索按钮)'; selectionLabel.style.marginBottom = '8px'; selectionLabel.style.display = 'inline-block'; const toggleWrapper = document.createElement('div'); const selectionToggle = document.createElement('label'); selectionToggle.className = 'toggle-switch'; const selectionInput = document.createElement('input'); selectionInput.type = 'checkbox'; selectionInput.checked = userSettings.enableSelectionSearch; selectionInput.addEventListener('change', function () { userSettings.enableSelectionSearch = this.checked; saveSettings(); }); const slider = document.createElement('span'); slider.className = 'toggle-slider'; selectionToggle.appendChild(selectionInput); selectionToggle.appendChild(slider); toggleWrapper.appendChild(selectionToggle); selectionSearchSetting.appendChild(selectionLabel); selectionSearchSetting.appendChild(toggleWrapper); settingsPanel.appendChild(selectionSearchSetting); // 分隔线 const divider = document.createElement('hr'); divider.style.cssText = 'border: 0; border-top: 1px solid #e5e7eb; margin: 20px 0;'; settingsPanel.appendChild(divider); // API 设置 settingsPanel.appendChild(createApiKeyInput('TMDB API Key', 'tmdbApiKey', userSettings.tmdbApiKey, '请输入 TMDB API Key')); settingsPanel.appendChild(createApiKeyInput('nullbr APP ID', 'appId', userSettings.appId, '请输入 APP ID')); settingsPanel.appendChild(createApiKeyInput('nullbr API Key', 'apiKey', userSettings.apiKey, '请输入 API Key')); // 保存按钮 const saveBtn = document.createElement('button'); saveBtn.className = 'save-btn'; saveBtn.textContent = '保存配置'; saveBtn.addEventListener('click', () => { saveSettings(); const originalText = saveBtn.textContent; saveBtn.textContent = '已保存!'; saveBtn.style.backgroundColor = '#10b981'; setTimeout(() => { saveBtn.textContent = originalText; saveBtn.style.backgroundColor = ''; }, 2000); }); settingsPanel.appendChild(saveBtn); container.appendChild(settingsPanel); // 5. Tab 切换逻辑 searchTab.addEventListener('click', () => { searchTab.classList.add('active'); settingsTab.classList.remove('active'); searchPanel.classList.add('active'); settingsPanel.classList.remove('active'); }); settingsTab.addEventListener('click', () => { settingsTab.classList.add('active'); searchTab.classList.remove('active'); settingsPanel.classList.add('active'); searchPanel.classList.remove('active'); }); // 6. 添加到页面 document.body.appendChild(container); // 悬浮球 const iconBtn = document.createElement('div'); iconBtn.className = 'tmdb-icon'; iconBtn.textContent = '搜源'; iconBtn.title = '打开资源搜索'; iconBtn.addEventListener('click', () => { const display = container.style.display; container.style.display = (display === 'none' || !display) ? 'block' : 'none'; }); document.body.appendChild(iconBtn); // 搜索事件绑定 const triggerSearch = () => { const val = input.value.trim(); if (val) searchTmdb(val); }; searchBtn.addEventListener('click', triggerSearch); input.addEventListener('keypress', (e) => { if (e.key === 'Enter') triggerSearch(); }); } // 保存当前已获取的磁链信息 function saveMagnetCache() { const cache = {}; const movieItems = document.querySelectorAll('.media-item'); movieItems.forEach(item => { const info = item.querySelector('.media-info'); if (info) { const infoText = info.textContent; const idMatch = infoText.match(/TMDB ID: (\d+)/); if (idMatch && idMatch[1]) { const id = idMatch[1]; const type = infoText.includes('电影') ? 'movie' : 'tv'; const magnetList = item.querySelector('.magnet-list'); if (magnetList && !magnetList.querySelector('.loading')) { // 区分来源缓存 const key = `${type}-${id}-${currentSource}`; cache[key] = magnetList.innerHTML; } } } }); return cache; } // 恢复之前已获取的磁链信息 function restoreMagnetCache(cache) { const movieItems = document.querySelectorAll('.media-item'); movieItems.forEach(item => { const info = item.querySelector('.media-info'); if (info) { const infoText = info.textContent; const idMatch = infoText.match(/TMDB ID: (\d+)/); if (idMatch && idMatch[1]) { const id = idMatch[1]; const type = infoText.includes('电影') ? 'movie' : 'tv'; const magnetList = item.querySelector('.magnet-list'); // 获取 magnetList const key = `${type}-${id}-${currentSource}`; // 构建缓存key if (magnetList && cache[key]) { magnetList.innerHTML = cache[key]; } else if (magnetList) { // 如果没有缓存内容或者是新的来源,重新请求 if (currentSource === '115') { get115Links(id, type, magnetList); } else if (currentSource === 'ed2k') { getEd2kLinks(id, type, magnetList); } else if (currentSource === 'm3u8') { getM3u8Links(id, type, magnetList); } else { getMagnetLinks(id, type, magnetList); } } } } }); } // 搜索TMDB API function searchTmdb(query) { const resultsContainer = document.getElementById('tmdb-magnet-results'); resultsContainer.innerHTML = '