This commit is contained in:
Liu committed 2026-05-09 12:50:49 +08:00
1 parent b75d009cbe
commit 565a401ef8
36 files changed
+11267 -5195

No files matched your search

+833
View File
@@ -0,0 +1,833 @@
import { Crypto, _ } from 'assets://js/lib/cat.js';
let siteUrl = '';
let key = '';
let iv = '';
let siteKey = '';
let siteType = 3;
let apiPrefix = 'getappapi.index';
const prefixMap = {
'1': 'getappapi.index',
'2': 'qijiappapi.index',
'3': 'appapi'
};
let enableVerifyTimeSign = false;
let headers = {
'User-Agent': 'okhttp/3.10.0',
'app-user-device-id': '291b226282010337c9443590d6457be15',
'app-version-code': '112'
};
let parseMap = {};
let homeVods = [];
let Searchstatus = false;
let searchApiSuffix = '';
let souParamName = '';
let souSalt = '';
let extraSearchHeaders = {};
let initSuffix = 'init';
let hasCustomInit = false;
let thirdDanmuBaseUrl = '';
let customPlayUa = ''; // 新增:自定义播放链接UA
let request = async (reqUrl, data, header, method) => {
let finalHeaders = { ...headers };
if (enableVerifyTimeSign) {
const timestamp = Math.floor(Date.now() / 1000).toString();
const sign = aesEncode(timestamp, key, iv);
finalHeaders['app-api-verify-time'] = timestamp;
finalHeaders['app-api-verify-sign'] = sign;
}
if (header) {
finalHeaders = { ...finalHeaders, ...header };
}
let res = await req(reqUrl, {
method: method || 'get',
data: data || '',
headers: finalHeaders,
postType: method === 'post' ? 'form' : '',
timeout: 10000,
});
return res.content;
};
let request_text = async (reqUrl, data, header, method, tobase64) => {
let finalHeaders = { ...headers };
if (enableVerifyTimeSign) {
const timestamp = Math.floor(Date.now() / 1000).toString();
const sign = aesEncode(timestamp, key, iv);
finalHeaders['app-api-verify-time'] = timestamp;
finalHeaders['app-api-verify-sign'] = sign;
}
if (header) {
finalHeaders = { ...finalHeaders, ...header };
}
let optObj = {
headers: finalHeaders,
method: method || 'get',
data: method === 'post' ? data : undefined,
postType: method === 'post' ? (data ? 'raw' : 'form') : undefined,
timeout: 10000,
};
if (tobase64) {
optObj.buffer = 2;
}
let res = await req(reqUrl, optObj);
return res.content;
};
async function init(cfg) {
siteKey = cfg.skey;
siteType = cfg.stype;
if (!cfg.ext) return;
let extObj;
if (typeof cfg.ext === 'string') {
extObj = JSON.parse(cfg.ext);
} else if (typeof cfg.ext === 'object' && cfg.ext !== null) {
extObj = cfg.ext;
} else {
return;
}
if (extObj.init && typeof extObj.init === 'string' && extObj.init.startsWith('V')) {
initSuffix = 'init' + extObj.init;
hasCustomInit = true;
}
if (extObj.time !== undefined) {
const timeVal = String(extObj.time);
enableVerifyTimeSign = (timeVal === '1' || timeVal === 'true' || timeVal === '1');
}
if (extObj.code !== undefined) {
forceVerifyCode = String(extObj.code).trim();
}
// 新增:读取自定义播放UA
if (extObj.ua2 !== undefined) {
customPlayUa = String(extObj.ua2).trim();
}
if (extObj.head && typeof extObj.head === 'string') {
const headStr = extObj.head.trim();
if (headStr) {
const items = headStr.split(',');
for (let item of items) {
const trimmed = item.trim();
if (!trimmed) continue;
const colonIndex = trimmed.indexOf(':');
if (colonIndex > 0) {
const hKey = trimmed.substring(0, colonIndex).trim();
const hValue = trimmed.substring(colonIndex + 1).trim();
if (hKey && hValue) {
headers[hKey] = hValue;
}
}
}
}
}
let host = extObj.host || '';
if (host) {
if (host.startsWith('http') && (host.endsWith('.txt') || host.endsWith('.json'))) {
let response = await request(host, null, headers, 'get');
let urls = response.split('\n').map(line => line.trim()).filter(line => line && line.startsWith('http'));
if (urls.length > 0) {
host = urls[0];
}
}
siteUrl = host.replace(/\/+$/, '');
if (!siteUrl.includes('php')) {
siteUrl += '/api.php';
}
}
key = extObj.key || '';
iv = extObj.iv || key;
if (extObj.api && prefixMap[extObj.api]) {
apiPrefix = prefixMap[extObj.api];
}
if (extObj.version) {
headers['app-version-code'] = String(extObj.version);
}
if (extObj.id) {
headers['app-user-device-id'] = extObj.id;
}
if (extObj.token) {
headers['app-user-token'] = extObj.token;
}
if (extObj.ua) {
headers['User-Agent'] = extObj.ua;
}
}
function processSignatureValue(sig) {
if (sig.length < 8) {
return sig.split('').reverse().join('');
} else {
let rear = sig.slice(-8).split('').reverse().join('');
let front = sig.slice(0, -8).split('').reverse().join('');
return front + rear;
}
}
async function home(filter) {
let initUrl = `${siteUrl}/${apiPrefix}/${initSuffix}`;
let rets = JSON.parse(await request(initUrl)).data;
let data = JSON.parse(aesDecode(rets, key, iv));
let rawDanmuUrl = data.config?.third_danmu_url || '';
if (Array.isArray(rawDanmuUrl)) {
rawDanmuUrl = rawDanmuUrl.find(u => u && typeof u === 'string' && u.trim()) || '';
} else if (typeof rawDanmuUrl !== 'string') {
rawDanmuUrl = '';
}
if (rawDanmuUrl) {
thirdDanmuBaseUrl = rawDanmuUrl.trim();
let lower = thirdDanmuBaseUrl.toLowerCase();
if (!lower.match(/[?&]url=$/)) {
thirdDanmuBaseUrl += thirdDanmuBaseUrl.includes('?') ? '&url=' : '?url=';
}
} else {
thirdDanmuBaseUrl = 'https://dmku.hls.one/?ac=dm&url=';
}
if (data.box_config) {
let originalKey = key;
let swappedKey = originalKey.split('').reverse().join('');
let md5Key = md5(swappedKey);
let dynamicIv = md5Key.substring(0, 16);
let decrypted = aesDecode(data.box_config, swappedKey, dynamicIv);
let boxJson = JSON.parse(decrypted);
if (boxJson.search_name) {
searchApiSuffix = boxJson.search_name;
}
if (boxJson.signature_name && boxJson.signature_value) {
souParamName = boxJson.signature_name;
souSalt = processSignatureValue(boxJson.signature_value);
}
if (boxJson.api_header && boxJson.api_header.key && boxJson.api_header.value) {
extraSearchHeaders[boxJson.api_header.key] = boxJson.api_header.value;
}
} else {
searchApiSuffix = 'searchList';
souParamName = '';
souSalt = '';
}
Searchstatus = data.config?.system_search_verify_status || false;
let filters = {};
let classes = [];
homeVods = [];
_.forEach(data.type_list, item => {
if (item.type_id > 0) {
if (item.recommend_list && Array.isArray(item.recommend_list)) {
homeVods = homeVods.concat(item.recommend_list);
}
}
classes.push({
type_id: item.type_id,
type_name: item.type_name,
});
let filterList = [];
_.forEach(item.filter_type_list, f => {
let filter = {};
if (f.name === 'class') {
filter['name'] = '分类';
filter['key'] = f.name;
filter['value'] = _.map(f.list, i => ({ v: i, n: i }));
}
if (f.name === 'area') {
filter['name'] = '区域';
filter['key'] = f.name;
filter['value'] = _.map(f.list, i => ({ v: i, n: i }));
}
if (f.name === 'lang') {
filter['name'] = '语言';
filter['key'] = f.name;
filter['value'] = _.map(f.list, i => ({ v: i, n: i }));
}
if (f.name === 'year') {
filter['name'] = '年份';
filter['key'] = f.name;
filter['value'] = _.map(f.list, i => ({ v: i, n: i }));
}
if (f.name === 'sort') {
filter['name'] = '排序';
filter['key'] = f.name;
filter['value'] = _.map(f.list, i => ({ v: i, n: i }));
}
if (Object.keys(filter).length > 0) {
filterList.push(filter);
}
});
if (filterList.length > 0) {
filters[item.type_id] = filterList;
}
});
return JSON.stringify({
'class': classes,
'filters': filters,
});
}
async function homeVod() {
return JSON.stringify({
list: homeVods,
});
}
async function category(tid, pg, filter, extend) {
if (pg <= 0) pg = 1;
let url = `${siteUrl}/${apiPrefix}/typeFilterVodList`;
let params = {
"area": extend['area'] || "全部",
"sort": extend['sort'] || "最新",
"class": extend['class'] || "全部",
"type_id": tid,
"year": extend['year'] || "全部",
"lang": extend['lang'] || '全部',
"page": pg,
};
let encData = JSON.parse(await request(url, params, '', 'post')).data;
let videos = JSON.parse(aesDecode(encData, key, iv)).recommend_list;
return JSON.stringify({
page: pg,
pagecount: 9999,
list: videos,
});
}
async function detail(id) {
let url = `${siteUrl}/${apiPrefix}/vodDetail`;
let resp = await request(url, { vod_id: id }, '', 'post');
let jsonResp = JSON.parse(resp);
let encData = jsonResp.data;
let decoded = aesDecode(encData, key, iv);
let info = JSON.parse(decoded);
let videos = {
vod_id: info.vod.vod_id,
vod_name: info.vod.vod_name,
vod_area: info.vod.vod_area,
vod_director: info.vod.vod_director,
vod_actor: info.vod.vod_actor,
vod_pic: info.vod.vod_pic,
vod_content: info.vod.vod_content,
type_name: info.vod.vod_class,
vod_year: info.vod.vod_year
};
let froms = [];
let urls = [];
let playSources = _.map(info.vod_play_list, item => {
const playerInfo = item.player_info || {};
const parse = playerInfo.parse || '';
const ua = playerInfo.user_agent || customPlayUa || ''; // 修改:优先使用自定义UA,如果没有则使用原有的
const nameUrls = _.map(item.urls || [], item2 => {
const { name = '', url = '', token = '', parse_api_url = '', nid = 1 } = item2;
return `${name}$${url}@@${parse}@@${token}@@${parse_api_url}@@${ua}@@${info.vod.vod_id}@@${nid}`;
}).join('#');
return {
show: playerInfo.show || 'Unknown',
urls: nameUrls
};
});
let showCount = {};
playSources = _.map(playSources, source => {
let showName = source.show;
if (showCount[showName]) {
showCount[showName]++;
showName = `${showName}${showCount[showName]}`;
} else {
showCount[showName] = 1;
}
return {
show: showName,
urls: source.urls
};
});
playSources.sort((a, b) => {
const aShow = a.show.toLowerCase();
const bShow = b.show.toLowerCase();
const getPriority = (show) => {
if (show.includes('4k')) return 1;
if (show.includes('K')) return 2;
if (show.includes('独家')) return 3;
if (show.includes('秒播')) return 4;
if (show.includes('自建')) return 5;
if (show.includes('蓝光')) return 6;
if (show.includes('专线')) return 7;
return 8;
};
return getPriority(aShow) - getPriority(bShow);
});
froms = _.map(playSources, source => source.show);
urls = _.map(playSources, source => source.urls);
videos.vod_play_from = froms.join('$$$');
videos.vod_play_url = urls.join('$$$');
return JSON.stringify({
list: [videos],
});
}
async function play(flag, id, flags) {
let parts = id.split('@@');
let playUrl = parts[0];
let parse = parts[1] || '';
let token = parts[2] || '';
let parse_api_url = parts[3] || '';
let ua = parts[4] || ''; // 新增:获取UA
let vodId = parts[5] || '';
let nidStr = parts[6] || '1';
let danmakuUrl = '';
if (thirdDanmuBaseUrl && vodId) {
let nid = parseInt(nidStr, 10);
if (isNaN(nid) || nid < 1) nid = 1;
let urlPosition = nid - 1;
let danmuParams = {
url_position: urlPosition.toString(),
vod_id: vodId
};
let danmuRet = await request(`${siteUrl}/${apiPrefix}/danmuList`, danmuParams, '', 'post');
let danmuResponse = JSON.parse(danmuRet);
if (danmuResponse.data) {
let decryptedDanmu = JSON.parse(aesDecode(danmuResponse.data, key, iv));
if (decryptedDanmu && decryptedDanmu.official_url) {
let realDanmuUrl = thirdDanmuBaseUrl + decryptedDanmu.official_url;
let EncodedUrl = encodeURIComponent(realDanmuUrl);
let headerJson = JSON.stringify(headers);
danmakuUrl = js2Proxy(
false,
siteType,
siteKey || '',
EncodedUrl,
headers
);
}
}
}
// 修改:如果存在自定义UA,添加到返回参数中
if (
(playUrl.includes('http://') || playUrl.includes('https://')) &&
(playUrl.includes('m3u8') || playUrl.includes('mp4') || playUrl.includes('mkv')) &&
!parse_api_url
) {
let result = {
parse: 0,
url: playUrl,
danmaku: danmakuUrl
};
// 如果存在自定义UA,添加到返回结果中
if (ua) {
result.header = { 'User-Agent': ua };
}
return JSON.stringify(result);
}
if (parse.startsWith("http")) {
let parseUrl = parse + playUrl;
if (token) {
parseUrl += '&token=' + token;
}
// 如果存在自定义UA,在请求解析接口时使用
let requestHeaders = {};
if (ua) {
requestHeaders['User-Agent'] = ua;
}
let rets = await request(parseUrl, null, requestHeaders, 'get');
if (rets.indexOf('DOCTYPE html') > -1) {
let result = {
parse: 1,
url: parseUrl,
danmaku: danmakuUrl
};
if (ua) {
result.header = { 'User-Agent': ua };
}
return JSON.stringify(result);
}
let parseJson = JSON.parse(rets);
let result = {
parse: 0,
url: parseJson['url'] || parseJson['data']['url'] || '',
danmaku: danmakuUrl
};
// 如果存在自定义UA,添加到返回结果中
if (ua) {
result.header = { 'User-Agent': ua };
}
return JSON.stringify(result);
}
let params = {
'parse_api': parse,
'url': aesEncode(playUrl, key, iv),
'token': token,
};
// 如果存在自定义UA,在请求时使用
let requestHeaders = {};
if (ua) {
requestHeaders['User-Agent'] = ua;
}
let rets = await request(`${siteUrl}/${apiPrefix}/vodParse`, params, requestHeaders, 'post');
let urlDecoded = aesDecode(JSON.parse(rets).data, key, iv);
let finalPlayUrl = '';
let parsed = JSON.parse(urlDecoded);
finalPlayUrl = JSON.parse(parsed.json).url || '';
let result = {
parse: 0,
url: finalPlayUrl,
danmaku: danmakuUrl
};
// 如果存在自定义UA,添加到返回结果中
if (ua) {
result.header = { 'User-Agent': ua };
}
return JSON.stringify(result);
}
async function search(wd, quick, pg) {
if (hasCustomInit) {
await home({});
}
let searchPath = searchApiSuffix || 'searchList';
let url = `${siteUrl}/${apiPrefix}/${searchPath}`;
let retryCount = 0;
const maxRetries = 1;
let videos = [];
let attemptedCaptcha = false;
let attemptedSlider = false;
let sliderVerified = false;
let sliderId = '';
let params = {
'page': '1',
'type_id': '0',
'keywords': wd,
};
if (souParamName && souSalt) {
const currentTimestamp = Math.floor(Date.now() / 1000).toString();
const souString = `/${souParamName}-${currentTimestamp}-sb-0-${souSalt}`;
const md5Value = md5(souString);
const finalValue = `${currentTimestamp}-sb-0-${md5Value}`;
params[souParamName] = finalValue;
}
let searchHeaders = { ...headers, ...extraSearchHeaders };
if (forceVerifyCode) {
params['code'] = forceVerifyCode;
const random_uuid = generateUUID();
params['key'] = random_uuid;
}
const maxWaitRetries = 2;
let waitRetryCount = 0;
while (true) {
let encData = await request(url, params, searchHeaders, 'post');
let response = JSON.parse(encData);
if (response.code === 1001 && response.need_slider === true && !attemptedSlider) {
attemptedSlider = true;
let getSliderUrl = `${siteUrl}/${apiPrefix}/getSlider`;
let sliderResponse = await request(getSliderUrl, {}, searchHeaders, 'post');
let sliderJson = JSON.parse(sliderResponse);
if (sliderJson.code === 1 && sliderJson.data) {
let sliderDecrypted = aesDecode(sliderJson.data, key, iv);
let sliderData = JSON.parse(sliderDecrypted);
sliderId = sliderData.slider_id || '';
let targetX = parseInt(sliderData.target_x) || 0;
let targetY = parseInt(sliderData.target_y) || 0;
if (sliderId && targetX > 0) {
let posX = targetX;
if (targetX > 0) {
let randomOffset = Math.floor(Math.random() * 3) - 1;
posX = targetX + randomOffset;
if (posX < 1) posX = 1;
if (posX > (sliderData.bg_width || 280)) posX = sliderData.bg_width || 280;
}
let timestamp = Date.now();
await new Promise(resolve => setTimeout(resolve, 1000));
let verifyParams = {
pos_x: posX.toString(),
slider_id: sliderId,
timestamp: timestamp.toString()
};
let verifySliderUrl = `${siteUrl}/${apiPrefix}/verifySlider`;
let verifyResponse = await request(verifySliderUrl, verifyParams, searchHeaders, 'post');
let verifyJson = JSON.parse(verifyResponse);
if (verifyJson.code === 1 && verifyJson.data) {
let verifyDecrypted = aesDecode(verifyJson.data, key, iv);
let verifyResult = JSON.parse(verifyDecrypted);
if (verifyResult.verified === true) {
sliderVerified = true;
continue;
}
}
}
}
if (!sliderVerified) {
if (!forceVerifyCode && (Searchstatus || (response.code === 0 && response.msg && response.msg.includes('验证码')))) {
if (attemptedCaptcha) break;
attemptedCaptcha = true;
const random_uuid = generateUUID();
let modifiedApiPrefix = apiPrefix.replace('.index', '');
let verifyUrl = `${siteUrl}/${modifiedApiPrefix}.verify/create?key=${random_uuid}`;
let base64Img = await request_text(verifyUrl, null, headers, 'get', true);
base64Img = base64Img.replace(/\n/g, '');
let ocrResult = await request_text('https://api.nn.ci/ocr/b64/text', base64Img, { 'User-Agent': 'okhttp/3.10.0' }, 'post');
params['code'] = ocrResult.trim();
params['key'] = random_uuid;
retryCount++;
continue;
} else {
break;
}
}
}
if (response.code === 0 &&
(!response.data || response.data.length === 0) &&
response.msg &&
/等.*秒|等待.*秒|等|搜|请等待|稍等|稍候/i.test(response.msg)) {
if (waitRetryCount >= maxWaitRetries) break;
let waitSeconds = extractWaitTime(response.msg);
if (waitSeconds < 1) waitSeconds = 2;
if (waitSeconds > 30) waitSeconds = 30;
const actualWait = waitSeconds + 1;
await new Promise(resolve => setTimeout(resolve, actualWait * 1000));
waitRetryCount++;
continue;
}
if (response.data && response.data.length > 0) {
let decodedData = JSON.parse(aesDecode(response.data, key, iv));
videos = decodedData.search_list || [];
break;
} else if (!forceVerifyCode && (Searchstatus || (response.code === 0 && response.msg && response.msg.includes('验证码')))) {
if (attemptedCaptcha) break;
attemptedCaptcha = true;
const random_uuid = generateUUID();
let modifiedApiPrefix = apiPrefix.replace('.index', '');
let verifyUrl = `${siteUrl}/${modifiedApiPrefix}.verify/create?key=${random_uuid}`;
let base64Img = await request_text(verifyUrl, null, headers, 'get', true);
base64Img = base64Img.replace(/\n/g, '');
let ocrResult = await request_text('https://api.nn.ci/ocr/b64/text', base64Img, { 'User-Agent': 'okhttp/3.10.0' }, 'post');
params['code'] = ocrResult.trim();
params['key'] = random_uuid;
retryCount++;
continue;
} else {
break;
}
}
return JSON.stringify({
list: videos,
});
}
function generateUUID() {
const chars = '0123456789abcdef';
const uuidTemplate = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx';
return uuidTemplate.replace(/[xy]/g, (c) => {
const r = Math.random() * 16 | 0;
const v = c === 'x' ? r : (r & 0x3 | 0x8);
return chars[v];
});
}
function aesDecode(str, keyStr, ivStr, type) {
const key = Crypto.enc.Utf8.parse(keyStr);
if (type === 'hex') {
str = Crypto.enc.Hex.parse(str);
return Crypto.AES.decrypt({
ciphertext: str
}, key, {
iv: Crypto.enc.Utf8.parse(ivStr),
mode: Crypto.mode.CBC,
padding: Crypto.pad.Pkcs7
}).toString(Crypto.enc.Utf8);
} else {
return Crypto.AES.decrypt(str, key, {
iv: Crypto.enc.Utf8.parse(ivStr),
mode: Crypto.mode.CBC,
padding: Crypto.pad.Pkcs7
}).toString(Crypto.enc.Utf8);
}
}
function aesEncode(str, keyStr, ivStr, type) {
const key = Crypto.enc.Utf8.parse(keyStr);
let encData = Crypto.AES.encrypt(str, key, {
iv: Crypto.enc.Utf8.parse(ivStr),
mode: Crypto.mode.CBC,
padding: Crypto.pad.Pkcs7
});
if (type === 'hex') return encData.ciphertext.toString(Crypto.enc.Hex);
return encData.toString();
}
function md5(text) {
return Crypto.MD5(text).toString();
}
function chineseToNumber(chineseStr) {
const chineseNumMap = {
'零': 0, '〇': 0, '一': 1, '壹': 1, '二': 2, '贰': 2, '两': 2,
'三': 3, '叁': 3, '四': 4, '肆': 4, '五': 5, '伍': 5,
'六': 6, '陆': 6, '七': 7, '柒': 7, '八': 8, '捌': 8,
'九': 9, '玖': 9, '十': 10, '拾': 10
};
if (chineseStr.length === 1) {
return chineseNumMap[chineseStr] || 2;
}
if (chineseStr.startsWith('十') && chineseStr.length === 2) {
const units = chineseNumMap[chineseStr[1]];
return units ? 10 + units : 10;
}
if (chineseStr.endsWith('十') && chineseStr.length === 2) {
const tens = chineseNumMap[chineseStr[0]];
return tens ? tens * 10 : 10;
}
if (chineseStr.length === 3 && chineseStr.includes('十')) {
const tens = chineseNumMap[chineseStr[0]];
const units = chineseNumMap[chineseStr[2]];
return tens && units ? tens * 10 + units : (tens ? tens * 10 : 10);
}
return 2;
}
function extractWaitTime(message) {
const digitMatch = message.match(/(\d+)\s*秒/);
if (digitMatch && digitMatch[1]) {
return parseInt(digitMatch[1], 10);
}
const chineseMatch = message.match(/([零一二三四五六七八九十两壹贰叁肆伍陆柒捌玖拾〇]+)\s*秒/);
if (chineseMatch && chineseMatch[1]) {
return chineseToNumber(chineseMatch[1]);
}
if (/几秒|数秒|若干秒/i.test(message)) {
return 3;
}
return 2;
}
function hexToDec(hex) {
hex = (hex || '#FFFFFF').replace(/^#/, '').toUpperCase();
if (hex.length !== 6) {
while (hex.length < 6) hex += '0';
hex = hex.slice(0, 6);
}
return parseInt(hex, 16);
}
function escapeXml(str) {
if (typeof str !== 'string') return '';
return str
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&apos;');
}
function jsonDanmakuToXml(json) {
if (!json || !json.danmuku || !Array.isArray(json.danmuku)) {
return '<?xml version="1.0" encoding="utf-8"?><i><code>0</code><id>error_no_danmaku</id></i>';
}
let lines = [];
lines.push('<?xml version="1.0" encoding="utf-8"?>');
lines.push('<i>');
lines.push(` <code>${json.code || 0}</code>`);
lines.push(` <id>${escapeXml(json.name || '12345')}</id>`);
const positionMap = {
'top': 5,
'right': 1,
'bottom': 4,
'left': 6,
'scroll': 1
};
json.danmuku.forEach(danmu => {
if (!Array.isArray(danmu) || danmu.length < 5) return;
let [
time = 0,
mode = 'scroll',
colorHex = '#FFFFFF',
,
text = '',
, ,
fontSize = "24px"
] = danmu;
colorHex = '#' + Array(6).fill(0).map(() => '0123456789ABCDEF'[Math.floor(Math.random() * 16)]).join('');
let pos = positionMap[mode] || 1;
let colorDec = hexToDec(colorHex);
let fs = parseInt(String(fontSize).replace(/px/gi, '')) || 24;
let p = `${Number(time).toFixed(3)},${pos},${fs},${colorDec},,,,,`;
let safeText = escapeXml(text);
lines.push(` <d p="${p}">${safeText}</d>`);
});
lines.push('</i>');
return lines.join('\n');
}
async function proxy(params) {
let jsonUrl = decodeURIComponent(
params.url || params[0] || params.danmaku || '' || ''
);
if (!jsonUrl || !jsonUrl.startsWith('http')) {
return JSON.stringify({
code: 400,
content: "缺少有效的弹幕 URL",
headers: { "Content-Type": "text/plain" }
});
}
let jsonContent = await request_text(jsonUrl, null, headers, 'get');
let jsonObj = JSON.parse(jsonContent);
let xmlContent = jsonDanmakuToXml(jsonObj);
return JSON.stringify({
code: 200,
content: xmlContent,
headers: {
"Content-Type": "text/xml; charset=utf-8",
"Cache-Control": "no-cache"
}
});
}
let forceVerifyCode = null;
export function __jsEvalReturn() {
return {
init: init,
home: home,
homeVod: homeVod,
category: category,
detail: detail,
play: play,
search: search,
proxy: proxy
};
}
+552
View File
@@ -0,0 +1,552 @@
# coding=utf-8
# !/usr/bin/python
"""
作者 精彩一瞬间 内容均从互联网收集而来 仅供交流学习使用 严禁用于商业用途 请于24小时内删除
====================Diudiumiao====================
"""
from Crypto.Util.Padding import unpad
from Crypto.Util.Padding import pad
from urllib.parse import urlparse
from urllib.parse import unquote
from Crypto.Cipher import ARC4
from urllib.parse import quote
from base.spider import Spider
from Crypto.Cipher import AES
from datetime import datetime
from bs4 import BeautifulSoup
from base64 import b64decode
import concurrent.futures
import urllib.request
import urllib.parse
import datetime
import binascii
import requests
import hashlib
import base64
import json
import time
import hmac
import sys
import re
import os
sys.path.append('..')
xurl = "https://www.wasu.cn"
headerx = {
'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.87 Safari/537.36'
}
headerz = {
'accept': '*/*',
'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6',
'cache-control': 'no-cache',
'origin': xurl,
'pragma': 'no-cache',
'priority': 'u=1, i',
'referer': xurl,
'sec-ch-ua': '"Microsoft Edge";v="143", "Chromium";v="143", "Not A(Brand";v="24"',
'sec-ch-ua-mobile': '?0',
'sec-ch-ua-platform': '"Windows"',
'sec-fetch-dest': 'empty',
'sec-fetch-mode': 'cors',
'sec-fetch-site': 'cross-site',
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36 Edg/143.0.0.0',
}
class Spider(Spider):
def getName(self):
return "精彩"
def init(self, extend):
pass
def isVideoFormat(self, url):
pass
def manualVideoCheck(self):
pass
def homeVideoContent(self):
pass
def homeContent(self, filter):
result = {}
result = {"class": [{"type_id": "961", "type_name": "电影"},
{"type_id": "962", "type_name": "剧集"},
{"type_id": "963", "type_name": "少儿"},
{"type_id": "965", "type_name": "栏目"},
{"type_id": "966", "type_name": "新闻"}],
"list": [],
"filters": {"961": [{"key": "地区",
"name": "地区",
"value": [{"n": "全部", "v": ""},
{"n": "内地", "v": "内地"},
{"n": "港台", "v": "港台"},
{"n": "欧美", "v": "欧美"},
{"n": "日韩", "v": "日韩"},
{"n": "泰国", "v": "泰国"},
{"n": "其他", "v": "其他"}]},
{"key": "类型",
"name": "类型",
"value": [{"n": "全部", "v": ""},
{"n": "动作", "v": "动作"},
{"n": "科幻", "v": "科幻"},
{"n": "惊悚", "v": "惊悚"},
{"n": "冒险", "v": "冒险"},
{"n": "剧情", "v": "剧情"},
{"n": "励志", "v": "励志"},
{"n": "爱情", "v": "爱情"},
{"n": "喜剧", "v": "喜剧"},
{"n": "家庭", "v": "家庭"},
{"n": "历史", "v": "历史"},
{"n": "魔幻", "v": "魔幻"},
{"n": "恐怖", "v": "恐怖"},
{"n": "战争", "v": "战争"},
{"n": "武侠", "v": "武侠"}]},
{"key": "年代",
"name": "年代",
"value": [{"n": "全部", "v": ""},
{"n": "2026", "v": "2026"},
{"n": "2025", "v": "2025"},
{"n": "2024", "v": "2024"},
{"n": "2023", "v": "2023"},
{"n": "2022", "v": "2022"},
{"n": "2021", "v": "2021"},
{"n": "2020", "v": "2020"},
{"n": "2019", "v": "2019"},
{"n": "2018", "v": "2018"},
{"n": "2017", "v": "2017"},
{"n": "2016", "v": "2016"},
{"n": "2015", "v": "2015"},
{"n": "2014", "v": "2014"},
{"n": "2013", "v": "2013"},
{"n": "2012", "v": "2012"},
{"n": "2011", "v": "2011"},
{"n": "2010", "v": "2010"}]}],
"962": [{"key": "地区",
"name": "地区",
"value": [{"n": "全部", "v": ""},
{"n": "内地", "v": "内地"},
{"n": "港台", "v": "港台"},
{"n": "日韩", "v": "日韩"},
{"n": "欧美", "v": "欧美"},
{"n": "泰国", "v": "泰国"},
{"n": "其他", "v": "其他"}]},
{"key": "类型",
"name": "类型",
"value": [{"n": "全部", "v": ""},
{"n": "都市", "v": "都市"},
{"n": "爱情", "v": "爱情"},
{"n": "短剧", "v": "短剧"}, {"n": "战争", "v": "战争"},
{"n": "家庭", "v": "家庭"},
{"n": "悬疑", "v": "悬疑"},
{"n": "古装", "v": "古装"}, {"n": "谍战", "v": "谍战"},
{"n": "喜剧", "v": "喜剧"},
{"n": "农村", "v": "农村"},
{"n": "刑侦", "v": "刑侦"},
{"n": "武侠", "v": "武侠"},
{"n": "历史", "v": "历史"}]},
{"key": "年代",
"name": "年代",
"value": [{"n": "全部", "v": ""},
{"n": "2026", "v": "2026"},
{"n": "2025", "v": "2025"},
{"n": "2024", "v": "2024"},
{"n": "2023", "v": "2023"},
{"n": "2022", "v": "2022"},
{"n": "2021", "v": "2021"},
{"n": "2020", "v": "2020"},
{"n": "2019", "v": "2019"},
{"n": "2018", "v": "2018"},
{"n": "2017", "v": "2017"},
{"n": "2016", "v": "2016"},
{"n": "2015", "v": "2015"},
{"n": "2014", "v": "2014"},
{"n": "2013", "v": "2013"},
{"n": "2012", "v": "2012"},
{"n": "2011", "v": "2011"},
{"n": "2010", "v": "2010"}]}],
"963": [{"key": "地区",
"name": "地区",
"value": [{"n": "全部", "v": ""},
{"n": "内地", "v": "内地"},
{"n": "日韩", "v": "日韩"},
{"n": "欧美", "v": "欧美"},
{"n": "港台", "v": "港台"},
{"n": "其他", "v": "其他"}]},
{"key": "类型",
"name": "类型",
"value": [{"n": "全部", "v": ""},
{"n": "动作", "v": "动作"},
{"n": "冒险", "v": "冒险"},
{"n": "益智", "v": "益智"},
{"n": "亲子", "v": "亲子"},
{"n": "热血", "v": "热血"},
{"n": "剧情", "v": "剧情"},
{"n": "魔幻", "v": "魔幻"},
{"n": "励志", "v": "励志"},
{"n": "机战", "v": "机战"},
{"n": "搞笑", "v": "搞笑"},
{"n": "科幻", "v": "科幻"},
{"n": "治愈", "v": "治愈"},
{"n": "儿歌", "v": "儿歌"},
{"n": "教育", "v": "教育"},
{"n": "校园", "v": "校园"},
{"n": "童话", "v": "童话"},
{"n": "推理", "v": "推理"},
{"n": "怀旧", "v": "怀旧"},
{"n": "宠物", "v": "宠物"},
{"n": "舞蹈", "v": "舞蹈"}]},
{"key": "年代",
"name": "年代",
"value": [{"n": "全部", "v": ""},
{"n": "2026", "v": "2026"},
{"n": "2025", "v": "2025"},
{"n": "2024", "v": "2024"},
{"n": "2023", "v": "2023"},
{"n": "2022", "v": "2022"},
{"n": "2021", "v": "2021"},
{"n": "2020", "v": "2020"},
{"n": "2019", "v": "2019"},
{"n": "2018", "v": "2018"},
{"n": "2017", "v": "2017"},
{"n": "2016", "v": "2016"},
{"n": "2015", "v": "2015"},
{"n": "2014", "v": "2014"},
{"n": "2013", "v": "2013"},
{"n": "2012", "v": "2012"},
{"n": "2011", "v": "2011"},
{"n": "2010", "v": "2010"}]}],
"965": [{"key": "地区",
"name": "地区",
"value": [{"n": "全部", "v": ""},
{"n": "内地", "v": "内地"},
{"n": "欧美", "v": "欧美"}]},
{"key": "类型",
"name": "类型",
"value": [{"n": "全部", "v": ""},
{"n": "文化", "v": "文化"},
{"n": "纪实", "v": "纪实"},
{"n": "访谈", "v": "访谈"},
{"n": "历史", "v": "历史"},
{"n": "美食", "v": "美食"},
{"n": "旅游", "v": "旅游"},
{"n": "时尚", "v": "时尚"},
{"n": "情感", "v": "情感"},
{"n": "生活", "v": "生活"},
{"n": "真人秀", "v": "真人秀"}]},
{"key": "年代",
"name": "年代",
"value": [{"n": "全部", "v": ""},
{"n": "2026", "v": "2026"},
{"n": "2025", "v": "2025"},
{"n": "2024", "v": "2024"},
{"n": "2023", "v": "2023"},
{"n": "2022", "v": "2022"},
{"n": "2021", "v": "2021"},
{"n": "2020", "v": "2020"},
{"n": "2019", "v": "2019"},
{"n": "2018", "v": "2018"},
{"n": "2017", "v": "2017"},
{"n": "2016", "v": "2016"},
{"n": "2015", "v": "2015"},
{"n": "2014", "v": "2014"},
{"n": "2013", "v": "2013"},
{"n": "2012", "v": "2012"},
{"n": "2011", "v": "2011"},
{"n": "2010", "v": "2010"}]}],
"966": [{"key": "类型",
"name": "类型",
"value": [{"n": "全部", "v": ""},
{"n": "国内视野", "v": "国内视野"},
{"n": "国际纵览", "v": "国际纵览"},
{"n": "军事话题", "v": "军事话题"},
{"n": "社会百态", "v": "社会百态"},
{"n": "央视频", "v": "央视频"}]}]}}
return result
def generate_x_sign(self, secret_b64):
secret_key = self.decode_secret_key(secret_b64)
data = self.get_data_string()
signature = self.compute_signature(secret_key, data)
x_sign = self.encode_signature(signature)
return x_sign
def decode_secret_key(self, secret_b64):
return base64.b64decode(secret_b64)
def get_data_string(self):
return "{}"
def compute_signature(self, secret_key, data):
return hmac.new(secret_key, data.encode('utf-8'), hashlib.sha256).digest()
def encode_signature(self, signature):
return base64.b64encode(signature).decode('utf-8')
def get_current_app_key(self, xurl, headerx):
resp = self.fetch_index_page(xurl, headerx)
js_path = self.extract_js_path(resp.text)
js_url = f"{xurl}{js_path}"
js_content = self.fetch_js_content(js_url, headerx)
target_key_b64 = self.extract_target_key(js_content)
return target_key_b64
def fetch_index_page(self, xurl, headerx):
resp = requests.get(xurl, headers=headerx)
resp.raise_for_status()
return resp
def extract_js_path(self, html_content):
js_path_pattern = r'src="(/[\d\.]+/assets/js/index-[\w\.-]+\.js)"'
match = re.search(js_path_pattern, html_content)
return match.group(1)
def fetch_js_content(self, js_url, headerx):
js_resp = requests.get(js_url, headers=headerx)
js_resp.raise_for_status()
return js_resp.text
def extract_target_key(self, js_content):
key_pattern = r'const \w+="([^"]+)",\w+="([^"]+)",\w+="([^"]+)";'
key_match = re.search(key_pattern, js_content)
return key_match.group(2)
def get_headers(self, sign1, xurl):
return {
'accept': 'application/json, text/plain, */*',
'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6',
'cache-control': 'no-cache',
'launchchannel': 'web_channel',
'origin': xurl,
'pragma': 'no-cache',
'priority': 'u=1, i',
'referer': xurl,
'sec-ch-ua': '"Microsoft Edge";v="143", "Chromium";v="143", "Not A(Brand";v="24"',
'sec-ch-ua-mobile': '?0',
'sec-ch-ua-platform': '"Windows"',
'sec-fetch-dest': 'empty',
'sec-fetch-mode': 'cors',
'sec-fetch-site': 'cross-site',
'siteid': '1000101',
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36 Edg/143.0.0.0',
'x-sign': sign1,
}
def categoryContent(self, cid, pg, filter, ext):
secret_b64 = self.get_current_app_key(xurl, headerx)
sign1 = self.generate_x_sign(secret_b64)
page = self.get_page_number(pg)
NdType, DqType, LxType = self.extract_filter_types(ext)
headers = self.get_headers(sign1, xurl)
params = self.build_category_params(cid, page, NdType, DqType, LxType)
data = self.fetch_category_data(params, headers)
videos = self.parse_category_videos(data)
return self.build_category_result(videos, pg)
def get_page_number(self, pg):
return int(pg) if pg else 1
def extract_filter_types(self, ext):
NdType = ext.get('年代', '全部')
DqType = ext.get('地区', '全部')
LxType = ext.get('类型', '全部')
return NdType, DqType, LxType
def build_category_params(self, cid, page, NdType, DqType, LxType):
return {'functionName': 'getNewsSearchedByCondition','nodeId': cid,'nodeTag': LxType,'yearTag': NdType,'countryTag': DqType,'orderType': '0','pageSize': '40','page': page,'keyword': '','siteId': '1000101',}
def fetch_category_data(self, params, headers):
detail = requests.get('https://ups.5g.wasu.tv/rmp-user-suggest/1000101/hzhs/searchServlet', params=params,headers=headers)
detail.encoding = "utf-8"
return detail.json()
def parse_category_videos(self, data):
videos = []
for vod in data['data']:
videos.append(self.parse_category_video(vod))
return videos
def parse_category_video(self, vod):
return {"vod_id": f"{vod['nodeId']}@{vod['newsId']}","vod_name": vod['title'],"vod_pic": vod['hPic'],"vod_year": vod.get('pubTime', '暂无日期'),"vod_remarks": vod.get('episodeDesc', '暂无备注')}
def build_category_result(self, videos, pg):
result = {'list': videos}
result['page'] = pg
result['pagecount'] = 9999
result['limit'] = 90
result['total'] = 999999
return result
def detailContent(self, ids):
did = ids[0]
secret_b64 = self.get_current_app_key(xurl, headerx)
sign1 = self.generate_x_sign(secret_b64)
fenge = did.split("@")
headers = self.get_headers(sign1, xurl)
params = self.build_detail_params(fenge)
data = self.fetch_detail_data(params, headers)
content = self.build_content(data)
director = self.extract_detail_field(data, 'director')
actor = self.extract_detail_field(data, 'actor')
remarks = self.extract_detail_field(data, 'episodeDesc')
year = self.extract_detail_field(data, 'pubTime')
area = self.extract_detail_field(data, 'countryTag')
bofang = self.build_play_url(data)
videos = [self.build_video_data(did, director, actor, remarks, year, area, content, bofang)]
return self.build_result(videos)
def build_detail_params(self, fenge):
return {'siteId': '1000101', 'functionName': 'getCurrentNews', 'nodeId': fenge[0], 'newsId': fenge[1],'platform': 'web',}
def fetch_detail_data(self, params, headers):
detail = requests.get('https://mcspapp.5g.wasu.tv/bvradio_app/hzhs/newsServlet', params=params, headers=headers)
detail.encoding = "utf-8"
return detail.json()
def build_content(self, data):
return '介绍剧情👉' + data.get('data', {}).get('newsAbstract', '')
def extract_detail_field(self, data, field_name):
return data.get('data', {}).get(field_name, '')
def build_play_url(self, data):
bofang = ''
for vod in data['data']['vodList']:
name = vod['title']
if len(vod['fileList']) > 1:
id = vod['fileList'][1]['playUrl']
else:
id = vod['fileList'][0]['playUrl']
bofang += name + '$' + id + '#'
return bofang[:-1]
def build_video_data(self, did, director, actor, remarks, year, area, content, bofang):
return {"vod_id": did, "vod_director": director, "vod_actor": actor, "vod_remarks": remarks, "vod_year": year, "vod_area": area, "vod_content": content, "vod_play_from": "华数", "vod_play_url": bofang}
def build_result(self, videos):
result = {}
result['list'] = videos
return result
def get_x_sign_for_post(self, secret_b64, playUrl):
secret_key = self.decode_secret_key(secret_b64)
payload_dict = self.build_payload_dict(playUrl)
data_string = self.serialize_payload(payload_dict)
signature = self.compute_hmac_signature(secret_key, data_string)
x_sign = self.encode_signature(signature)
return x_sign
def decode_secret_key(self, secret_b64):
return base64.b64decode(secret_b64)
def build_payload_dict(self, playUrl):
return {"playUrl": playUrl, "platform": "web"}
def serialize_payload(self, payload_dict):
return json.dumps(payload_dict, separators=(',', ':'), ensure_ascii=False)
def compute_hmac_signature(self, secret_key, data_string):
return hmac.new(secret_key, data_string.encode('utf-8'), hashlib.sha256).digest()
def encode_signature(self, signature):
return base64.b64encode(signature).decode('utf-8')
def playerContent(self, flag, id, vipFlags):
secret_b64 = self.get_current_app_key(xurl, headerx)
id = self.normalize_play_url(id)
sign2 = self.get_x_sign_for_post(secret_b64, id)
headers = self.get_headers(sign2, xurl)
json_data = self.build_request_json(id)
result = self.post_play_url_request(headers, json_data)
play_url = result['data']['playUrl']
return self.build_player_result(play_url)
def normalize_play_url(self, id):
return id.replace('.mp4', '/playlist.m3u8')
def build_request_json(self, playUrl):
return {'playUrl': playUrl, 'platform': 'web', }
def post_play_url_request(self, headers, json_data):
response = requests.post('https://mcspapp.5g.wasu.tv/thirdApiFile/file/getPlayUrl', headers=headers, json=json_data)
return response.json()
def build_player_result(self, play_url):
result = {}
result["parse"] = 0
result["playUrl"] = ''
result["url"] = play_url
result["header"] = headerz
return result
def searchContentPage(self, key, quick, pg):
page = self.get_page_number(pg)
secret_b64 = self.get_current_app_key(xurl, headerx)
sign1 = self.generate_x_sign(secret_b64)
headers = self.get_headers(sign1, xurl)
params = self.build_search_params(key, page)
data = self.fetch_search_data(params, headers)
videos = self.parse_search_videos(data)
return self.build_search_result(videos, pg)
def get_page_number(self, pg):
return int(pg) if pg else 1
def build_search_params(self, key, page):
return {'functionName': 'getNewsSearched', 'searchNewsType': '3,4,5', 'keyword': key, 'pageSize': 10, 'page': page, 'siteId': 1000101}
def fetch_search_data(self, params, headers):
detail = requests.get("https://ups.5g.wasu.tv/rmp-user-suggest/1000101/hzhs/searchServlet", params=params, headers=headers)
detail.encoding = "utf-8"
return detail.json()
def parse_search_videos(self, data):
videos = []
for vod in data['data']:
videos.append(self.parse_search_video(vod))
return videos
def parse_search_video(self, vod):
return {"vod_id": f"{vod['nodeId']}@{vod['newsId']}", "vod_name": vod['title'], "vod_pic": vod['hPic'], "vod_year": vod.get('pubTime', '暂无日期'), "vod_remarks": vod.get('episodeDesc', '暂无备注')}
def build_search_result(self, videos, pg):
result = {'list': videos}
result['page'] = pg
result['pagecount'] = 9999
result['limit'] = 90
result['total'] = 999999
return result
def searchContent(self, key, quick, pg="1"):
return self.searchContentPage(key, quick, '1')
def localProxy(self, params):
if params['type'] == "m3u8":
return self.proxyM3u8(params)
elif params['type'] == "media":
return self.proxyMedia(params)
elif params['type'] == "ts":
return self.proxyTs(params)
return None
+225
View File
@@ -0,0 +1,225 @@
# -*- coding: utf-8 -*-
# by @嗷呜
import json
import sys
import threading
import uuid
import requests
sys.path.append('..')
from base.spider import Spider
import time
from Crypto.Hash import MD5, SHA1
class Spider(Spider):
'''
配置示例:
{
"key": "xxxx",
"name": "xxxx",
"type": 3,
"api": ".所在路径/金牌.py",
"searchable": 1,
"quickSearch": 1,
"filterable": 1,
"changeable": 1,
"ext": {
"site": "https://www.jiabaide.cn,域名2,域名3"
}
},
'''
def init(self, extend=""):
if extend:
hosts=json.loads(extend)['site']
self.host = self.host_late(hosts)
pass
def getName(self):
pass
def isVideoFormat(self, url):
pass
def manualVideoCheck(self):
pass
def destroy(self):
pass
def homeContent(self, filter):
cdata = self.fetch(f"{self.host}/api/mw-movie/anonymous/get/filer/type", headers=self.getheaders()).json()
fdata = self.fetch(f"{self.host}/api/mw-movie/anonymous/v1/get/filer/list", headers=self.getheaders()).json()
result = {}
classes = []
filters={}
for k in cdata['data']:
classes.append({
'type_name': k['typeName'],
'type_id': str(k['typeId']),
})
sort_values = [{"n": "最近更新", "v": "2"},{"n": "人气高低", "v": "3"}, {"n": "评分高低", "v": "4"}]
for tid, d in fdata['data'].items():
current_sort_values = sort_values.copy()
if tid == '1':
del current_sort_values[0]
filters[tid] = [
{"key": "type", "name": "类型",
"value": [{"n": i["itemText"], "v": i["itemValue"]} for i in d["typeList"]]},
*([] if not d["plotList"] else [{"key": "v_class", "name": "剧情",
"value": [{"n": i["itemText"], "v": i["itemText"]}
for i in d["plotList"]]}]),
{"key": "area", "name": "地区",
"value": [{"n": i["itemText"], "v": i["itemText"]} for i in d["districtList"]]},
{"key": "year", "name": "年份",
"value": [{"n": i["itemText"], "v": i["itemText"]} for i in d["yearList"]]},
{"key": "lang", "name": "语言",
"value": [{"n": i["itemText"], "v": i["itemText"]} for i in d["languageList"]]},
{"key": "sort", "name": "排序", "value": current_sort_values}
]
result['class'] = classes
result['filters'] = filters
return result
def homeVideoContent(self):
data1 = self.fetch(f"{self.host}/api/mw-movie/anonymous/v1/home/all/list", headers=self.getheaders()).json()
data2=self.fetch(f"{self.host}/api/mw-movie/anonymous/home/hotSearch",headers=self.getheaders()).json()
data=[]
for i in data1['data'].values():
data.extend(i['list'])
data.extend(data2['data'])
vods=self.getvod(data)
return {'list':vods}
def categoryContent(self, tid, pg, filter, extend):
params = {
"area": extend.get('area', ''),
"filterStatus": "1",
"lang": extend.get('lang', ''),
"pageNum": pg,
"pageSize": "30",
"sort": extend.get('sort', '1'),
"sortBy": "1",
"type": extend.get('type', ''),
"type1": tid,
"v_class": extend.get('v_class', ''),
"year": extend.get('year', '')
}
data = self.fetch(f"{self.host}/api/mw-movie/anonymous/video/list?{self.js(params)}", headers=self.getheaders(params)).json()
result = {}
result['list'] = self.getvod(data['data']['list'])
result['page'] = pg
result['pagecount'] = 9999
result['limit'] = 90
result['total'] = 999999
return result
def detailContent(self, ids):
data=self.fetch(f"{self.host}/api/mw-movie/anonymous/video/detail?id={ids[0]}",headers=self.getheaders({'id':ids[0]})).json()
vod=self.getvod([data['data']])[0]
vod['vod_play_from']='拾光有文才'
vod['vod_play_url'] = '#'.join(
f"{i['name'] if len(vod['episodelist']) > 1 else vod['vod_name']}${ids[0]}@@{i['nid']}" for i in
vod['episodelist'])
vod.pop('episodelist', None)
return {'list':[vod]}
def searchContent(self, key, quick, pg="1"):
params = {
"keyword": key,
"pageNum": pg,
"pageSize": "8",
"sourceCode": "1"
}
data=self.fetch(f"{self.host}/api/mw-movie/anonymous/video/searchByWord?{self.js(params)}",headers=self.getheaders(params)).json()
vods=self.getvod(data['data']['result']['list'])
return {'list':vods,'page':pg}
def playerContent(self, flag, id, vipFlags):
self.header = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; ) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.6478.61 Chrome/126.0.6478.61 Not/A)Brand/8 Safari/537.36',
'sec-ch-ua-platform': '"Windows"',
'DNT': '1',
'sec-ch-ua': '"Not/A)Brand";v="8", "Chromium";v="126", "Google Chrome";v="126"',
'sec-ch-ua-mobile': '?0',
'Origin': self.host,
'Referer': f'{self.host}/'
}
ids=id.split('@@')
pdata = self.fetch(f"{self.host}/api/mw-movie/anonymous/v2/video/episode/url?clientType=1&id={ids[0]}&nid={ids[1]}",headers=self.getheaders({'clientType':'1','id': ids[0], 'nid': ids[1]})).json()
vlist=[]
for i in pdata['data']['list']:vlist.extend([i['resolutionName'],i['url']])
return {'parse':0,'url':vlist,'header':self.header}
def localProxy(self, param):
pass
def host_late(self, url_list):
if isinstance(url_list, str):
urls = [u.strip() for u in url_list.split(',')]
else:
urls = url_list
if len(urls) <= 1:
return urls[0] if urls else ''
results = {}
threads = []
def test_host(url):
try:
start_time = time.time()
response = requests.head(url, timeout=1.0, allow_redirects=False)
delay = (time.time() - start_time) * 1000
results[url] = delay
except Exception as e:
results[url] = float('inf')
for url in urls:
t = threading.Thread(target=test_host, args=(url,))
threads.append(t)
t.start()
for t in threads:
t.join()
return min(results.items(), key=lambda x: x[1])[0]
def md5(self, sign_key):
md5_hash = MD5.new()
md5_hash.update(sign_key.encode('utf-8'))
md5_result = md5_hash.hexdigest()
return md5_result
def js(self, param):
return '&'.join(f"{k}={v}" for k, v in param.items())
def getheaders(self, param=None):
if param is None:param = {}
t=str(int(time.time()*1000))
param['key']='cb808529bae6b6be45ecfab29a4889bc'
param['t']=t
sha1_hash = SHA1.new()
sha1_hash.update(self.md5(self.js(param)).encode('utf-8'))
sign = sha1_hash.hexdigest()
deviceid = str(uuid.uuid4())
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; ) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.6478.61 Chrome/126.0.6478.61 Not/A)Brand/8 Safari/537.36',
'Accept': 'application/json, text/plain, */*',
'sign': sign,
't': t,
'deviceid':deviceid
}
return headers
def convert_field_name(self, field):
field = field.lower()
if field.startswith('vod') and len(field) > 3:
field = field.replace('vod', 'vod_')
if field.startswith('type') and len(field) > 4:
field = field.replace('type', 'type_')
return field
def getvod(self, array):
return [{self.convert_field_name(k): v for k, v in item.items()} for item in array]
-772
View File
@@ -1,772 +0,0 @@
/**
title: "耐看影视",
author: "",
logo: "https://nkvod.org/upload/site/20241223-1/a9ca623af581c93ed3c2f3e98e3a5d4b.png",
more: {
sourceTag: "在线播放"
}
*/
import { Crypto, load, _ } from 'assets://js/lib/cat.js';
let HOST = 'https://nkvod.org';
let siteKey = "", siteType = "", sourceKey = "", ext = "";
let searchCookie = '';
const UA = 'Mozilla/5.0 (Linux; Android 12; ALN-AL00 Build/HUAWEIALN-AL00; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/114.0.5735.196 Mobile Safari/537.36';
async function getCaptchaBinaryData(url) {
try {
const res = await req(url, {
headers: {
'User-Agent': UA,
'Referer': HOST
},
buffer: 1
});
let binaryData;
if (Array.isArray(res.content)) {
binaryData = new Uint8Array(res.content);
} else {
binaryData = res.content;
}
return {
headers: res.headers,
binaryData: binaryData
};
} catch (error) {
throw error;
}
}
async function request(reqUrl, options = {}) {
const defaultOptions = {
method: 'GET',
headers: {
'User-Agent': UA,
'Referer': HOST
}
};
if (searchCookie) {
defaultOptions.headers['Cookie'] = searchCookie;
}
const mergedOptions = {...defaultOptions, ...options};
let res = await req(reqUrl, mergedOptions);
return res.content;
}
async function requestWithCaptcha(reqUrl, options = {}) {
let verifyType = 'search';
if (typeof options === 'string') {
verifyType = options;
options = {};
} else if (options.verifyType) {
verifyType = options.verifyType;
}
let html = await request(reqUrl, options);
const needCaptcha = html && (
html.includes('系统安全验证') ||
html.includes('请输入验证码') ||
html.includes('verify/index.html') ||
html.includes('verify_check')
);
if (needCaptcha) {
searchCookie = '';
await fetchCk(verifyType);
if (searchCookie) {
html = await request(reqUrl, {
...options,
headers: {
...options.headers,
'Cookie': searchCookie
}
});
}
const stillNeedCaptcha = html && (
html.includes('系统安全验证') ||
html.includes('请输入验证码')
);
if (stillNeedCaptcha) {
return null;
}
}
return html;
}
async function fetchCk(verifyType = 'search') {
try {
const yzm = HOST + "/index.php/verify/index.html?" + Date.now();
const yzmResponse = await getCaptchaBinaryData(yzm);
let cookie = "";
const headers = yzmResponse.headers;
for (const key in headers) {
if (key.toLowerCase() === "set-cookie") {
const cookieValue = headers[key];
if (Array.isArray(cookieValue)) {
cookie = cookieValue[0].split(";")[0];
} else if (typeof cookieValue === 'string') {
cookie = cookieValue.split(";")[0];
}
break;
}
}
if (!cookie) {
return;
}
let base64Image;
if (yzmResponse.binaryData instanceof Uint8Array) {
const wordArray = Crypto.lib.WordArray.create(yzmResponse.binaryData);
base64Image = Crypto.enc.Base64.stringify(wordArray);
} else {
return;
}
if (!base64Image) {
return;
}
try {
const ocrResponse = await req("https://api.nn.ci/ocr/b64/text", {
method: 'POST',
headers: {
'Content-Type': 'text/plain',
'User-Agent': UA,
'Accept': 'application/json'
},
body: base64Image,
timeout: 10000
});
if (!ocrResponse || !ocrResponse.content) {
return;
}
let verifyCode = ocrResponse.content.trim();
if (!verifyCode) {
return;
}
verifyCode = verifyCode.replace(/[^a-zA-Z0-9]/g, '');
if (verifyCode.length < 4) {
return;
}
const submit_url = `${HOST}/index.php/ajax/verify_check?type=${verifyType}&verify=${verifyCode}`;
const submitResponse = await req(submit_url, {
method: 'POST',
headers: {
'User-Agent': UA,
'X-Requested-With': 'XMLHttpRequest',
'Origin': HOST,
'Referer': HOST + '/',
'Cookie': cookie,
'Content-Type': 'application/x-www-form-urlencoded'
},
body: ''
});
if (submitResponse && submitResponse.content) {
try {
const submitJson = JSON.parse(submitResponse.content);
if (submitJson && submitJson.code === 1) {
searchCookie = cookie;
}
} catch (parseError) {
if (submitResponse.content.includes('"code":1') || submitResponse.content.includes('验证成功')) {
searchCookie = cookie;
}
}
}
} catch (ocrError) {
}
} catch (error) {
}
}
function init(cfg) {
siteKey = cfg.skey;
siteType = cfg.stype;
sourceKey = cfg.sourceKey;
ext = cfg.ext;
if (ext && ext.indexOf('http') == 0) {
HOST = ext;
}
}
// 辅助函数:获取元素文本内容(兼容 flutter_js 的 cheerio)
// flutter_js 的 cheerio 实现中 .text() 方法可能返回空,需要使用 .html() 作为备用
function getText($elem) {
if (!$elem || !$elem.length) return '';
// 优先使用 text(),如果返回空则尝试从 html() 提取
let text = '';
try {
text = $elem.text();
} catch (e) {}
if (!text || text.trim() === '') {
try {
// 从 html 中去除标签获取纯文本
const html = $elem.html();
if (html) {
text = html.replace(/<[^>]*>/g, ' ').replace(/\s+/g, ' ');
}
} catch (e) {}
}
return (text || '').trim();
}
async function home(filter) {
const classes = [
{ type_id: "1", type_name: "电影" },
{ type_id: "2", type_name: "剧集" },
{ type_id: "4", type_name: "动漫" },
{ type_id: "3", type_name: "综艺" }
];
const filterObj = {
"1":[{"key":"class","name":"剧情","value":[{"n":"全部","v":""},{"n":"喜剧","v":"喜剧"},{"n":"爱情","v":"爱情"},{"n":"恐怖","v":"恐怖"},{"n":"动作","v":"动作"},{"n":"科幻","v":"科幻"},{"n":"剧情","v":"剧情"},{"n":"战争","v":"战争"},{"n":"警匪","v":"警匪"},{"n":"犯罪","v":"犯罪"},{"n":"动画","v":"动画"},{"n":"奇幻","v":"奇幻"},{"n":"武侠","v":"武侠"},{"n":"冒险","v":"冒险"},{"n":"枪战","v":"枪战"},{"n":"悬疑","v":"悬疑"},{"n":"惊悚","v":"惊悚"},{"n":"经典","v":"经典"},{"n":"青春","v":"青春"},{"n":"文艺","v":"文艺"},{"n":"微电影","v":"微电影"},{"n":"古装","v":"古装"},{"n":"历史","v":"历史"},{"n":"运动","v":"运动"},{"n":"农村","v":"农村"},{"n":"儿童","v":"儿童"},{"n":"网络电影","v":"网络电影"}]},{"key":"area","name":"地区","value":[{"n":"全部","v":""},{"n":"大陆","v":"大陆"},{"n":"香港","v":"香港"},{"n":"台湾","v":"台湾"},{"n":"美国","v":"美国"},{"n":"法国","v":"法国"},{"n":"英国","v":"英国"},{"n":"日本","v":"日本"},{"n":"韩国","v":"韩国"},{"n":"德国","v":"德国"},{"n":"泰国","v":"泰国"},{"n":"印度","v":"印度"},{"n":"意大利","v":"意大利"},{"n":"西班牙","v":"西班牙"},{"n":"加拿大","v":"加拿大"},{"n":"其它","v":"其它"}]},{"key":"year","name":"年份","value":[{"n":"全部","v":""},{"n":"2026","v":"2026"},{"n":"2025","v":"2025"},{"n":"2024","v":"2024"},{"n":"2023","v":"2023"},{"n":"2022","v":"2022"},{"n":"2021","v":"2021"},{"n":"2020","v":"2020"},{"n":"2019","v":"2019"},{"n":"2018","v":"2018"},{"n":"2017","v":"2017"},{"n":"2016","v":"2016"},{"n":"2015","v":"2015"},{"n":"2014","v":"2014"},{"n":"2013","v":"2013"},{"n":"2012","v":"2012"},{"n":"2011","v":"2011"},{"n":"2010","v":"2010"}]},{"key":"lang","name":"语言","value":[{"n":"全部","v":""},{"n":"国语","v":"国语"},{"n":"英语","v":"英语"},{"n":"粤语","v":"粤语"},{"n":"闽南语","v":"闽南语"},{"n":"韩语","v":"韩语"},{"n":"日语","v":"日语"},{"n":"法语","v":"法语"},{"n":"德语","v":"德语"},{"n":"其它","v":"其它"}]},{"key":"letter","name":"字母","value":[{"n":"字母","v":""},{"n":"A","v":"A"},{"n":"B","v":"B"},{"n":"C","v":"C"},{"n":"D","v":"D"},{"n":"E","v":"E"},{"n":"F","v":"F"},{"n":"G","v":"G"},{"n":"H","v":"H"},{"n":"I","v":"I"},{"n":"J","v":"J"},{"n":"K","v":"K"},{"n":"L","v":"L"},{"n":"M","v":"M"},{"n":"N","v":"N"},{"n":"O","v":"O"},{"n":"P","v":"P"},{"n":"Q","v":"Q"},{"n":"R","v":"R"},{"n":"S","v":"S"},{"n":"T","v":"T"},{"n":"U","v":"U"},{"n":"V","v":"V"},{"n":"W","v":"W"},{"n":"X","v":"X"},{"n":"Y","v":"Y"},{"n":"Z","v":"Z"},{"n":"0-9","v":"0-9"}]},{"key":"by","name":"排序","value":[{"n":"时间排序","v":"time"},{"n":"人气排序","v":"hits"},{"n":"评分排序","v":"score"}]}],
"2":[{"key":"class","name":"剧情","value":[{"n":"全部","v":""},{"n":"古装","v":"古装"},{"n":"战争","v":"战争"},{"n":"青春偶像","v":"青春偶像"},{"n":"喜剧","v":"喜剧"},{"n":"动作","v":"动作"},{"n":"奇幻","v":"奇幻"},{"n":"剧情","v":"剧情"},{"n":"历史","v":"历史"},{"n":"经典","v":"经典"},{"n":"乡村","v":"乡村"},{"n":"情景","v":"情景"},{"n":"商战","v":"商战"},{"n":"网剧","v":"网剧"},{"n":"其他","v":"其他"}]},{"key":"area","name":"地区","value":[{"n":"全部","v":""},{"n":"内地","v":"内地"},{"n":"韩国","v":"韩国"},{"n":"香港","v":"香港"},{"n":"台湾","v":"台湾"},{"n":"日本","v":"日本"},{"n":"美国","v":"美国"},{"n":"泰国","v":"泰国"},{"n":"英国","v":"英国"},{"n":"新加坡","v":"新加坡"},{"n":"其他","v":"其他"}]},{"key":"lang","name":"语言","value":[{"n":"全部","v":""},{"n":"国语","v":"国语"},{"n":"英语","v":"英语"},{"n":"粤语","v":"粤语"},{"n":"闽南语","v":"闽南语"},{"n":"韩语","v":"韩语"},{"n":"日语","v":"日语"},{"n":"其它","v":"其它"}]},{"key":"year","name":"年份","value":[{"n":"全部","v":""},{"n":"2026","v":"2026"},{"n":"2025","v":"2025"},{"n":"2024","v":"2024"},{"n":"2023","v":"2023"},{"n":"2022","v":"2022"},{"n":"2021","v":"2021"},{"n":"2020","v":"2020"},{"n":"2019","v":"2019"},{"n":"2018","v":"2018"},{"n":"2017","v":"2017"},{"n":"2016","v":"2016"},{"n":"2015","v":"2015"},{"n":"2014","v":"2014"},{"n":"2013","v":"2013"},{"n":"2012","v":"2012"},{"n":"2011","v":"2011"},{"n":"2010","v":"2010"},{"n":"2009","v":"2009"},{"n":"2008","v":"2008"},{"n":"2006","v":"2006"},{"n":"2005","v":"2005"},{"n":"2004","v":"2004"}]},{"key":"letter","name":"字母","value":[{"n":"字母","v":""},{"n":"A","v":"A"},{"n":"B","v":"B"},{"n":"C","v":"C"},{"n":"D","v":"D"},{"n":"E","v":"E"},{"n":"F","v":"F"},{"n":"G","v":"G"},{"n":"H","v":"H"},{"n":"I","v":"I"},{"n":"J","v":"J"},{"n":"K","v":"K"},{"n":"L","v":"L"},{"n":"M","v":"M"},{"n":"N","v":"N"},{"n":"O","v":"O"},{"n":"P","v":"P"},{"n":"Q","v":"Q"},{"n":"R","v":"R"},{"n":"S","v":"S"},{"n":"T","v":"T"},{"n":"U","v":"U"},{"n":"V","v":"V"},{"n":"W","v":"W"},{"n":"X","v":"X"},{"n":"Y","v":"Y"},{"n":"Z","v":"Z"},{"n":"0-9","v":"0-9"}]},{"key":"by","name":"排序","value":[{"n":"时间排序","v":"time"},{"n":"人气排序","v":"hits"},{"n":"评分排序","v":"score"}]}],
"3":[{"key":"class","name":"剧情","value":[{"n":"全部","v":""},{"n":"选秀","v":"选秀"},{"n":"情感","v":"情感"},{"n":"访谈","v":"访谈"},{"n":"播报","v":"播报"},{"n":"旅游","v":"旅游"},{"n":"音乐","v":"音乐"},{"n":"美食","v":"美食"},{"n":"纪实","v":"纪实"},{"n":"曲艺","v":"曲艺"},{"n":"生活","v":"生活"},{"n":"游戏互动","v":"游戏互动"},{"n":"财经","v":"财经"},{"n":"求职","v":"求职"}]},{"key":"area","name":"地区","value":[{"n":"全部","v":""},{"n":"内地","v":"内地"},{"n":"港台","v":"港台"},{"n":"日韩","v":"日韩"},{"n":"欧美","v":"欧美"}]},{"key":"lang","name":"语言","value":[{"n":"全部","v":""},{"n":"国语","v":"国语"},{"n":"英语","v":"英语"},{"n":"粤语","v":"粤语"},{"n":"闽南语","v":"闽南语"},{"n":"韩语","v":"韩语"},{"n":"日语","v":"日语"},{"n":"其它","v":"其它"}]},{"key":"year","name":"年份","value":[{"n":"全部","v":""},{"n":"2026","v":"2026"},{"n":"2025","v":"2025"},{"n":"2024","v":"2024"},{"n":"2023","v":"2023"},{"n":"2022","v":"2022"},{"n":"2021","v":"2021"},{"n":"2020","v":"2020"},{"n":"2019","v":"2019"},{"n":"2018","v":"2018"},{"n":"2017","v":"2017"},{"n":"2016","v":"2016"},{"n":"2015","v":"2015"},{"n":"2014","v":"2014"},{"n":"2013","v":"2013"},{"n":"2012","v":"2012"},{"n":"2011","v":"2011"},{"n":"2010","v":"2010"},{"n":"2009","v":"2009"},{"n":"2008","v":"2008"},{"n":"2007","v":"2007"},{"n":"2006","v":"2006"},{"n":"2005","v":"2005"},{"n":"2004","v":"2004"}]},{"key":"letter","name":"字母","value":[{"n":"字母","v":""},{"n":"A","v":"A"},{"n":"B","v":"B"},{"n":"C","v":"C"},{"n":"D","v":"D"},{"n":"E","v":"E"},{"n":"F","v":"F"},{"n":"G","v":"G"},{"n":"H","v":"H"},{"n":"I","v":"I"},{"n":"J","v":"J"},{"n":"K","v":"K"},{"n":"L","v":"L"},{"n":"M","v":"M"},{"n":"N","v":"N"},{"n":"O","v":"O"},{"n":"P","v":"P"},{"n":"Q","v":"Q"},{"n":"R","v":"R"},{"n":"S","v":"S"},{"n":"T","v":"T"},{"n":"U","v":"U"},{"n":"V","v":"V"},{"n":"W","v":"W"},{"n":"X","v":"X"},{"n":"Y","v":"Y"},{"n":"Z","v":"Z"},{"n":"0-9","v":"0-9"}]},{"key":"by","name":"排序","value":[{"n":"时间排序","v":"time"},{"n":"人气排序","v":"hits"},{"n":"评分排序","v":"score"}]}],
"4":[{"key":"class","name":"剧情","value":[{"n":"全部","v":""},{"n":"情感","v":"情感"},{"n":"科幻","v":"科幻"},{"n":"热血","v":"热血"},{"n":"推理","v":"推理"},{"n":"搞笑","v":"搞笑"},{"n":"冒险","v":"冒险"},{"n":"萝莉","v":"萝莉"},{"n":"校园","v":"校园"},{"n":"动作","v":"动作"},{"n":"机战","v":"机战"},{"n":"运动","v":"运动"},{"n":"战争","v":"战争"},{"n":"少年","v":"少年"},{"n":"少女","v":"少女"},{"n":"社会","v":"社会"},{"n":"原创","v":"原创"},{"n":"亲子","v":"亲子"},{"n":"益智","v":"益智"},{"n":"励志","v":"励志"},{"n":"其他","v":"其他"}]},{"key":"area","name":"地区","value":[{"n":"全部","v":""},{"n":"国产","v":"国产"},{"n":"日本","v":"日本"},{"n":"欧美","v":"欧美"},{"n":"其他","v":"其他"}]},{"key":"lang","name":"语言","value":[{"n":"全部","v":""},{"n":"国语","v":"国语"},{"n":"英语","v":"英语"},{"n":"粤语","v":"粤语"},{"n":"闽南语","v":"闽南语"},{"n":"韩语","v":"韩语"},{"n":"日语","v":"日语"},{"n":"其它","v":"其它"}]},{"key":"year","name":"年份","value":[{"n":"全部","v":""},{"n":"2026","v":"2026"},{"n":"2025","v":"2025"},{"n":"2024","v":"2024"},{"n":"2023","v":"2023"},{"n":"2022","v":"2022"},{"n":"2021","v":"2021"},{"n":"2020","v":"2020"},{"n":"2019","v":"2019"},{"n":"2018","v":"2018"},{"n":"2017","v":"2017"},{"n":"2016","v":"2016"},{"n":"2015","v":"2015"},{"n":"2014","v":"2014"},{"n":"2013","v":"2013"},{"n":"2012","v":"2012"},{"n":"2011","v":"2011"},{"n":"2010","v":"2010"},{"n":"2009","v":"2009"},{"n":"2008","v":"2008"},{"n":"2007","v":"2007"},{"n":"2006","v":"2006"},{"n":"2005","v":"2005"},{"n":"2004","v":"2004"}]},{"key":"letter","name":"字母","value":[{"n":"字母","v":""},{"n":"A","v":"A"},{"n":"B","v":"B"},{"n":"C","v":"C"},{"n":"D","v":"D"},{"n":"E","v":"E"},{"n":"F","v":"F"},{"n":"G","v":"G"},{"n":"H","v":"H"},{"n":"I","v":"I"},{"n":"J","v":"J"},{"n":"K","v":"K"},{"n":"L","v":"L"},{"n":"M","v":"M"},{"n":"N","v":"N"},{"n":"O","v":"O"},{"n":"P","v":"P"},{"n":"Q","v":"Q"},{"n":"R","v":"R"},{"n":"S","v":"S"},{"n":"T","v":"T"},{"n":"U","v":"U"},{"n":"V","v":"V"},{"n":"W","v":"W"},{"n":"X","v":"X"},{"n":"Y","v":"Y"},{"n":"Z","v":"Z"},{"n":"0-9","v":"0-9"}]},{"key":"by","name":"排序","value":[{"n":"时间排序","v":"time"},{"n":"人气排序","v":"hits"},{"n":"评分排序","v":"score"}]}]}
return JSON.stringify({
class: classes,
filters: filterObj
});
}
async function homeVod() {
const link = `${HOST}/`;
try {
const html = await request(link);
if (!html) return null;
const $ = load(html);
const hotSection = $('.title-a:contains("热播推荐")').closest('.box-width');
if (hotSection.length === 0) return null;
const items = hotSection.find('.public-list-box');
if (items.length === 0) return null;
const videos = _.map(items, (item) => {
const $item = $(item);
const $link = $item.find('.public-list-exp');
const $img = $item.find('.gen-movie-img');
const $title = $item.find('.time-title');
const $subtitle = $item.find('.public-list-subtitle');
const href = $link.attr('href');
const title = $title.attr('title') || getText($title);
const imgSrc = $img.attr('data-src');
const remarks = getText($subtitle);
const vod_id = href && href.startsWith('http') ? href : HOST + (href || '');
const vod_name = title;
const vod_pic = imgSrc ? (imgSrc.startsWith('http') ? imgSrc : HOST + imgSrc) : '';
const vod_remarks = remarks;
return {
vod_id,
vod_name,
vod_pic,
vod_remarks
};
});
return JSON.stringify({
list: videos
});
} catch (error) {
return JSON.stringify({
list: []
});
}
}
async function category(tid, pg, filter, extend) {
if (pg <= 0) pg = 1;
const homeFilter = {};
const homeResult = JSON.parse(await home(homeFilter));
const filterObj = homeResult.filters;
if (!filterObj) return null;
const filterGroup = filterObj[tid];
if (!filterGroup) return null;
const area = getFilterValue(filterGroup, 'area', extend);
const by = getFilterValue(filterGroup, 'by', extend);
const classValue = getFilterValue(filterGroup, 'class', extend);
const lang = getFilterValue(filterGroup, 'lang', extend);
const letter = getFilterValue(filterGroup, 'letter', extend);
const year = getFilterValue(filterGroup, 'year', extend);
const cateId = getFilterValue(filterGroup, 'cateId', extend);
const baseUrl = `${HOST}/show/${tid}`;
const link = `${baseUrl}-${area}-${by}-${classValue}-${lang}-${letter}---${pg}---${year}.html`;
try {
const html = await requestWithCaptcha(link, 'show');
if (!html) return null;
const $ = load(html);
const isParsedSuccessfully = $('.public-list-box').length > 0;
if (!isParsedSuccessfully) return null;
const items = $('.public-list-box');
const videos = _.map(items, (item) => {
const $item = $(item);
const $link = $item.find('.public-list-exp');
const $title = $item.find('.time-title');
const $img = $item.find('.gen-movie-img');
const $remarks = $item.find('.public-list-subtitle');
const href = $link.attr('href');
const title = $title.attr('title') || getText($title);
const imgSrc = $img.attr('data-src');
const remarks = getText($remarks);
const vod_id = href && href.startsWith('http') ? href : HOST + (href || '');
const vod_name = title;
const vod_pic = imgSrc ? (imgSrc.startsWith('http') ? imgSrc : HOST + imgSrc) : '';
const vod_remarks = remarks;
return {
'vod_id': vod_id,
'vod_name': vod_name,
'vod_pic': vod_pic,
'vod_remarks': vod_remarks
};
});
return JSON.stringify({
list: videos
});
} catch (error) {
return JSON.stringify({
list: []
});
}
}
function getFilterValue(filterGroup, key, extend) {
const filterItem = filterGroup.find(item => item.key === key);
if (!filterItem) return '';
const userValue = extend[key];
if (userValue) {
const option = filterItem.value.find(opt => opt.v === userValue);
return option ? option.v : '';
}
return filterItem.value[0].v;
}
async function search(wd) {
const searchUrl = `${HOST}/nk/-------------.html?wd=${encodeURIComponent(wd)}`;
const lowerWd = wd.toLowerCase();
try {
const html = await requestWithCaptcha(searchUrl, 'search');
if (!html) return JSON.stringify({ list: [], error: '需要验证码但无法自动识别' });
const $ = load(html);
const hasSearchResults = $('.public-list-box.search-box').length > 0;
const hasSearchInput = $('input[name="wd"]').length > 0;
const hasSearchHeader = $('.m-search').length > 0;
if ((hasSearchInput || hasSearchHeader) && !hasSearchResults) {
return JSON.stringify({ list: [], error: '搜索无结果或需要验证码' });
}
if (!hasSearchInput && !hasSearchHeader && !hasSearchResults) {
return JSON.stringify({ list: [], error: '需要验证码但无法自动识别' });
}
const items = $('.public-list-box.search-box');
const videos = [];
items.each((index, item) => {
const $item = $(item);
const $link = $item.find('.public-list-exp');
const href = $link.attr('href');
if (!href) return;
const $title = $item.find('.thumb-txt a');
const vod_name = getText($title);
if (!vod_name) return;
const $img = $item.find('.gen-movie-img');
const vod_pic = $img.attr('data-src');
const $remarks = $item.find('.public-list-prb');
const vod_remarks = getText($remarks);
const $info = $item.find('.thumb-else');
const yearText = getText($info.find('a[href*="202"]').first());
const areaText = getText($info.find('a[href*="美国"], a[href*="英国"], a[href*="泰国"]').first());
const lowerTitle = vod_name.toLowerCase();
if (lowerTitle.includes(lowerWd)) {
videos.push({
'vod_id': HOST + href,
'vod_name': vod_name,
'vod_pic': vod_pic,
'vod_remarks': vod_remarks,
'vod_year': yearText,
'vod_area': areaText
});
}
});
return JSON.stringify({ list: videos });
} catch (error) {
return JSON.stringify({ list: [] });
}
}
async function detail(id) {
try {
const html = await request(id);
if (!html) return JSON.stringify({ list: [] });
const $ = load(html);
const vod = {
vod_id: id,
vod_name: getText($('.slide-info-title')),
vod_pic: $('.detail-pic img').attr('data-src') || $('.detail-pic img').attr('src'),
vod_year: '',
vod_area: '',
vod_remarks: '',
vod_actor: '',
vod_director: '',
vod_content: getText($('#height_limit')),
vod_play_from: '',
vod_play_url: ''
};
$('.slide-info').each((i, elem) => {
const $elem = $(elem);
const text = getText($elem);
if (text.includes('备注 :') || text.includes('备注 :')) {
vod.vod_remarks = text.replace(/备注\s*[::]/g, '').trim();
} else if (text.includes('导演 :') || text.includes('导演 :')) {
vod.vod_director = text.replace(/导演\s*[::]/g, '').replace(/[\/\s]+$/, '').trim();
} else if (text.includes('演员 :') || text.includes('演员 :')) {
vod.vod_actor = text.replace(/演员\s*[::]/g, '').replace(/[\/\s]+$/, '').trim();
} else if (text.includes('更新 :') || text.includes('更新 :')) {
const updateText = text.replace(/更新\s*[::]/g, '').trim();
const yearMatch = updateText.match(/\d{4}/);
if (yearMatch) {
vod.vod_year = yearMatch[0];
}
}
});
// 从链接中提取年份和地区
const yearLink = $('.slide-info a[href*="202"]').first();
if (yearLink.length) {
const yearText = getText(yearLink);
if (/^\d{4}$/.test(yearText)) vod.vod_year = yearText;
}
// 尝试从 slide-info-remarks 获取年份和地区
$('.slide-info-remarks a').each((i, elem) => {
const $a = $(elem);
const href = $a.attr('href') || '';
const aText = getText($a);
if (/\d{4}/.test(aText) && !vod.vod_year) {
vod.vod_year = aText.match(/\d{4}/)[0];
}
if ((href.includes('内地') || href.includes('香港') || href.includes('台湾') ||
href.includes('美国') || href.includes('韩国') || href.includes('日本') ||
href.includes('泰国') || href.includes('英国')) && !vod.vod_area) {
vod.vod_area = aText;
}
});
const playFrom = [];
const playUrl = [];
$('.anthology-tab .swiper-slide').each((i, tab) => {
const $tab = $(tab);
let from = $tab.attr('data-from') || '';
if (!from) from = `nk${i + 1}`;
// 获取线路名称,优先从文本中提取
let show = getText($tab);
// 清理文本:去除图标字符、badge数字等
show = show.replace(/[\ue000-\uf8ff]/g, '') // 去除图标字体
.replace(/&\w+;/g, '')
.replace(/\s*\d+\s*$/, '') // 去除末尾的badge数字
.trim();
if (!show) show = from;
playFrom.push(show);
});
$('.anthology-list-box').each((i, box) => {
const episodes = [];
const $tab = $('.anthology-tab .swiper-slide').eq(i);
let from = $tab.attr('data-from') || '';
if (!from) from = `nk${i + 1}`;
$(box).find('.anthology-list-play a').each((j, link) => {
const $link = $(link);
let episodeName = getText($link);
if (!episodeName) {
// 如果还是获取不到,从 href 中提取
const href = $link.attr('href') || '';
const match = href.match(/-(\d+)\.html$/);
episodeName = match ? `第${match[1]}集` : `第${j + 1}集`;
}
const episodeUrl = HOST + $link.attr('href');
episodes.push(`${episodeName}$${episodeUrl}@${from}`);
});
playUrl.push(episodes.join('#'));
});
vod.vod_play_from = playFrom.join('$$$');
vod.vod_play_url = playUrl.join('$$$');
return JSON.stringify({ list: [vod] });
} catch (error) {
return JSON.stringify({ list: [] });
}
}
async function play(flag, id, flags) {
try {
const parts = id.split('@');
if (parts.length < 2) {
throw new Error('播放链接格式错误,缺少英文标识');
}
const playUrl = parts[0];
const from = parts[1];
const html = await request(playUrl);
const playerStart = html.indexOf('var player_aaaa=');
if (playerStart === -1) throw new Error('未找到player_aaaa');
const playerEnd = html.indexOf('</script>', playerStart);
if (playerEnd === -1) throw new Error('未找到脚本结束标签');
let playerScript = html.substring(playerStart + 'var player_aaaa='.length, playerEnd);
playerScript = playerScript.split(';')[0].trim()
.replace(/,\s*$/, '')
.replace(/\/\*[\s\S]*?\*\//g, '')
.replace(/\/\/.*/g, '');
let playerData;
try {
playerData = JSON.parse(playerScript);
} catch (parseErr) {
eval(`playerData = ${playerScript}`);
}
if (!playerData || !playerData.url) {
throw new Error('播放数据格式异常: url 缺失');
}
let playurl = playerData.url;
if (/m3u8|mp4/i.test(playurl)) {
return JSON.stringify({ parse: 0, url: playurl });
}
const configUrl = `${HOST}/static/js/playerconfig.js?t=20251225`;
const configJs = await request(configUrl);
if (!configJs) throw new Error('获取 playerconfig.js 失败');
let MacPlayerConfig = {};
try {
const evalCode = `(function(){
var MacPlayerConfig = {};
${configJs.replace(/^var MacPlayerConfig=.*?;/, '')}
return MacPlayerConfig;
})()`;
MacPlayerConfig = eval(evalCode);
} catch (e) {
MacPlayerConfig = {
player_list: {
nk1: { parse: 'https://gg.xn--it-if7c19g5s4bps5c.com/nkvod3.php' },
nk2: { parse: 'https://gg.xn--it-if7c19g5s4bps5c.com/nkvod3.php' },
nk4k: { parse: 'https://gg.xn--it-if7c19g5s4bps5c.com/nkvod3.php' }
}
};
}
const playerList = MacPlayerConfig.player_list || {};
let parseDomain = playerList[from]?.parse || playerList.nk4k.parse;
let jx = '';
if (parseDomain.includes('?')) {
jx = `${parseDomain}&url=${encodeURIComponent(playurl)}&next=${encodeURIComponent(playerData.link_next || '')}&title=${encodeURIComponent(playerData.vod_data?.vod_name || '')}`;
} else {
jx = `${parseDomain}?url=${encodeURIComponent(playurl)}&next=${encodeURIComponent(playerData.link_next || '')}&title=${encodeURIComponent(playerData.vod_data?.vod_name || '')}`;
}
const jxhtml = await request(jx);
if (!jxhtml) throw new Error('解析接口响应为空');
let raw_key = [], iv = [], encrypted = '';
const rawKeyMatch = jxhtml.match(/var\s+raw_key\s*=\s*\[([^\]]+)\]/);
if (rawKeyMatch) {
raw_key = rawKeyMatch[1].split(',').map(item => parseInt(item.trim())).filter(num => !isNaN(num));
}
const ivMatch = jxhtml.match(/var\s+iv\s*=\s*\[([^\]]+)\]/);
if (ivMatch) {
iv = ivMatch[1].split(',').map(item => parseInt(item.trim())).filter(num => !isNaN(num));
}
const encryptedMatch = jxhtml.match(/var\s+encrypted\s*=\s*["']([a-fA-F0-9]+)["']/);
if (encryptedMatch) {
encrypted = encryptedMatch[1];
}
const validKeyLengths = [16, 32];
const validIVLength = 16;
if (!encrypted || !validKeyLengths.includes(raw_key.length) || iv.length !== validIVLength) {
throw new Error(`加密参数提取失败或长度异常 → key:${raw_key.length}, iv:${iv.length}, encrypted:${encrypted.length}`);
}
const decryptedText = decryptData(encrypted, raw_key, iv);
if (!decryptedText) {
throw new Error('解密失败');
}
let finalPlayUrl = '';
const randomMatch = decryptedText.match(/getrandom\(['"]([^'"]+)['"]\)/);
if (randomMatch) {
finalPlayUrl = getrandom(randomMatch[1]);
} else {
const urlPattern = /https?:\/\/[^\s"'<>]+/gi;
const urlMatches = decryptedText.match(urlPattern);
if (urlMatches && urlMatches.length > 0) {
finalPlayUrl = urlMatches[0];
} else {
throw new Error('未找到 getrandom 参数或URL');
}
}
return JSON.stringify({ parse: 0, url: finalPlayUrl });
} catch (error) {
return JSON.stringify({ parse: 1, url: id, error: error.message });
}
}
function validateHexData(hexStr) {
if (hexStr.length % 2 !== 0) {
return false;
}
if (!/^[0-9a-fA-F]+$/.test(hexStr)) {
return false;
}
return true;
}
function decryptData(encryptedHex, rawKeyArray, ivArray) {
if (!validateHexData(encryptedHex)) {
return null;
}
const rawKey = Crypto.lib.WordArray.create(new Uint8Array(rawKeyArray));
const iv = Crypto.lib.WordArray.create(new Uint8Array(ivArray));
const encrypted = Crypto.enc.Hex.parse(encryptedHex);
const modes = [
{ name: 'CBC', mode: Crypto.mode.CBC },
{ name: 'OFB', mode: Crypto.mode.OFB },
{ name: 'CTR', mode: Crypto.mode.CTR },
{ name: 'ECB', mode: Crypto.mode.ECB },
{ name: 'CFB', mode: Crypto.mode.CFB }
];
for (let modeInfo of modes) {
try {
const decryptionOptions = {
mode: modeInfo.mode,
padding: Crypto.pad.Pkcs7
};
if (modeInfo.name !== 'ECB') decryptionOptions.iv = iv;
const decrypted = Crypto.AES.decrypt({ ciphertext: encrypted }, rawKey, decryptionOptions);
const result = decrypted.toString(Crypto.enc.Utf8);
if (result && result.length > 0) {
return result;
}
} catch (e) {
}
}
return null;
}
function getrandom(b) {
try {
const string = b.substring(10);
let substr;
try {
const base64Data = Crypto.enc.Base64.parse(string);
substr = base64Data.toString(Crypto.enc.Latin1);
} catch (e) {
throw new Error('Base64解码失败');
}
if (!substr) {
throw new Error('Base64解码结果为空');
}
const substrTrim = substr.substring(10);
let data2 = substrTrim.replace('_nanke', '');
let data3 = data2.slice(0, 20) + data2.slice(21);
const hexStr = data3.replace(/[^0-9a-fA-F]/g, '');
const finalUrl = hexDecodeAndFilter(hexStr);
return finalUrl;
} catch (error) {
throw error;
}
}
function hexDecodeAndFilter(hexStr) {
try {
let pureHex = hexStr.replace(/[^0-9a-fA-F]/g, '');
if (pureHex.length % 2 !== 0) {
pureHex += '0';
}
let decoded = '';
for (let i = 0; i < pureHex.length; i += 2) {
const hexByte = pureHex.substr(i, 2);
const charCode = parseInt(hexByte, 16);
const char = String.fromCharCode(charCode);
const isUrlValid = /[a-zA-Z0-9:\/\.\-\?\&=\%]/.test(char);
if (isUrlValid) {
decoded += char;
}
}
const urlMatch = decoded.match(/https?:\/\/[^\s]+/);
decoded = urlMatch ? urlMatch[0] : decoded.trim();
return decoded;
} catch (e) {
throw e;
}
}
export function __jsEvalReturn() {
return {
init: init,
detail: detail,
home: home,
play: play,
search: search,
homeVod: homeVod,
category: category
};
}