import path from 'path'; import {fileURLToPath} from 'url'; import {existsSync} from 'fs'; import {base64Decode} from '../libs_drpy/crypto-util.js'; import {startJsonWatcher, getApiEngine} from "../utils/api_helper.js"; import * as drpyS from '../libs/drpyS.js'; import php from '../libs/php.js'; import catvod from '../libs/catvod.js'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); // Determine if we are running from the dist/libs directory (bundled) or source const isBundled = __dirname.endsWith('dist') || __dirname.endsWith('libs') || __dirname.endsWith('dist' + path.sep) || __dirname.endsWith('libs' + path.sep); // Smart root directory detection const possibleRoots = [ __dirname, path.resolve(__dirname, '..'), path.resolve(__dirname, '../..'), path.resolve(__dirname, '../../..') ]; const rootDir = possibleRoots.find(d => existsSync(path.join(d, 'spider/js'))) || (isBundled ? path.resolve(__dirname, '../..') : path.resolve(__dirname, '..')); // 定义options的目录 const jxDir = path.join(rootDir, 'jx'); const publicDir = path.join(rootDir, 'public'); const jsonDir = path.join(rootDir, 'json'); const jsDir = path.join(rootDir, 'spider/js'); const dr2Dir = path.join(rootDir, 'spider/js_dr2'); const phpDir = path.join(rootDir, 'spider/php'); const catDir = path.join(rootDir, 'spider/catvod'); const catLibDir = path.join(rootDir, 'spider/catLib'); const options = { rootDir, jxDir, publicDir, jsonDir, jsDir, dr2Dir, phpDir, catDir, catLibDir, }; /** * 支持的引擎映射表 * 包含drpyS、php、catvod */ const ENGINES = { drpyS, php, catvod, }; //const query = { // do: 'ds', // pg: 1, // extend: '', //}; /** * 创建带超时的Promise包装函数 * 为API操作添加超时控制,防止长时间阻塞 * * @param {Promise} promise - 要包装的Promise对象 * @param {number|null} timeoutMs - 超时时间(毫秒),null则使用默认值 * @param {string} operation - 操作描述,用于错误信息 * @param {string|null} invokeMethod - 调用方法类型,用于确定超时时间 * @returns {Promise} 包装后的Promise,会在超时时reject */ function withTimeout(promise, timeoutMs = null, operation = 'API操作', invokeMethod = null) { let defaultTimeout; // 根据invokeMethod确定超时时间 if (invokeMethod === 'action') { // action接口使用专用超时时间,默认60秒 defaultTimeout = 60 * 1000; } else { // 其他接口使用默认超时时间,默认20秒 defaultTimeout = 20 * 1000; } const actualTimeout = timeoutMs || defaultTimeout; return Promise.race([ promise, new Promise((_, reject) => { setTimeout(() => { reject(new Error(`${operation}超时 (${actualTimeout}ms)`)); }, actualTimeout); }) ]); } async function getEngine(moduleName, query, inject_env) { // 壳子可以注入环境变量,内部自行构造env inject_env = inject_env || {}; // 获取API引擎和模块路径 let {apiEngine, moduleDir, _ext, modulePath} = getApiEngine(ENGINES, moduleName, query, options); // 检查模块文件是否存在 if (!existsSync(modulePath)) { const error_msg = `Module ${moduleName} not found`; console.error(error_msg); return ""; } // 获取模块扩展参数 const moduleExt = query.extend || ''; /** * 构建环境对象 * 为规则执行提供必要的环境信息 * * @param {string} moduleName - 模块名称 * @returns {Object} 环境对象,包含各种URL和配置 */ function getEnv(moduleName) { // const proxyUrl = inject_env.proxyUrl || "http://127.0.0.1:9978/proxy?do=node"; const { jsonUrl = "http://127.0.0.1:9978/file/drpy-node/json/", publicUrl = "http://127.0.0.1:9978/file/drpy-node/public/", requestHost = "http://127.0.0.1:9978", hostname = "127.0.0.1:9978", hostUrl = "127.0.0.1", proxyUrl = `http://127.0.0.1:9978/proxy?do=node&siteKey=${moduleName}`, proxyPath, httpUrl, imageApi, mediaProxyUrl, webdavProxyUrl, ftpProxyUrl, wsName, fServer, } = inject_env; const getProxyUrl = function () { return proxyUrl }; return { httpUrl, imageApi, mediaProxyUrl, webdavProxyUrl, ftpProxyUrl, wsName, fServer, // 下面这些很常用 hostUrl, jsonUrl, publicUrl, requestHost, hostname, proxyUrl, proxyPath, // 代理路径 getProxyUrl, ext: moduleExt, moduleName: moduleName, } } const env = getEnv(moduleName); /** * 动态获取规则对象 * 支持跨规则调用,为规则提供调用其他规则的能力 * * @param {string} _moduleName - 目标模块名称 * @returns {Object|null} 规则对象,包含callRuleFn方法 */ env.getRule = async function (_moduleName) { const _modulePath = path.join(moduleDir, `${_moduleName}${_ext}`); if (!existsSync(_modulePath)) { return null; } const _env = getEnv(_moduleName); const RULE = await withTimeout(apiEngine.getRule(_modulePath, _env), null, `获取规则[${_moduleName}]`); /** * 规则函数调用方法 * 提供统一的规则方法调用接口 * * @param {string} _method - 方法名称 * @param {Array} _args - 方法参数 * @returns {*} 方法执行结果 */ RULE.callRuleFn = async function (_method, _args) { let invokeMethod = null; // 方法名映射到标准接口 switch (_method) { case 'class_parse': invokeMethod = 'home'; break; case '推荐': invokeMethod = 'homeVod'; break; case '一级': invokeMethod = 'category'; break; case '二级': invokeMethod = 'detail'; break; case '搜索': invokeMethod = 'search'; break; case 'lazy': invokeMethod = 'play'; break; case 'proxy_rule': invokeMethod = 'proxy'; break; case 'action': invokeMethod = 'action'; break; } // 如果没有映射的方法,直接调用规则对象的方法 if (!invokeMethod) { if (typeof RULE[_method] !== 'function') { return null } else { return await withTimeout( RULE[_method], null, `规则方法[${_method}]` ) } } // 调用映射后的标准接口 return await withTimeout( apiEngine[invokeMethod](_modulePath, _env, ..._args), null, `规则调用[${_method}]`, invokeMethod ) }; return RULE }; // 获取页码参数 const pg = Number(query.pg) || 1; // 根据 query 参数决定执行逻辑 // 处理播放逻辑 if ('play' in query) { const result = await withTimeout( apiEngine.play(modulePath, env, query.flag, query.play), null, `播放接口[${moduleName}]` ); return result; } // 处理分类逻辑 if ('ac' in query && 't' in query) { let ext = query.ext; let extend = {}; // 解析筛选参数 if (ext) { try { extend = JSON.parse(base64Decode(ext)) } catch (e) { console.error(`筛选参数错误:${e.message}`); } } const result = await withTimeout( apiEngine.category(modulePath, env, query.t, pg, 1, extend), null, `分类接口[${moduleName}]` ); return result; } // 处理详情逻辑 if ('ac' in query && 'ids' in query) { const result = await withTimeout( apiEngine.detail(modulePath, env, query.ids), null, `详情接口[${moduleName}]` ); return result; } // 处理动作逻辑 if ('ac' in query && 'action' in query) { const result = await withTimeout( apiEngine.action(modulePath, env, query.action, query.value), null, `动作接口[${moduleName}]`, 'action' ); return result; } // 处理搜索逻辑 if ('wd' in query) { const quick = 'quick' in query ? query.quick : 0; const result = await withTimeout( apiEngine.search(modulePath, env, query.wd, quick, pg), null, `搜索接口[${moduleName}]` ); return result; } // 处理代理逻辑 if ('proxy' in query) { // return [200, 'text/plain', 'hello world', {'User-Agent':'okhttp/3.11'}, 0]; const backRespList = await withTimeout( apiEngine.proxy(modulePath, env, query), null, `代理接口[${moduleName}]` ); return backRespList; } // 处理解析逻辑 if ('parse' in query) { let t1 = (new Date()).getTime(); // 记录开始时间 // 构建解析器文件路径 const jxName = query.parse; const jxPath = path.join(options.jxDir, `${jxName}.js`); const backResp = await withTimeout( drpyS.jx(jxPath, env, query), null, `解析接口[${jxName}]` ); const statusCode = 200; const mediaType = 'application/json; charset=utf-8'; // 处理对象类型的响应 if (typeof backResp === 'object') { // 设置默认的状态码 if (!backResp.code) { let statusCode = backResp.url && backResp.url !== query.url ? 200 : 404; backResp.code = statusCode } // 设置默认的消息 if (!backResp.msg) { let msgState = backResp.url && backResp.url !== query.url ? '成功' : '失败'; backResp.msg = `${jxName}解析${msgState}`; } backResp.type = `${mediaType}; charset=utf-8`; // 计算耗时 let t2 = (new Date()).getTime(); backResp.cost = t2 - t1; let backRespSend = backResp; console.log(backRespSend); return backRespSend; } // 处理字符串类型的响应 else if (typeof backResp === 'string') { // 构建标准响应格式 let statusCode = backResp && backResp !== query.url ? 200 : 404; let msgState = backResp && backResp !== query.url ? '成功' : '失败'; let t2 = (new Date()).getTime(); let result = { code: statusCode, url: backResp, msg: `${jxName}解析${msgState}`, cost: t2 - t1, type: `${mediaType}; charset=utf-8` } let backRespSend = result; console.log(backRespSend); return backRespSend; } else { // 其他类型的响应,返回失败 let t2 = (new Date()).getTime(); let result = { code: 404, url: "", msg: `${jxName}解析失败`, cost: t2 - t1, type: `${mediaType}; charset=utf-8` } let backRespSend = result; console.log(backRespSend); return backRespSend; } } // 处理强制刷新初始化逻辑 if ('refresh' in query) { const refreshedObject = await withTimeout( apiEngine.init(modulePath, env, true), null, `初始化接口[${moduleName}]` ); const {context, ...responseObject} = refreshedObject; return responseObject; } // 默认逻辑,返回 home + homeVod 接口 if (!('filter' in query)) { query.filter = 1 } const filter = 'filter' in query ? query.filter : 1; // 获取首页数据 const resultHome = await withTimeout( apiEngine.home(modulePath, env, filter), null, `首页接口[${moduleName}]` ); // 获取推荐数据 const resultHomeVod = await withTimeout( apiEngine.homeVod(modulePath, env), null, `推荐接口[${moduleName}]` ); // 合并结果 let result = { ...resultHome, }; // 如果有推荐数据,添加到结果中 if (Array.isArray(resultHomeVod) && resultHomeVod.length > 0) { Object.assign(result, {list: resultHomeVod}) } return result; }; global.getEngine = getEngine;