From b3d0e74e5d45c5bc2a2abcd3e3398f70c68ca617 Mon Sep 17 00:00:00 2001 From: gyjune <14752+gyjune@noreply.example.org> Date: Wed, 5 Aug 2026 04:27:54 +0200 Subject: [PATCH] =?UTF-8?q?=E5=88=A0=E9=99=A4=20js/=E8=8C=B6=E6=9D=AF?= =?UTF-8?q?=E7=8B=90.js?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- js/茶杯狐.js | 563 --------------------------------------------------- 1 file changed, 563 deletions(-) delete mode 100644 js/茶杯狐.js diff --git a/js/茶杯狐.js b/js/茶杯狐.js deleted file mode 100644 index 59baf47..0000000 --- a/js/茶杯狐.js +++ /dev/null @@ -1,563 +0,0 @@ -/* -@header({ - searchable: 1, - filterable: 1, - quickSearch: 1, - title: '茶杯狐', - lang: 'cat' -}) -*/ - -const API_HOST = 'http://www.bnjxjd.com'; - -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-language': 'zh-CN,zh;q=0.9', - 'Referer': 'http://www.bnjxjd.com/', - 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', -}; - -// ============ 工具函数 ============ - -async function fetchHtml(url) { - try { - const res = await req(url, { headers: HEADERS }); - return res.content || ''; - } catch (e) { - return ''; - } -} - -function cleanHtml(text) { - if (!text) return ''; - return text.replace(/<[^>]+>/g, '').replace(/\s+/g, ' ').trim(); -} - -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; -} - -// ============ 解析视频列表(基于实际HTML结构) ============ - -function parseVideoList(html) { - const videos = []; - if (!html) return videos; - - // 匹配每个 li 中的 stui-vodlist__box - const boxPattern = /]*class="stui-vodlist__box"[^>]*>([\s\S]*?)<\/div>\s*<\/div>\s*<\/div>/g; - let match; - - while ((match = boxPattern.exec(html)) !== null) { - try { - const block = match[1]; - - // 提取 a 标签 - 包含 href, title, data-original - const aMatch = block.match(/]*class="[^"]*stui-vodlist__thumb[^"]*"[^>]*href="([^"]+)"[^>]*title="([^"]*)"[^>]*(?:data-original|src)="([^"]+)"[^>]*>/); - if (!aMatch) continue; - - let href = aMatch[1]; - const title = aMatch[2].trim(); - let pic = aMatch[3]; - - if (!title || !href) continue; - - // 提取备注 (pic-text) - let remark = '更新中'; - const remarkMatch = block.match(/]*class="pic-text[^"]*"[^>]*>([^<]*)<\/span>/); - if (remarkMatch) { - remark = remarkMatch[1].trim(); - } - - // 修复 URL - if (pic && !pic.startsWith('http')) pic = fixUrl(pic); - if (href && !href.startsWith('http')) href = fixUrl(href); - - videos.push({ - vod_id: href, - vod_name: title, - vod_pic: pic || '', - vod_remarks: remark || '更新中' - }); - } catch (e) {} - } - - // 备用解析:如果没有匹配到,尝试用更简单的方式 - if (videos.length === 0) { - const altPattern = /]*class="[^"]*stui-vodlist__thumb[^"]*"[^>]*href="([^"]+)"[^>]*title="([^"]*)"[^>]*(?:data-original|src)="([^"]+)"[^>]*>/g; - let altMatch; - while ((altMatch = altPattern.exec(html)) !== null) { - let href = altMatch[1]; - const title = altMatch[2].trim(); - let pic = altMatch[3]; - if (!title || !href) continue; - if (pic && !pic.startsWith('http')) pic = fixUrl(pic); - if (href && !href.startsWith('http')) href = fixUrl(href); - videos.push({ - vod_id: href, - vod_name: title, - vod_pic: pic || '', - vod_remarks: '更新中' - }); - } - } - - return videos; -} - -// ============ 分类配置 ============ - -const CLASSES = [ - { type_id: '1', type_name: '电影' }, - { type_id: '2', type_name: '电视剧' }, - { type_id: '3', type_name: '综艺' }, - { type_id: '4', type_name: '动漫' } -]; - -const FILTERS = { - '1': [ - { key: 'class', name: '类型', value: [ - { v: '1', 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: '0', 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: '0', 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: '更早' } - ]}, - { key: 'sort', name: '排序', value: [ - { v: 'time', n: '时间' }, - { v: 'hits', n: '人气' }, - { v: 'score', n: '评分' } - ]} - ], - '2': [ - { key: 'class', name: '类型', value: [ - { v: '2', n: '全部' }, - { v: 'guochanju', n: '国产剧' }, - { v: 'oumeiju', n: '欧美剧' }, - { v: 'rihanju', n: '日韩剧' }, - { v: 'gangtaiju', n: '港台剧' }, - { v: 'haiwaiju', n: '海外剧' } - ]}, - { key: 'area', name: '地区', value: [ - { v: '0', n: '全部' }, - { v: '大陆', n: '大陆' }, - { v: '香港', n: '香港' }, - { v: '台湾', n: '台湾' }, - { v: '美国', n: '美国' }, - { v: '日本', n: '日本' }, - { v: '韩国', n: '韩国' }, - { v: '英国', n: '英国' } - ]}, - { key: 'year', name: '年份', value: [ - { v: '0', 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: '更早' } - ]}, - { key: 'sort', name: '排序', value: [ - { v: 'time', n: '时间' }, - { v: 'hits', n: '人气' }, - { v: 'score', n: '评分' } - ]} - ], - '3': [ - { key: 'year', name: '年份', value: [ - { v: '0', n: '全部' }, - { v: '2026', n: '2026' }, - { v: '2025', n: '2025' }, - { v: '2024', n: '2024' } - ]}, - { key: 'sort', name: '排序', value: [ - { v: 'time', n: '时间' }, - { v: 'hits', n: '人气' } - ]} - ], - '4': [ - { key: 'area', name: '地区', value: [ - { v: '0', n: '全部' }, - { v: '大陆', n: '大陆' }, - { v: '日本', n: '日本' }, - { v: '美国', n: '美国' } - ]}, - { key: 'year', name: '年份', value: [ - { v: '0', n: '全部' }, - { v: '2026', n: '2026' }, - { v: '2025', n: '2025' }, - { v: '2024', n: '2024' } - ]}, - { key: 'sort', name: '排序', value: [ - { v: 'time', n: '时间' }, - { v: 'hits', n: '人气' } - ]} - ] -}; - -// ============ 导出函数 ============ - -function init(cfg) { return true; } - -function home(filter) { - 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: [] }); - } -} - -async function category(tid, pg, filter, extend) { - try { - const page = parseInt(pg) || 1; - const classVal = (extend && extend.class) || ''; - const areaVal = (extend && extend.area) || '0'; - const yearVal = (extend && extend.year) || '0'; - const sortVal = (extend && extend.sort) || 'time'; - - // 构建筛选URL - let url = `${API_HOST}/vodshow`; - - // 构建参数 - const params = []; - if (classVal && classVal !== '1' && classVal !== '2') { - params.push(`id/${classVal}`); - } - if (areaVal && areaVal !== '0') { - params.push(`area/${encodeURIComponent(areaVal)}`); - } - if (yearVal && yearVal !== '0') { - params.push(`year/${yearVal}`); - } - if (sortVal && sortVal !== 'time') { - params.push(`by/${sortVal}`); - } - - // 如果没有任何筛选,使用默认分类页 - if (params.length === 0) { - if (tid === '1') { - url = `${API_HOST}/movie/fenlei1---${page}.html`; - } else if (tid === '2') { - url = `${API_HOST}/movie/fenlei2---${page}.html`; - } else if (tid === '3') { - url = `${API_HOST}/movie/fenlei3---${page}.html`; - } else if (tid === '4') { - url = `${API_HOST}/movie/fenlei4---${page}.html`; - } else { - url = `${API_HOST}/vodshow/id/${tid}/page/${page}.html`; - } - } else { - // 有筛选条件:/vodshow/id/dongzuopian/area/大陆/year/2024/by/time/page/2.html - const idParam = classVal && classVal !== '1' && classVal !== '2' ? `id/${classVal}` : `id/${tid}`; - const allParams = [idParam, ...params]; - url = `${API_HOST}/vodshow/${allParams.join('/')}/page/${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: [], page: pg }); - } -} - -async function detail(id) { - try { - let url = id; - if (!url.startsWith('http')) { - if (url.startsWith('/')) { - url = API_HOST + url; - } else { - url = API_HOST + '/' + url; - } - } - - const html = await fetchHtml(url); - const info = { vod_id: id }; - - // 标题 - let titleMatch = html.match(/]*class="title"[^>]*>([^<]*)<\/h1>/); - if (!titleMatch) { - titleMatch = html.match(/]*>([^<]*)<\/h1>/); - } - info.vod_name = titleMatch ? cleanHtml(titleMatch[1]) : ''; - - // 简介 - let descMatch = html.match(/]*class="detail-content"[^>]*>([\s\S]*?)<\/span>/); - if (!descMatch) { - descMatch = html.match(/]*class="col-pd"[^>]*>([\s\S]*?)<\/p>/); - } - if (descMatch) { - info.vod_content = cleanHtml(descMatch[1].replace(/<[^>]+>/g, '')); - } else { - info.vod_content = ''; - } - - // 海报 - let picMatch = html.match(/]*class="img-responsive"[^>]*src="([^"]+)"/); - if (!picMatch) { - picMatch = html.match(/]*src="([^"]+)"[^>]*class="[^"]*lazy[^"]*"/); - } - info.vod_pic = picMatch ? fixUrl(picMatch[1]) : ''; - - // 演员 - const actorMatch = html.match(/主演:<\/span>([\s\S]*?)<\/p>/); - info.vod_actor = actorMatch ? cleanHtml(actorMatch[1].replace(/<[^>]+>/g, '')) : ''; - - // 导演 - const directorMatch = html.match(/导演:<\/span>([\s\S]*?)<\/p>/); - info.vod_director = directorMatch ? cleanHtml(directorMatch[1].replace(/<[^>]+>/g, '')) : ''; - - // 地区 - const areaMatch = html.match(/地区:<\/span>([\s\S]*?)<\/p>/); - info.vod_area = areaMatch ? cleanHtml(areaMatch[1].replace(/<[^>]+>/g, '')) : ''; - - // 年份 - const yearMatch = html.match(/年份:<\/span>([\s\S]*?)<\/p>/); - info.vod_year = yearMatch ? cleanHtml(yearMatch[1].replace(/<[^>]+>/g, '')) : ''; - - // 类型 - const typeMatch = html.match(/类型:<\/span>([\s\S]*?)<\/p>/); - info.type_name = typeMatch ? cleanHtml(typeMatch[1].replace(/<[^>]+>/g, '')) : ''; - - // ========== 提取播放列表 ========== - const playFrom = []; - const playUrls = []; - - // 提取线路名称 - const tabs = []; - const tabPattern = /]*>.*?]*href="#playlist(\d+)"[^>]*>([^<]*)<\/a>/g; - let tabMatch; - while ((tabMatch = tabPattern.exec(html)) !== null) { - const lineName = cleanHtml(tabMatch[2].replace(/<[^>]+>/g, '')); - if (lineName && !['猜您喜欢', '同类型', '大家还在看', '影片评论', '播放地址'].includes(lineName)) { - tabs.push(lineName); - } - } - - // 提取剧集列表 - const ulPattern = /]*class="stui-content__playlist[^"]*"[^>]*>([\s\S]*?)<\/ul>/g; - const ulMatches = []; - let ulMatch; - while ((ulMatch = ulPattern.exec(html)) !== null) { - ulMatches.push(ulMatch[1]); - } - - for (let idx = 0; idx < ulMatches.length; idx++) { - const ulContent = ulMatches[idx]; - const liPattern = /]*>.*?]*href="([^"]+)"[^>]*>([^<]*)<\/a>/g; - const episodes = []; - let liMatch; - while ((liMatch = liPattern.exec(ulContent)) !== null) { - let epLink = liMatch[1]; - const epName = cleanHtml(liMatch[2]); - if (epName && epLink) { - if (!epLink.startsWith('http')) epLink = fixUrl(epLink); - episodes.push(epName + '$' + epLink); - } - } - if (episodes.length > 0) { - const sourceName = tabs[idx] || ('播放源' + (idx + 1)); - playFrom.push(sourceName); - playUrls.push(episodes.join('#')); - } - } - - // 备用:直接找所有播放链接 - if (playFrom.length === 0) { - const linkPattern = /]*href="([^"]*)"[^>]*>([^<]+)<\/a>/g; - const episodes = []; - let linkMatch; - while ((linkMatch = linkPattern.exec(html)) !== null) { - let epLink = linkMatch[1]; - const epName = cleanHtml(linkMatch[2]); - if (epLink && epName && epLink.includes('/play/')) { - if (epName && !epName.includes('立即') && !epName.includes('播放')) { - if (!epLink.startsWith('http')) epLink = fixUrl(epLink); - episodes.push(epName + '$' + epLink); - } - } - } - if (episodes.length > 0) { - playFrom.push('茶杯狐'); - playUrls.push(episodes.join('#')); - } - } - - info.vod_play_from = playFrom.join('$$$') || '茶杯狐'; - info.vod_play_url = playUrls.join('$$$') || ''; - - return JSON.stringify({ list: [info] }); - } catch (e) { - return JSON.stringify({ - list: [{ - vod_id: id, - vod_name: '加载失败', - vod_play_url: '' - }] - }); - } -} - -async function search(wd, quick, pg) { - try { - const page = parseInt(pg) || 1; - const encodedKey = encodeURIComponent(wd); - const url = `${API_HOST}/vodsearch/wd/${encodedKey}.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: [] }); - } -} - -async function play(flag, id, flags) { - let playUrl = id; - if (!playUrl.startsWith('http')) { - if (playUrl.startsWith('/')) { - playUrl = API_HOST + playUrl; - } else { - playUrl = API_HOST + '/' + playUrl; - } - } - - try { - const html = await fetchHtml(playUrl); - - let realUrl = null; - - // 匹配 JSON 中的 url - const urlPatterns = [ - /"url"\s*:\s*"([^"]+\.m3u8[^"]*)"/, - /url\s*:\s*"([^"]+\.m3u8[^"]*)"/, - /"url"\s*:\s*'([^']+\.m3u8[^']*)'/, - ]; - for (const p of urlPatterns) { - const match = html.match(p); - if (match) { - realUrl = match[1].replace(/\\\//g, '/'); - if (realUrl.startsWith('//')) realUrl = 'https:' + realUrl; - break; - } - } - - if (!realUrl) { - const m3u8Match = html.match(/https?:\/\/[^"'\s<>]+\.m3u8[^"'\s<>]*/i); - if (m3u8Match) realUrl = m3u8Match[0]; - } - - if (!realUrl) { - const mp4Match = html.match(/https?:\/\/[^"'\s<>]+\.mp4[^"'\s<>]*/i); - if (mp4Match) realUrl = mp4Match[0]; - } - - if (!realUrl) { - const iframeMatch = html.match(/]*src=["']([^"']+)["']/i); - if (iframeMatch) { - let src = iframeMatch[1]; - if (src.startsWith('//')) src = 'https:' + src; - if (src.startsWith('/')) src = API_HOST + src; - if (src.startsWith('http')) { - const iframeHtml = await fetchHtml(src); - if (iframeHtml) { - const innerMatch = iframeHtml.match(/https?:\/\/[^"'\s<>]+\.m3u8[^"'\s<>]*/i); - if (innerMatch) realUrl = innerMatch[0]; - } - } - } - } - - if (realUrl) { - if (realUrl.startsWith('//')) realUrl = 'https:' + realUrl; - const isDirect = /\.(m3u8|mp4|mkv|flv|ts)$/i.test(realUrl); - return JSON.stringify({ parse: isDirect ? 0 : 1, url: realUrl, header: HEADERS }); - } - - return JSON.stringify({ parse: 1, url: playUrl, header: HEADERS }); - } catch (e) { - return JSON.stringify({ parse: 1, url: playUrl, header: HEADERS }); - } -} - -export function __jsEvalReturn() { - return { - init: init, - home: home, - homeVod: homeVod, - category: category, - search: search, - detail: detail, - play: play - }; -} \ No newline at end of file