上传文件至「open」
This commit is contained in:
@@ -0,0 +1,310 @@
|
||||
/*
|
||||
@header({
|
||||
searchable: 1,
|
||||
filterable: 1,
|
||||
quickSearch: 1,
|
||||
title: '聚合儿歌[儿]',
|
||||
lang: 'cat'
|
||||
})
|
||||
*/
|
||||
|
||||
let siteName = '聚合儿歌', siteKey = '', siteType = 0;
|
||||
|
||||
const platformList = [
|
||||
{ name: '贝乐虎', id: 'beilehu' },
|
||||
{ name: '兔小贝', id: 'tuxiaobei' }
|
||||
];
|
||||
|
||||
const headers = {
|
||||
'User-Agent': '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'
|
||||
};
|
||||
|
||||
const rule = {
|
||||
beilehu: {
|
||||
host: 'https://vd.ubestkid.com',
|
||||
api: '/api/v1/bv/video'
|
||||
},
|
||||
tuxiaobei: {
|
||||
host: 'https://www.tuxiaobei.com',
|
||||
listApi: '/list/mip-data',
|
||||
playUrl: '/play/',
|
||||
searchApi: '/search/'
|
||||
}
|
||||
};
|
||||
|
||||
const filterOptions = {
|
||||
beilehu: [{
|
||||
key: "area", name: "分类",
|
||||
value: [
|
||||
{ "n": "最新上架", "v": "65" }, { "n": "人气热播", "v": "113" }, { "n": "经典童谣", "v": "56" },
|
||||
{ "n": "开心贝乐虎", "v": "137" }, { "n": "律动儿歌", "v": "53" }, { "n": "经典儿歌", "v": "59" },
|
||||
{ "n": "超级汽车1", "v": "101" }, { "n": "超级汽车第二季", "v": "119" }, { "n": "超级汽车第三季", "v": "136" },
|
||||
{ "n": "三字经", "v": "95" }, { "n": "幼儿手势舞", "v": "133" }, { "n": "哄睡儿歌", "v": "117" },
|
||||
{ "n": "英文儿歌", "v": "70" }, { "n": "节日与节气", "v": "116" }, { "n": "恐龙世界", "v": "97" },
|
||||
{ "n": "动画片儿歌", "v": "55" }, { "n": "流行歌曲", "v": "57" }, { "n": "贝乐虎入园记", "v": "118" },
|
||||
{ "n": "贝乐虎大百科", "v": "106" }, { "n": "经典古诗", "v": "62" }, { "n": "经典故事", "v": "63" },
|
||||
{ "n": "萌虎学功夫", "v": "128" }, { "n": "绘本故事", "v": "100" }, { "n": "开心贝乐虎英文版", "v": "121" },
|
||||
{ "n": "嗨贝乐虎情商动画", "v": "96" }, { "n": "动物音乐派对", "v": "108" }, { "n": "动物音乐派对英文版", "v": "126" },
|
||||
{ "n": "奇妙的身体", "v": "105" }, { "n": "奇妙的身体英文版", "v": "124" }, { "n": "认知卡片", "v": "64" },
|
||||
{ "n": "趣味简笔画", "v": "109" }, { "n": "数字儿歌", "v": "78" }, { "n": "识字体验版", "v": "120" },
|
||||
{ "n": "启蒙系列体验版", "v": "127" }
|
||||
]
|
||||
}],
|
||||
tuxiaobei: [{
|
||||
key: "area", name: "分类",
|
||||
value: [
|
||||
{ "n": "全部", "v": "" }, { "n": "儿歌", "v": "2" }, { "n": "故事", "v": "3" },
|
||||
{ "n": "公益", "v": "27" }, { "n": "十万个为什么", "v": "9" }, { "n": "安全教育", "v": "28" },
|
||||
{ "n": "动物奇缘", "v": "29" }, { "n": "弟子规", "v": "7" }, { "n": "古诗", "v": "5" },
|
||||
{ "n": "三字经", "v": "6" }, { "n": "千字文", "v": "8" }, { "n": "数学", "v": "11" },
|
||||
{ "n": "英语", "v": "25" }, { "n": "折纸", "v": "24" }
|
||||
]
|
||||
}]
|
||||
};
|
||||
|
||||
const ruleFilterDef = {
|
||||
beilehu: { area: '56' },
|
||||
tuxiaobei: { area: '2' }
|
||||
};
|
||||
|
||||
function init(cfg) {
|
||||
siteName = cfg.skey?.split('_')[1] || cfg.skey || '聚合儿歌';
|
||||
siteKey = cfg.skey;
|
||||
siteType = cfg.stype;
|
||||
}
|
||||
|
||||
function safeJSONParse(str, defaultValue = {}) {
|
||||
if (!str || typeof str === 'object') return str || defaultValue;
|
||||
try {
|
||||
return JSON.parse(str);
|
||||
} catch {
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
async function request(url, options = {}) {
|
||||
const reqHeaders = { ...headers, ...options.headers };
|
||||
let postType = reqHeaders['Content-Type']?.includes('json') ? 'json' :
|
||||
reqHeaders['Content-Type']?.includes('form') ? 'form' : '';
|
||||
|
||||
try {
|
||||
const response = await req(url, {
|
||||
method: options.method || 'GET',
|
||||
headers: reqHeaders,
|
||||
data: options.data,
|
||||
postType: postType,
|
||||
timeout: options.timeout || 15000
|
||||
});
|
||||
return response?.content || response?.data || response;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function getPlatList() {
|
||||
return platformList;
|
||||
}
|
||||
|
||||
async function getBeilehuList(typeId, page) {
|
||||
let videos = [];
|
||||
try {
|
||||
const postData = {
|
||||
age: 1,
|
||||
appver: "6.1.9",
|
||||
egvip_status: 0,
|
||||
svip_status: 0,
|
||||
vps: 60,
|
||||
subcateId: parseInt(typeId),
|
||||
p: page
|
||||
};
|
||||
|
||||
const html = await request(rule.beilehu.host + rule.beilehu.api, {
|
||||
method: 'POST',
|
||||
headers: { ...headers, 'Content-Type': 'application/json' },
|
||||
data: postData
|
||||
});
|
||||
|
||||
const json = safeJSONParse(html);
|
||||
const items = json.result?.items || [];
|
||||
|
||||
videos = items.map(item => ({
|
||||
vod_id: `beilehu@${item.url}`,
|
||||
vod_name: item.title || '未知视频',
|
||||
vod_pic: item.image || '',
|
||||
vod_remarks: `贝乐虎 | 播放:${item.viewcount || 0}`,
|
||||
vod_content: item.description || ''
|
||||
}));
|
||||
} catch (e) {}
|
||||
return videos;
|
||||
}
|
||||
|
||||
async function getTuxiaobeiList(typeId, page) {
|
||||
let videos = [];
|
||||
try {
|
||||
const url = `${rule.tuxiaobei.host}${rule.tuxiaobei.listApi}?typeId=${typeId}&page=${page}&callback=`;
|
||||
const html = await request(url, { headers });
|
||||
|
||||
const match = html.match(/\((.*?)\);/);
|
||||
if (!match) return videos;
|
||||
|
||||
const data = safeJSONParse(match[1]).data;
|
||||
const items = data.items || [];
|
||||
|
||||
videos = items.map(item => ({
|
||||
vod_id: `tuxiaobei@${item.video_id}`,
|
||||
vod_name: item.name || '未知视频',
|
||||
vod_pic: item.image || '',
|
||||
vod_remarks: `兔小贝 | ${item.root_category_name || ''} ${item.duration_string || ''}`,
|
||||
vod_content: item.description || ''
|
||||
}));
|
||||
} catch (e) {}
|
||||
return videos;
|
||||
}
|
||||
|
||||
async function getBeilehuDetail(url) {
|
||||
return {
|
||||
vod_id: url,
|
||||
vod_name: '贝乐虎视频',
|
||||
vod_remarks: '贝乐虎',
|
||||
vod_play_from: '贝乐虎',
|
||||
vod_play_url: `点击播放$${url}`
|
||||
};
|
||||
}
|
||||
|
||||
async function getTuxiaobeiDetail(id) {
|
||||
return {
|
||||
vod_id: id,
|
||||
vod_name: '兔小贝视频',
|
||||
vod_remarks: '兔小贝',
|
||||
vod_play_from: '兔小贝',
|
||||
vod_play_url: `点击播放$${rule.tuxiaobei.host}${rule.tuxiaobei.playUrl}${id}`
|
||||
};
|
||||
}
|
||||
|
||||
async function home(filter) {
|
||||
const platForms = getPlatList();
|
||||
const classes = platForms.map(item => ({ type_name: item.name, type_id: item.id }));
|
||||
const filters = {};
|
||||
platForms.forEach(item => { if (filterOptions[item.id]) filters[item.id] = filterOptions[item.id]; });
|
||||
return JSON.stringify({ class: classes, filters: filters });
|
||||
}
|
||||
|
||||
async function homeVod() {
|
||||
try {
|
||||
const platForms = getPlatList();
|
||||
const randomPlat = platForms[Math.floor(Math.random() * platForms.length)];
|
||||
const randomArea = ruleFilterDef[randomPlat.id]?.area || '';
|
||||
const categoryResult = await category(randomPlat.id, 1, { area: randomArea }, {});
|
||||
const categoryList = safeJSONParse(categoryResult).list || [];
|
||||
return JSON.stringify({ list: categoryList.slice(0, 12) });
|
||||
} catch (e) {
|
||||
return JSON.stringify({ list: [] });
|
||||
}
|
||||
}
|
||||
|
||||
async function category(tid, pg, filter, extend) {
|
||||
const page = pg || 1;
|
||||
extend = extend || {};
|
||||
|
||||
const platformItem = platformList.find(p => p.id === tid);
|
||||
if (!platformItem) {
|
||||
return JSON.stringify({ list: [], page, pagecount: 1, limit: 0, total: 0 });
|
||||
}
|
||||
|
||||
const searchKeyword = extend?.custom;
|
||||
if (searchKeyword) {
|
||||
return await cfs(tid, searchKeyword, pg);
|
||||
}
|
||||
|
||||
const area = filter?.area || extend?.area || ruleFilterDef[tid]?.area || '';
|
||||
const videos = [];
|
||||
|
||||
try {
|
||||
switch (tid) {
|
||||
case 'beilehu':
|
||||
videos.push(...await getBeilehuList(area, page));
|
||||
break;
|
||||
case 'tuxiaobei':
|
||||
videos.push(...await getTuxiaobeiList(area, page));
|
||||
break;
|
||||
}
|
||||
} catch (e) {}
|
||||
|
||||
return JSON.stringify({
|
||||
list: videos,
|
||||
page: page,
|
||||
pagecount: page + 1,
|
||||
limit: videos.length,
|
||||
total: videos.length * (page + 1)
|
||||
});
|
||||
}
|
||||
|
||||
async function detail(id) {
|
||||
try {
|
||||
const parts = id.split('@');
|
||||
const platform = parts[0];
|
||||
const did = parts.slice(1).join('@');
|
||||
|
||||
let vod = {};
|
||||
if (platform === 'beilehu') {
|
||||
vod = await getBeilehuDetail(did);
|
||||
} else if (platform === 'tuxiaobei') {
|
||||
vod = await getTuxiaobeiDetail(did);
|
||||
}
|
||||
return JSON.stringify({ list: [vod] });
|
||||
} catch (e) {
|
||||
return JSON.stringify({ list: [] });
|
||||
}
|
||||
}
|
||||
|
||||
async function play(flag, id, flags) {
|
||||
try {
|
||||
if (flag.includes('贝乐虎')) {
|
||||
return JSON.stringify({ parse: 0, url: id, header: headers });
|
||||
}
|
||||
|
||||
if (flag.includes('兔小贝')) {
|
||||
try {
|
||||
const html = await request(id, { headers });
|
||||
let videoUrl = '';
|
||||
|
||||
const srcMatch = html.match(/video-src=["']([^"']+)["']/);
|
||||
if (srcMatch) videoUrl = srcMatch[1];
|
||||
|
||||
if (!videoUrl) {
|
||||
const sourceMatch = html.match(/<source[^>]*src=["']([^"']+)["']/);
|
||||
if (sourceMatch) videoUrl = sourceMatch[1];
|
||||
}
|
||||
|
||||
if (!videoUrl) {
|
||||
const m3u8Match = html.match(/https?:\/\/[^"']+\.m3u8[^"']*/);
|
||||
if (m3u8Match) videoUrl = m3u8Match[0];
|
||||
}
|
||||
|
||||
if (!videoUrl) {
|
||||
return JSON.stringify({ parse: 0, url: id, msg: '未找到播放地址' });
|
||||
}
|
||||
|
||||
return JSON.stringify({ parse: 0, url: videoUrl, header: headers });
|
||||
} catch (e) {
|
||||
return JSON.stringify({ parse: 0, url: id, msg: `播放失败: ${e.message}` });
|
||||
}
|
||||
}
|
||||
|
||||
return JSON.stringify({ parse: 0, url: id });
|
||||
} catch (e) {
|
||||
return JSON.stringify({ parse: 0, url: id, msg: `播放失败: ${e.message}` });
|
||||
}
|
||||
}
|
||||
|
||||
async function cfs(siteId, wd, pg) {
|
||||
return JSON.stringify({ list: [], page: pg || 1, pagecount: 1, limit: 0, total: 0 });
|
||||
}
|
||||
|
||||
async function search(wd, quick, pg) {
|
||||
return JSON.stringify({ list: [], page: pg || 1, pagecount: 1, limit: 0, total: 0 });
|
||||
}
|
||||
|
||||
export function __jsEvalReturn() {
|
||||
return { init, home, homeVod, category, detail, play, search };
|
||||
}
|
||||
@@ -0,0 +1,434 @@
|
||||
/*
|
||||
@header({
|
||||
searchable: 1,
|
||||
filterable: 1,
|
||||
quickSearch: 0,
|
||||
title: '荐片',
|
||||
lang: 'cat'
|
||||
})
|
||||
*/
|
||||
|
||||
let siteName = '荐片', siteKey = '', siteType = 0;
|
||||
let host = 'https://api.ztcgi.com';
|
||||
let imghost = '';
|
||||
let maxPages = 5;
|
||||
let config = {};
|
||||
|
||||
let title_remove = ['名称排除', '广告', '破解', '群'];
|
||||
let line_remove = ['线路排除', '广告', '666', 'mymv'];
|
||||
let line_order = ['线路排序', '蓝光', 'ft', '官', 'ace', '1080p', 'dytt'];
|
||||
let cate_remove = ['分类排除', '推荐', '首页'];
|
||||
|
||||
let rule = {
|
||||
homeCategory: '/api/v2/settings/homeCategory',
|
||||
resourceDomain: '/api/v2/settings/resourceDomainConfig',
|
||||
slideList: '/api/slide/list',
|
||||
dyTag: '/api/dyTag/tpl2_data',
|
||||
crumbList: '/api/crumb/list',
|
||||
detail: '/api/video/detailv2',
|
||||
search: '/api/v2/search/videoV2'
|
||||
};
|
||||
|
||||
const headers = {
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/120.0.0.0 Safari/537.36'
|
||||
};
|
||||
|
||||
function safeJSONParse(str, defaultValue = {}) {
|
||||
if (!str || typeof str === 'object') return str || defaultValue;
|
||||
try {
|
||||
return JSON.parse(str);
|
||||
} catch {
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
async function request(url, options = {}) {
|
||||
const reqHeaders = { ...headers, ...options.headers };
|
||||
let postType = reqHeaders['Content-Type']?.includes('json') ? 'json' :
|
||||
reqHeaders['Content-Type']?.includes('form') ? 'form' : '';
|
||||
|
||||
try {
|
||||
const response = await req(url, {
|
||||
method: options.method || 'GET',
|
||||
headers: reqHeaders,
|
||||
data: options.data,
|
||||
postType: postType,
|
||||
timeout: options.timeout || 15000
|
||||
});
|
||||
return response?.content || response?.data || response;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function init(cfg) {
|
||||
siteName = cfg.skey?.split('_')[1] || cfg.skey || '荐片';
|
||||
siteKey = cfg.skey;
|
||||
siteType = cfg.stype;
|
||||
|
||||
let ext = cfg.ext !== undefined ? cfg.ext : cfg;
|
||||
|
||||
if (typeof ext === 'string' && ext.includes('$')) {
|
||||
const [url, order] = ext.split('$');
|
||||
const response = await req(url, { headers, timeout: 10000 });
|
||||
if (response && response.content) {
|
||||
config = safeJSONParse(response.content)[order] || {};
|
||||
host = config.host || config.hosturl || config.url || config.site;
|
||||
}
|
||||
} else if (ext && typeof ext === 'object') {
|
||||
config = ext;
|
||||
host = config.host || config.hosturl || config.url || config.site;
|
||||
}
|
||||
|
||||
if (!host) {
|
||||
host = 'https://api.ztcgi.com';
|
||||
}
|
||||
|
||||
|
||||
if (config.title_remove !== undefined) title_remove = Array.isArray(config.title_remove) ? config.title_remove : title_remove;
|
||||
if (config.line_remove !== undefined) line_remove = Array.isArray(config.line_remove) ? config.line_remove : line_remove;
|
||||
if (config.line_order !== undefined) line_order = Array.isArray(config.line_order) ? config.line_order : line_order;
|
||||
if (config.cate_remove !== undefined) cate_remove = Array.isArray(config.cate_remove) ? config.cate_remove : cate_remove;
|
||||
|
||||
try {
|
||||
let res = await req(`${host}${rule.resourceDomain}`, { headers, timeout: 10000 });
|
||||
if (res && res.content) {
|
||||
let configData = safeJSONParse(res.content);
|
||||
if (configData.code === 1 && configData.data && configData.data.imgDomain) {
|
||||
const domainList = configData.data.imgDomain.split(',');
|
||||
imghost = `https://${domainList[Math.floor(Math.random() * domainList.length)].trim()}`;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
imghost = 'https://img.jgsfnl.com';
|
||||
}
|
||||
}
|
||||
|
||||
async function home(filter) {
|
||||
let html = await request(`${host}${rule.homeCategory}`);
|
||||
if (!html) {
|
||||
return JSON.stringify({ class: [], filters: {} });
|
||||
}
|
||||
|
||||
let parsed = safeJSONParse(html);
|
||||
let res = parsed.data;
|
||||
|
||||
if (!res || !Array.isArray(res)) {
|
||||
return JSON.stringify({ class: [], filters: {} });
|
||||
}
|
||||
|
||||
let classes = [];
|
||||
res.forEach(item => {
|
||||
if (item && item.id && item.name) {
|
||||
classes.push({ type_id: item.id.toString(), type_name: item.name });
|
||||
}
|
||||
});
|
||||
|
||||
const commonFilter = [{
|
||||
"key": "cateId", "name": "分类",
|
||||
"value": [{"v": "", "n": "全部"}, {"v": "1", "n": "剧情"}, {"v": "2", "n": "爱情"}, {"v": "3", "n": "动画"}, {"v": "4", "n": "喜剧"}, {"v": "5", "n": "战争"}, {"v": "6", "n": "歌舞"}, {"v": "7", "n": "古装"}, {"v": "8", "n": "奇幻"}, {"v": "9", "n": "冒险"}, {"v": "10", "n": "动作"}, {"v": "11", "n": "科幻"}, {"v": "12", "n": "悬疑"}, {"v": "13", "n": "犯罪"}, {"v": "14", "n": "家庭"}, {"v": "15", "n": "传记"}, {"v": "16", "n": "运动"}, {"v": "18", "n": "惊悚"}, {"v": "20", "n": "短片"}, {"v": "21", "n": "历史"}, {"v": "22", "n": "音乐"}, {"v": "23", "n": "西部"}, {"v": "24", "n": "武侠"}, {"v": "25", "n": "恐怖"}]
|
||||
}, {
|
||||
"key": "area", "name": "地区",
|
||||
"value": [{"v": "", "n": "全部"}, {"v": "1", "n": "国产"}, {"v": "3", "n": "中国香港"}, {"v": "6", "n": "中国台湾"}, {"v": "5", "n": "美国"}, {"v": "18", "n": "韩国"}, {"v": "2", "n": "日本"}]
|
||||
}, {
|
||||
"key": "year", "name": "年代",
|
||||
"value": [{"v": "", "n": "全部"}, {"v": "162", "n": "2026"}, {"v": "107", "n": "2025"}, {"v": "119", "n": "2024"}, {"v": "153", "n": "2023"}, {"v": "101", "n": "2022"}, {"v": "118", "n": "2021"}, {"v": "16", "n": "2020"}, {"v": "7", "n": "2019"}, {"v": "2", "n": "2018"}, {"v": "3", "n": "2017"}, {"v": "22", "n": "2016"}, {"v": "2015", "n": "2015以前"}]
|
||||
}, {
|
||||
"key": "sort", "name": "排序",
|
||||
"value": [{"v": "update", "n": "最新"}, {"v": "hot", "n": "最热"}, {"v": "rating", "n": "评分"}]
|
||||
}];
|
||||
|
||||
let filterObj = {};
|
||||
classes.forEach(item => {
|
||||
if (item.type_id !== '88' && item.type_id !== '99') {
|
||||
filterObj[item.type_id] = commonFilter;
|
||||
}
|
||||
});
|
||||
|
||||
let i = 0;
|
||||
while (i < classes.length) {
|
||||
const isBad = cate_remove.some(word => new RegExp(word, 'i').test(classes[i].type_name));
|
||||
if (isBad) {
|
||||
classes.splice(i, 1);
|
||||
} else {
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
return JSON.stringify({ class: classes, filters: filterObj });
|
||||
}
|
||||
|
||||
async function homeVod() {
|
||||
let html = await request(`${host}${rule.slideList}?pos_id=88`);
|
||||
if (!html) {
|
||||
return JSON.stringify({ list: [] });
|
||||
}
|
||||
|
||||
let parsed = safeJSONParse(html);
|
||||
let res = parsed.data;
|
||||
|
||||
if (!res || !Array.isArray(res)) {
|
||||
return JSON.stringify({ list: [] });
|
||||
}
|
||||
|
||||
let videos = [];
|
||||
res.forEach(item => {
|
||||
if (item && item.jump_id) {
|
||||
videos.push({
|
||||
vod_id: item.jump_id,
|
||||
vod_name: item.title || '未知标题',
|
||||
vod_pic: imghost ? `${imghost}${item.thumbnail || ''}` : (item.thumbnail || ''),
|
||||
vod_remarks: "",
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
let filteredVideos = [];
|
||||
videos.forEach(item => {
|
||||
const title = item.vod_name;
|
||||
const isBadTitle = title_remove.some(word => new RegExp(word, 'i').test(title));
|
||||
if (!isBadTitle) {
|
||||
filteredVideos.push(item);
|
||||
}
|
||||
});
|
||||
|
||||
return JSON.stringify({ list: filteredVideos });
|
||||
}
|
||||
|
||||
async function DyTag(id, pg) {
|
||||
let url = `${host}${rule.dyTag}?id=${id}&page=${pg}`;
|
||||
let html = await request(url);
|
||||
if (!html) return [];
|
||||
|
||||
let parsed = safeJSONParse(html);
|
||||
let res = parsed.data;
|
||||
|
||||
if (!res || !Array.isArray(res)) return [];
|
||||
|
||||
let videos = [];
|
||||
res.forEach(item => {
|
||||
if (item) {
|
||||
videos.push({
|
||||
vod_id: item.id,
|
||||
vod_name: item.title || '未知标题',
|
||||
vod_pic: imghost ? `${imghost}${item.path || ''}` : (item.path || ''),
|
||||
vod_remarks: item.mask || '',
|
||||
});
|
||||
}
|
||||
});
|
||||
return videos;
|
||||
}
|
||||
|
||||
async function category(tid, pg, filter, extend) {
|
||||
if (pg <= 0) pg = 1;
|
||||
let videos = [];
|
||||
|
||||
if (tid === '99' || tid === 99) {
|
||||
videos = await DyTag(70, pg);
|
||||
} else {
|
||||
let extendParams = extend || {};
|
||||
let url = `${host}${rule.crumbList}?fcate_pid=${tid}&category_id=&area=${extendParams.area || ''}&year=${extendParams.year || ''}&type=${extendParams.cateId || ''}&sort=${extendParams.sort || ''}&page=${pg}`;
|
||||
|
||||
let html = await request(url);
|
||||
if (html) {
|
||||
let parsed = safeJSONParse(html);
|
||||
let res = parsed.data;
|
||||
if (res && Array.isArray(res)) {
|
||||
res.forEach(item => {
|
||||
if (item) {
|
||||
videos.push({
|
||||
vod_id: item.id,
|
||||
vod_name: item.title || '未知标题',
|
||||
vod_pic: imghost ? `${imghost}${item.path || ''}` : (item.path || ''),
|
||||
vod_remarks: item.mask || '',
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let filteredVideos = [];
|
||||
videos.forEach(item => {
|
||||
if (!item.vod_name) return;
|
||||
const title = item.vod_name;
|
||||
const isBadTitle = title_remove.some(word => new RegExp(word, 'i').test(title));
|
||||
if (!isBadTitle) {
|
||||
filteredVideos.push(item);
|
||||
}
|
||||
});
|
||||
|
||||
return JSON.stringify({
|
||||
page: parseInt(pg),
|
||||
pagecount: 99999,
|
||||
limit: filteredVideos.length,
|
||||
total: 99999,
|
||||
list: filteredVideos
|
||||
});
|
||||
}
|
||||
|
||||
async function detail(id) {
|
||||
let html = await request(`${host}${rule.detail}?id=${id}`);
|
||||
if (!html) {
|
||||
return JSON.stringify({ list: [] });
|
||||
}
|
||||
|
||||
let parsed = safeJSONParse(html);
|
||||
let res = parsed.data;
|
||||
if (!res) {
|
||||
return JSON.stringify({ list: [] });
|
||||
}
|
||||
|
||||
let playForm = [];
|
||||
let playUrls = [];
|
||||
|
||||
if (res.source_list_source && Array.isArray(res.source_list_source)) {
|
||||
res.source_list_source.forEach(item => {
|
||||
if (!item) return;
|
||||
const form = item.name || '未知线路';
|
||||
let finalForm = form;
|
||||
|
||||
if (item.source_list && item.source_list.length > 0 && item.source_list[0] && item.source_list[0].url) {
|
||||
let domain = extractDomain(item.source_list[0].url);
|
||||
if (domain.length > 8) domain = domain.substring(0, 8);
|
||||
finalForm = `${form}(${domain})`;
|
||||
}
|
||||
|
||||
const isBadLine = line_remove.some(pattern => finalForm.toLowerCase().includes(pattern.toLowerCase()));
|
||||
|
||||
if (!isBadLine) {
|
||||
playForm.push(finalForm);
|
||||
let urls = [];
|
||||
if (item.source_list && Array.isArray(item.source_list)) {
|
||||
item.source_list.forEach(source => {
|
||||
if (source && source.source_name && source.url) {
|
||||
urls.push(`${source.source_name}$${source.url}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
playUrls.push(urls.join('#'));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let combined = [];
|
||||
playForm.forEach((form, i) => {
|
||||
if (playUrls[i]) {
|
||||
combined.push({ form, url: playUrls[i] });
|
||||
}
|
||||
});
|
||||
|
||||
combined.sort((a, b) => {
|
||||
const getPri = name => {
|
||||
const idx = line_order.findIndex(k => name.toLowerCase().includes(k.toLowerCase()));
|
||||
return idx === -1 ? 999 : idx;
|
||||
};
|
||||
return getPri(a.form) - getPri(b.form);
|
||||
});
|
||||
|
||||
let sortedPlayForm = [];
|
||||
let sortedPlayUrls = [];
|
||||
combined.forEach(item => {
|
||||
sortedPlayForm.push(item.form);
|
||||
sortedPlayUrls.push(item.url);
|
||||
});
|
||||
|
||||
let play_from = [];
|
||||
sortedPlayForm.forEach(item => {
|
||||
play_from.push(item.replace(/常规线路/g, '边下边播'));
|
||||
});
|
||||
|
||||
const vod = {
|
||||
"vod_id": id,
|
||||
"vod_name": res.title || '未知标题',
|
||||
"vod_year": res.year || '',
|
||||
"vod_area": res.area || '',
|
||||
"vod_remarks": res.mask || '',
|
||||
"vod_content": res.description || '',
|
||||
"vod_pic": imghost ? `${imghost}${res.thumbnail || ''}` : (res.thumbnail || ''),
|
||||
"vod_play_from": play_from.join('$$$'),
|
||||
"vod_play_url": sortedPlayUrls.join('$$$')
|
||||
};
|
||||
|
||||
return JSON.stringify({ list: [vod] });
|
||||
}
|
||||
|
||||
async function play(flag, id, flags) {
|
||||
if (id && id.indexOf(".m3u8") > -1) {
|
||||
return JSON.stringify({ parse: 0, url: id });
|
||||
} else if (id) {
|
||||
return JSON.stringify({ parse: 0, url: `tvbox-xg:${id}` });
|
||||
}
|
||||
return JSON.stringify({ parse: 0, url: '', msg: '播放地址为空' });
|
||||
}
|
||||
|
||||
async function search(wd, quick, pg) {
|
||||
let page = pg || 1;
|
||||
|
||||
let promises = [];
|
||||
for (let p = page; p < page + maxPages; p++) {
|
||||
let url = `${host}${rule.search}?key=${encodeURIComponent(wd)}&category_id=88&page=${p}&pageSize=20`;
|
||||
promises.push(request(url, { headers, timeout: 8000 }));
|
||||
}
|
||||
|
||||
let results = await Promise.all(promises);
|
||||
|
||||
let allVideos = [];
|
||||
for (let html of results) {
|
||||
if (!html) continue;
|
||||
let parsed = safeJSONParse(html);
|
||||
let res = parsed.data;
|
||||
if (res && Array.isArray(res)) {
|
||||
res.forEach(item => {
|
||||
if (item && item.id) {
|
||||
allVideos.push({
|
||||
vod_id: item.id,
|
||||
vod_name: item.title || '未知标题',
|
||||
vod_pic: imghost ? `${imghost}${item.thumbnail || ''}` : (item.thumbnail || ''),
|
||||
vod_remarks: item.mask || '',
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let filteredVideos = [];
|
||||
for (let item of allVideos) {
|
||||
if (item.vod_name && new RegExp(wd, "i").test(item.vod_name)) {
|
||||
filteredVideos.push(item);
|
||||
}
|
||||
}
|
||||
|
||||
return JSON.stringify({
|
||||
page: page,
|
||||
pagecount: maxPages,
|
||||
limit: filteredVideos.length,
|
||||
total: filteredVideos.length,
|
||||
list: filteredVideos
|
||||
});
|
||||
}
|
||||
|
||||
function extractDomain(url) {
|
||||
if (!url) return '';
|
||||
const cleanUrl = url.replace(/^(https?:\/\/)?/, '');
|
||||
const domainPart = cleanUrl.split('/')[0];
|
||||
|
||||
if (domainPart.includes('-')) {
|
||||
return domainPart.split('-')[0];
|
||||
}
|
||||
|
||||
if (domainPart.includes('.')) {
|
||||
const dotParts = domainPart.split('.');
|
||||
if (dotParts.length > 2) {
|
||||
return dotParts[dotParts.length - 2];
|
||||
} else if (dotParts.length === 2) {
|
||||
return dotParts[0];
|
||||
}
|
||||
}
|
||||
|
||||
return domainPart;
|
||||
}
|
||||
|
||||
export function __jsEvalReturn() {
|
||||
return { init, home, homeVod, category, detail, play, search };
|
||||
}
|
||||
@@ -0,0 +1,208 @@
|
||||
/*
|
||||
@header({
|
||||
searchable: 1,
|
||||
filterable: 1,
|
||||
quickSearch: 1,
|
||||
title: '梨园[戏]',
|
||||
lang: 'cat',
|
||||
})
|
||||
*/
|
||||
let host = 'https://fly.daoran.tv';
|
||||
|
||||
let siteName = '梨园行', siteKey = '', siteType = 0;
|
||||
|
||||
let UA = {
|
||||
'User-Agent': 'okhttp/3.12.10',
|
||||
'Connection': 'Keep-Alive',
|
||||
'Content-Type': 'application/json',
|
||||
'md5': 'SkvyrWqK9QHTdCT12Rhxunjx+WwMTe9y4KwgeASFDhbYabRSPskR0Q=='
|
||||
};
|
||||
|
||||
let cate_remove = ['分类排除', '首页', '推荐'];
|
||||
|
||||
function init(cfg) {
|
||||
siteName = cfg.skey?.split('_')[1] || cfg.skey || '梨园行';
|
||||
siteKey = cfg.skey;
|
||||
siteType = cfg.stype;
|
||||
|
||||
let ext = cfg.ext !== undefined ? cfg.ext : cfg;
|
||||
if (typeof ext === 'string' && ext.trim() !== '') {
|
||||
host = ext.trim();
|
||||
} else if (typeof ext === 'object') {
|
||||
host = ext.host || ext.hosturl || ext.url || ext.site || host;
|
||||
if (ext.cate_remove !== undefined) {
|
||||
cate_remove = Array.isArray(ext.cate_remove) ? ext.cate_remove : cate_remove;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function safeJSONParse(str, defaultValue = {}) {
|
||||
if (!str || typeof str === 'object') return str || defaultValue;
|
||||
try {
|
||||
return JSON.parse(str);
|
||||
} catch {
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
async function request(url, options = {}) {
|
||||
const reqHeaders = { ...UA, ...options.headers };
|
||||
let postType = reqHeaders['Content-Type']?.includes('json') ? 'json' :
|
||||
reqHeaders['Content-Type']?.includes('form') ? 'form' : '';
|
||||
|
||||
try {
|
||||
const response = await req(url, {
|
||||
method: options.method || 'GET',
|
||||
headers: reqHeaders,
|
||||
data: options.data,
|
||||
postType: postType,
|
||||
timeout: options.timeout || 15000
|
||||
});
|
||||
return response?.content || response?.data || response;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function home(filter) {
|
||||
const classNames = '豫剧&黄梅戏&越剧&京剧&评剧&曲剧&坠子&秦腔&河北梆子&潮剧&粤剧&沪剧&二夹弦&昆曲&河南琴书&淮剧&单弦&西秦戏&婺剧&上党梆子&白字戏&河南大鼓书&越调&滇剧&太康道情&民族音乐&扬剧&其他&曲艺晚会&二人台&北路梆子&彩调&乐腔&老年大学&吕剧&天津时调&戏曲&柳琴戏&京韵大鼓&皮影戏&湘剧&四平调&琼剧&锡剧&评书&绍剧&京东大鼓&庐剧&话剧&西河大鼓&莆仙戏&花鼓戏&川剧&相声&宛梆&晋中秧歌&采茶戏&蒲剧&汉剧&闽剧&晋剧&北京琴书&山歌剧&吉剧&正字戏&赣剧&麦田乡韾&楚剧&大平调&保定老调';
|
||||
const classUrls = 'yuju&hmx&yueju&jingju&pingju&quju&hnzz&qinq&hbbz&chaoju&gddx&huju&ejx&kunqu&hnqs&huaiju&danxian&xqx&wuju&SDBZ&bzx&hndgs&yued&dianju&tkdq&MZYY&yangju&other&else&ERT&blbz&caidiao&lq&WK&lvjv&tjsd&xq&liuqx&jydg&pyx&xj&spd&qiongju&xiju&pingshu&shaojv&jddg&luju&huaju&xhdg&huagx&chuanju&xiang&wb&jzyg&caichaxi&pujv&hj&minju&jinju&bjqs&sgj&jiju&zzx&gj&chuju&dpd&bdld';
|
||||
|
||||
const names = classNames.split('&');
|
||||
const urls = classUrls.split('&');
|
||||
|
||||
const classes = [];
|
||||
for (let i = 0; i < names.length && i < urls.length; i++) {
|
||||
const typeName = names[i];
|
||||
const isBad = cate_remove.some(word => typeName.includes(word));
|
||||
if (!isBad) {
|
||||
classes.push({ type_id: urls[i], type_name: typeName });
|
||||
}
|
||||
}
|
||||
|
||||
const filters = {};
|
||||
classes.forEach(cls => { filters[cls.type_id] = []; });
|
||||
|
||||
return JSON.stringify({ class: classes, filters: filters });
|
||||
}
|
||||
|
||||
async function homeVod() {
|
||||
const data = {
|
||||
"cur": 1, "free": 0, "orderby": "hot", "pageSize": 50, "resType": 1, "sect": [],
|
||||
"tagId": 0, "userId": "5af3139f6f73ae8a360402f381b94221", "channel": "yingyongbao",
|
||||
"item": "y9", "nodeCode": "001000", "project": "lyhxcx",
|
||||
"sign": "MmhTLnUjLLHi48rk9zwBANI3OX3f5EffDpHK7XK6pDakUevGnPah7dcDppuBR90yYbrerbCKxbFwSBTeysxD8g=="
|
||||
};
|
||||
|
||||
const html = await request(`${host}/API_ROP/search/album/list`, { method: 'POST', data: data });
|
||||
const list = safeJSONParse(html).pb.dataList;
|
||||
const videos = list.map(item => ({
|
||||
vod_id: `https://zheshiyitaiojialianjie.com?${item.code}`,
|
||||
vod_name: item.name,
|
||||
vod_pic: `https://ottphoto.daoran.tv/HD/${item.imgsec}`,
|
||||
vod_remarks: item.des || '戏曲'
|
||||
}));
|
||||
|
||||
return JSON.stringify({ list: videos });
|
||||
}
|
||||
|
||||
async function category(tid, pg, filter, extend) {
|
||||
const data = {
|
||||
"cur": pg || 1, "free": 0, "orderby": "play", "pageSize": 50, "resType": 1,
|
||||
"sect": tid, "tagId": 0, "userId": "5af3139f6f73ae8a360402f381b94221",
|
||||
"channel": "yingyongbao", "item": "y9", "nodeCode": "001000", "project": "lyhxcx",
|
||||
"sign": "MmhTLnUjLLHi48rk9zwBANI3OX3f5EffDpHK7XK6pDakUevGnPah7dcDppuBR90yYbrerbCKxbFwSBTeysxD8g=="
|
||||
};
|
||||
|
||||
const html = await request(`${host}/API_ROP/search/album/screen`, { method: 'POST', data: data });
|
||||
const list = safeJSONParse(html).pb.dataList;
|
||||
const videos = list.map(item => ({
|
||||
vod_id: `https://zheshiyitaiojialianjie.com?${item.code}`,
|
||||
vod_name: item.name,
|
||||
vod_pic: `https://ottphoto.daoran.tv/HD/${item.imgsec}`,
|
||||
vod_remarks: item.des || '戏曲'
|
||||
}));
|
||||
|
||||
return JSON.stringify({
|
||||
list: videos,
|
||||
page: pg || 1,
|
||||
pagecount: 1,
|
||||
limit: 20,
|
||||
total: videos.length
|
||||
});
|
||||
}
|
||||
|
||||
async function detail(id) {
|
||||
const code = id.split('?')[1];
|
||||
const data = {
|
||||
"albumCode": code, "cur": 1, "pageSize": 100,
|
||||
"userId": "5af3139f6f73ae8a360402f381b94221", "channel": "oppo", "item": "y9",
|
||||
"nodeCode": "001000", "project": "lyhxcx",
|
||||
"sign": "MmhTLnUjLLHi48rk9zwBANI3OX3f5EffDpHK7XK6pDakUevGnPah7dcDppuBR90yonrdMsz0hMVGJ92jA6Flzw=="
|
||||
};
|
||||
|
||||
const html = await request(`${host}/API_ROP/album/res/list`, { method: 'POST', data: data });
|
||||
const response = safeJSONParse(html);
|
||||
const list = response.pb.dataList;
|
||||
const album = response.album;
|
||||
|
||||
const playUrls = [];
|
||||
list.forEach(it => {
|
||||
playUrls.push(`${it.name}$https://zheshiyitaiojialianjie.com?${it.code}`);
|
||||
});
|
||||
|
||||
const vod = {
|
||||
vod_id: id,
|
||||
vod_name: album.name,
|
||||
vod_pic: 'https://img0.baidu.com/it/u=4079405848,3806507810&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=750',
|
||||
vod_content: album.des || '暂无简介',
|
||||
vod_remarks: "戏曲",
|
||||
vod_play_from: '梨园行',
|
||||
vod_play_url: playUrls.join('$$$')
|
||||
};
|
||||
|
||||
return JSON.stringify({ list: [vod] });
|
||||
}
|
||||
|
||||
async function play(flag, id, flags) {
|
||||
const code = id.split('?')[1];
|
||||
const data = {
|
||||
"item": "o3", "mask": 0, "nodeCode": "001000", "project": "lyhxcx",
|
||||
"px": 2, "resCode": code, "userId": "5af3139f6f73ae8a360402f381b94221"
|
||||
};
|
||||
|
||||
const html = await request(`${host}/API_ROP/play/get/playurl`, { method: 'POST', data: data });
|
||||
const parsed = safeJSONParse(html);
|
||||
const url = parsed.playUrls.hd;
|
||||
|
||||
return JSON.stringify({ parse: 0, url: url, header: {} });
|
||||
}
|
||||
|
||||
async function search(wd, quick, pg = "1") {
|
||||
const data = JSON.stringify({
|
||||
"cur": pg || 1, "free": 0, "keyword": wd, "nodeCode": "001000",
|
||||
"orderby": "hot", "pageSize": 200, "project": "lyhxcx", "px": 2,
|
||||
"sect": [], "userId": "5af3139f6f73ae8a360402f381b94221"
|
||||
});
|
||||
|
||||
const html = await request(`${host}/API_ROP/search/album/list`, { method: 'POST', data: data });
|
||||
const list = safeJSONParse(html).pb.dataList;
|
||||
const videos = list.map(item => ({
|
||||
vod_id: `https://zheshiyitaiojialianjie.com?${item.code}`,
|
||||
vod_name: item.name,
|
||||
vod_pic: 'https://img0.baidu.com/it/u=4079405848,3806507810&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=750',
|
||||
vod_remarks: item.des || ''
|
||||
}));
|
||||
|
||||
return JSON.stringify({
|
||||
list: videos,
|
||||
page: parseInt(pg),
|
||||
pagecount: 1,
|
||||
limit: 20,
|
||||
total: videos.length
|
||||
});
|
||||
}
|
||||
|
||||
export function __jsEvalReturn() {
|
||||
return { init, home, homeVod, category, detail, play, search };
|
||||
}
|
||||
@@ -0,0 +1,258 @@
|
||||
/*
|
||||
@header({
|
||||
searchable: 2,
|
||||
filterable: 1,
|
||||
quickSearch: 0,
|
||||
title: '听海[听]',
|
||||
author: 'EylinSir',
|
||||
'类型': '音乐',
|
||||
logo: 'https://pic.qqtf.com/up/2025-11/20251127175478238.png',
|
||||
lang: 'cat'
|
||||
})
|
||||
*/
|
||||
|
||||
let siteName = '听海', siteKey = '', siteType = 0;
|
||||
let host = 'http://wapi.kuwo.cn';
|
||||
|
||||
function init(cfg) {
|
||||
siteName = cfg.skey?.split('_')[1] || cfg.skey || '听海';
|
||||
siteKey = cfg.skey;
|
||||
siteType = cfg.stype;
|
||||
}
|
||||
|
||||
function safeJSONParse(str, defaultValue = {}) {
|
||||
if (!str || typeof str === 'object') return str || defaultValue;
|
||||
try {
|
||||
return JSON.parse(str);
|
||||
} catch {
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
async function request(url, options = {}) {
|
||||
const reqHeaders = { 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/120.0.0.0 Safari/537.36', ...options.headers };
|
||||
let postType = reqHeaders['Content-Type']?.includes('json') ? 'json' :
|
||||
reqHeaders['Content-Type']?.includes('form') ? 'form' : '';
|
||||
|
||||
try {
|
||||
const response = await req(url, {
|
||||
method: options.method || 'GET',
|
||||
headers: reqHeaders,
|
||||
data: options.data,
|
||||
postType: postType,
|
||||
timeout: options.timeout || 15000
|
||||
});
|
||||
return response?.content || response?.data || response;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function sleep(seconds) {
|
||||
return new Promise(resolve => setTimeout(resolve, seconds * 1000));
|
||||
}
|
||||
|
||||
function hd(img) {
|
||||
if (!img) return '';
|
||||
return img.replace('/120/', '/4000/').replace('/500/', '/2160/').replace('/150/', '/1000/').replace('/300/', '/1500/');
|
||||
}
|
||||
|
||||
function generateFilters() {
|
||||
return {
|
||||
"12": [{ key: "type", name: "专区", value: [{ "n": "国风专区", "v": "12" }, { "n": "老歌专区", "v": "13" }, { "n": "BGM专区", "v": "143" }, { "n": "伤感专区", "v": "147" }] }],
|
||||
"2189": [{ key: "type", name: "主题", value: [{ "n": "抖音", "v": "2189" }, { "n": "经典", "v": "1265" }, { "n": "情歌", "v": "2200" }, { "n": "BGM", "v": "2199" }, { "n": "演唱会", "v": "2212" }, { "n": "游戏", "v": "1877" }, { "n": "怀旧", "v": "155" }, { "n": "合唱", "v": "2201" }, { "n": "网络", "v": "621" }, { "n": "儿童", "v": "171" }, { "n": "ACG", "v": "181" }, { "n": "影视", "v": "180" }, { "n": "网红", "v": "1879" }, { "n": "春节", "v": "2190" }, { "n": "翻唱", "v": "1848" }] }],
|
||||
"146": [{ key: "type", name: "心情", value: [{ "n": "伤感", "v": "146" }, { "n": "解压", "v": "62" }, { "n": "励志", "v": "58" }, { "n": "开心", "v": "143" }, { "n": "甜蜜", "v": "137" }, { "n": "兴奋", "v": "139" }, { "n": "安静", "v": "67" }, { "n": "思念", "v": "160" }] }],
|
||||
"376": [{ key: "type", name: "场景", value: [{ "n": "开车", "v": "376" }, { "n": "运动", "v": "366" }, { "n": "睡眠", "v": "354" }, { "n": "跳舞", "v": "378" }, { "n": "学习", "v": "1876" }, { "n": "清晨", "v": "353" }, { "n": "KTV", "v": "361" }, { "n": "店铺专用", "v": "263" }, { "n": "校园", "v": "382" }, { "n": "旅行", "v": "375" }, { "n": "工作", "v": "386" }, { "n": "广场舞", "v": "334" }, { "n": "通勤", "v": "2202" }, { "n": "宅家", "v": "2203" }, { "n": "Citywalk", "v": "2214" }, { "n": "露营", "v": "2213" }] }],
|
||||
"637": [{ key: "type", name: "年代", value: [{ "n": "70后", "v": "637" }, { "n": "80后", "v": "638" }, { "n": "90后", "v": "639" }, { "n": "00后", "v": "640" }] }],
|
||||
"393": [{ key: "type", name: "曲风", value: [{ "n": "流行", "v": "393" }, { "n": "DJ", "v": "168" }, { "n": "古风", "v": "127" }, { "n": "佛乐", "v": "220" }, { "n": "轻音乐", "v": "173" }, { "n": "纯音乐", "v": "577" }, { "n": "电子", "v": "391" }, { "n": "喊麦", "v": "216" }, { "n": "3D", "v": "1366" }, { "n": "器乐", "v": "578" }, { "n": "摇滚", "v": "389" }, { "n": "民歌", "v": "1921" }, { "n": "民谣", "v": "392" }, { "n": "古典", "v": "390" }, { "n": "嘻哈", "v": "387" }, { "n": "乡村", "v": "399" }, { "n": "爵士", "v": "397" }, { "n": "R&B", "v": "394" }] }],
|
||||
"37": [{ key: "type", name: "语言", value: [{ "n": "华语", "v": "37" }, { "n": "欧美", "v": "35" }, { "n": "韩语", "v": "1093" }, { "n": "粤语", "v": "13" }, { "n": "日语", "v": "1091" }, { "n": "小语种", "v": "12" }] }]
|
||||
};
|
||||
}
|
||||
|
||||
async function getLyric(rid) {
|
||||
const maxRetries = 20;
|
||||
for (let retryCount = 0; retryCount < maxRetries; retryCount++) {
|
||||
try {
|
||||
let url = `https://kuwo.cn/openapi/v1/www/lyric/getlyric?musicId=${rid}`;
|
||||
let html = await request(url);
|
||||
if (html) {
|
||||
let json = safeJSONParse(html);
|
||||
if (json.code === 200 && json.data && json.data.lrclist && json.data.lrclist.length > 0) {
|
||||
let lrclist = json.data.lrclist;
|
||||
let lyric = lrclist.map(item => {
|
||||
let time = parseFloat(item.time) || 0;
|
||||
let min = Math.floor(time / 60).toString().padStart(2, '0');
|
||||
let sec = Math.floor(time % 60).toString().padStart(2, '0');
|
||||
let ms = Math.floor((time % 1) * 100).toString().padStart(2, '0');
|
||||
return `[${min}:${sec}.${ms}]${item.lineLyric || ''}`;
|
||||
}).join('\n');
|
||||
return lyric;
|
||||
}
|
||||
}
|
||||
} catch (e) {}
|
||||
if (retryCount < maxRetries - 1) await sleep(0.01);
|
||||
}
|
||||
return '暂无歌词';
|
||||
}
|
||||
|
||||
async function getUrl(rid, br) {
|
||||
let url = `https://nmobi.kuwo.cn/mobi.s?f=web&user=0&source=kwplayer_ar_4.4.2.7_B_nuoweida_vh.apk&type=convert_url_with_sign&rid=${rid}&format=flac&br=${br}`;
|
||||
let html = await request(url);
|
||||
if (html) {
|
||||
let j = safeJSONParse(html);
|
||||
return j?.data?.url?.trim() || '';
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
function home(filter) {
|
||||
return JSON.stringify({
|
||||
class: [
|
||||
{ type_id: '12', type_name: '专区' }, { type_id: '2189', type_name: '主题' },
|
||||
{ type_id: '146', type_name: '心情' }, { type_id: '376', type_name: '场景' },
|
||||
{ type_id: '637', type_name: '年代' }, { type_id: '393', type_name: '曲风流派' },
|
||||
{ type_id: '37', type_name: '语言' }
|
||||
],
|
||||
filters: generateFilters()
|
||||
});
|
||||
}
|
||||
|
||||
async function homeVod() {
|
||||
let url = `${host}/api/pc/classify/playlist/getRcmPlayList?pn=1&rn=30&order=hot`;
|
||||
let html = await request(url);
|
||||
let res = safeJSONParse(html);
|
||||
let data = res.data?.data || [];
|
||||
let videos = data.map(it => ({ vod_id: (it.id || '').toString(), vod_name: it.name || '未命名歌单', vod_pic: hd(it.img), vod_remarks: '🎧' + (it.listencnt || '0') }));
|
||||
return JSON.stringify({ list: videos });
|
||||
}
|
||||
|
||||
async function category(tid, pg, filter, extend) {
|
||||
if (pg <= 0 || typeof pg == 'undefined') pg = 1;
|
||||
const id = extend?.type || tid;
|
||||
let url = `${host}/api/pc/classify/playlist/getTagPlayList?pn=${pg}&rn=30&id=${id}`;
|
||||
let html = await request(url);
|
||||
let res = safeJSONParse(html);
|
||||
let data = res.data?.data || [];
|
||||
let videos = data.map(it => ({ vod_id: (it.id || '').toString(), vod_name: it.name || '未命名歌单', vod_pic: hd(it.img), vod_remarks: '🎧' + (it.listencnt || '0') }));
|
||||
return JSON.stringify({ page: parseInt(pg), pagecount: 999, limit: 30, total: 999, list: videos });
|
||||
}
|
||||
|
||||
async function detail(id) {
|
||||
let input = id.toString();
|
||||
|
||||
if (input.indexOf('$') > -1) {
|
||||
let s = input.split('$');
|
||||
return JSON.stringify({ list: [{ vod_id: s[1], vod_name: s[0], vod_pic: hd(s[2]), vod_play_from: '听海单曲', vod_play_url: s[0] + '$' + s[1], vod_play_pic: hd(s[2]), vod_play_pic_ratio: 1.0 }] });
|
||||
}
|
||||
|
||||
const limit = 100;
|
||||
let baseUrl = `${host}/api/www/playlist/playListInfo?pid=${input}&rn=${limit}&httpsStatus=1&pn=`;
|
||||
let html = await request(baseUrl + '1');
|
||||
let json = safeJSONParse(html);
|
||||
let data = json.data || {};
|
||||
let songs = data.musicList || data.musiclist || [];
|
||||
let total = parseInt(data.total || 0);
|
||||
|
||||
if (total > limit) {
|
||||
let tasks = [];
|
||||
for (let p = 2; p <= Math.min(Math.ceil(total / limit), 5); p++) {
|
||||
tasks.push(request(baseUrl + p));
|
||||
}
|
||||
let results = await Promise.all(tasks);
|
||||
results.forEach(r => {
|
||||
let d = safeJSONParse(r).data || {};
|
||||
songs = songs.concat(d.musicList || d.musiclist || []);
|
||||
});
|
||||
}
|
||||
|
||||
let playArr = [];
|
||||
let songPicArr = [];
|
||||
songs.forEach(it => {
|
||||
let rid = (it.rid || it.musicrid || '').toString().replace('MUSIC_', '');
|
||||
let song = (it.name || '').replace(/ /g, ' ');
|
||||
let artist = (it.artist || '').replace(/ /g, ' ');
|
||||
let albumpic = hd(it.albumpic || it.pic);
|
||||
let displayName = artist ? `${song} [${artist}]` : song;
|
||||
if (rid) {
|
||||
playArr.push(`${displayName}$${rid}&&${albumpic}&&${albumpic}`);
|
||||
songPicArr.push(albumpic);
|
||||
}
|
||||
});
|
||||
|
||||
const vod = { vod_id: input, vod_name: data.name || '酷我歌单', vod_pic: hd(data.img || data.img500), vod_content: data.info || '', vod_remarks: '听海歌单', vod_play_from: "听海歌单", vod_play_url: playArr.join('#'), vod_play_pic: songPicArr.join('#'), vod_play_pic_ratio: 1.0 };
|
||||
|
||||
return JSON.stringify({ list: [vod] });
|
||||
}
|
||||
|
||||
async function play(flag, id, flags) {
|
||||
let parts = id.split('&&');
|
||||
let firstPart = parts[0] || '';
|
||||
let firstParts = firstPart.split('$');
|
||||
let songId = firstParts.length > 1 ? firstParts[1] : firstParts[0];
|
||||
let albumPic = hd(parts[1]);
|
||||
|
||||
if (/\.(m3u8|mp4|m4a|mp3|aac|flac|ogg|mgg)(\?|$)/i.test(songId)) {
|
||||
let cleanUrl = songId.split('?')[0];
|
||||
return JSON.stringify({ parse: 0, url: cleanUrl, pic: albumPic, cover: albumPic, lrc: '', height: 720 });
|
||||
}
|
||||
|
||||
const qualities = [{ name: 'FLAC无损', br: '2000k' }, { name: 'HQ高品质', br: '320k' }, { name: '标准品质', br: '192k' }, { name: 'AAC高品', br: '128k' }];
|
||||
let urls = [];
|
||||
let seenUrls = new Set();
|
||||
|
||||
for (let q of qualities) {
|
||||
let url = await getUrl(songId, q.br);
|
||||
if (url && !url.includes('.mgg')) {
|
||||
let cleanUrl = url.split('?')[0];
|
||||
if (!seenUrls.has(cleanUrl)) {
|
||||
seenUrls.add(cleanUrl);
|
||||
urls.push(q.name, cleanUrl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let lrcPromise = getLyric(songId);
|
||||
let picUrl = albumPic;
|
||||
if (!picUrl) {
|
||||
try {
|
||||
let picRes = await request(`http://artistpicserver.kuwo.cn/pic.web?type=rid_pic&pictype=url&size=500&rid=${songId}`);
|
||||
picUrl = picRes.trim().replace('/500/', '/2160/');
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
let result = { parse: 0, url: urls, header: { 'User-Agent': 'Mozilla/5.0' }, pic: picUrl, cover: picUrl, height: 720 };
|
||||
let lrc = await lrcPromise;
|
||||
if (lrc && lrc !== '暂无歌词') result.lrc = lrc;
|
||||
return JSON.stringify(result);
|
||||
}
|
||||
|
||||
async function search(wd, quick, pg) {
|
||||
if (pg <= 0 || typeof pg == 'undefined') pg = 1;
|
||||
let searchUrl = `https://search.kuwo.cn/r.s?client=kt&all=${encodeURIComponent(wd)}&pn=${(pg - 1) * 30}&rn=30&vipver=1&ft=music&encoding=utf8&rformat=json&mobi=1`;
|
||||
let html = '';
|
||||
while (!html) {
|
||||
html = await request(searchUrl);
|
||||
if (!html) await sleep(0.1);
|
||||
}
|
||||
let json = safeJSONParse(html.replace(/'/g, '"'));
|
||||
let videos = [];
|
||||
if (json.abslist) {
|
||||
json.abslist.forEach(it => {
|
||||
let rid = it.DC_TARGETID || it.MUSICRID?.replace('MUSIC_', '') || '';
|
||||
let pic = it.web_albumpic_short ? `http://img1.kuwo.cn/star/albumcover/${it.web_albumpic_short}` : (it.hts_MVPIC || '');
|
||||
videos.push({ vod_id: `${it.SONGNAME} - ${it.ARTIST}$${rid}$${pic}`, vod_name: `${it.SONGNAME} - ${it.ARTIST}`, vod_pic: hd(pic), vod_remarks: it.ALBUM || '酷我音乐' });
|
||||
});
|
||||
}
|
||||
return JSON.stringify({ page: parseInt(pg), pagecount: 999, limit: 30, total: 999, list: videos });
|
||||
}
|
||||
|
||||
async function action(action, value) {
|
||||
if (action == '最新评论') {
|
||||
return JSON.stringify({ action: { type: 'comment', actionId: '最新评论', title: '最新评论', subtitle: '暂不支持评论功能', remarks: '评论功能开发中', list: [{ id: '功能提示', title: '暂不支持评论', subtitle: '', logo: '', content: '酷我音乐评论功能正在开发中...', remarks: '', remarks2: '' }] } });
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
export function __jsEvalReturn() {
|
||||
return { init, home, homeVod, category, detail, play, search, action };
|
||||
}
|
||||
+853
@@ -0,0 +1,853 @@
|
||||
import '../lib/htmlParser.js';
|
||||
import { Quark, Baidu, UC } from "../lib/pans.js";
|
||||
|
||||
// 分类排除关键词
|
||||
const class_exclude = ['115', '123', '天移', '留言', '关于', '原盘'];
|
||||
|
||||
/**
|
||||
* 网盘站点域名配置
|
||||
* 每个站点对应一组可用域名,用于并发检测
|
||||
*/
|
||||
const DOM_CFG = {
|
||||
"玩偶": [
|
||||
"https://wogg.xxooo.cf",
|
||||
"https://woggpan.333232.xyz",
|
||||
"https://wogg.heshiheng.top",
|
||||
"https://www.wogg.one",
|
||||
"https://www.wogg.lol"
|
||||
],
|
||||
"至臻": [
|
||||
"https://mihdr.top",
|
||||
"https://xiaomi666.fun",
|
||||
"https://zhizhen8.click",
|
||||
"https://www.zhizhen8.click"
|
||||
],
|
||||
"蜡笔": [
|
||||
"http://xiaocge.fun",
|
||||
"http://feimo.fun",
|
||||
"https://feimao666.fun",
|
||||
"http://feimao888.fun",
|
||||
"http://www.labi88.sbs",
|
||||
"http://fmao.site",
|
||||
"http://fmao.shop",
|
||||
"http://xiaocgege.shop"
|
||||
],
|
||||
"木偶": [
|
||||
"http://123.666291.xyz",
|
||||
"https://mogg.5568.eu.org",
|
||||
"https://mo.666291.xyz",
|
||||
"http://666.666291.xyz",
|
||||
"https://mo.muouso.fun"
|
||||
],
|
||||
"二小": [
|
||||
"https://www.2xiaozhan.top",
|
||||
"https://erxiaofn.click",
|
||||
"https://www.xhww.net"
|
||||
],
|
||||
"多多": [
|
||||
"https://tv.yydsys.top",
|
||||
"https://tv.yydsys.cc"
|
||||
],
|
||||
"虎斑": [
|
||||
"http://103.45.162.207:20720",
|
||||
"http://xsayang.fun:12512"
|
||||
],
|
||||
"欧歌": [
|
||||
"https://woog.xn--dkw.xn--6qq986b3xl",
|
||||
"https://woog.nxog.eu.org"
|
||||
],
|
||||
"闪电": [
|
||||
"https://sd.sduc.site"
|
||||
],
|
||||
"快映": [
|
||||
"http://xsayang.fun:12512"
|
||||
]
|
||||
};
|
||||
|
||||
// 全局变量
|
||||
let host = ''; // 当前可用域名
|
||||
let ext = ''; // 扩展配置
|
||||
let apitype = ''; // API类型: vodshow 或 index.php
|
||||
let siteName = '网盘'; // 站点名称
|
||||
let UA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (Chrome/120.0.0.0 Safari/537.36";
|
||||
let line_order = ['百度', '夸克', '优汐']; // 线路排序
|
||||
let downThreads = '32'; // 下载线程数
|
||||
|
||||
let cachedClasses = [];
|
||||
let cachedFilters = {};
|
||||
|
||||
// 站点配置存储对象
|
||||
const siteConfigs = {};
|
||||
|
||||
// ==================== 开关配置 (1=开启,0=关闭)====================
|
||||
let quarkInfinite = 0; // 夸克网盘无限画质开关 (1=开启无限画质, 0=关闭)
|
||||
let quarkOrig = 1; // 夸克网盘原画开关 (1=开启原画, 0=关闭)
|
||||
let quarkTransfer = 1; // 夸克网盘转存开关 (1=开启转存获取画质, 0=关闭)
|
||||
let enableProxy = 0; // 全局代理开关 (1=开启代理, 0=关闭)
|
||||
// ================================================
|
||||
|
||||
// 代理配置
|
||||
let enable_image = 1; // 图片代理开关
|
||||
let proxyimg = 'https://wsrv.nl/?url='; // 图片代理
|
||||
let proxyurl = 'http://127.0.0.1:2525/proxy?url='; // 播放代理
|
||||
|
||||
const CACHE_NS = 'wanpan_cache';
|
||||
const CLASS_CACHE_KEY = 'class_data';
|
||||
|
||||
// 请求头配置
|
||||
const headers = {
|
||||
'User-Agent': UA,
|
||||
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
|
||||
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8'
|
||||
};
|
||||
|
||||
/**
|
||||
* 安全JSON解析
|
||||
*/
|
||||
function safeJSONParse(str, defaultValue = {}) {
|
||||
if (!str || typeof str === 'object') return str || defaultValue;
|
||||
try {
|
||||
return JSON.parse(str);
|
||||
} catch {
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 统一请求函数
|
||||
* @param {string} url - 请求地址
|
||||
* @param {object} options - 请求选项
|
||||
* @returns {Promise<string|null>} 响应内容
|
||||
*/
|
||||
async function request(url, options = {}) {
|
||||
const reqHeaders = { ...headers, ...options.headers };
|
||||
let postType = reqHeaders['Content-Type']?.includes('json') ? 'json' :
|
||||
reqHeaders['Content-Type']?.includes('form') ? 'form' : '';
|
||||
|
||||
try {
|
||||
const response = await req(url, {
|
||||
method: options.method || 'GET',
|
||||
headers: reqHeaders,
|
||||
data: options.data,
|
||||
postType: postType,
|
||||
timeout: options.timeout || 15000
|
||||
});
|
||||
return response?.content || response?.data || response;
|
||||
} catch (error) {
|
||||
console.error(`Request failed: ${url}`, error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 映射分辨率名称
|
||||
* @param {string} res - 原始分辨率标识
|
||||
* @returns {string} 中文分辨率名称
|
||||
*/
|
||||
function mapResolution(res) {
|
||||
const map = {
|
||||
'low': '流畅', 'normal': '标清', 'high': '高清', 'super': '超清',
|
||||
'4k': '4K', '2k': '2K', 'hdr': 'HDR', 'dolby_vision': 'HDR',
|
||||
'M3U8_AUTO_480': '480P', 'M3U8_AUTO_720': '720P', 'M3U8_AUTO_1080': '1080P',
|
||||
'M3U8_AUTO_2K': '2K', 'M3U8_AUTO_4K': '4K'
|
||||
};
|
||||
return map[res] || res;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断分类是否被排除
|
||||
* @param {string} className - 分类名称
|
||||
* @returns {boolean} 是否被排除
|
||||
*/
|
||||
function isClassExcluded(className) {
|
||||
if (!className) return false;
|
||||
return class_exclude.some(keyword => className.toLowerCase().includes(keyword));
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化函数
|
||||
* 加载配置、检测可用域名、获取分类和筛选器
|
||||
*/
|
||||
async function init(cfg) {
|
||||
ext = cfg.ext || cfg;
|
||||
|
||||
// 获取站点名称
|
||||
siteName = (cfg.skey?.split('_')[1] || cfg.skey) || (cfg.key?.split('_')[1] || cfg.key) || '网盘';
|
||||
|
||||
let quarkCookie = "";
|
||||
let baiduCookie = "";
|
||||
let ucCookie = "";
|
||||
let ucToken = "";
|
||||
let selectedConfig = "至臻";
|
||||
let siteOrder = "";
|
||||
let customLineOrder = null;
|
||||
let customThreads = null;
|
||||
let customQuarkOriginal = null;
|
||||
let customQuarkInfinite = null;
|
||||
let customQuarkTransfer = null;
|
||||
let customEnableProxy = null;
|
||||
let customEnableImage = null;
|
||||
let customProxyUrl = null;
|
||||
let customProxyImg = null;
|
||||
|
||||
// 解析配置参数
|
||||
if (cfg) {
|
||||
if (typeof ext === 'object') {
|
||||
quarkCookie = cfg.quark_cookie || "";
|
||||
baiduCookie = cfg.baidu_cookie || "";
|
||||
ucCookie = cfg.uc_cookie || "";
|
||||
ucToken = cfg.uc_token || "";
|
||||
customLineOrder = cfg.line_order || null;
|
||||
customThreads = cfg.threads || null;
|
||||
customQuarkOriginal = cfg.quark_original !== undefined ? cfg.quark_original : null;
|
||||
customQuarkInfinite = cfg.quark_infinite !== undefined ? cfg.quark_infinite : null;
|
||||
customQuarkTransfer = cfg.quark_transfer !== undefined ? cfg.quark_transfer : null;
|
||||
customEnableProxy = cfg.enable_proxy !== undefined ? cfg.enable_proxy : null;
|
||||
customEnableImage = cfg.enable_image !== undefined ? cfg.enable_image : null;
|
||||
customProxyUrl = cfg.proxy_url || null;
|
||||
customProxyImg = cfg.proxy_img || null;
|
||||
} else if (typeof ext === 'string') {
|
||||
const [url, order] = ext.split('$');
|
||||
|
||||
const html = await request(url);
|
||||
const json = safeJSONParse(html);
|
||||
|
||||
quarkCookie = json.quark_cookie || "";
|
||||
baiduCookie = json.baidu_cookie || "";
|
||||
ucCookie = json.uc_cookie || "";
|
||||
ucToken = json.uc_token || "";
|
||||
siteOrder = order?.trim() || "";
|
||||
customLineOrder = json.line_order || null;
|
||||
customThreads = json.threads || null;
|
||||
customQuarkOriginal = json.quark_original !== undefined ? json.quark_original : null;
|
||||
customQuarkInfinite = json.quark_infinite !== undefined ? json.quark_infinite : null;
|
||||
customQuarkTransfer = json.quark_transfer !== undefined ? json.quark_transfer : null;
|
||||
customEnableProxy = json.enable_proxy !== undefined ? json.enable_proxy : null;
|
||||
customEnableImage = json.enable_image !== undefined ? json.enable_image : null;
|
||||
customProxyUrl = json.proxy_url || null;
|
||||
customProxyImg = json.proxy_img || null;
|
||||
}
|
||||
}
|
||||
|
||||
// 应用配置
|
||||
line_order = customLineOrder?.length ? customLineOrder : line_order;
|
||||
downThreads = customThreads || downThreads;
|
||||
quarkOrig = customQuarkOriginal !== null ? customQuarkOriginal : quarkOrig;
|
||||
quarkInfinite = customQuarkInfinite !== null ? customQuarkInfinite : quarkInfinite;
|
||||
quarkTransfer = customQuarkTransfer !== null ? customQuarkTransfer : quarkTransfer;
|
||||
enableProxy = customEnableProxy !== null ? customEnableProxy : enableProxy;
|
||||
enable_image = customEnableImage !== null ? customEnableImage : enable_image;
|
||||
proxyurl = customProxyUrl || proxyurl;
|
||||
proxyimg = customProxyImg || proxyimg;
|
||||
|
||||
// 设置网盘Cookie
|
||||
if (quarkCookie?.length > 10) Quark.cookie = quarkCookie;
|
||||
if (baiduCookie?.length > 10) Baidu.cookie = baiduCookie;
|
||||
if (ucCookie?.length > 10) UC.cookie = ucCookie;
|
||||
if (ucToken?.length > 10) UC.token = ucToken;
|
||||
|
||||
// 匹配站点配置
|
||||
if (siteOrder) {
|
||||
for (const [key] of Object.entries(DOM_CFG)) {
|
||||
if (siteOrder.includes(key)) {
|
||||
selectedConfig = key;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const domains = DOM_CFG[selectedConfig];
|
||||
const configId = selectedConfig;
|
||||
|
||||
// 初始化主机 - 使用local缓存
|
||||
try {
|
||||
const cached = await local.get(CACHE_NS, 'domain_' + configId);
|
||||
if (cached && domains.includes(cached)) {
|
||||
host = cached;
|
||||
headers.Referer = host + "/";
|
||||
}
|
||||
} catch {}
|
||||
|
||||
if (!host) {
|
||||
try {
|
||||
const results = await Promise.any(
|
||||
domains.map(async (domain) => {
|
||||
const content = await request(domain, { timeout: 8000 });
|
||||
if (content && content.includes('href')) {
|
||||
return domain;
|
||||
}
|
||||
throw new Error('无效域名');
|
||||
})
|
||||
);
|
||||
|
||||
if (results) {
|
||||
host = results;
|
||||
headers.Referer = host + "/";
|
||||
await local.set(CACHE_NS, 'domain_' + configId, host).catch(() => {});
|
||||
}
|
||||
} catch (error) {
|
||||
host = domains[0];
|
||||
}
|
||||
}
|
||||
|
||||
apitype = await getApiType();
|
||||
|
||||
if (host) {
|
||||
// 尝试从缓存读取分类和筛选器数据
|
||||
const cacheKey = `${CLASS_CACHE_KEY}_${configId}`;
|
||||
let needUpdate = true;
|
||||
|
||||
try {
|
||||
const cached = await local.get(CACHE_NS, cacheKey);
|
||||
if (cached) {
|
||||
const parsed = safeJSONParse(cached);
|
||||
const cacheTime = parsed.timestamp || 0;
|
||||
const now = Date.now();
|
||||
|
||||
// 缓存7天内有效
|
||||
if (now - cacheTime < 7 * 24 * 60 * 60 * 1000) {
|
||||
cachedClasses = parsed.classes || [];
|
||||
cachedFilters = parsed.filters || {};
|
||||
|
||||
// 保存到siteConfigs
|
||||
siteConfigs[siteName] = {
|
||||
classes: cachedClasses,
|
||||
filters: cachedFilters
|
||||
};
|
||||
|
||||
needUpdate = false;
|
||||
}
|
||||
}
|
||||
} catch {}
|
||||
|
||||
if (needUpdate && host) {
|
||||
try {
|
||||
// 请求首页获取分类
|
||||
const html = await request(host);
|
||||
const classes = [];
|
||||
const seenTypeIds = new Set();
|
||||
|
||||
const navItems = pdfa(html, '.nav-menu-items&&li');
|
||||
|
||||
navItems.forEach((item) => {
|
||||
const href = pd(item, 'a&&href', host).trim();
|
||||
const typeName = pdfh(item, 'a&&Text').trim();
|
||||
const match = href.match(/\/([^\/]+)\.html$/);
|
||||
|
||||
// 检查是否在排除列表中
|
||||
if (match && typeName && !seenTypeIds.has(match[1]) && /^\d+$/.test(match[1]) && !isClassExcluded(typeName)) {
|
||||
classes.push({"type_name": typeName, "type_id": match[1]});
|
||||
seenTypeIds.add(match[1]);
|
||||
}
|
||||
});
|
||||
|
||||
cachedClasses = classes;
|
||||
|
||||
if (classes.length > 0) {
|
||||
// 使用 Promise.all 并发获取筛选器
|
||||
const filterPromises = classes.map(async (cls) => {
|
||||
const type_id = cls.type_id;
|
||||
const url = apitype === "vodshow" ?
|
||||
`${host}/vodshow/${type_id}-----------.html` :
|
||||
`${host}/index.php/vod/show/id/${type_id}.html`;
|
||||
|
||||
const html = await request(url);
|
||||
const filters = await getFilters(type_id, html);
|
||||
return { type_id, filters };
|
||||
});
|
||||
|
||||
const filterResults = await Promise.all(filterPromises);
|
||||
|
||||
const filters = {};
|
||||
filterResults.forEach(result => {
|
||||
filters[result.type_id] = result.filters;
|
||||
});
|
||||
|
||||
cachedFilters = filters;
|
||||
|
||||
// 保存到siteConfigs
|
||||
siteConfigs[siteName] = {
|
||||
classes: cachedClasses,
|
||||
filters: cachedFilters
|
||||
};
|
||||
|
||||
// 存储到缓存
|
||||
const cacheData = {
|
||||
timestamp: Date.now(),
|
||||
classes: cachedClasses,
|
||||
filters: cachedFilters
|
||||
};
|
||||
await local.set(CACHE_NS, cacheKey, JSON.stringify(cacheData)).catch(() => {});
|
||||
}
|
||||
} catch (error) {}
|
||||
}
|
||||
}
|
||||
|
||||
return JSON.stringify({});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取筛选条件
|
||||
* @param {string} type_id - 分类ID
|
||||
* @param {string} pageHtml - 页面HTML
|
||||
* @returns {Array} 筛选条件列表
|
||||
*/
|
||||
async function getFilters(type_id, pageHtml) {
|
||||
if (!pageHtml || pageHtml.length < 300) return [];
|
||||
const cats = [
|
||||
{ key: 'cateId', name: '类型', reg: /\/id\/(\d+)/ },
|
||||
{ key: 'class', name: '剧情' }, { key: 'lang', name: '语言' },
|
||||
{ key: 'area', name: '地区' }, { key: 'year', name: '时间' }, { key: 'letter', name: '字母' }
|
||||
];
|
||||
const sortOpts = { "时间": "time", "人气": "hits", "评分": "score" };
|
||||
let filters = [];
|
||||
|
||||
try {
|
||||
cats.forEach(cat => {
|
||||
const libraryBoxes = pdfa(pageHtml, '.library-box');
|
||||
const box = libraryBoxes.find(b => (pdfh(b, 'a&&Text') || '').includes(cat.name));
|
||||
if (!box) return;
|
||||
|
||||
const linkItems = pdfa(box, 'div a');
|
||||
let values = linkItems.map(a => {
|
||||
const n = pdfh(a, "a&&Text") || "全部";
|
||||
let v = n;
|
||||
if (cat.key === 'cateId') {
|
||||
const href = pd(a, 'a&&href', host);
|
||||
const m = href?.match(cat.reg);
|
||||
v = m?.[1] || n;
|
||||
}
|
||||
if (/全部|字母/.test(n)) return { n: "全部", v: "" };
|
||||
return { n, v };
|
||||
}).filter(x => x?.n)
|
||||
.filter((item, idx, self) => self.findIndex(i => i.n === item.n) === idx);
|
||||
|
||||
if (values.length > 3) filters.push({ key: cat.key, name: cat.name, value: values });
|
||||
});
|
||||
|
||||
const sortVals = Object.entries(sortOpts).map(([n, v]) => ({ n, v }));
|
||||
if (sortVals.length) filters.push({ key: "by", name: "排序", value: sortVals });
|
||||
} catch {}
|
||||
return filters;
|
||||
}
|
||||
|
||||
/**
|
||||
* 首页 - 返回分类和筛选器
|
||||
*/
|
||||
async function home(filter) {
|
||||
let info = getSite(siteName);
|
||||
|
||||
if (!info.classes || info.classes.length === 0) {
|
||||
info.classes = cachedClasses || [];
|
||||
}
|
||||
|
||||
return JSON.stringify({
|
||||
class: info.classes,
|
||||
filters: info.filters
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取视频列表
|
||||
* @param {string} html - 页面HTML
|
||||
* @returns {Array} 视频列表
|
||||
*/
|
||||
function getList(html) {
|
||||
const videos = [];
|
||||
const items = pdfa(html, ".module-items .module-item");
|
||||
|
||||
items.forEach((it) => {
|
||||
const name = pdfh(it, "a&&title");
|
||||
const pic = pd(it, "img&&data-src", host);
|
||||
const desc = pdfh(it, ".module-item-text&&Text");
|
||||
const url = pd(it, "a&&href", host);
|
||||
|
||||
if (name && url) {
|
||||
videos.push({
|
||||
"vod_id": url,
|
||||
"vod_name": name,
|
||||
"vod_pic": proxyImage(pic),
|
||||
"vod_remarks": desc || ""
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return videos;
|
||||
}
|
||||
|
||||
/**
|
||||
* 首页推荐
|
||||
*/
|
||||
async function homeVod() {
|
||||
const html = await request(host);
|
||||
return JSON.stringify({ list: getList(html) });
|
||||
}
|
||||
|
||||
/**
|
||||
* 分类页
|
||||
*/
|
||||
async function category(tid, pg, filter, extend) {
|
||||
const p = pg || 1;
|
||||
const fl = extend || {};
|
||||
let url = '';
|
||||
|
||||
if (apitype === "vodshow") {
|
||||
url = `${host}/vodshow/${tid}-${fl.area || ''}-${fl.by || 'time'}-${fl.class || ''}--${fl.letter || ''}---${p}---${fl.year || ''}.html`;
|
||||
} else {
|
||||
const parts = [
|
||||
fl.area ? `area/${fl.area}` : '',
|
||||
fl.by ? `by/${fl.by}` : '',
|
||||
fl.class ? `class/${fl.class}` : '',
|
||||
fl.cateId ? `id/${fl.cateId}` : `id/${tid}`,
|
||||
fl.lang ? `lang/${fl.lang}` : '',
|
||||
fl.letter ? `letter/${fl.letter}` : '',
|
||||
fl.year ? `year/${fl.year}` : ''
|
||||
].filter(Boolean);
|
||||
|
||||
url = `${host}/index.php/vod/show/${parts.join('/')}/page/${p}.html`;
|
||||
}
|
||||
|
||||
const html = await request(url);
|
||||
const videos = getList(html);
|
||||
return JSON.stringify({list: videos, page: p, pagecount: 999, limit: 20, total: 999});
|
||||
}
|
||||
|
||||
/**
|
||||
* 详情页 - 解析视频信息和播放线路
|
||||
*/
|
||||
async function detail(id) {
|
||||
let html = await request(id);
|
||||
|
||||
let vod_name = pdfh(html, '.video-info h1&&Text') || pdfh(html, 'h1&&Text') || '未知名称';
|
||||
let type_name = pdfh(html, '.tag-link&&Text') || '未知类型';
|
||||
let vod_pic = pd(html, '.lazyload&&data-src', host) || '';
|
||||
vod_pic = proxyImage(vod_pic);
|
||||
let vod_content = pdfh(html, '.sqjj_a--span&&Text') || pdfh(html, '.video-info-content&&Text') || '暂无简介';
|
||||
let vod_remarks = pdfh(html, '.video-info-items:eq(3)&&Text') || '未知';
|
||||
let vod_year = pdfh(html, '.tag-link:eq(2)&&Text') || '未知年份';
|
||||
let vod_area = pdfh(html, '.tag-link:eq(3)&&Text') || '未知地区';
|
||||
let vod_actor = pdfh(html, '.video-info-actor:eq(1)&&Text') || '未知演员';
|
||||
let vod_director = pdfh(html, '.video-info-actor:eq(0)&&Text') || '未知导演';
|
||||
|
||||
let playFrom = [], playUrl = [], playPic = [];
|
||||
let data = pdfa(html, '.module-row-title');
|
||||
|
||||
let panCounters = {'夸克': 1, '百度': 1, '优汐': 1};
|
||||
let allLines = [];
|
||||
|
||||
data.forEach((item) => {
|
||||
let text = pdfh(item, 'p&&Text');
|
||||
if (text) {
|
||||
let link = text.trim();
|
||||
if (/\.quark/.test(link)) allLines.push({ type: '夸克', link: link });
|
||||
else if (/\.baidu/.test(link)) allLines.push({ type: '百度', link: link });
|
||||
else if (/\.uc|drive\.uc\.cn/.test(link)) allLines.push({ type: '优汐', link: link });
|
||||
}
|
||||
});
|
||||
|
||||
for (let item of allLines) {
|
||||
if (item.type === '夸克') {
|
||||
let shareData = Quark.getShareData(item.link);
|
||||
let files = await Quark.getFilesByShareUrl(shareData);
|
||||
let lineName = '夸克#' + panCounters.夸克;
|
||||
if (files && files.length > 0) {
|
||||
let url = files.map(v => {
|
||||
let size = v.size ? ` 【${formatFileSize(v.size)}】` : '';
|
||||
return `${v.file_name}${size}$${[shareData.shareId, v.stoken, v.fid, v.share_fid_token, v.pdir_fid || '', v.subtitle?.fid || '', v.subtitle?.share_fid_token || ''].join('*')}`;
|
||||
}).join('#');
|
||||
let imgs = files.map(v => proxyImage(v.thumbnail || v.thumb || v.pic || '')).join('#');
|
||||
playFrom.push(lineName);
|
||||
playUrl.push(url);
|
||||
playPic.push(imgs);
|
||||
panCounters.夸克++;
|
||||
}
|
||||
}
|
||||
else if (item.type === '百度') {
|
||||
let shareData = Baidu.getShareData(item.link);
|
||||
if (shareData) {
|
||||
let files = await Baidu.getFilesByShareUrl(shareData);
|
||||
if (files && files.length > 0) {
|
||||
let lineName = '百度#' + panCounters.百度;
|
||||
let url = files.map(v => {
|
||||
let size = v.size ? ` 【${formatFileSize(v.size)}】` : '';
|
||||
let info = { surl: shareData.surl, pwd: shareData.pwd || '' };
|
||||
return `${v.name}${size}$${[v.path, v.uk, v.shareid, v.fsid, JSON.stringify(info)].join('*')}`;
|
||||
}).join('#');
|
||||
let imgs = files.map(v => proxyImage(v.thumbnail || v.thumb || v.pic || '')).join('#');
|
||||
playFrom.push(lineName);
|
||||
playUrl.push(url);
|
||||
playPic.push(imgs);
|
||||
panCounters.百度++;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (item.type === '优汐') {
|
||||
let shareData = UC.getShareData(item.link);
|
||||
if (shareData) {
|
||||
let files = await UC.getFilesByShareUrl(shareData);
|
||||
if (files && files.length > 0) {
|
||||
let lineName = '优汐#' + panCounters.优汐;
|
||||
let url = files.map(v => {
|
||||
let size = v.size ? ` 【${formatFileSize(v.size)}】` : '';
|
||||
return `${v.file_name}${size}$${[shareData.shareId, v.stoken, v.fid, v.share_fid_token, v.subtitle?.fid || '', v.subtitle?.share_fid_token || ''].join('*')}`;
|
||||
}).join('#');
|
||||
let imgs = files.map(v => proxyImage(v.thumbnail || v.thumb || v.pic || '')).join('#');
|
||||
playFrom.push(lineName);
|
||||
playUrl.push(url);
|
||||
playPic.push(imgs);
|
||||
panCounters.优汐++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 线路排序和过滤
|
||||
if (playFrom.length > 0) {
|
||||
let sortedLines = playFrom.map((name, index) => ({
|
||||
name,
|
||||
url: playUrl[index],
|
||||
pic: playPic[index],
|
||||
type: name.split('#')[0]
|
||||
})).sort((a, b) => {
|
||||
let aIndex = line_order.indexOf(a.type);
|
||||
let bIndex = line_order.indexOf(b.type);
|
||||
if (aIndex === -1) aIndex = Infinity;
|
||||
if (bIndex === -1) bIndex = Infinity;
|
||||
return aIndex - bIndex;
|
||||
});
|
||||
|
||||
let sorted = sortedLines.filter(line => !/(无名|失效)/.test(line.url));
|
||||
|
||||
playFrom = sorted.map(line => line.name);
|
||||
playUrl = sorted.map(line => line.url);
|
||||
playPic = sorted.map(line => line.pic);
|
||||
}
|
||||
|
||||
let vod = {
|
||||
'vod_id': id,
|
||||
'vod_name': vod_name,
|
||||
'vod_pic': vod_pic,
|
||||
'vod_content': vod_content,
|
||||
'vod_remarks': vod_remarks,
|
||||
'vod_year': vod_year,
|
||||
'vod_area': vod_area,
|
||||
'vod_actor': vod_actor,
|
||||
'vod_director': vod_director,
|
||||
'type_name': type_name,
|
||||
'vod_play_from': playFrom.join('$$$'),
|
||||
'vod_play_url': playUrl.join('$$$'),
|
||||
'vod_play_pic': playPic.join('$$$'),
|
||||
'vod_play_pic_ratio': 1.0
|
||||
};
|
||||
|
||||
return JSON.stringify({ list: [vod] });
|
||||
}
|
||||
|
||||
/**
|
||||
* 搜索
|
||||
*/
|
||||
async function search(wd, quick, pg) {
|
||||
const p = pg || 1;
|
||||
let url = apitype === "vodshow"
|
||||
? `${host}/vodsearch/${wd}----------${p}---.html`
|
||||
: `${host}/index.php/vod/search/page/${p}/wd/${encodeURIComponent(wd)}.html`;
|
||||
|
||||
const html = await request(url);
|
||||
if (!html) return JSON.stringify({ list: [], page: p, pagecount: 0, limit: 20, total: 0 });
|
||||
|
||||
const data = pdfa(html, '.module-items .module-search-item');
|
||||
const videos = [];
|
||||
|
||||
data.forEach((it) => {
|
||||
const name = pdfh(it, '.video-info&&a&&title');
|
||||
if (!name) return;
|
||||
let pic = pd(it, 'img&&data-src', host) || '';
|
||||
pic = proxyImage(pic);
|
||||
const desc = pdfh(it, '.module-item-text') || "";
|
||||
let url = pd(it, '.video-info&&a&&href', host) || '';
|
||||
videos.push({ "vod_id": url, "vod_name": name, "vod_pic": pic, "vod_remarks": desc });
|
||||
});
|
||||
|
||||
const filteredResults = videos.filter(item => (item.vod_name || '').toLowerCase().includes(wd.toLowerCase()));
|
||||
|
||||
return JSON.stringify({ list: filteredResults, page: p, pagecount: 10, limit: 20, total: 100 });
|
||||
}
|
||||
|
||||
/**
|
||||
* 播放 - 获取视频播放链接
|
||||
*/
|
||||
async function play(flag, id, flags) {
|
||||
let ids = id.split('*');
|
||||
let urls = [];
|
||||
|
||||
const addUrl = (name, u) => {
|
||||
if (u) {
|
||||
let finalUrl = u;
|
||||
if (enableProxy == 1 && proxyurl) {
|
||||
finalUrl = proxyurl + encodeURIComponent(u);
|
||||
}
|
||||
urls.push(name, finalUrl + `&threads=${downThreads}`);
|
||||
}
|
||||
};
|
||||
|
||||
// 夸克网盘
|
||||
if (flag.startsWith('夸克') && ids.length >= 4) {
|
||||
let [shareId, stoken, fid, share_fid_token] = ids;
|
||||
let header = {
|
||||
'User-Agent': UA,
|
||||
'origin': 'https://pan.quark.cn',
|
||||
'referer': 'https://pan.quark.cn/',
|
||||
'Cookie': Quark.cookie
|
||||
};
|
||||
|
||||
// 无限画质(免转存直链)
|
||||
if (quarkInfinite == 1) {
|
||||
const tokenUrls = await Quark.getUrl(shareId, stoken, fid, share_fid_token);
|
||||
tokenUrls?.forEach(item => { if (item?.url) addUrl("无限" + (item.name || ''), item.url); });
|
||||
}
|
||||
|
||||
// 转存逻辑(原画和多分辨率)
|
||||
let shouldTransfer = quarkTransfer;
|
||||
if (quarkInfinite != 1 && quarkOrig != 1 && quarkTransfer != 1) {
|
||||
shouldTransfer = 1;
|
||||
}
|
||||
|
||||
if (shouldTransfer == 1) {
|
||||
// 原画
|
||||
if (quarkOrig == 1) {
|
||||
let down = await Quark.getDownload(shareId, stoken, fid, share_fid_token, true);
|
||||
if (down && down.error) {
|
||||
const errorMsg = down.message || '未知错误';
|
||||
return JSON.stringify({ parse: 0, msg: `夸克: ${errorMsg}`, header: header });
|
||||
}
|
||||
if (down?.download_url) addUrl("原画", down.download_url);
|
||||
}
|
||||
|
||||
// 转码画质
|
||||
let transcoding = await Quark.getLiveTranscoding(shareId, stoken, fid, share_fid_token);
|
||||
if (transcoding?.length) {
|
||||
transcoding.filter(t => t.accessable).forEach(t => {
|
||||
if (t?.video_info?.url) {
|
||||
addUrl(mapResolution(t.resolution), t.video_info.url);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (urls.length) {
|
||||
return JSON.stringify({ parse: 0, url: urls, header: header });
|
||||
}
|
||||
}
|
||||
|
||||
// 百度网盘
|
||||
if (flag.startsWith('百度') && ids.length >= 5) {
|
||||
let [path, uk, shareid, fsid, shareDataStr] = ids;
|
||||
let shareData = safeJSONParse(shareDataStr);
|
||||
|
||||
let original = await Baidu.getAppShareUrl(path, uk, shareid, fsid, shareData);
|
||||
if (original && !original.error) {
|
||||
addUrl("原画", original);
|
||||
} else {
|
||||
const errorMsg = original?.message || '获取链接失败,请检查Cookie是否有效';
|
||||
return JSON.stringify({ parse: 0, msg: `百度: ${errorMsg}`, header: headers });
|
||||
}
|
||||
|
||||
if (urls.length) {
|
||||
return JSON.stringify({
|
||||
parse: 0,
|
||||
url: urls,
|
||||
header: { "User-Agent": 'netdisk;P2SP;2.2.91.136;android-android;' }
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 优汐网盘
|
||||
if (flag.startsWith('优汐') && ids.length >= 4) {
|
||||
let [shareId, stoken, fid, share_fid_token] = ids;
|
||||
|
||||
let down = await UC.getDownload(shareId, stoken, fid, share_fid_token, true);
|
||||
if (down && down.error) {
|
||||
const errorMsg = down.message || '未知错误';
|
||||
return JSON.stringify({ parse: 0, msg: `优汐: ${errorMsg}`, header: headers });
|
||||
}
|
||||
if (down?.length) {
|
||||
down.forEach(item => { if (item?.url) addUrl(mapResolution(item.name), item.url); });
|
||||
}
|
||||
|
||||
if (urls.length) {
|
||||
return JSON.stringify({ parse: 0, url: urls });
|
||||
}
|
||||
}
|
||||
|
||||
return JSON.stringify({ parse: 0, msg: `解析分享链接失败, 分享链接可能已失效`, header: headers });
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取API类型
|
||||
*/
|
||||
async function getApiType() {
|
||||
if (!host) return "index.php";
|
||||
try {
|
||||
const content = await request(host, { timeout: 3000 });
|
||||
return content?.includes('vodshow') ? "vodshow" : "index.php";
|
||||
} catch {
|
||||
return "index.php";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化文件大小
|
||||
*/
|
||||
function formatFileSize(bytes) {
|
||||
if (!bytes || bytes === 0) return '0 B';
|
||||
const units = ['B', 'KB', 'MB', 'GB', 'TB'];
|
||||
let i = 0;
|
||||
while (bytes >= 1024 && i < units.length - 1) {
|
||||
bytes /= 1024;
|
||||
i++;
|
||||
}
|
||||
return bytes.toFixed(2) + ' ' + units[i];
|
||||
}
|
||||
|
||||
/**
|
||||
* 图片代理
|
||||
*/
|
||||
function proxyImage(url) {
|
||||
if (!url) return '';
|
||||
url = url.replace(/(https?:\/\/[^/]+\/)+/g, '$1');
|
||||
if (enable_image != 1) return url;
|
||||
if (/zy|xinlangtupian/.test(url)) {
|
||||
return proxyimg + encodeURIComponent(url);
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取站点配置
|
||||
*/
|
||||
function getSite(name) {
|
||||
const config = siteConfigs[name];
|
||||
if (config) {
|
||||
return {
|
||||
classes: config.classes || [],
|
||||
filters: config.filters || {}
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
classes: cachedClasses || [],
|
||||
filters: cachedFilters || {}
|
||||
};
|
||||
}
|
||||
|
||||
export function __jsEvalReturn() {
|
||||
return {
|
||||
init: init,
|
||||
home: home,
|
||||
homeVod: homeVod,
|
||||
category: category,
|
||||
detail: detail,
|
||||
play: play,
|
||||
search: search,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user