From b84bdfc18584ea04b4842bf3612449db29151586 Mon Sep 17 00:00:00 2001 From: gyjune <14752+gyjune@noreply.example.org> Date: Wed, 5 Aug 2026 03:59:03 +0200 Subject: [PATCH] =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E6=96=87=E4=BB=B6=E8=87=B3?= =?UTF-8?q?=E3=80=8Cjs=E3=80=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- js/茶杯狐.js | 543 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 543 insertions(+) create mode 100644 js/茶杯狐.js diff --git a/js/茶杯狐.js b/js/茶杯狐.js new file mode 100644 index 0000000..cabbd93 --- /dev/null +++ b/js/茶杯狐.js @@ -0,0 +1,543 @@ +/* +@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; +} + +// ============ 解析视频列表 ============ + +function parseVideoList(html) { + const videos = []; + if (!html) return videos; + + // 使用正则匹配 stui-vodlist__box 或 stui-vodlist__item + const itemPattern = /]*class="[^"]*stui-vodlist__box[^"]*"[^>]*>([\s\S]*?)<\/div>\s*<\/div>\s*<\/div>/g; + let match; + + while ((match = itemPattern.exec(html)) !== null) { + const block = match[1]; + try { + // 提取链接和标题 + const linkMatch = block.match(/]*class="[^"]*stui-vodlist__thumb[^"]*"[^>]*title="([^"]*)"[^>]*href="([^"]*)"[^>]*>/); + if (!linkMatch) continue; + const title = linkMatch[1].trim(); + let href = linkMatch[2]; + if (!title || !href) continue; + + // 提取图片 + let pic = ''; + const picMatch = block.match(/data-original="([^"]+)"/); + if (picMatch) { + pic = picMatch[1]; + } else { + const srcMatch = block.match(/src="([^"]+)"/); + if (srcMatch) pic = srcMatch[1]; + } + + // 提取备注 + let remark = ''; + const remarkMatch = block.match(/]*class="pic-text[^"]*"[^>]*>([^<]*)<\/span>/); + if (remarkMatch) remark = remarkMatch[1].trim(); + + 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 pattern = /]*class="[^"]*stui-vodlist__thumb[^"]*"[^>]*title="([^"]*)"[^>]*(?:data-original|src)="([^"]*)"[^>]*href="([^"]*)"[^>]*>/g; + let m; + while ((m = pattern.exec(html)) !== null) { + const title = m[1].trim(); + let pic = m[2]; + let href = m[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: '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', 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: '泰国' }, + { 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: '17', n: '国产剧' }, + { v: '21', n: '欧美剧' }, + { v: '20', n: '日韩剧' }, + { v: '18', n: '港台剧' }, + { v: '22', 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'; + // sort 仅用于排序,URL中不体现 + + // 构建URL: /movie/fenlei{tid}--{class}--{area}--{year}---{page}---.html + 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 = `${API_HOST}/movie/fenlei${tid}--${c}--${a}--${y}---${page}---.html`; + } else { + 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: [], 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; + } + } + + // 直接匹配 m3u8 URL + if (!realUrl) { + const m3u8Match = html.match(/https?:\/\/[^"'\s<>]+\.m3u8[^"'\s<>]*/i); + if (m3u8Match) realUrl = m3u8Match[0]; + } + + // 匹配 iframe 并递归 + 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, + detail: detail, + search: search, + play: play + }; +} \ No newline at end of file