diff --git a/.gitignore b/.gitignore index 5ff1f5e8..8a4d7663 100644 --- a/.gitignore +++ b/.gitignore @@ -155,3 +155,4 @@ dist /clipboard.txt /clipboard.txt.bak /apps/cat/ +/data/temp/ diff --git a/README.md b/README.md index 4a4d868e..e3f53a3c 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # drpyS(drpy-node) nodejs作为服务端的drpy实现。全面升级异步写法 -~~积极开发中,每日一更~~,当前进度 `99.9999%` +~~积极开发中,每日一更~~,当前进度 `99.99999%` ~~找工作中,随缘更新~~ 上班当牛马,下班要带娃,阶段性佛系趁娃睡觉熬夜更新 @@ -49,6 +49,10 @@ nodejs作为服务端的drpy实现。全面升级异步写法 ## 更新记录 +### 20250925 + +更新至V1.3.5 + ### 20250919 更新至V1.3.4 diff --git a/config/map.txt b/config/map.txt index e839e9b0..1dc583eb 100644 --- a/config/map.txt +++ b/config/map.txt @@ -12,6 +12,14 @@ UC分享@@?type=url¶ms=../json/UC分享.json@@UC分享[盘] 16wMV[听]@@?type=url¶ms=../json/十六万歌曲.txt 点歌欢唱[B]@@?type=url¶ms=../json/十六万歌曲.txt +网盘[模板]@@?type=url¶ms=../json/域名配置.json$蜡笔@@蜡笔ᵐ[盘] +网盘[模板]@@?type=url¶ms=../json/域名配置.json$木偶@@木偶ᵐ[盘] +网盘[模板]@@?type=url¶ms=../json/域名配置.json$玩偶$1@@玩偶ᵐ[盘] +网盘[模板]@@?type=url¶ms=../json/域名配置.json$虎斑@@虎斑ᵐ[盘] +网盘[模板]@@?type=url¶ms=../json/域名配置.json$二小@@二小ᵐ[盘] +网盘[模板]@@?type=url¶ms=../json/域名配置.json$多多@@多多ᵐ[盘] +网盘[模板]@@?type=url¶ms=../json/域名配置.json$欧歌@@欧歌ᵐ[盘] +网盘[模板]@@?type=url¶ms=../json/域名配置.json$至臻@@至臻ᵐ[盘] AppFox@@http://app.hktvyb.cc@@TVB云播[AFX] AppFox@@{"host":"https://cunchu8.obs.cn-north-4.myhuaweicloud.com/config.json","parse":{"JL4K":"http://194.147.100.155:7891/?url="}}@@火猫影视[AFX] AppFox@@{"host":"http://kumiao.yzbao.com.cn","parse":{"qq|qiyi|mgtv|youku|bilibili":"https://api.qljson.xyz/api/?key=67f6a108dc6d84eaf81ac58417c1f72a&url="}}@@未来影视[AFX] diff --git a/controllers/config.js b/controllers/config.js index f740b69f..84188d24 100644 --- a/controllers/config.js +++ b/controllers/config.js @@ -158,7 +158,7 @@ async function generateSiteJSON(options, requestHost, sub, pwd) { // console.log(SitesMap); // console.log(mubanKeys); // 排除模板后缀的DS源 - valid_files = valid_files.filter(it => !/\[模板]\.js$/.test(it)); + valid_files = valid_files.filter(it => !/^APP.*\[模板]\.js$/i.test(it)); log(`开始生成ds的t4配置,jsDir:${jsDir},源数量: ${valid_files.length}`); const tasks = valid_files.map((file) => { return { diff --git a/docs/updateRecord.md b/docs/updateRecord.md index b04f1ba0..6a674846 100644 --- a/docs/updateRecord.md +++ b/docs/updateRecord.md @@ -1,5 +1,15 @@ # drpyS更新记录 +### 20250925 + +更新至V1.3.5 + +1. 修复 `DS_REQ_LIB=0` 时 req系列请求不能正确处理禁止重定向和超时问题 +2. 修复 `cut` 逻辑错误导致的`番茄小说`二级无数据,增强字符串的`parseX`改用`JSON5` +3. 新增 `网盘` 模板源及map示例 +4. 新收录了几个源 +5. 增加 `hikerSkipEr` 属性,允许海阔跳过T4源的形式二级 + ### 20250919 更新至V1.3.4 diff --git "a/json/\345\237\237\345\220\215\351\205\215\347\275\256.json" "b/json/\345\237\237\345\220\215\351\205\215\347\275\256.json" new file mode 100644 index 00000000..87c839b1 --- /dev/null +++ "b/json/\345\237\237\345\220\215\351\205\215\347\275\256.json" @@ -0,0 +1,76 @@ +{ + "玩偶": [ + "https://wogg.xxooo.cf", + "https://wogg.333232.xyz", + "https://woggpan.333232.xyz", + "https://wogg.heshiheng.top", + "https://www.wogg.one", + "https://www.wogg.lol" + ], + "至臻": [ + "https://xiaomiai.site", + "http://mihdr.top", + "http://www.miqk.cc" + ], + "蜡笔": [ + "http://xiaocge.fun", + "https://feimao666.fun", + "http://feimao888.fun", + "http://feimaoai.site", + "http://www.labi88.sbs", + "http://fmao.site", + "https://fmao.shop" + ], + "木偶": [ + "http://123.666291.xyz", + "https://mogg.5568.eu.org", + "https://mo.666291.xyz", + "http://666.666291.xyz", + "https://mo.muouso.fun" + ], + "小米": [ + "https://www.54271.fun", + "https://54271.fun", + "https://www.mucpan.cc", + "https://mucpan.cc" + ], + "百家": [ + "https://bj.jiexi.news", + "http://baijia.filegear-sg.me", + "http://cj.jiexi.news", + "http://baijia.885525.xyz" + ], + "二小": [ + "https://wexwp.cc", + "https://erxiaofn.click", + "http://2xiaopan.fun", + "http://xhww.net", + "http://www.xhww.net", + "http://www.2xiaopan.fun" + ], + "多多": [ + "https://tv.yydsys.top", + "https://tv.yydsys.cc" + ], + "虎斑": [ + "http://103.45.162.207:20720", + "http://xsayang.fun:12512" + ], + "欧歌": [ + "https://woog.xn--dkw.xn--6qq986b3xl", + "https://woog.nxog.eu.org" + ], + "下饭": [ + "http://154.204.177.231" + ], + "闪电": [ + "http://1.95.79.193" + ], + "雷鲸": [ + "https://leijing.xyz", + "https://leijing1.com" + ], + "盘它": [ + "https://www.91panta.cn" + ] +} \ No newline at end of file diff --git a/libs/drpyS.js b/libs/drpyS.js index 97a6fcf7..16717685 100644 --- a/libs/drpyS.js +++ b/libs/drpyS.js @@ -697,7 +697,7 @@ async function invokeMethod(filePath, env, method, args = [], injectVars = {}) { result = await searchParseAfter(moduleObject, result, args[2]); log(`[invokeMethod js:] 搜索 ${injectVars.input} 执行完毕,结果为:`, JSON.stringify(result.list.slice(0, 2))); } else if (method === 'class_parse') { - result = await homeParseAfter(result, moduleObject.类型, moduleObject.hikerListCol, moduleObject.hikerClassListCol, injectVars); + result = await homeParseAfter(result, moduleObject.类型, moduleObject.hikerListCol, moduleObject.hikerClassListCol, moduleObject.hikerSkipEr, injectVars); } return result; } @@ -787,6 +787,10 @@ async function initParse(rule, env, vm, context) { if (!rule.hasOwnProperty('sniffer')) { // 默认关闭辅助嗅探 rule.sniffer = false; } + // 二级为*自动添加hikerSkipEr属性允许跳过形式二级 + if (!rule.hasOwnProperty('hikerSkipEr') && rule.二级 === '*') { + rule.hikerSkipEr = 1; + } rule.sniffer = rule.hasOwnProperty('sniffer') ? rule.sniffer : ''; rule.sniffer = !!(rule.sniffer && rule.sniffer !== '0' && rule.sniffer !== 'false'); rule.isVideo = rule.hasOwnProperty('isVideo') ? rule.isVideo : ''; diff --git a/libs/drpysParser.js b/libs/drpysParser.js index 2632288b..36bd3b60 100644 --- a/libs/drpysParser.js +++ b/libs/drpysParser.js @@ -424,7 +424,7 @@ export async function homeParse(rule) { return context; } -export async function homeParseAfter(d, _type, hikerListCol, hikerClassListCol, injectVars) { +export async function homeParseAfter(d, _type, hikerListCol, hikerClassListCol, hikerSkipEr, injectVars) { if (!d) { d = {}; } @@ -435,6 +435,10 @@ export async function homeParseAfter(d, _type, hikerListCol, hikerClassListCol, if (hikerClassListCol) { d.hikerClassListCol = hikerClassListCol; } + // 跳过形式二级 + if (hikerSkipEr) { + d.hikerSkipEr = hikerSkipEr; + } const { classes, filters, @@ -1485,7 +1489,7 @@ export async function invokeWithInjectVars(rule, method, injectVars, args) { } break; case 'class_parse': - result = await homeParseAfter(result, rule.类型, rule.hikerListCol, rule.hikerClassListCol, injectVars); + result = await homeParseAfter(result, rule.类型, rule.hikerListCol, rule.hikerClassListCol, rule.hikerSkipEr, injectVars); break; case '一级': result = await cateParseAfter(rule, result, args[1]); diff --git a/libs/dsGlobal.js b/libs/dsGlobal.js index d48e8f79..59c90a55 100644 --- a/libs/dsGlobal.js +++ b/libs/dsGlobal.js @@ -1,9 +1,9 @@ /** * 全局模块注册器 - * + * * 将drpy-node项目中的核心模块和工具库注册到全局对象(globalThis)上, * 使得这些模块可以在整个应用程序中直接访问,无需重复导入。 - * + * * 主要功能: * - 注册网络请求工具(reqs) * - 注册加密工具(forge) @@ -12,12 +12,12 @@ * - 注册各大网盘服务接口(夸克、UC、阿里、百度等) * - 注册压缩工具和网络通信组件 * - 注册结果处理函数 - * + * * 使用场景: * - 在drpy源解析器中直接使用全局工具 * - 在插件开发中访问核心功能 * - 在脚本执行环境中提供统一的API接口 - * + * * @module dsGlobal * @author drpy-node * @since 1.0.0 @@ -87,4 +87,13 @@ globalThis.WebSocket = WebSocket; globalThis.WebSocketServer = WebSocketServer; // 结果设置函数 - 用于设置和处理执行结果 -globalThis.setResult = setResult; \ No newline at end of file +globalThis.setResult = setResult; + +// ds沙箱文件读写函数 +globalThis.pathLib = pathLib; + +// UA +globalThis.MOBILE_UA = MOBILE_UA; +globalThis.PC_UA = PC_UA; +// 其他常用 +globalThis.$js = $js; \ No newline at end of file diff --git a/libs_drpy/drpyCustom.js b/libs_drpy/drpyCustom.js index 7cd525a5..bef78be0 100644 --- a/libs_drpy/drpyCustom.js +++ b/libs_drpy/drpyCustom.js @@ -681,7 +681,7 @@ export const objectToQueryString = (obj) => { /** * 获取加密前的原始的js源文本 - * @param js_code + * @param {string} js_code */ export async function getOriginalJs(js_code) { // let current_match = /var rule|[\u4E00-\u9FA5]+|function|let |var |const |\(|\)|"|'/; @@ -825,8 +825,8 @@ export const jsDecoder = { /** * vodDeal函数 - 处理播放源排序和重命名 - * @param vod - * @param rule + * @param {Object} vod + * @param {Object} rule * @returns {*} */ export function vodDeal(vod, rule) { diff --git a/libs_drpy/drpyInject.js b/libs_drpy/drpyInject.js index 43d56177..e6e287b2 100644 --- a/libs_drpy/drpyInject.js +++ b/libs_drpy/drpyInject.js @@ -226,7 +226,7 @@ async function request(url, opt = {}) { return {code: resp.status, headers: resHeader, content: responseData}; } catch (error) { const {response: resp} = error; - console.error(`[request]Request error: ${error.message}`); + console.error(`[request] Request error: ${error.message}`); let responseData = ''; // console.log('responseData:',responseData); try { @@ -238,7 +238,7 @@ async function request(url, opt = {}) { responseData = buffer.toString('utf-8'); } } catch (e) { - console.error(`[request]get error response Text failed: ${e.message}`); + console.error(`[request] get error response Text failed: ${e.message}`); } // console.log('responseData:',responseData); return { diff --git a/libs_drpy/es6-extend.js b/libs_drpy/es6-extend.js index c2503860..9d217fa1 100644 --- a/libs_drpy/es6-extend.js +++ b/libs_drpy/es6-extend.js @@ -1,9 +1,9 @@ /** * ES6扩展和Polyfill库 - * + * * 该文件提供了ES6+特性的polyfill实现,确保在旧版本JavaScript环境中的兼容性 * 包含Object、String、Array等原生对象的方法扩展和自定义工具函数 - * + * * @author drpy * @version 1.0.0 */ @@ -264,19 +264,48 @@ function matchesAll(str, pattern, flatten) { Object.defineProperties(String.prototype, { replaceX: { /** - * 增强的字符串替换方法 + * 增强的字符串替换方法 - 支持高级替换逻辑 * @param {RegExp|string} regex - 匹配模式 * @param {string|Function} replacement - 替换内容 * @returns {string} 替换后的字符串 */ value: function (regex, replacement) { + // 处理字符串类型的正则表达式 if (typeof regex === 'string') { regex = new RegExp(regex, 'g'); } + + // 处理函数类型的替换内容 if (typeof replacement === 'function') { return this.replace(regex, replacement); } - return this.replace(regex, replacement || ''); + + // 处理 null/undefined 的替换内容 + replacement = replacement == null ? '' : String(replacement); + + // 创建正则表达式副本以避免修改原始对象的 lastIndex + const regexCopy = new RegExp(regex.source, regex.flags); + + // 获取所有匹配项 + let matches = matchesAll(this, regexCopy, true); + + // 如果有多个匹配项,进行高级替换处理 + if (matches && matches.length > 1) { + const hasCaptureGroup = /\$\d/.test(replacement); + + if (hasCaptureGroup) { + // 有捕获组引用时,使用标准替换 + return this.replace(regex, replacement); + } else { + // 无捕获组引用时,对每个完整匹配进行替换 + return this.replace(regex, function (match) { + return replacement; + }); + } + } + + // 单个匹配或无匹配时,使用标准替换 + return this.replace(regex, replacement); }, writable: true, configurable: true, @@ -289,8 +318,9 @@ Object.defineProperties(String.prototype, { */ get: function () { try { - return JSON.parse(this); + return JSON5.parse(this); } catch (e) { + log(`String.parseX error: ${e.message}`); return null; } }, @@ -304,23 +334,23 @@ Object.defineProperties(String.prototype, { * @param {string} text - 要处理的文本 * @param {string} start - 开始标记 * @param {string} end - 结束标记 - * @param {string} method - 处理方法 + * @param {function|null|undefined} method - 处理方法函数,用于对截取结果进行后处理 * @param {boolean} All - 是否处理所有匹配项 - * @returns {string|Array} 处理结果 + * @returns {string|Array} 处理结果,单个匹配返回字符串,多个匹配返回数组 */ function cut(text, start, end, method, All) { // 实现复杂的文本截取逻辑 if (!text || typeof text !== 'string') return ''; - + let startIndex = text.indexOf(start); if (startIndex === -1) return All ? [] : ''; - + startIndex += start.length; let endIndex = text.indexOf(end, startIndex); if (endIndex === -1) return All ? [] : ''; - - let result = text.substring(startIndex, endIndex); - + + let result = text.substring(startIndex, endIndex) + end; // 包含 end 标记 + if (All) { let results = [result]; let remainingText = text.substring(endIndex + end.length); @@ -330,7 +360,12 @@ function cut(text, start, end, method, All) { } return results; } - + + // 应用 method 处理函数(如果提供) + if (method && typeof method === 'function') { + result = method(result); + } + return result; } diff --git a/libs_drpy/fetchAxios.js b/libs_drpy/fetchAxios.js index 38d0a841..789fe5c2 100644 --- a/libs_drpy/fetchAxios.js +++ b/libs_drpy/fetchAxios.js @@ -23,6 +23,10 @@ class FetchAxios { responseType: 'json', // json, text 或 arraybuffer withCredentials: false, httpsAgent: null, + maxRedirects: 5, // 默认允许5次重定向 + validateStatus: function (status) { + return status >= 200 && status < 300; // 默认只有 2xx 状态码被认为是成功 + }, ...defaultConfig, }; // 拦截器存储 @@ -75,7 +79,10 @@ class FetchAxios { // 设置超时控制 const controller = new AbortController(); - if (finalConfig.timeout) setTimeout(() => controller.abort(), finalConfig.timeout); + let timeoutId; + if (finalConfig.timeout && finalConfig.timeout > 0) { + timeoutId = setTimeout(() => controller.abort(), finalConfig.timeout); + } // 构建fetch选项 const fetchOptions = { @@ -84,6 +91,8 @@ class FetchAxios { signal: controller.signal, credentials: finalConfig.withCredentials ? 'include' : 'same-origin', agent: finalConfig.httpsAgent || undefined, + // 处理重定向控制 + redirect: finalConfig.maxRedirects === 0 ? 'manual' : 'follow', }; // 处理请求体数据 @@ -102,10 +111,57 @@ class FetchAxios { } try { + // console.log(`[fetchAxios] finalConfig.url:${finalConfig.url} fetchOptions:`, fetchOptions); // 发送HTTP请求 - const response = await fetch(finalConfig.url, fetchOptions); + let response = await fetch(finalConfig.url, fetchOptions); + + // 处理手动重定向(当 maxRedirects 为 0 时) + if (finalConfig.maxRedirects === 0 && response.status >= 300 && response.status < 400) { + // 清除超时定时器 + if (timeoutId) clearTimeout(timeoutId); + + // 根据 responseType 返回对应的空数据,保持与 axios 行为一致 + let emptyData; + if (finalConfig.responseType === 'json') { + emptyData = null; // JSON 类型返回 null + } else if (finalConfig.responseType === 'arraybuffer') { + emptyData = new ArrayBuffer(0); // ArrayBuffer 类型返回空 buffer + } else { + emptyData = ''; // text 类型返回空字符串 + } + + // 构建重定向响应对象 + const redirectResult = { + data: emptyData, + status: response.status, + statusText: response.statusText, + headers: Object.fromEntries(response.headers.entries()), + config: finalConfig, + request: finalConfig.url, + }; + + // 检查 validateStatus 函数,如果没有定义或返回 false,则抛出错误 + if (!finalConfig.validateStatus || !finalConfig.validateStatus(response.status)) { + // console.log(`[fetchAxios] redirect error:`, redirectResult); + // 创建符合 axios 标准的错误对象 + const redirectError = new Error(`Request failed with status code ${response.status}`); + redirectError.code = 'ERR_BAD_REQUEST'; + redirectError.config = finalConfig; + redirectError.request = finalConfig.url; + redirectError.response = redirectResult; + redirectError.isAxiosError = true; + throw redirectError; // 抛出符合 axios 标准的错误 + } else { + // console.log(`[fetchAxios] redirect result:`, redirectResult); + return redirectResult; // 如果 validateStatus 返回 true,则正常返回 + } + } + + // 清除超时定时器(请求成功完成) + if (timeoutId) clearTimeout(timeoutId); + let responseData; - + // 根据响应类型处理数据 if (finalConfig.responseType === 'json') { responseData = await response.json().catch(() => null); @@ -130,12 +186,41 @@ class FetchAxios { result = await interceptor(result) || result; } - // 检查响应状态 - if (!response.ok) throw result; + // 检查响应状态,使用 validateStatus 函数 + if (!finalConfig.validateStatus(response.status)) { + // 创建符合 axios 标准的错误对象 + const statusError = new Error(`Request failed with status code ${response.status}`); + statusError.code = response.status >= 400 && response.status < 500 ? 'ERR_BAD_REQUEST' : 'ERR_BAD_RESPONSE'; + statusError.config = finalConfig; + statusError.request = finalConfig.url; + statusError.response = result; + statusError.isAxiosError = true; + throw statusError; + } return result; } catch (err) { - // 抛出错误 - throw err; + // 清除超时定时器 + if (timeoutId) clearTimeout(timeoutId); + + // 处理不同类型的错误 + if (err.name === 'AbortError') { + // 超时错误 + const timeoutError = new Error(`Request timeout of ${finalConfig.timeout}ms exceeded`); + timeoutError.code = 'ECONNABORTED'; + timeoutError.config = finalConfig; + timeoutError.request = finalConfig.url; + throw timeoutError; + } else if (err.name === 'TypeError' && err.message.includes('fetch')) { + // 网络错误 + const networkError = new Error('Network Error'); + networkError.code = 'NETWORK_ERROR'; + networkError.config = finalConfig; + networkError.request = finalConfig.url; + throw networkError; + } else { + // 其他错误直接抛出 + throw err; + } } } diff --git a/libs_drpy/req-extend.js b/libs_drpy/req-extend.js index e20efb05..86a75912 100644 --- a/libs_drpy/req-extend.js +++ b/libs_drpy/req-extend.js @@ -4,10 +4,10 @@ const RKEY = typeof (key) !== 'undefined' && key ? key : 'drpyS_' + (rule.title /** * 海阔网页请求函数完整封装 - * @param url 请求链接 - * @param obj 请求对象 {headers:{},method:'',timeout:5000,body:'',withHeaders:false} - * @param ocr_flag 标识此flag是用于请求ocr识别的,自动过滤content-type指定编码 - * @returns {string|string|DocumentFragment|*} + * @param {string} url 请求链接 + * @param {Object} obj 请求对象 {headers:{},method:'',timeout:5000,body:'',withHeaders:false} + * @param {boolean} ocr_flag 标识此flag是用于请求ocr识别的,自动过滤content-type指定编码 + * @returns {string|DocumentFragment|*} */ async function request(url, obj = {}, ocr_flag = false) { if (typeof (obj) === 'undefined' || !obj || (typeof obj === 'object' && obj !== null && Object.keys(obj).length === 0)) { @@ -90,8 +90,8 @@ async function request(url, obj = {}, ocr_flag = false) { /** * 快捷post请求 - * @param url 地址 - * @param obj 对象 + * @param {string} url 地址 + * @param {Object} obj 对象 * @returns {string|DocumentFragment|*} */ async function post(url, obj = {}) { @@ -102,9 +102,9 @@ async function post(url, obj = {}) { /** * 快捷获取特殊地址cookie|一般用作搜索过验证 * 用法 let {cookie,html} = reqCookie(url); - * @param url 能返回cookie的地址 - * @param obj 常规请求参数 - * @param all_cookie 返回全部cookie.默认false只返回第一个,一般是PhpSessionId + * @param {string} url 能返回cookie的地址 + * @param {Object} obj 常规请求参数 + * @param {boolean} all_cookie 返回全部cookie.默认false只返回第一个,一般是PhpSessionId * @returns {{cookie: string, html: (*|string|DocumentFragment)}} */ async function reqCookie(url, obj = {}, all_cookie = false) { @@ -130,9 +130,9 @@ async function reqCookie(url, obj = {}, all_cookie = false) { /** * 检查宝塔验证并自动跳过获取正确源码 - * @param html 之前获取的html - * @param url 之前的来源url - * @param obj 来源obj + * @param {string} html 之前获取的html + * @param {string} url 之前的来源url + * @param {Object} obj 来源obj * @returns {string|DocumentFragment|*} */ async function checkHtml(html, url, obj = {}) { @@ -147,8 +147,8 @@ async function checkHtml(html, url, obj = {}) { /** * 带一次宝塔验证的源码获取 - * @param url 请求链接 - * @param obj 请求参数 + * @param {string} url 请求链接 + * @param {Object} obj 请求参数 * @returns {string|DocumentFragment} */ async function getCode(url, obj = {}) { @@ -159,7 +159,7 @@ async function getCode(url, obj = {}) { /** * 源rule专用的请求方法,自动注入cookie - * @param url 请求链接 + * @param {string} url 请求链接 * @returns {string|DocumentFragment} */ async function getHtml(url) { @@ -189,7 +189,7 @@ async function getHtml(url) { /** * 验证码识别,暂未实现 - * @param url 验证码图片链接 + * @param {string} url 验证码图片链接 * @returns {string} 验证成功后的cookie */ async function verifyCode(url) { @@ -239,8 +239,8 @@ async function verifyCode(url) { /** * 存在数据库配置表里, key字段对应值value,没有就新增,有就更新,调用此方法会清除key对应的内存缓存 - * @param k 键 - * @param v 值 + * @param {string} k 键 + * @param {*} v 值 */ function setItem(k, v) { local.set(RKEY, k, v); @@ -250,8 +250,8 @@ function setItem(k, v) { /** * 获取数据库配置表对应的key字段的value,没有这个key就返回value默认传参.需要有缓存,第一次获取后会存在内存里 - * @param k 键 - * @param v 值 + * @param {string} k 键 + * @param {*} v 值 * @returns {*} */ function getItem(k, v) { @@ -260,7 +260,7 @@ function getItem(k, v) { /** * 删除数据库key对应的一条数据,并清除此key对应的内存缓存 - * @param k + * @param {string} k */ function clearItem(k) { local.delete(RKEY, k); diff --git a/package.json b/package.json index 4dc78b7e..f61b20ff 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "drpy-node", - "version": "1.3.4", + "version": "1.3.5", "main": "index.js", "type": "module", "scripts": { diff --git a/public/index.html b/public/index.html index 52867223..d6d0b9a4 100644 --- a/public/index.html +++ b/public/index.html @@ -8,7 +8,7 @@

