// ==UserScript== // @name Bangumi 直接看番 // @namespace http://tampermonkey.net/ // @version 0.8.7 // @description 在Bangumi番剧页面添加直接看番按钮,悬浮播放器播放,支持弹幕和截图 // @author NeetFlix // @license MIT // @match https://bgm.tv/subject/* // @match https://bangumi.tv/subject/* // @match https://chii.in/subject/* // @match https://www.7sefun.top/* // @match https://www.agedm.io/* // @match https://dmbus.cc/* // @match *://jx.ejtsyc.com/* // @match *://*/* // @grant GM_xmlhttpRequest // @grant GM_addStyle // @grant GM_getValue // @grant GM_setValue // @connect www.agedm.io // @connect www.7sefun.top // @connect dmbus.cc // @connect api.dandanplay.net // @connect cas2.dandanplay.net // @connect bytetos.com // @connect bytecdn.cn // @connect bilibili.com // @connect bilivideo.com // @connect hdslb.com // @connect mcdn.bilivideo.cn // @connect akamaized.net // @connect cloudfront.net // @connect cdn.jsdelivr.net // @run-at document-start // @downloadURL https://update.greasyfork.icu/scripts/573437/Bangumi%20%E7%9B%B4%E6%8E%A5%E7%9C%8B%E7%95%AA.user.js // @updateURL https://update.greasyfork.icu/scripts/573437/Bangumi%20%E7%9B%B4%E6%8E%A5%E7%9C%8B%E7%95%AA.meta.js // ==/UserScript== (function() { 'use strict'; const PLAYER_MODE_KEY = 'neetflix_player_mode'; const PLAYER_VIDEO_URL_KEY = 'neetflix_video_url'; const PLAYER_TITLE_KEY = 'neetflix_title'; const PLAYER_EPISODE_KEY = 'neetflix_episode'; const PLAYER_BGM_ID_KEY = 'neetflix_bgm_id'; const currentHost = window.location.hostname; const isBangumi = currentHost.includes('bgm.tv') || currentHost.includes('bangumi.tv') || currentHost.includes('chii.in'); const isIframe = window.self !== window.top; // 首先注入全局弹幕脚本(所有页面,包括iframe) injectDanmakuScriptGlobal(); // 检查是否是视频网站页面 const isVideoSite = currentHost.includes('7sefun.top') || currentHost.includes('agedm.io') || currentHost.includes('dmbus.cc'); if (isVideoSite) { // 视频网站页面需要执行播放模式检测 // iframe里需要延迟检测,因为GM API可能还没准备好 if (isIframe) { let retryCount = 0; function tryInitVideoSiteMode() { const playerMode = GM_getValue(PLAYER_MODE_KEY, ''); if (playerMode || retryCount > 10) { initVideoSiteMode(); } else { retryCount++; setTimeout(tryInitVideoSiteMode, 100); } } tryInitVideoSiteMode(); } else { initVideoSiteMode(); } if (!isIframe) return; } // 如果是iframe,停止执行后续Bangumi页面逻辑 if (isIframe) { return; } const Plugins = [ { name: "7sefun", baseUrl: "https://www.7sefun.top/", searchUrl: "https://www.7sefun.top/vodsearch/-------------.html?wd=@keyword", searchList: "//div[2]/div[2]/div[2]/div[2]/div", searchName: "//div[2]/text()", searchResult: "//a", chapterRoads: "//div[2]/div[2]/div[2]/div/div[2]/div[1]/div[2]", chapterResult: "//a", useIframe: true, needsProxy: false }, { name: "AGE", baseUrl: "https://www.agedm.io/", searchUrl: "https://www.agedm.io/search?query=@keyword", searchList: "//div[2]/div/section/div/div/div/div", searchName: "//div/div[2]/h5/a", searchResult: "//div/div[2]/h5/a", chapterRoads: "//div[2]/div/section/div/div[2]/div[2]/div[2]/div", chapterResult: "//ul/li/a", useIframe: true, needsProxy: true }, { name: "DM84", baseUrl: "https://dmbus.cc/", searchUrl: "https://dmbus.cc/s----------.html?wd=@keyword", searchList: "//div/div[3]/ul/li", searchName: "//div/a[2]", searchResult: "//div/a[2]", chapterRoads: "//div/div[4]/div/ul", chapterResult: "//li/a", useIframe: true, needsProxy: false }, ]; const UserAgents = [ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36" ]; let currentPlayerState = { visible: false, url: '', title: '', episode: '', roads: [], currentRoad: 0, currentEpisode: 0, pluginName: '', danmakuEnabled: true, danmakuOpacity: 0.8, danmakuFontSize: 24, danmakuSpeed: 8 }; function getRandomUA() { return UserAgents[Math.floor(Math.random() * UserAgents.length)]; } // 全局弹幕脚本注入(在所有页面运行,包括iframe) function injectDanmakuScriptGlobal() { const script = document.createElement('script'); script.textContent = ` (function() { if (window.neetflixDanmakuInjected) return; window.neetflixDanmakuInjected = true; var isIframe = (window.self !== window.top); console.log('[NeetFlix Danmaku] Script loaded, isIframe=' + isIframe); // ========== IFRAME 逻辑:发送视频时间给父窗口 ========== if (isIframe) { var iframeVideo = null; var iframePaused = true; var hasVideo = false; // 监听来自子iframe的时间更新并转发给父窗口 window.addEventListener('message', function(e) { if (e.data && e.data.type === 'neetflixTimeUpdate' && window.parent !== window) { // 从子iframe收到时间,转发给父窗口 try { window.parent.postMessage({ type: 'neetflixTimeUpdate', time: e.data.time, playbackRate: e.data.playbackRate, paused: e.data.paused, _fromChild: true }, '*'); } catch(err) {} } if (e.data && e.data.type === 'neetflixRequestTime' && iframeVideo) { try { e.source.postMessage({ type: 'neetflixTimeUpdate', time: iframeVideo.currentTime, playbackRate: iframeVideo.playbackRate, paused: iframeVideo.paused }, '*'); } catch(err) {} } // 转发截图命令到子iframe if (e.data && e.data.type === 'neetflixScreenshot') { console.log('[NeetFlix] Forwarding screenshot command to child iframe'); var iframes = document.querySelectorAll('iframe'); iframes.forEach(function(iframe) { try { iframe.contentWindow.postMessage({ type: 'neetflixScreenshot' }, '*'); } catch(err) { console.log('[NeetFlix] Cannot forward to iframe:', err.message); } }); // 尝试在当前页面截图 if (iframeVideo) { console.log('[NeetFlix] Attempting screenshot on current video'); try { var canvas = document.createElement('canvas'); canvas.width = iframeVideo.videoWidth || 640; canvas.height = iframeVideo.videoHeight || 360; var ctx = canvas.getContext('2d'); ctx.drawImage(iframeVideo, 0, 0, canvas.width, canvas.height); var dataUrl = canvas.toDataURL('image/png'); console.log('[NeetFlix] Screenshot success, data length:', dataUrl.length); // 发送截图结果给父窗口 window.parent.postMessage({ type: 'neetflixScreenshotResult', dataUrl: dataUrl }, '*'); } catch(err) { console.log('[NeetFlix] Screenshot failed (CORS):', err.message); // 通知父窗口截图失败,需要使用特权方式 window.parent.postMessage({ type: 'neetflixScreenshotFailed', videoSrc: iframeVideo.src, currentTime: iframeVideo.currentTime }, '*'); } } } // 转发截图结果到顶层窗口 if (e.data && e.data.type === 'neetflixScreenshotResult') { console.log('[NeetFlix] Forwarding screenshot result to parent'); window.parent.postMessage({ type: 'neetflixScreenshotResult', dataUrl: e.data.dataUrl }, '*'); } // 转发截图失败消息到顶层窗口 if (e.data && e.data.type === 'neetflixScreenshotFailed') { console.log('[NeetFlix] Forwarding screenshot failure to parent'); window.parent.postMessage({ type: 'neetflixScreenshotFailed', videoSrc: e.data.videoSrc, currentTime: e.data.currentTime }, '*'); } }); function sendTime() { if (iframeVideo && hasVideo) { try { window.parent.postMessage({ type: 'neetflixTimeUpdate', time: iframeVideo.currentTime, playbackRate: iframeVideo.playbackRate, paused: iframeVideo.paused }, '*'); } catch(e) {} } requestAnimationFrame(sendTime); } function findAndWatchVideo() { var videos = document.querySelectorAll('video'); if (videos.length > 0) { iframeVideo = videos[0]; iframePaused = iframeVideo.paused; hasVideo = true; iframeVideo.addEventListener('pause', function() { iframePaused = true; }); iframeVideo.addEventListener('play', function() { iframePaused = false; }); sendTime(); console.log('[NeetFlix Danmaku] Iframe: video found, sending time to parent'); return true; } return false; } function tryFind() { if (findAndWatchVideo()) return; // 监听DOM变化,检测动态加载的video和iframe var observer = new MutationObserver(function(mutations) { if (findAndWatchVideo()) { observer.disconnect(); return; } // DOM变化了,检查嵌套iframe checkIframes(); }); if (document.body) { observer.observe(document.body, { childList: true, subtree: true }); } function checkIframes() { var iframes = document.querySelectorAll('iframe'); iframes.forEach(function(iframe) { // 监听iframe加载完成 iframe.addEventListener('load', function() { console.log('[NeetFlix Danmaku] Iframe loaded, checking for video...'); setTimeout(function() { findAndWatchVideo(); sendRequestToIframe(iframe); }, 500); }); // 也立即尝试发送请求 sendRequestToIframe(iframe); }); } function sendRequestToIframe(iframe) { try { var childWin = iframe.contentWindow; if (childWin) { childWin.postMessage({ type: 'neetflixRequestTime', from: 'parent' }, '*'); console.log('[NeetFlix Danmaku] Sent time request to iframe'); } } catch(e) { console.log('[NeetFlix Danmaku] Cannot access iframe (cross-origin):', e.message); } } // 立即检查一次 checkIframes(); // 持续尝试 var retryCount = 0; var maxRetries = 20; function retry() { if (hasVideo) return; retryCount++; checkIframes(); if (retryCount < maxRetries) { setTimeout(retry, 1000); } else { console.log('[NeetFlix Danmaku] Iframe: giving up after ' + maxRetries + ' retries'); } } retry(); } if (document.readyState === 'complete') { tryFind(); } else { document.addEventListener('DOMContentLoaded', tryFind); } return; } // ========== 顶层窗口逻辑:接收时间,渲染弹幕 ========== var canvas = null; var ctx = null; var danmakuList = []; var danmakuData = {}; var enabled = true; var opacity = 0.8; var fontSize = 24; var speed = 8; var lineHeight = 1.5; var lineCount = 12; var lastTime = 0; var currentVideoTime = 0; var currentPlaybackRate = 1; var currentVideoPaused = false; var lastVideoSecond = -1; // 监听来自iframe的时间更新 var messageCount = 0; window.addEventListener('message', function(e) { if (e.data && e.data.type === 'neetflixTimeUpdate') { currentVideoTime = e.data.time; if (e.data.playbackRate) currentPlaybackRate = e.data.playbackRate; if (e.data.paused !== undefined) currentVideoPaused = e.data.paused; messageCount++; if (messageCount <= 5 || messageCount % 60 === 0) { console.log('[NeetFlix Danmaku] Time update: ' + currentVideoTime.toFixed(2) + 's, paused=' + currentVideoPaused); } } // 接收弹幕数据 if (e.data && e.data.type === 'neetflixLoadDanmaku') { danmakuData = e.data.data || {}; danmakuList = []; lastVideoSecond = -1; console.log('[NeetFlix Danmaku] Loaded ' + Object.keys(danmakuData).length + ' seconds of danmaku'); // 更新窗口模式的弹幕数量 var countSpan = document.getElementById('neetflix-danmaku-count'); if (countSpan) { var totalCount = 0; for (var key in danmakuData) { totalCount += danmakuData[key].length; } countSpan.textContent = '(' + totalCount + ')'; } } // 接收弹幕开关(悬浮播放器模式) if (e.data && e.data.type === 'neetflixSetDanmakuEnabled') { enabled = e.data.enabled; if (!enabled) danmakuList = []; } // 接收弹幕开关(窗口模式) if (e.data && e.data.type === 'neetflixWindowSetDanmaku') { enabled = e.data.enabled; if (!enabled) danmakuList = []; // 更新按钮状态 var danmakuBtn = document.getElementById('neetflix-danmaku-toggle'); if (danmakuBtn) { if (enabled) { danmakuBtn.classList.add('active'); } else { danmakuBtn.classList.remove('active'); } } } }); // 也尝试在顶层窗口找video var foundVideo = null; function findVideoInTop() { var videos = document.querySelectorAll('video'); if (videos.length > 0) { foundVideo = videos[0]; console.log('[NeetFlix Danmaku] Top window: found video'); function trackTime() { if (foundVideo) { currentVideoTime = foundVideo.currentTime; currentPlaybackRate = foundVideo.playbackRate; currentVideoPaused = foundVideo.paused; } requestAnimationFrame(trackTime); } trackTime(); } } var playerContainer = null; var playerMode = false; function initCanvas() { if (!document.body) { setTimeout(initCanvas, 100); return; } // 检测是否是iframe模式(播放器是否存在) playerContainer = document.getElementById('neetflix-player'); playerMode = !!playerContainer; // 如果canvas已存在,需要检查位置是否正确 if (canvas) { // 如果播放器已创建但canvas不在播放器内,重新定位 if (playerMode) { var playerBody = document.querySelector('#neetflix-player .neetflix-player-body'); if (playerBody && canvas.parentElement !== playerBody) { console.log('[NeetFlix Danmaku] Moving canvas to player body'); canvas.remove(); canvas = null; ctx = null; } } // 如果canvas位置正确,不需要重新初始化 if (canvas) return; } canvas = document.createElement('canvas'); canvas.id = 'neetflix-danmaku-canvas'; if (playerMode) { // iframe模式:canvas放在播放器内部 var playerBody = document.querySelector('#neetflix-player .neetflix-player-body'); if (playerBody) { canvas.style.cssText = 'position:absolute;top:0;left:0;width:100%;height:100%;pointer-events:none;z-index:10;'; playerBody.appendChild(canvas); console.log('[NeetFlix Danmaku] Canvas added to player body (iframe mode)'); } else { document.body.appendChild(canvas); } } else { // 窗口模式:canvas覆盖全屏 canvas.style.cssText = 'position:fixed;top:0;left:0;width:100%;height:100%;pointer-events:none;z-index:2147483647;'; document.body.appendChild(canvas); } ctx = canvas.getContext('2d'); resize(); window.addEventListener('resize', function() { resize(); }); // 监听播放器大小变化 if (playerMode && playerContainer) { var resizeObserver = new ResizeObserver(function() { // 拖动时暂停resize,防止弹幕闪烁 if (window.playerDragging) return; resize(); }); // 观察播放器body var playerBody = playerContainer.querySelector('.neetflix-player-body'); if (playerBody) { resizeObserver.observe(playerBody); } resizeObserver.observe(playerContainer); } document.addEventListener('fullscreenchange', function() { var fullscreenEl = document.fullscreenElement; if (fullscreenEl && canvas) { if (canvas.parentElement !== fullscreenEl) { fullscreenEl.appendChild(canvas); resize(); } } else if (!fullscreenEl && canvas && canvas.parentElement !== document.body && canvas.parentElement !== playerContainer) { if (playerMode && playerContainer) { var playerBody = document.querySelector('#neetflix-player .neetflix-player-body'); if (playerBody) playerBody.appendChild(canvas); } else { document.body.appendChild(canvas); } resize(); } }); lastTime = performance.now(); animate(); console.log('[NeetFlix Danmaku] Canvas initialized (playerMode=' + playerMode + ')'); } function resize() { if (!canvas || !ctx) return; if (playerMode && playerContainer) { var playerBody = playerContainer.querySelector('.neetflix-player-body'); if (playerBody) { var w = playerBody.clientWidth || 640; var h = playerBody.clientHeight || 360; canvas.width = w; canvas.height = h; canvas.style.width = w + 'px'; canvas.style.height = h + 'px'; } } else { canvas.width = window.innerWidth; canvas.height = window.innerHeight; canvas.style.width = window.innerWidth + 'px'; canvas.style.height = window.innerHeight + 'px'; } lineCount = Math.floor(canvas.height / (fontSize * lineHeight)); } function animate() { if (!ctx || !canvas) { requestAnimationFrame(animate); return; } var now = performance.now(); var delta = (now - lastTime) / 1000; lastTime = now; ctx.clearRect(0, 0, canvas.width, canvas.height); if (enabled) { for (var i = danmakuList.length - 1; i >= 0; i--) { var d = danmakuList[i]; if (!currentVideoPaused) { d.x -= d.speed * delta; } if (d.x + d.width < 0) { danmakuList.splice(i, 1); continue; } ctx.globalAlpha = opacity; ctx.font = 'bold ' + fontSize + 'px Microsoft YaHei, SimHei, sans-serif'; ctx.fillStyle = d.color; ctx.strokeStyle = 'rgba(0,0,0,0.8)'; ctx.lineWidth = 2; ctx.strokeText(d.text, d.x, d.y); ctx.fillText(d.text, d.x, d.y); } } requestAnimationFrame(animate); } function findLine() { var startY = fontSize * lineHeight; var maxLine = Math.min(lineCount, Math.floor((canvas.height - startY) / (fontSize * lineHeight))); if (maxLine <= 0) maxLine = 1; for (var i = 0; i < maxLine; i++) { var available = true; for (var j = 0; j < danmakuList.length; j++) { var d = danmakuList[j]; if (d.line === i && d.x + d.width > canvas.width * 0.8) { available = false; break; } } if (available) return i; } return Math.floor(Math.random() * maxLine); } var danmakuShownCount = 0; function showDanmaku(text, color, type) { if (!enabled || !ctx || !canvas) return; ctx.font = 'bold ' + fontSize + 'px Microsoft YaHei, SimHei, sans-serif'; var metrics = ctx.measureText(text); var width = metrics.width; var line = findLine(); var y = fontSize * lineHeight + line * fontSize * lineHeight + fontSize / 2; var d = { text: text, color: color || '#FFFFFF', x: canvas.width, y: y, width: width, speed: (canvas.width + width) / speed, line: line, type: type || 1 }; danmakuList.push(d); danmakuShownCount++; if (danmakuShownCount <= 5) { console.log('[NeetFlix Danmaku] showDanmaku: "' + text + '", total: ' + danmakuShownCount); } } function showDanmakusForSecond(second) { if (!danmakuData[second]) return; danmakuData[second].forEach(function(d, idx) { setTimeout(function() { showDanmaku(d.message, d.color, d.type); }, idx * 100); }); } function checkDanmakuTime() { var intTime = Math.floor(currentVideoTime); if (intTime !== lastVideoSecond) { if (intTime % 10 === 0) { console.log('[NeetFlix Danmaku] Time: ' + intTime + 's, paused: ' + currentVideoPaused); } if (!currentVideoPaused && danmakuData[intTime]) { console.log('[NeetFlix Danmaku] Showing danmaku for second ' + intTime); showDanmakusForSecond(intTime); } lastVideoSecond = intTime; } } function mainLoop() { if (!foundVideo) { findVideoInTop(); } checkDanmakuTime(); requestAnimationFrame(mainLoop); } function start() { initCanvas(); mainLoop(); // 监听播放器出现(iframe模式) // 需要观察整个document,因为播放器是动态创建的 setTimeout(function() { console.log('[NeetFlix Danmaku] Setting up player observer...'); var playerObserver = new MutationObserver(function(mutations) { mutations.forEach(function(mutation) { // 检查class属性变化(播放器显示/隐藏) // 只在播放器从隐藏变为显示时重新初始化,忽略拖动等class变化 if (mutation.type === 'attributes' && mutation.attributeName === 'class') { var target = mutation.target; if (target.id === 'neetflix-player') { var oldValue = mutation.oldValue || ''; var wasHidden = !oldValue.includes('show'); var isNowShown = target.classList.contains('show'); // 只有从隐藏变为显示时才重新初始化 if (wasHidden && isNowShown) { console.log('[NeetFlix Danmaku] Player shown, reinitializing canvas'); if (canvas) { canvas.remove(); canvas = null; ctx = null; danmakuList = []; } setTimeout(function() { initCanvas(); setTimeout(resize, 100); }, 300); } } } // 检查新增节点 if (mutation.type === 'childList' && mutation.addedNodes.length > 0) { console.log('[NeetFlix Danmaku] DOM changed, added nodes:', mutation.addedNodes.length); mutation.addedNodes.forEach(function(node) { if (node.id === 'neetflix-player') { console.log('[NeetFlix Danmaku] Player detected, reinitializing canvas'); if (canvas) { canvas.remove(); canvas = null; ctx = null; danmakuList = []; } setTimeout(function() { initCanvas(); setTimeout(resize, 100); }, 300); } }); } }); }); // 观察整个document,需要 attributeOldValue 来判断 class 变化 try { playerObserver.observe(document.documentElement, { childList: true, subtree: true, attributes: true, attributeFilter: ['class'], attributeOldValue: true }); console.log('[NeetFlix Danmaku] Player observer started on documentElement'); } catch(e) { console.error('[NeetFlix Danmaku] Observer error:', e); } // 也观察body if (document.body) { playerObserver.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['class'] }); console.log('[NeetFlix Danmaku] Player observer started on body'); } // 直接检查播放器是否已存在(可能是页面加载时创建的) var existingPlayer = document.getElementById('neetflix-player'); if (existingPlayer) { console.log('[NeetFlix Danmaku] Player already exists in DOM'); // 如果播放器有show类,说明用户已经打开过播放器了 if (existingPlayer.classList.contains('show')) { console.log('[NeetFlix Danmaku] Player already visible'); setTimeout(function() { initCanvas(); setTimeout(resize, 100); }, 300); } } // 定时检查播放器是否存在(MutationObserver的备选方案) var checkCount = 0; var checkInterval = setInterval(function() { checkCount++; var player = document.getElementById('neetflix-player'); if (player) { console.log('[NeetFlix Danmaku] Periodic check found player after ' + checkCount + ' checks'); clearInterval(checkInterval); if (canvas) { canvas.remove(); canvas = null; ctx = null; danmakuList = []; } setTimeout(function() { initCanvas(); setTimeout(resize, 100); }, 300); } // 最多检查60次(60秒) if (checkCount >= 60) { clearInterval(checkInterval); } }, 1000); }, 100); } if (document.body) { start(); } else { document.addEventListener('DOMContentLoaded', start); } console.log('[NeetFlix Danmaku] Top window script initialized'); })(); `; if (document.head) { document.head.appendChild(script); } else { document.addEventListener('DOMContentLoaded', () => { document.head.appendChild(script); }); } } function initVideoSiteMode() { const playerMode = GM_getValue(PLAYER_MODE_KEY, ''); const videoUrl = (GM_getValue(PLAYER_VIDEO_URL_KEY, '') || '').replace(/,+$/, ''); const title = GM_getValue(PLAYER_TITLE_KEY, ''); const episode = GM_getValue(PLAYER_EPISODE_KEY, ''); console.log(`[NeetFlix VideoSite] mode=${playerMode}, url=${videoUrl}, title="${title}", episode="${episode}", isIframe=${isIframe}, href=${window.location.href}`); // 如果没有playerMode,无条件注入(支持窗口模式下切换集数) if (!playerMode && !isIframe) { const isVideoSite = currentHost.includes('agedm.io') || currentHost.includes('7sefun.top') || currentHost.includes('dmbus.cc'); if (isVideoSite) { console.log('[NeetFlix] Window mode (no GM value), injecting...'); window.playerModeActive = false; injectPlayerModeCSS(); injectWindowModeUI(); return; } } // 如果有playerMode设置,说明需要激活播放器 if (playerMode) { console.log(`[NeetFlix] Player mode: ${playerMode}, isIframe: ${isIframe}`); if (playerMode === 'iframe' && isIframe) { // 7sefun/DM84: 当前页面是被嵌入的播放页面 console.log('[NeetFlix] Iframe mode detected, injecting CSS...'); // 清除之前的标志,允许重新注入(切换集数时需要) window.playerModeActive = false; injectPlayerModeCSS(); return; } if (playerMode === 'window' && !isIframe) { // AGE新窗口: 只要在支持的视频网站上就激活 const currentUrl = window.location.href.replace(/,+$/, ''); console.log(`[NeetFlix] Window mode check: currentUrl="${currentUrl}"`); // 检查是否在支持的视频网站 const isVideoSite = currentHost.includes('agedm.io') || currentHost.includes('7sefun.top') || currentHost.includes('dmbus.cc'); if (isVideoSite) { console.log('[NeetFlix] Window mode detected, injecting CSS...'); injectPlayerModeCSS(); injectWindowModeUI(); if (title) { setTimeout(() => loadDanmakuForTitle(title, episode), 1500); } } else { console.log('[NeetFlix] Not a supported video site, skipping'); } return; } } // 无论是否有playerMode设置,只要是播放页面,都尝试注入播放器样式 // 这样可以处理嵌套iframe的情况 if (isIframe) { checkAndInjectPlayerMode(); } } function checkAndInjectPlayerMode() { // 对于支持的视频网站,无条件注入(切换集数后GM值可能还没同步) if (!isBangumi) { const isVideoPage = currentHost.includes('agedm.io') || currentHost.includes('7sefun.top') || currentHost.includes('dmbus.cc'); if (isVideoPage) { console.log('[NeetFlix] Video page detected, injecting CSS...'); injectPlayerModeCSS(); } } } // 监听iframe加载完成事件 window.addEventListener('load', function() { const playerMode = GM_getValue(PLAYER_MODE_KEY, ''); if (playerMode) { console.log('[NeetFlix] Window load, playerMode=' + playerMode + ', isIframe=' + isIframe); if (playerMode === 'iframe' && isIframe) { // 清除之前的标志,允许重新注入 window.playerModeActive = false; injectPlayerModeCSS(); } if (playerMode === 'window' && !isIframe) { const isVideoSite = currentHost.includes('agedm.io') || currentHost.includes('7sefun.top') || currentHost.includes('dmbus.cc'); if (isVideoSite) { window.playerModeActive = false; injectPlayerModeCSS(); injectWindowModeUI(); } } } }); function injectPlayerModeCSS() { if (window.playerModeActive) return; window.playerModeActive = true; // 清除playerMode,避免重复注入 GM_setValue(PLAYER_MODE_KEY, ''); const style = document.createElement('style'); style.id = 'playerModeStyle'; style.textContent = ` html, body { margin: 0 !important; padding: 0 !important; overflow: hidden !important; background: #000 !important; } video, iframe { position: fixed !important; top: 0 !important; left: 0 !important; width: 100% !important; height: 100% !important; border: none !important; } video { z-index: 999999 !important; object-fit: contain !important; } iframe { z-index: 999998 !important; } `; if (document.head) { document.head.appendChild(style); } else { document.addEventListener('DOMContentLoaded', () => { document.head.appendChild(style); }); } function findAndResizeVideo() { const videos = document.querySelectorAll('video'); videos.forEach(v => { v.style.position = 'fixed'; v.style.top = '0'; v.style.left = '0'; v.style.width = '100%'; v.style.height = '100%'; v.style.objectFit = 'contain'; v.style.zIndex = '999999'; }); const iframes = document.querySelectorAll('iframe'); iframes.forEach(f => { f.style.position = 'fixed'; f.style.top = '0'; f.style.left = '0'; f.style.width = '100%'; f.style.height = '100%'; f.style.border = 'none'; f.style.zIndex = '999998'; }); } findAndResizeVideo(); setTimeout(findAndResizeVideo, 500); setTimeout(findAndResizeVideo, 1000); setTimeout(findAndResizeVideo, 2000); const startObserver = () => { if (document.body) { const observer = new MutationObserver(findAndResizeVideo); observer.observe(document.body, { childList: true, subtree: true }); } else { setTimeout(startObserver, 100); } }; startObserver(); console.log('[NeetFlix] Player mode activated'); } function injectWindowModeUI() { const style = document.createElement('style'); style.textContent = ` .neetflix-window-header { position: fixed; top: 0; left: 0; right: 0; height: 40px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); display: flex; justify-content: space-between; align-items: center; padding: 0 16px; z-index: 1000000; } .neetflix-window-title { color: white; font-weight: bold; font-size: 14px; } .neetflix-window-btn { background: rgba(255,255,255,0.2); border: none; color: white; padding: 6px 12px; border-radius: 4px; cursor: pointer; margin-left: 8px; } .neetflix-window-btn:hover { background: rgba(255,255,255,0.3); } .neetflix-window-btn.active { background: rgba(255,255,255,0.5); } `; if (document.head) { document.head.appendChild(style); } const addHeader = () => { if (!document.body) { setTimeout(addHeader, 50); return; } const header = document.createElement('div'); header.className = 'neetflix-window-header'; header.innerHTML = `
NeetFlix 播放器
`; document.body.appendChild(header); document.getElementById('neetflix-back-btn').addEventListener('click', () => { window.close(); }); // 选集按钮 document.getElementById('neetflix-episode-toggle').addEventListener('click', (e) => { var roads = GM_getValue('neetflix_roads', null); var currentRoad = GM_getValue('neetflix_current_road', 0); var currentEp = GM_getValue('neetflix_current_episode', 0); if (!roads || roads.length === 0) { alert('暂无选集信息'); return; } var road = roads[currentRoad]; if (!road || !road.episodes) { alert('暂无选集信息'); return; } var html = '
'; html += '
' + road.name + '
'; road.episodes.forEach(function(ep, idx) { var isActive = (idx === currentEp) ? 'background:#667eea;' : ''; html += '
' + ep.name + '
'; }); html += '
'; var popup = document.getElementById('neetflix-episode-popup'); if (popup) popup.remove(); popup = document.createElement('div'); popup.id = 'neetflix-episode-popup'; popup.innerHTML = html; popup.style.cssText = 'position:fixed;top:50px;right:10px;width:200px;background:#1a1a2e;border:1px solid #333;border-radius:8px;z-index:999999;color:#fff;'; document.body.appendChild(popup); popup.querySelectorAll('.neetflix-window-episode').forEach(function(el) { el.addEventListener('click', function() { var url = this.dataset.url; var idx = parseInt(this.dataset.idx); var epName = this.textContent; GM_setValue('neetflix_current_episode', idx); GM_setValue(PLAYER_EPISODE_KEY, epName); window.location.href = url; }); }); }); // 点击其他地方关闭选集弹窗 document.addEventListener('click', function(e) { if (e.target.id !== 'neetflix-episode-toggle') { var popup = document.getElementById('neetflix-episode-popup'); if (popup) popup.remove(); } }); document.getElementById('neetflix-danmaku-toggle').addEventListener('click', (e) => { var btn = e.currentTarget; var isActive = btn.classList.contains('active'); isActive = !isActive; btn.classList.toggle('active', isActive); // 保存状态 GM_setValue('neetflix_danmaku_enabled', isActive); // 发送弹幕开关消息到脚本 window.postMessage({ type: 'neetflixWindowSetDanmaku', enabled: isActive }, '*'); }); document.body.style.paddingTop = '40px'; // 读取当前弹幕状态并更新按钮 const danmakuEnabled = GM_getValue('neetflix_danmaku_enabled', true); const danmakuBtn = document.getElementById('neetflix-danmaku-toggle'); if (danmakuBtn && !danmakuEnabled) { danmakuBtn.classList.remove('active'); } }; // 如果有集数信息,触发弹幕加载 const danmakuTitle = GM_getValue(PLAYER_TITLE_KEY, ''); const danmakuEpisode = GM_getValue(PLAYER_EPISODE_KEY, ''); const danmakuBgmId = GM_getValue(PLAYER_BGM_ID_KEY, null); console.log('[NeetFlix Window] Danmaku title: "' + danmakuTitle + '", episode: "' + danmakuEpisode + '", bgmId: ' + danmakuBgmId); if (danmakuTitle) { console.log('[NeetFlix Window] Triggering danmaku load'); setTimeout(function() { loadDanmakuForTitle(danmakuTitle, danmakuEpisode, danmakuBgmId); }, 1500); } addHeader(); } async function loadDanmakuForTitle(title, episode, bgmBangumiId) { console.log(`[NeetFlix] Loading danmaku for: ${title} episode ${episode} bgmId: ${bgmBangumiId}`); let animeId = null; try { if (bgmBangumiId) { animeId = await danmakuGetByBgmId(bgmBangumiId); } if (!animeId && title) { const searchResults = await danmakuSearch(title); if (searchResults.length > 0) { const bestMatch = searchResults[0]; animeId = bestMatch.animeId; console.log(`[NeetFlix] Best match by title: ${bestMatch.animeTitle} (ID: ${animeId})`); } } if (!animeId) { console.log('[NeetFlix] No danmaku found'); return; } const episodes = await danmakuGetEpisodes(animeId); if (episodes.length === 0) { console.log('[NeetFlix] No episodes found'); return; } const epNumMatch = episode.match(/(\d+)/); const epNum = epNumMatch ? parseInt(epNumMatch[1]) : 1; console.log(`[NeetFlix] Extracted episode number: ${epNum} from "${episode}"`); let targetEpisode = episodes.find(e => e.episodeTitle.includes(`第${epNum}集`) || e.episodeTitle.includes(`第${epNum}话`) || e.episodeTitle.includes(`${epNum}`) || e.episodeTitle === epNum.toString() ); if (!targetEpisode) { targetEpisode = episodes[Math.min(epNum - 1, episodes.length - 1)]; } console.log(`[NeetFlix] Target episode: ${targetEpisode.episodeTitle} (ID: ${targetEpisode.episodeId})`); const danmakus = await danmakuGetComments(targetEpisode.episodeId); const data = {}; danmakus.forEach(d => { const second = Math.floor(d.time); if (!data[second]) data[second] = []; data[second].push({ message: d.message, color: d.color, type: d.type }); }); window.postMessage({ type: 'neetflixLoadDanmaku', data: data }, '*'); console.log(`[NeetFlix] Sent ${danmakus.length} danmakus to player`); } catch (e) { console.error('[NeetFlix] Load danmaku error:', e); } } function generateDanmakuSignature(path, timestamp) { const appId = 'kvpx7qkqjh'; const appValue = 'rABUaBLqdz7aCSi3fe88ZDj2gwga9Vax'; const data = appId + timestamp.toString() + path + appValue; const encoder = new TextEncoder(); const dataBuffer = encoder.encode(data); return crypto.subtle.digest('SHA-256', dataBuffer).then(hashBuffer => { const hashArray = Array.from(new Uint8Array(hashBuffer)); const hashBase64 = btoa(String.fromCharCode.apply(null, hashArray)); return hashBase64; }); } async function danmakuFetch(url) { const appId = 'kvpx7qkqjh'; const uri = new URL(url); const path = uri.pathname; const timestamp = Math.floor(Date.now() / 1000); const signature = await generateDanmakuSignature(path, timestamp); return new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: 'GET', url: url, headers: { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', 'Accept': 'application/json', 'X-Auth': '1', 'X-AppId': appId, 'X-Timestamp': timestamp.toString(), 'X-Signature': signature }, onload: (response) => { if (response.status >= 200 && response.status < 300) { try { resolve(JSON.parse(response.responseText)); } catch (e) { reject(e); } } else { reject(new Error(`HTTP ${response.status}`)); } }, onerror: reject }); }); } async function danmakuSearch(keyword) { const url = `https://api.dandanplay.net/api/v2/search/anime?keyword=${encodeURIComponent(keyword)}`; const json = await danmakuFetch(url); const results = []; if (json.animes) { json.animes.forEach(a => { results.push({ animeId: a.animeId, animeTitle: a.animeTitle }); }); } return results; } async function danmakuGetByBgmId(bgmBangumiId) { const url = `https://api.dandanplay.net/api/v2/bangumi/bgmtv/${bgmBangumiId}`; try { const json = await danmakuFetch(url); console.log('[NeetFlix] danmakuGetByBgmId response:', JSON.stringify(json).substring(0, 500)); if (json.bangumi && json.bangumi.animeId) { console.log(`[NeetFlix] Found danmaku by BgmId: ${json.bangumi.animeId}`); return json.bangumi.animeId; } } catch (e) { console.log(`[NeetFlix] danmakuGetByBgmId failed: ${e.message}`); } return null; } async function danmakuGetEpisodes(animeId) { const url = `https://api.dandanplay.net/api/v2/bangumi/${animeId}`; const json = await danmakuFetch(url); const results = []; if (json.bangumi && json.bangumi.episodes) { json.bangumi.episodes.forEach(e => { results.push({ episodeId: e.episodeId, episodeTitle: e.episodeTitle }); }); } return results; } async function danmakuGetComments(episodeId) { const url = `https://api.dandanplay.net/api/v2/comment/${episodeId}?withRelated=true`; const json = await danmakuFetch(url); const results = []; if (json.comments) { json.comments.forEach(c => { if (c.p) { const parts = c.p.split(','); if (parts.length >= 4) { const time = parseFloat(parts[0]) || 0; const type = parseInt(parts[1]) || 1; const colorValue = parseInt(parts[2]) || 16777215; const r = (colorValue >> 16) & 0xFF; const g = (colorValue >> 8) & 0xFF; const b = colorValue & 0xFF; const color = `#${r.toString(16).padStart(2,'0')}${g.toString(16).padStart(2,'0')}${b.toString(16).padStart(2,'0')}`; results.push({ message: c.m || '', time: time, type: type, color: color }); } } }); } return results; } function getBangumiName() { let nameCn = ''; let nameJp = ''; const infobox = document.querySelector('#infobox') || document.querySelector('.infobox'); if (infobox) { const rows = infobox.querySelectorAll('li'); for (const row of rows) { const tipEl = row.querySelector('.tip') || row.querySelector('span'); if (tipEl) { const tipText = tipEl.textContent || ''; if (tipText.includes('中文名')) { const fullText = row.textContent || ''; nameCn = fullText.replace(tipText, '').trim(); console.log(`[NeetFlix] Found 中文名: "${nameCn}" from "${fullText}"`); break; } } } } const titleEl = document.querySelector('h1.nameSingle a'); if (titleEl) { nameJp = titleEl.textContent.trim(); } if (!nameJp) { const h1El = document.querySelector('h1'); if (h1El) { const link = h1El.querySelector('a'); nameJp = link ? link.textContent.trim() : h1El.textContent.trim(); } } const displayName = nameCn || nameJp || document.title.split(' ')[0]; console.log(`[NeetFlix] NameCn: "${nameCn}", NameJp: "${nameJp}", DisplayName: "${displayName}"`); return displayName; } function getBangumiId() { const match = window.location.pathname.match(/\/subject\/(\d+)/); return match ? match[1] : null; } function createButton() { const btn = document.createElement('button'); btn.id = 'neetflix-play-btn'; btn.textContent = '直接看番'; btn.style.cssText = ` background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; border: none; padding: 10px 24px; border-radius: 25px; font-size: 14px; font-weight: bold; cursor: pointer; box-shadow: 0 4px 15px rgba(102, 126, 234, 0.4); transition: all 0.3s ease; margin-left: 10px; `; btn.onmouseover = () => { btn.style.transform = 'translateY(-2px)'; btn.style.boxShadow = '0 6px 20px rgba(102, 126, 234, 0.6)'; }; btn.onmouseout = () => { btn.style.transform = 'translateY(0)'; btn.style.boxShadow = '0 4px 15px rgba(102, 126, 234, 0.4)'; }; return btn; } function createPanel() { const panel = document.createElement('div'); panel.id = 'neetflix-panel'; panel.innerHTML = `

