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