金牌影院
https://vv3nwjk.com
zpccool (13551) 2天前 下载:615
视频
优化清晰度选择,优化登录令牌获取;崩溃优化
// @name 金牌影院
// @uuid jinpaiyinyuan
// @url https://vv3nwjk.com
// @type video
// @version 1.3.5
// @author AI
// @enabled true
// @description 优化清晰度选择,优化登录令牌获取;崩溃优化
var BASE = 'https://vv3nwjk.com';
var WWW_BASE = 'https://www.vv3nwjk.com';
var API_BASE = BASE + '/api';
var SIGN_KEY = 'cb808529bae6b6be45ecfab29a4889bc';
var SCOPE = 'jpyy_vv3nwjk';
var UA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:150.0) Gecko/20100101 Firefox/150.0';
var AUTH_KEYS = ['authorization', 'token', 'accessToken', 'loginToken', 'userToken'];
var CATEGORIES = {
'首页': '/',
'电影': '/vod/show/id/1',
'电视剧': '/vod/show/id/2',
'综艺': '/vod/show/id/3',
'动漫': '/vod/show/id/4'
};
var PAGE_HEADERS = {
'User-Agent': UA,
'Accept': 'application/json, text/plain, */*',
'Accept-Language': 'zh-CN,zh;q=0.9',
'Referer': BASE + '/',
'Origin': BASE,
'client-type': '1'
};
var PLAY_HEADERS = {
'User-Agent': UA,
'Accept': '*/*',
'Accept-Language': 'zh-CN,zh;q=0.9,zh-TW;q=0.8,zh-HK;q=0.7,en-US;q=0.6,en;q=0.5',
'Accept-Encoding': 'gzip, deflate, br, zstd',
'Referer': BASE + '/',
'Origin': BASE,
'Sec-Fetch-Dest': 'empty',
'Sec-Fetch-Mode': 'cors',
'Sec-Fetch-Site': 'cross-site'
};
function init() {
legado.log('[init] 金牌影院_vv ready');
}
function log(msg) {
try {
legado.log(msg);
} catch (e) {}
}
async function httpGetRetry(url, headers) {
var text = '';
var lastErr = null;
for (var attempt = 0; attempt < 3; attempt++) {
try {
text = await legado.http.get(url, headers);
lastErr = null;
break;
} catch (e) {
lastErr = e;
}
}
if (lastErr) throw lastErr;
return text;
}
function trim(s) {
return String(s || '').replace(/^\s+|\s+$/g, '');
}
function stripHtml(s) {
return trim(String(s || '')
.replace(/<script[\s\S]*?<\/script>/gi, '')
.replace(/<style[\s\S]*?<\/style>/gi, '')
.replace(/<[^>]+>/g, ' ')
.replace(/ /g, ' ')
.replace(/&/g, '&')
.replace(/"/g, '"')
.replace(/'/g, "'")
.replace(/\s+/g, ' '));
}
function decodeJsonString(s) {
s = String(s || '');
try {
return JSON.parse('"' + s.replace(/\\/g, '\\\\').replace(/"/g, '\\"') + '"');
} catch (e) {
return s
.replace(/\\u([0-9a-fA-F]{4})/g, function(all, hex) { return String.fromCharCode(parseInt(hex, 16)); })
.replace(/\\r\\n|\\n|\\r/g, ' ')
.replace(/\\"/g, '"')
.replace(/\\\\/g, '\\');
}
}
function normalizeNextData(html) {
return String(html || '')
.replace(/\\u0026/g, '&')
.replace(/\\u003c/g, '<')
.replace(/\\u003e/g, '>')
.replace(/\\"/g, '"')
.replace(/\\r\\n|\\n|\\r/g, ' ');
}
function extractField(block, field) {
var re = new RegExp('"' + field + '"\\s*:\\s*(?:"((?:\\\\.|[^"\\\\])*)"|([^,}\\]]+))');
var m = re.exec(block);
if (!m) return '';
var v = m[1] !== undefined ? decodeJsonString(m[1]) : trim(m[2]);
if (v === 'null' || v === 'undefined') return '';
return v;
}
function absoluteUrl(url) {
url = trim(url);
if (!url) return '';
if (/^https?:\/\//i.test(url)) return url;
if (url.indexOf('//') === 0) return 'https:' + url;
if (url.charAt(0) === '/') return BASE + url;
return BASE + '/' + url;
}
function absoluteByBase(url, baseUrl) {
url = trim(url);
if (!url || url.charAt(0) === '#') return url;
if (/^(https?:)?\/\//i.test(url)) return url.indexOf('//') === 0 ? 'https:' + url : url;
if (/^(data|blob):/i.test(url)) return url;
try {
return new URL(url, baseUrl).toString();
} catch (e) {
var base = baseUrl.split('?')[0];
base = base.substring(0, base.lastIndexOf('/') + 1);
while (url.indexOf('../') === 0) {
url = url.substring(3);
base = base.replace(/[^\/]+\/$/, '');
}
if (url.indexOf('./') === 0) url = url.substring(2);
return base + url;
}
}
function getVodId(url) {
var m = String(url || '').match(/\/detail\/(\d+)/);
if (m) return m[1];
m = String(url || '').match(/\/vod\/play\/(\d+)\/\d+\/\d+/);
if (m) return m[1];
m = String(url || '').match(/(?:^|[?&])id=(\d+)/);
return m ? m[1] : '';
}
function getNid(url) {
var s = String(url || '');
var parts = s.split('||');
if (parts.length > 1 && /^\d+$/.test(parts[1])) return parts[1];
var m = s.match(/\/vod\/play\/\d+\/\d+\/(\d+)/);
return m ? m[1] : '';
}
function getDeviceId() {
var id = '';
try {
id = legado.config.read(SCOPE, 'deviceId');
} catch (e) {}
if (!id) {
id = 'web-' + Date.now() + '-' + Math.random().toString(16).slice(2);
try {
legado.config.write(SCOPE, 'deviceId', id);
} catch (e2) {}
}
return id;
}
function readConfig(key) {
var value = '';
try {
value = legado.config.read(SCOPE, key);
} catch (e) {}
if (!value) {
try {
value = legado.config.read(key);
} catch (e2) {}
}
return trim(value);
}
function writeConfig(key, value) {
try {
legado.config.write(SCOPE, key, value);
} catch (e) {}
try {
legado.config.write(key, value);
} catch (e2) {}
}
function getAuthorizationHeader() {
for (var i = 0; i < AUTH_KEYS.length; i++) {
var value = readConfig(AUTH_KEYS[i]);
if (value) return value;
}
return '';
}
function saveAuthorization(value) {
value = trim(value);
writeConfig('authorization', value);
if (value) writeConfig('token', value);
return value ? '登录令牌已保存' : '登录令牌已清空';
}
function clearAuthorization() {
for (var i = 0; i < AUTH_KEYS.length; i++) writeConfig(AUTH_KEYS[i], '');
return '登录令牌已清空';
}
function escapeHtml(s) {
return String(s || '')
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
function settingsPage() {
var auth = getAuthorizationHeader();
var savedText = auth ? '已保存' : '未保存';
return {
type: 'html',
html: '<!doctype html><html><head><meta charset="utf-8"><style>' +
'body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;margin:18px;color:#202124;background:#fff}' +
'.row{display:flex;gap:8px;margin-top:10px}.box{width:100%;box-sizing:border-box;padding:10px;border:1px solid #d0d7de;border-radius:6px}' +
'button{padding:9px 12px;border:1px solid #d0d7de;border-radius:6px;background:#f6f8fa}.primary{background:#0969da;color:#fff;border-color:#0969da}' +
'#msg{margin-top:10px;color:#57606a;font-size:13px;word-break:break-all}</style></head><body>' +
'<div>登录令牌: <b id="state">' + savedText + '</b></div>' +
'<textarea id="auth" class="box" rows="5" placeholder="粘贴 authorization / token">' + escapeHtml(auth) + '</textarea>' +
'<div class="row"><button class="primary" onclick="saveAuth()">保存令牌</button><button onclick="clearAuth()">清空令牌</button><button onclick="openLogin()">打开登录</button><button onclick="syncToken()">同步令牌</button></div>' +
'<div id="msg"></div><script>' +
'function msg(t){document.getElementById("msg").textContent=t||""}' +
'async function saveAuth(){try{var v=document.getElementById("auth").value;msg(await legado.callSource("saveAuthorization",v));document.getElementById("state").textContent=v?"已保存":"未保存"}catch(e){msg(e.message||String(e))}}' +
'async function clearAuth(){try{msg(await legado.callSource("clearAuthorization"));document.getElementById("auth").value="";document.getElementById("state").textContent="未保存"}catch(e){msg(e.message||String(e))}}' +
'async function openLogin(){try{msg(await legado.callSource("openLogin"))}catch(e){msg(e.message||String(e))}}' +
'async function syncToken(){try{var r=await legado.callSource("syncLoginToken");msg(r);location.reload()}catch(e){msg(e.message||String(e))}}' +
'</script></body></html>'
};
}
async function openLogin() {
var id = legado.browser.acquire('jpyy-login', { visible: true, userAgent: UA, timeoutSecs: 120 });
legado.browser.navigate(id, BASE + '/', { waitUntil: 'load', timeoutSecs: 120 });
return '已打开登录窗口,请在网页内完成登录';
}
function tokenExtractScript() {
return 'return (function(){function pick(o){if(!o)return "";var keys=["authorization","token","accessToken","loginToken","userToken"];for(var i=0;i<keys.length;i++){if(o[keys[i]])return o[keys[i]];}if(o.state){var v=pick(o.state);if(v)return v;}if(o.user){var u=pick(o.user);if(u)return u;}if(o.userInfo){var ui=pick(o.userInfo);if(ui)return ui;}return ""}function parse(v){try{return pick(JSON.parse(v))}catch(e){return ""}}var keys=["authorization","token","accessToken","loginToken","userToken"];for(var i=0;i<keys.length;i++){var v=localStorage.getItem(keys[i])||sessionStorage.getItem(keys[i]);if(v)return v;}var us=parse(localStorage.getItem("userStore")||sessionStorage.getItem("userStore")||"");if(us)return us;for(var j=0;j<localStorage.length;j++){var k=localStorage.key(j);var vv=localStorage.getItem(k);if(/token|authorization/i.test(k)&&vv)return vv;var pv=parse(vv);if(pv)return pv;}return "";})()';
}
async function syncLoginToken() {
var id = legado.browser.acquire('jpyy-login', { visible: true, userAgent: UA, timeoutSecs: 120 });
var script = tokenExtractScript();
var token = '';
try {
token = trim(legado.browser.eval(id, script));
} catch (e) {}
if (!token) {
legado.browser.navigate(id, BASE + '/', { waitUntil: 'load', timeoutSecs: 60 });
token = trim(legado.browser.eval(id, script));
}
if (!token) return '未找到登录令牌:请先在打开的网页中登录,再点击同步令牌';
saveAuthorization(token);
return '登录令牌已同步';
}
function sortedQuery(params) {
var keys = [];
for (var k in params) {
if (params.hasOwnProperty(k) && params[k] !== undefined && params[k] !== null && params[k] !== '') keys.push(k);
}
keys.sort();
var arr = [];
for (var i = 0; i < keys.length; i++) arr.push(keys[i] + '=' + encodeURIComponent(String(params[keys[i]])));
return arr.join('&');
}
async function apiGet(path, params, referer) {
params = params || {};
var query = sortedQuery(params);
var t = String(Date.now());
var md5 = await legado.md5(query + '&key=' + SIGN_KEY + '&t=' + t);
var sign = await legado.sha1(md5);
var headers = {};
for (var k in PAGE_HEADERS) headers[k] = PAGE_HEADERS[k];
headers.Referer = referer || BASE + '/';
headers.t = t;
headers.sign = sign;
headers.authorization = getAuthorizationHeader();
headers.deviceId = getDeviceId();
var apiUrl = API_BASE + path + (query ? '?' + query : '');
var text = '';
var lastErr = null;
for (var attempt = 0; attempt < 3; attempt++) {
try {
text = await httpGetRetry(apiUrl, headers);
lastErr = null;
break;
} catch (e) {
lastErr = e;
}
}
if (lastErr) throw lastErr;
var json = JSON.parse(text);
if (json.code && json.code !== 200) throw new Error('api error ' + json.code + ': ' + (json.msg || ''));
return json.data !== undefined ? json.data : json;
}
async function getVideoDetail(vodId, referer) {
var data = await apiGet('/mw-movie/anonymous/video/detail', { id: vodId }, referer || (BASE + '/detail/' + vodId));
return data && data.data ? data.data : (data || {});
}
function normalizePlayInfo(data) {
return data && data.data ? data.data : (data || {});
}
function getPlayList(info) {
return (info && (info.list || info.urls)) || [];
}
function isAuthKeyPlayUrl(url) {
return /[?&]auth_key=/i.test(String(url || ''));
}
function isIpBoundPlayUrl(url) {
url = String(url || '');
return /[?&]whip=/i.test(url) || /[?&]sign=/i.test(url) || /\/\/ppvod01\.kqgfbs\.com\//i.test(url);
}
function hasAuthKeyPlayUrl(list) {
for (var i = 0; i < list.length; i++) {
if (list[i] && isAuthKeyPlayUrl(list[i].url)) return true;
}
return false;
}
function hasIpBoundPlayUrl(list) {
for (var i = 0; i < list.length; i++) {
if (list[i] && isIpBoundPlayUrl(list[i].url)) return true;
}
return false;
}
async function getEpisodePlayInfo(vodId, nid) {
var data = await apiGet('/mw-movie/anonymous/v2/video/episode/url', {
clientType: 1,
id: vodId,
nid: nid
}, BASE + '/vod/play/' + vodId + '/1/' + nid);
return normalizePlayInfo(data);
}
function makeBookItem(v) {
var vodId = String(v.vodId || v.id || v.videoId || '');
var bookUrl = BASE + '/detail/' + vodId;
return {
name: v.vodName || v.name || v.title || ('影片' + vodId),
author: v.vodDirector || v.director || v.vodActor || v.actor || '',
bookUrl: bookUrl,
tocUrl: bookUrl,
coverUrl: absoluteUrl(v.vodPic || v.cover || v.pic || v.coverUrl || ''),
intro: stripHtml(v.vodContent || v.vodBlurb || v.content || v.desc || ''),
latestChapter: v.vodVersion || v.latestChapter || v.remarks || '',
latestChapterUrl: bookUrl,
updateTime: v.vodTime || v.updateTime || '',
status: v.vodVersion || v.status || '',
kind: v.typeName || v.vodClass || ''
};
}
function parseVodItemsFromNextData(html) {
var text = normalizeNextData(html);
var items = [];
var seen = {};
var re = /"vodId"\s*:\s*(\d+)/g;
var m;
while ((m = re.exec(text)) !== null) {
var vodId = m[1];
if (seen[vodId]) continue;
var next = text.indexOf('"vodId"', re.lastIndex);
var block = text.slice(m.index, next > m.index ? next : m.index + 5000);
var name = extractField(block, 'vodName');
if (!name) continue;
seen[vodId] = true;
items.push(makeBookItem({
vodId: vodId,
vodName: name,
vodDirector: extractField(block, 'vodDirector'),
vodActor: extractField(block, 'vodActor'),
vodPic: extractField(block, 'vodPic'),
vodContent: extractField(block, 'vodContent') || extractField(block, 'vodBlurb'),
vodBlurb: extractField(block, 'vodBlurb'),
vodRemarks: extractField(block, 'vodRemarks'),
vodVersion: extractField(block, 'vodVersion'),
vodPubdate: extractField(block, 'vodPubdate'),
vodClass: extractField(block, 'vodClass'),
typeName: extractField(block, 'typeName')
}));
}
return items;
}
function parseListFromHtml(html) {
var items = [];
var seen = {};
var re = /href=["']\/detail\/(\d+)["'][\s\S]{0,1200}?(?:alt=["']([^"']+)["']|title=["']([^"']+)["']|class=["'][^"']*name[^"']*["'][^>]*>([^<]+)<|<h[1-6][^>]*>([^<]+)<|<span[^>]*>([^<]+)<)/gi;
var m;
while ((m = re.exec(html)) !== null) {
var id = m[1];
if (seen[id]) continue;
var name = stripHtml(m[2] || m[3] || m[4] || m[5] || m[6] || ('影片' + id));
if (!name || name.length > 80) name = '影片' + id;
seen[id] = true;
items.push({ name: name, bookUrl: BASE + '/detail/' + id, tocUrl: BASE + '/detail/' + id, author: '', coverUrl: '', intro: '', latestChapter: '', kind: '' });
}
return items;
}
async function search(keyword, page) {
page = page || 1;
keyword = keyword || '';
var url = BASE + '/vod/search/' + encodeURIComponent(keyword) + (page > 1 ? '?page=' + page : '');
var html = await httpGetRetry(url, PAGE_HEADERS);
var items = parseVodItemsFromNextData(html);
return items.length ? items : parseListFromHtml(html);
}
async function explore(page, category) {
if (page === 'GETALL' || category === 'GETALL' || page === undefined) {
return ['首页', '电影', '电视剧', '综艺', '动漫', '\u8bbe\u7f6e'];
}
if (typeof page === 'string' && !/^\d+$/.test(page) && category === undefined) {
category = page;
page = 1;
}
page = page || 1;
category = category || '首页';
if (category === '\u8bbe\u7f6e') return settingsPage();
var path = CATEGORIES[category] || CATEGORIES['首页'];
var url = BASE + path + (page > 1 ? (path === '/' ? '?page=' + page : '/page/' + page) : '');
var html = await httpGetRetry(url, PAGE_HEADERS);
var items = parseVodItemsFromNextData(html);
return items.length ? items : parseListFromHtml(html);
}
async function bookInfo(bookUrl) {
var vodId = getVodId(bookUrl);
if (!vodId) throw new Error('missing vodId: ' + bookUrl);
return makeBookItem(await getVideoDetail(vodId, BASE + '/detail/' + vodId));
}
async function chapterList(tocUrl) {
var vodId = getVodId(tocUrl);
if (!vodId) throw new Error('missing vodId: ' + tocUrl);
var detail = await getVideoDetail(vodId, BASE + '/detail/' + vodId);
var list = detail.episodeList || detail.vodEpisodeList || detail.episodes || [];
list.sort(function(a, b) { return Number(a.sort || a.episodeSort || 0) - Number(b.sort || b.episodeSort || 0); });
var chapters = [];
for (var i = 0; i < list.length; i++) {
var ep = list[i] || {};
var nid = ep.nid || ep.id || ep.episodeId;
if (!nid) continue;
chapters.push({ name: ep.name || ep.title || detail.vodVersion || ('第' + (i + 1) + '集'), url: BASE + '/detail/' + vodId + '||' + nid, group: detail.vodVersion || '默认' });
}
return chapters;
}
function looksLikeM3u8(text) {
return trim(text).indexOf('#EXTM3U') === 0;
}
function absolutizeM3u8(text, m3u8Url) {
text = String(text || '').replace(/\r\n/g, '\n').replace(/\r/g, '\n');
var lines = text.split('\n');
for (var i = 0; i < lines.length; i++) {
var line = trim(lines[i]);
if (!line) lines[i] = line;
else if (line.charAt(0) === '#') lines[i] = line.replace(/URI="([^"]+)"/g, function(all, uri) { return 'URI="' + absoluteByBase(uri, m3u8Url) + '"'; });
else lines[i] = absoluteByBase(line, m3u8Url);
}
return lines.join('\n');
}
async function fetchM3u8Text(m3u8Url) {
var text = await httpGetRetry(m3u8Url, PLAY_HEADERS);
if (!looksLikeM3u8(text)) throw new Error('m3u8 content invalid: ' + String(text || '').slice(0, 80));
return absolutizeM3u8(text, m3u8Url);
}
async function isPlayableM3u8(m3u8Url) {
try {
var text = await legado.http.get(m3u8Url, PLAY_HEADERS);
return looksLikeM3u8(text);
} catch (e) {
log('[play] direct m3u8 check failed: ' + (e && e.message ? e.message : e));
return false;
}
}
function playUrlPathKey(url) {
var m = String(url || '').match(/\/splitOut\/[^?]+\/index\.m3u8/i);
return m ? m[0] : '';
}
function samePlayPath(a, b) {
var aa = playUrlPathKey(a);
var bb = playUrlPathKey(b);
return aa && bb && aa === bb;
}
function browserStateScript(auth, resolution) {
var script = '';
if (auth) {
var tokenJson = JSON.stringify(auth);
script += 'localStorage.setItem("token",' + tokenJson + ');localStorage.setItem("authorization",' + tokenJson + ');sessionStorage.setItem("token",' + tokenJson + ');sessionStorage.setItem("authorization",' + tokenJson + ');try{var u=JSON.parse(localStorage.getItem("userStore")||"{\\"state\\":{}}");u.state=u.state||{};u.state.token=' + tokenJson + ';u.state.isLogin=true;localStorage.setItem("userStore",JSON.stringify(u));}catch(e){};';
}
if (resolution !== undefined && resolution !== null && resolution !== '') {
script += 'localStorage.setItem("userResolution",' + JSON.stringify(String(resolution)) + ');';
}
return script ? script + 'return true;' : 'return true;';
}
function injectBrowserState(id, auth, resolution) {
var script = browserStateScript(auth, resolution);
try {
legado.browser.eval(id, script);
return true;
} catch (e) {
legado.browser.navigate(id, BASE + '/', { waitUntil: 'load', timeoutSecs: 8 });
legado.browser.eval(id, script);
return true;
}
}
async function captureM3u8(playUrl, preferredUrl, resolution) {
var id = legado.browser.acquire('jpyy-play', { visible: false, muted: true, userAgent: UA, timeoutSecs: 12 });
var auth = getAuthorizationHeader();
var found = '';
var bodyUrl = '';
legado.browser.onRequest(id, function(event) {
var url = event && event.url ? String(event.url) : '';
var lower = url.toLowerCase();
if (lower.indexOf('.m3u8') === -1) return;
if (event.status !== 200) return;
var bodyOk = event.responseBody && looksLikeM3u8(event.responseBody);
if (preferredUrl && samePlayPath(url, preferredUrl) && (bodyOk || lower.indexOf('auth_key=') !== -1)) {
found = url;
return;
}
if (bodyOk && !bodyUrl) bodyUrl = url;
if (!found && lower.indexOf('auth_key=') !== -1) found = url;
}, { captureBody: true, urlPattern: 'm3u8' });
try {
try {
injectBrowserState(id, auth, resolution);
} catch (e1) {
log('[captureM3u8] inject state failed: ' + (e1 && e1.message ? e1.message : e1));
}
legado.browser.navigate(id, playUrl, { waitUntil: 'networkidle', timeoutSecs: 12 });
} finally {
try { legado.browser.offRequest(id); } catch (e) {}
}
if (!found && bodyUrl) found = bodyUrl;
if (!found) throw new Error('未抓到可用 m3u8: ' + playUrl);
return found;
}
function getSelectedQualityId(selectedCategories) {
if (!selectedCategories) return '';
if (typeof selectedCategories === 'string') {
try {
selectedCategories = JSON.parse(selectedCategories);
} catch (e) {
return selectedCategories;
}
}
return selectedCategories.quality || selectedCategories.definition || selectedCategories.resolution || '';
}
function qualityId(item, index) {
return 'q' + index + '_' + String(item.resolution || item.resolutionName || index).replace(/[^\w-]+/g, '_');
}
function qualityName(item, index) {
return item.resolutionName || String(item.resolution || ('线路' + (index + 1)));
}
async function chapterContent(chapterUrl, selectedCategories) {
var vodId = getVodId(chapterUrl);
var nid = getNid(chapterUrl);
if (!vodId) throw new Error('missing vodId: ' + chapterUrl);
if (!nid) {
var chapters = await chapterList(BASE + '/detail/' + vodId);
if (!chapters.length) throw new Error('no episode: ' + vodId);
nid = getNid(chapters[0].url);
}
var playInfo = await getEpisodePlayInfo(vodId, nid);
var list = playInfo.list || playInfo.urls || [];
var auth = getAuthorizationHeader();
var selectedQualityId = getSelectedQualityId(selectedCategories);
var selected = null;
var fallback = null;
var qualities = [];
var qualityIndexById = {};
var options = [];
for (var i = 0; i < list.length; i++) {
var item = list[i] || {};
if (!item.url) continue;
var id = qualityId(item, i);
item._qualityId = id;
var name = qualityName(item, i);
var label = name;
var locked = item.needLogin === true && !auth;
if (locked) label += ' (需登录)';
if (!locked) {
qualityIndexById[id] = qualities.length;
qualities.push({ label: name, url: item.url });
}
options.push({ id: id, label: label, badge: item.needLogin === true ? '登录' : '' });
if (selectedQualityId && selectedQualityId === id && !locked) selected = item;
if (!fallback && !locked) fallback = item;
}
if (!selected) selected = fallback;
if (!selected && auth && list.length) {
for (var j = 0; j < list.length; j++) {
if (list[j] && list[j].url) {
selected = list[j];
break;
}
}
}
if (!selected || !selected.url) throw new Error(auth ? ('未获取到播放地址: ' + chapterUrl) : '未登录或登录令牌未同步,请到设置页登录后同步令牌');
if (isIpBoundPlayUrl(selected.url)) {
try {
if (await isPlayableM3u8(selected.url)) {
log('[chapterContent] direct m3u8 playable');
} else {
var captured = await captureM3u8(BASE + '/vod/play/' + vodId + '/1/' + nid, selected.url, selected.resolution);
log('[chapterContent] replaced ip-bound url with captured m3u8');
selected.url = captured;
}
} catch (e) {
log('[chapterContent] capture fallback failed: ' + (e && e.message ? e.message : e));
}
}
var q = qualityIndexById[selected._qualityId];
if (q !== undefined && qualities[q]) {
qualities[q].url = selected.url;
}
return JSON.stringify({
url: selected.url,
type: 'hls',
headers: PLAY_HEADERS,
qualities: qualities,
categories: [{
id: 'quality',
label: '\u6e05\u6670\u5ea6',
defaultSelected: selected._qualityId || '',
options: options
}]
});
}