Files
tvbox_collect/TB18/TS/AppFox.js
T
2026-07-05 10:21:24 +00:00

664 lines
22 KiB
JavaScript

/*小心儿悠悠
配置说明:
{
"host": "https://xxx", // 必填,站点地址
"ver": 1, // 可选,API版本:1或2,默认1
"cms": "", // 可选,CMS地址
"from": "", // 可选,搜索来源
"category": 1, // 可选,分类模式:0-无分类 1-普通 2-搜索
"timeout": 5, // 可选,超时时间(秒),默认5
"custom_first": 0, // 可选,自定义解析优先:0-否 1-是
"source_order": ["线路A", "线路B"], // 可选,线路排序
// UA配置//可选,特殊站点需要自定义
"ua": {
"host": "UA字符串", // 主机检测UA
"config": "UA字符串", // 配置获取UA
"home": "UA字符串", // 首页UA
"category": "UA字符串", // 分类页UA
"search": "UA字符串", // 搜索页UA
"parse": "UA字符串", // 解析UA
"player": "UA字符串" // 播放UA
},
// 解析配置
"parse": {
"线路名": "解析地址或parse:前缀"
}
}
使用示例:
{
"host": "https://xxx",
"ver": 1,
"source_order": ["超清", "高清", "标清"],
"ua": {
"home": "Mozilla/5.0...",
"player": "Mozilla/5.0..."
},
"parse": {
"超清": "https://jx.xxx/?url=",
"高清": "parse:https://parse.xxx.com/"
}
}
*/
import { Crypto, _ } from 'assets://js/lib/cat.js';
const MOBILE_UA = "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.91 Mobile Safari/537.36";
const TV_UA = "Dalvik/2.1.0 (Linux; U; Android 9; Pixel Build/PI)";
var def_header = {'User-Agent': TV_UA};
var cachedPlayUrls = {};
var HOST;
var foxConfig = {
host: '',
headers: {'User-Agent': TV_UA},
timeout: 5000,
ver: 1,
uas: {},
parses: {},
custom_parses: {},
froms: '',
detail: [],
custom_first: 0,
category: 1,
cms: '',
sourceOrder: []
};
var recommendCache = {
data: [],
timestamp: 0,
cacheTime: 30 * 60 * 1000
};
function decodeBase64(str) {
try {
return Crypto.enc.Utf8.stringify(Crypto.enc.Base64.parse(str));
} catch (e) {
return str;
}
}
function request(reqUrl, header, data, method) {
try {
let optObj = {
headers: header || foxConfig.headers,
method: method || 'get',
timeout: foxConfig.timeout,
};
if (method === 'post') {
optObj.data = data || {};
optObj.postType = 'form';
}
let res = req(reqUrl, optObj);
return res.content;
} catch (e) {
throw new Error('请求失败: ' + reqUrl);
}
}
function getdata(url, data, method) {
try {
url = /^http/.test(url) ? url : `${HOST}${url}`;
let res = request(url, foxConfig.headers, data, method);
return JSON.parse(res);
} catch (e) {
return {};
}
}
function sortSources(play_from, play_urls) {
if (!foxConfig.sourceOrder || foxConfig.sourceOrder.length === 0) {
return { play_from, play_urls };
}
let sourceMap = new Map();
for (let i = 0; i < play_from.length; i++) {
if (i < play_urls.length) {
sourceMap.set(play_from[i], {
originalName: play_from[i],
url: play_urls[i],
index: i
});
}
}
let sorted_play_from = [];
let sorted_play_urls = [];
for (let sourceName of foxConfig.sourceOrder) {
for (let [key, source] of sourceMap) {
if (key.includes(sourceName)) {
sorted_play_from.push(source.originalName);
sorted_play_urls.push(source.url);
sourceMap.delete(key);
break;
}
}
}
for (let [key, source] of sourceMap) {
sorted_play_from.push(source.originalName);
sorted_play_urls.push(source.url);
}
return {
play_from: sorted_play_from,
play_urls: sorted_play_urls
};
}
async function getRecommendVideos() {
try {
const now = Date.now();
if (recommendCache.data.length > 0 && now - recommendCache.timestamp < recommendCache.cacheTime) {
return recommendCache.data;
}
let headers = Object.assign({}, foxConfig.headers);
let recommend_ua = foxConfig.uas.home || foxConfig.uas.homeVideo;
if (recommend_ua) headers['User-Agent'] = recommend_ua;
let videos = [];
let res = request(HOST + '/api.php/Appfox/vodList?sort=热门&page=1&limit=12', headers);
let response = JSON.parse(res);
if (response.data && response.data.list) {
videos = response.data.list.slice(0, 12);
} else if (response.data && response.data.recommend_list) {
videos = response.data.recommend_list.slice(0, 12);
} else if (Array.isArray(response.data)) {
videos = response.data.slice(0, 12);
}
if (videos.length > 0) {
recommendCache.data = videos;
recommendCache.timestamp = now;
}
return videos;
} catch (e) {
return [];
}
}
async function init(cfg) {
try {
if (!cfg.ext) throw new Error('配置参数为空');
let extValue = cfg.ext;
if (typeof extValue === 'string') {
try {
extValue = decodeBase64(extValue);
} catch (e) {}
try {
extValue = JSON.parse(extValue);
} catch (e) {
if (extValue.startsWith('http')) {
extValue = {host: extValue};
} else {
throw new Error('配置格式错误');
}
}
}
let host = extValue.host || '';
if (!host) throw new Error('主机地址为空');
if (!host.match(/^https?:\/\/[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*(:\d+)?(\/)?$/)) {
let headers = Object.assign({}, foxConfig.headers);
let custom_ua = foxConfig.uas.host;
if (custom_ua) headers['User-Agent'] = custom_ua;
let res = request(host, headers);
let result = JSON.parse(res);
host = result.apiDomain;
}
foxConfig.host = host.replace(/\/+$/, '');
HOST = foxConfig.host;
foxConfig.ver = extValue.ver === 2 ? 2 : 1;
foxConfig.cms = extValue.cms || '';
foxConfig.froms = extValue.from || '';
foxConfig.custom_parses = extValue.parse || {};
foxConfig.custom_first = extValue.custom_first || 0;
foxConfig.category = extValue.category || 1;
foxConfig.timeout = (extValue.timeout || 5) * 1000;
foxConfig.sourceOrder = extValue.source_order || [];
let ua = extValue.ua;
if (ua) {
if (typeof ua === 'string') {
foxConfig.headers['User-Agent'] = ua;
} else if (typeof ua === 'object') {
foxConfig.uas = {
host: ua.host,
config: ua.config,
home: ua.home,
category: ua.category,
search: ua.search,
parse: ua.parse,
player: ua.player || 'Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1'
};
}
}
if (foxConfig.cms) {
let cms = foxConfig.cms.replace(/\/+$/, '');
if (cms.match(/^https?:\/\/.*\/vod/)) {
cms += cms.includes('?') ? '&' : '?';
foxConfig.cms = cms;
}
}
} catch (e) {
throw new Error('初始化失败: ' + e.message);
}
}
async function home(filter) {
try {
if (!HOST || foxConfig.category === 0) return '{"class":[]}';
let headers = Object.assign({}, foxConfig.headers);
let custom_ua = foxConfig.uas.home;
if (custom_ua) headers['User-Agent'] = custom_ua;
if (foxConfig.cms && foxConfig.category !== 2) {
let class_url = foxConfig.cms.replace(/&ac=videolist|ac=videolist&|ac=videolist|ac=detail/g, '');
if (!class_url.includes('ac=')) class_url += 'ac=list';
let classRes = request(class_url, headers);
let classes = JSON.parse(classRes).class;
let recommendVideos = await getRecommendVideos();
let dataRes = request(foxConfig.cms.replace(/&$/, ''), headers);
let data = JSON.parse(dataRes);
data.class = classes;
if (recommendVideos.length > 0) {
data.recommend = recommendVideos;
}
return JSON.stringify(data);
} else {
let res = request(HOST + '/api.php/Appfox/init', headers);
let response = JSON.parse(res);
let classes = [];
for (let i of response.data.type_list) {
classes.push({type_id: i.type_id, type_name: i.type_name});
}
let recommendVideos = await getRecommendVideos();
let result = {class: classes};
if (recommendVideos.length > 0) {
result.recommend = recommendVideos;
}
return JSON.stringify(result);
}
} catch (e) {
return '{"class":[]}';
}
}
async function homeVod() {
try {
if (!HOST || foxConfig.category === 0) return '{"list":[]}';
if (foxConfig.cms && foxConfig.category !== 2) {
let headers = Object.assign({}, foxConfig.headers);
let custom_ua = foxConfig.uas.homeVideo || foxConfig.uas.home;
if (custom_ua) headers['User-Agent'] = custom_ua;
let res = request(foxConfig.cms.replace(/&$/, ''), headers);
let data = JSON.parse(res);
if (!data.list || data.list.length === 0) {
let recommendVideos = await getRecommendVideos();
if (recommendVideos.length > 0) {
data.list = recommendVideos;
}
}
if (foxConfig.category === 2 && data.list) {
for (let i of data.list) {
i.vod_id = 'msearch:' + i.vod_id;
}
}
return JSON.stringify(data);
}
let headers = Object.assign({}, foxConfig.headers);
let custom_ua = foxConfig.uas.homeVideo || foxConfig.uas.home;
if (custom_ua) headers['User-Agent'] = custom_ua;
let path = foxConfig.ver === 2 ? 'nav' : 'index';
let res = request(HOST + '/api.php/Appfox/' + path, headers);
let response = JSON.parse(res);
let videos = [];
if (foxConfig.ver === 2) {
let navigationId = '';
for (let i of response.data) {
if (i.navigationId) {
navigationId = i.navigationId;
break;
}
}
if (navigationId) {
let navRes = request(HOST + '/api.php/Appfox/nav_video?id=' + navigationId, headers);
let navData = JSON.parse(navRes);
for (let item of navData.data) {
if (item.videos) {
videos = videos.concat(item.videos);
}
}
}
} else {
for (let item of response.data) {
if (item.banner) videos = videos.concat(item.banner);
if (item.categories) {
for (let cat of item.categories) {
if (cat.videos) videos = videos.concat(cat.videos);
}
}
}
}
if (videos.length === 0) {
videos = await getRecommendVideos();
}
if (foxConfig.category === 2) {
for (let i of videos) {
i.vod_id = 'msearch:' + i.vod_id;
}
}
return JSON.stringify({list: videos.slice(0, 20)});
} catch (e) {
try {
let recommendVideos = await getRecommendVideos();
return JSON.stringify({list: recommendVideos});
} catch (e2) {
return '{"list":[]}';
}
}
}
async function category(tid, pg, filter, extend) {
try {
if (!HOST) return '{"list":[]}';
pg = parseInt(pg) || 1;
let headers = Object.assign({}, foxConfig.headers);
let category_ua = foxConfig.uas.category;
if (category_ua) headers['User-Agent'] = category_ua;
if (foxConfig.cms && foxConfig.category !== 2) {
let res = request(foxConfig.cms + 'pg=' + pg + '&t=' + tid, headers);
let data = JSON.parse(res);
if (foxConfig.category === 2 && data.list) {
for (let i of data.list) {
i.vod_id = 'msearch:' + i.vod_id;
}
}
return JSON.stringify(data);
} else {
let res = request(HOST + '/api.php/Appfox/vodList?type_id=' + tid + '&page=' + pg, headers);
let response = JSON.parse(res);
let videos = response.data?.list || response.data?.recommend_list || [];
let data = {list: videos};
if (foxConfig.category === 2) {
for (let i of data.list) {
i.vod_id = 'msearch:' + i.vod_id;
}
}
return JSON.stringify(data);
}
} catch (e) {
return '{"list":[]}';
}
}
async function search(wd, quick, pg) {
try {
if (!HOST) return '{"list":[]}';
pg = parseInt(pg) || 1;
let headers = Object.assign({}, foxConfig.headers);
let search_ua = foxConfig.uas.search;
if (search_ua) headers['User-Agent'] = search_ua;
if (foxConfig.cms) {
let cms = foxConfig.cms.split('?')[0] + '?';
let res = request(cms + 'ac=detail&wd=' + encodeURIComponent(wd), headers);
let response = JSON.parse(res);
foxConfig.detail = response.list || [];
return JSON.stringify(response);
} else {
let path = HOST + '/api.php/Appfox/vod?ac=detail&wd=' + encodeURIComponent(wd);
if (foxConfig.froms) path += '&from=' + foxConfig.froms;
let res = request(path, headers);
let response = JSON.parse(res);
foxConfig.detail = response.list || [];
return JSON.stringify(response);
}
} catch (e) {
return '{"list":[]}';
}
}
async function detail(id) {
try {
let headers = Object.assign({}, foxConfig.headers);
let detail_ua = foxConfig.uas.detail || foxConfig.uas.search;
if (detail_ua) headers['User-Agent'] = detail_ua;
let video = null;
for (let i of foxConfig.detail) {
if (String(i.vod_id) === String(id)) {
video = Object.assign({}, i);
break;
}
}
if (!video) {
if (foxConfig.cms) {
let cms = foxConfig.cms.split('?')[0] + '?';
let res = request(cms + 'ac=detail&ids=' + id, headers);
let response = JSON.parse(res);
video = response.list?.[0] || null;
} else {
let res = request(HOST + '/api.php/Appfox/vod?ac=detail&ids=' + id, headers);
let response = JSON.parse(res);
video = response.list?.[0] || null;
}
}
if (!video) return '{"list":[]}';
let play_from = video.vod_play_from ? video.vod_play_from.split('$$$') : [];
let play_urls = video.vod_play_url ? video.vod_play_url.split('$$$') : [];
let sortedSources = sortSources(play_from, play_urls);
play_from = sortedSources.play_from;
play_urls = sortedSources.play_urls;
try {
let config_headers = Object.assign({}, foxConfig.headers);
let custom_ua = foxConfig.uas.config;
if (custom_ua) config_headers['User-Agent'] = custom_ua;
let res = request(HOST + '/api.php/Appfox/config', config_headers);
let config_response = JSON.parse(res);
let player_list = config_response.data?.playerList || [];
let jiexi_data_list = config_response.data?.jiexiDataList || [];
let player_map = {};
for (let player of player_list) {
player_map[player.playerCode] = player;
}
let processed_play_urls = [];
for (let idx = 0; idx < play_from.length; idx++) {
let play_code = play_from[idx];
if (player_map[play_code]) {
let player_info = player_map[play_code];
if (player_info.playerCode !== player_info.playerName) {
play_from[idx] = player_info.playerName + '\u2005(' + play_code + ')';
}
}
if (idx < play_urls.length) {
let urls = play_urls[idx].split('#');
let processed_urls = [];
for (let url of urls) {
let parts = url.split('$');
if (parts.length >= 2) {
parts[1] = play_code + '@' + parts[1];
processed_urls.push(parts.join('$'));
} else {
processed_urls.push(url);
}
}
processed_play_urls.push(processed_urls.join('#'));
}
}
video.vod_play_from = play_from.join('$$$💕琉芸👉');
video.vod_play_url = processed_play_urls.join('$$$');
foxConfig.parses = {};
for (let p of jiexi_data_list) {
if (p.url && p.url.startsWith('http')) {
foxConfig.parses[p.playerCode] = p.url;
}
}
} catch (e) {
}
return JSON.stringify({list: [video]});
} catch (e) {
return '{"list":[]}';
}
}
async function play(flag, id, flags) {
try {
let parts = id.split('@', 2);
if (parts.length < 2) {
throw new Error('ID格式错误');
}
let play_from = parts[0];
let raw_url = parts[1];
let jx = 0, parse = 0, parsed = 0;
let headers = Object.assign({}, foxConfig.headers);
let parse_ua = foxConfig.uas.parse;
if (parse_ua) headers['User-Agent'] = parse_ua;
let player_ua = foxConfig.uas.player || MOBILE_UA;
let url = raw_url;
let parses_main = [];
if (foxConfig.custom_first === 1) {
parses_main.push(foxConfig.custom_parses);
parses_main.push(foxConfig.parses);
} else {
parses_main.push(foxConfig.parses);
parses_main.push(foxConfig.custom_parses);
}
outer: for (let parses2 of parses_main) {
if (!parsed && !url.match(/https?:\/\/.*\.(m3u8|mp4|flv|mkv)/)) {
for (let key in parses2) {
if (!play_from.includes(key)) continue;
let parsers = parses2[key];
if (Array.isArray(parsers)) {
for (let parser of parsers) {
if (parser && parser.startsWith('parse:')) {
url = parser.split('parse:')[1] + raw_url;
jx = 0;
parse = 1;
break outer;
}
if (parser && parser.startsWith('http')) {
try {
let res = request(parser + raw_url, headers);
let response = JSON.parse(res);
if (response.url && response.url.startsWith('http')) {
url = response.url;
parsed = 1;
break outer;
}
} catch (e) {
continue;
}
}
}
} else {
if (parsers && parsers.startsWith('parse:')) {
url = parsers.split('parse:')[1] + raw_url;
jx = 0;
parse = 1;
break outer;
}
if (parsers && parsers.startsWith('http')) {
try {
let res = request(parsers + raw_url, headers);
let response = JSON.parse(res);
if (response.url && response.url.startsWith('http')) {
url = response.url;
parsed = 1;
break outer;
}
} catch (e) {
continue;
}
}
}
if (parsed || parse) break;
}
}
if (parsed || parse) break;
}
if (!(url.match(/https?:\/\/.*\.(m3u8|mp4|flv|mkv)/) || parsed === 1)) {
jx = 1;
}
return JSON.stringify({
jx: jx,
parse: parse,
url: url,
header: {'User-Agent': player_ua}
});
} catch (e) {
return JSON.stringify({
jx: 0,
parse: 0,
url: id,
header: {}
});
}
}
export function __jsEvalReturn() {
return {
init: init,
home: home,
homeVod: homeVod,
category: category,
search: search,
detail: detail,
play: play
};
}