api.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385
  1. import path from 'path';
  2. import {existsSync} from 'fs';
  3. import {base64Decode} from '../libs_drpy/crypto-util.js';
  4. import * as drpy from '../libs/drpyS.js';
  5. import {ENV} from "../utils/env.js";
  6. import {validatePwd} from "../utils/api_validate.js";
  7. export default (fastify, options, done) => {
  8. // 动态加载模块并根据 query 执行不同逻辑
  9. fastify.route({
  10. method: ['GET', 'POST'], // 同时支持 GET 和 POST
  11. url: '/api/:module',
  12. preHandler: validatePwd,
  13. schema: {
  14. consumes: ['application/json', 'application/x-www-form-urlencoded'], // 声明支持的内容类型
  15. },
  16. handler: async (request, reply) => {
  17. const moduleName = request.params.module;
  18. const modulePath = path.join(options.jsDir, `${moduleName}.js`);
  19. if (!existsSync(modulePath)) {
  20. reply.status(404).send({error: `Module ${moduleName} not found`});
  21. return;
  22. }
  23. const method = request.method.toUpperCase();
  24. // 根据请求方法选择参数来源
  25. const query = method === 'GET' ? request.query : request.body;
  26. const moduleExt = query.extend || '';
  27. // console.log('moduleExt:', typeof moduleExt, moduleExt);
  28. const protocol = request.protocol;
  29. const hostname = request.hostname;
  30. // const proxyUrl = `${protocol}://${hostname}${request.url}`.split('?')[0].replace('/api/', '/proxy/') + '/?do=js';
  31. // const proxyUrl = `${protocol}://${hostname}/proxy/${moduleName}/?do=js`;
  32. // console.log('proxyUrl:', proxyUrl);
  33. const publicUrl = `${protocol}://${hostname}/public/`;
  34. const jsonUrl = `${protocol}://${hostname}/json/`;
  35. const httpUrl = `${protocol}://${hostname}/http`;
  36. const mediaProxyUrl = `${protocol}://${hostname}/mediaProxy`;
  37. const hostUrl = `${hostname.split(':')[0]}`;
  38. const fServer = fastify.server;
  39. // console.log(`proxyUrl:${proxyUrl}`);
  40. function getEnv(moduleName) {
  41. const proxyUrl = `${protocol}://${hostname}/proxy/${moduleName}/?do=js`;
  42. const getProxyUrl = function () {
  43. return proxyUrl
  44. };
  45. return {
  46. proxyUrl,
  47. publicUrl,
  48. jsonUrl,
  49. httpUrl,
  50. mediaProxyUrl,
  51. hostUrl,
  52. hostname,
  53. fServer,
  54. getProxyUrl,
  55. ext: moduleExt
  56. }
  57. }
  58. const env = getEnv(moduleName);
  59. env.getRule = async function (_moduleName) {
  60. const _modulePath = path.join(options.jsDir, `${_moduleName}.js`);
  61. if (!existsSync(_modulePath)) {
  62. return null;
  63. }
  64. const _env = getEnv(_moduleName);
  65. const RULE = await drpy.getRule(_modulePath, _env);
  66. RULE.callRuleFn = async function (_method, _args) {
  67. let invokeMethod = null;
  68. switch (_method) {
  69. case 'class_parse':
  70. invokeMethod = 'home';
  71. break;
  72. case '推荐':
  73. invokeMethod = 'homeVod';
  74. break;
  75. case '一级':
  76. invokeMethod = 'cate';
  77. break;
  78. case '二级':
  79. invokeMethod = 'detail';
  80. break;
  81. case '搜索':
  82. invokeMethod = 'search';
  83. break;
  84. case 'lazy':
  85. invokeMethod = 'play';
  86. break;
  87. case 'proxy_rule':
  88. invokeMethod = 'proxy';
  89. break;
  90. case 'action':
  91. invokeMethod = 'action';
  92. break;
  93. }
  94. if (!invokeMethod) {
  95. if (typeof RULE[_method] !== 'function') {
  96. return null
  97. } else {
  98. return await RULE[_method]
  99. }
  100. }
  101. return await drpy[invokeMethod](_modulePath, _env, ..._args)
  102. };
  103. return RULE
  104. };
  105. const pg = Number(query.pg) || 1;
  106. try {
  107. // 根据 query 参数决定执行逻辑
  108. if ('play' in query) {
  109. // 处理播放逻辑
  110. // console.log('play query:', query);
  111. const result = await drpy.play(modulePath, env, query.flag, query.play);
  112. return reply.send(result);
  113. }
  114. if ('ac' in query && 't' in query) {
  115. let ext = query.ext;
  116. // console.log('ext:', ext);
  117. let extend = {};
  118. if (ext) {
  119. try {
  120. extend = JSON.parse(base64Decode(ext))
  121. } catch (e) {
  122. fastify.log.error(`筛选参数错误:${e.message}`);
  123. }
  124. }
  125. // 分类逻辑
  126. const result = await drpy.cate(modulePath, env, query.t, pg, 1, extend);
  127. return reply.send(result);
  128. }
  129. if ('ac' in query && 'ids' in query) {
  130. if (method === 'POST') {
  131. fastify.log.info(`[${moduleName}] 二级已接收post数据: ${query.ids}`);
  132. }
  133. // 详情逻辑
  134. const result = await drpy.detail(modulePath, env, query.ids.split(','));
  135. return reply.send(result);
  136. }
  137. if ('ac' in query && 'action' in query) {
  138. // 处理动作逻辑
  139. const result = await drpy.action(modulePath, env, query.action, query.value);
  140. return reply.send(result);
  141. }
  142. if ('wd' in query) {
  143. // 搜索逻辑
  144. const quick = 'quick' in query ? query.quick : 0;
  145. const result = await drpy.search(modulePath, env, query.wd, quick, pg);
  146. return reply.send(result);
  147. }
  148. if ('refresh' in query) {
  149. // 强制刷新初始化逻辑
  150. const refreshedObject = await drpy.init(modulePath, env, true);
  151. return reply.send(refreshedObject);
  152. }
  153. if (!('filter' in query)) {
  154. query.filter = 1
  155. }
  156. // 默认逻辑,返回 home + homeVod 接口
  157. const filter = 'filter' in query ? query.filter : 1;
  158. const resultHome = await drpy.home(modulePath, env, filter);
  159. const resultHomeVod = await drpy.homeVod(modulePath, env);
  160. let result = {
  161. ...resultHome,
  162. // list: resultHomeVod,
  163. };
  164. if (Array.isArray(resultHomeVod) && resultHomeVod.length > 0) {
  165. Object.assign(result, {list: resultHomeVod})
  166. }
  167. reply.send(result);
  168. } catch (error) {
  169. // console.log('Error processing request:', error);
  170. // reply.status(500).send({error: `Failed to process request for module ${moduleName}: ${error.message}`});
  171. fastify.log.error(`Error api module ${moduleName}:${error.message}`);
  172. reply.status(500).send({error: `Failed to process module ${moduleName}: ${error.message}`});
  173. }
  174. }
  175. });
  176. fastify.get('/proxy/:module/*', async (request, reply) => {
  177. const moduleName = request.params.module;
  178. const query = request.query; // 获取 query 参数
  179. const modulePath = path.join(options.jsDir, `${moduleName}.js`);
  180. if (!existsSync(modulePath)) {
  181. reply.status(404).send({error: `Module ${moduleName} not found`});
  182. return;
  183. }
  184. const proxyPath = request.params['*']; // 捕获整个路径
  185. fastify.log.info(`try proxy for ${moduleName} -> ${proxyPath}: ${JSON.stringify(query)}`);
  186. const rangeHeader = request.headers.range; // 获取客户端的 Range 请求头
  187. const moduleExt = query.extend || '';
  188. const protocol = request.protocol;
  189. const hostname = request.hostname;
  190. const publicUrl = `${protocol}://${hostname}/public/`;
  191. const jsonUrl = `${protocol}://${hostname}/json/`;
  192. const httpUrl = `${protocol}://${hostname}/http`;
  193. const mediaProxyUrl = `${protocol}://${hostname}/mediaProxy`;
  194. const hostUrl = `${hostname.split(':')[0]}`;
  195. const fServer = fastify.server;
  196. function getEnv(moduleName) {
  197. const proxyUrl = `${protocol}://${hostname}/proxy/${moduleName}/?do=js`;
  198. const getProxyUrl = function () {
  199. return proxyUrl
  200. };
  201. return {
  202. proxyUrl,
  203. proxyPath,
  204. publicUrl,
  205. jsonUrl,
  206. httpUrl,
  207. mediaProxyUrl,
  208. hostUrl,
  209. hostname,
  210. fServer,
  211. getProxyUrl,
  212. ext: moduleExt
  213. }
  214. }
  215. const env = getEnv(moduleName);
  216. try {
  217. const backRespList = await drpy.proxy(modulePath, env, query);
  218. const statusCode = backRespList[0];
  219. const mediaType = backRespList[1] || 'application/octet-stream';
  220. let content = backRespList[2] || '';
  221. const headers = backRespList.length > 3 ? backRespList[3] : null;
  222. const toBytes = backRespList.length > 4 ? backRespList[4] : null;
  223. // 如果需要转换为字节内容(尝试base64转bytes)
  224. if (toBytes === 1) {
  225. try {
  226. if (content.includes('base64,')) {
  227. content = unescape(content.split("base64,")[1]);
  228. }
  229. content = Buffer.from(content, 'base64');
  230. } catch (e) {
  231. fastify.log.error(`Local Proxy toBytes error: ${e}`);
  232. }
  233. }
  234. // 流代理
  235. else if (toBytes === 2 && content.startsWith('http')) {
  236. const new_headers = {
  237. ...(headers ? headers : {}),
  238. ...(rangeHeader ? {Range: rangeHeader} : {}), // 添加 Range 请求头
  239. }
  240. // return proxyStreamMediaMulti(content, new_headers, request, reply); // 走 流式代理
  241. // 将查询参数构建为目标 URL
  242. const redirectUrl = `/mediaProxy?url=${encodeURIComponent(content)}&headers=${encodeURIComponent(JSON.stringify(new_headers))}&thread=${ENV.get('thread') || 1}`;
  243. // 执行重定向
  244. return reply.redirect(redirectUrl);
  245. }
  246. // 根据媒体类型来决定如何设置字符编码
  247. if (typeof content === 'string') {
  248. // 如果返回的是文本内容(例如 JSON 或字符串)
  249. if (mediaType && (mediaType.includes('text') || mediaType === 'application/json')) {
  250. // 对于文本类型,设置 UTF-8 编码
  251. reply
  252. .code(statusCode)
  253. .type(`${mediaType}; charset=utf-8`) // 设置编码为 UTF-8
  254. .headers(headers || {}) // 如果有headers, 则加上
  255. .send(content);
  256. } else {
  257. // 对于其他类型的文本(例如 XML),直接返回,不指定 UTF-8 编码
  258. reply
  259. .code(statusCode)
  260. .type(mediaType)
  261. .headers(headers || {})
  262. .send(content);
  263. }
  264. } else {
  265. // 如果返回的是二进制内容(例如图片或其他文件)
  266. reply
  267. .code(statusCode)
  268. .type(mediaType) // 使用合适的媒体类型,如 image/png
  269. .headers(headers || {})
  270. .send(content);
  271. }
  272. } catch (error) {
  273. fastify.log.error(`Error proxy module ${moduleName}:${error.message}`);
  274. reply.status(500).send({error: `Failed to proxy module ${moduleName}: ${error.message}`});
  275. }
  276. });
  277. fastify.get('/parse/:jx', async (request, reply) => {
  278. let t1 = (new Date()).getTime();
  279. const jxName = request.params.jx;
  280. const query = request.query; // 获取 query 参数
  281. const jxPath = path.join(options.jxDir, `${jxName}.js`);
  282. if (!existsSync(jxPath)) {
  283. return reply.status(404).send({error: `解析 ${jxName} not found`});
  284. }
  285. const moduleExt = query.extend || '';
  286. const protocol = request.protocol;
  287. const hostname = request.hostname;
  288. const publicUrl = `${protocol}://${hostname}/public/`;
  289. const jsonUrl = `${protocol}://${hostname}/json/`;
  290. const httpUrl = `${protocol}://${hostname}/http`;
  291. const mediaProxyUrl = `${protocol}://${hostname}/mediaProxy`;
  292. const hostUrl = `${hostname.split(':')[0]}`;
  293. const fServer = fastify.server;
  294. function getEnv(moduleName) {
  295. // const proxyUrl = `${protocol}://${hostname}/proxy/${moduleName}/?do=js`;
  296. const proxyUrl = `${protocol}://${hostname}${request.url}`.split('?')[0].replace('/parse/', '/proxy/') + '/?do=js';
  297. const getProxyUrl = function () {
  298. return proxyUrl
  299. };
  300. return {
  301. proxyUrl,
  302. publicUrl,
  303. jsonUrl,
  304. httpUrl,
  305. mediaProxyUrl,
  306. hostUrl,
  307. hostname,
  308. getProxyUrl,
  309. fServer,
  310. ext: moduleExt
  311. }
  312. }
  313. const env = getEnv('');
  314. try {
  315. const backResp = await drpy.jx(jxPath, env, query);
  316. const statusCode = 200;
  317. const mediaType = 'application/json; charset=utf-8';
  318. if (typeof backResp === 'object') {
  319. if (!backResp.code) {
  320. let statusCode = backResp.url && backResp.url !== query.url ? 200 : 404;
  321. backResp.code = statusCode
  322. }
  323. if (!backResp.msg) {
  324. let msgState = backResp.url && backResp.url !== query.url ? '成功' : '失败';
  325. backResp.msg = `${jxName}解析${msgState}`;
  326. }
  327. let t2 = (new Date()).getTime();
  328. backResp.cost = t2 - t1;
  329. let backRespSend = JSON.stringify(backResp);
  330. console.log(backRespSend);
  331. return reply.code(statusCode).type(`${mediaType}; charset=utf-8`).send(backRespSend);
  332. } else if (typeof backResp === 'string') {
  333. if (backResp.startsWith('redirect://')) {
  334. return reply.redirect(backResp.split('redirect://')[1]);
  335. }
  336. let statusCode = backResp && backResp !== query.url ? 200 : 404;
  337. let msgState = backResp && backResp !== query.url ? '成功' : '失败';
  338. let t2 = (new Date()).getTime();
  339. let result = {
  340. code: statusCode,
  341. url: backResp,
  342. msg: `${jxName}解析${msgState}`,
  343. cost: t2 - t1
  344. }
  345. let backRespSend = JSON.stringify(result);
  346. console.log(backRespSend);
  347. return reply.code(statusCode).type(`${mediaType}; charset=utf-8`).send(backRespSend);
  348. } else {
  349. return reply.status(404).send({error: `${jxName}解析失败`});
  350. }
  351. } catch (error) {
  352. fastify.log.error(`Error proxy jx ${jxName}:${error.message}`);
  353. reply.status(500).send({error: `Failed to proxy jx ${jxName}: ${error.message}`});
  354. }
  355. });
  356. done();
  357. };