From 1d5410ea35b5d701f41affa9f020cd92fee50e2f Mon Sep 17 00:00:00 2001 From: gyjune Date: Wed, 5 Aug 2026 11:47:47 +0800 Subject: [PATCH] e --- js/茶杯狐.js | 550 ++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 394 insertions(+), 156 deletions(-) diff --git a/js/茶杯狐.js b/js/茶杯狐.js index 7dbe334..0b30088 100644 --- a/js/茶杯狐.js +++ b/js/茶杯狐.js @@ -8,200 +8,438 @@ }) */ -const API_HOST = 'http://www.bnjxjd.com'; +import { load } from 'assets://js/lib/cat.js'; + +let host = 'http://www.bnjxjd.com'; +let cookie = ''; const 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', - 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', + 'User-Agent': 'Mozilla/5.0 (Linux; Android 9; ALN-AL00 Build/PQ3B.190801.05281406; wv) AppleWebKit/537.36', + 'accept-language': 'zh-CN,zh;q=0.9', 'Referer': 'http://www.bnjxjd.com/', }; -async function fetchHtml(url) { +async function request(url, options = {}) { try { - const res = await req(url, { headers: HEADERS }); + let hdrs = { ...HEADERS, ...options.headers }; + if (cookie) hdrs['Cookie'] = cookie; + let res = await req(url, { headers: hdrs, ...options }); + let setCookie = res.headers && res.headers['set-cookie']; + if (setCookie) { + cookie = Array.isArray(setCookie) ? setCookie.join('; ') : setCookie; + } return res.content || ''; } catch (e) { + console.log('[请求失败] ' + url + ' - ' + e.message); return ''; } } -function fixUrl(url) { - if (!url) return ''; - if (url.startsWith('//')) return 'https:' + url; - if (url.startsWith('http')) return url; - if (url.startsWith('/')) return API_HOST + url; - return API_HOST + '/' + url; +async function init(ext) { + if (typeof ext === 'string' && ext.trim().length > 0) host = ext.trim(); + console.log('[当前域名] ' + host); + await request(host); + console.log('[Cookie] ' + cookie); + return true; } -// 直接抓 a 标签 -function parseVideoList(html) { - const videos = []; - if (!html) return videos; +function home() { + const classes = [ + { type_id: '1', type_name: '电影' }, + { type_id: '2', type_name: '电视剧' }, + { type_id: '3', type_name: '综艺' }, + { type_id: '4', type_name: '动漫' } + ]; - // 匹配 stui-vodlist__thumb 的 a 标签 - const regex = /]*class="[^"]*stui-vodlist__thumb[^"]*"[^>]*href="([^"]+)"[^>]*title="([^"]*)"[^>]*data-original="([^"]+)"[^>]*>/g; - - let match; - while ((match = regex.exec(html)) !== null) { - videos.push({ - vod_id: fixUrl(match[1]), - vod_name: match[2].trim(), - vod_pic: fixUrl(match[3]), - vod_remarks: '更新中' - }); - } - - return videos; -} + const filters = { + '1': [ + { key: 'class', name: '类型', value: [ + { v: '', n: '全部' }, + { v: 'dongzuopian', n: '动作片' }, + { v: 'xijupian', n: '喜剧片' }, + { v: 'aiqingpian', n: '爱情片' }, + { v: 'kehuanpian', n: '科幻片' }, + { v: 'kongbupian', n: '恐怖片' }, + { v: 'juqingpian', n: '剧情片' }, + { v: 'zhanzhengpian', n: '战争片' }, + { v: 'jilupian', n: '记录片' }, + { v: 'donghuapian', n: '动画片' } + ]}, + { key: 'area', name: '地区', value: [ + { v: '', n: '全部' }, + { v: '大陆', n: '大陆' }, + { v: '香港', n: '香港' }, + { v: '台湾', n: '台湾' }, + { v: '美国', n: '美国' }, + { v: '日本', n: '日本' }, + { v: '韩国', n: '韩国' }, + { v: '泰国', n: '泰国' }, + { v: '印度', n: '印度' }, + { v: '加拿大', n: '加拿大' }, + { v: '其他', n: '其他' } + ]}, + { key: 'year', name: '年份', value: [ + { v: '', n: '全部' }, + { v: '2026', n: '2026' }, + { v: '2025', n: '2025' }, + { v: '2024', n: '2024' }, + { v: '2023', n: '2023' }, + { v: '2022', n: '2022' }, + { v: '2021', n: '2021' }, + { v: '2020', n: '2020' }, + { v: '2019', n: '2019' }, + { v: '2018', n: '2018' }, + { v: '2017', n: '2017' }, + { v: '2016', n: '2016' }, + { v: '2015', n: '2015' }, + { v: '2014', n: '2014' }, + { v: '2009', n: '更早' } + ]} + ], + '2': [ + { key: 'class', name: '类型', value: [ + { v: '', n: '全部' }, + { v: 'guochanju', n: '国产剧' }, + { v: 'oumeiju', n: '欧美剧' }, + { v: 'rihanju', n: '日韩剧' }, + { v: 'gangtaiju', n: '港台剧' }, + { v: 'haiwaiju', n: '海外剧' } + ]}, + { key: 'area', name: '地区', value: [ + { v: '', n: '全部' }, + { v: '大陆', n: '大陆' }, + { v: '香港', n: '香港' }, + { v: '台湾', n: '台湾' }, + { v: '美国', n: '美国' }, + { v: '日本', n: '日本' }, + { v: '韩国', n: '韩国' }, + { v: '英国', n: '英国' } + ]}, + { key: 'year', name: '年份', value: [ + { v: '', n: '全部' }, + { v: '2026', n: '2026' }, + { v: '2025', n: '2025' }, + { v: '2024', n: '2024' }, + { v: '2023', n: '2023' }, + { v: '2022', n: '2022' }, + { v: '2021', n: '2021' }, + { v: '2020', n: '2020' }, + { v: '2019', n: '2019' }, + { v: '2018', n: '2018' }, + { v: '2017', n: '2017' }, + { v: '2016', n: '2016' }, + { v: '2015', n: '2015' }, + { v: '2014', n: '2014' }, + { v: '2009', n: '更早' } + ]} + ], + '3': [ + { key: 'year', name: '年份', value: [ + { v: '', n: '全部' }, + { v: '2026', n: '2026' }, + { v: '2025', n: '2025' }, + { v: '2024', n: '2024' } + ]} + ], + '4': [ + { key: 'area', name: '地区', value: [ + { v: '', n: '全部' }, + { v: '大陆', n: '大陆' }, + { v: '日本', n: '日本' }, + { v: '美国', n: '美国' } + ]}, + { key: 'year', name: '年份', value: [ + { v: '', n: '全部' }, + { v: '2026', n: '2026' }, + { v: '2025', n: '2025' }, + { v: '2024', n: '2024' } + ]} + ] + }; -const CLASSES = [ - { type_id: '1', type_name: '电影' }, - { type_id: '2', type_name: '电视剧' }, - { type_id: '3', type_name: '综艺' }, - { type_id: '4', type_name: '动漫' } -]; - -function init(cfg) { return true; } - -function home(filter) { - return JSON.stringify({ class: CLASSES, filters: {} }); + return JSON.stringify({ class: classes, filters: filters }); } async function homeVod() { - try { - const html = await fetchHtml(API_HOST + '/'); - const videos = parseVideoList(html); - return JSON.stringify({ list: videos.slice(0, 12) }); - } catch (e) { - return JSON.stringify({ list: [] }); + if (!host || typeof host !== 'string') { + host = 'http://www.bnjxjd.com'; + await init(); } + let html = await request(host); + if (!html) return JSON.stringify({ list: [] }); + + const $ = load(html); + let list = []; + $('.stui-vodlist__box').each((i, el) => { + if (i >= 12) return false; + let a = $(el).find('a.stui-vodlist__thumb').first(); + let href = a.attr('href') || ''; + let title = a.attr('title') || ''; + let pic = a.attr('data-original') || a.attr('src') || ''; + let remarks = $(el).find('.pic-text').first().text().trim(); + if (title && href) { + list.push({ + vod_id: href.startsWith('http') ? href : (host + href), + vod_name: title, + vod_pic: pic.startsWith('http') ? pic : (host + pic), + vod_remarks: remarks || '更新中' + }); + } + }); + return JSON.stringify({ list }); } async function category(tid, pg, filter, extend) { - try { - const page = parseInt(pg) || 1; - const url = `${API_HOST}/movie/fenlei${tid}---${page}.html`; - const html = await fetchHtml(url); - const videos = parseVideoList(html); - return JSON.stringify({ - list: videos, - page: page, - pagecount: 9999, - limit: 90, - total: 999999 - }); - } catch (e) { - return JSON.stringify({ list: [] }); + if (!host || typeof host !== 'string') { + host = 'http://www.bnjxjd.com'; + await init(); } + let page = parseInt(pg) || 1; + let classVal = (extend && extend.class) || ''; + let areaVal = (extend && extend.area) || ''; + let yearVal = (extend && extend.year) || ''; + + let url; + if (classVal || areaVal || yearVal) { + let parts = [`id/${classVal || tid}`]; + if (areaVal) parts.push(`area/${encodeURIComponent(areaVal)}`); + if (yearVal) parts.push(`year/${yearVal}`); + url = host + `/vodshow/${parts.join('/')}/page/${page}.html`; + } else { + url = host + `/movie/fenlei${tid}---${page}.html`; + } + + console.log('[category] ' + url); + let html = await request(url); + if (!html) return JSON.stringify({ list: [], page, pagecount: 1 }); + + const $ = load(html); + let list = []; + $('.stui-vodlist__box').each((i, el) => { + let a = $(el).find('a.stui-vodlist__thumb').first(); + let href = a.attr('href') || ''; + let title = a.attr('title') || ''; + let pic = a.attr('data-original') || a.attr('src') || ''; + let remarks = $(el).find('.pic-text').first().text().trim(); + if (title && href) { + list.push({ + vod_id: href.startsWith('http') ? href : (host + href), + vod_name: title, + vod_pic: pic.startsWith('http') ? pic : (host + pic), + vod_remarks: remarks || '更新中' + }); + } + }); + + let pagecount = page; + let pageLinks = $('.stui-page__item a, .page a, .pagination a'); + if (pageLinks.length > 0) { + let maxPage = 0; + pageLinks.each((i, el) => { + let m = ($(el).attr('href') || '').match(/page[\/_](\d+)\.html/); + if (m && m[1]) { + let p = parseInt(m[1]); + if (p > maxPage) maxPage = p; + } + }); + if (maxPage > 0) pagecount = maxPage; + } + + return JSON.stringify({ list, page, pagecount, limit: list.length, total: pagecount * list.length }); } async function detail(id) { - try { - let url = id.startsWith('http') ? id : API_HOST + id; - const html = await fetchHtml(url); - - // 提取标题 - let title = ''; - const titleMatch = html.match(/]*class="title"[^>]*>([^<]*)<\/h1>/); - if (titleMatch) title = titleMatch[1].trim(); - - // 提取图片 - let pic = ''; - const picMatch = html.match(/]*class="img-responsive"[^>]*src="([^"]+)"/); - if (picMatch) pic = fixUrl(picMatch[1]); - - // 提取简介 - let content = ''; - const contentMatch = html.match(/]*class="detail-content"[^>]*>([\s\S]*?)<\/span>/); - if (contentMatch) { - content = contentMatch[1].replace(/<[^>]+>/g, '').replace(/\s+/g, ' ').trim(); - } - - // 提取播放列表(剧集) - const playFrom = ['茶杯狐']; - const episodes = []; - - // 匹配播放列表中的链接 - const ulMatch = html.match(/]*class="stui-content__playlist[^"]*"[^>]*>([\s\S]*?)<\/ul>/); - if (ulMatch) { - const ulContent = ulMatch[1]; - const liRegex = /]*>.*?]*href="([^"]+)"[^>]*>([^<]*)<\/a>/g; - let liMatch; - while ((liMatch = liRegex.exec(ulContent)) !== null) { - let epLink = liMatch[1]; - const epName = liMatch[2].trim(); - if (epName && epLink) { - if (!epLink.startsWith('http')) epLink = fixUrl(epLink); - episodes.push(epName + '$' + epLink); - } - } - } - - const vod = { - vod_id: id, - vod_name: title || '未知', - vod_pic: pic || '', - vod_content: content || '', - vod_play_from: playFrom.join('$$$'), - vod_play_url: episodes.join('#') || '' - }; - - return JSON.stringify({ list: [vod] }); - } catch (e) { - return JSON.stringify({ list: [{ vod_id: id, vod_name: '加载失败' }] }); + if (!host || typeof host !== 'string') { + host = 'http://www.bnjxjd.com'; + await init(); } + let url = id.startsWith('http') ? id : (host + id); + let html = await request(url); + if (!html) return JSON.stringify({ list: [] }); + + const $ = load(html); + + // 标题 + let vod_name = $('h1.title').first().text().trim(); + if (!vod_name) vod_name = $('h1').first().text().trim(); + + // 海报 + let vod_pic = $('img.img-responsive').first().attr('src') || ''; + if (!vod_pic) vod_pic = $('.stui-content__thumb img').first().attr('src') || ''; + if (vod_pic && !vod_pic.startsWith('http')) vod_pic = host + vod_pic; + + // 简介 + let vod_content = $('.detail-content').text().trim() || $('.col-pd').text().trim() || $('.intro').text().trim(); + + // 详情信息 + let type_name = '', vod_area = '', vod_year = '', vod_director = '', vod_actor = ''; + let infoItems = $('.stui-content__detail .data, .myui-content__detail .data'); + infoItems.each((i, el) => { + let text = $(el).text().trim(); + if (text.includes('类型:') || text.includes('分类:')) { + type_name = text.replace(/类型:|分类:/g, '').trim(); + } else if (text.includes('地区:')) { + vod_area = text.replace('地区:', '').trim(); + } else if (text.includes('年份:')) { + vod_year = text.replace('年份:', '').trim(); + } else if (text.includes('导演:')) { + vod_director = text.replace('导演:', '').trim(); + } else if (text.includes('主演:')) { + vod_actor = text.replace('主演:', '').trim(); + } + }); + + // 播放列表 + let sourceNames = []; + let sourceUrls = []; + + // 方法1: 标准茶杯狐结构 + let tabs = []; + $('.play-source-tab').each((i, el) => { + let name = $(el).text().trim(); + if (name && !name.includes('夸克') && !name.includes('网盘')) { + tabs.push(name); + } + }); + + $('.play-source-content').each((i, el) => { + let episodes = []; + $(el).find('a').each((j, a) => { + let href = $(a).attr('href') || ''; + let text = $(a).text().trim(); + if (text && href && !text.includes('script')) { + if (!href.startsWith('http')) href = host + href; + episodes.push(text + '$' + href); + } + }); + if (episodes.length > 0) { + let name = tabs[i] || ('线路' + (i + 1)); + sourceNames.push(name); + sourceUrls.push(episodes.join('#')); + } + }); + + // 方法2: 备用 - stui-content__playlist + if (sourceNames.length === 0) { + $('.stui-content__playlist').each((i, el) => { + let episodes = []; + $(el).find('a').each((j, a) => { + let href = $(a).attr('href') || ''; + let text = $(a).text().trim(); + if (text && href) { + if (!href.startsWith('http')) href = host + href; + episodes.push(text + '$' + href); + } + }); + if (episodes.length > 0) { + sourceNames.push('线路' + (i + 1)); + sourceUrls.push(episodes.join('#')); + } + }); + } + + // 方法3: 再备用 - 任何包含 /play/ 的链接 + if (sourceNames.length === 0) { + let episodes = []; + $('a[href*="/play/"]').each((i, a) => { + let href = $(a).attr('href') || ''; + let text = $(a).text().trim(); + if (text && href) { + if (!href.startsWith('http')) href = host + href; + episodes.push(text + '$' + href); + } + }); + if (episodes.length > 0) { + sourceNames.push('茶杯狐'); + sourceUrls.push(episodes.join('#')); + } + } + + return JSON.stringify({ + list: [{ + vod_id: id, + vod_name: vod_name || '未知', + vod_pic: vod_pic || '', + vod_content: vod_content || '', + vod_play_from: sourceNames.join('$$$') || '茶杯狐', + vod_play_url: sourceUrls.join('$$$') || '', + vod_director: vod_director || '', + vod_actor: vod_actor || '', + type_name: type_name || '', + vod_area: vod_area || '', + vod_year: vod_year || '', + vod_remarks: '' + }] + }); } async function search(wd, quick, pg) { - try { - const page = parseInt(pg) || 1; - const url = `${API_HOST}/vodsearch/wd/${encodeURIComponent(wd)}.html?page=${page}`; - const html = await fetchHtml(url); - const videos = parseVideoList(html); - return JSON.stringify({ list: videos, page: page }); - } catch (e) { - return JSON.stringify({ list: [] }); + if (!host || typeof host !== 'string') { + host = 'http://www.bnjxjd.com'; + await init(); } + let page = parseInt(pg) || 1; + let url = host + `/vodsearch/wd/${encodeURIComponent(wd)}.html?page=${page}`; + let html = await request(url); + if (!html) return JSON.stringify({ list: [] }); + + const $ = load(html); + let list = []; + $('.stui-vodlist__box').each((i, el) => { + let a = $(el).find('a.stui-vodlist__thumb').first(); + let href = a.attr('href') || ''; + let title = a.attr('title') || ''; + let pic = a.attr('data-original') || a.attr('src') || ''; + let remarks = $(el).find('.pic-text').first().text().trim(); + if (title && href) { + list.push({ + vod_id: href.startsWith('http') ? href : (host + href), + vod_name: title, + vod_pic: pic.startsWith('http') ? pic : (host + pic), + vod_remarks: remarks || '更新中' + }); + } + }); + return JSON.stringify({ list, page }); } async function play(flag, id, flags) { - let playUrl = id.startsWith('http') ? id : API_HOST + id; + let url = id.startsWith('http') ? id : (host + id); + let html = await request(url); - try { - const html = await fetchHtml(playUrl); - - // 提取真实视频地址 - let realUrl = null; - - // 匹配 m3u8 - const m3u8Match = html.match(/https?:\/\/[^"'\s<>]+\.m3u8[^"'\s<>]*/i); - if (m3u8Match) realUrl = m3u8Match[0]; - - // 匹配 json 中的 url - if (!realUrl) { - const jsonMatch = html.match(/"url"\s*:\s*"([^"]+\.m3u8[^"]*)"/); - if (jsonMatch) realUrl = jsonMatch[1].replace(/\\\//g, '/'); - } - - if (realUrl) { - if (realUrl.startsWith('//')) realUrl = 'https:' + realUrl; - return JSON.stringify({ parse: 0, url: realUrl, header: HEADERS }); - } - - return JSON.stringify({ parse: 1, url: playUrl, header: HEADERS }); - } catch (e) { - return JSON.stringify({ parse: 1, url: playUrl, header: HEADERS }); + // 提取 m3u8 + let m3u8Match = html.match(/https?:\/\/[^"'\s<>]+\.m3u8[^"'\s<>]*/i); + if (m3u8Match) { + return JSON.stringify({ parse: 0, url: m3u8Match[0], header: HEADERS }); } + + // 提取 mp4 + let mp4Match = html.match(/https?:\/\/[^"'\s<>]+\.mp4[^"'\s<>]*/i); + if (mp4Match) { + return JSON.stringify({ parse: 0, url: mp4Match[0], header: HEADERS }); + } + + // 提取 iframe 中的 url + let iframeMatch = html.match(/]*src=["']([^"']+)["']/i); + if (iframeMatch) { + let src = iframeMatch[1]; + if (src.startsWith('//')) src = 'https:' + src; + if (src.startsWith('/')) src = host + src; + if (src.startsWith('http')) { + let iframeHtml = await request(src); + if (iframeHtml) { + let innerMatch = iframeHtml.match(/https?:\/\/[^"'\s<>]+\.m3u8[^"'\s<>]*/i); + if (innerMatch) { + return JSON.stringify({ parse: 0, url: innerMatch[0], header: HEADERS }); + } + } + } + return JSON.stringify({ parse: 1, url: src, header: HEADERS }); + } + + return JSON.stringify({ parse: 1, url: id, header: HEADERS }); } export function __jsEvalReturn() { - return { - init: init, - home: home, - homeVod: homeVod, - category: category, - detail: detail, - search: search, - play: play - }; + return { init, home, homeVod, category, detail, search, play }; } \ No newline at end of file