选择播放源

搜索中...
`; return panel; } function createPlayer() { const player = document.createElement('div'); player.id = 'neetflix-player'; player.innerHTML = `
播放器
选集
`; return player; } function addStyles() { GM_addStyle(` #neetflix-panel { display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; z-index: 99999; } #neetflix-panel.show { display: block; } .neetflix-overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.7); backdrop-filter: blur(5px); } .neetflix-content { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); background: #1a1a2e; border-radius: 16px; width: 90%; max-width: 800px; max-height: 80vh; overflow: hidden; box-shadow: 0 25px 50px rgba(0, 0, 0, 0.5); } .neetflix-header { display: flex; justify-content: space-between; align-items: center; padding: 20px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; } .neetflix-header h3 { margin: 0; font-size: 18px; } .neetflix-close { background: none; border: none; color: white; font-size: 28px; cursor: pointer; line-height: 1; padding: 0; width: 30px; height: 30px; } .neetflix-close:hover { opacity: 0.8; } .neetflix-body { padding: 20px; max-height: 60vh; overflow-y: auto; color: #fff; } .neetflix-search-keyword { background: #252540; padding: 12px 16px; border-radius: 8px; margin-bottom: 16px; color: #888; } .neetflix-search-keyword strong { color: #667eea; font-size: 16px; } .neetflix-loading { text-align: center; padding: 40px; color: #888; } .neetflix-source { margin-bottom: 20px; border: 1px solid #333; border-radius: 8px; overflow: hidden; } .neetflix-source-header { background: #252540; padding: 12px 16px; font-weight: bold; color: #667eea; } .neetflix-results { padding: 10px; } .neetflix-result-item { padding: 10px 12px; margin: 5px 0; background: #2a2a4a; border-radius: 6px; cursor: pointer; transition: all 0.2s; } .neetflix-result-item:hover { background: #3a3a5a; transform: translateX(5px); } .neetflix-episodes { display: none; padding: 10px; background: #1e1e36; } .neetflix-episodes.show { display: block; } .neetflix-episode { display: inline-block; padding: 8px 14px; margin: 4px; background: #3a3a5a; border-radius: 6px; cursor: pointer; font-size: 13px; transition: all 0.2s; } .neetflix-episode:hover { background: #667eea; } .neetflix-episode.playing { background: #667eea; box-shadow: 0 0 10px rgba(102, 126, 234, 0.5); } .neetflix-error { color: #ff6b6b; text-align: center; padding: 20px; } #neetflix-player { display: none; position: fixed; width: 640px; height: 400px; background: #0a0a14; border-radius: 12px; box-shadow: 0 10px 40px rgba(0, 0, 0, 0.8); z-index: 100000; overflow: hidden; min-width: 320px; min-height: 200px; } #neetflix-player.show { display: flex; flex-direction: column; left: 50%; top: 50%; transform: translate(-50%, -50%); } #neetflix-player.show.dragging { transform: translate(-50%, -50%); transition: none; } #neetflix-player.fullscreen { top: 0; left: 0; width: 100%; height: 100%; border-radius: 0; transform: none; } .neetflix-player-header { display: flex; justify-content: space-between; align-items: center; padding: 8px 12px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); cursor: move; flex-shrink: 0; } .neetflix-player-title { color: white; font-size: 14px; font-weight: bold; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 300px; } .neetflix-player-controls { display: flex; gap: 8px; flex-shrink: 0; } .neetflix-player-btn { background: rgba(255, 255, 255, 0.2); border: none; color: white; padding: 4px 10px; border-radius: 4px; cursor: pointer; font-size: 12px; transition: all 0.2s; } .neetflix-player-btn:hover { background: rgba(255, 255, 255, 0.3); } .neetflix-player-btn.active { background: #667eea; } .neetflix-player-close { font-size: 18px; padding: 0 8px; } .neetflix-resize-handle { position: absolute; bottom: 0; right: 0; width: 20px; height: 20px; cursor: se-resize; z-index: 10; } .neetflix-resize-handle::after { content: ''; position: absolute; bottom: 4px; right: 4px; width: 8px; height: 8px; border-right: 2px solid rgba(255,255,255,0.4); border-bottom: 2px solid rgba(255,255,255,0.4); } .neetflix-player-body { flex: 1; position: relative; overflow: hidden; } #neetflix-drag-overlay { position: absolute; top: 0; left: 0; right: 0; bottom: 0; pointer-events: none; z-index: 5; } #neetflix-player-iframe { width: 100%; height: 100%; border: none; background: #000; position: absolute; top: 0; left: 0; z-index: 1; } .neetflix-player-sidebar { display: none; position: absolute; top: 40px; right: 0; width: 200px; height: calc(100% - 40px); background: rgba(26, 26, 46, 0.95); border-left: 1px solid #333; flex-direction: column; z-index: 2; } .neetflix-player-sidebar.show { display: flex; } .neetflix-sidebar-header { display: flex; justify-content: space-between; align-items: center; padding: 12px; border-bottom: 1px solid #333; color: #fff; font-weight: bold; } .neetflix-sidebar-close { background: none; border: none; color: #fff; font-size: 18px; cursor: pointer; } .neetflix-sidebar-body { flex: 1; overflow-y: auto; padding: 10px; } .neetflix-road-label { color: #888; font-size: 12px; margin: 10px 0 5px 0; padding-bottom: 5px; border-bottom: 1px solid #333; } .neetflix-sidebar-episode { display: inline-block; padding: 6px 10px; margin: 3px; background: #2a2a4a; border-radius: 4px; cursor: pointer; font-size: 12px; color: #fff; transition: all 0.2s; } .neetflix-sidebar-episode:hover { background: #3a3a5a; } .neetflix-sidebar-episode.playing { background: #667eea; } `); } function fetchUrl(url, referer) { return new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: 'GET', url: url, headers: { 'User-Agent': getRandomUA(), 'Referer': referer || url, 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8' }, onload: (response) => { if (response.status >= 200 && response.status < 300) { resolve(response.responseText); } else { reject(new Error(`HTTP ${response.status}`)); } }, onerror: (error) => { reject(error); } }); }); } function resolveUrl(url, baseUrl) { if (!url) return url; if (url.startsWith('http')) return url; if (!baseUrl) return url; if (baseUrl.endsWith('/') && url.startsWith('/')) { return baseUrl + url.substring(1); } if (!baseUrl.endsWith('/') && !url.startsWith('/')) { return baseUrl + '/' + url; } return baseUrl + url; } function parseXPath(html, xpath) { const parser = new DOMParser(); const doc = parser.parseFromString(html, 'text/html'); const result = doc.evaluate(xpath, doc, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); const nodes = []; for (let i = 0; i < result.snapshotLength; i++) { nodes.push(result.snapshotItem(i)); } return nodes; } async function searchPlugin(plugin, keyword) { const searchUrl = plugin.searchUrl.replace('@keyword', encodeURIComponent(keyword)); console.log(`[${plugin.name}] Searching: ${searchUrl}`); try { const html = await fetchUrl(searchUrl, plugin.baseUrl); const results = []; const listNodes = parseXPath(html, plugin.searchList); console.log(`[${plugin.name}] Found ${listNodes.length} items`); for (const node of listNodes) { try { let nameXPath = plugin.searchName; let resultXPath = plugin.searchResult; if (nameXPath.startsWith('//') && !nameXPath.startsWith('.//')) { nameXPath = '.' + nameXPath; } if (resultXPath.startsWith('//') && !resultXPath.startsWith('.//')) { resultXPath = '.' + resultXPath; } const nameResult = document.evaluate(nameXPath, node, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null); const urlResult = document.evaluate(resultXPath, node, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null); const nameNode = nameResult.singleNodeValue; const urlNode = urlResult.singleNodeValue; if (nameNode && urlNode) { const name = nameNode.textContent?.trim() || ''; const url = urlNode.getAttribute('href') || ''; if (name && url && url !== '/') { results.push({ name: name, url: resolveUrl(url, plugin.baseUrl) }); } } } catch (e) { console.error(`[${plugin.name}] Parse error:`, e); } } return results; } catch (e) { console.error(`[${plugin.name}] Search error:`, e); return []; } } async function getEpisodes(plugin, pageUrl) { console.log(`[${plugin.name}] Getting episodes from: ${pageUrl}`); try { const html = await fetchUrl(pageUrl, plugin.baseUrl); const roads = []; if (!plugin.chapterRoads) { const road = { name: '播放列表1', episodes: [] }; const epNodes = parseXPath(html, plugin.chapterResult); for (const node of epNodes) { const name = node.textContent?.trim() || ''; const url = node.getAttribute('href') || ''; if (name && url) { road.episodes.push({ name: name, url: resolveUrl(url, plugin.baseUrl) }); } } if (road.episodes.length > 0) { roads.push(road); } } else { const roadNodes = parseXPath(html, plugin.chapterRoads); let count = 1; for (const roadNode of roadNodes) { const road = { name: `播放列表${count}`, episodes: [] }; let epXPath = plugin.chapterResult; if (epXPath.startsWith('//') && !epXPath.startsWith('.//')) { epXPath = '.' + epXPath; } const epResult = document.evaluate(epXPath, roadNode, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); for (let i = 0; i < epResult.snapshotLength; i++) { const node = epResult.snapshotItem(i); const name = node.textContent?.trim() || ''; const url = node.getAttribute('href') || ''; if (name && url) { road.episodes.push({ name: name, url: resolveUrl(url, plugin.baseUrl) }); } } if (road.episodes.length > 0) { roads.push(road); count++; } } } return roads; } catch (e) { console.error(`[${plugin.name}] Get episodes error:`, e); return []; } } function showPlayer(url, title, episode, roads, roadIndex, episodeIndex, pluginName) { const plugin = Plugins.find(p => p.name === pluginName); currentPlayerState = { visible: true, url: url, title: title, episode: episode, roads: roads, currentRoad: roadIndex, currentEpisode: episodeIndex, pluginName: pluginName, danmakuEnabled: currentPlayerState.danmakuEnabled, danmakuOpacity: currentPlayerState.danmakuOpacity, danmakuFontSize: currentPlayerState.danmakuFontSize, danmakuSpeed: currentPlayerState.danmakuSpeed }; const bgmId = getBangumiId(); if (plugin && plugin.useIframe) { const player = document.getElementById('neetflix-player'); const iframe = document.getElementById('neetflix-player-iframe'); const titleEl = player.querySelector('.neetflix-player-title'); GM_setValue(PLAYER_MODE_KEY, 'iframe'); GM_setValue(PLAYER_VIDEO_URL_KEY, url); GM_setValue(PLAYER_TITLE_KEY, title); GM_setValue(PLAYER_EPISODE_KEY, episode); GM_setValue(PLAYER_BGM_ID_KEY, bgmId); titleEl.textContent = `${title} - ${episode}`; player.classList.add('show'); // 显示遮罩层,阻止操作后面的页面 showPlayerOverlay(); // 对于需要代理的网站,使用 GM_xmlhttpRequest 获取内容 if (plugin.needsProxy) { loadIframeWithProxy(iframe, url, plugin.baseUrl); } else { iframe.src = url; } updateSidebar(); updateDanmakuButton(); // 加载弹幕(使用 Bangumi ID 精确匹配) console.log('[NeetFlix] BgmId for danmaku:', bgmId); setTimeout(() => loadDanmakuForTitle(title, episode, bgmId), 2000); } else { GM_setValue(PLAYER_MODE_KEY, 'window'); GM_setValue(PLAYER_VIDEO_URL_KEY, url); GM_setValue(PLAYER_TITLE_KEY, title); GM_setValue(PLAYER_EPISODE_KEY, episode); GM_setValue(PLAYER_BGM_ID_KEY, bgmId); GM_setValue('neetflix_roads', roads); GM_setValue('neetflix_current_road', roadIndex); GM_setValue('neetflix_current_episode', episodeIndex); window.open(url, '_blank'); } } function hidePlayer() { const player = document.getElementById('neetflix-player'); const iframe = document.getElementById('neetflix-player-iframe'); GM_setValue(PLAYER_MODE_KEY, ''); iframe.src = ''; player.classList.remove('show'); player.classList.remove('fullscreen'); // 隐藏遮罩层 hidePlayerOverlay(); currentPlayerState.visible = false; } function showPlayerOverlay() { let overlay = document.getElementById('neetflix-player-overlay'); if (!overlay) { overlay = document.createElement('div'); overlay.id = 'neetflix-player-overlay'; overlay.style.cssText = ` position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.3); z-index: 99999; display: none; `; document.body.appendChild(overlay); } overlay.style.display = 'block'; } function hidePlayerOverlay() { const overlay = document.getElementById('neetflix-player-overlay'); if (overlay) { overlay.style.display = 'none'; } } function loadIframeWithProxy(iframe, url, baseUrl) { console.log('[NeetFlix] Loading iframe with proxy:', url); GM_xmlhttpRequest({ method: 'GET', url: url, headers: { 'User-Agent': getRandomUA(), 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8' }, onload: function(response) { if (response.status >= 200 && response.status < 300) { let html = response.responseText; // 修改相对路径为绝对路径 html = html.replace(/href="\/([^"]*)"/g, `href="${baseUrl}$1"`); html = html.replace(/src="\/([^"]*)"/g, `src="${baseUrl}$1"`); html = html.replace(/href='\/([^']*)'/g, `href='${baseUrl}$1'`); html = html.replace(/src='\/([^']*)'/g, `src='${baseUrl}$1'`); // 添加 base 标签 if (!html.includes(']*)>/i, ``); } // 注入播放器样式、弹幕脚本 const injectedCode = ` `; // 在 前插入 html = html.replace(/<\/head>/i, injectedCode + ''); // 创建 blob URL const blob = new Blob([html], { type: 'text/html;charset=utf-8' }); const blobUrl = URL.createObjectURL(blob); iframe.src = blobUrl; console.log('[NeetFlix] Iframe loaded with proxy'); } else { console.log('[NeetFlix] Proxy load failed:', response.status); // 回退到直接加载 iframe.src = url; } }, onerror: function(err) { console.log('[NeetFlix] Proxy request error:', err); // 回退到直接加载 iframe.src = url; } }); } function updateSidebar() { const sidebar = document.getElementById('neetflix-player-sidebar'); const body = document.getElementById('neetflix-sidebar-body'); if (!currentPlayerState.roads || currentPlayerState.roads.length === 0) { body.innerHTML = '
无选集信息
'; return; } let html = ''; currentPlayerState.roads.forEach((road, roadIdx) => { html += `
${road.name}
`; road.episodes.forEach((ep, epIdx) => { const isPlaying = roadIdx === currentPlayerState.currentRoad && epIdx === currentPlayerState.currentEpisode; html += `${ep.name}`; }); }); body.innerHTML = html; body.querySelectorAll('.neetflix-sidebar-episode').forEach(el => { el.addEventListener('click', function() { const roadIdx = parseInt(this.dataset.road); const epIdx = parseInt(this.dataset.episode); const url = this.dataset.url; currentPlayerState.currentRoad = roadIdx; currentPlayerState.currentEpisode = epIdx; currentPlayerState.url = url; currentPlayerState.episode = currentPlayerState.roads[roadIdx].episodes[epIdx].name; const iframe = document.getElementById('neetflix-player-iframe'); const titleEl = document.querySelector('.neetflix-player-title'); // 设置playerMode让iframe页面注入CSS GM_setValue(PLAYER_MODE_KEY, 'iframe'); GM_setValue(PLAYER_VIDEO_URL_KEY, url); GM_setValue(PLAYER_EPISODE_KEY, currentPlayerState.episode); iframe.src = url; titleEl.textContent = `${currentPlayerState.title} - ${currentPlayerState.episode}`; // 重新加载弹幕 const bgmId = getBangumiId(); setTimeout(() => loadDanmakuForTitle(currentPlayerState.title, currentPlayerState.episode, bgmId), 2000); updateSidebar(); }); }); } function updateDanmakuButton() { const btn = document.getElementById('neetflix-danmaku-btn'); if (currentPlayerState.danmakuEnabled) { btn.classList.add('active'); } else { btn.classList.remove('active'); } } async function copyScreenshotToClipboard(dataUrl) { try { const response = await fetch(dataUrl); const blob = await response.blob(); await navigator.clipboard.write([ new ClipboardItem({ 'image/png': blob }) ]); console.log('[NeetFlix] Screenshot copied to clipboard'); showScreenshotToast('截图已复制到剪贴板'); } catch(err) { console.log('[NeetFlix] Failed to copy to clipboard:', err.message); alert('截图失败:无法复制到剪贴板'); } } function showScreenshotToast(message) { let toast = document.getElementById('neetflix-screenshot-toast'); if (toast) toast.remove(); toast = document.createElement('div'); toast.id = 'neetflix-screenshot-toast'; toast.textContent = message; toast.style.cssText = ` position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: rgba(102, 126, 234, 0.95); color: white; padding: 16px 32px; border-radius: 8px; font-size: 16px; font-weight: bold; z-index: 999999; box-shadow: 0 4px 20px rgba(0,0,0,0.3); animation: neetflixToastFade 2s ease forwards; `; const style = document.createElement('style'); style.textContent = ` @keyframes neetflixToastFade { 0% { opacity: 0; transform: translate(-50%, -50%) scale(0.8); } 20% { opacity: 1; transform: translate(-50%, -50%) scale(1); } 80% { opacity: 1; transform: translate(-50%, -50%) scale(1); } 100% { opacity: 0; transform: translate(-50%, -50%) scale(0.8); } } `; document.head.appendChild(style); document.body.appendChild(toast); setTimeout(() => { toast.remove(); style.remove(); }, 2000); } function takeScreenshotNewWindow(videoSrc, currentTime) { if (!videoSrc) { console.log('[NeetFlix] No video source for new window screenshot'); showScreenshotToast('截图失败:无法获取视频源'); return; } console.log('[NeetFlix] Opening new window for screenshot'); const win = window.open('', '_blank', 'width=800,height=600'); if (!win) { showScreenshotToast('请允许弹出窗口'); return; } win.document.write(` 截图预览 - NeetFlix
正在截图...
`); win.document.close(); } function takeScreenshotWithPrivilege(videoSrc, currentTime) { if (!videoSrc) { console.log('[NeetFlix] No video source for privileged screenshot'); showScreenshotToast('截图失败:无法获取视频源'); return; } console.log('[NeetFlix] Attempting privileged screenshot for:', videoSrc); showScreenshotToast('正在截图...'); GM_xmlhttpRequest({ method: 'GET', url: videoSrc, responseType: 'blob', headers: { 'Accept': 'video/*, */*' }, onload: function(response) { if (response.status >= 200 && response.status < 300 || response.status === 206) { const blob = response.response; const videoUrl = URL.createObjectURL(blob); const video = document.createElement('video'); video.muted = true; let done = false; let timeoutId = null; function cleanup() { if (timeoutId) clearTimeout(timeoutId); URL.revokeObjectURL(videoUrl); } function doScreenshot() { if (done) return; done = true; try { const canvas = document.createElement('canvas'); canvas.width = video.videoWidth || 640; canvas.height = video.videoHeight || 360; const ctx = canvas.getContext('2d'); ctx.drawImage(video, 0, 0, canvas.width, canvas.height); const dataUrl = canvas.toDataURL('image/png'); copyScreenshotToClipboard(dataUrl); cleanup(); console.log('[NeetFlix] Privileged screenshot success'); } catch(err) { console.log('[NeetFlix] Privileged screenshot failed:', err.message); cleanup(); takeScreenshotPreview(videoSrc, currentTime); } } video.addEventListener('loadeddata', function() { if (currentTime && currentTime > 0) { video.currentTime = currentTime; } else { doScreenshot(); } }); video.addEventListener('seeked', doScreenshot); video.addEventListener('canplay', function() { if (!done && video.readyState >= 2) { if (!currentTime || currentTime === 0) { doScreenshot(); } } }); video.addEventListener('error', function(err) { if (done) return; done = true; console.log('[NeetFlix] Video load error:', err); cleanup(); takeScreenshotPreview(videoSrc, currentTime); }); video.src = videoUrl; video.load(); timeoutId = setTimeout(function() { if (!done) { done = true; console.log('[NeetFlix] Screenshot timeout'); cleanup(); takeScreenshotPreview(videoSrc, currentTime); } }, 8000); } else { console.log('[NeetFlix] HTTP error:', response.status); takeScreenshotPreview(videoSrc, currentTime); } }, onerror: function(err) { console.log('[NeetFlix] Request error:', err); takeScreenshotPreview(videoSrc, currentTime); } }); } function takeScreenshotPreview(videoSrc, currentTime) { if (!videoSrc) { console.log('[NeetFlix] No video source for preview screenshot'); showScreenshotToast('截图失败:无法获取视频源'); return; } console.log('[NeetFlix] Creating preview screenshot'); const video = document.createElement('video'); video.src = videoSrc; video.muted = true; video.crossOrigin = 'anonymous'; let done = false; function showPreview() { if (done) return; done = true; const canvas = document.createElement('canvas'); canvas.width = video.videoWidth || 640; canvas.height = video.videoHeight || 360; const ctx = canvas.getContext('2d'); ctx.drawImage(video, 0, 0, canvas.width, canvas.height); canvas.style = 'max-width:100%'; const win = window.open('', '_blank'); if (win) { win.document.title = `截图预览 - ${currentPlayerState.title || 'NeetFlix'}`; win.document.body.style.textAlign = 'center'; win.document.body.style.background = '#000'; win.document.body.appendChild(canvas); } else { showScreenshotToast('请允许弹出窗口'); } } video.addEventListener('loadeddata', () => { if (currentTime > 0) { video.currentTime = currentTime; } else { showPreview(); } }); video.addEventListener('seeked', showPreview); video.addEventListener('error', (e) => { if (done) return; done = true; showScreenshotToast('视频加载失败'); }); video.load(); setTimeout(() => { if (!done) { done = true; showScreenshotToast('截图超时'); } }, 10000); } function initPlayerEvents() { const player = document.getElementById('neetflix-player'); const header = player.querySelector('.neetflix-player-header'); const closeBtn = document.getElementById('neetflix-close-btn'); const openBtn = document.getElementById('neetflix-open-btn'); const episodeBtn = document.getElementById('neetflix-episode-btn'); const danmakuBtn = document.getElementById('neetflix-danmaku-btn'); const sidebar = document.getElementById('neetflix-player-sidebar'); const sidebarClose = sidebar.querySelector('.neetflix-sidebar-close'); closeBtn.addEventListener('click', (e) => { e.stopPropagation(); dragOverlay.style.pointerEvents = 'none'; hidePlayer(); }); openBtn.addEventListener('click', (e) => { e.stopPropagation(); // 关闭dragOverlay dragOverlay.style.pointerEvents = 'none'; if (currentPlayerState.url) { const bgmId = getBangumiId(); GM_setValue(PLAYER_MODE_KEY, 'window'); GM_setValue(PLAYER_VIDEO_URL_KEY, currentPlayerState.url); GM_setValue(PLAYER_TITLE_KEY, currentPlayerState.title); GM_setValue(PLAYER_EPISODE_KEY, currentPlayerState.episode); GM_setValue(PLAYER_BGM_ID_KEY, bgmId); window.open(currentPlayerState.url, '_blank'); } }); danmakuBtn.addEventListener('click', (e) => { e.stopPropagation(); // 关闭dragOverlay dragOverlay.style.pointerEvents = 'none'; currentPlayerState.danmakuEnabled = !currentPlayerState.danmakuEnabled; updateDanmakuButton(); window.postMessage({ type: 'neetflixSetDanmakuEnabled', enabled: currentPlayerState.danmakuEnabled }, '*'); }); const screenshotBtn = document.getElementById('neetflix-screenshot-btn'); screenshotBtn.addEventListener('click', (e) => { e.stopPropagation(); // 关闭dragOverlay dragOverlay.style.pointerEvents = 'none'; // 发送截图命令到iframe console.log('[NeetFlix] Screenshot command received'); var iframe = document.getElementById('neetflix-player-iframe'); if (iframe && iframe.contentWindow) { iframe.contentWindow.postMessage({ type: 'neetflixScreenshot' }, '*'); } }); // 监听截图结果 window.addEventListener('message', function(e) { if (e.data && e.data.type === 'neetflixScreenshotResult') { console.log('[NeetFlix] Screenshot result received'); copyScreenshotToClipboard(e.data.dataUrl); } if (e.data && e.data.type === 'neetflixScreenshotFailed') { console.log('[NeetFlix] Screenshot failed, trying new window mode...'); console.log('[NeetFlix] Video src:', e.data.videoSrc, 'Time:', e.data.currentTime); takeScreenshotNewWindow(e.data.videoSrc, e.data.currentTime); } if (e.data && e.data.type === 'neetflixTryPrivileged') { console.log('[NeetFlix] New window failed, trying privileged mode...'); console.log('[NeetFlix] Video src:', e.data.videoSrc, 'Time:', e.data.currentTime); takeScreenshotWithPrivilege(e.data.videoSrc, e.data.currentTime); } }); episodeBtn.addEventListener('click', (e) => { e.stopPropagation(); // 检查popup是否已显示,如果已显示则关闭 var existingPopup = document.getElementById('neetflix-episode-popup'); if (existingPopup) { existingPopup.remove(); return; } // 关闭dragOverlay,允许点击iframe dragOverlay.style.pointerEvents = 'none'; var roads = currentPlayerState.roads; var currentRoad = currentPlayerState.currentRoad; var currentEp = currentPlayerState.currentEpisode; if (!roads || roads.length === 0) { alert('暂无选集信息'); return; } var road = roads[currentRoad]; if (!road || !road.episodes) { alert('暂无选集信息'); return; } var html = '
'; html += '
' + road.name + '
'; road.episodes.forEach(function(ep, idx) { var isActive = (idx === currentEp) ? 'background:#667eea;' : ''; html += '
' + ep.name + '
'; }); html += '
'; var popup = document.getElementById('neetflix-episode-popup'); if (popup) popup.remove(); popup = document.createElement('div'); popup.id = 'neetflix-episode-popup'; popup.innerHTML = html; popup.style.cssText = 'position:absolute;top:40px;right:50px;width:180px;background:#1a1a2e;border:1px solid #333;border-radius:8px;z-index:100;color:#fff;'; player.appendChild(popup); popup.querySelectorAll('.neetflix-episode-item').forEach(function(el) { el.addEventListener('click', function(e) { e.stopPropagation(); var url = this.dataset.url; var idx = parseInt(this.dataset.idx); var name = this.dataset.name; // 关闭弹窗 popup.remove(); // 更新iframe var iframe = document.getElementById('neetflix-player-iframe'); GM_setValue(PLAYER_MODE_KEY, 'iframe'); iframe.src = url; // 更新状态 currentPlayerState.currentEpisode = idx; currentPlayerState.episode = name; updateSidebar(); // 重新加载弹幕 const bgmId = getBangumiId(); setTimeout(() => { loadDanmakuForTitle(currentPlayerState.title, currentPlayerState.episode, bgmId); }, 2000); }); }); // 点击其他地方关闭 setTimeout(function() { document.addEventListener('click', function closePopup(e) { var popup = document.getElementById('neetflix-episode-popup'); if (popup && !popup.contains(e.target) && e.target !== episodeBtn) { popup.remove(); document.removeEventListener('click', closePopup); } }); }, 10); }); let isDragging = false; let dragOffset = { x: 0, y: 0 }; let isFirstDrag = true; window.playerDragging = false; header.addEventListener('mousedown', (e) => { if (e.target.tagName === 'BUTTON') return; e.preventDefault(); e.stopPropagation(); isDragging = true; isFirstDrag = true; window.playerDragging = true; player.classList.add('dragging'); // 从居中位置切换到绝对位置 const rect = player.getBoundingClientRect(); dragOffset.x = e.clientX - rect.left; dragOffset.y = e.clientY - rect.top; // 移除居中样式,切换到绝对定位 player.style.left = rect.left + 'px'; player.style.top = rect.top + 'px'; player.style.transform = 'none'; }); // 拖动覆盖层 const dragOverlay = document.getElementById('neetflix-drag-overlay'); document.addEventListener('mousemove', (e) => { if (!isDragging) return; // 防止选择文本 e.preventDefault(); // 计算新位置 let x = e.clientX - dragOffset.x; let y = e.clientY - dragOffset.y; // 边界限制(允许拖出屏幕边缘) const maxX = window.innerWidth - 50; const maxY = window.innerHeight - 50; x = Math.max(-player.offsetWidth + 50, Math.min(maxX, x)); y = Math.max(-player.offsetHeight + 50, Math.min(maxY, y)); player.style.left = x + 'px'; player.style.top = y + 'px'; }); const stopDragging = (e) => { if (isDragging) { isDragging = false; window.playerDragging = false; player.classList.remove('dragging'); player.classList.add('dragged'); // 禁用覆盖层 dragOverlay.style.pointerEvents = 'none'; } }; // 拖动时启用覆盖层 header.addEventListener('mousedown', () => { dragOverlay.style.pointerEvents = 'auto'; }); document.addEventListener('mouseup', stopDragging); document.addEventListener('mouseleave', stopDragging); // 自定义resize手柄逻辑(固定左上角缩放) const resizeHandle = document.getElementById('neetflix-resize-handle'); let isResizing = false; let resizeStartX = 0; let resizeStartY = 0; let resizeStartWidth = 0; let resizeStartHeight = 0; resizeHandle.addEventListener('mousedown', (e) => { e.preventDefault(); e.stopPropagation(); isResizing = true; // 启用覆盖层 dragOverlay.style.pointerEvents = 'auto'; // 如果是居中状态,先切换到绝对定位 if (player.classList.contains('show') && !player.style.left) { const rect = player.getBoundingClientRect(); player.style.left = rect.left + 'px'; player.style.top = rect.top + 'px'; player.style.transform = 'none'; } resizeStartX = e.clientX; resizeStartY = e.clientY; resizeStartWidth = player.offsetWidth; resizeStartHeight = player.offsetHeight; }); document.addEventListener('mousemove', (e) => { if (!isResizing) return; const deltaX = e.clientX - resizeStartX; const deltaY = e.clientY - resizeStartY; const newWidth = Math.max(320, resizeStartWidth + deltaX); const newHeight = Math.max(200, resizeStartHeight + deltaY); player.style.width = newWidth + 'px'; player.style.height = newHeight + 'px'; // 触发resize更新canvas resize(); }); document.addEventListener('mouseup', () => { if (isResizing) { isResizing = false; dragOverlay.style.pointerEvents = 'none'; } }); } function renderResults(panel, results, searchKeyword) { const body = panel.querySelector('.neetflix-body'); let html = `
搜索关键词: ${searchKeyword}
`; if (results.length === 0) { html += '
未找到相关资源
'; body.innerHTML = html; return; } for (const { plugin, items } of results) { if (items.length === 0) continue; html += `
${plugin.name} (${items.length}个结果) ${plugin.useIframe ? '' : '[新窗口]'}
`; for (let i = 0; i < items.length; i++) { const item = items[i]; html += `
${item.name}
`; } html += '
'; } body.innerHTML = html; body.querySelectorAll('.neetflix-result-item').forEach(item => { item.addEventListener('click', async function() { const url = this.dataset.url; const pluginName = this.dataset.plugin; const plugin = Plugins.find(p => p.name === pluginName); const episodesDiv = this.nextElementSibling; if (episodesDiv.classList.contains('show')) { episodesDiv.classList.remove('show'); return; } episodesDiv.innerHTML = '
加载中...
'; episodesDiv.classList.add('show'); const roads = await getEpisodes(plugin, url); if (roads.length === 0) { episodesDiv.innerHTML = '
未找到播放链接
'; return; } let epHtml = ''; for (const road of roads) { epHtml += `
${road.name}
`; for (const ep of road.episodes) { epHtml += `${ep.name}`; } } episodesDiv.innerHTML = epHtml; const bangumiName = getBangumiName(); episodesDiv.querySelectorAll('.neetflix-episode').forEach(ep => { ep.addEventListener('click', function() { const playUrl = this.dataset.url; const roadIdx = parseInt(this.dataset.road); const epIdx = parseInt(this.dataset.episode); const epName = this.textContent; const plugin = Plugins.find(p => p.name === pluginName); showPlayer(playUrl, bangumiName, epName, roads, roadIdx, epIdx, pluginName); }); }); }); }); } async function searchAll(keyword) { const results = []; const promises = Plugins.map(async plugin => { const items = await searchPlugin(plugin, keyword); return { plugin, items }; }); const allResults = await Promise.all(promises); return allResults.filter(r => r.items.length > 0); } function init() { addStyles(); const nameSingle = document.querySelector('.nameSingle'); if (!nameSingle) { console.log('Not a bangumi subject page'); return; } const btn = createButton(); nameSingle.appendChild(btn); const panel = createPanel(); document.body.appendChild(panel); const player = createPlayer(); document.body.appendChild(player); initPlayerEvents(); btn.addEventListener('click', async () => { panel.classList.add('show'); const body = panel.querySelector('.neetflix-body'); const name = getBangumiName(); body.innerHTML = `
搜索关键词: ${name}
搜索中...
`; console.log('Searching for:', name); const results = await searchAll(name); renderResults(panel, results, name); }); panel.querySelector('.neetflix-close').addEventListener('click', () => { panel.classList.remove('show'); }); panel.querySelector('.neetflix-overlay').addEventListener('click', () => { panel.classList.remove('show'); }); console.log('Bangumi Direct Play initialized'); console.log('Bangumi Name:', getBangumiName()); console.log('Bangumi ID:', getBangumiId()); } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } })();