import cheerio from 'assets://js/lib/cheerio.min.js'; const appConfig = { siteName: "伦理专题影片", siteUrl: "http://yoxayg.com" }; const UA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"; async function init(ext) { console.log("初始化爬虫:", appConfig.siteName); } const classList = [ { type_id: "1", type_name: "电影" }, { type_id: "2", type_name: "电视剧" }, { type_id: "3", type_name: "综艺" }, { type_id: "4", type_name: "动漫" }, { type_id: "36", type_name: "短剧" }, { type_id: "6", type_name: "动作片" }, { type_id: "7", type_name: "喜剧片" }, { type_id: "8", type_name: "爱情片" }, { type_id: "9", type_name: "科幻片" }, { type_id: "10", type_name: "恐怖片" }, { type_id: "11", type_name: "剧情片" }, { type_id: "12", type_name: "战争片" }, { type_id: "20", type_name: "记录片" }, { type_id: "34", type_name: "伦理片" }, { type_id: "13", type_name: "国产剧" }, { type_id: "14", type_name: "香港剧" }, { type_id: "15", type_name: "韩国剧" }, { type_id: "16", type_name: "欧美剧" }, { type_id: "21", type_name: "台湾剧" }, { type_id: "22", type_name: "日本剧" }, { type_id: "23", type_name: "海外剧" }, { type_id: "24", type_name: "泰国剧" }, { type_id: "25", type_name: "大陆综艺" }, { type_id: "26", type_name: "港台综艺" }, { type_id: "27", type_name: "日韩综艺" }, { type_id: "28", type_name: "欧美综艺" }, { type_id: "29", type_name: "国产动漫" }, { type_id: "30", type_name: "日韩动漫" }, { type_id: "31", type_name: "欧美动漫" }, { type_id: "32", type_name: "港台动漫" }, { type_id: "33", type_name: "海外动漫" } ]; const typeSlugMap = { "1": "dianying", "2": "dianshiju", "3": "zongyi", "4": "dongman", "6": "dongzuopian", "7": "xijupian", "8": "aiqingpian", "9": "kehuanpian", "10": "kongbupian", "11": "juqingpian", "12": "zhanzhengpian", "13": "guochanju", "14": "xianggangju", "15": "hanguoju", "16": "oumeiju", "20": "jilupian", "21": "taiwanju", "22": "ribenju", "23": "haiwaiju", "24": "taiguoju", "25": "daluzongyi", "26": "gangtaizongyi", "27": "rihanzongyi", "28": "oumeizongyi", "29": "guochandongman", "30": "rihandongman", "31": "oumeidongman", "32": "gangtaidongman", "33": "haiwaidongman", "34": "lunlipian", "36": "duanju" }; const typeParentMap = { "1": 0, "2": 0, "3": 0, "4": 0, "6": 1, "7": 1, "8": 1, "9": 1, "10": 1, "11": 1, "12": 1, "13": 2, "14": 2, "15": 2, "16": 2, "20": 1, "21": 2, "22": 2, "23": 2, "24": 2, "25": 3, "26": 3, "27": 3, "28": 3, "29": 4, "30": 4, "31": 4, "32": 4, "33": 4, "34": 1, "36": 2 }; function topType(tid) { tid = parseInt(tid || 0, 10); if (tid === 36) return 36; const p = parseInt(typeParentMap[String(tid)] || 0, 10); return p > 0 ? p : tid; } function typeSlug(tid) { return typeSlugMap[String(parseInt(tid || 0, 10))] || ('type' + parseInt(tid || 0, 10)); } function getAreaFilter() { return { "key": "area", "name": "地区", "value": [ { "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": "其他" } ] }; } function getYearFilter() { let years = [{ "n": "全部", "v": "" }]; const currentYear = new Date().getFullYear(); for (let y = currentYear; y >= 2010; y--) { years.push({ "n": String(y), "v": String(y) }); } return { "key": "year", "name": "年份", "value": years }; } const commonFilters = [getAreaFilter(), getYearFilter()]; const myFilters = {}; classList.forEach(item => { myFilters[item.type_id] = commonFilters; }); function fixUrl(u) { if (!u) return ''; if (u.startsWith('http')) return u; if (u.startsWith('//')) return 'http:' + u; if (u.startsWith('/')) return appConfig.siteUrl + u; return u; } async function home(filter) { let list = []; try { const html = (await req(appConfig.siteUrl, { method: "GET", headers: { "User-Agent": UA } })).content; const $ = cheerio.load(html); let seen = {}; $(".tpl-card[href]").each(function () { let vod_id = $(this).attr("href"); if (!vod_id || seen[vod_id]) return; let vod_name = $(this).attr("title") || $(this).find("strong").first().text().trim() || ""; vod_name = vod_name.replace(/免费在线观看$/, ""); let vod_pic = fixUrl($(this).find("img").first().attr("src") || ""); let vod_remarks = $(this).find("em").first().text().trim(); if (vod_name && vod_id) { seen[vod_id] = true; list.push({ vod_id, vod_name, vod_pic, vod_remarks }); } }); } catch (e) { console.error("首页推荐获取失败:", e.message); } return JSON.stringify({ class: classList, filters: myFilters, list: list.slice(0, 30) }); } function buildCategoryUrl(tid, pg, extend) { extend = extend || {}; let top = topType(tid); let topSlug = typeSlug(top); let childSlug = (tid != top) ? typeSlug(tid) : ''; let path; if (childSlug && tid != 36) { path = `/${topSlug}/${childSlug}/`; } else { path = `/${topSlug}/`; } if (pg > 1) { path += `page-${pg}.html`; } let area = extend.area || ''; let year = extend.year || ''; if (area || year) { let params = []; if (area) params.push('area=' + encodeURIComponent(area)); if (year) params.push('year=' + encodeURIComponent(year)); return appConfig.siteUrl + `/api_proxy.php?ac=list&t=${tid}&pg=${pg}&${params.join('&')}`; } return appConfig.siteUrl + path; } function parseListHtml(html) { const $ = cheerio.load(html); let list = []; let vodIds = {}; $(".tpl-card.card-poster, .tpl-card.card-wide").each(function () { let $a = $(this); let vod_id = $a.attr("href"); if (!vod_id || vodIds[vod_id]) return; let vod_name = $a.attr("title") || $a.find("strong").first().text().trim() || ""; vod_name = vod_name.replace(/免费在线观看$/, ""); let vod_pic = fixUrl($a.find("img").first().attr("src") || ""); let vod_remarks = $a.find("em").first().text().trim() || $a.find("small").first().text().trim(); if (vod_name && vod_id) { vodIds[vod_id] = true; list.push({ vod_id, vod_name, vod_pic, vod_remarks }); } }); if (list.length === 0) { try { let data = JSON.parse(html); if (data.list && Array.isArray(data.list)) { data.list.forEach(item => { let vod_id = `/${typeSlug(topType(item.type_id))}/${(item.vod_en || item.vod_name || '').toLowerCase().replace(/[^a-z0-9]+/g, '')}.html`; let vod_name = item.vod_name || ""; let vod_pic = item.vod_pic || ""; let vod_remarks = item.vod_remarks || ""; if (vod_name) { list.push({ vod_id, vod_name, vod_pic, vod_remarks }); } }); } } catch (e) { } } let pagecount = 1; $(".pagination a").each(function () { let href = $(this).attr("href") || ''; let m = href.match(/page-(\d+)/); if (m) { let p = parseInt(m[1]); if (p > pagecount) pagecount = p; } }); return { list, pagecount }; } async function category(tid, pg, filter, extend) { pg = pg || 1; extend = extend || {}; let url = buildCategoryUrl(tid, pg, extend); try { const html = (await req(url, { method: "GET", headers: { "User-Agent": UA, "Referer": appConfig.siteUrl } })).content; const result = parseListHtml(html); return JSON.stringify(result); } catch (e) { console.error("分类列表获取失败:", e.message); return JSON.stringify({ list: [], pagecount: 0 }); } } async function search(wd, quick, page) { page = page || 1; try { const url = `${appConfig.siteUrl}/search-${encodeURIComponent(wd)}${page > 1 ? '-page-' + page : ''}.html`; const html = (await req(url, { method: "GET", headers: { "User-Agent": UA, "Referer": appConfig.siteUrl } })).content; const result = parseListHtml(html); return JSON.stringify(result); } catch (e) { console.error("搜索失败:", e.message); return JSON.stringify({ list: [], pagecount: 0 }); } } async function detail(id) { try { const html = (await req(appConfig.siteUrl + id, { method: "GET", headers: { "User-Agent": UA, "Referer": appConfig.siteUrl } })).content; const $ = cheerio.load(html); let vod_name = $("h1").first().text().trim(); let vod_pic = fixUrl($(".detail-poster img").first().attr("src") || ""); let vod_director = ""; let vod_actor = ""; let vod_area = ""; let vod_year = ""; let vod_content = ""; let vod_class = ""; let vod_remarks = ""; $(".detail-tags span").each(function () { let text = $(this).text().trim(); if (/^\d{4}$/.test(text) && !vod_year) vod_year = text; else if (/大陆|香港|台湾|美国|日本|韩国|英国|法国|德国|泰国|印度|其他/.test(text) && !vod_area) vod_area = text; else if (!vod_class && text !== vod_year && text !== vod_area && !/更新至|集|期|TC|HD|正片/.test(text)) vod_class = text; }); vod_remarks = $(".detail-poster span").first().text().trim(); $(".detail-meta p").each(function () { let text = $(this).text(); if (text.includes("主演:") && !vod_actor) { vod_actor = text.replace("主演:", "").trim(); } if (text.includes("导演:") && !vod_director) { vod_director = text.replace("导演:", "").trim(); } }); let $intro = $(".detail-intro"); if ($intro.length > 0) { vod_content = $intro.first().text().replace("简介:", "").trim(); } if (!vod_content) { $intro = $(".article-text"); if ($intro.length > 0) { vod_content = $intro.first().text().trim(); } } let lines = []; let playlists = []; let epArray = []; $(".episode-grid a").each(function () { let name = $(this).text().trim(); let href = $(this).attr('href') || ''; if (name && href) { epArray.push({ name, href }); } }); epArray.sort((a, b) => { let numA = parseInt(a.name.match(/第(\d+)/)?.[1] || 0); let numB = parseInt(b.name.match(/第(\d+)/)?.[1] || 0); return numA - numB; }); let episodes = epArray.map(ep => `${ep.name}$${ep.href}`); if (episodes.length > 0) { lines.push("默认"); playlists.push(episodes); } else { let playHref = $(".detail-actions a.btn-primary").attr("href") || ""; if (playHref) { lines.push("默认"); playlists.push([`正片$${playHref}`]); } } if (lines.length === 0) { lines.push("默认"); playlists.push([`暂无播放地址$${id}`]); } const { vod_play_from, vod_play_url } = buildVodPlayData(lines, playlists); return JSON.stringify({ list: [{ vod_id: id, vod_name, vod_pic, vod_actor, vod_director, vod_remarks, vod_year, vod_area, vod_content, vod_class, vod_play_from, vod_play_url }] }); } catch (error) { console.error(`解析详情页异常 [ID: ${id}]:`, error); return JSON.stringify({ list: [] }); } } function buildVodPlayData(lines, playlists) { const processedPlaylists = playlists.map(eps => eps.join('#')); return { vod_play_from: lines.filter(Boolean).join('$$$'), vod_play_url: processedPlaylists.join('$$$') }; } async function play(flag, id, flags) { try { if (id.startsWith("http")) { return JSON.stringify({ parse: 0, Header: { "User-Agent": UA, "Referer": appConfig.siteUrl }, url: id }); } const html = (await req(`${appConfig.siteUrl}${id}`, { method: "GET", headers: { "User-Agent": UA, "Referer": appConfig.siteUrl } })).content; // 方式1:从 noscript 中提取 m3u8 URL let noscriptMatch = html.match(/