上传文件至「/」
This commit is contained in:
@@ -0,0 +1,481 @@
|
||||
// ============================================
|
||||
// 茶杯狐 TVBox JS版 - 完整可用
|
||||
// 完全复制Python版请求行为
|
||||
// ============================================
|
||||
|
||||
let siteUrl = 'http://www.bnjxjd.com';
|
||||
let headers = {
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
|
||||
'Referer': 'http://www.bnjxjd.com'
|
||||
};
|
||||
|
||||
// ============ 工具函数 ============
|
||||
|
||||
// 完全复制Python版fetch行为
|
||||
async function fetchUrl(url) {
|
||||
try {
|
||||
let res = await req(url, {
|
||||
method: 'get',
|
||||
headers: headers,
|
||||
timeout: 15000
|
||||
});
|
||||
|
||||
let content = res.content || '';
|
||||
|
||||
// 如果返回的是Buffer/ArrayBuffer,转成UTF-8字符串
|
||||
if (typeof content === 'object' && !(content instanceof String)) {
|
||||
try {
|
||||
content = new TextDecoder('utf-8').decode(content);
|
||||
} catch (e) {
|
||||
content = String(content);
|
||||
}
|
||||
}
|
||||
|
||||
// 处理可能的乱码
|
||||
if (typeof content === 'string') {
|
||||
// 移除BOM头
|
||||
if (content.charCodeAt(0) === 0xFEFF) {
|
||||
content = content.slice(1);
|
||||
}
|
||||
}
|
||||
|
||||
return content;
|
||||
} catch (e) {
|
||||
console.log('请求失败:', url, e.message);
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
function cleanText(text) {
|
||||
if (!text) return '';
|
||||
return text.replace(/ /g, ' ')
|
||||
.replace(/\n/g, ' ')
|
||||
.replace(/\r/g, ' ')
|
||||
.replace(/\s+/g, ' ')
|
||||
.trim();
|
||||
}
|
||||
|
||||
// ============ 核心函数 ============
|
||||
|
||||
async function init(cfg) {
|
||||
// 支持外部传入地址
|
||||
if (cfg && cfg.ext && typeof cfg.ext === 'string' && cfg.ext.startsWith('http')) {
|
||||
siteUrl = cfg.ext;
|
||||
}
|
||||
if (siteUrl.endsWith('/')) {
|
||||
siteUrl = siteUrl.slice(0, -1);
|
||||
}
|
||||
headers.Referer = siteUrl;
|
||||
}
|
||||
|
||||
async function home(filter) {
|
||||
let classes = [
|
||||
{ type_id: '1', type_name: '电影' },
|
||||
{ type_id: '2', type_name: '电视剧' },
|
||||
{ type_id: '3', type_name: '综艺' },
|
||||
{ type_id: '4', type_name: '动漫' }
|
||||
];
|
||||
|
||||
let filters = {
|
||||
'1': [
|
||||
{ key: 'class', name: '类型', value: [
|
||||
{n:'全部',v:'1'},{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:'31'}
|
||||
]},
|
||||
{ key: 'area', name: '地区', value: [
|
||||
{n:'全部',v:'0'},{n:'大陆',v:'大陆'},{n:'香港',v:'香港'},{n:'台湾',v:'台湾'},
|
||||
{n:'美国',v:'美国'},{n:'日本',v:'日本'},{n:'韩国',v:'韩国'},{n:'英国',v:'英国'},
|
||||
{n:'法国',v:'法国'},{n:'德国',v:'德国'},{n:'泰国',v:'泰国'},{n:'印度',v:'印度'},
|
||||
{n:'加拿大',v:'加拿大'},{n:'其他',v:'其他'}
|
||||
]},
|
||||
{ key: 'year', name: '年份', value: [
|
||||
{n:'全部',v:'0'},{n:'2026',v:'2026'},{n:'2025',v:'2025'},{n:'2024',v:'2024'},
|
||||
{n:'2023',v:'2023'},{n:'2022',v:'2022'},{n:'2021',v:'2021'},{n:'2020',v:'2020'},
|
||||
{n:'2019',v:'2019'},{n:'2018',v:'2018'},{n:'2017',v:'2017'},{n:'2016',v:'2016'},
|
||||
{n:'2015',v:'2015'},{n:'2014',v:'2014'},{n:'更早',v:'2009'}
|
||||
]},
|
||||
{ key: 'sort', name: '排序', value: [
|
||||
{n:'时间',v:'time'},{n:'人气',v:'hits'},{n:'评分',v:'score'}
|
||||
]}
|
||||
],
|
||||
'2': [
|
||||
{ key: 'class', name: '类型', value: [
|
||||
{n:'全部',v:'2'},{n:'国产剧',v:'17'},{n:'欧美剧',v:'21'},{n:'日韩剧',v:'20'},
|
||||
{n:'港台剧',v:'18'},{n:'海外剧',v:'22'}
|
||||
]},
|
||||
{ key: 'area', name: '地区', value: [
|
||||
{n:'全部',v:'0'},{n:'大陆',v:'大陆'},{n:'香港',v:'香港'},{n:'台湾',v:'台湾'},
|
||||
{n:'美国',v:'美国'},{n:'日本',v:'日本'},{n:'韩国',v:'韩国'},{n:'英国',v:'英国'}
|
||||
]},
|
||||
{ key: 'year', name: '年份', value: [
|
||||
{n:'全部',v:'0'},{n:'2026',v:'2026'},{n:'2025',v:'2025'},{n:'2024',v:'2024'},
|
||||
{n:'2023',v:'2023'},{n:'2022',v:'2022'},{n:'2021',v:'2021'},{n:'2020',v:'2020'},
|
||||
{n:'2019',v:'2019'},{n:'2018',v:'2018'},{n:'2017',v:'2017'},{n:'2016',v:'2016'},
|
||||
{n:'2015',v:'2015'},{n:'2014',v:'2014'},{n:'更早',v:'2009'}
|
||||
]},
|
||||
{ key: 'sort', name: '排序', value: [
|
||||
{n:'时间',v:'time'},{n:'人气',v:'hits'},{n:'评分',v:'score'}
|
||||
]}
|
||||
],
|
||||
'3': [
|
||||
{ key: 'year', name: '年份', value: [
|
||||
{n:'全部',v:'0'},{n:'2026',v:'2026'},{n:'2025',v:'2025'},{n:'2024',v:'2024'}
|
||||
]},
|
||||
{ key: 'sort', name: '排序', value: [
|
||||
{n:'时间',v:'time'},{n:'人气',v:'hits'}
|
||||
]}
|
||||
],
|
||||
'4': [
|
||||
{ key: 'area', name: '地区', value: [
|
||||
{n:'全部',v:'0'},{n:'大陆',v:'大陆'},{n:'日本',v:'日本'},{n:'美国',v:'美国'}
|
||||
]},
|
||||
{ key: 'year', name: '年份', value: [
|
||||
{n:'全部',v:'0'},{n:'2026',v:'2026'},{n:'2025',v:'2025'},{n:'2024',v:'2024'}
|
||||
]},
|
||||
{ key: 'sort', name: '排序', value: [
|
||||
{n:'时间',v:'time'},{n:'人气',v:'hits'}
|
||||
]}
|
||||
]
|
||||
};
|
||||
|
||||
return JSON.stringify({
|
||||
class: classes,
|
||||
filters: filters
|
||||
});
|
||||
}
|
||||
|
||||
async function homeVod() {
|
||||
let html = await fetchUrl(siteUrl);
|
||||
if (!html) return JSON.stringify({ list: [] });
|
||||
|
||||
let videos = parseVideoList(html);
|
||||
return JSON.stringify({ list: videos });
|
||||
}
|
||||
|
||||
async function category(tid, pg, filter, extend) {
|
||||
let page = parseInt(pg) || 1;
|
||||
extend = extend || {};
|
||||
|
||||
let classVal = extend['class'] || '';
|
||||
let areaVal = extend['area'] || '0';
|
||||
let yearVal = extend['year'] || '0';
|
||||
let sortVal = extend['sort'] || 'time';
|
||||
|
||||
let c = (classVal && classVal !== '1' && classVal !== '2') ? classVal : '';
|
||||
let a = areaVal !== '0' ? areaVal : '';
|
||||
let y = yearVal !== '0' ? yearVal : '';
|
||||
|
||||
let url;
|
||||
if (c || a || y) {
|
||||
url = `${siteUrl}/movie/fenlei${tid}--${c}--${a}--${y}---${page}---.html`;
|
||||
} else {
|
||||
url = `${siteUrl}/movie/fenlei${tid}---${page}.html`;
|
||||
}
|
||||
|
||||
let html = await fetchUrl(url);
|
||||
let videos = html ? parseVideoList(html) : [];
|
||||
|
||||
return JSON.stringify({
|
||||
list: videos,
|
||||
page: page,
|
||||
pagecount: 9999,
|
||||
limit: 90,
|
||||
total: 999999
|
||||
});
|
||||
}
|
||||
|
||||
async function detail(id) {
|
||||
let url = id.startsWith('http') ? id : siteUrl + (id.startsWith('/') ? '' : '/') + id;
|
||||
let html = await fetchUrl(url);
|
||||
if (!html) return JSON.stringify({ list: [] });
|
||||
|
||||
const $ = load(html);
|
||||
let info = { vod_id: id };
|
||||
|
||||
// 标题
|
||||
let title = $('h1.title').text().trim() || $('h1').text().trim();
|
||||
if (title) info.vod_name = cleanText(title);
|
||||
|
||||
// 简介
|
||||
let content = $('span.detail-content').html() || $('p.col-pd').html() || '';
|
||||
if (content) {
|
||||
info.vod_content = cleanText(content.replace(/<[^>]+>/g, ''));
|
||||
}
|
||||
|
||||
// 海报
|
||||
let pic = $('img.img-responsive').attr('src') || '';
|
||||
if (!pic) {
|
||||
$('img.lazy').each((i, n) => {
|
||||
let src = $(n).attr('src') || $(n).attr('data-original') || '';
|
||||
if (src) pic = src;
|
||||
});
|
||||
}
|
||||
if (pic && !pic.startsWith('http')) {
|
||||
pic = siteUrl + (pic.startsWith('/') ? '' : '/') + pic;
|
||||
}
|
||||
if (pic) info.vod_pic = pic;
|
||||
|
||||
// 用正则提取详情信息(和Python版一致)
|
||||
let actorMatch = html.match(/<span class="text-muted">主演:<\/span>([\s\S]*?)<\/p>/);
|
||||
if (actorMatch) info.vod_actor = cleanText(actorMatch[1].replace(/<[^>]+>/g, ''));
|
||||
|
||||
let directorMatch = html.match(/<span class="text-muted">导演:<\/span>([\s\S]*?)<\/p>/);
|
||||
if (directorMatch) info.vod_director = cleanText(directorMatch[1].replace(/<[^>]+>/g, ''));
|
||||
|
||||
let areaMatch = html.match(/<span class="text-muted">地区:<\/span>([\s\S]*?)<\/p>/);
|
||||
if (areaMatch) info.vod_area = cleanText(areaMatch[1].replace(/<[^>]+>/g, ''));
|
||||
|
||||
let yearMatch = html.match(/<span class="text-muted">年份:<\/span>([\s\S]*?)<\/p>/);
|
||||
if (yearMatch) info.vod_year = cleanText(yearMatch[1].replace(/<[^>]+>/g, ''));
|
||||
|
||||
let typeMatch = html.match(/<span class="text-muted">类型:<\/span>([\s\S]*?)<\/p>/);
|
||||
if (typeMatch) info.type_name = cleanText(typeMatch[1].replace(/<[^>]+>/g, ''));
|
||||
|
||||
// 播放列表(和Python版逻辑一致)
|
||||
let ktabs = [];
|
||||
let klists = [];
|
||||
|
||||
// 提取线路名称
|
||||
let tabs = [];
|
||||
let tabMatches = html.match(/<li[^>]*>.*?<a[^>]*href="#playlist\d+"[^>]*>([\s\S]*?)<\/a>/g) || [];
|
||||
tabMatches.forEach(tab => {
|
||||
let nameMatch = tab.match(/<a[^>]*>([\s\S]*?)<\/a>/);
|
||||
if (nameMatch) {
|
||||
let name = cleanText(nameMatch[1].replace(/<[^>]+>/g, ''));
|
||||
if (name && !['猜您喜欢','同类型','大家还在看','影片评论','播放地址'].includes(name)) {
|
||||
tabs.push(name);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 提取剧集
|
||||
let ulMatches = html.match(/<ul[^>]*class="stui-content__playlist[^"]*"[^>]*>([\s\S]*?)<\/ul>/g) || [];
|
||||
ulMatches.forEach((ulContent, idx) => {
|
||||
let liMatches = ulContent.match(/<li[^>]*>.*?<a[^>]*href="([^"]+)"[^>]*>([\s\S]*?)<\/a>/g) || [];
|
||||
let episodes = [];
|
||||
liMatches.forEach(li => {
|
||||
let hrefMatch = li.match(/href="([^"]+)"/);
|
||||
let nameMatch = li.match(/<a[^>]*>([\s\S]*?)<\/a>/);
|
||||
if (hrefMatch && nameMatch) {
|
||||
let epLink = hrefMatch[1];
|
||||
let epName = cleanText(nameMatch[1]);
|
||||
if (epName && epLink) {
|
||||
if (!epLink.startsWith('http')) {
|
||||
epLink = siteUrl + (epLink.startsWith('/') ? '' : '/') + epLink;
|
||||
}
|
||||
episodes.push(`${epName}$${epLink}`);
|
||||
}
|
||||
}
|
||||
});
|
||||
if (episodes.length > 0) {
|
||||
let sourceName = tabs[idx] || `播放源${idx+1}`;
|
||||
ktabs.push(sourceName);
|
||||
klists.push(episodes.join('#'));
|
||||
}
|
||||
});
|
||||
|
||||
// 备用:直接找所有播放链接
|
||||
if (ktabs.length === 0) {
|
||||
let allLinks = html.match(/<a[^>]*href="([^"]*)"[^>]*>([^<]+)<\/a>/g) || [];
|
||||
let episodes = [];
|
||||
allLinks.forEach(link => {
|
||||
let hrefMatch = link.match(/href="([^"]+)"/);
|
||||
let nameMatch = link.match(/<a[^>]*>([^<]+)<\/a>/);
|
||||
if (hrefMatch && nameMatch) {
|
||||
let epLink = hrefMatch[1];
|
||||
let epName = cleanText(nameMatch[1]);
|
||||
if (epLink && epName && epLink.includes('/play/')) {
|
||||
if (!['立即播放','播放','观看'].includes(epName)) {
|
||||
if (!epLink.startsWith('http')) {
|
||||
epLink = siteUrl + (epLink.startsWith('/') ? '' : '/') + epLink;
|
||||
}
|
||||
episodes.push(`${epName}$${epLink}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
if (episodes.length > 0) {
|
||||
ktabs.push('茶杯狐');
|
||||
klists.push(episodes.join('#'));
|
||||
}
|
||||
}
|
||||
|
||||
info.vod_play_from = ktabs.join('$$$') || '茶杯狐';
|
||||
info.vod_play_url = klists.join('$$$') || '';
|
||||
|
||||
return JSON.stringify({ list: [info] });
|
||||
}
|
||||
|
||||
async function play(flag, id, flags) {
|
||||
let url = id.startsWith('http') ? id : siteUrl + (id.startsWith('/') ? '' : '/') + id;
|
||||
let html = await fetchUrl(url);
|
||||
if (!html) {
|
||||
return JSON.stringify({ parse: 1, url: url });
|
||||
}
|
||||
|
||||
// 提取m3u8地址(和Python版一致)
|
||||
let realUrl = extractM3u8(html);
|
||||
|
||||
if (realUrl) {
|
||||
realUrl = realUrl.replace(/\\\//g, '/');
|
||||
if (realUrl.startsWith('//')) {
|
||||
realUrl = 'https:' + realUrl;
|
||||
}
|
||||
let parse = /\.(m3u8|mp4|mkv|flv|ts)$/i.test(realUrl) ? 0 : 1;
|
||||
return JSON.stringify({
|
||||
parse: parse,
|
||||
url: realUrl,
|
||||
header: {
|
||||
'User-Agent': headers['User-Agent'],
|
||||
'Referer': siteUrl
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 如果没找到,尝试iframe(和Python版一致)
|
||||
let iframeMatch = html.match(/<iframe[^>]*src=["']([^"']+)["']/i);
|
||||
if (iframeMatch) {
|
||||
let src = iframeMatch[1];
|
||||
if (src.startsWith('//')) src = 'https:' + src;
|
||||
else if (src.startsWith('/')) src = siteUrl + src;
|
||||
let iframeHtml = await fetchUrl(src);
|
||||
if (iframeHtml) {
|
||||
let iframeUrl = extractM3u8(iframeHtml);
|
||||
if (iframeUrl) {
|
||||
return JSON.stringify({
|
||||
parse: /\.(m3u8|mp4|mkv|flv|ts)$/i.test(iframeUrl) ? 0 : 1,
|
||||
url: iframeUrl,
|
||||
header: {
|
||||
'User-Agent': headers['User-Agent'],
|
||||
'Referer': siteUrl
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return JSON.stringify({
|
||||
parse: 1,
|
||||
url: url,
|
||||
header: {
|
||||
'User-Agent': headers['User-Agent'],
|
||||
'Referer': siteUrl
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function search(wd, quick, pg) {
|
||||
let page = parseInt(pg) || 1;
|
||||
let encodedKey = encodeURIComponent(wd);
|
||||
let url = `${siteUrl}/vodsearch/wd/${encodedKey}.html`;
|
||||
|
||||
let html = await fetchUrl(url);
|
||||
let videos = html ? parseSearchList(html) : [];
|
||||
|
||||
return JSON.stringify({
|
||||
list: videos,
|
||||
page: page,
|
||||
pagecount: 9999,
|
||||
limit: 90,
|
||||
total: 999999
|
||||
});
|
||||
}
|
||||
|
||||
// ============ 辅助解析函数 ============
|
||||
|
||||
function parseVideoList(html) {
|
||||
let videos = [];
|
||||
const $ = load(html);
|
||||
|
||||
$('.stui-vodlist__box, .stui-vodlist__item').each((i, n) => {
|
||||
let link = $(n).find('.stui-vodlist__thumb');
|
||||
if (!link.length) return;
|
||||
|
||||
let name = cleanText(link.attr('title') || '');
|
||||
let pic = link.attr('data-original') || link.attr('src') || '';
|
||||
let href = link.attr('href') || '';
|
||||
let remark = $(n).find('.pic-text.text-right').text().trim() || '';
|
||||
|
||||
if (pic && !pic.startsWith('http')) {
|
||||
pic = siteUrl + (pic.startsWith('/') ? '' : '/') + pic;
|
||||
}
|
||||
if (href && !href.startsWith('http')) {
|
||||
href = siteUrl + (href.startsWith('/') ? '' : '/') + href;
|
||||
}
|
||||
|
||||
if (name && href) {
|
||||
videos.push({
|
||||
vod_id: href,
|
||||
vod_name: name,
|
||||
vod_pic: pic,
|
||||
vod_remarks: cleanText(remark)
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return videos;
|
||||
}
|
||||
|
||||
function parseSearchList(html) {
|
||||
let videos = [];
|
||||
const $ = load(html);
|
||||
|
||||
$('.stui-vodlist__box, .stui-vodlist__media li, .stui-vodlist__item').each((i, n) => {
|
||||
let link = $(n).find('.stui-vodlist__thumb');
|
||||
if (!link.length) return;
|
||||
|
||||
let name = cleanText(link.attr('title') || '');
|
||||
let pic = link.attr('data-original') || link.attr('src') || '';
|
||||
let href = link.attr('href') || '';
|
||||
let remark = $(n).find('.pic-text.text-right, .pic-text').text().trim() || '';
|
||||
|
||||
if (pic && !pic.startsWith('http')) {
|
||||
pic = siteUrl + (pic.startsWith('/') ? '' : '/') + pic;
|
||||
}
|
||||
if (href && !href.startsWith('http')) {
|
||||
href = siteUrl + (href.startsWith('/') ? '' : '/') + href;
|
||||
}
|
||||
|
||||
if (name && href) {
|
||||
videos.push({
|
||||
vod_id: href,
|
||||
vod_name: name,
|
||||
vod_pic: pic,
|
||||
vod_remarks: cleanText(remark)
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return videos;
|
||||
}
|
||||
|
||||
function extractM3u8(html) {
|
||||
let patterns = [
|
||||
/"url"\s*:\s*"([^"]+\.m3u8[^"]*)"/,
|
||||
/url\s*:\s*"([^"]+\.m3u8[^"]*)"/,
|
||||
/"url"\s*:\s*'([^']+\.m3u8[^']*)'/,
|
||||
/https?:\/\/[^"']+\.m3u8[^"']*/
|
||||
];
|
||||
|
||||
for (let p of patterns) {
|
||||
let match = html.match(p);
|
||||
if (match) {
|
||||
return match[1] || match[0];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// ============ 导出 ============
|
||||
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