// ==UserScript== // @name 影巢 Emby&115 转存助手 // @version 1.1 // @description 影巢显示emby已入库以及未入库 支持主页,详情页,用户页面,合集页面,添加一键转存 115 按钮转存到 115 网盘,ui 可选文件夹 id 支持日志显示与日志推送 tgbot, 支持并适配移动端与pc端 // @author 楠 // @match *://hdhive.com/* // @match *://www.hdhive.com/* // @match *://115.com/s/* // @match *://115cdn.com/* // @grant GM_xmlhttpRequest // @grant GM_setValue // @grant GM_getValue // @grant window.close // @grant window.opener // @license MIT // @icon https://hdhive.com/apple-touch-icon.png // @namespace https://greasyfork.org/users/1514724 // @run-at document-start // @downloadURL https://update.greasyfork.icu/scripts/552585/%E5%BD%B1%E5%B7%A2%20Emby115%20%E8%BD%AC%E5%AD%98%E5%8A%A9%E6%89%8B.user.js // @updateURL https://update.greasyfork.icu/scripts/552585/%E5%BD%B1%E5%B7%A2%20Emby115%20%E8%BD%AC%E5%AD%98%E5%8A%A9%E6%89%8B.meta.js // ==/UserScript== (function() { 'use strict'; const CONFIG = { targetDomain: 'hdhive.com', autoCloseDelay: 1500, maxWaitTime: 10000, api115: 'https://115cdn.com/webapi/share/receive', snap115: 'https://115cdn.com/webapi/share/snap', tgApi: 'https://api.telegram.org/bot{token}/sendMessage', symediaApiPath: '/api/v1/plugin/cloud_helper/add_share_urls_115' }; let EMBY_CONFIG = { HOST: GM_getValue("embyHost", ""), API_KEY: GM_getValue("embyApiKey", "") }; const state = { processingItems: new Set(), processedItems: new Set(), embyCache: new Map() }; const BUTTON_STYLES = { posterBtn: { size: '25px', position: { top: '10px', right: '10px' }, has: { bg: 'linear-gradient(135deg, #4CAF50 0%, #2E7D32 100%)', icon: '✓', border: '2px solid rgba(255,255,255,0.8)' }, notHas: { bg: 'linear-gradient(135deg, #F44336 0%, #C62828 100%)', icon: '✗', border: '2px solid rgba(255,255,255,0.8)' }, hoverEffect: 'scale(1.1)' }, nameBtn: { padding: '3px 10px', marginTop: '5px', fontSize: '11px', has: { bg: 'rgba(76, 175, 80, 0.15)', text: '已入库', textColor: '#4CAF50', border: '1px solid rgba(76, 175, 80, 0.3)' }, notHas: { bg: 'rgba(244, 67, 54, 0.15)', text: '未入库', textColor: '#F44336', border: '1px solid rgba(244, 67, 54, 0.3)' }, hoverEffect: 'translateY(-1px)' }, detailBtn: { posterBtn: { size: '30px', position: { top: '15px', right: '15px' }, has: { bg: 'linear-gradient(135deg, #4CAF50 0%, #2E7D32 100%)', icon: '✓', border: '2px solid rgba(255,255,255,0.9)' }, notHas: { bg: 'linear-gradient(135deg, #F44336 0%, #C62828 100%)', icon: '✗', border: '2px solid rgba(255,255,255,0.9)' }, hoverEffect: 'scale(1.15)' }, titleBtn: { padding: '5px 15px', marginLeft: '10px', fontSize: '12px', has: { bg: 'rgba(76, 175, 80, 0.15)', text: '已入库', textColor: '#4CAF50', border: '1px solid rgba(76, 175, 80, 0.4)' }, notHas: { bg: 'rgba(244, 67, 54, 0.15)', text: '未入库', textColor: '#F44336', border: '1px solid rgba(244, 67, 54, 0.4)' }, hoverEffect: 'translateY(-1px)' } }, searchYearBtn: { padding: '3px 10px', marginLeft: '10px', fontSize: '11px', has: { bg: 'rgba(76, 175, 80, 0.15)', text: '已入库', textColor: '#4CAF50', border: '1px solid rgba(76, 175, 80, 0.3)' }, notHas: { bg: 'rgba(244, 67, 54, 0.15)', text: '未入库', textColor: '#F44336', border: '1px solid rgba(244, 67, 54, 0.3)' }, hoverEffect: 'translateY(-1px)' }, userPageBtn: { padding: '3px 10px', marginLeft: '8px', fontSize: '11px', has: { bg: 'rgba(76, 175, 80, 0.35)', text: '已入库', textColor: '#4CAF50', border: '1px solid rgba(76, 175, 80, 0.4)' }, notHas: { bg: 'rgba(244, 67, 54, 0.35)', text: '未入库', textColor: '#F44336', border: '1px solid rgba(244, 67, 54, 0.4)' }, hoverEffect: 'translateY(-1px)' }, collectionBtn: { padding: '3px 10px', marginLeft: '10px', fontSize: '11px', has: { bg: 'rgba(76, 175, 80, 0.15)', text: '已入库', textColor: '#4CAF50', border: '1px solid rgba(76, 175, 80, 0.3)' }, notHas: { bg: 'rgba(244, 67, 54, 0.15)', text: '未入库', textColor: '#F44336', border: '1px solid rgba(244, 67, 54, 0.3)' }, hoverEffect: 'translateY(-1px)' }, settingBtn: { padding: '3px 10px', marginRight: '10px', fontSize: '11px', has: { bg: 'rgba(100, 181, 246, 0.35)', text: '设置', textColor: '#64B5F6', border: '1px solid rgba(100, 181, 246, 0.4)' }, hoverEffect: 'translateY(-1px)', iconSize: '16px' }, transferBtn: { padding: '3px 10px', marginLeft: '4px', fontSize: '11px', bg: 'rgba(227, 242, 253, 1)', textColor: '#0d47a1', border: '1px solid rgba(13, 71, 161, 0.3)', hoverEffect: 'translateY(-1px)', iconSize: '16px' } }; const Utils = { normalizeText: (t = '') => String(t).replace(/\s+/g, '').trim().toLowerCase(), isSafari: (() => { try { const ua = navigator.userAgent; return /Safari/.test(ua) && !/Chrome|Chromium|Edg|OPR|Android/.test(ua); } catch (e) { return false; } })(), isDetailPage: () => { const path = window.location.pathname; return /^\/(movie|tv)\/[\w-]+/.test(path); }, isUserPage: () => window.location.pathname.startsWith('/user/'), isCollectionPage: () => window.location.pathname.startsWith('/collection/'), isResourcePage: () => location.href.includes('/resource/'), isFinal115Page: () => location.href.includes('115cdn.com') || location.href.includes('115.com/s/'), isParentPage: () => { const href = location.href; const isDetailPage = (href.includes('/movie/') && href.split('/movie/').length > 1) || (href.includes('/tv/') && href.split('/tv/').length > 1); return href.includes(CONFIG.targetDomain) && isDetailPage && !href.includes('/resource/'); }, isHDHiveSite: () => { return location.hostname.includes('hdhive.com') && !Utils.isResourcePage() && !Utils.isFinal115Page(); }, verifyAndFormatUrl: (rawUrl) => { try { if (!rawUrl) return { success: false, msg: "链接为空" }; const urlObj = new URL(rawUrl); if (!urlObj.hostname.includes('115')) return { success: false, msg: "非115域名" }; const pickcode = urlObj.pathname.split('/').pop(); if (!pickcode) return { success: false, msg: "无法提取Pickcode" }; const search = urlObj.search; if (!search || !search.includes('=')) return { success: false, msg: "链接未包含密码(Key)" }; const lastEqualIndex = rawUrl.lastIndexOf('='); let potentialPass = rawUrl.substring(lastEqualIndex + 1); if (potentialPass.length >= 4) var password = potentialPass.substring(0, 4); else return { success: false, msg: "密码长度不足" }; return { success: true, url: `https://115.com/s/${pickcode}?password=${password}`, msg: "格式化成功" }; } catch (e) { return { success: false, msg: `解析异常: ${e.message}` }; } }, parseShareLink: (shareLink) => { const shareCodeMatch = shareLink.match(/\/s\/([^?]+)/); const passwordMatch = shareLink.match(/password=(\w{4})/); if (!shareCodeMatch || !passwordMatch) return { success: false }; return { success: true, shareCode: shareCodeMatch[1], receiveCode: passwordMatch[1] }; }, humanReadable: (size) => { if (size < 1024) return `${size}B`; else if (size < 1024**2) return `${(size/1024).toFixed(2)}KB`; else if (size < 1024**3) return `${(size/1024/1024).toFixed(2)}MB`; else if (size < 1024**4) return `${(size/1024/1024/1024).toFixed(2)}GB`; else if (size < 1024**5) return `${(size/1024/1024/1024/1024).toFixed(2)}TB`; else return `${(size/1024/1024/1024/1024/1024).toFixed(2)}PB`; }, normalizeUrl: (url) => { if (!url) return ''; return url.replace(/\/+$/, ''); } }; const Logger = { stats: { free: 0, paid: 0, unlocked: 0 }, processedLinks: new Set(), currentTaskId: null, logPanel: null, logContent: null, init: () => { Logger.createLogPanel(); }, createLogPanel: () => { const logPanel = document.createElement('div'); logPanel.id = 'hdhive-log-panel'; Object.assign(logPanel.style, { position: 'fixed', bottom: '85px', right: '20px', width: '380px', height: '380px', backgroundColor: 'white', boxShadow: '0 4px 15px rgba(0,0,0,0.2)', borderRadius: '8px', zIndex: 99999, display: 'none', flexDirection: 'column', fontFamily: 'sans-serif', border: '1px solid #ddd', fontSize: '12px' }); logPanel.innerHTML = `
🤖 115转存助手日志
`; document.body.appendChild(logPanel); Logger.logPanel = logPanel; Logger.logContent = document.getElementById('log-content'); }, showLogPanel: () => { if (Logger.logPanel) { Logger.logPanel.style.display = 'flex'; } }, hideLogPanel: () => { if (Logger.logPanel) { Logger.logPanel.style.display = 'none'; } }, toggleLogPanel: () => { if (Logger.logPanel.style.display === 'flex') { Logger.hideLogPanel(); } else { Logger.showLogPanel(); } }, startNewTask: (resourceUrl) => { const taskId = Date.now() + '_' + Math.random().toString(36).substr(2, 9); Logger.currentTaskId = taskId; TelegramPush.startTask(taskId, resourceUrl); return taskId; }, endCurrentTask: (status = 'completed') => { if (Logger.currentTaskId) { TelegramPush.endTask(Logger.currentTaskId, status); Logger.currentTaskId = null; } }, addLog: (msg, type = 'info', includeInTg = true) => { if (!Logger.logContent) return; const entry = document.createElement('div'); entry.style.borderBottom = '1px dashed #eee'; entry.style.padding = '2px 0'; const time = new Date().toLocaleTimeString(); let color = ({error:'#d32f2f',success:'#2e7d32',process:'#0288d1'})[type]||'#333'; const logText = `[${time}] ${msg.replace(/<[^>]*>/g, '')}`; entry.innerHTML = `[${time}] ${msg}`; Logger.logContent.appendChild(entry); Logger.logContent.scrollTop = Logger.logContent.scrollHeight; if (includeInTg && Logger.currentTaskId) { TelegramPush.addLogToTask(Logger.currentTaskId, logText); } }, updateStats: (type) => { const statElement = document.getElementById(`stat-${type}`); if (Logger.stats[type]!==undefined && statElement) { Logger.stats[type]++; statElement.textContent = `${type==='free'?'免费':(type==='paid'?'付费':'已解锁')}: ${Logger.stats[type]}`; statElement.style.display='inline'; } }, isLinkProcessed: (link) => { return Logger.processedLinks.has(link); }, markLinkAsProcessed: (link) => { Logger.processedLinks.add(link); } }; const TelegramPush = { pendingTasks: new Map(), init: () => { TelegramPush.pendingTasks.clear(); }, startTask: (taskId, resourceUrl) => { TelegramPush.pendingTasks.set(taskId, { logs: [], startTime: new Date(), resourceUrl: resourceUrl, status: 'processing' }); }, addLogToTask: (taskId, logEntry) => { if (TelegramPush.pendingTasks.has(taskId)) { const task = TelegramPush.pendingTasks.get(taskId); task.logs.push(logEntry); if (task.status === 'completed' || task.status === 'failed') { TelegramPush.processTask(taskId); } } }, endTask: (taskId, status = 'completed') => { if (TelegramPush.pendingTasks.has(taskId)) { const task = TelegramPush.pendingTasks.get(taskId); task.status = status; task.endTime = new Date(); TelegramPush.processTask(taskId); } }, processTask: async (taskId) => { if (!TelegramPush.pendingTasks.has(taskId)) return; const task = TelegramPush.pendingTasks.get(taskId); const enableTgPush = GM_getValue('tg_enable_push', false); if (!enableTgPush) { TelegramPush.pendingTasks.delete(taskId); return; } if (task.logs.length === 0) return; let message = ''; task.logs.forEach(log => { message += `${log}\n`; }); await TelegramPush.sendToTelegram(message); TelegramPush.pendingTasks.delete(taskId); }, sendToTelegram: async (message) => { const botToken = GM_getValue('tg_bot_token', ''); const chatId = GM_getValue('tg_chat_id', ''); const proxyUrl = GM_getValue('tg_proxy', ''); if (!botToken || !chatId) { console.warn('Telegram推送配置不完整'); return false; } const apiUrl = CONFIG.tgApi.replace('{token}', botToken); return new Promise((resolve) => { const params = { chat_id: chatId, text: message, parse_mode: 'HTML', disable_web_page_preview: true }; const requestOptions = { method: 'POST', url: apiUrl, headers: { 'Content-Type': 'application/json' }, data: JSON.stringify(params), onload: (response) => { if (response.status === 200) { console.log('Telegram推送成功'); resolve(true); } else { console.error('Telegram推送失败:', response.statusText); resolve(false); } }, onerror: (error) => { console.error('Telegram推送错误:', error); resolve(false); } }; if (proxyUrl) { requestOptions.proxy = proxyUrl; } GM_xmlhttpRequest(requestOptions); }); }, testPush: async () => { const testMessage = '🚀 HDHIVE 推送测试\n📅 ' + new Date().toLocaleString() + '\n✅ 这是一条测试消息,用于验证Telegram推送功能是否正常工作。'; const result = await TelegramPush.sendToTelegram(testMessage); if (result) { Logger.addLog('✅ Telegram推送测试成功', 'success'); } else { Logger.addLog('❌ Telegram推送测试失败,请检查配置', 'error'); } return result; } }; const Transfer115 = { transfer: async (shareLink, cookie, targetCid) => { const parseResult = Utils.parseShareLink(shareLink); if (!parseResult.success) { return { success: false, message: '❌ 无法解析分享链接或密码', file_size: '' }; } const { shareCode, receiveCode } = parseResult; return new Promise((resolve) => { const postData = new URLSearchParams({ share_code: shareCode, receive_code: receiveCode, cid: targetCid, is_check: 0 }); GM_xmlhttpRequest({ method: "POST", url: CONFIG.api115, headers: { "Cookie": cookie, "Content-Type": "application/x-www-form-urlencoded" }, data: postData.toString(), onload: (response) => { try { const respJson = JSON.parse(response.responseText); if (respJson.state === true) { Transfer115.getFileSize(shareCode, receiveCode, cookie).then(fileSize => { resolve({ success: true, message: `✅ 115转存成功[${fileSize}]`, file_size: fileSize }); }).catch(err => { resolve({ success: true, message: '✅ 115转存成功[大小未知]', file_size: '' }); }); } else if (respJson.errno === 4100024) { resolve({ success: false, message: '⚠️ 转存失败:你已经转存过该文件', file_size: '' }); } else if (respJson.errno === 4100008) { resolve({ success: false, message: '❌ 转存失败:分享链接密码错误', file_size: '' }); } else if (respJson.errno === 4100010) { resolve({ success: false, message: '❌ 转存失败:分享已取消', file_size: '' }); } else if (respJson.errno === 4100018) { resolve({ success: false, message: '❌ 转存失败:链接已过期', file_size: '' }); } else { resolve({ success: false, message: `❌ 转存失败: ${respJson.error || '未知错误'}`, file_size: '' }); } } catch (e) { resolve({ success: false, message: `❌ 转存异常: ${e.message}`, file_size: '' }); } }, onerror: (error) => { resolve({ success: false, message: '❌ 转存接口调用失败', file_size: '' }); } }); }); }, getFileSize: async (shareCode, receiveCode, cookie) => { return new Promise((resolve, reject) => { const snapParams = { "_v": 2, "share_code": shareCode, "receive_code": receiveCode, "offset": 0, "limit": 20, "cid": "" }; const queryString = new URLSearchParams(snapParams).toString(); GM_xmlhttpRequest({ method: "GET", url: `${CONFIG.snap115}?${queryString}`, headers: { "Cookie": cookie, "Referer": "https://115.com/", "Origin": "https://115.com", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" }, onload: (response) => { try { const fileInfoJson = JSON.parse(response.responseText); if (fileInfoJson.state && fileInfoJson.data && fileInfoJson.data.list && fileInfoJson.data.list[0]) { const fileSize = fileInfoJson.data.list[0].s || 0; resolve(Utils.humanReadable(fileSize)); } else { if (fileInfoJson.data && fileInfoJson.data[0]) { const fileSize = fileInfoJson.data[0].s || fileInfoJson.data[0].size || 0; resolve(Utils.humanReadable(fileSize)); } else { reject(new Error('无法获取文件大小')); } } } catch (e) { reject(e); } }, onerror: (error) => { reject(error); } }); }); }, transferBySymedia: async (shareLink, symediaUrl, symediaToken, targetCid) => { if (!symediaUrl || !symediaToken) { return { success: false, message: '❌ Symedia配置不完整' }; } const normalizedUrl = Utils.normalizeUrl(symediaUrl); const apiUrl = `${normalizedUrl}${CONFIG.symediaApiPath}?token=${symediaToken}`; return new Promise((resolve) => { const postData = JSON.stringify({ urls: [shareLink], parent_id: parseInt(targetCid) || 0 }); GM_xmlhttpRequest({ method: "POST", url: apiUrl, headers: { "Content-Type": "application/json" }, data: postData, onload: (response) => { try { const respJson = JSON.parse(response.responseText); if (response.status === 200 && respJson.success === true) { if (respJson.message && respJson.message.includes('转存失败')) { resolve({ success: false, message: `❌ Symedia转存失败: ${respJson.message}` }); } else { resolve({ success: true, message: `✅ Symedia转存: ${respJson.message}` }); } } else { resolve({ success: false, message: `❌ Symedia转存失败: ${respJson.message || '未知错误'}` }); } } catch (e) { resolve({ success: false, message: `❌ Symedia转存异常: ${e.message}` }); } }, onerror: (error) => { resolve({ success: false, message: '❌ Symedia接口调用失败' }); } }); }); } }; const EmbyHelper = { checkEmbyResource: (name, year) => { return new Promise((resolve) => { const cacheKey = `${name}-${year}`; if (state.embyCache.has(cacheKey)) { resolve(state.embyCache.get(cacheKey)); return; } const searchUrl = `${EMBY_CONFIG.HOST}/emby/Items?api_key=${EMBY_CONFIG.API_KEY}&SearchTerm=${encodeURIComponent(name)}&IncludeItemTypes=Movie,Series&Recursive=true&Fields=ProductionYear,OriginalTitle&Limit=20`; GM_xmlhttpRequest({ method: 'GET', url: searchUrl, onload: function(response) { try { const data = JSON.parse(response.responseText); let hasResource = false; if (data.Items && data.Items.length > 0) { const chineseMatch = data.Items.find(item => { const itemName = item.Name; const itemYear = item.ProductionYear; return itemName === name && itemYear === year; }); if (chineseMatch) { hasResource = true; } else { const englishMatch = data.Items.find(item => { const itemOriginalTitle = item.OriginalTitle; const itemYear = item.ProductionYear; return itemOriginalTitle && itemOriginalTitle === name && itemYear === year; }); hasResource = !!englishMatch; } } state.embyCache.set(cacheKey, hasResource); resolve(hasResource); } catch (error) { state.embyCache.set(cacheKey, false); resolve(false); } }, onerror: function(error) { state.embyCache.set(cacheKey, false); resolve(false); } }); }); }, extractInfoFromPoster: (poster) => { const nameElement = poster.querySelector('p.mui-1kj6qkz'); const yearElement = poster.querySelector('p.mui-1mfu778'); if (nameElement && yearElement) { const name = nameElement.textContent.trim(); const year = parseInt(yearElement.textContent.trim(), 10); if (name && !isNaN(year)) { return { name, year, element: poster }; } } return null; }, extractInfoFromDetail: () => { const titleElement = document.querySelector('h1.mui-oqdiq5'); if (titleElement) { const titleText = titleElement.childNodes[0].textContent.trim(); const yearElement = titleElement.querySelector('p.mui-eo8gqg'); if (yearElement) { const yearText = yearElement.textContent.trim(); const yearMatch = yearText.match(/\((\d{4})\)/); if (yearMatch) { const year = parseInt(yearMatch[1], 10); return { name: titleText, year }; } } } return null; }, extractInfoFromUserPage: (element) => { const text = element.textContent.trim(); const match = text.match(/(.+?)\s*\((\d{4})\)/); if (match) { const name = match[1].trim(); const year = parseInt(match[2], 10); return { name, year, element }; } return null; }, extractInfoFromSearchYear: (element) => { const text = element.textContent.trim(); const match = text.match(/\((\d{4})\)/); if (match) { const year = parseInt(match[1], 10); const nameElement = element.previousElementSibling; if (nameElement) { const name = nameElement.textContent.trim(); return { name, year, element }; } } return null; }, extractInfoFromCollection: (element) => { const nameElement = element.querySelector('p.mui-1kj6qkz'); const yearElement = element.querySelector('p.mui-1mfu778'); if (nameElement && yearElement) { const name = nameElement.textContent.trim(); const year = parseInt(yearElement.textContent.trim(), 10); if (name && !isNaN(year)) { return { name, year, element }; } } return null; }, createPosterButton: (hasResource) => { const btn = document.createElement('div'); btn.className = `emby-poster-btn ${hasResource ? 'has' : 'not-has'}`; btn.textContent = hasResource ? BUTTON_STYLES.posterBtn.has.icon : BUTTON_STYLES.posterBtn.notHas.icon; btn.title = hasResource ? 'Emby库中有此资源' : 'Emby库中无此资源'; return btn; }, createNameButton: (hasResource) => { const btn = document.createElement('span'); btn.className = `emby-name-btn ${hasResource ? 'has' : 'not-has'}`; btn.textContent = hasResource ? BUTTON_STYLES.nameBtn.has.text : BUTTON_STYLES.nameBtn.notHas.text; btn.title = hasResource ? 'Emby库中有此资源' : 'Emby库中无此资源'; return btn; }, createDetailPosterButton: (hasResource) => { const btn = document.createElement('div'); btn.className = `emby-detail-poster-btn ${hasResource ? 'has' : 'not-has'}`; btn.textContent = hasResource ? BUTTON_STYLES.detailBtn.posterBtn.has.icon : BUTTON_STYLES.detailBtn.posterBtn.notHas.icon; btn.title = hasResource ? 'Emby库中有此资源' : 'Emby库中无此资源'; return btn; }, createDetailTitleButton: (hasResource) => { const btn = document.createElement('span'); btn.className = `emby-detail-title-btn ${hasResource ? 'has' : 'not-has'}`; btn.textContent = hasResource ? BUTTON_STYLES.detailBtn.titleBtn.has.text : BUTTON_STYLES.detailBtn.titleBtn.notHas.text; btn.title = hasResource ? 'Emby库中有此资源' : 'Emby库中无此资源'; return btn; }, createSearchYearButton: (hasResource) => { const btn = document.createElement('span'); btn.className = `emby-search-year-btn ${hasResource ? 'has' : 'not-has'}`; btn.textContent = hasResource ? BUTTON_STYLES.searchYearBtn.has.text : BUTTON_STYLES.searchYearBtn.notHas.text; btn.title = hasResource ? 'Emby库中有此资源' : 'Emby库中无此资源'; return btn; }, createUserPageButton: (hasResource) => { const btn = document.createElement('span'); btn.className = `emby-user-page-btn ${hasResource ? 'has' : 'not-has'}`; const state = hasResource ? BUTTON_STYLES.userPageBtn.has : BUTTON_STYLES.userPageBtn.notHas; btn.textContent = state.text; btn.title = hasResource ? 'Emby库中有此资源' : 'Emby库中无此资源'; btn.disabled = true; return btn; }, createCollectionButton: (hasResource) => { const btn = document.createElement('span'); btn.className = `emby-collection-btn ${hasResource ? 'has' : 'not-has'}`; btn.textContent = hasResource ? BUTTON_STYLES.collectionBtn.has.text : BUTTON_STYLES.collectionBtn.notHas.text; btn.title = hasResource ? 'Emby库中有此资源' : 'Emby库中无此资源'; return btn; }, createSettingButton: () => { const btn = document.createElement('span'); btn.className = 'emby-setting-btn'; btn.innerHTML = ` 设置 `; btn.title = '多功能设置'; return btn; }, createTransferButton: () => { const btn = document.createElement('div'); btn.className = 'one-click-transfer-btn'; btn.style.cssText = 'cursor:pointer;margin-left:4px;background:rgba(227, 242, 253, 0.3);color:#0d47a1;display:inline-flex;align-items:center;padding:0 8px;height:27px;border-radius:13.5px;font-weight:bold;font-size:13px;'; btn.innerHTML = '一键转存'; return btn; } }; const SettingsManager = { showSettingsModal: () => { if (document.querySelector('#tm-settings-modal')) return; const embyHost = EMBY_CONFIG.HOST || ''; const embyApiKey = EMBY_CONFIG.API_KEY || ''; const cookie115 = GM_getValue('115_cookie') || ''; const cid115 = GM_getValue('115_cid') || '0'; const tgBotToken = GM_getValue('tg_bot_token') || ''; const tgChatId = GM_getValue('tg_chat_id') || ''; const tgProxy = GM_getValue('tg_proxy') || ''; const tgEnablePush = GM_getValue('tg_enable_push', false); const transferMethod = GM_getValue('115_transfer_method', 'cookie'); // cookie 或 symedia const symediaUrl = GM_getValue('symedia_url', ''); const symediaToken = GM_getValue('symedia_token', 'symedia'); const overlay = document.createElement('div'); overlay.id = 'tm-settings-modal'; Object.assign(overlay.style, { position: 'fixed', top: '0', left: '0', width: '100%', height: '100%', background: 'rgba(0,0,0,0.5)', zIndex: 10001, display: 'flex', justifyContent: 'center', alignItems: 'center' }); const modal = document.createElement('div'); Object.assign(modal.style, { background: '#fff', padding: '20px 25px', borderRadius: '10px', width: '500px', boxShadow: '0 6px 20px rgba(0,0,0,0.3)', fontFamily: 'Arial, sans-serif', maxHeight: '85vh', overflowY: 'auto' }); modal.innerHTML = `

