/*小心儿悠悠 配置说明: { "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 }; }