// ==UserScript== // @name 武恩赐影视-New // @namespace http://tampermonkey.net/ // @version 5.1.54 // @description 【❤️ 视频自动解析,体会拥有VIP的感觉❤️,适配PC+移动 】功能有:1、支持B站大会员番剧,全网独创自由选择自动解析接口;2、爱奇艺、腾讯、优酷、芒果等全网VIP视频免费解析去广告(免跳出观影特方便) // @author 武恩赐影视 // @icon https://img10.360buyimg.com/ddimg/jfs/t1/410724/18/4303/4155/69c6ea87Fc312bd76/00150c80c86856ea.jpg // @require https://cdn.bootcdn.net/ajax/libs/jquery/3.7.1/jquery.min.js // @tag VIP解析 // @tag 视频破解 // @tag 去广告 // @tag 屏蔽小窗 // @tag 全网通 // @match *://*/* // @connect * // @grant GM_addStyle // @grant GM_setValue // @grant GM_getValue // @grant GM_xmlhttpRequest // @grant GM_registerMenuCommand // @grant GM_unregisterMenuCommand // @run-at document-end // @grant GM_log // @antifeature referral-link 【武恩赐提醒您:此提示为GreasyFork、ScriptCat代码规范要求脚本必须添加,实际使用无任何强制跳转,代码可查,请知悉】 // @license MIT // ==/UserScript== /*    ┏┓   ┏┓   ┏┛┻━━━━━━┛ ┻┓   ┃      ┃   ┃   ━   ┃   ┃ ┳┛ ┗┳  ┃   ┃      ┃   ┃   ┻   ┃   ┃      ┃   ┗━━━┓   ┏━┛Codes are far away from bugs with the animal protecting     ┃   ┃ 神兽保佑,代码无bug     ┃   ┃     ┃   ┗━━━┓     ┃      ┣┓     ┃     ┏━┛     ┗┓┓┏━┳┓┏┛      ┃┫┫ ┃┫┫      ┗┻┛ ┗┻┛ */ (function() { 'use strict'; if (window.top !== window.self) return; console.log('WuEnci-Video-Script: 正在主窗口启动...'); // ================= 配置中心(已全部修复)================= const DEFAULT_APIS = [ { name: "虾米 (推荐)", url: "https://jx.xmflv.com/?url=" }, { name: "TXNP (推荐)", url: "https://bfq.txnp.cn/player?url=" }, { name: "SUPER (推荐)", url: "https://super.playr.top/?url=" }, { name: "DMFLV (推荐)", url: "https://jx.dmflv.cc/?url=" }, { name: "M1907 (推荐)", url: "https://video.isyour.love/html/html2/jiexi/m1907.html?m1907jx=" }, { name: "优酷视频", url: "http://117.24.7.9:8811/bbyk.php/?url=" }, { name: "爱奇艺", url: "http://117.24.7.9:8811/bbiqy.php/?url=" }, { name: "芒果视频", url: "http://117.24.7.9:8811/mg.php?url=" }, { name: "腾讯视频", url: "http://117.24.7.9:8811/bbqq.php/?url=" }, { name: "PP视频", url: "http://117.24.7.9:8811/bbjuli.php?url=" }, { name: "搜狐视频", url: "http://117.24.7.9:8811/bt.php?url=" }, { name: "爱奇艺VIP", url: "http://117.24.7.9:8811/ys/viqiyi1.php?token=e0a00333191d1ddabe47e0545100da84&v=" }, { name: "万播VIP", url: "http://117.24.7.9:8811/bofangqi/xinluan/?url=" }, { name: "万播SVIP", url: "http://117.24.7.9:8866/mayi/?url=" }, { name: "虾米2 (备用)", url: "https://jx.xmflv.cc/?url=" }, { name: "纯净解析", url: "https://im1907.top/?jx=" }, { name: "咸鱼云", url: "https://jx.xymp4.cc/?url=" }, { name: "爱豆", url: "https://jx.aidouer.net/?url=" }, { name: "CHok", url: "https://www.gai4.com/?url=" }, { name: "OK解析", url: "https://okjx.cc/?url=" }, { name: "思古3", url: "https://jsap.attakids.com/?url=" }, { name: "YT", url: "https://jx.yangtu.top/?url=" }, { name: "789", url: "https://jiexi.789jiexi.icu:4433/?url=" }, { name: "HLS", url: "https://jx.hls.one/?url=" }, { name: "极速", url: "https://jx.2s0.cn/player/?url=" }, { name: "Play", url: "https://jx.playerjy.com/?url=" }, { name: "冰豆", url: "https://bd.jx.cn/?url=" }, { name: "百域", url: "https://jx.618g.com/?url=" }, { name: "CK", url: "https://www.ckplayer.vip/jiexi/?url=" }, { name: "ckmov", url: "https://www.ckmov.vip/api.php?url=" }, { name: "H8", url: "https://www.h8jx.com/jiexi.php?url=" }, { name: "通用", url: "https://ckmov.ccyjjd.com/ckmov/?url=" }, { name: "MAO", url: "https://www.mtosz.com/m3u8.php?url=" }, { name: "M3U8", url: "https://jx.m3u8.tv/jiexi/?url=" }, { name: "诺讯", url: "https://www.nxflv.com/?url=" }, { name: "PM", url: "https://www.playm3u8.cn/jiexi.php?url=" }, { name: "4K", url: "https://jx.4kdv.com/?url=" }, { name: "8090", url: "https://www.8090g.cn/?url=" }, { name: "剖元", url: "https://www.pouyun.com/?url=" }, { name: "夜幕", url: "https://www.yemu.xyz/?url=" }, { name: "七七云", url: "https://jx.77flv.cc/?url=" }, { name: "芒果TV1", url: "https://video.isyour.love/player/getplayer?url=" }, { name: "七哥", url: "https://jx.nnxv.cn/tv.php?url=" }, { name: "云析(带选集)", url: "https://jx.yparse.com/index.php?url=" }, { name: "优质A", url: "https://json.fongmi.cc/web?url=" }, { name: "937解析", url: "https://bfq.937auth.vip?url=" }, { name: "jsonplayer", url: "https://jx.777jiexi.com/player/?url=" }, { name: "云解析", url: "https://yparse.ik9.cc/index.php?url=" } ]; const DEFAULT_CONFIG = { autoSnap: true, openInNewTab: true, embedMode: true, autoSync: true, antiMini: true, idleOpacity: true, shortcut: true, darkMode: 'auto', posPct: { x: 2, y: 30 }, lastApi: '', apis: DEFAULT_APIS, agreedDisclaimer: true, version: '5.1.40' }; const State = { get: () => { try { const saved = GM_getValue('config', {}); if (!saved.posPct || isNaN(saved.posPct.x)) saved.posPct = { x: 2, y: 30 }; saved.agreedDisclaimer = true; return { ...DEFAULT_CONFIG, ...saved }; } catch (e) { return DEFAULT_CONFIG; } }, set: (newConfig) => GM_setValue('config', newConfig), update: (key, value) => { const config = State.get(); config[key] = value; State.set(config); return config; } }; const CONTAINER_ID = 'WuEnci-Video'; let container = document.getElementById(CONTAINER_ID); if (!container) { container = document.createElement('div'); container.id = CONTAINER_ID; (document.documentElement || document.body).appendChild(container); } const shadow = container.attachShadow({ mode: 'open' }); const getStyles = (isDark) => ` :host { --primary: #3B82F6; --primary-grad: linear-gradient(135deg, #3B82F6 0%, #7C3AED 100%); --warn: #EF4444; --success: #10B981; --warning: #F59E0B; --bg-blur: ${isDark ? 'rgba(30,30,30,0.95)' : 'rgba(255,255,255,0.95)'}; --text-main: ${isDark ? '#F3F4F6' : '#1F2937'}; --text-sub: ${isDark ? '#9CA3AF' : '#6B7280'}; --border: ${isDark ? 'rgba(255,255,255,0.1)' : 'rgba(255,255,255,0.6)'}; --hover-bg: ${isDark ? 'rgba(255,255,255,0.08)' : 'rgba(0,0,0,0.04)'}; position: fixed; top:0; left:0; z-index:2147483647; font-family: system-ui, -apple-system, sans-serif; font-size:14px; pointer-events:none; color:var(--text-main); width:100vw; height:100vh; overflow:visible; display:block !important; } .wrapper { position:absolute; width:50px; height:50px; pointer-events:auto; display:block !important; z-index:2147483647; top:30%; left:10px; } .float-ball { width:50px; height:50px; background:var(--primary-grad); border-radius:50%; box-shadow:0 4px 15px rgba(59,130,246,0.4); cursor:pointer; display:flex; justify-content:center; align-items:center; transition:transform 0.2s, opacity 0.3s, filter 0.3s; border:2px solid rgba(255,255,255,0.2); backdrop-filter:blur(4px); } .float-ball:hover { transform:scale(1.1); } .float-ball:active { transform:scale(0.95); } .float-ball.idle { opacity:0.5; filter:grayscale(0.5); transform:scale(0.9); } .ball-icon { width:24px; height:24px; fill:white; transition:transform 0.3s; } .wrapper.active .ball-icon { transform:rotate(90deg); } .menu-panel { position:absolute; top:0; width:300px; background:var(--bg-blur); backdrop-filter:blur(20px); -webkit-backdrop-filter:blur(20px); border-radius:18px; box-shadow:0 10px 40px rgba(0,0,0,0.2); border:1px solid var(--border); display:none; flex-direction:column; overflow:hidden; animation:menuIn 0.25s cubic-bezier(0.2,0.8,0.2,1); } @keyframes menuIn { from{opacity:0; transform:scale(0.95) translateY(-10px);} to{opacity:1; transform:scale(1) translateY(0);} } .pos-right .menu-panel { right:60px; left:auto; transform-origin:top right; } .pos-left .menu-panel { left:60px; right:auto; transform-origin:top left; } .menu-header { padding:16px; display:flex; justify-content:space-between; align-items:center; border-bottom:1px solid var(--hover-bg); } .app-name { font-weight:800; font-size:15px; background:var(--primary-grad); -webkit-background-clip:text; -webkit-text-fill-color:transparent; } .tools { display:flex; gap:4px; } .icon-btn { width:28px; height:28px; border-radius:8px; cursor:pointer; display:flex; align-items:center; justify-content:center; transition:all 0.2s; fill:var(--text-sub); } .icon-btn:hover { background:var(--hover-bg); fill:var(--primary); } .icon-btn.testing { animation:spin 1s linear infinite; fill:var(--primary); pointer-events:none; } @keyframes spin { 100%{transform:rotate(360deg);} } .content-view { display:none; padding:15px; max-height:360px; overflow-y:auto; } .content-view.active { display:block; animation:fadeIn 0.2s; } .alert-box { margin:10px 12px 0 12px; padding:10px; background:${isDark ? 'rgba(239,68,68,0.15)' : '#FEF2F2'}; border:1px solid ${isDark ? 'rgba(239,68,68,0.3)' : '#FECACA'}; border-radius:10px; color:var(--warn); font-size:12px; display:flex; gap:8px; align-items:start; cursor:pointer; line-height:1.4; } .api-grid { display:grid; grid-template-columns:repeat(2,1fr); gap:10px; } .api-item { padding:12px 8px; border-radius:10px; background:var(--hover-bg); border:1px solid transparent; font-size:13px; text-align:center; cursor:pointer; transition:all 0.2s; position:relative; color:var(--text-main) !important; font-weight:500; display:flex; flex-direction:column; gap:4px; } .api-item:hover { background:transparent; border-color:var(--primary); color:var(--primary) !important; transform:translateY(-2px); } .api-item.current { background:var(--primary-grad); color:white !important; } .ping-tag { font-size:10px; font-weight:normal; opacity:0.8; } .ping-green { color:var(--success); } .ping-yellow { color:var(--warning); } .ping-red { color:var(--warn); } .current .ping-tag { color:rgba(255,255,255,0.9); } .footer { padding:10px; text-align:center; border-top:1px solid var(--hover-bg); font-size:11px; color:var(--text-sub); } .link { color:var(--primary); text-decoration:none; display:inline-flex; align-items:center; cursor:pointer; transition:0.2s; } .link:hover { opacity:0.8; } .set-row { display:flex; justify-content:space-between; align-items:center; padding:12px 0; border-bottom:1px solid var(--hover-bg); } .toggle { width:42px; height:22px; background:${isDark ? '#4B5563' : '#E5E7EB'}; border-radius:20px; position:relative; cursor:pointer; transition:0.3s; } .toggle::after { content:''; position:absolute; top:3px; left:3px; width:16px; height:16px; background:white; border-radius:50%; transition:0.3s; box-shadow:0 1px 2px rgba(0,0,0,0.2); } .toggle.checked { background:var(--primary); } .toggle.checked::after { transform:translateX(20px); } .mode-switch { display:flex; background:var(--hover-bg); padding:3px; border-radius:8px; } .mode-btn { flex:1; text-align:center; padding:4px; font-size:12px; cursor:pointer; border-radius:6px; color:var(--text-sub); transition:0.2s; } .mode-btn.active { background:var(--bg-blur); color:var(--primary); font-weight:bold; } .toast { position:fixed; top:40%; left:50%; transform:translate(-50%,-50%); background:rgba(0,0,0,0.9); color:white; padding:12px 24px; border-radius:50px; opacity:0; pointer-events:none; transition:0.3s; z-index:2147483647; } .toast.show { opacity:1; transform:translate(-50%,-60%); } `; const styleElement = document.createElement('style'); shadow.appendChild(styleElement); function updateTheme() { const config = State.get(); let isDark = config.darkMode === 'auto' ? (window.matchMedia && window.matchMedia('(prefers-color-scheme:dark)').matches) : (config.darkMode === 'dark'); styleElement.textContent = getStyles(isDark); } const ICONS = { play: ``, close: ``, gear: ``, edit: ``, back: ``, cross: ``, speed: `` }; const wrapper = document.createElement('div'); wrapper.className = 'wrapper'; const ball = document.createElement('div'); ball.className = 'float-ball'; ball.innerHTML = ICONS.play; ball.title = 'VIP解析'; const menu = document.createElement('div'); menu.className = 'menu-panel'; const toast = document.createElement('div'); toast.className = 'toast'; GM_registerMenuCommand("♻️ 重置悬浮球位置", () => { State.update('posPct', { x: 2, y: 30 }); location.reload(); }); GM_registerMenuCommand("🔨 强制渲染 UI", () => { launchUI(); showToast('已强制重新渲染'); }); function init() { console.log('WuEnci-VIP: Init...'); launchUI(); if (State.get().antiMini) injectAntiMiniStyles(); setupUrlListener(); } function testLatency(url) { return new Promise(resolve => { const start = Date.now(); GM_xmlhttpRequest({ method: 'GET', url: url, timeout: 3000, headers: { 'Cache-Control': 'no-cache' }, onload: (res) => { const time = Date.now() - start; if (res.status >= 200 && res.status < 400) resolve(time); else resolve(-1); }, onerror: () => resolve(-1), ontimeout: () => resolve(-1) }); }); } async function runSpeedTest() { const btn = menu.querySelector('#btn-speed'); if (btn.classList.contains('testing')) return; btn.classList.add('testing'); showToast('🚀 开始智能测速...'); const items = menu.querySelectorAll('.api-item'); const tasks = Array.from(items).map(async (item) => { const url = item.dataset.url; const tag = item.querySelector('.ping-tag'); tag.textContent = '检测中...'; tag.className = 'ping-tag'; const testUrl = url.includes('?') ? url : (url + '?'); const ms = await testLatency(testUrl); if (ms === -1) { tag.textContent = '超时/故障'; tag.classList.add('ping-red'); } else { tag.textContent = ms + 'ms'; if (ms < 500) tag.classList.add('ping-green'); else if (ms < 1500) tag.classList.add('ping-yellow'); else tag.classList.add('ping-red'); } }); await Promise.all(tasks); btn.classList.remove('testing'); showToast('✅ 测速完成'); } function safeOpen(baseUrl, name) { const config = State.update('lastApi', baseUrl); const targetUrl = baseUrl + encodeURIComponent(location.href); if (config.embedMode) { showToast(`📺 正在覆盖解析: ${name}`); toggleMenu(false); setTimeout(() => coverPlayerGlobal(targetUrl), 200); } else { showToast(`🚀 正在新标签页解析: ${name}`); setTimeout(() => { config.openInNewTab ? window.open(targetUrl, '_blank') : (location.href = targetUrl); toggleMenu(false); }, 500); } } function findVideoElement() { const video = document.querySelector('video'); if (video) return video; const selectors = ['#player','.player-container','#mod_player','#bilibili-player','.iqp-player','#mgtv-player-wrap']; for (let sel of selectors) { const el = document.querySelector(sel); if (el && el.clientWidth > 100) return el; } return null; } function injectAntiMiniStyles() { if (!document.getElementById('gemini-anti-mini-style')) { const style = document.createElement('style'); style.id = 'gemini-anti-mini-style'; style.textContent = ` .txp_player_mini,.txp_mini_player,.txp-pip-player,.txp-player-mini-container{display:none !important;} .bilibili-player-video-float,.mini-player,.bpx-player-container[data-screen="mini"]{display:none !important;} .mini-player,.yp-player-mini,.iqp-player-mini,.iqp-player.mini,.pip-mode,.float-player{display:none !important;} .mgtv-player-mini,.mgtv-pip-player,.mini-player-mask{display:none !important;} #mini-player,.player-mini{display:none !important;} `; document.head.appendChild(style); } } function removeAntiMiniStyles() { const s = document.getElementById('gemini-anti-mini-style'); if (s) s.remove(); } function coverPlayerGlobal(url) { const targetEl = findVideoElement(); if (!targetEl) { showToast('❌ 未找到播放器,尝试弹窗播放'); window.open(url, '_blank'); return; } if (State.get().antiMini) injectAntiMiniStyles(); if (window.muteInterval) clearInterval(window.muteInterval); window.muteInterval = setInterval(() => { document.querySelectorAll('video').forEach(v => { if (!v.muted || !v.paused) { v.muted = true; v.pause(); v.volume = 0; } }); }, 1500); let overlayId = 'gemini-global-overlay'; let overlay = document.getElementById(overlayId); if (!overlay) { overlay = document.createElement('div'); overlay.id = overlayId; overlay.style.cssText = `position:absolute; z-index:2147483646; background:#000; display:flex; flex-direction:column; overflow:hidden;`; const toolbar = document.createElement('div'); toolbar.style.cssText = `height:0; position:absolute; top:0; right:0; z-index:2147483648;`; toolbar.innerHTML = `
✕ 退出
`; toolbar.querySelector('#btn-close-overlay').onclick = closeOverlay; overlay.appendChild(toolbar); document.body.appendChild(overlay); } const oldFrame = overlay.querySelector('iframe'); if (oldFrame) oldFrame.remove(); // ================= 终极修复:自动处理 HTTP/HTTPS,不拦截、不报错 ================= if (url.startsWith('http://')) { // HTTP 接口 → 自动新标签页打开,彻底绕过浏览器混合内容拦截 window.open(url, '_blank'); showToast('🚀 HTTP接口已自动新标签播放(不拦截)'); return; } // HTTPS 接口 → 正常页内覆盖播放 const iframe = document.createElement('iframe'); iframe.src = url; iframe.style.cssText = `width:100%; height:100%; border:none; background:#000;`; iframe.allow = "fullscreen; autoplay"; iframe.referrerPolicy = 'no-referrer'; iframe.sandbox = 'allow-same-origin allow-scripts allow-forms allow-presentation'; overlay.appendChild(iframe); updateOverlayPosition(targetEl, overlay); if (window.overlayUpdateTimer) clearInterval(window.overlayUpdateTimer); window.overlayUpdateTimer = setInterval(() => updateOverlayPosition(targetEl, overlay), 300); showToast('✅ 解析成功!已屏蔽原视频'); } function updateOverlayPosition(targetEl, overlay) { if (!targetEl || !overlay || !document.body.contains(targetEl)) { closeOverlay(); return; } const rect = targetEl.getBoundingClientRect(); if (rect.width > 0 && rect.height > 0) { overlay.style.display = 'flex'; const top = rect.top + window.scrollY; const left = rect.left + window.scrollX; overlay.style.top = top + 'px'; overlay.style.left = left + 'px'; overlay.style.width = rect.width + 'px'; overlay.style.height = rect.height + 'px'; } } function closeOverlay() { const overlay = document.getElementById('gemini-global-overlay'); if (overlay) overlay.remove(); if (window.muteInterval) clearInterval(window.muteInterval); if (window.overlayUpdateTimer) clearInterval(window.overlayUpdateTimer); } function setupUrlListener() { let lastUrl = location.href; setInterval(() => { if (location.href !== lastUrl) { lastUrl = location.href; const config = State.get(); if (config.autoSync && config.embedMode && document.getElementById('gemini-global-overlay')) { showToast('🔄 正在同步新剧集...'); coverPlayerGlobal(location.href); } } }, 1000); document.addEventListener('keydown', (e) => { if (e.key === 'Escape') closeOverlay(); }); } function launchUI() { updateTheme(); wrapper.style.display = 'block'; renderPos(); renderMenu(); document.addEventListener('keydown', (e) => { if (State.get().shortcut && e.altKey && e.code === 'KeyQ') toggleMenu(); }); window.addEventListener('resize', () => renderPos()); } function renderMenu() { const config = State.get(); menu.innerHTML = `
🚨
安全提醒:切勿相信视频内任何广告!
${config.apis.map(api => `
${api.name || '未知接口'} 未检测
`).join('')}
${config.apis.map((api, idx) => `
${api.name || '未知'}
`).join('')}
📖 使用说明 点击打开
⚠️ 使用协议 点击打开
`; bindEvents(); } function bindEvents() { const switchView = (id) => { menu.querySelectorAll('.content-view').forEach(v => v.classList.remove('active')); menu.querySelector(`#${id}`).classList.add('active'); menu.querySelector('#btn-back')?.remove(); if (id !== 'view-home') { const b = document.createElement('div'); b.innerHTML = ICONS.back; b.className = 'icon-btn'; b.id = 'btn-back'; b.onclick = () => renderMenu(); menu.querySelector('.menu-header').prepend(b); } }; menu.querySelector('#alert-bar').onclick = () => alert('请勿相信视频内任何广告!'); menu.querySelector('#btn-speed').onclick = () => runSpeedTest(); menu.querySelector('#btn-edit').onclick = () => switchView('view-edit'); menu.querySelector('#btn-set').onclick = () => switchView('view-set'); menu.querySelector('#btn-close').onclick = () => toggleMenu(false); menu.querySelectorAll('.api-item').forEach(i => i.onclick = (e) => { e.stopPropagation(); safeOpen(i.dataset.url, i.dataset.name); }); menu.querySelectorAll('.mode-btn').forEach(b => b.onclick = () => { State.update('darkMode', b.dataset.mode); updateTheme(); renderMenu(); }); menu.querySelectorAll('.toggle').forEach(t => t.onclick = () => { const k = t.dataset.key; const newVal = !t.classList.contains('checked'); State.update(k, newVal); t.classList.toggle('checked'); if (k === 'idleOpacity') resetIdle(); if (k === 'antiMini') { newVal ? injectAntiMiniStyles() : removeAntiMiniStyles(); showToast(newVal ? '🚫 已屏蔽迷你小窗' : '👀 已恢复迷你小窗'); } }); menu.querySelector('#btn-add-api')?.addEventListener('click', () => { const n = menu.querySelector('#new-name').value, u = menu.querySelector('#new-url').value; if (n && u) { const c = State.get(); c.apis.push({ name: n, url: u }); State.set(c); renderMenu(); menu.querySelector('#btn-edit').click(); } }); menu.querySelectorAll('.btn-del').forEach(b => b.onclick = () => { const c = State.get(); c.apis.splice(b.dataset.idx, 1); State.set(c); renderMenu(); menu.querySelector('#btn-edit').click(); }); } function showToast(msg) { toast.textContent = msg; toast.classList.add('show'); setTimeout(() => toast.classList.remove('show'), 2000); } let isMenuOpen = false, idleTimer; function renderPos() { const c = State.get(), w = window.innerWidth, h = window.innerHeight; if (!c.posPct || isNaN(c.posPct.x) || isNaN(c.posPct.y)) c.posPct = { x: 2, y: 30 }; let x = (c.posPct.x / 100) * w, y = (c.posPct.y / 100) * h; x = Math.min(Math.max(0, x), w - 50); y = Math.min(Math.max(0, y), h - 50); if (isNaN(x)) x = 20; if (isNaN(y)) y = 200; wrapper.style.left = x + 'px'; wrapper.style.top = y + 'px'; wrapper.style.display = 'block'; if (x > w / 2) { wrapper.classList.add('pos-right'); wrapper.classList.remove('pos-left'); } else { wrapper.classList.add('pos-left'); wrapper.classList.remove('pos-right'); } } function toggleMenu(s) { isMenuOpen = s !== undefined ? s : !isMenuOpen; if (isMenuOpen) { updateTheme(); renderMenu(); renderPos(); menu.style.display = 'flex'; ball.innerHTML = ICONS.close; ball.style.opacity = '1'; ball.style.transform = 'scale(0.9)'; } else { menu.style.display = 'none'; ball.innerHTML = ICONS.play; ball.style.transform = 'scale(1)'; resetIdle(); } } function resetIdle() { ball.classList.remove('idle'); clearTimeout(idleTimer); if (State.get().idleOpacity && !isMenuOpen) idleTimer = setTimeout(() => ball.classList.add('idle'), 3000); } let isDrag = false, sX, sY, sL, sT, moved = false; const onMove = (e) => { if (!isDrag) return; e.preventDefault(); const c = e.touches ? e.touches[0] : e, dx = c.clientX - sX, dy = c.clientY - sY; if (Math.abs(dx) > 2 || Math.abs(dy) > 2) moved = true; if (moved) { let nx = sL + dx, ny = sT + dy; wrapper.style.left = Math.min(Math.max(0, nx), window.innerWidth - 50) + 'px'; wrapper.style.top = Math.min(Math.max(0, ny), window.innerHeight - 50) + 'px'; } }; const onEnd = () => { if (!isDrag) return; isDrag = false; ball.style.cursor = 'pointer'; if (!moved) { toggleMenu(); return; } wrapper.style.transition = 'all 0.4s cubic-bezier(0.18,0.89,0.32,1.28)'; const r = wrapper.getBoundingClientRect(), w = window.innerWidth, h = window.innerHeight; let fx = r.left; if (State.get().autoSnap) fx = (fx + 25 < w / 2) ? 10 : (w - 60); wrapper.style.left = fx + 'px'; State.update('posPct', { x: (fx / w) * 100, y: (r.top / h) * 100 }); setTimeout(renderPos, 410); }; const onStart = (e) => { if ((e.type === 'mousedown' && e.button !== 0) || (e.target !== ball && !ball.contains(e.target))) return; const c = e.touches ? e.touches[0] : e; sX = c.clientX; sY = c.clientY; const r = wrapper.getBoundingClientRect(); sL = r.left; sT = r.top; moved = false; isDrag = true; ball.style.cursor = 'grabbing'; wrapper.style.transition = 'none'; resetIdle(); }; ball.addEventListener('mousedown', onStart); ball.addEventListener('touchstart', onStart, { passive: false }); document.addEventListener('mousemove', onMove, { passive: false }); document.addEventListener('touchmove', onMove, { passive: false }); document.addEventListener('mouseup', onEnd); document.addEventListener('touchend', onEnd); ball.addEventListener('mouseup', e => { if (e.button === 1) { e.preventDefault(); const c = State.get(); c.lastApi ? safeOpen(c.lastApi, '上次接口') : toggleMenu(true); } }); document.addEventListener('click', e => { if (isMenuOpen && !container.contains(e.target)) toggleMenu(false); }); wrapper.appendChild(ball); wrapper.appendChild(menu); wrapper.appendChild(toast); shadow.appendChild(wrapper); init(); })();