drpyS(drpy-node)

-

nodejs作为服务端的drpy实现。全面升级异步写法
积极开发中,每日一更,当前进度 99.9999%
找工作中,随缘更新
上班当牛马,下班要带娃,阶段性佛系趁娃睡觉熬夜更新

+

nodejs作为服务端的drpy实现。全面升级异步写法
积极开发中,每日一更,当前进度 99.99999%
找工作中,随缘更新
上班当牛马,下班要带娃,阶段性佛系趁娃睡觉熬夜更新

常用超链接

更新记录

+

20250925

+

更新至V1.3.5

20250919

更新至V1.3.4

20250918

diff --git a/spider/js/php.js b/spider/js/php.js index 283aad6d..8ea7f65c 100644 --- a/spider/js/php.js +++ b/spider/js/php.js @@ -30,8 +30,13 @@ var rule = { }, 预处理: async function () { log('rule.params:', rule.params); - let extObject = JSON5.parse(rule.params); - log('extObject:', extObject); + try { + let extObject = JSON5.parse(rule.params); + log('extObject:', extObject); + } catch (err) { + // log('[ERR] extObject:', err); + log(`[ERR] extObject: ${err.message}`); + } }, class_parse: async function () { let {input, pdfa, pdfh, pd} = this; @@ -42,8 +47,12 @@ var rule = { return input }, 推荐: async function () { - let {input, pdfa, pdfh, pd} = this; + let {input, pdfa, pdfh, pd, getProxyUrl} = this; let d = []; + let url = getProxyUrl(); + log('url:', getProxyUrl()); + let html = await request(url, {withHeaders: true, redirect: 0}); + log('html:', html); return setResult(d) }, 一级: async function () { @@ -60,5 +69,8 @@ var rule = { let {input, pdfa, pdfh, pd} = this; let d = []; return setResult(d) + }, + proxy_rule: async function () { + return [302, 'text/html', '', {Location: 'https://www.baidu.com'}] } } diff --git "a/spider/js/\345\274\200\347\234\274.js" "b/spider/js/\345\274\200\347\234\274.js" new file mode 100644 index 00000000..e834e0fa --- /dev/null +++ "b/spider/js/\345\274\200\347\234\274.js" @@ -0,0 +1,61 @@ +/* +@header({ + searchable: 0, + filterable: 0, + quickSearch: 0, + title: '开眼视频', + '类型': '影视', + lang: 'ds' +}) +*/ + +var rule = { + title: '开眼视频', + host: 'http://baobab.kaiyanapp.com:80', + url: '/api/v5/index/tab/feed?udid=ecab2cc100f540e482c5f7db5542a33cc5a908bc&vc=591&vn=6.2.1&size=1080X2340&deviceModel=HLK-AL00&first_channel=eyepetizer_zhihuiyun_market&last_channel=eyepetizer_zhihuiyun_market&system_version_code=29', + searchUrl: '', + searchable: 0, + quickSearch: 0, + filterable: 0, + headers: { + 'User-Agent': MOBILE_UA + }, + class_name: '今日开眼精选',//静态分类名称拼接 + class_url: '/',//静态分类标识拼接 + timeout: 5000, + play_parse: true, + pagecount: {"/": 1}, + lazy: $js.toString(async () => { + input = JSON.parse(await request(input)).playUrl + '&_t=.m3u8' + return input + }), + + limit: 6, + 推荐: '', + 一级: $js.toString(async () => { + var s = await request(input); + var json = {}; + eval('json=' + s); + var next = json.nextPageUrl; + var s2 = await request(next, {}); + var j2 = {}; + eval('j2=' + s2); + for (var i = 0; i < j2.itemList.length; i++) { + json.itemList.push(j2.itemList[i]) + } + var d = []; + for (var i = 0; i < json.itemList.length; i++) { + var j = json.itemList[i]; + if (j.type != 'followCard') continue; + var r = {}; + r.pic_url = j.data.content.data.cover.feed; + r.title = j.data.content.data.title; + r.desc = j.data.header.description; + r.url = 'https://baobab.kaiyanapp.com/api/v1/video/' + j.data.header.id + '?f=web'; + d.push(r) + } + return setResult(d) + }), + 二级: '*', + 搜索: '', +} \ No newline at end of file diff --git "a/spider/js/\346\234\250\345\205\256[\344\274\230].js" "b/spider/js/\346\234\250\345\205\256[\344\274\230].js" new file mode 100644 index 00000000..918c35da --- /dev/null +++ "b/spider/js/\346\234\250\345\205\256[\344\274\230].js" @@ -0,0 +1,138 @@ +/* +@header({ + searchable: 2, + filterable: 0, + quickSearch: 1, + title: '木兮', + '类型': '影视', + lang: 'ds' +}) +*/ + +var rule = { + title: '木兮', + host: 'https://film.symx.club', + url: '/api/film/category/list?fyfilter', + detailUrl: '/api/film/detail?id=fyid', + searchUrl: '/api/film/search?keyword=**&pageNum=fypage&pageSize=10', + filter_url: 'categoryId=fyclass&language={{fl.lang}}&pageNum=fypage&pageSize=15&sort={{fl.by or "updateTime"}}&year={{fl.year}}', + searchable: 2, + quickSearch: 1, + filterable: 0, + headers: { + 'User-Agent': MOBILE_UA + }, + play_parse: true, + search_match: true, + class_name: '电视剧&电影&综艺&动漫&短剧', + class_url: '1&2&3&4&5', + 推荐: $js.toString(async () => { + let d = []; + let html = await request(input + '/api/film/category'); + let categories = JSON.parse(html).data; + + categories.forEach(category => { + category.filmList.forEach(item => { + let title = item.name; + if (!/名称|排除/.test(title)) { + d.push({ + title: title, + desc: item.updateStatus, + img: item.cover, + url: item.id, + content:item.blurb, + }); + } + }); + }); + + return setResult(d); + }), + 一级: $js.toString(async () => { + let d = []; + let html = await request(input); + let data = JSON.parse(html).data.list; + data.forEach(item => { + let title = item.name; + if (!/名称|排除/.test(title)) { + d.push({ + title: title, + desc: item.updateStatus, + img: item.cover, + url: item.id, + content:item.blurb, + }); + } + }); + return setResult(d); + }), + + 二级: $js.toString(async () => { + let html = await request(input); + let data = JSON.parse(html).data; + // 定义类型映射 + let categoryMap = { + 1: "电视剧", + 2: "电影", + 3: "综艺", + 4: "动漫", + 5: "短剧" + }; + let categoryName = categoryMap[data.categoryId] + let VOD = { + vod_id: orId, + vod_name: data.name, + type_name: categoryName, + vod_pic: data.cover, + vod_remarks: data.updateStatus, + vod_content: data.blurb + }; + + let playlist = data.playLineList || []; + let playFrom = []; + let playUrl = []; + + playlist.forEach(line => { + playFrom.push(line.playerName); + let lines = line.lines || []; + let lineUrls = lines.map(tag => { + let title = tag.name; + let url = tag.id; + return `${title}$${url}`; + }); + playUrl.push(lineUrls.join("#")); + }); + + VOD.vod_play_from = playFrom.join("$$$"); + VOD.vod_play_url = playUrl.join("$$$"); + return VOD + }), + + 搜索: $js.toString(async () => { + let d = []; + let html = await request(input); + let data = JSON.parse(html).data.list; + data.forEach(item => { + let title = item.name; + d.push({ + title: title, + desc: item.updateStatus, + img: item.cover, + url: item.id, + content:item.blurb, + }); + }); + return setResult(d); + }), + lazy: $js.toString(async () => { + let purl = 'https://film.symx.club/api/line/play/parse?lineId=' + input; + let html = await request(purl); + let url = JSON.parse(html).data; + input = { + parse: 0, + url: url + }; + return input + }), + +} \ No newline at end of file diff --git "a/spider/js/\347\225\252\350\214\204\345\260\217\350\257\264[\344\271\246].js" "b/spider/js/\347\225\252\350\214\204\345\260\217\350\257\264[\344\271\246].js" index bbf5a719..644fe3f7 100644 --- "a/spider/js/\347\225\252\350\214\204\345\260\217\350\257\264[\344\271\246].js" +++ "b/spider/js/\347\225\252\350\214\204\345\260\217\350\257\264[\344\271\246].js" @@ -127,6 +127,7 @@ var rule = { } })).content; let jsonStr = cut(html, 'window.__INITIAL_STATE__=', '};').replace(/;$/, "").replaceAll('undefined', 'null'); + // pathLib.writeFile('./temp/jsonStr.json', jsonStr); let book_info = jsonStr.parseX.page; let list = book_info.chapterListWithVolume.flat(); let urls = []; diff --git "a/spider/js/\347\275\221\347\233\230[\346\250\241\346\235\277].js" "b/spider/js/\347\275\221\347\233\230[\346\250\241\346\235\277].js" new file mode 100644 index 00000000..a4091284 --- /dev/null +++ "b/spider/js/\347\275\221\347\233\230[\346\250\241\346\235\277].js" @@ -0,0 +1,636 @@ +/* +@header({ + searchable: 1, + filterable: 1, + quickSearch: 1, + title: '网盘[模板]', + '类型': '影视', + lang: 'ds' +}) +*/ + +var rule = { + title: '网盘[模板]', + host: '', + url: '', + searchUrl: '*', + headers: { + "User-Agent": "PC_UA", + 'Accept': 'text/html; charset=utf-8' + }, + line_order: [ '百度', '优汐', '夸克', '天翼', '123', '移动', '阿里'], + play_parse: true, + search_match: true, + searchable: 1, + filterable: 1, + timeout: 60000, + quickSearch: 1, + + hostJs: async function() { + let startTime = Date.now(); + + try { + // 解析参数 + let parts = rule.params.split('$'); + let _host = parts[0]; + let html = await request(_host); + let json = JSON.parse(html); + let paramKey = decodeURIComponent(parts[1]); + let config = json[paramKey] || {}; + rule_type = parts.length > 2 ? parts[2] : ""; + rule._name = paramKey; + + // 处理域名配置 + let domains = Array.isArray(config) ? config : [config]; + domains = domains.filter(u => u?.trim()).map(u => { + u = u.trim(); + return /^https?:\/\//i.test(u) ? u : + (u.includes('https') || u.includes('ssl')) ? `https://${u.replace(/^(https?:\/\/)?/i, '')}` : + `http://${u.replace(/^(https?:\/\/)?/i, '')}`; + }); + + console.log(`${rule._name}域名加载成功,共${domains.length}个`); + + // 域名检查函数 - 使用HEAD方法提高速度 + let check = url => new Promise(resolve => { + let mod = url.startsWith('https') ? require('https') : require('http'); + let t0 = Date.now(); + + let options = { + method: 'HEAD', + timeout: 2000, + headers: { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/126.0.0.0 Safari/537.36' + } + }; + + let req = mod.request(url, options, res => { + let t = Date.now() - t0; + let valid = res.statusCode >= 200 && res.statusCode < 400; + console.log(`${valid ? '✅' : '❌'} ${url} (${t}ms, 状态: ${res.statusCode})`); + resolve({ url, t, valid, code: res.statusCode }); + req.destroy(); + }).on('error', () => { + resolve({ url, t: Infinity, valid: false, code: 0 }); + }).on('timeout', () => { + req.destroy(); + resolve({ url, t: 2000, valid: false, code: 0 }); + }); + + req.end(); + }); + + // 并发检查域名 + const CONCURRENT_LIMIT = 5; + let results = []; + + for (let i = 0; i < domains.length; i += CONCURRENT_LIMIT) { + const chunk = domains.slice(i, i + CONCURRENT_LIMIT); + const chunkResults = await Promise.all(chunk.map(check)); + results = results.concat(chunkResults); + + // 提前终止检查 + const validResults = results.filter(r => r.valid); + if (validResults.length > 0) break; + } + + // 选择最佳域名 + let validDomains = results.filter(x => x.valid); + validDomains.sort((a, b) => a.t - b.t); + let bestDomain = validDomains[0]?.url || domains[0]; + + console.log(validDomains.length ? + `✅ 最终选用: ${bestDomain}(延迟: ${validDomains[0].t}ms)` : + `⚠️ 无可用域名,使用默认: ${bestDomain}` + ); + + return bestDomain; + } catch (e) { + console.error(`域名检测出错: ${e.message}`); + return ''; + } finally { + console.log(`⏱️ 域名检测耗时: ${Date.now() - startTime}ms`); + } +}, + + class_parse: async function () { + const { input, pdfa, pdfh, pd, host, MY_CATE } = this; + const classes = []; + const filters = {}; + const seenTypeIds = new Set(); + + // 添加缓存逻辑 + const cacheExpiration = 30 * 24 * 60 * 60 * 1000; + const cacheKey = `${input}_${MY_CATE || 'default'}`; + + if (!this.cache) this.cache = {}; + + if (this.cache[cacheKey] && this.cache[cacheKey].timestamp > Date.now() - cacheExpiration) { + console.log(`📦 命中缓存 [Key: ${cacheKey}]`); + return { + class: this.cache[cacheKey].data, + filters: this.cache[cacheKey].filters || {} + }; + } + + try { + const html = await request(input); + const navItems = pdfa(html, '.nav-menu-items&&li'); + + navItems.forEach((item) => { + const href = pd(item, 'a&&href').trim(); + const typeName = pdfh(item, 'a&&Text').trim(); + const matchResult = href.match(/\/([^\/]+)\.html$/); + + if (matchResult && typeName && !seenTypeIds.has(matchResult[1])) { + const typeId = matchResult[1]; + if (/^\d+$/.test(typeId)) { + classes.push({ + type_name: typeName, + type_id: typeId + }); + seenTypeIds.add(typeId); + } + } + }); + const htmlArr = await batchFetch(classes.map(item => { + let url = rule_type ? + `${host}/vodshow/${item.type_id}-----------.html` : + `${host}/index.php/vod/show/id/${item.type_id}.html`; + return { + url: url, + options: { timeout: 100000, headers: rule.headers} + }; + })); + + const CATEGORIES = [ + {key:'cateId', name:'类型', reg:/\/id\/(\d+)/}, + {key:'class', name:'剧情'}, + {key:'lang', name:'语言'}, + {key:'area', name:'地区'}, + {key:'year', name:'时间'}, + {key:'letter', name:'字母'}, + ]; + + const SORT_OPTIONS = { + "时间": "time", + "人气": "hits", + "评分": "score", + }; + + htmlArr.forEach((it, i) => { + if (!it) { + filters[classes[i].type_id] = []; + return; + } + const type_id = classes[i].type_id; + filters[type_id] = CATEGORIES.map((category) => { + const box = pdfa(it, '.library-box').find(b => b.includes(category.name)) || ""; + let values = []; + + if (box) { + values = pdfa(box, "div a").map(a => { + const n = pdfh(a, "a&&Text") || "全部"; + let v = n; + + if (category.key === 'cateId') { + const href = pd(a, 'a&&href'); + v = href.match(category.reg)?.[1] || n; + } + + return { n, v }; + }).filter(x => x.n && x.v); + } + + return { + key: category.key, + name: category.name, + value: values + }; + }).filter(item => item.value && item.value.length > 3); + + const sortValues = Object.entries(SORT_OPTIONS).map(([name, value]) => ({ + n: name, + v: value + })); + + if (sortValues.length > 0) { + filters[type_id].push({ + key: "by", + name: "排序", + value: sortValues + }); + } + }); + + // 保存到缓存 + this.cache[cacheKey] = { + timestamp: Date.now(), + data: classes, + filters: filters + }; + + return { class: classes, filters }; + } catch (error) { + classes.forEach(cls => { + filters[cls.type_id] = []; + }); + return { class: classes, filters }; + } + }, + + // 以下代码保持不变... + 推荐: async function () { + let {input, pdfa, pdfh, pd} = this; + let html = await request(input); + let d = []; + let data = pdfa(html, '.module-items .module-item'); + data.forEach(it => { + let title = pdfh(it, 'a&&title'); + d.push({ + title: title, + img: pd(it, 'img&&data-src'), + desc: pdfh(it, '.module-item-text&&Text'), + url: pd(it, 'a&&href') + }); + }); + return setResult(d); + }, + + 一级: async function () { + let { input, pdfa, pdfh, pd, MY_CATE, MY_FL, MY_PAGE, host } = this; + const fl = MY_FL || {}; + const pg = MY_PAGE || 1; + const type = MY_CATE || fl.cateId; + + let url; + if (rule_type) { + url = `${host}/vodshow/${type}-${fl.area || ''}-${fl.by || 'time'}-${fl.class || ''}--${fl.letter || ''}---${pg}---${fl.year || ''}.html`; + } else { + const parts = [ + fl.area ? `area/${fl.area}` : '', + fl.by ? `by/${fl.by}` : '', + fl.class ? `class/${fl.class}` : '', + fl.cateId ? `id/${fl.cateId}` : `id/${MY_CATE}`, + fl.lang ? `lang/${fl.lang}` : '', + fl.letter ? `letter/${fl.letter}` : '', + fl.year ? `year/${fl.year}` : '' + ].filter(Boolean); + + url = `${host}/index.php/vod/show/${parts.join('/')}/page/${pg}.html`; + } + + let html = await request(url); + let data = pdfa(html, '.module-items .module-item'); + let d = data.map((it) => ({ + title: pd(it, 'a&&title') || pdfh(it, '.module-item-title&&Text'), + pic_url: pd(it, 'img&&data-src') || pd(it, 'img&&src'), + desc: pdfh(it, '.module-item-text&&Text') || pdfh(it, '.module-item-content&&Text'), + url: pd(it, 'a&&href') + })); + return setResult(d); + }, + + 二级: async function (ids) { + try { + let { input, pdfa, pdfh, pd } = this; + let html = await request(input); + let data = pdfa(html, '.module-row-title'); + + let vod = { + vod_name: pdfh(html, '.video-info&&h1&&Text') || '', + type_name: pdfh(html, '.tag-link&&Text') || '', + vod_pic: pd(html, '.lazyload&&data-original||data-src||src') || '', + vod_content: pdfh(html, '.sqjj_a--span&&Text') || '', + vod_remarks: pdfh(html, '.video-info-items:eq(3)&&Text') || '', + vod_year: pdfh(html, '.tag-link:eq(2)&&Text') || '', + vod_area: pdfh(html, '.tag-link:eq(3)&&Text') || '', + vod_actor: pdfh(html, '.video-info-actor:eq(1)&&Text') || '', + vod_director: pdfh(html, '.video-info-actor:eq(0)&&Text') || '' + }; + + let playform = []; + let playurls = []; + let playPans = []; + + let panCounters = { + '夸克': 1, + '优汐': 1, + '百度': 1, + '天翼': 1, + '123': 1, + '移动': 1, + '阿里': 1 + }; + + let allLines = []; + let allLinks = new Set(); + + for (let item of data) { + let link = pd(item, 'p&&Text'); + if (link) { + link = link.trim(); + allLinks.add(link); + } + } + + let baiduLinks = Array.from(allLinks).filter(link => /pan\.baidu\.com/.test(link)); + let baiduLinkCount = baiduLinks.length; + + for (let link of allLinks) { + if (/\.quark/.test(link)) { + playPans.push(link); + let shareData = await Quark.getShareData(link); + if (shareData) { + let videos = await Quark.getFilesByShareUrl(shareData); + let lineName = '夸克#' + panCounters.夸克; + let playUrl = videos.length > 0 + ? videos.map(v => `${v.file_name}$${[ + shareData.shareId, + v.stoken, + v.fid, + v.share_fid_token, + v.subtitle?.fid || '', + v.subtitle?.share_fid_token || '' + ].join('*')}`).join('#') + : "资源已经失效,请访问其他资源"; + allLines.push({ name: lineName, url: playUrl, type: '夸克' }); + panCounters.夸克++; + } + } + else if (/\.uc/i.test(link)) { + playPans.push(link); + let shareData = await UC.getShareData(link); + if (shareData) { + let videos = await UC.getFilesByShareUrl(shareData); + let lineName = '优汐#' + panCounters.优汐; + let playUrl = videos.length > 0 + ? videos.map(v => `${v.file_name}$${[ + shareData.shareId, + v.stoken, + v.fid, + v.share_fid_token, + v.subtitle?.fid || '', + v.subtitle?.share_fid_token || '' + ].join('*')}`).join('#') + : "资源已经失效,请访问其他资源"; + allLines.push({ name: lineName, url: playUrl, type: '优汐' }); + panCounters.优汐++; + } + } + else if (/\.189/.test(link)) { + playPans.push(link); + let cloudData = await Cloud.getShareData(link); + Object.keys(cloudData).forEach(it => { + let lineName = '天翼-' + it; + const urls = cloudData[it].map(item => + `${item.name}$${[item.fileId, item.shareId].join('*')}` + ).join('#'); + allLines.push({ name: lineName, url: urls, type: '天翼' }); + }); + } + else if (/\.139/.test(link)) { + playPans.push(link); + let yunData = await Yun.getShareData(link); + Object.keys(yunData).forEach(it => { + let lineName = '移动-' + it; + const urls = yunData[it].map(item => + `${item.name}$${[item.contentId, item.linkID].join('*')}` + ).join('#'); + allLines.push({ name: lineName, url: urls, type: '移动' }); + }); + } + else if (/\.123/.test(link)) { + playPans.push(link); + let shareData = await Pan.getShareData(link); + let videos = await Pan.getFilesByShareUrl(shareData); + Object.keys(videos).forEach(it => { + let lineName = '123-' + it; + const urls = videos[it].map(v => + `${v.FileName}$${[v.ShareKey, v.FileId, v.S3KeyFlag, v.Size, v.Etag].join('*')}` + ).join('#'); + allLines.push({ name: lineName, url: urls, type: '123' }); + }); + } + else if (/\.baidu/.test(link)) { + playPans.push(link); + let baiduData = await Baidu2.getShareData(link); + + Object.keys(baiduData).forEach((it, index) => { + let lineName; + if (baiduLinkCount === 1) { + lineName = '百度#1'; + } else { + let lastPart = it.split('/').pop(); + lineName = '百度-' + lastPart; + } + + const urls = baiduData[it].map(item => + item.name + "$" + [item.path, item.uk, item.shareid, item.fsid].join('*') + ).join('#'); + allLines.push({ name: lineName, url: urls, type: '百度' }); + }); + } + else if (/\.alipan/.test(link)) { + playPans.push(link); + const shareData = await Ali.getShareData(link); + if (shareData) { + const videos = await Ali.getFilesByShareUrl(shareData); + let lineName = '阿里#' + panCounters.阿里; + let playUrl; + if (videos.length > 0) { + playUrl = videos.map((v) => { + const ids = [ + v.share_id, + v.file_id, + v.subtitle ? v.subtitle.file_id : '' + ]; + return `${v.name}$${ids.join('*')}`; + }).join('#'); + } else { + playUrl = "资源已经失效,请访问其他资源"; + } + allLines.push({ name: lineName, url: playUrl, type: '阿里' }); + panCounters.阿里++; + } + } + } + + allLines.sort((a, b) => { + let aIndex = rule.line_order.indexOf(a.type); + let bIndex = rule.line_order.indexOf(b.type); + if (aIndex === -1) aIndex = Infinity; + if (bIndex === -1) bIndex = Infinity; + return aIndex - bIndex; + }); + + playform = allLines.map(line => line.name); + playurls = allLines.map(line => line.url); + vod.vod_play_from = playform.join("$$$"); + vod.vod_play_url = playurls.join("$$$"); + vod.vod_play_pan = playPans.join("$$$"); + + return vod; + } catch (error) { + return { + vod_name: '加载失败', + type_name: '错误', + vod_pic: '', + vod_content: `加载失败: ${error.message}`, + vod_remarks: '请检查网络连接或配置是否正确', + vod_play_from: '加载错误$$$所有链接无效', + vod_play_url: `错误详情: ${error.message}$$$建议重试或联系维护者`, + vod_play_pan: '' + }; + } + }, + + 搜索: async function(wd, quick, pg) { + let { host, input, pdfa, pdfh, pd } = this; + + let url = rule_type ? + `${host}/vodsearch/${wd}----------${pg}---.html` : + `${host}/index.php/vod/search/page/${pg}/wd/${wd}.html`; + + let d = []; + let html = await request(url); + let data = pdfa(html, '.module-items .module-search-item'); + data.forEach(it => { + let title = pdfh(it, '.video-info&&a&&title'); + let desc = pdfh(it, '.video-serial&&Text'); + let content = pdfh(it, '.video-info-item:eq(2)&&Text').replace(/(bsp;)|(&n.*?)|( )|(\s+)/gi, ''); + if (rule.search_match && !title.includes(wd)) { + return; + } + + d.push({ + title, + img: pd(it, 'img&&data-src'), + desc: desc, + content: content, + url: pd(it, '.video-info&&a&&href') + }); + }); + + return setResult(d); + }, + lazy: async function (flag, id, flags) { + let { input, mediaProxyUrl } = this; + let ids = input.split('*'); + let urls = []; + let UCDownloadingCache = {}; + let UCTranscodingCache = {}; + + if (flag.startsWith('夸克')) { + let down = await Quark.getDownload(ids[0], ids[1], ids[2], ids[3], true); + let headers = { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36', + 'origin': 'https://pan.quark.cn', + 'referer': 'https://pan.quark.cn/', + 'Cookie': Quark.cookie + }; + urls.push("原画", down.download_url + '#fastPlayMode##threads=10#'); + urls.push( + "原代服", + mediaProxyUrl + `?thread=${ENV.get('thread') || 6}&form=urlcode&randUa=1&url=` + + encodeURIComponent(down.download_url) + '&header=' + encodeURIComponent(JSON.stringify(headers)) + ); + if (ENV.get('play_local_proxy_type', '1') === '2') { + urls.push( + "原代本", + `http://127.0.0.1:7777/?thread=${ENV.get('thread') || 6}&form=urlcode&randUa=1&url=` + + encodeURIComponent(down.download_url) + '&header=' + encodeURIComponent(JSON.stringify(headers)) + ); + } else { + urls.push( + "原代本", + `http://127.0.0.1:5575/proxy?thread=${ENV.get('thread') || 6}&chunkSize=256&url=` + + encodeURIComponent(down.download_url) + ); + } + let transcoding = (await Quark.getLiveTranscoding(ids[0], ids[1], ids[2], ids[3])).filter((t) => t.accessable); + transcoding.forEach((t) => { + let res = t.resolution === 'low' ? "流畅" : + t.resolution === 'high' ? "高清" : + t.resolution === 'super' ? "超清" : + t.resolution === 'dolby_vision' ? "HDR" : "4K"; + urls.push(res, t.video_info.url); + }); + + return { + parse: 0, + url: urls, + header: headers + }; + } else if (flag.startsWith('优汐')) { + let down = await UC.getDownload(ids[0], ids[1], ids[2], ids[3], true); + down.forEach(t => { + let res = t.name === 'low' ? "流畅" : + t.name === 'high' ? "高清" : + t.name === 'super' ? "超清" : + t.name === 'dolby_vision' ? "HDR" : "4K"; + urls.push(res, `${t.url}`); + }); + + return { parse: 0, url: urls }; + } else if (flag.startsWith('移动')) { + let url = await Yun.getSharePlay(ids[0], ids[1]); + return { + url: `${url}` + }; + } else if (flag.startsWith('天翼')) { + let url = await Cloud.getShareUrl(ids[0], ids[1]); + return { + url: `${url}` + }; + } else if (flag.startsWith('123')) { + let url = await Pan.getDownload(ids[0], ids[1], ids[2], ids[3], ids[4]); + urls.push("原画", url); + return { + parse: 0, + url: urls + }; + } else if (flag.startsWith('阿里')) { + const transcoding_flag = { + UHD: "4K 超清", + QHD: "2K 超清", + FHD: "1080 全高清", + HD: "720 高清", + SD: "540 标清", + LD: "360 流畅" + }; + const down = await Ali.getDownload(ids[0], ids[1], flag === 'down'); + urls.push("原画", down.url + "#isVideo=true##ignoreMusic=true#"); + urls.push("极速原画", down.url + "#fastPlayMode##threads=10#"); + const transcoding = (await Ali.getLiveTranscoding(ids[0], ids[1])).sort((a, b) => b.template_width - a.template_width); + transcoding.forEach((t) => { + if (t.url !== '') { + urls.push(transcoding_flag[t.template_id], t.url); + } + }); + return { + parse: 0, + url: urls, + header: { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36', + 'Referer': 'https://www.aliyundrive.com/' + } + }; + } else if (flag.startsWith('百度')) { + let url = await Baidu2.getAppShareUrl(ids[0], ids[1], ids[2], ids[3]); + urls.push("原画", url + "#isVideo=true##fastPlayMode##threads=10#"); + urls.push( + "原代本", + `http://127.0.0.1:7777/?thread=${ENV.get('thread') || 6}&form=urlcode&randUa=1&url=` + + encodeURIComponent(url) + ); + return { + parse: 0, + url: urls, + header: { + "User-Agent": 'netdisk;P2SP;2.2.91.136;android-android;' + } + }; + } + } +} \ No newline at end of file diff --git "a/spider/js/\347\275\221\347\233\230\350\265\204\346\272\220[\346\220\234].js" "b/spider/js/\347\275\221\347\233\230\350\265\204\346\272\220[\346\220\234].js" new file mode 100644 index 00000000..de5328e9 --- /dev/null +++ "b/spider/js/\347\275\221\347\233\230\350\265\204\346\272\220[\346\220\234].js" @@ -0,0 +1,370 @@ +/* +@header({ + searchable: 1, + filterable: 0, + quickSearch: 1, + title: '网盘资源[搜]', + '类型': '搜索', + lang: 'ds' +}) +*/ + +var rule = { + 类型: '搜索', + title: '网盘资源[搜]', + alias: '网盘搜索引擎', + desc: '纯搜索源', + host: 'https://so.yinpai.xyz', + url: '', + searchUrl: '/api.php', + headers: { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36' + }, + searchable: 1, + quickSearch: 1, + filterable: 0, + double: true, + play_parse: true, + search_match: true, + limit: 10, + 网盘搜索_img: 'https://pan.losfer.cn/view.php/6f48c2262c08f929f50b5ba35c3f9aab.png', + action: async function (action, value) { + if (action === 'only_search') { + return '此源为纯搜索源,直接搜索即可,如输入 大奉打更人'; + } + return `未定义动作:${action}`; + }, + + 推荐: async function () { + return [{ + vod_id: 'only_search', + vod_pic: rule.网盘搜索_img, + vod_name: '这是个纯搜索源哦', + vod_tag: 'action' + }]; + }, + 二级: async function (ids) { + let {orId} = this; + let html = await request('https://so.yinpai.xyz/api.php?ids=' + orId); + const data = JSON.parse(html).list[0]; + const link = data?.vod_play_url?.split('$')?.[1] || ''; + + let vod = { + vod_name: data.vod_name || '', + vod_pic: data.vod_pic || '', + vod_remarks: data.vod_remarks || '', + }; + + let playform = []; + let playurls = []; + let playPans = []; + + let panCounters = { + '夸克': 1, + '优汐': 1, + '百度': 1, + '天翼': 1, + '123': 1, + '移动': 1, + '阿里': 1 + }; + + let allLines = []; + + if (/\.quark/.test(link)) { + playPans.push(link); + let shareData = await Quark.getShareData(link); + if (shareData) { + let videos = await Quark.getFilesByShareUrl(shareData); + let lineName = '夸克#' + panCounters.夸克; + let playUrl = videos.length > 0 ? + videos.map(v => `${v.file_name}$${[ + shareData.shareId, + v.stoken, + v.fid, + v.share_fid_token, + v.subtitle?.fid || '', + v.subtitle?.share_fid_token || '' + ].join('*')}`).join('#') : + "资源已经失效,请访问其他资源"; + allLines.push({ + name: lineName, + url: playUrl, + type: '夸克' + }); + panCounters.夸克++; + } + } else if (/\.uc/i.test(link)) { + playPans.push(link); + let shareData = await UC.getShareData(link); + if (shareData) { + let videos = await UC.getFilesByShareUrl(shareData); + let lineName = '优汐#' + panCounters.优汐; + let playUrl = videos.length > 0 ? + videos.map(v => `${v.file_name}$${[ + shareData.shareId, + v.stoken, + v.fid, + v.share_fid_token, + v.subtitle?.fid || '', + v.subtitle?.share_fid_token || '' + ].join('*')}`).join('#') : + "资源已经失效,请访问其他资源"; + allLines.push({ + name: lineName, + url: playUrl, + type: '优汐' + }); + panCounters.优汐++; + } + } else if (/\.189/.test(link)) { + playPans.push(link); + let cloudData = await Cloud.getShareData(link); + Object.keys(cloudData).forEach(it => { + let lineName = '天翼-' + it; + const urls = cloudData[it].map(item => + `${item.name}$${[item.fileId, item.shareId].join('*')}` + ).join('#'); + allLines.push({ + name: lineName, + url: urls, + type: '天翼' + }); + }); + } else if (/\.139/.test(link)) { + playPans.push(link); + let yunData = await Yun.getShareData(link); + Object.keys(yunData).forEach(it => { + let lineName = '移动-' + it; + const urls = yunData[it].map(item => + `${item.name}$${[item.contentId, item.linkID].join('*')}` + ).join('#'); + allLines.push({ + name: lineName, + url: urls, + type: '移动' + }); + }); + } else if (/\.123/.test(link)) { + playPans.push(link); + let shareData = await Pan.getShareData(link); + let videos = await Pan.getFilesByShareUrl(shareData); + Object.keys(videos).forEach(it => { + let lineName = '123-' + it; + const urls = videos[it].map(v => + `${v.FileName}$${[v.ShareKey, v.FileId, v.S3KeyFlag, v.Size, v.Etag].join('*')}` + ).join('#'); + allLines.push({ + name: lineName, + url: urls, + type: '123' + }); + }); + } else if (/\.baidu/.test(link)) { + playPans.push(link); + let baiduData = await Baidu2.getShareData(link); + Object.keys(baiduData).forEach((it, index) => { + let lineName = '百度#' + panCounters.百度; + const urls = baiduData[it].map(item => + item.name + "$" + [item.path, item.uk, item.shareid, item.fsid].join('*') + ).join('#'); + allLines.push({ + name: lineName, + url: urls, + type: '百度' + }); + }); + } else if (/\.alipan/.test(link)) { + playPans.push(link); + const shareData = await Ali.getShareData(link); + if (shareData) { + const videos = await Ali.getFilesByShareUrl(shareData); + let lineName = '阿里#' + panCounters.阿里; + let playUrl; + if (videos.length > 0) { + playUrl = videos.map((v) => { + const ids = [ + v.share_id, + v.file_id, + v.subtitle ? v.subtitle.file_id : '' + ]; + return `${v.name}$${ids.join('*')}`; + }).join('#'); + } else { + playUrl = "资源已经失效,请访问其他资源"; + } + allLines.push({ + name: lineName, + url: playUrl, + type: '阿里' + }); + panCounters.阿里++; + } + } + + playform = allLines.map(line => line.name); + playurls = allLines.map(line => line.url); + vod.vod_play_from = playform.join("$$$"); + vod.vod_play_url = playurls.join("$$$"); + vod.vod_play_pan = playPans.join("$$$"); + + return vod; + }, + 搜索: async function (wd, pg) { + let {input} = this; + const types = ['baidu', 'xunlei', 'tianyi', 'aliyun', 'uc', 'dquark', 'mobile']; + const urls = types.map(type => + `${input}?type=${type}&wd=${encodeURIComponent(wd)}` + ); + + const htmls = await Promise.all( + urls.map(url => request(url)) + ); + + const allItems = []; + htmls.forEach(html => { + const data = JSON.parse(html); + if (data.list && Array.isArray(data.list)) { + data.list.forEach(item => { + allItems.push(item); + }); + } + }); + + const d = []; + allItems.forEach(item => { + let title = item.vod_name; + if (rule.search_match && !title.includes(wd)) { + return; + } + d.push({ + title: title || '未知名称', + img: item.vod_pic || '无图片', + desc: item.vod_remarks || '无备注', + url: item.vod_id || '无ID' + }); + }); + + return setResult(d); + }, + + lazy: async function (flag, id, flags) { + let {input, mediaProxyUrl} = this; + let ids = input.split('*'); + let urls = []; + let UCDownloadingCache = {}; + let UCTranscodingCache = {}; + + if (flag.startsWith('夸克')) { + let down = await Quark.getDownload(ids[0], ids[1], ids[2], ids[3], true); + let headers = { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36', + 'origin': 'https://pan.quark.cn', + 'referer': 'https://pan.quark.cn/', + 'Cookie': Quark.cookie + }; + urls.push("原画", down.download_url + '#fastPlayMode##threads=10#'); + urls.push( + "原代服", + mediaProxyUrl + `?thread=${ENV.get('thread') || 6}&form=urlcode&randUa=1&url=` + + encodeURIComponent(down.download_url) + '&header=' + encodeURIComponent(JSON.stringify(headers)) + ); + if (ENV.get('play_local_proxy_type', '1') === '2') { + urls.push( + "原代本", + `http://127.0.0.1:7777/?thread=${ENV.get('thread') || 6}&form=urlcode&randUa=1&url=` + + encodeURIComponent(down.download_url) + '&header=' + encodeURIComponent(JSON.stringify(headers)) + ); + } else { + urls.push( + "原代本", + `http://127.0.0.1:5575/proxy?thread=${ENV.get('thread') || 6}&chunkSize=256&url=` + + encodeURIComponent(down.download_url) + ); + } + let transcoding = (await Quark.getLiveTranscoding(ids[0], ids[1], ids[2], ids[3])).filter((t) => t.accessable); + transcoding.forEach((t) => { + let res = t.resolution === 'low' ? "流畅" : + t.resolution === 'high' ? "高清" : + t.resolution === 'super' ? "超清" : + t.resolution === 'dolby_vision' ? "HDR" : "4K"; + urls.push(res, t.video_info.url); + }); + + return { + parse: 0, + url: urls, + header: headers + }; + } else if (flag.startsWith('优汐')) { + let down = await UC.getDownload(ids[0], ids[1], ids[2], ids[3], true); + down.forEach(t => { + let res = t.name === 'low' ? "流畅" : + t.name === 'high' ? "高清" : + t.name === 'super' ? "超清" : + t.name === 'dolby_vision' ? "HDR" : "4K"; + urls.push(res, `${t.url}`); + }); + + return {parse: 0, url: urls}; + } else if (flag.startsWith('移动')) { + let url = await Yun.getSharePlay(ids[0], ids[1]); + return { + url: `${url}` + }; + } else if (flag.startsWith('天翼')) { + let url = await Cloud.getShareUrl(ids[0], ids[1]); + return { + url: `${url}` + }; + } else if (flag.startsWith('123')) { + let url = await Pan.getDownload(ids[0], ids[1], ids[2], ids[3], ids[4]); + urls.push("原画", url); + return { + parse: 0, + url: urls + }; + } else if (flag.startsWith('阿里')) { + const transcoding_flag = { + UHD: "4K 超清", + QHD: "2K 超清", + FHD: "1080 全高清", + HD: "720 高清", + SD: "540 标清", + LD: "360 流畅" + }; + const down = await Ali.getDownload(ids[0], ids[1], flag === 'down'); + urls.push("原画", down.url + "#isVideo=true##ignoreMusic=true#"); + urls.push("极速原画", down.url + "#fastPlayMode##threads=10#"); + const transcoding = (await Ali.getLiveTranscoding(ids[0], ids[1])).sort((a, b) => b.template_width - a.template_width); + transcoding.forEach((t) => { + if (t.url !== '') { + urls.push(transcoding_flag[t.template_id], t.url); + } + }); + return { + parse: 0, + url: urls, + header: { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36', + 'Referer': 'https://www.aliyundrive.com/' + } + }; + } else if (flag.startsWith('百度')) { + let url = await Baidu2.getAppShareUrl(ids[0], ids[1], ids[2], ids[3]); + urls.push("原画", url + "#isVideo=true##fastPlayMode##threads=10#"); + urls.push( + "原代本", + `http://127.0.0.1:7777/?thread=${ENV.get('thread') || 6}&form=urlcode&randUa=1&url=` + + encodeURIComponent(url) + ); + return { + parse: 0, + url: urls, + header: { + 'User-Agent': 'netdisk;1.4.2;22021211RC;android-android;12;JSbridge4.4.0;jointBridge;1.1.0;', + } + }; + } + } +} \ No newline at end of file