// ==UserScript== // @name 常看VIP视频全网解析 (Beta版) // @namespace https://scriptcat.org/zh-CN/script-show-page/6457 // @version 1.0.0.1 // @description 💎VIP视频解析 | 🎬爱奇艺解析 | 🔍腾讯VIP去广 | 📺优酷独播解锁 | ⚡芒果TV大会员 | 🎥B站番剧解析 | 🔗搜狐视频直连 | ✦乐视超清播放 | ◆PPTV聚力解析 | ●咪咕体育直播 | ★西瓜VIP解锁 | 🔍抖音短剧解析 | 🎬快手短视频解析 | 📺1905电影网 | ⚡AcFun弹幕解析 | 🔗土豆/风行/暴风兼容 | ✦全网影视通解 | ◆4K蓝光画质 | ●HDR杜比音效 | ★智能线路优选 | 💎免登录免费看 | 🎞️热播剧/最新电影/独家综艺/国漫日番 // @icon data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMDAgMTAwIj48Y2lyY2xlIGN4PSI1MCIgY3k9IjUwIiByPSI1MCIgZmlsbD0idXJsKCNncmFkKSIvPjxkZWZzPjxsaW5lYXJHcmFkaWVudCBpZD0iZ3JhZCIgeDE9IjAlIiB5MT0iMCUiIHgyPSIxMDAlIiB5Mj0iMTAwJSI+PHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iIzAwMzM5OSIvPjxzdG9wIG9mZnNldD0iMTAwJSIgc3RvcC1jb2xvcj0iIzAwNjZjYyIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjx0ZXh0IHg9IjUwIiB5PSI3MCIgZm9udC1zaXplPSI1MCIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZmlsbD0id2hpdGUiIGZvbnQtZmFtaWx5PSJzYW5zLXNlcmlmIiBmb250LXdlaWdodD0ibjkwMCI+VklQPC90ZXh0Pjwvc3ZnPg== // @author lsym // @noframes // @match *://*/* // @grant GM_addStyle // @grant GM_xmlhttpRequest // @grant GM_setValue // @grant GM_getValue // @grant GM_registerMenuCommand // @run-at document-end // @connect * // @antifeature piracy // @license MIT // ==/UserScript== (function() { if (window.hasInitVipScript) return; window.hasInitVipScript = true; 'use strict'; const CONFIG = { API_TIMEOUT: 6000, STUCK_CHECK_TIMEOUT: 5000, SEARCH_CONCURRENCY: 8, SMART_SORTING: true, AUTOPLAY_NEXT_DELAY: 100, PANEL_LEAVE_CLOSE_DELAY: 2000, PANEL_EPISODE_GRACE_PERIOD: 2000, SPA_DEBOUNCE: 500, STORAGE_KEY_ICON_POSITION: 'tm_icon_position_v6', VIDEO_URL_PATTERNS: [/iqiyi\.com\/[vwa]_/, /iq\.com\/play\//, /youku\.com\/v_show\/id_/, /v\.youku\.com\/v_show\/id_/, /v\.qq\.com\/(x\/cover|x\/page|tv)\//, /mgtv\.com\/b\//, /mgtv\.com\/s\//, /bilibili\.com\/(video|bangumi\/play)\//, /b23\.tv\//, /le\.com\/ptv\/vplay\//, /tv\.sohu\.com\/v\//, /film\.sohu\.com\/album\//, /pptv\.com\/show\//, /acfun\.cn\/v\/ac/, /1905\.com\/play\//, /ixigua\.com\/video\//, /ixigua\.com\/play\//, /tudou\.com\/(listplay|albumplay|programs\/view)\//, /fun\.tv\/vod-play\//, /baofeng\.com\/play\//, /migumovie\.hcs\.cmvideo\.cn\/movie/, /miguvideo\.com\/detail/, /douyin\.com\/video\//, /kuaishou\.com\/short-video\//, /hanju\.koudaibaobao\.com\//, /maiduidui\.com\/play\//, /rrsp\.tv\/play\//, /vas\.hiaiabc\.com\/play/], MESSAGES: { VIDEO_ENDED: 'tm_video_ended', PLAY_SUCCESS: 'tm_play_success', PLAY_ERROR: 'tm_play_error' }, SELECTORS: { PLAYER_ELEMENTS: ['.txp_player_root', '#player-container', '#player', '.container-player', '#tenvideo_player', '#sohuplayer', '#flashbox', '.iqp-player', '#bilibili-player', '.bpx-player-container', '#mgtv-player-wrap', '#le_player', '#player_swf', '#pp-player', '#ACPlayer', '#video-player', '#xigua-player', '.video-area', '.player-container'], QUICK_TITLE: ['meta[property="og:title"]', 'h1', '.video-title', '.title', '.vod_title'], PRECISE_TITLE: { 'iqiyi.com': '.qy-episode-item[class*="is-active"] a, .album-list .is-active .title-content, #text[style*="IQYHT-Bold"]', 'youku.com': '.anthology-wrap li.active span', 'v.qq.com': '.episode-item--select, .playlist-item--current, [class*="selected"] [class*="episode-item-text"], [class*="episode-item"][class*="selected"] .episode-item-text, [class*="episode-item"][class*="current"] .episode-item-text, [class*="episode-item"][class*="active"] .episode-item-text, [class*="numberListItem_select"] [class*="numberListItem_title"], [class*="ele_select"] .episode-item-text', 'bilibili.com': '[class*="numberListItem_select"] [class*="numberListItem_title"], .ep-list-item.on .ep-item-title, [class*="episode_list"] [class*="selected"]', 'mgtv.com': '.episode-list .current a', 'sohu.com': '.player-album-list .on a', 'le.com': '.js-episode-item.on', 'pptv.com': '.episode-list .current', 'acfun.cn': '.active .title-wenzi' }, PRECISE_MAIN_TITLE: { 'qq.com': '.intro-title[title]', 'iqiyi.com': '[data-ai-entity="主标题"]', 'iq.com': '[data-ai-entity="主标题"]', 'youku.com': '[data-spm-anchor-id*="introduction"] .title, .title[style*="max-width"]', 'bilibili.com': '[class*="mediaTitle"][title], [class*="mediaTitle"]', 'b23.tv': '[class*="mediaTitle"][title], [class*="mediaTitle"]' } }, // 电影/电视剧判定关键词列表 MOVIE_KEYWORDS: /^(HD|超清|高清|正片|国语|HD国语|720P|1080P|蓝光|4K|BD|TC|TS|DVD|抢先|高清版|HD高清|国语高清|HD中字)$/i, MOVIE_PRIORITY: ['蓝光','4K','1080P','超清','HD国语','HD','国语','高清','720P','BD','正片','HD高清','国语高清','HD中字','TC','TS','DVD','抢先','高清版'] }; const ApiStats = { _p: {}, _t: null, _flush() { const p = this._p; this._p = {}; this._t = null; for (const [k, v] of Object.entries(p)) GM_setValue(`api_stats_${k}`, v); }, _schedule() { if (!this._t) this._t = setTimeout(() => this._flush(), 2000); }, get(n) { return this._p[n] || GM_getValue(`api_stats_${n}`) || { s: 0, f: 0, l: 0, r: 0 }; }, ok(n, lat) { const s = this.get(n); s.s++; s.l += lat; s.r++; this._p[n] = s; this._schedule(); }, fail(n) { const s = this.get(n); s.f++; s.r++; this._p[n] = s; this._schedule(); }, score(s) { if (s.r < 3) return 1000; const sr = s.s / s.r; if (sr < 0.5) return -1000; return sr * 10000 - (s.s ? s.l / s.s : CONFIG.API_TIMEOUT); } }; const RAW_APIS = [ { n: "真逗", u: atob("aHR0cDovL3lzLnJlYWxkb3UuY24vYXBpLnBocC9hcHAv") }, { n: "豪华", u: atob("aHR0cHM6Ly9oaHp5YXBpLmNvbS9hcGkucGhwL3Byb3ZpZGUvdm9kLw==") }, { n: "金鹰2", u: atob("aHR0cHM6Ly9qeXp5YXBpLmNvbS9wcm92aWRlL3ZvZC8=") }, { n: "AI", u: atob("aHR0cDovLzEyNC4yMjIuMTE2LjUvbWFjb3Mvc2V2ZW4vYXBpLnBocC9wcm92aWRlL3ZvZC8=") }, { n: "索尼", u: atob("aHR0cHM6Ly9zdW9uaWFwaS5jb20vYXBpLnBocC9wcm92aWRlL3ZvZC8=") }, { n: "无尽", u: atob("aHR0cHM6Ly9hcGkud3VqaW5hcGkubWUvYXBpLnBocC9wcm92aWRlL3ZvZC8=") }, { n: "最大", u: atob("aHR0cHM6Ly9hcGkuenVpZGFwaS5jb20vYXBpLnBocC9wcm92aWRlL3ZvZC8=") }, { n: "速播2", u: atob("aHR0cDovL3N1Ym96aXl1YW4ubmV0L2FwaS5waHAvcHJvdmlkZS92b2Qv") }, { n: "猫眼", u: atob("aHR0cHM6Ly9hcGkubWFveWFuYXBpLnRvcC9hcGkucGhwL3Byb3ZpZGUvdm9k") }, { n: "天空", u: atob("aHR0cHM6Ly9hcGkudGlhbmtvbmdhcGkuY29tL2FwaS5waHAvcHJvdmlkZS92b2Qv") }, { n: "量子", u: atob("aHR0cHM6Ly9jai5semlhcGkuY29tL2FwaS5waHAvcHJvdmlkZS92b2Qv") }, { n: "金鹰", u: atob("aHR0cHM6Ly9qeXp5YXBpLmNvbS9hcGkucGhwL3Byb3ZpZGUvdm9kLw==") }, { n: "U酷2", u: atob("aHR0cHM6Ly9hcGkudWt1YXBpODguY29tL2FwaS5waHAvcHJvdmlkZS92b2Qv") }, { n: "光速", u: atob("aHR0cHM6Ly9hcGkuZ3VhbmdzdWFwaS5jb20vYXBpLnBocC9wcm92aWRlL3ZvZC8=") }, { n: "极速", u: atob("aHR0cHM6Ly9qc3p5YXBpLmNvbS9hcGkucGhwL3Byb3ZpZGUvdm9kLw==") }, { n: "闪电3", u: atob("aHR0cHM6Ly94c2Quc2R6eWFwaS5jb20vYXBpLnBocC9wcm92aWRlL3ZvZC8=") }, { n: "闪电", u: atob("aHR0cDovL3NkenlhcGkuY29tL2FwaS5waHAvcHJvdmlkZS92b2Qv") }, { n: "飘零2", u: atob("aHR0cHM6Ly9wMjEwMC5uZXQvYXBpLnBocC9wcm92aWRlL3ZvZC8=") }, { n: "麒麟", u: atob("aHR0cHM6Ly93d3cucWlsaW56eXouY29tL2FwaS5waHAvcHJvdmlkZS92b2Qv") }, { n: "百度", u: atob("aHR0cHM6Ly9hcGkuYXBpYmR6eS5jb20vYXBpLnBocC9wcm92aWRlL3ZvZC8=") }, { n: "如意", u: atob("aHR0cDovL2NqLnJ5Y2phcGkuY29tL2FwaS5waHAvcHJvdmlkZS92b2Qv") }, { n: "天堂2", u: atob("aHR0cDovL2NhaWppLmR5dHR6eWFwaS5jb20vYXBpLnBocC9wcm92aWRlL3ZvZC8=") }, { n: "牛牛", u: atob("aHR0cHM6Ly9hcGkubml1bml1enkubWUvYXBpLnBocC9wcm92aWRlL3ZvZC8=") }, { n: "非凡2", u: atob("aHR0cDovL2NqLmZmenlhcGkuY29tL2FwaS5waHAvcHJvdmlkZS92b2QvZnJvbS9mZm0zdTgv") }, { n: "快车", u: atob("aHR0cHM6Ly9jYWlqaS5rdWFpY2hlenkub3JnL2FwaS5waHAvcHJvdmlkZS92b2Qv") }, { n: "天涯", u: atob("aHR0cHM6Ly90eXlzenlhcGkuY29tL2FwaS5waHAvcHJvdmlkZS92b2Qv") }, { n: "U酷", u: atob("aHR0cHM6Ly9hcGkudWt1YXBpLmNvbS9hcGkucGhwL3Byb3ZpZGUvdm9kLw==") }, { n: "ikun", u: atob("aHR0cHM6Ly9pa3VuenlhcGkuY29tL2FwaS5waHAvcHJvdmlkZS92b2Qv") }, { n: "蛋蛋", u: atob("aHR0cHM6Ly9kZG1mLm5ldC9hcGkucGhwL3Byb3ZpZGUvdm9kLw==") }, { n: "快鹰", u: atob("aHR0cDovL3NhdnZpdXV4LmhrMy4zNDU4ODgueHl6LmNkbi5jbG91ZGZsYXJlLm5ldC9hcGkucGhwL3Byb3ZpZGUvdm9kLw==") }, { n: "360", u: atob("aHR0cHM6Ly8zNjB6eS5jb20vYXBpLnBocC9wcm92aWRlL3ZvZC8=") }, { n: "OK", u: atob("aHR0cHM6Ly9hcGkub2t6eXcubmV0L2FwaS5waHAvcHJvdmlkZS92b2Qv") }, { n: "量子2", u: atob("aHR0cDovL3d3dy5senp5LnR2L2FwaS5waHAvcHJvdmlkZS92b2Qv") }, { n: "iqiyi", u: atob("aHR0cHM6Ly9pcWl5aXp5YXBpLmNvbS9hcGkucGhwL3Byb3ZpZGUvdm9kLw==") }, { n: "如意2", u: atob("aHR0cHM6Ly93d3cucnl6eXcuY29tL2FwaS5waHAvcHJvdmlkZS92b2Qv") }, { n: "虎牙", u: atob("aHR0cHM6Ly93d3cuaHV5YWFwaS5jb20vYXBpLnBocC9wcm92aWRlL3ZvZA==") }, { n: "海外", u: atob("aHR0cDovL2FwaS5oYWl3YWlrYW4uY29tL3YxL3ZvZA==") }, { n: "艾旦", u: atob("aHR0cHM6Ly93d3cubG92ZWRhbi5uZXQvYXBpLnBocC9wcm92aWRlL3ZvZC8=") }, { n: "樱花", u: atob("aHR0cHM6Ly9tM3U4LmFwaXloenkuY29tL2FwaS5waHAvcHJvdmlkZS92b2Qv") }, { n: "红牛", u: atob("aHR0cHM6Ly93d3cuaG9uZ25pdXp5Mi5jb20vYXBpLnBocC9wcm92aWRlL3ZvZC8=") }, { n: "暴风", u: atob("aHR0cHM6Ly9iZnp5YXBpLmNvbS9hcGkucGhwL3Byb3ZpZGUvdm9kLw==") }, { n: "非凡", u: atob("aHR0cHM6Ly9hcGkuZmZ6eWFwaS5jb20vYXBpLnBocC9wcm92aWRlL3ZvZC8=") }, { n: "快车2", u: atob("aHR0cHM6Ly9jYWlqaS5rY3p5YXBpLmNvbS9hcGkucGhwL3Byb3ZpZGUvdm9kLw==") }, { n: "天堂", u: atob("aHR0cHM6Ly9jYWlqaS5keXR0enlhcGkuY29tL2FwaS5waHAvcHJvdmlkZS92b2Qv") }, { n: "森林", u: atob("aHR0cHM6Ly9zbGFwaWJmLmNvbS9hcGkucGhwL3Byb3ZpZGUvdm9kLw==") }, { n: "一零", u: atob("aHR0cDovL2FwaS4xMDgwenlrdS5jb20vaW5jL2FwaV9tYWMxMC5waHA=") }, { n: "速播", u: atob("aHR0cHM6Ly9zdWJvY2FpamkuY29tL2FwaS5waHAvcHJvdmlkZS92b2Qv") }, { n: "花旗", u: atob("aHR0cHM6Ly93d3cuc2VhY21zLm9yZy9hcGkucGhwL3Byb3ZpZGUvdm9kLw==") }, { n: "CK", u: atob("aHR0cHM6Ly9ja3p5Lm1lL2FwaS5waHAvcHJvdmlkZS92b2Qv") }, { n: "无忧", u: atob("aHR0cHM6Ly93d3cud3l2b2QuY29tL2FwaS5waHAvcHJvdmlkZS92b2Qv") }, { n: "淘片", u: atob("aHR0cHM6Ly90YW9waWFuYXBpLmNvbS9jamFwaS9tYzEwL3ZvZC9qc29uLmh0bWw=") }, { n: "魔都2", u: atob("aHR0cHM6Ly93d3cubWR6eWFwaS5jb20vYXBpLnBocC9wcm92aWRlL3ZvZC8=") }, { n: "快龙", u: atob("aHR0cHM6Ly9sei4xMTgzMTgueHl6L2FwaS5waHAvcHJvdmlkZS92b2Qv") }, { n: "优质", u: atob("aHR0cHM6Ly9hcGkueXp6eS1hcGkuY29tL2luYy9hcGlqc29uLnBocC9wcm92aWRlL3ZvZC8=") }, { n: "影图", u: atob("aHR0cHM6Ly9jai52b2RpbWcudG9wL2FwaS5waHAvcHJvdmlkZS92b2Qv") }, { n: "1080", u: atob("aHR0cHM6Ly9hcGkuMTA4MHp5a3UuY29tL2luYy9hcGlqc29uLnBocA==") }, { n: "新浪", u: atob("aHR0cHM6Ly9hcGkueGlubGFuZ2FwaS5jb20veGlubGFuZ2FwaS5waHAvcHJvdmlkZS92b2Qv") }, { n: "魔都", u: atob("aHR0cHM6Ly9jYWlqaS5tb2R1YXBpLmNjL2FwaS5waHAvcHJvdmlkZS92b2Qv") }, { n: "鸭鸭", u: atob("aHR0cHM6Ly9jai55YXlhenkubmV0L2FwaS5waHAvcHJvdmlkZS92b2Qv") }, { n: "云解", u: atob("aHR0cHM6Ly9hcGkueXBhcnNlLmNvbS9hcGkvanNvbg==") }, { n: "享看", u: atob("aHR0cHM6Ly94a2FuenkuY29tL2FwaS5waHAvcHJvdmlkZS92b2Qv") }, { n: "39影", u: atob("aHR0cHM6Ly93d3cuMzlrYW4uY29tL2FwaS5waHAvcHJvdmlkZS92b2Qv") } ]; const processApis = raw => { const m = new Map(); raw.forEach(a => { if (!m.has(a.u)) m.set(a.u, { name: a.n, url: a.u, shortName: a.n.substring(0, 4) }); }); let apis = Array.from(m.values()); if (CONFIG.SMART_SORTING) apis = apis.map(a => ({ ...a, score: ApiStats.score(ApiStats.get(a.shortName)) })).sort((a, b) => b.score - a.score); return apis; }; const UNIQUE_APIS = processApis(RAW_APIS); const $ = (s, p = document) => p.querySelector(s); const $$ = (s, p = document) => Array.from(p.querySelectorAll(s)); const State = { eps: [], curUrl: '', hiddenEl: null, panelOpen: false, curURL: location.href, dom: {}, timers: {}, cache: { key: null, results: [] }, activeName: null, firstAuto: false, curEp: null, failed: new Set(), closed: false, searchId: 0, playing: false, epOpenAt: 0, switchCount: 0, stuckPending: false }; const onVideoPage = () => CONFIG.VIDEO_URL_PATTERNS.some(p => p.test(location.href)); /* ========== UI ========== */ const UI = { init() { this._css(); State.dom.c = this._el('div', { id: 't' }); State.dom.btn = this._el('button', { id: 'tb' }); State.dom.p = this._el('div', { id: 'tp' }); State.dom.ov = this._el('div', { id: 'to' }); State.dom.ov.innerHTML = ''; document.body.append(State.dom.c, State.dom.ov); State.dom.c.append(State.dom.btn, State.dom.p); State.dom.ifr = document.getElementById('ti'); State.dom.cls = document.getElementById('tx'); State.dom.toast = UI._el('div', { id: 'ttf' }); document.body.append(State.dom.toast); this._drag(); State.dom.cls.onclick = () => Player.close(); State.dom.p.onmouseenter = () => { clearTimer('pc'); clearTimer('eg'); }; State.dom.p.onmouseleave = () => { State.timers.pc = setTimeout(() => hideP(), CONFIG.PANEL_LEAVE_CLOSE_DELAY); }; window.addEventListener('resize', () => { clearTimer('rs'); State.timers.rs = setTimeout(() => Player.repos(), 150); }); window.addEventListener('message', e => Player.onMsg(e)); }, _el: (t, p) => Object.assign(document.createElement(t), p), _css() { GM_addStyle('\ #t{position:fixed;z-index:2147483647;width:36px;height:36px;cursor:grab;user-select:none}\ #tb{width:100%;height:100%;border:none;border-radius:50%;background:#0066cc;color:#fff;cursor:pointer;box-shadow:0 0 20px rgba(0,102,204,.5),inset 0 0 15px rgba(255,255,255,.3),0 0 0 5px rgba(0,0,0,0.6),0 0 0 6px rgba(255,255,255,0.15);transition:transform .25s,box-shadow .25s;padding:0;display:flex;align-items:center;justify-content:center;overflow:hidden}\ #tb:hover{transform:scale(1.08);box-shadow:0 0 28px rgba(0,102,204,.7),inset 0 0 14px rgba(255,255,255,.2)}\ #tb::before{content:"VIP";font-size:14px;font-weight:900;background:linear-gradient(to right, #60a5fa 0%, #ffffff 50%, #a5c8ff 100%);-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent;text-shadow:0 1px 3px rgba(0,0,0,0.5);filter:contrast(1.3);}\ #tb.loading{background:#0066cc!important;box-shadow:0 0 28px rgba(0,102,204,.7)}\ #tb.loading::before{font-size:0;content:"";width:3px;height:16px;background:#fff;border-radius:3px;animation:pulse 1s infinite ease-in-out}\ @keyframes pulse{0%,100%{transform:scaleY(.35);opacity:.6}50%{transform:scaleY(1);opacity:1}}\ #tp{display:none;position:absolute;left:44px;top:0;max-width:420px;background:rgba(15,15,30,.72);border-radius:14px;padding:10px;max-height:70vh;box-shadow:0 12px 40px rgba(0,0,0,.55),inset 0 1px 0 rgba(255,255,255,.06);font-size:12px;backdrop-filter:blur(28px);-webkit-backdrop-filter:blur(28px);border:1px solid rgba(255,255,255,.08);flex-direction:column}\ #ts{display:flex;align-items:center;justify-content:center;flex-shrink:0;width:100%;box-sizing:border-box;padding:7px 12px;margin-bottom:6px;background:rgba(59,130,246,.3);border-radius:10px;font-weight:500;color:#c4b8f0;font-size:12px;height:28px;border:1px solid rgba(59,130,246,.25);box-shadow:0 2px 6px rgba(0,0,0,.2);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\ .tb{display:flex;align-items:center;justify-content:center;width:100%;padding:7px 10px;margin-bottom:4px;background:rgba(255,255,255,.05);border:1px solid rgba(255,255,255,.06);border-radius:9px;cursor:pointer;text-align:center;font-weight:500;color:#d1d5db;font-size:12px;transition:background .18s,transform .18s,border-color .18s;white-space:nowrap;box-sizing:border-box}\ .tb:hover{background:rgba(90,79,207,.18);transform:translateY(-1px);border-color:rgba(90,79,207,.35)}\ .tb.on{background:#3b82f6!important;color:#fff!important;border-color:rgba(255,255,255,.18)!important;box-shadow:0 4px 14px rgba(59,130,246,.4)}\ #tc{display:grid;grid-template-columns:1fr;gap:4px;flex:1 1 auto;min-height:0;overflow-y:auto;padding-right:6px;box-sizing:border-box}\ #tc.ep{grid-template-columns:repeat(2,1fr)}#tc.ep .tb{width:100%}\ #tc.sl{grid-template-columns:repeat(2,1fr)}#tp.sl-open{width:210px!important}\ #tc.sl .tb{width:100%;height:28px;min-width:0;overflow:hidden}\ #tc::-webkit-scrollbar{width:5px}#tc::-webkit-scrollbar-track{background:rgba(10,15,30,.2);border-radius:3px}\ #tc::-webkit-scrollbar-thumb{background:rgba(90,79,207,.3);border-radius:3px}#tc::-webkit-scrollbar-thumb:hover{background:rgba(90,79,207,.6)}\ #to{position:absolute;background:#000;z-index:2147483646;display:none;border-radius:2px;overflow:hidden}\ #ti{width:100%;height:100%;border:none}#tx{position:absolute;top:8px;right:8px;z-index:2147483647;width:30px;height:30px;border-radius:50%;background:rgba(15,15,30,.65);backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);border:1px solid rgba(255,255,255,.08);color:#e5e7eb;cursor:pointer;font-size:16px;display:flex;align-items:center;justify-content:center;transition:all .25s}\ #tx:hover{background:rgba(239,68,68,.85);border-color:transparent;color:#fff;transform:rotate(90deg);box-shadow:0 4px 12px rgba(239,68,68,.4)}\ #ttf{position:fixed;top:20px;left:50%;transform:translateX(-50%);background:rgba(15,15,30,.88);backdrop-filter:blur(14px);-webkit-backdrop-filter:blur(14px);color:#c4b8f0;font-size:13px;padding:10px 24px;border-radius:20px;z-index:2147483647;border:1px solid rgba(90,79,207,.35);box-shadow:0 6px 24px rgba(0,0,0,.55);transition:opacity .35s;pointer-events:none;opacity:0;display:none}\ #ttf.show{display:block;opacity:1}\ '); }, _drag() { let d = false, m = false, ox, oy; const c = State.dom.c; const pos = GM_getValue(CONFIG.STORAGE_KEY_ICON_POSITION, { l: '20px', t: '250px' }); c.style.left = pos.l; c.style.top = pos.t; const mv = e => { if (!d) return; m = true; c.style.left = Math.max(0, Math.min(e.clientX - ox, innerWidth - 36)) + 'px'; c.style.top = Math.max(0, Math.min(e.clientY - oy, innerHeight - 36)) + 'px'; }; const up = () => { if (!d) return; d = false; document.body.style.userSelect = ''; c.style.cursor = 'grab'; if (m) GM_setValue(CONFIG.STORAGE_KEY_ICON_POSITION, { l: c.style.left, t: c.style.top }); removeEventListener('mousemove', mv, true); removeEventListener('mouseup', up, true); removeEventListener('blur', up, true); }; c.addEventListener('mousedown', e => { e.stopPropagation(); if (e.button !== 0) return; m = false; d = true; document.body.style.userSelect = 'none'; c.style.cursor = 'grabbing'; ox = e.clientX - c.getBoundingClientRect().left; oy = e.clientY - c.getBoundingClientRect().top; addEventListener('mousemove', mv, true); addEventListener('mouseup', up, true); addEventListener('blur', up, true); }); State.dom.btn.onclick = e => { e.stopPropagation(); setTimeout(() => { if (State.panelOpen) { hideP(); } else { const k = location.href; if (State.cache.key === k && State.cache.results.length) { State.closed = false; showP(); renderSrc(); } else if (State._savedCache && State._savedCache.results.length) { State.cache = { key: k, results: State._savedCache.results }; State.closed = false; showP(); renderSrc(); } else { showP(); Search.go(); } } }, 60); }; }, addSrc(r) { const ca = $('#tc', State.dom.p); if (!ca || !State.panelOpen || ca.classList.contains('ep')) return; State.dom.p.style.cssText = 'display:flex;flex-direction:column;width:210px'; _flipPanel(); ca.style.display = ''; const old = $$('.tb', ca).find(b => b.dataset.n === r.name); if (old) old.remove(); const pu = r.data.vod_play_url || ''; const cnt = pu.includes('$$$') ? pu.split('$$$').pop().split('#').length : pu.includes('#') ? pu.split('#').length : pu.includes('$') ? pu.split('$').length : 1; const btn = UI._el('button', { textContent: r.name + ' (' + cnt + '集)', className: 'tb', onclick() { State.activeName = r.name; UI.epList(r, false, State.curEp); } }); btn.dataset.n = r.name; State.dom.p.classList.add('sl-open'); if (r.name === State.activeName) btn.classList.add('on'); ca.appendChild(btn); }, epList(src, auto = false, cur = null) { if (State.closed) return; clearAll(); const ca = $('#tc', State.dom.p); if (!ca) return; ca.innerHTML = ''; State.dom.p.style.cssText = 'display:flex;flex-direction:column'; _flipPanel(); State.dom.p.classList.remove('sl-open'); ca.className = 'ep'; stat('‹ 返回源列表', false); const sb = $('#ts', State.dom.p); sb.style.cursor = 'pointer'; sb.onclick = () => renderSrc(); State.eps = []; const pu = src.data.vod_play_url || ''; if (!pu) { stat('该源无可播放地址', true); _grace(); return; } const eps = pu.includes('$$$') ? pu.split('$$$').pop().split('#') : pu.includes('#') ? pu.split('#') : [pu]; if (!eps.length || (eps.length === 1 && !eps[0])) { stat('该源剧集数据为空', true); _grace(); return; } let vc = 0; eps.forEach(ep => { const [nm, url] = ep.split('$'); const n = (nm || '').trim(); const u = (url || nm || '').trim(); if (!n && !u) return; vc++; State.eps.push({ name: n || '集' + vc, url: u }); const b = UI._el('button', { textContent: n || '集' + vc, className: 'tb', onclick() { const en = Utils.epNum(n); if (en) State.curEp = en; Player.start(u); } }); b.dataset.url = u; ca.appendChild(b); }); _rsz(ca); if (!cur) { if (auto && State.eps.length) { const best = _bestMovie(State.eps); if (best) { const bb = $$('.tb', ca).find(b => b.dataset.url === best.url); if (bb) bb.classList.add('on'); stat('正在播放: ' + best.name); Player.start(best.url); } } _grace(); return; } const nc = String(parseInt(cur, 10)); let mb = null; const btns = $$('.tb', ca); for (const b of btns) { const en = Utils.epNum(b.textContent); if (en && String(parseInt(en, 10)) === nc) { mb = b; break; } } if (!mb) for (const b of btns) { if (b.textContent.includes(nc)) { mb = b; break; } } if (mb) { mb.classList.add('on'); setTimeout(() => mb.scrollIntoView({ behavior: 'smooth', block: 'center' }), 100); if (auto) { stat('正在播放: ' + mb.textContent); Player.start(mb.dataset.url); } else { stat('已匹配剧集: ' + mb.textContent); } } _grace(); }, updateStatus: stat, highlightPlayingEpisode(url) { const ca = $('#tc', State.dom.p); if (!ca) return; $$('.tb.on', ca).forEach(b => b.classList.remove('on')); const t = $$( '.tb', ca).find(b => b.dataset.url === url); if (t) { t.classList.add('on'); t.scrollIntoView({ behavior: 'smooth', block: 'center' }); } }, toggleLoading: v => State.dom.btn.classList.toggle('loading', v), showSwitchNotify() { const t = State.dom.toast; if (!t) return; t.textContent = '正在自动切换可用源…'; t.classList.add('show'); }, hideSwitchNotify() { const t = State.dom.toast; if (!t) return; t.classList.remove('show'); } }; function _flipPanel() { const r = State.dom.c.getBoundingClientRect(); if (r.right + 460 > innerWidth) { State.dom.p.style.left = 'auto'; State.dom.p.style.right = (innerWidth - r.left + 8) + 'px'; } else { State.dom.p.style.left = '44px'; State.dom.p.style.right = 'auto'; } } function renderSrc() { State.dom.p.style.cssText = 'display:flex;flex-direction:column;width:210px'; _flipPanel(); State.dom.p.classList.add('sl-open'); State.dom.p.innerHTML = '
'; stat('共 ' + State.cache.results.length + ' 个资源', false); const ca = $('#tc', State.dom.p); ca.innerHTML = ''; State.cache.results.forEach(r => UI.addSrc(r)); } function stat(t, err) { const s = $('#ts', State.dom.p); if (!s) return; s.textContent = t; if (err) { s.style.cssText = 'color:#fca5a5;background:rgba(90,79,207,.2);border-color:rgba(90,79,207,.25)'; } else { s.style.cssText = ''; } } function showP() { State.dom.p.style.display = 'flex'; State.panelOpen = true; if (State.curUrl) { UI.highlightPlayingEpisode(State.curUrl); const ce = State.eps.find(e => e.url === State.curUrl); if (ce) stat('已匹配剧集: ' + ce.name); } } function hideP() { State.dom.p.style.display = 'none'; State.panelOpen = false; clearAll(); } function _grace() { if (!State.panelOpen) return; clearTimer('eg'); if (State.dom.p.matches(':hover')) return; State.epOpenAt = Date.now(); State.timers.eg = setTimeout(() => hideP(), CONFIG.PANEL_EPISODE_GRACE_PERIOD); } function _rsz(ca) { setTimeout(() => { if (!State.panelOpen || !ca) return; const btns = $$('.tb', ca); if (!btns.length) return; let mw = 0; for (const b of btns) { const w = b.scrollWidth + 6; if (w > mw) mw = w; } const need = Math.max(mw * 2 + 20, 170); const max = Math.min(420, innerWidth - State.dom.c.getBoundingClientRect().right - 20); State.dom.p.style.width = Math.min(need, max) + 'px'; }, 10); } function _bestMovie(eps) { let best = eps[0], bi = Infinity; for (const ep of eps) { for (let i = 0; i < CONFIG.MOVIE_PRIORITY.length; i++) { if (ep.name === CONFIG.MOVIE_PRIORITY[i] || ep.name.includes(CONFIG.MOVIE_PRIORITY[i])) { if (i < bi) { bi = i; best = ep; } break; } } } return best; } function clearTimer(t) { clearInterval(State.timers[t]); clearTimeout(State.timers[t]); State.timers[t] = null; } function clearAll() { Object.keys(State.timers).forEach(t => clearTimer(t)); } /* ========== Search ========== */ const Search = { async go() { clearAll(); State.dom.p.style.display = 'flex'; State.dom.p.style.flexDirection = 'row'; State.dom.p.style.alignItems = 'center'; State.dom.p.style.justifyContent = 'center'; State.dom.p.style.padding = '8px 15px'; State.dom.p.style.width = 'auto'; State.dom.p.style.top = '50%'; State.dom.p.style.transform = 'translateY(-50%)'; State.dom.p.innerHTML = '
'; UI.toggleLoading(true); const title = Utils.title(); if (!title) { stat('无法获取视频标题', true); UI.toggleLoading(false); return; } const curEp = await Utils.curEp(); stat('评估资源 ' + (curEp ? ' 第' + curEp + '集' : '') + ' (0/' + UNIQUE_APIS.length + ')'); State.cache = { key: location.href, results: [] }; State._savedCache = null; State.activeName = null; State.firstAuto = false; State.failed = new Set(); State.closed = false; const id = Date.now(); State.searchId = id; State.curEp = curEp; setTimeout(() => { if (State.searchId !== id) return; this._search(title, curEp, id); }, 100); }, async _search(title, curEp, id) { let cnt = 0; const fn = async api => { if (State.closed || State.searchId !== id) return; const r = await this._one(api, title); cnt++; if (State.panelOpen) stat('评估资源 ' + (curEp ? ' 第' + curEp + '集' : '') + ' (' + cnt + '/' + UNIQUE_APIS.length + ')'); if (!r) { ApiStats.fail(api.name); return; } ApiStats.ok(api.name, r.latency); if (State.closed || State.searchId !== id) return; const eps = r.data.vod_play_url.split('$$$').pop().split('#'); let tUrl = null, match = false, isMovie = false; const nEp = curEp ? String(parseInt(curEp, 10)) : null; const mEps = eps.filter(ep => { const [nm] = ep.split('$'); return nm && CONFIG.MOVIE_KEYWORDS.test(nm.trim()); }); const nmEps = eps.filter(ep => { const [nm] = ep.split('$'); return nm && !CONFIG.MOVIE_KEYWORDS.test(nm.trim()); }); if (mEps.length >= 1 && nmEps.length === 0) { isMovie = true; let best = mEps[0], bi = Infinity; for (const ep of mEps) { const [nm] = ep.split('$'); const tn = nm.trim(); for (let i = 0; i < CONFIG.MOVIE_PRIORITY.length; i++) { if (tn === CONFIG.MOVIE_PRIORITY[i] || tn.includes(CONFIG.MOVIE_PRIORITY[i])) { if (i < bi) { bi = i; best = ep; } break; } } } tUrl = best.split('$')[1] || best.split('$')[0]; } else if (nEp && nmEps.length) { let te = nmEps.find(ep => { const [nm] = ep.split('$'); const en = Utils.epNum(nm); return en && String(parseInt(en, 10)) === nEp; }); if (!te) te = nmEps.find(ep => { const [nm] = ep.split('$'); return nm && nm.includes(nEp); }); if (!te) te = nmEps.find(ep => { const [nm] = ep.split('$'); const en = Utils.epNum(nm); return en && parseInt(en, 10) === parseInt(nEp, 10); }); if (te) { tUrl = te.split('$')[1] || te.split('$')[0]; match = true; } } else if (nmEps.length) { const fe = nmEps[0] || eps[0]; if (fe && fe.includes('$')) tUrl = fe.split('$')[1] || fe.split('$')[0]; } else if (eps.length && eps[0].includes('$')) { tUrl = eps[0].split('$')[1] || eps[0].split('$')[0]; } if (!tUrl || (!tUrl.includes('.m3u8') && !tUrl.includes('.mp4') && !tUrl.includes('.flv'))) return; const ev = await Utils.evalSrc(tUrl); if (!ev) { _addR({ ...r, score: 50, resolution: 0, latency: 0 }); return; } const fr = { ...r, score: Math.max(0, Math.min(100, ev.score)), resolution: ev.resolution, latency: ev.latency, evaluatedUrl: ev.url }; const autoPlay = isMovie || match; if (!State.firstAuto && autoPlay) { State.firstAuto = true; State.activeName = fr.name; UI.epList(fr, true, isMovie ? null : State.curEp); hideP(); } _addR(fr); function _addR(rr) { const ei = State.cache.results.findIndex(x => x.name === rr.name); if (ei > -1) State.cache.results[ei] = rr; else State.cache.results.push(rr); State.cache.results.sort((a, b) => b.score - a.score); UI.addSrc(rr); } }; await Utils.pool(CONFIG.SEARCH_CONCURRENCY, UNIQUE_APIS, fn).then(() => { if (State.searchId !== id || State.closed) return; const vs = State.cache.results.filter(r => r.score >= 10); if (!vs.length) { UI.toggleLoading(false); stat('未找到可用源,请刷新重试', true); } else { UI.toggleLoading(false); } }); }, _one: (api, title) => new Promise(async resolve => { try { const wd = encodeURIComponent(title); const l = await Utils.req('ac=list&wd=' + wd, api); const items = l.data?.list; if (items && items.length > 0) { const first = items[0]; if (first.vod_play_url) { resolve({ name: api.name, data: first, latency: l.latency }); return; } const vd = await Utils.req('ac=detail&ids=' + first.vod_id, api); const v = vd.data?.list?.[0]; if (v?.vod_play_url) { resolve({ name: api.name, data: v, latency: l.latency + vd.latency }); return; } } const d = await Utils.req('ac=detail&wd=' + wd, api); if (d.data?.list?.[0]?.vod_play_url) { resolve({ name: api.name, data: d.data.list[0], latency: d.latency }); return; } resolve(null); } catch (e) { resolve(null); } }) }; /* ========== Player ========== */ const Player = { init() { if (State.closed) return; State.dom.ifr.srcdoc = '
全网资源极速加载中
'; this._pos(0); }, _pos(at) { if (State.closed || at > 12) return; let r = null; if (!State.hiddenEl) State.hiddenEl = Utils.findPlayer(); if (!State.hiddenEl) { const v = document.querySelector('video'); if (v) { let p = v.parentElement; while (p && p.tagName !== 'BODY' && p.offsetHeight < 300) p = p.parentElement; if (p && p.offsetHeight >= 300) { State.hiddenEl = p; State.hiddenEl.style.opacity = '0'; } } } if (State.hiddenEl) { try { r = State.hiddenEl.getBoundingClientRect(); } catch (e) {} } if (r && r.width > 200 && r.height > 100) { State.dom.ov.style.cssText = 'position:absolute;top:' + (r.top + scrollY) + 'px;left:' + r.left + 'px;width:' + r.width + 'px;height:' + r.height + 'px;display:block;z-index:2147483646'; } else { if (State.curUrl && at > 6) { State.dom.ov.style.cssText = 'position:fixed;top:50%;left:50%;width:80%;height:80%;transform:translate(-50%,-50%);display:block;z-index:2147483646'; } else if (at <= 12) { setTimeout(() => this._pos(at + 1), 400); } } }, async start(url) { if (State.closed) return; clearAll(); hideP(); State.curUrl = url; UI.highlightPlayingEpisode(url); State.playing = true; State.switchCount = 0; this._pauseOrig(); this._pos(0); State.dom.ov.style.display = 'block'; this._play(url); }, _play(url) { if (State.closed) return; clearTimer('sk'); State.dom.ifr.srcdoc = this._html(url); State.dom.ifr.onload = () => { if (State.closed) return; State.stuckPending = true; State.timers.sk = setTimeout(() => { if (State.stuckPending && !State.closed) this._switch(); }, CONFIG.STUCK_CHECK_TIMEOUT); setTimeout(() => this.repos(), 200); }; }, _switch() { try { if (!State.cache.results.length) return; State.stuckPending = false; clearTimer('sk'); UI.showSwitchNotify(); if (State.switchCount >= 5) { UI.hideSwitchNotify(); this.close(); return; } State.switchCount++; State.failed.add(State.curUrl); const epT = parseInt(State.curEp, 10); if (isNaN(epT)) { for (const r of State.cache.results) { if (r.name === State.activeName || !r.data?.vod_play_url) continue; const eps = r.data.vod_play_url.split('$$$').pop().split('#'); const fe = eps.find(ep => { const [, u] = ep.split('$'); return u && !State.failed.has(u); }); if (fe) { State.activeName = r.name; UI.epList(r, true, null); return; } } return; } for (const r of State.cache.results) { if (r.name === State.activeName || !r.data?.vod_play_url) continue; const eps = r.data.vod_play_url.split('$$$').pop().split('#'); const te = eps.find(ep => { const [nm, u] = ep.split('$'); const en = Utils.epNum(nm); return en && String(parseInt(en, 10)) === String(epT) && !State.failed.has(u); }); if (te) { State.activeName = r.name; UI.epList(r, true, State.curEp); return; } } } catch (e) {} UI.hideSwitchNotify(); },_html(url) { const u = url.replace(//g, ">").replace(/'/g, "\\'"); return 'VIP Play