// ==UserScript== // @name 常看VIP视频全网解析 (Beta版) // @namespace https://scriptcat.org/zh-CN/script-show-page/6457 // @version 2.2.1 // @description 💎VIP视频解析 | 🎬爱奇艺解析 | 🔍腾讯VIP去广 | 📺优酷独播解锁 | ⚡芒果TV大会员 | 🎥B站番剧解析 | 🔗搜狐视频直连 | ✦乐视超清播放 | ◆PPTV聚力解析 | ●咪咕体育直播 | ★西瓜VIP解锁 | 🔍抖音短剧解析 | 🎬快手短视频解析 | 📺1905电影网 | ⚡AcFun弹幕解析 | 🔗土豆/风行/暴风兼容 | ✦全网影视通解 | ◆4K蓝光画质 | ●HDR杜比音效 | ★智能线路优选 | 💎免登录免费看 | 🎞️热播剧/最新电影/独家综艺/国漫日番 | ⚡极速评估 | 🔄智能缓存 | 🚀DNS预解析 | 🚀24并发加速 // @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 // @require https://cdn.jsdelivr.net/gh/Tampermonkey/utils@3b32b826e84ccc99a0a3e3d8d6e5ce0fa9834f23/requires/gh_2215_make_GM_xhr_more_parallel_again.js // @run-at document-end // @connect 360zy.com // @connect api.1080zyku.com // @connect api.apibdzy.com // @connect api.ffzyapi.com // @connect api.guangsuapi.com // @connect api.maoyanapi.top // @connect api.niuniuzy.me // @connect api.okzyw.net // @connect api.tiankongapi.com // @connect api.ukuapi.com // @connect api.ukuapi88.com // @connect api.wujinapi.me // @connect api.xinlangapi.com // @connect api.yparse.com // @connect api.yzzy-api.com // @connect api.zuidapi.com // @connect caiji.dyttzyapi.com // @connect caiji.kczyapi.com // @connect caiji.kuaichezy.org // @connect caiji.moduapi.cc // @connect cj.ffzyapi.com // @connect cj.lziapi.com // @connect cj.rycjapi.com // @connect cj.vodimg.top // @connect cj.yayazy.net // @connect ckzy.me // @connect ddmf.net // @connect hhzyapi.com // @connect ikunzyapi.com // @connect iqiyizyapi.com // @connect jszyapi.com // @connect jyzyapi.com // @connect lz.118318.xyz // @connect m3u8.apiyhzy.com // @connect p2100.net // @connect savviuux.hk3.345888.xyz.cdn.cloudflare.net // @connect sdzyapi.com // @connect slapibf.com // @connect subocaiji.com // @connect suboziyuan.net // @connect suoniapi.com // @connect taopianapi.com // @connect tyyszyapi.com // @connect www.39kan.com // @connect www.hongniuzy2.com // @connect www.huyaapi.com // @connect www.lovedan.net // @connect www.lzzy.tv // @connect www.mdzyapi.com // @connect www.qilinzyz.com // @connect www.ryzyw.com // @connect www.seacms.org // @connect www.wyvod.com // @connect xkanzy.com // @connect xsd.sdzyapi.com // @connect fastly.jsdelivr.net // @connect cdn.jsdelivr.net // @antifeature piracy // @license MIT // ==/UserScript== (function () { if (window.hasInitVipScript) return; window.hasInitVipScript = true; 'use strict'; /* ========== 基础配置 ========== */ const CONFIG = { API_TIMEOUT: 2500, STUCK_CHECK_TIMEOUT: 8000, SEARCH_CONCURRENCY: 20, FAST_EVAL_TIMEOUT: 700, SMART_SORTING: true, AUTOPLAY_NEXT_DELAY: 200, PANEL_LEAVE_CLOSE_DELAY: 1500, SPA_DEBOUNCE: 400, STORAGE_KEY_ICON_POSITION: 'vip_icon_pos_v8', 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|play)\//, /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/, /miguvideo\.com\/(v|n|p)\/play/, /miguvideo\.com\/play\//, /cmvideo\.cn\/(v|n|p)\/play/, /cmvideo\.cn\/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', STREAM_ALIVE: 'tm_stream_alive' }, SELECTORS: { PLAYER_ELEMENTS: [ '#tenvideo_player', '.txp_player_root', '#player-container', '#player', '.container-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', '.artplayer-app' ], QUICK_TITLE: ['meta[property="og:title"]', 'h1', '.video-title', '.title', '.vod_title', '.video-info-title'], PRECISE_TITLE: { 'iqiyi.com': '[class*="episodes_playingItem"] [class*="episodes_order"], .qy-episode-item[class*="is-active"] a, .album-list .is-active .title-content, [class*="selected"] .qy-episode-num, #text[style*="IQYHT-Bold"]', 'youku.com': '.box-anthology-item.active, .anthology-wrap li.active span, .anthology-item.current, .play-panel-item.active', '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"], [data-v-db0ab5fa].episode-item-text', 'bilibili.com': '[class*="EpisodeVirtualList_numberTitle"], [class*="numberListItem_select"] [class*="numberListItem_title"], .ep-list-item.on .ep-item-title, [class*="episode_list"] [class*="selected"]', 'mgtv.com': '[class*="mgtv-player-aside-number-selector__number"], .episode-list .current a, .episode-series .current', 'sohu.com': 'li.pane-item.vip.on a, li.pane-item.on a, .player-album-list .on a', 'le.com': '.js-episode-item.on', 'pptv.com': '.episode-list .current', 'acfun.cn': '.active .title-wenzi', 'miguvideo.com': '[data-v-50548e8b].on span[data-v-50548e8b], [data-v-50548e8b].on, [class*="episodeTitle"][class*="on"]' }, PRECISE_MAIN_TITLE: { 'qq.com': '.intro-title[title], .video-title[title], .player-title', 'iqiyi.com': '[data-ai-entity="主标题"], [class*="episodeTitle"], .album-head-title', 'iq.com': '[data-ai-entity="主标题"]', 'youku.com': '[data-spm-anchor-id*="introduction"] .title, .title[style*="max-width"], .video-title, a[data-pb-txid="pg_playlist_title"][title]', 'bilibili.com': '[class*="mediaTitle"][title], [class*="mediaTitle"], .media-info-title-t', 'b23.tv': '[class*="mediaTitle"][title], [class*="mediaTitle"]', 'sohu.com': 'a[data-pb-txid="pg_playlist_title"][title]', 'mgtv.com': 'h2[class*="mgtv-player-aside-info__title"][title]', 'miguvideo.com': '[data-v-50548e8b][title].episodeTitle' } }, 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 _MEDIA_EXT_RE = /\.m3u8|\.mp4|\.flv/i; const _TITLE_SPLIT_RE = /[-_\s((]/; const _EP_REMOVE_RE = /第.+[集季部]/; const _NUM_ONLY_RE = /^\d+$/; const _EP_PATTERN_RE = /(?:第|EP|Ep|ep|E)\s*(\d+)(?:集|话|期|部|季|章)?/i; const _EP_PREFIX_RE = /^(\d+)\s*(?:集|话|期|部|季|章)/; const _EP_SUFFIX_RE = /(\d+)\s*(?:集|话|期|部|季|章)(?:\D|$)/; const _EP_EP_RE = /(?:EP|Ep|ep)(\d+)/i; const _EP_SEASON_RE = /第\s*\d+\s*[季部]\s*第\s*(\d+)\s*[集话]/; const _EP_FALLBACK_RE = /(?:\D|^)(\d{1,4})(?:\D|$)/; const _M3U8_URL_RE = /https?:\/\/[^\s"']+\.m3u8[^\s"']*/gi; const _IFRAME_SRC_RE = /]+src=["']([^"']+)['"]/i; /* ========== 缓存模块 ========== */ const _CACHE_VERSION = 'v5'; const _CACHE_EXPIRY = 3600000; const SearchCache = { get(key) { try { const c = JSON.parse(GM_getValue('cache_' + _CACHE_VERSION + '_' + key, 'null')); if (c && Date.now() - c.ts < _CACHE_EXPIRY) return c.data; } catch (e) {} return null; }, set(key, data) { try { GM_setValue('cache_' + _CACHE_VERSION + '_' + key, JSON.stringify({ data: data, ts: Date.now() })); } catch (e) {} } }; /* ========== 统计与打分 ========== */ const ApiStats = { _p: {}, _t: null, _priority: new Set(['iqiy', '天堂', '360', '猫眼', '魔都', '1080', '红牛', 'ikun', '速播2', 'U酷', '非凡', '豪华']), _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, name) { if (name && this._priority.has(name)) return 99999; 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 _0x4cbb5a=_0x40ee;(function(_0x8458d4,_0x84b4a9){const _0x3ce818=_0x40ee,_0x588bbb=_0x8458d4();while(!![]){try{const _0x4582cc=-parseInt(_0x3ce818(0x133))/0x1*(parseInt(_0x3ce818(0x13f))/0x2)+-parseInt(_0x3ce818(0x13d))/0x3*(-parseInt(_0x3ce818(0x122))/0x4)+-parseInt(_0x3ce818(0x12c))/0x5+-parseInt(_0x3ce818(0x125))/0x6*(parseInt(_0x3ce818(0x130))/0x7)+parseInt(_0x3ce818(0x12e))/0x8*(-parseInt(_0x3ce818(0x11f))/0x9)+-parseInt(_0x3ce818(0x128))/0xa*(-parseInt(_0x3ce818(0x11b))/0xb)+-parseInt(_0x3ce818(0x13c))/0xc*(-parseInt(_0x3ce818(0x127))/0xd);if(_0x4582cc===_0x84b4a9)break;else _0x588bbb['push'](_0x588bbb['shift']());}catch(_0x4dec95){_0x588bbb['push'](_0x588bbb['shift']());}}}(_0x3993,0x2a540));function _0x3993(){const _0x315e35=['aHR0cHM6Ly9hcGkuMTA4MHp5a3UuY29tL2luYy9hcGlqc29uLnBocC9wcm92aWRlL3ZvZC8=','aHR0cDovL3d3dy5sb3ZlZGFuLm5ldC9hcGkucGhwL3Byb3ZpZGUvdm9kLw==','aHR0cDovL2FwaS5hcGliZHp5LmNvbS9hcGkucGhwL3Byb3ZpZGUvdm9kLw==','aHR0cHM6Ly9qeXp5YXBpLmNvbS9hcGkucGhwL3Byb3ZpZGUvdm9kLw==','535513edblbM','快车2','aHR0cHM6Ly9ja3p5Lm1lL2FwaS5waHAvcHJvdmlkZS92b2Qv','魔都2','113643KMtvJD','aHR0cHM6Ly9hcGkub2t6eXcubmV0L2FwaS5waHAvcHJvdmlkZS92b2Qv','aHR0cHM6Ly9zbGFwaWJmLmNvbS9hcGkucGhwL3Byb3ZpZGUvdm9kLw==','79228WnVdvd','aHR0cHM6Ly9hcGkueXp6eS1hcGkuY29tL2luYy9hcGlqc29uLnBocC9wcm92aWRlL3ZvZC8=','aHR0cHM6Ly9hcGkudGlhbmtvbmdhcGkuY29tL2FwaS5waHAvcHJvdmlkZS92b2Qv','165852RwRKfn','aHR0cHM6Ly9jai52b2RpbWcudG9wL2FwaS5waHAvcHJvdmlkZS92b2Qv','13kJykgC','40rFGRUe','aHR0cDovL2NqLmZmenlhcGkuY29tL2FwaS5waHAvcHJvdmlkZS92b2Qv','aHR0cHM6Ly93d3cuaG9uZ25pdXp5Mi5jb20vYXBpLnBocC9wcm92aWRlL3ZvZC8=','aHR0cHM6Ly9jYWlqaS5rdWFpY2hlenkub3JnL2FwaS5waHAvcHJvdmlkZS92b2Qv','425440pbZMEw','aHR0cHM6Ly93d3cuc2VhY21zLm9yZy9hcGkucGhwL3Byb3ZpZGUvdm9kLw==','104eCLerI','360','70RxadVb','非凡2','aHR0cHM6Ly9wMjEwMC5uZXQvYXBpLnBocC9wcm92aWRlL3ZvZC8=','19486XbZYwZ','aHR0cHM6Ly9jYWlqaS5tb2R1YXBpLmNjL2FwaS5waHAvcHJvdmlkZS92b2Qv','aHR0cHM6Ly9hcGkubml1bml1enkubWUvYXBpLnBocC9wcm92aWRlL3ZvZC8=','aHR0cHM6Ly9hcGkuenVpZGFwaS5jb20vYXBpLnBocC9wcm92aWRlL3ZvZC8=','天堂2','aHR0cDovL2FwaS4xMDgwenlrdS5jb20vaW5jL2FwaV9tYWMxMC5waHAvcHJvdmlkZS92b2Qv','aHR0cHM6Ly94a2FuenkuY29tL2FwaS5waHAvcHJvdmlkZS92b2Qv','aHR0cDovL3NkenlhcGkuY29tL2FwaS5waHAvcHJvdmlkZS92b2Qv','aHR0cHM6Ly9hcGkueGlubGFuZ2FwaS5jb20veGlubGFuZ2FwaS5waHAvcHJvdmlkZS92b2Qv','6983112SaXmfs','3WkLURd','aHR0cHM6Ly9jai55YXlhenkubmV0L2FwaS5waHAvcHJvdmlkZS92b2Qv','10JINYmO','aHR0cHM6Ly9zdWJvY2FpamkuY29tL2FwaS5waHAvcHJvdmlkZS92b2Qv','aHR0cHM6Ly93d3cubWR6eWFwaS5jb20vYXBpLnBocC9wcm92aWRlL3ZvZC8=','aHR0cHM6Ly9tM3U4LmFwaXloenkuY29tL2FwaS5waHAvcHJvdmlkZS92b2Qv','39影','iqiyi','aHR0cHM6Ly90eXlzenlhcGkuY29tL2FwaS5waHAvcHJvdmlkZS92b2Qv','量子2','aHR0cHM6Ly93d3cuaHV5YWFwaS5jb20vYXBpLnBocC9wcm92aWRlL3ZvZC8=','闪电3','aHR0cHM6Ly8zNjB6eS5jb20vYXBpLnBocC9wcm92aWRlL3ZvZC8=','1080','aHR0cHM6Ly9jYWlqaS5keXR0enlhcGkuY29tL2FwaS5waHAvcHJvdmlkZS92b2Qv','金鹰2','aHR0cHM6Ly9hcGkuZ3VhbmdzdWFwaS5jb20vYXBpLnBocC9wcm92aWRlL3ZvZC8=','aHR0cHM6Ly9zdW9uaWFwaS5jb20vYXBpLnBocC9wcm92aWRlL3ZvZC8=','aHR0cDovL3NhdnZpdXV4LmhrMy4zNDU4ODgueHl6LmNkbi5jbG91ZGZsYXJlLm5ldC9hcGkucGhwL3Byb3ZpZGUvdm9kLw==','aHR0cHM6Ly90YW9waWFuYXBpLmNvbS9jamFwaS9tYzEwL3ZvZC8=','aHR0cHM6Ly9hcGkud3VqaW5hcGkubWUvYXBpLnBocC9wcm92aWRlL3ZvZC8='];_0x3993=function(){return _0x315e35;};return _0x3993();}function _0x40ee(_0xafcd45,_0x5ad1a7){const _0x39933d=_0x3993();return _0x40ee=function(_0x40ee1f,_0x18a59a){_0x40ee1f=_0x40ee1f-0x110;let _0x3e1091=_0x39933d[_0x40ee1f];return _0x3e1091;},_0x40ee(_0xafcd45,_0x5ad1a7);}const RAW_APIS=[{'n':_0x4cbb5a(0x12f),'u':atob(_0x4cbb5a(0x149))},{'n':_0x4cbb5a(0x144),'u':atob('aHR0cHM6Ly9pcWl5aXp5YXBpLmNvbS9hcGkucGhwL3Byb3ZpZGUvdm9kLw==')},{'n':'百度','u':atob(_0x4cbb5a(0x119))},{'n':'艾旦','u':atob(_0x4cbb5a(0x118))},{'n':'蛋蛋','u':atob('aHR0cDovL2RkbWYubmV0L2FwaS5waHAvcHJvdmlkZS92b2Qv')},{'n':'新浪','u':atob(_0x4cbb5a(0x13b))},{'n':'享看','u':atob(_0x4cbb5a(0x139))},{'n':'魔都','u':atob(_0x4cbb5a(0x134))},{'n':_0x4cbb5a(0x137),'u':atob('aHR0cDovL2NhaWppLmR5dHR6eWFwaS5jb20vYXBpLnBocC9wcm92aWRlL3ZvZC8=')},{'n':'快龙','u':atob('aHR0cHM6Ly9sei4xMTgzMTgueHl6L2FwaS5waHAvcHJvdmlkZS92b2Qv')},{'n':'猫眼','u':atob('aHR0cHM6Ly9hcGkubWFveWFuYXBpLnRvcC9hcGkucGhwL3Byb3ZpZGUvdm9kLw==')},{'n':'森林','u':atob(_0x4cbb5a(0x121))},{'n':'花旗','u':atob(_0x4cbb5a(0x12d))},{'n':'量子','u':atob('aHR0cHM6Ly9jai5semlhcGkuY29tL2FwaS5waHAvcHJvdmlkZS92b2Qv')},{'n':_0x4cbb5a(0x146),'u':atob('aHR0cDovL3d3dy5senp5LnR2L2FwaS5waHAvcHJvdmlkZS92b2Qv')},{'n':'索尼','u':atob(_0x4cbb5a(0x113))},{'n':'快车','u':atob(_0x4cbb5a(0x12b))},{'n':'最大','u':atob(_0x4cbb5a(0x136))},{'n':'牛牛','u':atob(_0x4cbb5a(0x135))},{'n':'OK','u':atob(_0x4cbb5a(0x120))},{'n':_0x4cbb5a(0x148),'u':atob('aHR0cHM6Ly94c2Quc2R6eWFwaS5jb20vYXBpLnBocC9wcm92aWRlL3ZvZC8=')},{'n':'闪电','u':atob(_0x4cbb5a(0x13a))},{'n':'鸭鸭','u':atob(_0x4cbb5a(0x13e))},{'n':'无尽','u':atob(_0x4cbb5a(0x116))},{'n':'光速','u':atob(_0x4cbb5a(0x112))},{'n':'速播','u':atob(_0x4cbb5a(0x140))},{'n':'速播2','u':atob('aHR0cDovL3N1Ym96aXl1YW4ubmV0L2FwaS5waHAvcHJvdmlkZS92b2Qv')},{'n':_0x4cbb5a(0x111),'u':atob('aHR0cHM6Ly9qeXp5YXBpLmNvbS9hcGkucGhwL3Byb3ZpZGUvdm9kLw==')},{'n':'金鹰','u':atob(_0x4cbb5a(0x11a))},{'n':'红牛','u':atob(_0x4cbb5a(0x12a))},{'n':'豪华','u':atob('aHR0cHM6Ly9oaHp5YXBpLmNvbS9hcGkucGhwL3Byb3ZpZGUvdm9kLw==')},{'n':'虎牙','u':atob(_0x4cbb5a(0x147))},{'n':'极速','u':atob('aHR0cHM6Ly9qc3p5YXBpLmNvbS9hcGkucGhwL3Byb3ZpZGUvdm9kLw==')},{'n':'樱花','u':atob(_0x4cbb5a(0x142))},{'n':_0x4cbb5a(0x131),'u':atob(_0x4cbb5a(0x129))},{'n':'非凡','u':atob('aHR0cHM6Ly9hcGkuZmZ6eWFwaS5jb20vYXBpLnBocC9wcm92aWRlL3ZvZC8=')},{'n':'飘零2','u':atob(_0x4cbb5a(0x132))},{'n':'一零','u':atob(_0x4cbb5a(0x138))},{'n':'优质','u':atob(_0x4cbb5a(0x123))},{'n':_0x4cbb5a(0x14a),'u':atob(_0x4cbb5a(0x117))},{'n':_0x4cbb5a(0x11e),'u':atob(_0x4cbb5a(0x141))},{'n':'天堂','u':atob(_0x4cbb5a(0x110))},{'n':'ikun','u':atob('aHR0cHM6Ly9pa3VuenlhcGkuY29tL2FwaS5waHAvcHJvdmlkZS92b2Qv')},{'n':'天涯','u':atob(_0x4cbb5a(0x145))},{'n':'U酷2','u':atob('aHR0cHM6Ly9hcGkudWt1YXBpODguY29tL2FwaS5waHAvcHJvdmlkZS92b2Qv')},{'n':'U酷','u':atob('aHR0cHM6Ly9hcGkudWt1YXBpLmNvbS9hcGkucGhwL3Byb3ZpZGUvdm9kLw==')},{'n':'无忧','u':atob('aHR0cHM6Ly93d3cud3l2b2QuY29tL2FwaS5waHAvcHJvdmlkZS92b2Qv')},{'n':'CK','u':atob(_0x4cbb5a(0x11d))},{'n':'天空','u':atob(_0x4cbb5a(0x124))},{'n':'麒麟','u':atob('aHR0cHM6Ly93d3cucWlsaW56eXouY29tL2FwaS5waHAvcHJvdmlkZS92b2Qv')},{'n':'快鹰','u':atob(_0x4cbb5a(0x114))},{'n':_0x4cbb5a(0x11c),'u':atob('aHR0cHM6Ly9jYWlqaS5rY3p5YXBpLmNvbS9hcGkucGhwL3Byb3ZpZGUvdm9kLw==')},{'n':'淘片','u':atob(_0x4cbb5a(0x115))},{'n':'影图','u':atob(_0x4cbb5a(0x126))},{'n':'云解','u':atob('aHR0cHM6Ly9hcGkueXBhcnNlLmNvbS9hcGkvcHJvdmlkZS92b2Qv')},{'n':_0x4cbb5a(0x143),'u':atob('aHR0cHM6Ly93d3cuMzlrYW4uY29tL2FwaS5waHAvcHJvdmlkZS92b2Qv')}]; const _PRIORITY_ORDER = ['iqiy', '天堂', '360', '猫眼', '魔都', '1080', '红牛', 'ikun', '速播2', 'U酷', '非凡', '豪华']; const _LOW_PRIORITY = new Set(['量子', '最大']); 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) }); }); const apis = Array.from(m.values()); const priority = apis.filter(a => ApiStats._priority.has(a.shortName)); priority.sort((a, b) => { const ai = _PRIORITY_ORDER.indexOf(a.shortName); const bi = _PRIORITY_ORDER.indexOf(b.shortName); return (ai === -1 ? 999 : ai) - (bi === -1 ? 999 : bi); }); const low = apis.filter(a => _LOW_PRIORITY.has(a.shortName)); const others = apis.filter(a => !ApiStats._priority.has(a.shortName) && !_LOW_PRIORITY.has(a.shortName)); if (CONFIG.SMART_SORTING) { others.sort((a, b) => ApiStats.score(ApiStats.get(b.shortName), b.shortName) - ApiStats.score(ApiStats.get(a.shortName), a.shortName)); } return [...priority, ...others, ...low]; }; const UNIQUE_APIS = processApis(RAW_APIS); /* ========== 运行状态 ========== */ 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(), failedSources: new Set(), closed: false, searchId: 0, playing: false, switchCount: 0, stuckPending: false, isPageLoad: true, switching: false, lastSwitchTime: 0, isManualSelect: false // 记录是否为用户手动选源 }; const EP_MATCH_PATTERNS = [ { regex: /第\s*(\d+)\s*集/, group: 1 }, { regex: /第\s*(\d+)\s*话/, group: 1 }, { regex: /第\s*(\d+)\s*期/, group: 1 }, { regex: /EP\s*(\d+)/i, group: 1 }, { regex: /Ep\s*(\d+)/, group: 1 }, { regex: /ep\s*(\d+)/, group: 1 }, { regex: /E(\d+)/i, group: 1 }, { regex: /(\d+)\s*集/, group: 1 }, { regex: /(\d+)\s*话/, group: 1 }, { regex: /(\d+)\s*期/, group: 1 }, { regex: /\[(\d+)\]/, group: 1 }, { regex: /【(\d+)】/, group: 1 }, { regex: /((\d+))/, group: 1 }, { regex: /\((\d+)\)/, group: 1 } ]; function matchEpisodeEnhanced(epName, targetNum) { if (!epName || !targetNum) return false; const cleanName = epName.trim(); const num = parseInt(targetNum, 10); if (/^\d+$/.test(cleanName) && parseInt(cleanName, 10) === num) return true; for (const pattern of EP_MATCH_PATTERNS) { const match = cleanName.match(pattern.regex); if (match && match[pattern.group]) { if (parseInt(match[pattern.group], 10) === num) return true; } } if (cleanName.includes(targetNum)) { const idx = cleanName.indexOf(targetNum); const before = idx === 0 ? '' : cleanName.charAt(idx - 1); const after = idx + targetNum.length >= cleanName.length ? '' : cleanName.charAt(idx + targetNum.length); if ((/\D/.test(before) || idx === 0) && (/\D/.test(after) || idx + targetNum.length === cleanName.length)) { return true; } } return false; } const onVideoPage = () => { if (CONFIG.VIDEO_URL_PATTERNS.some(p => p.test(location.href))) return true; const hn = location.hostname; if ((hn.includes('miguvideo.com') || hn.includes('cmvideo.cn')) && (document.querySelector('#wPlayer') || document.querySelector('[class*="migu-player"]') || document.querySelector('[class*="commonPlayer"]'))) { return true; } return false; }; /* ========== UI 与交互交互系统 ========== */ const UI = { init() { this._css(); State.dom.c = this._el('div', { id: 'vip-root' }); State.dom.btn = this._el('div', { id: 'vip-btn', title: '点击展开/解析VIP视频 (可拖拽)' }); State.dom.btn.innerHTML = '
VIP
'; State.dom.p = this._el('div', { id: 'vip-panel' }); State.dom.ov = this._el('div', { id: 'vip-overlay' }); 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('vip-iframe'); State.dom.cls = document.getElementById('vip-close'); State.dom.toast = this._el('div', { id: 'vip-toast' }); document.body.append(State.dom.toast); this._drag(); State.dom.cls.onclick = () => Player.close(); State.dom.p.onmouseenter = () => clearTimer('panel_leave'); State.dom.p.onmouseleave = () => { State.timers.panel_leave = setTimeout(() => { if (State.panelOpen && !State.dom.btn.matches(':hover')) hideP(); }, CONFIG.PANEL_LEAVE_CLOSE_DELAY); }; window.addEventListener('resize', () => { clearTimer('resize'); State.timers.resize = setTimeout(() => Player.repos(), 150); }, { passive: true }); window.addEventListener('message', e => Player.onMsg(e), { passive: true }); }, _el: (tag, props = {}) => Object.assign(document.createElement(tag), props), _css() { GM_addStyle(` #vip-root { position: fixed; z-index: 2147483647; user-select: none; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; } #vip-btn { width: 44px; height: 44px; border-radius: 50%; background: linear-gradient(135deg, #2563eb, #7c3aed); cursor: grab; padding: 3px; box-sizing: border-box; box-shadow: 0 8px 24px rgba(124, 58, 237, 0.4), 0 0 0 2px rgba(255, 255, 255, 0.2); transition: transform 0.2s cubic-bezier(0.34, 1.56, 0.64, 1), box-shadow 0.2s ease; display: flex; align-items: center; justify-content: center; } #vip-btn:hover { transform: scale(1.08); box-shadow: 0 10px 28px rgba(124, 58, 237, 0.6), 0 0 0 3px rgba(255, 255, 255, 0.4); } #vip-btn:active { cursor: grabbing; transform: scale(0.95); } .vip-btn-inner { width: 100%; height: 100%; border-radius: 50%; background: rgba(15, 23, 42, 0.85); display: flex; align-items: center; justify-content: center; color: #fff; font-weight: 800; font-size: 13px; letter-spacing: 0.5px; backdrop-filter: blur(4px); } .vip-btn-inner span { background: linear-gradient(120deg, #60a5fa, #c084fc); -webkit-background-clip: text; -webkit-text-fill-color: transparent; } #vip-btn.loading .vip-btn-inner span { display: none; } #vip-btn.loading .vip-btn-inner::after { content: ""; width: 16px; height: 16px; border: 2px solid rgba(255, 255, 255, 0.2); border-top-color: #a855f7; border-radius: 50%; animation: vip-spin 0.8s linear infinite; } @keyframes vip-spin { to { transform: rotate(360deg); } } #vip-panel { display: none; position: absolute; left: 54px; top: 0; width: 320px; max-height: 75vh; background: rgba(15, 23, 42, 0.88); backdrop-filter: blur(20px); -webkit-backdrop-filter: blur(20px); border: 1px solid rgba(255, 255, 255, 0.12); border-radius: 16px; padding: 12px; box-sizing: border-box; box-shadow: 0 20px 50px rgba(0, 0, 0, 0.6), 0 0 0 1px rgba(255, 255, 255, 0.05); flex-direction: column; gap: 8px; color: #e2e8f0; } .vip-header { display: flex; align-items: center; justify-content: space-between; padding: 6px 10px; background: rgba(255, 255, 255, 0.06); border-radius: 10px; font-size: 13px; font-weight: 600; color: #93c5fd; } .vip-header-btn { cursor: pointer; padding: 2px 8px; border-radius: 6px; background: rgba(59, 130, 246, 0.2); border: 1px solid rgba(59, 130, 246, 0.3); color: #bfdbfe; font-size: 12px; transition: all 0.2s; } .vip-header-btn:hover { background: rgba(59, 130, 246, 0.4); color: #fff; } .vip-list-wrap { display: grid; grid-template-columns: repeat(2, 1fr); gap: 6px; overflow-y: auto; max-height: 52vh; padding-right: 4px; box-sizing: border-box; } .vip-list-wrap.ep-mode { grid-template-columns: repeat(4, 1fr); } .vip-item-btn { display: flex; align-items: center; justify-content: center; padding: 8px 6px; background: rgba(255, 255, 255, 0.05); border: 1px solid rgba(255, 255, 255, 0.06); border-radius: 8px; color: #cbd5e1; font-size: 12px; font-weight: 500; cursor: pointer; transition: all 0.15s ease; text-align: center; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .vip-item-btn:hover { background: rgba(124, 58, 237, 0.25); border-color: rgba(168, 85, 247, 0.4); color: #fff; transform: translateY(-1px); } .vip-item-btn.active { background: linear-gradient(135deg, #2563eb, #7c3aed) !important; border-color: transparent !important; color: #fff !important; box-shadow: 0 4px 12px rgba(124, 58, 237, 0.4); font-weight: 600; } .vip-item-btn .quality-badge { font-size: 10px; margin-left: 4px; opacity: 0.8; color: #38bdf8; } .vip-list-wrap::-webkit-scrollbar { width: 5px; } .vip-list-wrap::-webkit-scrollbar-thumb { background: rgba(255, 255, 255, 0.2); border-radius: 4px; } #vip-overlay { position: absolute; background: #000; z-index: 2147483646; display: none; border-radius: 4px; overflow: hidden; box-shadow: 0 10px 40px rgba(0, 0, 0, 0.8); } #vip-iframe { width: 100%; height: 100%; border: none; display: block; } #vip-close { position: absolute; top: 12px; right: 12px; z-index: 2147483647; width: 32px; height: 32px; border-radius: 50%; background: rgba(15, 23, 42, 0.75); backdrop-filter: blur(8px); border: 1px solid rgba(255, 255, 255, 0.2); color: #f1f5f9; cursor: pointer; display: flex; align-items: center; justify-content: center; font-size: 14px; transition: all 0.2s ease; } #vip-close:hover { background: #ef4444; border-color: #ef4444; color: #fff; transform: scale(1.1); } #vip-toast { position: fixed; top: 24px; left: 50%; transform: translateX(-50%) translateY(-20px); background: rgba(15, 23, 42, 0.92); backdrop-filter: blur(12px); color: #93c5fd; font-size: 13px; font-weight: 500; padding: 8px 18px; border-radius: 30px; border: 1px solid rgba(59, 130, 246, 0.3); box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5); pointer-events: none; opacity: 0; z-index: 2147483647; transition: all 0.3s cubic-bezier(0.34, 1.56, 0.64, 1); } #vip-toast.show { opacity: 1; transform: translateX(-50%) translateY(0); } `); }, _drag() { let isDragging = false, hasMoved = false, startX, startY, origLeft, origTop; const c = State.dom.c; const pos = GM_getValue(CONFIG.STORAGE_KEY_ICON_POSITION, { l: 20, t: 200 }); c.style.left = `${pos.l}px`; c.style.top = `${pos.t}px`; const onMove = e => { if (!isDragging) return; const dx = e.clientX - startX; const dy = e.clientY - startY; if (Math.abs(dx) > 3 || Math.abs(dy) > 3) hasMoved = true; if (!hasMoved) return; let nx = origLeft + dx; let ny = origTop + dy; nx = Math.max(8, Math.min(innerWidth - 52, nx)); ny = Math.max(8, Math.min(innerHeight - 52, ny)); c.style.left = `${nx}px`; c.style.top = `${ny}px`; }; const onUp = () => { if (!isDragging) return; isDragging = false; document.body.style.userSelect = ''; if (hasMoved) { const rect = c.getBoundingClientRect(); const snapLeft = rect.left < innerWidth / 2 ? 14 : innerWidth - 58; c.style.transition = 'left 0.3s cubic-bezier(0.34, 1.56, 0.64, 1)'; c.style.left = `${snapLeft}px`; setTimeout(() => { c.style.transition = ''; }, 300); GM_setValue(CONFIG.STORAGE_KEY_ICON_POSITION, { l: snapLeft, t: rect.top }); } removeEventListener('mousemove', onMove, true); removeEventListener('mouseup', onUp, true); }; c.addEventListener('mousedown', e => { if (e.button !== 0 || e.target.closest('#vip-panel')) return; isDragging = true; hasMoved = false; startX = e.clientX; startY = e.clientY; const r = c.getBoundingClientRect(); origLeft = r.left; origTop = r.top; document.body.style.userSelect = 'none'; addEventListener('mousemove', onMove, true); addEventListener('mouseup', onUp, true); }); State.dom.btn.onclick = e => { e.stopPropagation(); if (hasMoved) return; if (State.panelOpen) { hideP(); } else { const currentUrl = location.href; if (State.cache.key === currentUrl && State.cache.results.length) { State.closed = false; showP(); renderSrc(); } else { showP(); Search.go(); } } }; }, addSrc(r) { const list = document.querySelector('#vip-list'); if (!list || !State.panelOpen || list.classList.contains('ep-mode')) return; const old = list.querySelector(`[data-name="${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 : 1); const btn = UI._el('div', { className: `vip-item-btn ${r.name === State.activeName ? 'active' : ''}`, innerHTML: `${r.name}${r.resolution ? r.resolution + 'P' : cnt + '集'}`, onclick() { State.activeName = r.name; State.isManualSelect = true; // 用户主动点击 UI.epList(r, false, State.curEp); } }); btn.dataset.name = r.name; list.appendChild(btn); }, epList(src, auto = false, cur = null, clearFailed = true) { if (State.closed) return; clearAll(); const panel = State.dom.p; panel.innerHTML = ''; const header = UI._el('div', { className: 'vip-header' }); header.innerHTML = `${src.name} - 选集
‹ 返回源列表
`; header.querySelector('.vip-header-btn').onclick = () => renderSrc(); const list = UI._el('div', { className: 'vip-list-wrap ep-mode', id: 'vip-list' }); panel.append(header, list); State.eps = []; const pu = src.data?.vod_play_url || ''; if (!pu) { header.querySelector('span').textContent = '该源无有效播放地址'; return; } const rawEps = pu.includes('$$$') ? pu.split('$$$').pop().split('#') : (pu.includes('#') ? pu.split('#') : [pu]); const frag = document.createDocumentFragment(); let idx = 0; rawEps.forEach(epStr => { const [nm, uStr] = epStr.split('$'); const name = (nm || '').trim(); const url = (uStr || nm || '').trim(); if (!name && !url) return; idx++; const finalName = name || `第${idx}集`; State.eps.push({ name: finalName, url }); const btn = UI._el('div', { className: 'vip-item-btn', textContent: finalName, onclick() { const epNum = Utils.epNum(finalName); if (epNum) State.curEp = epNum; if (clearFailed) { State.switchCount = 0; State.failed.clear(); State.failedSources.clear(); } State.isManualSelect = true; Player.start(url); UI.highlightPlayingEpisode(url); } }); btn.dataset.url = url; btn.dataset.name = finalName; frag.appendChild(btn); }); list.appendChild(frag); _adjustPanelPosition(); if (!cur) { if (auto && State.eps.length) { const best = _bestMovie(State.eps); if (best) { UI.highlightPlayingEpisode(best.url); if (clearFailed) { State.switchCount = 0; State.failed.clear(); State.failedSources.clear(); } Player.start(best.url); } } return; } const targetNum = String(parseInt(cur, 10)); const btns = Array.from(list.querySelectorAll('.vip-item-btn')); let matchedBtn = btns.find(b => matchEpisodeEnhanced(b.dataset.name, targetNum)) || btns.find(b => b.dataset.name.includes(targetNum)); if (matchedBtn) { matchedBtn.classList.add('active'); setTimeout(() => matchedBtn.scrollIntoView({ behavior: 'smooth', block: 'center' }), 100); if (auto) { if (clearFailed) { State.switchCount = 0; State.failed.clear(); State.failedSources.clear(); } Player.start(matchedBtn.dataset.url); } } }, highlightPlayingEpisode(url) { const list = document.querySelector('#vip-list'); if (!list) return; list.querySelectorAll('.vip-item-btn').forEach(b => b.classList.remove('active')); const target = list.querySelector(`[data-url="${CSS.escape(url)}"]`); if (target) { target.classList.add('active'); target.scrollIntoView({ behavior: 'smooth', block: 'center' }); } }, toggleLoading: v => State.dom.btn.classList.toggle('loading', v), toast(msg, time = 2400) { const t = State.dom.toast; if (!t) return; t.textContent = msg; t.classList.add('show'); clearTimeout(State.timers.toast); State.timers.toast = setTimeout(() => t.classList.remove('show'), time); }, dismissToast() { clearTimeout(State.timers.toast); if (State.dom.toast) State.dom.toast.classList.remove('show'); } }; function _adjustPanelPosition() { const r = State.dom.c.getBoundingClientRect(); const panel = State.dom.p; if (r.right + 340 > innerWidth) { panel.style.left = 'auto'; panel.style.right = '54px'; } else { panel.style.left = '54px'; panel.style.right = 'auto'; } } function renderSrc() { const panel = State.dom.p; panel.innerHTML = ''; _adjustPanelPosition(); const header = UI._el('div', { className: 'vip-header' }); header.innerHTML = `解析源 (共 ${State.cache.results.length} 个)`; const list = UI._el('div', { className: 'vip-list-wrap', id: 'vip-list' }); panel.append(header, list); const frag = document.createDocumentFragment(); State.cache.results.forEach(r => { const pu = r.data?.vod_play_url || ''; const cnt = pu.includes('$$$') ? pu.split('$$$').pop().split('#').length : (pu.includes('#') ? pu.split('#').length : 1); const btn = UI._el('div', { className: `vip-item-btn ${r.name === State.activeName ? 'active' : ''}`, innerHTML: `${r.name}${r.resolution ? r.resolution + 'P' : cnt + '集'}`, onclick() { State.activeName = r.name; State.isManualSelect = true; // 标记用户主动选择 UI.epList(r, false, State.curEp); } }); btn.dataset.name = r.name; frag.appendChild(btn); }); list.appendChild(frag); } function showP() { State.dom.p.style.display = 'flex'; State.panelOpen = true; _adjustPanelPosition(); } function hideP() { State.dom.p.style.display = 'none'; State.panelOpen = false; clearTimer('panel_leave'); } 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) { if (State.timers[t]) { clearInterval(State.timers[t]); clearTimeout(State.timers[t]); State.timers[t] = null; } } function clearAll() { for (const t in State.timers) clearTimer(t); } /* ========== 解析与搜索调度系统 ========== */ const Search = { async go() { clearAll(); UI.toggleLoading(true); const title = Utils.title(); if (!title) { UI.toast('无法获取视频标题,请在播放页重试'); UI.toggleLoading(false); return; } const curEp = await Utils.curEp(); const cacheKey = `${title}_${curEp || 'main'}`; if (!State.isPageLoad) { const cached = SearchCache.get(cacheKey); if (cached && cached.length) { State.cache = { key: location.href, results: cached }; State.activeName = null; State.firstAuto = false; State.closed = false; State.switchCount = 0; State.failed.clear(); State.failedSources.clear(); const best = cached.find(r => r.score >= 50) || cached[0]; if (best) { State.activeName = best.name; State.isManualSelect = false; UI.epList(best, true, curEp); hideP(); } else { showP(); renderSrc(); } UI.toggleLoading(false); return; } } State.isPageLoad = false; State.cache = { key: location.href, results: [] }; State.activeName = null; State.firstAuto = false; State.failed.clear(); State.closed = false; const searchId = Date.now(); State.searchId = searchId; State.curEp = curEp; renderSrc(); this._search(title, curEp, searchId, cacheKey); }, async _search(title, curEp, id, cacheKey) { const handleOneApi = async api => { if (State.closed || State.searchId !== id) return; const res = await this._one(api, title); if (!res) { ApiStats.fail(api.name); return; } ApiStats.ok(api.name, res.latency); if (State.closed || State.searchId !== id) return; const eps = res.data.vod_play_url.split('$$$').pop().split('#'); let targetUrl = null, isMatch = false, isMovie = false; const numEp = curEp ? String(parseInt(curEp, 10)) : null; const mEps = [], nmEps = []; for (const ep of eps) { const [nm] = ep.split('$'); if (nm && CONFIG.MOVIE_KEYWORDS.test(nm.trim())) mEps.push(ep); else nmEps.push(ep); } 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; } } } targetUrl = best.split('$')[1] || best.split('$')[0]; } else if (numEp && nmEps.length) { let te = nmEps.find(ep => { const [nm] = ep.split('$'); const en = Utils.epNum(nm); return en && String(parseInt(en, 10)) === numEp; }); if (!te) te = nmEps.find(ep => { const [nm] = ep.split('$'); return nm && nm.includes(numEp); }); if (te) { targetUrl = te.split('$')[1] || te.split('$')[0]; isMatch = true; } } else if (nmEps.length) { const fe = nmEps[0] || eps[0]; if (fe && fe.includes('$')) targetUrl = fe.split('$')[1] || fe.split('$')[0]; } else if (eps.length && eps[0].includes('$')) { targetUrl = eps[0].split('$')[1] || eps[0].split('$')[0]; } if (!targetUrl) { _insertResult({ ...res, score: 50, resolution: 0, latency: res.latency }); return; } const ev = await Utils.evalSrc(targetUrl); const finalRes = { ...res, score: ev ? Math.max(0, Math.min(100, ev.score)) : 50, resolution: ev ? ev.resolution : 0, latency: ev ? ev.latency : res.latency, evaluatedUrl: ev ? ev.url : targetUrl }; const autoPlay = isMovie || isMatch; if (!State.firstAuto && autoPlay) { State.firstAuto = true; State.activeName = finalRes.name; State.isManualSelect = false; State.switchCount = 0; State.failed.clear(); UI.epList(finalRes, true, isMovie ? null : State.curEp); hideP(); UI.toast(`已自动优选: ${finalRes.name}`); } _insertResult(finalRes); function _insertResult(item) { const idx = State.cache.results.findIndex(x => x.name === item.name); if (idx > -1) State.cache.results[idx] = item; else State.cache.results.push(item); State.cache.results.sort((a, b) => b.score - a.score); UI.addSrc(item); } }; const priorityApis = UNIQUE_APIS.filter(a => ApiStats._priority.has(a.shortName)); const otherApis = UNIQUE_APIS.filter(a => !ApiStats._priority.has(a.shortName)); await Utils.pool(8, priorityApis, handleOneApi); Utils.pool(CONFIG.SEARCH_CONCURRENCY, otherApis, handleOneApi).then(() => { if (State.searchId !== id || State.closed) return; UI.toggleLoading(false); if (State.cache.results.length > 0) { SearchCache.set(cacheKey, State.cache.results); } else { UI.toast('未找到可用解析源,请刷新重试'); } }); }, async _one(api, title) { 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) return { name: api.name, data: first, latency: l.latency }; const vd = await Utils.req(`ac=detail&ids=${first.vod_id}`, api); const v = vd.data?.list?.[0]; if (v?.vod_play_url) return { name: api.name, data: v, latency: l.latency + vd.latency }; } const d = await Utils.req(`ac=detail&wd=${wd}`, api); if (d.data?.list?.[0]?.vod_play_url) return { name: api.name, data: d.data.list[0], latency: d.latency }; return null; } catch (e) { return null; } } }; /* ========== 播放器与核心沙箱 ========== */ const Player = { _pos(attempt = 0) { if (State.closed || attempt > 15) return; let targetRect = 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 { targetRect = State.hiddenEl.getBoundingClientRect(); } catch (e) {} } if (targetRect && targetRect.width > 200 && targetRect.height > 100) { State.dom.ov.style.cssText = `position:absolute;top:${targetRect.top + scrollY}px;left:${targetRect.left + scrollX}px;width:${targetRect.width}px;height:${targetRect.height}px;display:block;z-index:2147483646;`; } else if (State.curUrl && attempt > 6) { State.dom.ov.style.cssText = 'position:fixed;top:50%;left:50%;width:86%;height:80%;transform:translate(-50%,-50%);display:block;z-index:2147483646;box-shadow:0 0 50px rgba(0,0,0,0.8);border-radius:8px;'; } else if (attempt <= 15) { requestAnimationFrame(() => this._pos(attempt + 1)); } }, async start(url) { if (State.closed) return; clearAll(); hideP(); UI.dismissToast(); State.curUrl = url; UI.highlightPlayingEpisode(url); State.playing = true; this._pauseOrig(); this._pos(0); State.dom.ov.style.display = 'block'; const resolvedUrl = await Utils.resolveUrl(url); this._play(resolvedUrl); }, _play(url) { if (State.closed) return; clearTimer('stuck_watchdog'); State.dom.ifr.srcdoc = this._html(url); State.dom.ifr.onload = () => { if (State.closed) return; State.stuckPending = true; // 核心修复:如果是用户手动选源,绝不超时暴力切源,仅监控死链 if (!State.isManualSelect) { State.timers.stuck_watchdog = setTimeout(() => { if (State.stuckPending && !State.closed && !State.isManualSelect) { State.switching = false; this._switch(); } }, CONFIG.STUCK_CHECK_TIMEOUT); } setTimeout(() => this.repos(), 200); }; }, _switch() { if (State.switching) return; const now = Date.now(); if (now - State.lastSwitchTime < 2500) return; State.switching = true; State.lastSwitchTime = now; try { if (!State.cache.results.length) { State.switching = false; return; } State.stuckPending = false; clearTimer('stuck_watchdog'); UI.toast('源加载缓慢,正在为你自动切换备用源...'); // 延迟2秒再执行实际切换,给当前源最后机会 clearTimer('pre_switch'); State.timers.pre_switch = setTimeout(() => { if (State.closed) { State.switching = false; return; } this._doSwitch(); }, 2000); } catch (e) { State.switching = false; } }, _doSwitch() { try { State.switchCount++; State.failed.add(State.curUrl); if (State.activeName) State.failedSources.add(State.activeName); if (State.failedSources.size >= State.cache.results.length) { UI.toast('所有解析源已轮询完毕'); State.switching = false; return; } const epNum = parseInt(State.curEp, 10); let nextFound = false; for (const r of State.cache.results) { if (State.failedSources.has(r.name) || !r.data?.vod_play_url) continue; const eps = r.data.vod_play_url.split('$$$').pop().split('#'); let targetEp = null; if (isNaN(epNum)) { targetEp = eps.find(ep => { const [, u] = ep.split('$'); return u && !State.failed.has(u); }); } else { targetEp = eps.find(ep => { const [nm, u] = ep.split('$'); return matchEpisodeEnhanced(nm, String(epNum)) && !State.failed.has(u); }); } if (targetEp) { State.activeName = r.name; nextFound = true; State.isManualSelect = false; UI.epList(r, true, State.curEp, false); break; } } if (!nextFound) { State.switching = false; } } catch (e) { State.switching = false; } }, _html(url) { const cleanUrl = url.replace(/'/g, "\\'").replace(/"/g, '"'); return ` VIP Play