上传文件至「/」

This commit is contained in:
2026-07-30 02:22:33 +02:00
parent c72f8806e3
commit 83cf7ffe6b
+481
View File
@@ -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
};
}