多功能助手设置

Emby服务器设置

115网盘设置

说明:
• Cookie转存:需要有效的115 Cookie,可点击"浏览文件夹"选择
• Symedia转存:转存可触发实时监控归档整理 token默认symedia
• 文件夹ID:0为转存到根目录

Telegram推送设置

注意:代理设置将在测试时立即生效,保存后对所有推送生效

操作日志

`; overlay.appendChild(modal); document.body.appendChild(overlay); const tabBtns = modal.querySelectorAll('.tm-tab-btn'); const tabContents = modal.querySelectorAll('.tm-tab-content'); tabBtns.forEach(btn => { btn.addEventListener('click', function() { const tab = this.getAttribute('data-tab'); tabBtns.forEach(b => b.classList.remove('active')); tabContents.forEach(c => c.classList.remove('active')); this.classList.add('active'); document.getElementById(`tm-tab-${tab}`).classList.add('active'); if (tab === 'logs') { SettingsManager.refreshLogContent(); } }); }); const transferMethodSelect = modal.querySelector('#tm-transfer-method'); const cookieSettings = modal.querySelector('#tm-cookie-settings'); const symediaSettings = modal.querySelector('#tm-symedia-settings'); const browseFoldersBtn = modal.querySelector('#tm-browse-folders'); transferMethodSelect.addEventListener('change', function() { const method = this.value; cookieSettings.style.display = method === 'cookie' ? 'block' : 'none'; symediaSettings.style.display = method === 'symedia' ? 'block' : 'none'; browseFoldersBtn.style.display = method === 'cookie' ? '' : 'none'; }); modal.querySelector('#tm-settings-close').onclick = () => overlay.remove(); const embyApiKeyInput = modal.querySelector('#tm-emby-apikey'); const toggleEmbyApiKeyBtn = modal.querySelector('#tm-toggle-emby-apikey'); if (toggleEmbyApiKeyBtn) { toggleEmbyApiKeyBtn.addEventListener('click', function() { if (embyApiKeyInput.type === 'password') { embyApiKeyInput.type = 'text'; toggleEmbyApiKeyBtn.textContent = '隐藏'; } else { embyApiKeyInput.type = 'password'; toggleEmbyApiKeyBtn.textContent = '显示'; } }); } const cookieInput = modal.querySelector('#tm-cookie-input'); const toggleCookieBtn = modal.querySelector('#tm-toggle-cookie'); toggleCookieBtn.addEventListener('click', function() { if (cookieInput.type === 'password') { cookieInput.type = 'text'; toggleCookieBtn.textContent = '隐藏'; } else { cookieInput.type = 'password'; toggleCookieBtn.textContent = '显示'; } }); const tokenInput = modal.querySelector('#tm-tg-token'); const toggleTokenBtn = modal.querySelector('#tm-toggle-token'); toggleTokenBtn.addEventListener('click', function() { if (tokenInput.type === 'password') { tokenInput.type = 'text'; toggleTokenBtn.textContent = '隐藏'; } else { tokenInput.type = 'password'; toggleTokenBtn.textContent = '显示'; } }); modal.querySelector('#tm-browse-folders').onclick = () => { const cookieValue = cookieInput.value.trim(); if (!cookieValue) { alert('请先输入Cookie'); return; } GM_setValue('115_cookie', cookieValue); SettingsManager.showFolderBrowser(); }; modal.querySelector('#tm-test-proxy').onclick = async () => { const token = tokenInput.value.trim(); const chatId = modal.querySelector('#tm-tg-chatid').value.trim(); const proxy = modal.querySelector('#tm-tg-proxy').value.trim(); const enablePush = modal.querySelector('#tm-tg-enable').checked; const originalToken = GM_getValue('tg_bot_token'); const originalChatId = GM_getValue('tg_chat_id'); const originalProxy = GM_getValue('tg_proxy'); const originalEnablePush = GM_getValue('tg_enable_push'); GM_setValue('tg_bot_token', token); GM_setValue('tg_chat_id', chatId); GM_setValue('tg_proxy', proxy); GM_setValue('tg_enable_push', enablePush); const testBtn = modal.querySelector('#tm-test-proxy'); const originalText = testBtn.textContent; testBtn.textContent = '测试中...'; testBtn.disabled = true; try { const result = await TelegramPush.testPush(); if (result) { alert('✅ Telegram推送测试成功!'); } else { alert('❌ Telegram推送测试失败,请检查配置和网络连接'); } } catch (error) { alert('❌ 测试过程中发生错误: ' + error.message); } finally { testBtn.textContent = originalText; testBtn.disabled = false; GM_setValue('tg_bot_token', originalToken); GM_setValue('tg_chat_id', originalChatId); GM_setValue('tg_proxy', originalProxy); GM_setValue('tg_enable_push', originalEnablePush); } }; modal.querySelector('#tm-clear-logs').onclick = () => { if (Logger.logContent) { Logger.logContent.innerHTML = ''; Logger.stats = { free: 0, paid: 0, unlocked: 0 }; document.querySelectorAll('#log-stats span').forEach(span => { span.style.display = 'none'; span.textContent = span.id.replace('stat-', '') + ': 0'; }); } SettingsManager.refreshLogContent(); }; modal.querySelector('#tm-show-log-panel').onclick = () => { Logger.showLogPanel(); overlay.remove(); }; modal.querySelector('#tm-settings-cancel').onclick = () => overlay.remove(); modal.querySelector('#tm-settings-save').onclick = () => { const newEmbyHost = modal.querySelector('#tm-emby-host').value.trim(); const newEmbyApiKey = embyApiKeyInput.value.trim(); const newCookie = cookieInput.value.trim(); const newCid = modal.querySelector('#tm-cid-input').value.trim() || '0'; const newTgToken = tokenInput.value.trim(); const newTgChatId = modal.querySelector('#tm-tg-chatid').value.trim(); const newTgProxy = modal.querySelector('#tm-tg-proxy').value.trim(); const newTgEnable = modal.querySelector('#tm-tg-enable').checked; const newTransferMethod = modal.querySelector('#tm-transfer-method').value; const newSymediaUrl = modal.querySelector('#tm-symedia-url').value.trim(); const newSymediaToken = modal.querySelector('#tm-symedia-token').value.trim() || 'symedia'; GM_setValue('embyHost', newEmbyHost); GM_setValue('embyApiKey', newEmbyApiKey); EMBY_CONFIG.HOST = newEmbyHost; EMBY_CONFIG.API_KEY = newEmbyApiKey; GM_setValue('115_cookie', newCookie); GM_setValue('115_cid', newCid); GM_setValue('tg_bot_token', newTgToken); GM_setValue('tg_chat_id', newTgChatId); GM_setValue('tg_proxy', newTgProxy); GM_setValue('tg_enable_push', newTgEnable); GM_setValue('115_transfer_method', newTransferMethod); GM_setValue('symedia_url', newSymediaUrl); GM_setValue('symedia_token', newSymediaToken); state.embyCache.clear(); state.processedItems.clear(); state.processingItems.clear(); Logger.addLog('✅ 所有设置已保存', 'success'); overlay.remove(); processAllPosters(); }; SettingsManager.refreshLogContent(); const style = document.createElement('style'); style.textContent = ` .tm-tab-btn { padding: 8px 16px; border: none; background: #f1f3f5; color: #666; cursor: pointer; font-size: 12px; border-radius: 4px 4px 0 0; transition: all 0.3s; } .tm-tab-btn.active { background: #2196F3; color: white; font-weight: bold; } .tm-tab-content { display: none; } .tm-tab-content.active { display: block; } `; modal.appendChild(style); }, refreshLogContent: () => { const logContainer = document.querySelector('#tm-settings-log-content'); if (!logContainer || !Logger.logContent) return; logContainer.innerHTML = Logger.logContent.innerHTML || '
暂无日志
'; }, showFolderBrowser: async () => { if (document.querySelector('#tm-folder-browser')) return; const overlay = document.createElement('div'); overlay.id = 'tm-folder-browser'; Object.assign(overlay.style, { position: 'fixed', top: '0', left: '0', width: '100%', height: '100%', background: 'rgba(0,0,0,0.5)', zIndex: 10002, display: 'flex', justifyContent: 'center', alignItems: 'center' }); const modal = document.createElement('div'); Object.assign(modal.style, { background: '#fff', padding: '20px', borderRadius: '10px', width: '500px', maxHeight: '80vh', boxShadow: '0 6px 20px rgba(0,0,0,0.3)', fontFamily: 'Arial, sans-serif', display: 'flex', flexDirection: 'column' }); modal.innerHTML = `

浏览文件夹

根目录
加载中...
`; overlay.appendChild(modal); document.body.appendChild(overlay); let currentCid = 0; let currentPath = ["根目录"]; let cidStack = []; let pathStack = []; const getFolders = async (cid = 0) => { const cookie = GM_getValue('115_cookie'); if (!cookie) { Logger.addLog('❌ 获取文件夹失败:Cookie 未设置', 'error'); return []; } return new Promise((resolve) => { GM_xmlhttpRequest({ method: "GET", url: `https://webapi.115.com/files?aid=1&cid=${cid}&show_dir=1&nsprefix=1`, headers: { "Cookie": cookie, "User-Agent": "Mozilla/5.0" }, onload: (response) => { try { const data = JSON.parse(response.responseText); if (data.state && data.data) { const folders = data.data .filter(item => item.fl && item.fl.length === 0) .map(item => ({ name: item.n, cid: item.cid })); resolve(folders); } else { resolve([]); } } catch (e) { resolve([]); } }, onerror: () => resolve([]) }); }); }; const loadFolders = async (cid = 0) => { const foldersList = document.getElementById('tm-folders-list'); foldersList.innerHTML = '
加载中...
'; const folders = await getFolders(cid); if (folders.length === 0) { foldersList.innerHTML = '
该目录下没有文件夹
'; return; } foldersList.innerHTML = ''; folders.forEach(folder => { const folderItem = document.createElement('div'); Object.assign(folderItem.style, { padding: '10px', borderBottom: '1px solid #eee', cursor: 'pointer', display: 'flex', justifyContent: 'space-between', fontSize: '12px' }); folderItem.innerHTML = ` ${folder.name} CID: ${folder.cid} `; folderItem.addEventListener('mouseenter', () => { folderItem.style.backgroundColor = '#f5f5f5'; }); folderItem.addEventListener('mouseleave', () => { folderItem.style.backgroundColor = 'transparent'; }); folderItem.onclick = () => { cidStack.push(currentCid); pathStack.push([...currentPath]); currentCid = folder.cid; currentPath.push(folder.name); updatePathDisplay(); loadFolders(currentCid); }; foldersList.appendChild(folderItem); }); }; const updatePathDisplay = () => { const pathElement = document.getElementById('tm-current-path'); pathElement.textContent = currentPath.join(' / '); }; modal.querySelector('#tm-folder-back').onclick = () => { if (cidStack.length > 0) { currentCid = cidStack.pop(); currentPath = pathStack.pop(); updatePathDisplay(); loadFolders(currentCid); } }; modal.querySelector('#tm-folder-cancel').onclick = () => { overlay.remove(); }; modal.querySelector('#tm-folder-select').onclick = () => { if (currentCid !== 0) { const cidInput = document.querySelector('#tm-cid-input'); if (cidInput) { cidInput.value = currentCid; } Logger.addLog(`已选择文件夹: ${currentPath.join(' / ')}`, 'success'); } overlay.remove(); }; await loadFolders(currentCid); updatePathDisplay(); }, addSettingButtons: () => { const checkInterval = setInterval(() => { const target1 = document.querySelector('a.mui-1oi34mv'); const target2 = document.querySelector('button.mui-19y4szv'); if (target1 && target2) { clearInterval(checkInterval); const btn1 = EmbyHelper.createSettingButton(); btn1.addEventListener('click', () => { SettingsManager.showSettingsModal(); }); target1.parentNode.insertBefore(btn1, target1); const btn2 = EmbyHelper.createSettingButton(); btn2.addEventListener('click', () => { SettingsManager.showSettingsModal(); }); target2.parentNode.insertBefore(btn2, target2); } }, 500); } }; async function processPoster(poster) { const itemKey = `poster-${poster.href}`; if (state.processedItems.has(itemKey) || state.processingItems.has(itemKey)) { return; } state.processingItems.add(itemKey); const info = EmbyHelper.extractInfoFromPoster(poster); if (!info) { state.processingItems.delete(itemKey); return; } try { const hasResource = await EmbyHelper.checkEmbyResource(info.name, info.year); if (!poster.querySelector('.emby-poster-btn')) { const posterImageContainer = poster.querySelector('div.mui-1daepjq'); if (posterImageContainer) { const btn = EmbyHelper.createPosterButton(hasResource); posterImageContainer.style.position = 'relative'; posterImageContainer.appendChild(btn); } } if (!poster.querySelector('.emby-name-btn')) { const yearElement = poster.querySelector('p.mui-1mfu778'); if (yearElement) { const btn = EmbyHelper.createNameButton(hasResource); const buttonContainer = document.createElement('div'); buttonContainer.style.display = 'flex'; buttonContainer.style.justifyContent = 'center'; buttonContainer.style.width = '100%'; buttonContainer.appendChild(btn); yearElement.parentNode.insertBefore(buttonContainer, yearElement.nextSibling); } } state.processedItems.add(itemKey); } catch (error) { } finally { state.processingItems.delete(itemKey); } } async function processDetailPage() { if (!Utils.isDetailPage()) return; const detailKey = 'detail-page'; if (state.processedItems.has(detailKey) || state.processingItems.has(detailKey)) { return; } state.processingItems.add(detailKey); const info = EmbyHelper.extractInfoFromDetail(); if (!info) { state.processingItems.delete(detailKey); return; } try { const hasResource = await EmbyHelper.checkEmbyResource(info.name, info.year); if (!document.querySelector('.emby-detail-poster-btn')) { const posterContainer = document.querySelector('.mui-ja4wo8, .mui-77cdso'); if (posterContainer) { const btn = EmbyHelper.createDetailPosterButton(hasResource); posterContainer.style.position = 'relative'; posterContainer.appendChild(btn); } } if (!document.querySelector('.emby-detail-title-btn')) { const titleElement = document.querySelector('.mui-oqdiq5'); if (titleElement) { const btn = EmbyHelper.createDetailTitleButton(hasResource); titleElement.parentNode.insertBefore(btn, titleElement.nextSibling); } } state.processedItems.add(detailKey); } catch (error) { } finally { state.processingItems.delete(detailKey); } } async function processSearchYearButtons() { const resultItems = document.querySelectorAll('a[href*="/tmdb/"]'); for (const item of resultItems) { const itemKey = `search-${item.href}`; if (state.processedItems.has(itemKey) || state.processingItems.has(itemKey)) { continue; } state.processingItems.add(itemKey); const yearText = item.querySelector('.MuiTypography-body2'); if (yearText && yearText.textContent.includes('(')) { const info = EmbyHelper.extractInfoFromSearchYear(yearText); if (info) { try { const hasResource = await EmbyHelper.checkEmbyResource(info.name, info.year); if (!yearText.parentNode.querySelector('.emby-search-year-btn')) { const btn = EmbyHelper.createSearchYearButton(hasResource); yearText.parentNode.insertBefore(btn, yearText.nextSibling); } } catch (error) { } } } state.processedItems.add(itemKey); state.processingItems.delete(itemKey); } } async function processUserPageButtons() { function showNotice(msg) { if (document.getElementById('hdhive-notice')) return; const div = document.createElement('div'); div.id = 'hdhive-notice'; div.textContent = msg; document.body.appendChild(div); } async function addButtonsToElements(elements) { let added = false; for (const el of elements) { const itemKey = `user-${el.textContent}`; if (state.processedItems.has(itemKey) || state.processingItems.has(itemKey)) { continue; } state.processingItems.add(itemKey); const info = EmbyHelper.extractInfoFromUserPage(el); if (info) { try { const hasResource = await EmbyHelper.checkEmbyResource(info.name, info.year); if (!el.querySelector('.emby-user-page-btn')) { const btn = EmbyHelper.createUserPageButton(hasResource); el.appendChild(btn); added = true; } } catch (error) { } } state.processedItems.add(itemKey); state.processingItems.delete(itemKey); } return added; } function waitForElements(selector, callback, maxTry = 40, intervalTime = 300) { let count = 0; const timer = setInterval(async () => { const elements = Array.from(document.querySelectorAll(selector)) .filter(el => /\(\d{4}\)/.test(el.textContent)); if (elements.length > 0) { const added = await callback(elements); if (added) { const notice = document.getElementById('hdhive-notice'); if (notice) notice.remove(); } clearInterval(timer); } else if (++count >= maxTry) { showNotice('页面内容正在渲染,按钮稍后显示或请刷新页面'); clearInterval(timer); } }, intervalTime); } waitForElements('p', addButtonsToElements); } async function processCollectionButtons() { if (!Utils.isCollectionPage()) return; const collectionItems = document.querySelectorAll('a.MuiBox-root.mui-ytumd6, a.mui-r5wu0g'); for (const item of collectionItems) { const itemKey = `collection-${item.href}`; if (state.processedItems.has(itemKey) || state.processingItems.has(itemKey)) { continue; } state.processingItems.add(itemKey); const info = EmbyHelper.extractInfoFromCollection(item); if (info) { try { const hasResource = await EmbyHelper.checkEmbyResource(info.name, info.year); if (!item.querySelector('.emby-poster-btn')) { const posterImageContainer = item.querySelector('div.mui-1daepjq, div.mui-19aj6fg'); if (posterImageContainer) { const btn = EmbyHelper.createPosterButton(hasResource); posterImageContainer.style.position = 'relative'; posterImageContainer.appendChild(btn); } } if (!item.querySelector('.emby-collection-btn')) { const btn = EmbyHelper.createCollectionButton(hasResource); const yearElement = item.querySelector('p.mui-1mfu778'); if (yearElement) { const buttonContainer = document.createElement('div'); buttonContainer.style.display = 'flex'; buttonContainer.style.justifyContent = 'center'; buttonContainer.style.width = '100%'; buttonContainer.appendChild(btn); yearElement.parentNode.insertBefore(buttonContainer, yearElement.nextSibling); } } } catch (error) { } } state.processedItems.add(itemKey); state.processingItems.delete(itemKey); } } async function processAllPosters() { const posters = document.querySelectorAll('a.MuiBox-root.mui-ytumd6'); for (const poster of posters) { await processPoster(poster); } const popoverPosters = document.querySelectorAll('.MuiPopover-root a.MuiBox-root.mui-ytumd6'); for (const poster of popoverPosters) { await processPoster(poster); } await processDetailPage(); await processSearchYearButtons(); await processCollectionButtons(); if (Utils.isUserPage()) { await processUserPageButtons(); } } function initParentPage() { window.addEventListener('message',(event)=>{ if(event.data && event.data.type==='HDHIVE_RESULT'){ const {status,url,error,step}=event.data; if(status==='success') { if (Logger.isLinkProcessed(url)) { return; } Logger.markLinkAsProcessed(url); Logger.addLog(`✅ 获取成功: ${url}`,'success'); handleTransfer115(url); } else if(status==='process') { if (!Logger.isLinkProcessed(`process_${step}`)) { Logger.markLinkAsProcessed(`process_${step}`); Logger.addLog(`👉 ${step}`,'process'); } } else if(status==='error') Logger.addLog(`❌ 失败: ${error}`,'error'); } }); async function handleTransfer115(url) { const transferMethod = GM_getValue('115_transfer_method', 'cookie'); const cid = GM_getValue('115_cid') || '0'; const transferKey = `transfer_${url}`; if (Logger.isLinkProcessed(transferKey)) { return; } Logger.markLinkAsProcessed(transferKey); let result; if (transferMethod === 'cookie') { const cookie = GM_getValue('115_cookie'); if (!cookie) { Logger.addLog('❌ 未填写 115 Cookie,取消转存', 'process'); Logger.endCurrentTask('failed'); return; } result = await Transfer115.transfer(url, cookie, cid); } else if (transferMethod === 'symedia') { const symediaUrl = GM_getValue('symedia_url', ''); const symediaToken = GM_getValue('symedia_token', 'symedia'); if (!symediaUrl) { Logger.addLog('❌ 未填写 Symedia 地址,取消转存', 'process'); Logger.endCurrentTask('failed'); return; } result = await Transfer115.transferBySymedia(url, symediaUrl, symediaToken, cid); } else { Logger.addLog('❌ 未知的转存方式', 'error'); Logger.endCurrentTask('failed'); return; } if (result.success) { Logger.addLog(result.message, 'success'); Logger.endCurrentTask('completed'); } else { if (!result.message.includes('❌') && !result.message.includes('⚠️')) { Logger.addLog(`❌ ${result.message}`, 'error'); } else { Logger.addLog(result.message, 'error'); } Logger.endCurrentTask('failed'); } } function is115Tab(){return document.querySelector('.MuiTab-root.Mui-selected')?.textContent?.includes('115网盘');} function startObserver(){ new MutationObserver(()=>{if(is115Tab()) addButtons(); else removeButtons();}) .observe(document.body,{childList:true,subtree:true}); if(is115Tab()) addButtons(); } function removeButtons(){document.querySelectorAll('.one-click-transfer-btn').forEach(b=>b.remove());} function addButtons(){ document.querySelectorAll('.MuiGrid2-root.mui-50yj0f').forEach(container=>{ if(container.querySelector('.one-click-transfer-btn')) return; const chipContainer=container.querySelector('.mui-drwuj3'); const link=container.querySelector('a[href^="/resource/"]'); if(!chipContainer||!link) return; let type='unknown'; let cost=''; chipContainer.querySelectorAll('.MuiChip-root').forEach(chip=>{ const t=chip.textContent; if(t.includes('免费')) type='free'; else if(t.includes('已解锁')) type='unlocked'; else if(t.includes('积分')){type='paid';cost=t.replace(/[^\d]/g,'');} }); Logger.updateStats(type); const btn=EmbyHelper.createTransferButton(); btn.onclick=(e)=>{ e.preventDefault(); e.stopPropagation(); const logType=(type==='paid')?`${cost} 积分`:(type==='free')?'免费':'已解锁'; const rawTarget=`${location.origin}${link.getAttribute('href')}`; if (Logger.isLinkProcessed(rawTarget)) { Logger.addLog(`⚠️ 该资源已在处理中,请勿重复点击`, 'process'); return; } Logger.markLinkAsProcessed(rawTarget); Logger.startNewTask(rawTarget); Logger.addLog(`开始处理 链接 [${logType}] ${rawTarget}`,'process'); const processedTarget=`${rawTarget}?autotransfer=1&type=${type}`; window.open(processedTarget,'_blank',`width=600,height=500`); }; chipContainer.appendChild(btn); }); } startObserver(); } function initChildPage(){ const params=new URLSearchParams(location.search); if(!params.has('autotransfer')) return; const type=params.get('type'); let isFinished=false; const send=(data)=>window.opener && window.opener.postMessage({type:'HDHIVE_RESULT',...data},'*'); const log=(step)=>send({status:'process',step}); const fail=(msg)=>{if(isFinished) return; isFinished=true; clearAllFinders(); send({status:'error',error:msg});}; const success=(rawUrl)=>{if(isFinished) return; const check=Utils.verifyAndFormatUrl(rawUrl); if(check.success){isFinished=true; clearAllFinders(); send({status:'success',url:check.url}); setTimeout(()=>window.close(),CONFIG.autoCloseDelay);} else{if(type==='paid') console.log("捕获到无效链接(无密码),忽略:",rawUrl); else fail(`链接校验不通过: ${check.msg} (URL: ${rawUrl})`);}}; const finders=[]; function clearAllFinders(){finders.forEach(id=>{try{clearInterval(id);}catch(e){}; try{clearTimeout(id);}catch(e){};}); finders.length=0;} if(type==='paid'){ const unlockFinder=setInterval(()=>{ if(isFinished) return; const buttons=Array.from(document.querySelectorAll('button')); const unlockBtn=buttons.find(btn=>{ const text=Utils.normalizeText(btn.textContent); return text.includes('确定解锁')||(text.includes('解锁')&&!text.includes('取消')&&!text.includes('close')); }); if(unlockBtn && !unlockBtn.dataset.clicked){ unlockBtn.dataset.clicked="true"; const boxRoots=Array.from(document.querySelectorAll('.MuiBox-root')); const pointsDiv=boxRoots.find(el=>el.textContent && (el.textContent.includes('积分解锁')||el.textContent.includes('需要使用'))); if (pointsDiv) { const text=pointsDiv.textContent||''; const unlockedMatch=text.match(/已解锁人数\s*[::]?\s*(\d+)/)||text.match(/已解锁\s*(\d+)/); const unlockedCount=unlockedMatch?unlockedMatch[1]:'未知'; const pointsMatch=text.match(/需要使用\s*(\d+)\s*积分/)||text.match(/消耗[::]?\s*(\d+)\s*积分/); const pointsCount=pointsMatch?pointsMatch[1]:'未知'; log(`🔍 已解锁: ${unlockedCount} 人`); log(`💰 需要: ${pointsCount} 积分`); } log('✅ 找到解锁按钮'); if (Utils.isSafari) { unlockBtn.dispatchEvent(new MouseEvent('click',{bubbles:true,cancelable:true,view:window})); } else { try{unlockBtn.click();} catch(e){unlockBtn.dispatchEvent(new MouseEvent('click',{bubbles:true,cancelable:true,view:window})); log('⚠️ click() 失败,改用 dispatchEvent');} } } },300); finders.push(unlockFinder); } document.addEventListener('DOMContentLoaded',()=>{ const to=setTimeout(()=>{if(!isFinished) fail('操作超时 (10秒未获取到有效带密码链接)');},CONFIG.maxWaitTime); finders.push(to); const observer=new MutationObserver((mutations,obs)=>{ if(isFinished){obs.disconnect();return;} if(type!=='paid'){ const links=document.querySelectorAll('a'); for(let a of links){ if(a.href && (a.href.includes('115cdn.com')||a.href.includes('115.com/s/'))){ const check=Utils.verifyAndFormatUrl(a.href); if(check.success){success(a.href);obs.disconnect();return;} } } } }); observer.observe(document.body,{childList:true,subtree:true}); }); const oldXhr=XMLHttpRequest.prototype.open; XMLHttpRequest.prototype.open=function(){ this.addEventListener('load',function(){ const txt=this.responseText; if(txt && (txt.includes('115cdn.com')||txt.includes('115.com/s/'))){ const match=txt.match(/https?:\/\/[^\s"']+/); if(match){const check=Utils.verifyAndFormatUrl(match[0]);if(check.success){log('已拦截XHR中的解锁链接');success(match[0]);}} } }); return oldXhr.apply(this,arguments); }; const oldOpen=window.open; window.open=function(url){ if(url && (url.includes('115cdn.com')||url.includes('115.com/s/'))){ const check=Utils.verifyAndFormatUrl(url); if(check.success){log('已拦截window.open跳转');success(url);return null;} } return oldOpen.apply(this,arguments); }; } function initFinal115Page(){ if(!window.opener) return; const check=Utils.verifyAndFormatUrl(location.href); if(check.success){window.opener.postMessage({type:'HDHIVE_RESULT',status:'success',url:check.url},'*');window.close();} else{window.opener.postMessage({type:'HDHIVE_RESULT',status:'error',error:`跳转链接无效: ${check.msg}`},'*');} } function init() { TelegramPush.init(); Logger.init(); if (Utils.isResourcePage()) { initChildPage(); } else if (Utils.isFinal115Page()) { initFinal115Page(); } else if (Utils.isHDHiveSite()) { SettingsManager.addSettingButtons(); processAllPosters(); setupSearchListener(); setupUrlChangeListener(); setupSearchDialogListener(); let mutationTimeout; const observer = new MutationObserver(mutations => { clearTimeout(mutationTimeout); mutationTimeout = setTimeout(() => { let shouldProcess = false; for (const mutation of mutations) { if (mutation.addedNodes.length > 0) { shouldProcess = true; break; } } if (shouldProcess) { processAllPosters(); } }, 300); }); observer.observe(document.body, { childList: true, subtree: true }); if (Utils.isParentPage()) { initParentPage(); } } } function setupSearchListener() { const searchInput = document.querySelector('input[type="text"][name="search"]'); if (searchInput) { searchInput.addEventListener('input', () => { state.processedItems.clear(); state.processingItems.clear(); setTimeout(processAllPosters, 1000); }); } } function setupUrlChangeListener() { let currentUrl = window.location.href; const observer = new MutationObserver(() => { if (window.location.href !== currentUrl) { currentUrl = window.location.href; state.processedItems.clear(); state.processingItems.clear(); state.embyCache.clear(); setTimeout(processAllPosters, 1000); } }); observer.observe(document.body, { childList: true, subtree: true }); } function setupSearchDialogListener() { const observer = new MutationObserver(() => { const searchDialog = document.querySelector('.MuiDialog-paper'); if (searchDialog) { state.processedItems.clear(); state.processingItems.clear(); setTimeout(processAllPosters, 500); } }); observer.observe(document.body, { childList: true, subtree: true }); } const style = document.createElement('style'); style.textContent = ` .emby-poster-btn { position: absolute; width: ${BUTTON_STYLES.posterBtn.size}; height: ${BUTTON_STYLES.posterBtn.size}; top: ${BUTTON_STYLES.posterBtn.position.top}; right: ${BUTTON_STYLES.posterBtn.position.right}; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 18px; font-weight: bold; cursor: pointer; z-index: 100; box-shadow: 0 4px 15px rgba(0,0,0,0.2); transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1); text-shadow: 0 1px 2px rgba(0,0,0,0.2); } .emby-poster-btn.has { background: ${BUTTON_STYLES.posterBtn.has.bg}; color: white; border: ${BUTTON_STYLES.posterBtn.has.border}; } .emby-poster-btn.not-has { background: ${BUTTON_STYLES.posterBtn.notHas.bg}; color: white; border: ${BUTTON_STYLES.posterBtn.notHas.border}; } .emby-poster-btn:hover { transform: ${BUTTON_STYLES.posterBtn.hoverEffect}; box-shadow: 0 6px 20px rgba(0,0,0,0.3); } .emby-name-btn { display: inline-flex; align-items: center; margin-top: ${BUTTON_STYLES.nameBtn.marginTop}; padding: ${BUTTON_STYLES.nameBtn.padding}; border-radius: 12px; font-size: ${BUTTON_STYLES.nameBtn.fontSize}; font-weight: 600; cursor: pointer; transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1); box-shadow: 0 1px 3px rgba(0,0,0,0.1); backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px); background: transparent; } .emby-name-btn.has { background: ${BUTTON_STYLES.nameBtn.has.bg}; color: ${BUTTON_STYLES.nameBtn.has.textColor}; border: ${BUTTON_STYLES.nameBtn.has.border}; } .emby-name-btn.not-has { background: ${BUTTON_STYLES.nameBtn.notHas.bg}; color: ${BUTTON_STYLES.nameBtn.notHas.textColor}; border: ${BUTTON_STYLES.nameBtn.notHas.border}; } .emby-name-btn:hover { transform: ${BUTTON_STYLES.nameBtn.hoverEffect}; box-shadow: 0 2px 6px rgba(0,0,0,0.15); } .emby-detail-poster-btn { position: absolute; width: ${BUTTON_STYLES.detailBtn.posterBtn.size}; height: ${BUTTON_STYLES.detailBtn.posterBtn.size}; top: ${BUTTON_STYLES.detailBtn.posterBtn.position.top}; right: ${BUTTON_STYLES.detailBtn.posterBtn.position.right}; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 20px; font-weight: bold; cursor: pointer; z-index: 100; box-shadow: 0 6px 20px rgba(0,0,0,0.3); transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1); text-shadow: 0 2px 4px rgba(0,0,0,0.3); } .emby-detail-poster-btn.has { background: ${BUTTON_STYLES.detailBtn.posterBtn.has.bg}; color: white; border: ${BUTTON_STYLES.detailBtn.posterBtn.has.border}; } .emby-detail-poster-btn.not-has { background: ${BUTTON_STYLES.detailBtn.posterBtn.notHas.bg}; color: white; border: ${BUTTON_STYLES.detailBtn.posterBtn.notHas.border}; } .emby-detail-poster-btn:hover { transform: ${BUTTON_STYLES.detailBtn.posterBtn.hoverEffect}; box-shadow: 0 8px 25px rgba(0,0,0,0.4); } .emby-detail-title-btn { display: inline-flex; align-items: center; margin-left: ${BUTTON_STYLES.detailBtn.titleBtn.marginLeft}; padding: ${BUTTON_STYLES.detailBtn.titleBtn.padding}; border-radius: 15px; font-size: ${BUTTON_STYLES.detailBtn.titleBtn.fontSize}; font-weight: 600; cursor: pointer; transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1); box-shadow: 0 2px 8px rgba(0,0,0,0.15); backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px); background: transparent; } .emby-detail-title-btn.has { background: ${BUTTON_STYLES.detailBtn.titleBtn.has.bg}; color: ${BUTTON_STYLES.detailBtn.titleBtn.has.textColor}; border: ${BUTTON_STYLES.detailBtn.titleBtn.has.border}; } .emby-detail-title-btn.not-has { background: ${BUTTON_STYLES.detailBtn.titleBtn.notHas.bg}; color: ${BUTTON_STYLES.detailBtn.titleBtn.notHas.textColor}; border: ${BUTTON_STYLES.detailBtn.titleBtn.notHas.border}; } .emby-detail-title-btn:hover { transform: ${BUTTON_STYLES.detailBtn.titleBtn.hoverEffect}; box-shadow: 0 4px 12px rgba(0,0,0,0.2); } .emby-search-year-btn { display: inline-flex; align-items: center; margin-left: ${BUTTON_STYLES.searchYearBtn.marginLeft}; padding: ${BUTTON_STYLES.searchYearBtn.padding}; border-radius: 12px; font-size: ${BUTTON_STYLES.searchYearBtn.fontSize}; font-weight: 600; cursor: pointer; transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1); box-shadow: 0 1px 3px rgba(0,0,0,0.1); backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px); background: transparent; } .emby-search-year-btn.has { background: ${BUTTON_STYLES.searchYearBtn.has.bg}; color: ${BUTTON_STYLES.searchYearBtn.has.textColor}; border: ${BUTTON_STYLES.searchYearBtn.has.border}; } .emby-search-year-btn.not-has { background: ${BUTTON_STYLES.searchYearBtn.notHas.bg}; color: ${BUTTON_STYLES.searchYearBtn.notHas.textColor}; border: ${BUTTON_STYLES.searchYearBtn.notHas.border}; } .emby-search-year-btn:hover { transform: ${BUTTON_STYLES.searchYearBtn.hoverEffect}; box-shadow: 0 2px 6px rgba(0,0,0,0.15); } .emby-user-page-btn { display: inline-flex; align-items: center; margin-left: ${BUTTON_STYLES.userPageBtn.marginLeft}; padding: ${BUTTON_STYLES.userPageBtn.padding}; border-radius: 12px; font-size: ${BUTTON_STYLES.userPageBtn.fontSize}; font-weight: 600; cursor: default; transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1); box-shadow: 0 1px 3px rgba(0,0,0,0.1); backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px); opacity: 0.7; } .emby-user-page-btn.has { background: ${BUTTON_STYLES.userPageBtn.has.bg}; color: ${BUTTON_STYLES.userPageBtn.has.textColor}; border: ${BUTTON_STYLES.userPageBtn.has.border}; } .emby-user-page-btn.not-has { background: ${BUTTON_STYLES.userPageBtn.notHas.bg}; color: ${BUTTON_STYLES.userPageBtn.notHas.textColor}; border: ${BUTTON_STYLES.userPageBtn.notHas.border}; } .emby-user-page-btn:hover { transform: ${BUTTON_STYLES.userPageBtn.hoverEffect}; box-shadow: 0 2px 6px rgba(0,0,0,0.15); } .emby-collection-btn { display: inline-flex; align-items: center; margin-left: ${BUTTON_STYLES.collectionBtn.marginLeft}; padding: ${BUTTON_STYLES.collectionBtn.padding}; border-radius: 12px; font-size: ${BUTTON_STYLES.collectionBtn.fontSize}; font-weight: 600; cursor: pointer; transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1); box-shadow: 0 1px 3px rgba(0,0,0,0.1); backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px); background: transparent; } .emby-collection-btn.has { background: ${BUTTON_STYLES.collectionBtn.has.bg}; color: ${BUTTON_STYLES.collectionBtn.has.textColor}; border: ${BUTTON_STYLES.collectionBtn.has.border}; } .emby-collection-btn.not-has { background: ${BUTTON_STYLES.collectionBtn.notHas.bg}; color: ${BUTTON_STYLES.collectionBtn.notHas.textColor}; border: ${BUTTON_STYLES.collectionBtn.notHas.border}; } .emby-collection-btn:hover { transform: ${BUTTON_STYLES.collectionBtn.hoverEffect}; box-shadow: 0 2px 6px rgba(0,0,0,0.15); } .emby-setting-btn { display: inline-flex; align-items: center; padding: ${BUTTON_STYLES.settingBtn.padding}; border-radius: 12px; font-size: ${BUTTON_STYLES.settingBtn.fontSize}; font-weight: 600; cursor: pointer; transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1); box-shadow: 0 1px 3px rgba(0,0,0,0.1); backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px); background: ${BUTTON_STYLES.settingBtn.has.bg}; color: ${BUTTON_STYLES.settingBtn.has.textColor}; border: ${BUTTON_STYLES.settingBtn.has.border}; } .emby-setting-btn:hover { transform: ${BUTTON_STYLES.settingBtn.hoverEffect}; box-shadow: 0 2px 6px rgba(0,0,0,0.15); } .emby-name-btn::before, .emby-detail-title-btn::before, .emby-search-year-btn::before, .emby-user-page-btn::before, .emby-collection-btn::before { content: ""; display: inline-block; width: 16px; height: 16px; margin-right: 6px; background-image: url('https://raw.githubusercontent.com/lige47/QuanX-icon-rule/main/icon/emby.png'); background-size: contain; background-repeat: no-repeat; background-position: center; filter: brightness(0.9); } .emby-detail-title-btn::before { width: 18px; height: 18px; margin-right: 8px; } .MuiPopover-root .emby-poster-btn, .MuiPopover-root .emby-name-btn, .MuiPopover-root .emby-detail-poster-btn, .MuiPopover-root .emby-detail-title-btn, .MuiPopover-root .emby-search-year-btn, .MuiPopover-root .emby-user-page-btn, .MuiPopover-root .emby-collection-btn, .MuiPopover-root .emby-setting-btn { z-index: 1500; } #hdhive-notice { position: fixed; top: 10px; left: 50%; transform: translateX(-50%); padding: 8px 12px; background-color: #ff9800; color: #fff; font-size: 14px; font-weight: bold; border-radius: 4px; z-index: 9999; box-shadow: 0 2px 6px rgba(0,0,0,0.3); } `; document.head.appendChild(style); if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } })();