Greasy Fork is available in English.
获取页面影视名称,查询TMDB ID并获取资源链接
// ==UserScript==
// @name TMDB 影视资源查询
// @namespace http://tampermonkey.net/
// @version 0.0.5
// @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
// ==/UserScript==
(function () {
'use strict';
// 用户设置,使用GM_getValue获取存储的设置,如果没有则使用默认值
const userSettings = {
enableSelectionSearch: GM_getValue('enableSelectionSearch', true),
enableTgSearch: GM_getValue('enableTgSearch', 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('enableTgSearch', userSettings.enableTgSearch);
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.tg { background: #e1f5fe; color: #0288d1; }
.media-tag.link {
cursor: pointer;
text-decoration: none;
transition: all 0.2s;
}
.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;
}
/* TG 搜索按钮 */
.tg-search-btn {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 5px 12px;
background: linear-gradient(135deg, #0088cc, #00aaff);
color: white;
border-radius: 6px;
font-size: 11px;
cursor: pointer;
margin-top: 8px;
border: none;
transition: all 0.2s;
font-weight: 500;
text-decoration: none;
}
.tg-search-btn:hover {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(0, 136, 204, 0.3);
filter: brightness(1.1);
}
/* Toast 提示 */
.tmdb-toast {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: rgba(0, 0, 0, 0.75);
color: white;
padding: 10px 20px;
border-radius: 8px;
font-size: 14px;
z-index: 11000;
pointer-events: none;
backdrop-filter: blur(4px);
opacity: 0;
transition: opacity 0.3s;
}
.tmdb-toast.show {
opacity: 1;
}
`);
// 创建设置项输入框
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: '🌐 115', value: '115' },
{ label: '🧲 磁力', value: 'magnet' },
{ 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 === '115') 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);
// TG 搜索助手开关
const tgSearchSetting = document.createElement('div');
tgSearchSetting.className = 'setting-item';
const tgLabel = document.createElement('span');
tgLabel.className = 'setting-label';
tgLabel.textContent = '启用 Telegram 115 搜索助手 (复制指令并跳转)';
tgLabel.style.marginBottom = '8px';
tgLabel.style.display = 'inline-block';
const tgToggleWrapper = document.createElement('div');
const tgToggle = document.createElement('label');
tgToggle.className = 'toggle-switch';
const tgInput = document.createElement('input');
tgInput.type = 'checkbox';
tgInput.checked = userSettings.enableTgSearch;
tgInput.addEventListener('change', function () {
userSettings.enableTgSearch = this.checked;
saveSettings();
});
const tgSlider = document.createElement('span');
tgSlider.className = 'toggle-slider';
tgToggle.appendChild(tgInput);
tgToggle.appendChild(tgSlider);
tgToggleWrapper.appendChild(tgToggle);
tgSearchSetting.appendChild(tgLabel);
tgSearchSetting.appendChild(tgToggleWrapper);
settingsPanel.appendChild(tgSearchSetting);
// 分隔线
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);
// 初始化 Toast
const toast = document.createElement('div');
toast.className = 'tmdb-toast';
toast.id = 'tmdb-toast';
document.body.appendChild(toast);
// 搜索事件绑定
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 = '<div class="loading">正在搜索TMDB...</div>';
// 构建URL,搜索电影和电视剧
const tmdbKey = getTmdbApiKey();
const movieUrl = `https://api.themoviedb.org/3/search/movie?api_key=${tmdbKey}&query=${encodeURIComponent(query)}&language=zh-CN`;
const tvUrl = `https://api.themoviedb.org/3/search/tv?api_key=${tmdbKey}&query=${encodeURIComponent(query)}&language=zh-CN`;
// 先搜索电影
GM_xmlhttpRequest({
method: 'GET',
url: movieUrl,
headers: {
'Accept': 'application/json'
},
onload: function (response) {
try {
const movieData = JSON.parse(response.responseText);
// 然后搜索电视剧
GM_xmlhttpRequest({
method: 'GET',
url: tvUrl,
headers: {
'Accept': 'application/json'
},
onload: function (tvResponse) {
try {
const tvData = JSON.parse(tvResponse.responseText);
// 合并结果
const results = [
...movieData.results.map(item => ({ ...item, type: 'movie' })),
...tvData.results.map(item => ({ ...item, type: 'tv' }))
];
// 显示结果
displayResults(results);
} catch (error) {
showError('解析电视剧数据失败: ' + error.message);
}
},
onerror: function () {
showError('获取电视剧数据失败');
}
});
} catch (error) {
showError('解析电影数据失败: ' + error.message);
}
},
onerror: function () {
showError('获取电影数据失败');
}
});
}
// 显示搜索结果
function displayResults(results, skipMagnet) {
const resultsContainer = document.getElementById('tmdb-magnet-results');
window._tmdbLastResults = results; // 用于筛选切换时重渲染
if (!results || results.length === 0) {
resultsContainer.innerHTML = '<div class="no-results">未找到相关影视作品</div>';
return;
}
// 清空容器
resultsContainer.innerHTML = '';
// 根据筛选类型过滤
let filteredResults = results;
if (typeof currentFilter !== 'undefined' && currentFilter !== 'all') {
filteredResults = results.filter(item => item.type === currentFilter);
}
if (filteredResults.length === 0) {
resultsContainer.innerHTML = '<div class="no-results">筛选后无结果</div>';
return;
}
// 展示全部结果(不再限制5条)
for (const item of filteredResults) {
const resultItem = document.createElement('div');
resultItem.className = 'media-item';
// TMDB 详情链接基础 URL
const tmdbDetailUrl = `https://www.themoviedb.org/${item.type}/${item.id}`;
// 左侧封面图
const posterLink = document.createElement('a');
posterLink.href = tmdbDetailUrl;
posterLink.target = '_blank';
const poster = document.createElement('img');
poster.className = 'media-poster';
poster.src = item.poster_path ? `https://image.tmdb.org/t/p/w92${item.poster_path}` : 'https://via.placeholder.com/68x102?text=No+Poster';
poster.alt = item.type === 'movie' ? item.title : item.name;
posterLink.appendChild(poster);
resultItem.appendChild(posterLink);
// 右侧详情区
const details = document.createElement('div');
details.className = 'media-details';
// 标题 (也是链接)
const title = document.createElement('a');
title.className = 'media-title';
title.href = tmdbDetailUrl;
title.target = '_blank';
title.textContent = item.type === 'movie' ? item.title : item.name;
// 信息
const info = document.createElement('div');
info.className = 'media-info';
// 添加年份
let year = '';
if (item.type === 'movie' && item.release_date) {
year = new Date(item.release_date).getFullYear();
} else if (item.type === 'tv' && item.first_air_date) {
year = new Date(item.first_air_date).getFullYear();
}
// 创建类型标签
const typeSpan = document.createElement('span');
typeSpan.textContent = item.type === 'movie' ? '电影' : '剧集';
typeSpan.className = `media-tag ${item.type}`;
// 创建年份标签
const yearSpan = document.createElement('span');
yearSpan.textContent = year || '未知年份';
yearSpan.className = 'media-tag';
yearSpan.style.background = '#f3f4f6';
// 创建TMDB ID标签 (也是链接)
const idLink = document.createElement('a');
idLink.href = tmdbDetailUrl;
idLink.target = '_blank';
idLink.className = 'media-tag link';
idLink.textContent = `TMDB ID: ${item.id}`;
idLink.style.background = '#f3f4f6';
// 添加标签到信息区
info.appendChild(typeSpan);
info.appendChild(yearSpan);
info.appendChild(idLink);
// 磁力链接区域
const magnetList = document.createElement('div');
magnetList.className = 'magnet-list';
magnetList.innerHTML = `<div class="loading">获取${getSourceName(currentSource)}中...</div>`;
// 组装内容到详情区
details.appendChild(title);
details.appendChild(info);
// TG 搜索按钮 (仅在 115 标签下显示,且需开启开关)
if (currentSource === '115' && userSettings.enableTgSearch) {
const tgBtn = document.createElement('button');
tgBtn.className = 'tg-search-btn';
tgBtn.innerHTML = '<span>✈️</span> TG 机器人搜索';
tgBtn.title = '此结果由网友投稿分享到 Telegram 机器人';
tgBtn.addEventListener('click', (e) => {
e.stopPropagation();
const name = item.type === 'movie' ? item.title : item.name;
handleTelegramSearch(name);
});
details.appendChild(tgBtn);
}
details.appendChild(magnetList);
// 添加详情区到主容器
resultItem.appendChild(details);
// 添加到结果容器
resultsContainer.appendChild(resultItem);
// 获取资源
if (!skipMagnet) {
if (currentSource === '115') {
get115Links(item.id, item.type, magnetList);
} else if (currentSource === 'ed2k') {
getEd2kLinks(item.id, item.type, magnetList);
} else if (currentSource === 'm3u8') {
getM3u8Links(item.id, item.type, magnetList);
} else {
getMagnetLinks(item.id, item.type, magnetList);
}
}
}
}
// 获取磁力链接
function getMagnetLinks(tmdbId, type, container) {
if (type === 'movie') {
// 电影:用movie接口
const url = `https://api.nullbr.com/movie/${tmdbId}`;
GM_xmlhttpRequest({
method: 'GET',
url: url,
headers: {
'Accept': 'application/json',
'X-APP-ID': getAppId()
},
onload: function (response) {
try {
const data = JSON.parse(response.responseText);
if (data['magnet-flg'] === 1) {
getMovieMagnets(tmdbId, container);
} else {
container.innerHTML = '<div class="no-results">未找到磁力链接</div>';
}
} catch (error) {
container.innerHTML = `<div class="error">解析电影信息失败: ${error.message}`;
}
},
onerror: function () {
container.innerHTML = '<div class="error">获取电影信息失败</div>';
}
});
} else if (type === 'tv') {
// 剧集:用tv接口,自动获取所有季和集的magnet标志
const url = `https://api.nullbr.com/tv/${tmdbId}`;
GM_xmlhttpRequest({
method: 'GET',
url: url,
headers: {
'Accept': 'application/json',
'X-APP-ID': getAppId()
},
onload: function (response) {
try {
const data = JSON.parse(response.responseText);
if (!data.number_of_seasons || data.number_of_seasons < 1) {
container.innerHTML = '<div class="no-results">未找到季信息</div>';
return;
}
// 展示所有季
container.innerHTML = '';
for (let season = 1; season <= data.number_of_seasons; season++) {
getSeasonMagnets(tmdbId, season, container);
}
} catch (error) {
container.innerHTML = `<div class="error">解析剧集信息失败: ${error.message}`;
}
},
onerror: function () {
container.innerHTML = '<div class="error">获取剧集信息失败</div>';
}
});
}
}
// 获取115网盘资源
function get115Links(tmdbId, type, container) {
const url = `https://api.nullbr.com/${type}/${tmdbId}/115`;
GM_xmlhttpRequest({
method: 'GET',
url: url,
headers: {
'Accept': 'application/json',
'X-APP-ID': getAppId(),
'X-API-KEY': getApiKey()
},
onload: function (response) {
try {
const data = JSON.parse(response.responseText);
if (data && data['115'] && Array.isArray(data['115']) && data['115'].length > 0) {
container.innerHTML = '';
data['115'].forEach(item => {
const linkItem = document.createElement('div');
linkItem.className = 'magnet-link';
// 创建链接
const link = document.createElement('a');
link.href = item.share_link;
link.target = '_blank';
link.title = item.title;
link.textContent = item.title;
link.style.color = '#2ecc71'; // 115链接使用绿色区分
// 创建信息标签
const infoSpan = document.createElement('div');
infoSpan.style.fontSize = '12px';
infoSpan.style.color = '#888';
infoSpan.style.marginTop = '3px';
// 添加文件大小
if (item.size) {
const sizeSpan = document.createElement('span');
sizeSpan.textContent = item.size;
sizeSpan.style.marginRight = '8px';
infoSpan.appendChild(sizeSpan);
}
// 添加分辨率
if (item.resolution) {
const resSpan = document.createElement('span');
resSpan.textContent = item.resolution;
resSpan.style.backgroundColor = '#f0f0f0';
resSpan.style.padding = '1px 5px';
resSpan.style.borderRadius = '3px';
resSpan.style.marginRight = '5px';
infoSpan.appendChild(resSpan);
}
// 添加质量
if (item.quality) {
const qualitySpan = document.createElement('span');
qualitySpan.textContent = item.quality;
qualitySpan.style.backgroundColor = '#f0f0f0';
qualitySpan.style.padding = '1px 5px';
qualitySpan.style.borderRadius = '3px';
qualitySpan.style.marginRight = '5px';
infoSpan.appendChild(qualitySpan);
}
// 剧集显示季列表
if (item.season_list && Array.isArray(item.season_list) && item.season_list.length > 0) {
const seasonSpan = document.createElement('span');
seasonSpan.textContent = item.season_list.join(', ');
seasonSpan.style.backgroundColor = '#e8f5e9';
seasonSpan.style.color = '#2e7d32';
seasonSpan.style.padding = '1px 5px';
seasonSpan.style.borderRadius = '3px';
infoSpan.appendChild(seasonSpan);
}
linkItem.appendChild(link);
linkItem.appendChild(infoSpan);
container.appendChild(linkItem);
});
} else {
container.innerHTML = '<div class="no-results">未找到115资源</div>';
}
} catch (error) {
container.innerHTML = `<div class="error">解析115资源失败: ${error.message}</div>`;
}
},
onerror: function () {
container.innerHTML = '<div class="error">获取115资源失败</div>';
}
});
}
// 获取电影磁力资源
function getMovieMagnets(tmdbId, container) {
const url = `https://api.nullbr.com/movie/${tmdbId}/magnet`;
GM_xmlhttpRequest({
method: 'GET',
url: url,
headers: {
'Accept': 'application/json',
'X-APP-ID': getAppId(),
'X-API-KEY': getApiKey()
},
onload: function (response) {
try {
const data = JSON.parse(response.responseText);
if (Array.isArray(data.magnet) && data.magnet.length > 0) {
container.innerHTML = '';
data.magnet.forEach(item => {
const magnetItem = document.createElement('div');
magnetItem.className = 'magnet-link';
// 创建链接
const link = document.createElement('a');
link.href = item.magnet;
link.target = '_blank';
link.title = item.magnet;
link.textContent = item.name;
// 创建信息标签
const infoSpan = document.createElement('div');
infoSpan.style.fontSize = '12px';
infoSpan.style.color = '#888';
infoSpan.style.marginTop = '3px';
// 添加文件大小
const sizeSpan = document.createElement('span');
sizeSpan.textContent = item.size;
sizeSpan.style.marginRight = '8px';
infoSpan.appendChild(sizeSpan);
// 添加分辨率
if (item.resolution) {
const resSpan = document.createElement('span');
resSpan.textContent = item.resolution;
resSpan.style.backgroundColor = '#f0f0f0';
resSpan.style.padding = '1px 5px';
resSpan.style.borderRadius = '3px';
resSpan.style.marginRight = '5px';
infoSpan.appendChild(resSpan);
}
// 添加来源
if (item.source) {
const sourceSpan = document.createElement('span');
sourceSpan.textContent = item.source;
sourceSpan.style.backgroundColor = '#f0f0f0';
sourceSpan.style.padding = '1px 5px';
sourceSpan.style.borderRadius = '3px';
sourceSpan.style.marginRight = '5px';
infoSpan.appendChild(sourceSpan);
}
// 添加质量
if (item.quality) {
const qualityText = Array.isArray(item.quality) ? item.quality.join(',') : item.quality;
if (qualityText) {
const qualitySpan = document.createElement('span');
qualitySpan.textContent = qualityText;
qualitySpan.style.backgroundColor = '#f0f0f0';
qualitySpan.style.padding = '1px 5px';
qualitySpan.style.borderRadius = '3px';
infoSpan.appendChild(qualitySpan);
}
}
// 添加中文字幕标记
if (item.zh_sub === 1) {
const zhSpan = document.createElement('span');
zhSpan.textContent = '中字';
zhSpan.style.backgroundColor = '#4caf50';
zhSpan.style.color = 'white';
zhSpan.style.padding = '1px 5px';
zhSpan.style.borderRadius = '3px';
zhSpan.style.marginLeft = '5px';
infoSpan.appendChild(zhSpan);
}
// 组装磁力项
magnetItem.appendChild(link);
magnetItem.appendChild(infoSpan);
container.appendChild(magnetItem);
});
} else {
container.innerHTML = '<div class="no-results">未找到磁力资源</div>';
}
} catch (error) {
container.innerHTML = `<div class="error">解析磁力资源失败: ${error.message}</div>`;
}
},
onerror: function () {
container.innerHTML = '<div class="error">获取磁力资源失败</div>';
}
});
}
// 获取某一季的磁力资源(不再请求单集磁力)
function getSeasonMagnets(tmdbId, season, container) {
const url = `https://api.nullbr.com/tv/${tmdbId}/season/${season}/magnet`;
GM_xmlhttpRequest({
method: 'GET',
url: url,
headers: {
'Accept': 'application/json',
'X-APP-ID': getAppId(),
'X-API-KEY': getApiKey()
},
onload: function (response) {
try {
const data = JSON.parse(response.responseText);
// 创建季标题
const seasonHeader = document.createElement('div');
seasonHeader.style.marginTop = '12px';
seasonHeader.style.marginBottom = '8px';
seasonHeader.style.fontWeight = '600';
seasonHeader.style.display = 'flex';
seasonHeader.style.alignItems = 'center';
const seasonBadge = document.createElement('span');
seasonBadge.textContent = `第${season}季`;
seasonBadge.style.backgroundColor = '#0d253f';
seasonBadge.style.color = 'white';
seasonBadge.style.padding = '2px 8px';
seasonBadge.style.borderRadius = '4px';
seasonBadge.style.fontSize = '13px';
seasonHeader.appendChild(seasonBadge);
container.appendChild(seasonHeader);
// 创建磁力链接列表
const magnetContainer = document.createElement('div');
magnetContainer.style.marginLeft = '10px';
if (Array.isArray(data.magnet) && data.magnet.length > 0) {
data.magnet.forEach(item => {
const magnetItem = document.createElement('div');
magnetItem.className = 'magnet-link';
// 创建链接
const link = document.createElement('a');
link.href = item.magnet;
link.target = '_blank';
link.title = item.magnet;
link.textContent = item.name;
// 创建信息标签
const infoSpan = document.createElement('div');
infoSpan.style.fontSize = '12px';
infoSpan.style.color = '#888';
infoSpan.style.marginTop = '3px';
// 添加文件大小
const sizeSpan = document.createElement('span');
sizeSpan.textContent = item.size;
sizeSpan.style.marginRight = '8px';
infoSpan.appendChild(sizeSpan);
// 添加分辨率
if (item.resolution) {
const resSpan = document.createElement('span');
resSpan.textContent = item.resolution;
resSpan.style.backgroundColor = '#f0f0f0';
resSpan.style.padding = '1px 5px';
resSpan.style.borderRadius = '3px';
resSpan.style.marginRight = '5px';
infoSpan.appendChild(resSpan);
}
// 添加来源
if (item.source) {
const sourceSpan = document.createElement('span');
sourceSpan.textContent = item.source;
sourceSpan.style.backgroundColor = '#f0f0f0';
sourceSpan.style.padding = '1px 5px';
sourceSpan.style.borderRadius = '3px';
sourceSpan.style.marginRight = '5px';
infoSpan.appendChild(sourceSpan);
}
// 添加质量
if (item.quality) {
const qualityText = Array.isArray(item.quality) ? item.quality.join(',') : item.quality;
if (qualityText) {
const qualitySpan = document.createElement('span');
qualitySpan.textContent = qualityText;
qualitySpan.style.backgroundColor = '#f0f0f0';
qualitySpan.style.padding = '1px 5px';
qualitySpan.style.borderRadius = '3px';
infoSpan.appendChild(qualitySpan);
}
}
// 添加中文字幕标记
if (item.zh_sub === 1) {
const zhSpan = document.createElement('span');
zhSpan.textContent = '中字';
zhSpan.style.backgroundColor = '#4caf50';
zhSpan.style.color = 'white';
zhSpan.style.padding = '1px 5px';
zhSpan.style.borderRadius = '3px';
zhSpan.style.marginLeft = '5px';
infoSpan.appendChild(zhSpan);
}
// 组装磁力项
magnetItem.appendChild(link);
magnetItem.appendChild(infoSpan);
magnetContainer.appendChild(magnetItem);
});
} else {
const noResults = document.createElement('div');
noResults.className = 'no-results';
noResults.style.margin = '5px 0';
noResults.textContent = '未找到磁力资源';
magnetContainer.appendChild(noResults);
}
container.appendChild(magnetContainer);
} catch (error) {
const errDiv = document.createElement('div');
errDiv.className = 'error';
errDiv.textContent = `解析第${season}季磁力资源失败: ${error.message}`;
container.appendChild(errDiv);
}
},
onerror: function () {
const errDiv = document.createElement('div');
errDiv.className = 'error';
errDiv.textContent = `获取第${season}季磁力资源失败`;
container.appendChild(errDiv);
}
});
}
// 获取ED2K资源
function getEd2kLinks(tmdbId, type, container) {
if (type === 'tv') {
container.innerHTML = '<div class="no-results">剧集暂不支持ED2K批量获取,请使用磁力或115</div>';
return;
}
const url = `https://api.nullbr.com/${type}/${tmdbId}/ed2k`;
GM_xmlhttpRequest({
method: 'GET',
url: url,
headers: {
'Accept': 'application/json',
'X-APP-ID': getAppId(),
'X-API-KEY': getApiKey()
},
onload: function (response) {
try {
const data = JSON.parse(response.responseText);
if (data && data.ed2k && Array.isArray(data.ed2k) && data.ed2k.length > 0) {
container.innerHTML = '';
// 排序:有中文字幕的优先
data.ed2k.sort((a, b) => (b.zh_sub || 0) - (a.zh_sub || 0));
data.ed2k.forEach(item => {
const linkItem = document.createElement('div');
linkItem.className = 'magnet-link';
const link = document.createElement('a');
link.href = item.ed2k;
link.textContent = item.name;
link.style.color = '#e67e22'; // ED2K使用橙色
const infoSpan = document.createElement('div');
infoSpan.style.fontSize = '12px';
infoSpan.style.color = '#888';
infoSpan.style.marginTop = '3px';
if (item.size) {
const sizeSpan = document.createElement('span');
sizeSpan.textContent = item.size;
sizeSpan.style.marginRight = '8px';
infoSpan.appendChild(sizeSpan);
}
if (item.resolution) {
const resSpan = document.createElement('span');
resSpan.textContent = item.resolution;
resSpan.style.backgroundColor = '#f0f0f0';
resSpan.style.padding = '1px 5px';
resSpan.style.borderRadius = '3px';
resSpan.style.marginRight = '5px';
infoSpan.appendChild(resSpan);
}
if (item.zh_sub === 1) {
const zhSpan = document.createElement('span');
zhSpan.textContent = '中字';
zhSpan.style.backgroundColor = '#4caf50';
zhSpan.style.color = 'white';
zhSpan.style.padding = '1px 5px';
zhSpan.style.borderRadius = '3px';
infoSpan.appendChild(zhSpan);
}
linkItem.appendChild(link);
linkItem.appendChild(infoSpan);
container.appendChild(linkItem);
});
} else {
container.innerHTML = '<div class="no-results">未找到ED2K资源</div>';
}
} catch (error) {
container.innerHTML = `<div class="error">解析ED2K失败: ${error.message}</div>`;
}
},
onerror: function () {
container.innerHTML = '<div class="error">获取ED2K资源失败</div>';
}
});
}
// 获取M3U8/在线资源
function getM3u8Links(tmdbId, type, container) {
if (type === 'tv') {
container.innerHTML = '<div class="no-results">剧集暂不支持在线资源批量获取,请使用磁力或115</div>';
return;
}
const url = `https://api.nullbr.com/${type}/${tmdbId}/video`;
GM_xmlhttpRequest({
method: 'GET',
url: url,
headers: {
'Accept': 'application/json',
'X-APP-ID': getAppId(),
'X-API-KEY': getApiKey()
},
onload: function (response) {
try {
const data = JSON.parse(response.responseText);
if (data && data.video && Array.isArray(data.video) && data.video.length > 0) {
container.innerHTML = '';
data.video.forEach(item => {
const linkItem = document.createElement('div');
linkItem.className = 'magnet-link';
const link = document.createElement('a');
link.href = item.link;
link.target = '_blank';
link.textContent = `[${item.name}] ${item.source}`;
link.style.color = '#e74c3c'; // M3U8使用红色
const infoSpan = document.createElement('div');
infoSpan.style.fontSize = '12px';
infoSpan.style.color = '#888';
infoSpan.style.marginTop = '3px';
const typeSpan = document.createElement('span');
typeSpan.textContent = item.type.toUpperCase();
typeSpan.style.backgroundColor = '#f0f0f0';
typeSpan.style.padding = '1px 5px';
typeSpan.style.borderRadius = '3px';
infoSpan.appendChild(typeSpan);
linkItem.appendChild(link);
linkItem.appendChild(infoSpan);
container.appendChild(linkItem);
});
} else {
container.innerHTML = '<div class="no-results">未找到在线资源</div>';
}
} catch (error) {
container.innerHTML = `<div class="error">解析在线资源失败: ${error.message}</div>`;
}
},
onerror: function () {
container.innerHTML = '<div class="error">获取在线资源失败</div>';
}
});
}
// 获取资源名称辅助函数
function getSourceName(source) {
switch (source) {
case '115': return '115资源';
case 'ed2k': return 'ED2K资源';
case 'm3u8': return '在线资源';
case 'telegram': return 'Telegram机器人';
default: return '磁力链接';
}
}
// 显示错误信息
function showError(message) {
const resultsContainer = document.getElementById('tmdb-magnet-results');
resultsContainer.innerHTML = `<div class="error">${message}</div>`;
}
// 提示信息
function showToast(message) {
const toast = document.getElementById('tmdb-toast');
if (!toast) return;
toast.textContent = message;
toast.classList.add('show');
setTimeout(() => {
toast.classList.remove('show');
}, 2500);
}
// 复制到剪贴板
function copyToClipboard(text) {
const tempInput = document.createElement('textarea');
tempInput.value = text;
document.body.appendChild(tempInput);
tempInput.select();
document.execCommand('copy');
document.body.removeChild(tempInput);
}
// 处理 Telegram 搜索跳转
function handleTelegramSearch(name) {
const command = `/ss ${name}`;
copyToClipboard(command);
showToast('指令已复制!请在即将打开的 TG 对话框中粘贴并发送。');
setTimeout(() => {
window.open('tg://resolve?domain=nullbr_search_bot', '_blank');
}, 800);
}
// 当页面加载完成后创建UI
window.addEventListener('load', function () {
// 延迟1秒加载UI,确保页面已完全加载
setTimeout(createUI, 1000);
});
// 选中弹出悬浮搜索按钮
let searchBtn = null;
let lastSelection = '';
let currentFilter = 'all'; // 提升为全局变量
let currentSource = '115'; // 当前资源来源,设为 115 优先
function removeSearchBtn() {
if (searchBtn && searchBtn.parentNode) {
searchBtn.parentNode.removeChild(searchBtn);
searchBtn = null;
}
}
document.addEventListener('selectionchange', function () {
// 如果用户关闭了选中搜索功能,则不显示搜索按钮
if (!userSettings.enableSelectionSearch) {
removeSearchBtn();
return;
}
removeSearchBtn();
const sel = window.getSelection();
if (!sel || sel.isCollapsed) return;
const text = sel.toString().trim();
if (!text) return;
lastSelection = text;
const range = sel.getRangeAt(0);
const rect = range.getBoundingClientRect();
// 创建按钮
searchBtn = document.createElement('button');
searchBtn.textContent = '🔍 搜索资源';
searchBtn.style.position = 'fixed';
searchBtn.style.zIndex = 99999;
searchBtn.style.left = (rect.right + 10) + 'px';
searchBtn.style.top = (rect.top - 10) + 'px';
searchBtn.style.background = 'linear-gradient(120deg, #00C6FF 0%, #0072FF 100%)';
searchBtn.style.color = '#fff';
searchBtn.style.border = 'none';
searchBtn.style.borderRadius = '50px';
searchBtn.style.padding = '8px 16px';
searchBtn.style.cursor = 'pointer';
searchBtn.style.boxShadow = '0 4px 15px rgba(0, 114, 255, 0.4)';
searchBtn.style.fontSize = '14px';
searchBtn.style.fontWeight = '600';
searchBtn.style.transition = 'all 0.3s cubic-bezier(0.34, 1.56, 0.64, 1)';
searchBtn.style.backdropFilter = 'blur(4px)';
searchBtn.onmousedown = e => e.preventDefault(); // 防止失去选区
searchBtn.onmouseover = function () {
this.style.transform = 'scale(1.05)';
this.style.boxShadow = '0 4px 12px rgba(0,0,0,0.25)';
};
searchBtn.onmouseout = function () {
this.style.transform = 'scale(1)';
this.style.boxShadow = '0 2px 8px rgba(0,0,0,0.2)';
};
searchBtn.onclick = function (e) {
e.stopPropagation();
removeSearchBtn();
showTmdbPanelWithText(lastSelection);
};
document.body.appendChild(searchBtn);
});
document.addEventListener('mousedown', function (e) {
if (searchBtn && !searchBtn.contains(e.target)) {
removeSearchBtn();
}
});
// 自动填入并弹出面板
function showTmdbPanelWithText(text) {
const container = document.getElementById('tmdb-magnet-container');
if (container) container.style.display = 'block';
const input = document.getElementById('tmdb-magnet-input');
if (input) {
input.value = text;
// 自动搜索
searchTmdb(text);
}
}
})();