Sync all projects
This commit is contained in:
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"请求头": "手机",
|
||||
"主页url": "http://www.qiyoudy2.com/",
|
||||
"状态": "地区:&&</p>",
|
||||
"导演": "导演:&&</p>",
|
||||
"主演": "主演:&&</p>",
|
||||
"简介": "简介:&&</p>",
|
||||
"数组": "<div class=\"stui-vodlist__box\">&&</a>",
|
||||
"图片": "data-original=\"&&\"",
|
||||
"标题": "title=\"&&\"",
|
||||
"副标题": "text-right\">&&</span>",
|
||||
"链接": "href=\"&&\"",
|
||||
"搜索url": "http://www.qiyoudy2.com/search.php;post;searchword={wd}",
|
||||
"搜索数组": "v-thumb stui-vodlist__thumb&&</a>[不包含:伦理#韩语中字#日语中字#高清中字#日本中字#高清#HD中字#日本#韩国#HD]",
|
||||
"搜索图片": "data-original=\"&&\"",
|
||||
"搜索标题": "title=\"&&\"",
|
||||
"搜索副标题": "text-right\">&&</span>",
|
||||
"搜索链接": "href=\"&&\"",
|
||||
"线路数组": "href=\"#down&&/a>[替换:播放源1>>七月大佬一线#播放源2>>七月大佬二线#播放源3>>七月大佬三线#播放源4>>七月大佬四线]",
|
||||
"线路标题": ">&&</a>",
|
||||
"播放数组": "stui-content__playlist clearfix&&</ul>",
|
||||
"播放列表": "<li>&&</li>",
|
||||
"播放标题": ">&&</a>",
|
||||
"分类url": "http://www.qiyoudy2.com/list/{cateId}_{catePg}.html;;ak",
|
||||
"分类": "电影$1#电视剧$2#动漫$3#综艺$4#伦理$6"
|
||||
}
|
||||
@@ -0,0 +1,433 @@
|
||||
import cheerio from 'assets://js/lib/cheerio.min.js';
|
||||
|
||||
|
||||
const sites = [
|
||||
'https://dzsx5k01kgm6y.cloudfront.net',//这个可以直连
|
||||
'https://attack.bjidvlyog.com',
|
||||
'https://agency.bjidvlyog.com/'
|
||||
]
|
||||
const baseUrl = sites[0];
|
||||
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";
|
||||
|
||||
function mylog() {
|
||||
const TAG = "xp天堂18+";
|
||||
console.log(TAG, ...arguments)
|
||||
}
|
||||
async function init(extend) { }
|
||||
|
||||
|
||||
let cachedClasses = [];
|
||||
let cachedFilters = {};
|
||||
let hasParsed = false;
|
||||
|
||||
/**
|
||||
* 1. 动态获取并解析全量的一级分类 (class)
|
||||
*/
|
||||
async function home(filter) {
|
||||
try {
|
||||
// 1. 请求首页或者含有这个导航 container 的网页源码
|
||||
const html = await req(baseUrl).content; // 如果是在特定页面,把 baseUrl 改为对应路径
|
||||
const $ = cheerio.load(html);
|
||||
let classes = [];
|
||||
let filters = {};
|
||||
|
||||
// 标准的排序规则
|
||||
const sortFilter = [
|
||||
{
|
||||
key: "sort",
|
||||
name: "排序",
|
||||
value: [
|
||||
{ n: "最近更新", v: "update" },
|
||||
{ n: "最高收藏", v: "favorite" },
|
||||
{ n: "近期最佳", v: "hot" },
|
||||
{ n: "最多观看", v: "watch" }
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
// 2. 扫一遍所有的 container 区块,全量榨干“选片”和“标签”
|
||||
$('.app-nav .container').each((index, element) => {
|
||||
// 抓取区块的标题(比如“选片”)
|
||||
const blockTitle = $(element).find('.title-box h2').text().trim();
|
||||
|
||||
// === 自动解析【选片主题】块 ===
|
||||
if (blockTitle.includes("选片") || blockTitle.includes("主题")) {
|
||||
$(element).find('a.tjtagmanager').each((i, el) => {
|
||||
const name = $(el).text().trim();
|
||||
let href = $(el).attr('href') || '';
|
||||
|
||||
// 砍掉末尾自带的 /favorite/ 或 /update/,还原成纯净的 ID 路径
|
||||
// 例如: /theme/detail/64/favorite/ -> /theme/detail/64
|
||||
href = href.replace(/\/(favorite|update|hot|watch)\/?$/, '');
|
||||
|
||||
if (href && name) {
|
||||
classes.push({ type_id: href, type_name: name });
|
||||
filters[href] = sortFilter; // 给它绑定排序筛选
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// === 自动解析【平级标签】块 ===
|
||||
// 根据特征:含有 .tag 类的 a 标签
|
||||
if ($(element).find('a.tag').length > 0) {
|
||||
$(element).find('a.tag').each((i, el) => {
|
||||
const name = $(el).text().trim();
|
||||
let href = $(el).attr('href') || '';
|
||||
|
||||
// 砍掉末尾可能自带的排序后缀(如 /hot/)和开头的斜杠,还原成纯净的 ID 路径
|
||||
// 例如: /tags/9540/hot/ -> /tags/9540
|
||||
href = href.replace(/\/(favorite|update|hot|watch)\/?$/, '');
|
||||
|
||||
if (href && name) {
|
||||
// 加上前缀或者小图标区分标签和主题
|
||||
classes.push({ type_id: href, type_name: `🏷️ ${name}` });
|
||||
filters[href] = sortFilter; // 给标签也绑定平级的排序筛选
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 3. 缓存结果,防止后续调用 homeFilter 时二次请求浪费时间
|
||||
// 暂时过滤资讯和回家
|
||||
cachedClasses = classes.filter(item => !item.type_name.includes("资讯")&&!item.type_name.includes('回家'));
|
||||
cachedFilters = filters;
|
||||
hasParsed = true;
|
||||
return JSON.stringify({
|
||||
class: cachedClasses,
|
||||
filters: await homeFilter()
|
||||
});
|
||||
|
||||
} catch (e) {
|
||||
console.error("❌ 全自动解析 class 失败: ", e.message);
|
||||
// 崩溃兜底,防止壳子黑屏
|
||||
return JSON.stringify({ class: [] });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 2. 完美的平级筛选逻辑
|
||||
*/
|
||||
async function homeFilter() {
|
||||
mylog("开始解析筛选逻辑");
|
||||
// 如果已经解析过了,直接扔出缓存结果
|
||||
if (hasParsed) {
|
||||
return cachedFilters;
|
||||
}
|
||||
// 理论上壳子会先执行 home 再执行 homeFilter,如果未解析则返回空对象防错
|
||||
return {};
|
||||
}
|
||||
|
||||
function fixVodName(name = "") {
|
||||
return name.trim().split(" ").slice(1, -1).join("");
|
||||
}
|
||||
|
||||
// async function parseArticleList(html) {
|
||||
// // 确保已经加载了 cheerio
|
||||
// const $ = cheerio.load(html);
|
||||
// const list = [];
|
||||
|
||||
// // 针对每个视频卡片进行遍历
|
||||
// $('.video-img-box').each((index, element) => {
|
||||
// const titleEl = $(element).find('h3.title a.text');
|
||||
// const imgEl = $(element).find('img.zximg');
|
||||
// const descEl = $(element).find('p.sub-title');
|
||||
|
||||
// // 提取核心数据
|
||||
// const vod_id = titleEl.attr('href') || '';
|
||||
// const vod_name = titleEl.text().trim();
|
||||
|
||||
// // 关键点:提取自定义属性 z-image-loader-url
|
||||
// const vod_pic = imgEl.attr('z-image-loader-url') || '';
|
||||
|
||||
// // 提取备注(简介/日期)
|
||||
// const vod_remarks = descEl.text().trim();
|
||||
|
||||
// // 过滤掉无效数据(如果没有链接或标题则跳过)
|
||||
// if (vod_id && vod_name) {
|
||||
// list.push({
|
||||
// vod_id: vod_id, // 对应详情页接口的参数
|
||||
// vod_name: vod_name, // 列表显示的标题
|
||||
// vod_pic: vod_pic, // 封面图
|
||||
// vod_remarks: vod_remarks // 列表右上角的备注信息
|
||||
// });
|
||||
// }
|
||||
// });
|
||||
|
||||
// return JSON.stringify({ list: list });
|
||||
// }
|
||||
|
||||
// ⭐ 优化 1:分类页采用 Promise.all 并发多线程解析
|
||||
async function category(tid, pg, filter, extend) {
|
||||
try {
|
||||
if(!tid) return JSON.stringify({ list: [] });
|
||||
pg = pg || 1;
|
||||
// const url = baseUrl + tid + (extend.type || '') + `/${pg}/`;
|
||||
|
||||
const sort = extend.sort || '';
|
||||
|
||||
|
||||
let url = `${baseUrl}${tid}/${sort}/${pg}/`;
|
||||
mylog(`🚀 正在请求分类URL: ${url}`);
|
||||
|
||||
// if (tid.includes('article')) {
|
||||
// url = baseUrl + tid
|
||||
// mylog(`🚀 正在请求分类URL: ${url}`);
|
||||
|
||||
// const html = (await req(url)).content;
|
||||
// const $ = cheerio.load(html);
|
||||
// return parseArticleList(html)
|
||||
// }
|
||||
|
||||
|
||||
const html = (await req(url)).content;
|
||||
const $ = cheerio.load(html);
|
||||
|
||||
const videoElements = $('.col-6.col-sm-4.col-lg-3').toArray();
|
||||
|
||||
// 将每一项的解析包装成独立的异步任务(不阻塞彼此)
|
||||
const tasks = videoElements.map(async (el) => {
|
||||
const item = $(el).find('.video-img-box a');
|
||||
const href = item.attr('href') || '';
|
||||
|
||||
if (href.includes('/videos/')) {
|
||||
const vod_id = href;
|
||||
let vod_name = $(el).find('.title a').text().trim();
|
||||
vod_name = fixVodName(vod_name);
|
||||
|
||||
const watchCount = $(el).find('span[class^="interaction_watch_count_"]').text().trim() || '';
|
||||
const vod_remarks = watchCount && (watchCount + "播放");
|
||||
const vod_year = $(el).find('.label').text().trim();
|
||||
|
||||
let vod_pic = $(el).find('img.zximg').attr('z-image-loader-url') || '';
|
||||
|
||||
// 并发执行:所有的图片解密网络请求同时发出
|
||||
vod_pic = await getRealImgurl(vod_pic);
|
||||
|
||||
return {
|
||||
vod_id,
|
||||
vod_name,
|
||||
vod_pic,
|
||||
vod_year,
|
||||
vod_remarks,
|
||||
land: 1,
|
||||
ratio: 1.78
|
||||
};
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
// ⭐ 核心:静静等待所有并发任务一起完成
|
||||
const results = await Promise.all(tasks);
|
||||
// 过滤掉广告(null数据)
|
||||
let list = results.filter(item => item !== null);
|
||||
|
||||
// 分页逻辑
|
||||
let total = $('ul.dx-pager ').attr("data-rec-total");
|
||||
let perPageCount = $('ul.dx-pager ').attr("data-rec-per-page");
|
||||
const pagecount = Math.ceil(total / perPageCount);
|
||||
|
||||
mylog(`category 成功并发抓取有效视频数: ${list.length}`);
|
||||
|
||||
return JSON.stringify({ list, pagecount });
|
||||
|
||||
} catch (e) {
|
||||
mylog(e);
|
||||
return JSON.stringify({ list: [] });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 专用函数:解析“文章类型”页面
|
||||
*/
|
||||
// async function parseArticleDetail($, id) {
|
||||
// const vod_name = $('h1').text().trim();
|
||||
// // 优先读取 textarea 的 Markdown 内容
|
||||
// const vod_content = $('textarea').text().trim() || $('.text-content').text().trim();
|
||||
|
||||
// let play_url = "";
|
||||
|
||||
// // 逻辑:从 Markdown 内容中正则提取播放地址
|
||||
// const match = vod_content.match(/\(\/(videos|play)\/([^\/]+)\/\)/);
|
||||
// if (match) {
|
||||
// play_url = match[0].replace(/[()]/g, '');
|
||||
// } else {
|
||||
// // 兜底:搜索页面上的链接
|
||||
// const link = $('a[href*="/videos/"]').first().attr('href');
|
||||
// if (link) play_url = link;
|
||||
// }
|
||||
|
||||
// return JSON.stringify({
|
||||
// list: [{
|
||||
// vod_name: vod_name,
|
||||
// vod_content: vod_content,
|
||||
// vod_play_from: play_url ? "XP天堂" : "文章内容",
|
||||
// vod_play_url: play_url ? `立即播放$${play_url}` : ""
|
||||
// }]
|
||||
// });
|
||||
// }
|
||||
async function detail(vid) {
|
||||
try {
|
||||
const url = baseUrl + vid;
|
||||
const html = (await req(url)).content;
|
||||
const $ = cheerio.load(html);
|
||||
|
||||
// if (vid.includes('/article/')) {
|
||||
// return await parseArticleDetail($, vid);
|
||||
// }
|
||||
|
||||
let vod_name = $('h1.my-foldable-content').text().trim();
|
||||
let vod_pic = $('#player').attr('data-src') || '';
|
||||
vod_pic = await getRealImgurl(vod_pic);
|
||||
|
||||
// 🔍 1. 精准提取标签,确保没有任何前后空格
|
||||
let tagsArray = [];
|
||||
$('h5.tags a').each((i, el) => {
|
||||
let tagName = $(el).text().trim();
|
||||
if (tagName) tagsArray.push(tagName);
|
||||
});
|
||||
|
||||
let vod_actor = tagsArray.join('/');
|
||||
let vod_class = tagsArray.join(' ');
|
||||
|
||||
// 🔍 3. 备用保险:利用 CLICKER 规则,在简介里生成绝对能跳转的超链接
|
||||
let vod_content = "(todo)标签快捷搜索:\n";
|
||||
tagsArray.forEach(tag => {
|
||||
// 拼接蜂蜜专属的底层的 CLICKER 格式
|
||||
vod_content += `[a=cr:{"action":"category","key":"${tag}"}/]【${tag}】[/a] `;
|
||||
});
|
||||
|
||||
// 提取播放地址
|
||||
const regex = /https?:\/\/[^\s"'`]+\.m3u8(?:\?[^\s"'`]+)?/g;
|
||||
const match = html.match(regex);
|
||||
let hlsUrl = match ? match[0] : '';
|
||||
|
||||
const lines = ["hls线路"];
|
||||
const vod_play_from = lines.join("$$$");
|
||||
const playlistArray = [`正片\$${hlsUrl}`];
|
||||
const vod_play_url = playlistArray.join('$$$');
|
||||
|
||||
const watchCount = $('.video-info span[class^="interaction_watch_count_"]').text().trim().toUpperCase() || '';
|
||||
const favorite_count = $('#bind_collect_count').text().trim().toUpperCase() || '';
|
||||
let vod_remarks = watchCount && (watchCount + "播放");
|
||||
vod_remarks += favorite_count && (" | " + favorite_count + "收藏");
|
||||
|
||||
const back = {
|
||||
vod_id: vid,
|
||||
vod_remarks,
|
||||
vod_name: vod_name,
|
||||
vod_pic: vod_pic,
|
||||
vod_content: vod_content, // 👈 简介里会显示高亮可点击的标签
|
||||
vod_actor: vod_actor, // 👈 演员药丸标签(已剔除空格坑)
|
||||
vod_class: vod_class, // 👈 类型标签(多重兼容兜底)
|
||||
vod_play_from,
|
||||
vod_play_url
|
||||
};
|
||||
|
||||
return JSON.stringify({ list: [back] });
|
||||
} catch (e) {
|
||||
mylog(e);
|
||||
return JSON.stringify({ list: [] });
|
||||
}
|
||||
}
|
||||
// ⭐ 优化 2:搜索页同样采用 Promise.all 并发加速
|
||||
async function search(key, quick, page) {
|
||||
try {
|
||||
page = page || 1;
|
||||
|
||||
const url = baseUrl + `/search/${key}/${page}/`;
|
||||
mylog(`正在搜索: ${url}`);
|
||||
|
||||
const html = (await req(url)).content;
|
||||
const $ = cheerio.load(html);
|
||||
|
||||
const searchElements = $('.video-img-box').toArray();
|
||||
|
||||
const tasks = searchElements.map(async (el) => {
|
||||
const a = $(el).find('.img-box > a');
|
||||
const vod_id = $(a).attr('href') || '';
|
||||
const vod_name = $(a).find('img').attr('alt') || '';
|
||||
let vod_pic = $(a).find('img.zximg').attr('z-image-loader-url') || '';
|
||||
|
||||
if (vod_pic) {
|
||||
vod_pic = await getRealImgurl(vod_pic);
|
||||
}
|
||||
|
||||
const vod_remarks = $(el).find('.absolute-bottom-right .label').text().trim();
|
||||
|
||||
return {
|
||||
vod_id: vod_id,
|
||||
vod_name: fixVodName(vod_name),
|
||||
vod_pic: vod_pic,
|
||||
vod_remarks: vod_remarks
|
||||
};
|
||||
});
|
||||
|
||||
const list = await Promise.all(tasks);
|
||||
|
||||
// 分页逻辑
|
||||
let total = $('ul.dx-pager ').attr("data-rec-total");
|
||||
let perPageCount = $('ul.dx-pager ').attr("data-rec-per-page");
|
||||
const pagecount = Math.ceil(total / perPageCount);
|
||||
return JSON.stringify({
|
||||
list,
|
||||
pagecount
|
||||
});
|
||||
} catch (e) {
|
||||
mylog(e);
|
||||
return JSON.stringify({ list: [] });
|
||||
}
|
||||
}
|
||||
async function play(flag, id, vipFlags) {
|
||||
return JSON.stringify({
|
||||
parse: 0,
|
||||
url: id,
|
||||
header: { "User-Agent": UA, "Referer": baseUrl }
|
||||
});
|
||||
}
|
||||
async function getRealImgurl(imgurl) {
|
||||
try {
|
||||
if (!imgurl) return "";
|
||||
let res = await req(imgurl, {
|
||||
method: "get",
|
||||
headers: {
|
||||
"User-Agent": UA,
|
||||
"Referer": "https://wuabeza.gyqspl.cn/"
|
||||
},
|
||||
buffer: 2
|
||||
});
|
||||
|
||||
const encryptedBase64 = res.content;
|
||||
if (!encryptedBase64) return "";
|
||||
|
||||
let realImageBase64 = aesX(
|
||||
"AES/CBC/No",
|
||||
false,
|
||||
encryptedBase64,
|
||||
true,
|
||||
"f5d965df75336270",
|
||||
"97b60394abc2fbe1",
|
||||
true
|
||||
);
|
||||
|
||||
if (!realImageBase64) return "";
|
||||
|
||||
let ext = "jpeg";
|
||||
if (imgurl.toLowerCase().indexOf(".gif") !== -1) {
|
||||
ext = "gif";
|
||||
} else if (imgurl.toLowerCase().indexOf(".png") !== -1) {
|
||||
ext = "png";
|
||||
}
|
||||
|
||||
const back = "data:image/" + ext + ";base64," + realImageBase64;
|
||||
|
||||
// ⭐ 优化 3:【核心改动】删除了原先直接打印超长 base64 的 mylog,改为只打印长度,解放系统 CPU
|
||||
// mylog("[base64] ", back);
|
||||
|
||||
return back;
|
||||
} catch (e) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
export default { init, home, category, detail, search, play };
|
||||
Reference in New Issue
Block a user