Skip to content

Commit d28e9a5

Browse files
author
Taois
committed
feat: 发布新版本,支持PHP
需要自己搭建PHP环境
1 parent 082a59a commit d28e9a5

File tree

15 files changed

+440
-2
lines changed

15 files changed

+440
-2
lines changed

.env.development

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ QQ_SMTP_AUTH_CODE =
3434

3535
# 调试猫源-推荐开启
3636
CAT_DEBUG=1
37+
PHP_PATH=
3738
PYTHON_PATH=
3839
VIRTUAL_ENV=
3940
daemonMode=0

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,10 @@ nodejs作为服务端的drpy实现。全面升级异步写法
6969

7070
## 更新记录
7171

72+
### 20260127
73+
74+
更新至V1.3.20
75+
7276
### 20260125
7377

7478
更新至V1.3.19

controllers/api.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,16 +24,18 @@ import {validatePwd} from "../utils/api_validate.js";
2424
import {startJsonWatcher, getApiEngine} from "../utils/api_helper.js";
2525
import * as drpyS from '../libs/drpyS.js';
2626
import hipy from '../libs/hipy.js';
27+
import php from '../libs/php.js';
2728
import xbpq from '../libs/xbpq.js';
2829
import catvod from '../libs/catvod.js';
2930

3031
/**
3132
* 支持的引擎映射表
32-
* 包含drpyS、hipy、xbpq、catvod四种引擎
33+
* 包含drpyS、hipy、phipy、xbpq、catvod五种引擎
3334
*/
3435
const ENGINES = {
3536
drpyS,
3637
hipy,
38+
php,
3739
xbpq,
3840
catvod,
3941
};

controllers/config.js

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import {validateBasicAuth, validatePwd} from "../utils/api_validate.js";
2323
import {getSitesMap} from "../utils/sites-map.js";
2424
import {getParsesDict} from "../utils/file.js";
2525
import batchExecute from '../libs_drpy/batchExecute.js';
26+
import {isPhpAvailable} from '../utils/phpEnv.js';
2627

2728
const {jsEncoder} = drpyS;
2829

@@ -76,6 +77,7 @@ async function generateSiteJSON(options, requestHost, sub, pwd) {
7677
const jsDir = options.jsDir;
7778
const dr2Dir = options.dr2Dir;
7879
const pyDir = options.pyDir;
80+
const phpDir = options.phpDir;
7981
const catDir = options.catDir;
8082
const configDir = options.configDir;
8183
const jsonDir = options.jsonDir;
@@ -490,6 +492,63 @@ async function generateSiteJSON(options, requestHost, sub, pwd) {
490492
await batchExecute(py_tasks, listener);
491493

492494
}
495+
496+
// 根据用户是否启用php源去生成对应配置
497+
const enable_php = ENV.get('enable_php', '1');
498+
console.log('isPhpAvailable:', isPhpAvailable);
499+
if (enable_php === '1' && isPhpAvailable) {
500+
const php_files = readdirSync(phpDir);
501+
const api_type = 4;
502+
let php_valid_files = php_files.filter((file) => file.endsWith('.php') && !file.startsWith('_') && !['config.php', 'index.php', 'test_runner.php'].includes(file));
503+
log(`开始生成php的T${api_type}配置,phpDir:${phpDir},源数量: ${php_valid_files.length}`);
504+
505+
const php_tasks = php_valid_files.map((file) => {
506+
return {
507+
func: async ({file, phpDir, requestHost, pwd, SitesMap}) => {
508+
const baseName = path.basename(file, '.php');
509+
let api = `${requestHost}/api/${baseName}?do=php`;
510+
let ext = '';
511+
if (pwd) {
512+
api += `&pwd=${pwd}`;
513+
}
514+
let ruleObject = {
515+
searchable: 1,
516+
filterable: 1,
517+
quickSearch: 1,
518+
};
519+
let ruleMeta = {...ruleObject};
520+
const filePath = path.join(phpDir, file);
521+
522+
Object.assign(ruleMeta, {
523+
title: baseName,
524+
lang: 'php',
525+
});
526+
ruleMeta.title = enableRuleName ? ruleMeta.title || baseName : baseName;
527+
528+
let fileSites = [];
529+
let key = `php_${ruleMeta.title}`;
530+
let name = `${ruleMeta.title}(PHP)`;
531+
fileSites.push({key, name, ext});
532+
533+
fileSites.forEach((fileSite) => {
534+
const site = {
535+
key: fileSite.key,
536+
name: fileSite.name,
537+
type: api_type,
538+
api,
539+
...ruleMeta,
540+
ext: fileSite.ext || "",
541+
};
542+
sites.push(site);
543+
});
544+
},
545+
param: {file, phpDir, requestHost, pwd, SitesMap},
546+
id: file,
547+
};
548+
});
549+
await batchExecute(php_tasks, listener);
550+
}
551+
493552
const enable_cat = ENV.get('enable_cat', '1');
494553
// 根据用户是否启用cat源去生成对应配置
495554
if (enable_cat === '1' || enable_cat === '2') {

docs/updateRecord.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,16 @@
11
# drpyS更新记录
22

3+
### 20260127
4+
5+
更新至V1.3.20
6+
7+
重磅升级来了!!!
8+
9+
1. 支持了php适配器,支持自动加载php源,环境变量新增 `PHP_PATH=`,如果不指定默认则是'php',可以配置成自己的路径
10+
2. 尝试启动加速,插件异步加载
11+
3. 启动日志大幅精简,还你一个干净清爽的启动界面
12+
4. 设置中心修改,支持启用/关闭 PHP的源
13+
314
### 20260125
415

516
更新至V1.3.19

index.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ const startTime = performance.now();
33

44
import * as fastlogger from './controllers/fastlogger.js'
55
import path from 'path';
6+
import {checkPhpAvailable} from './utils/phpEnv.js';
67
import os from 'os';
78
import qs from 'qs';
89
import {fileURLToPath} from 'url';
@@ -33,6 +34,7 @@ const jsonDir = path.join(__dirname, 'json');
3334
const jsDir = path.join(__dirname, 'spider/js');
3435
const dr2Dir = path.join(__dirname, 'spider/js_dr2');
3536
const pyDir = path.join(__dirname, 'spider/py');
37+
const phpDir = path.join(__dirname, 'spider/php');
3638
const catDir = path.join(__dirname, 'spider/catvod');
3739
const catLibDir = path.join(__dirname, 'spider/catLib');
3840
const xbpqDir = path.join(__dirname, 'spider/xbpq');
@@ -46,6 +48,7 @@ setTimeout(() => {
4648

4749
// 添加钩子事件
4850
fastify.addHook('onReady', async () => {
51+
await checkPhpAvailable();
4952
const endTime = performance.now();
5053
console.log(`🚀 Server started in ${(endTime - startTime).toFixed(2)}ms`);
5154
try {
@@ -176,6 +179,7 @@ const registerOptions = {
176179
jsDir,
177180
dr2Dir,
178181
pyDir,
182+
phpDir,
179183
catDir,
180184
catLibDir,
181185
xbpqDir,

libs/php.js

Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
import path from "path";
2+
import {readFile} from "fs/promises";
3+
import {fileURLToPath} from 'url';
4+
import {execFile} from 'child_process';
5+
import {promisify} from 'util';
6+
import {getSitesMap} from "../utils/sites-map.js";
7+
import {computeHash, deepCopy, getNowTime, urljoin} from "../utils/utils.js";
8+
import {md5} from "../libs_drpy/crypto-util.js";
9+
import {fastify} from "../controllers/fastlogger.js";
10+
// import dotenv from 'dotenv';
11+
//
12+
// dotenv.config({ path: path.join(process.cwd(), '.env.development') });
13+
14+
const execFileAsync = promisify(execFile);
15+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
16+
const _config_path = path.join(__dirname, '../config');
17+
const _bridge_path = path.join(__dirname, '../spider/php/_bridge.php');
18+
19+
// Cache for module objects
20+
const moduleCache = new Map();
21+
22+
// Mapping from JS method names to PHP Spider method names
23+
const methodMapping = {
24+
'init': 'init',
25+
'home': 'homeContent',
26+
'homeVod': 'homeVideoContent',
27+
'category': 'categoryContent',
28+
'detail': 'detailContent',
29+
'search': 'searchContent',
30+
'play': 'playerContent',
31+
'proxy': 'proxy', // Not standard in BaseSpider, but might exist
32+
'action': 'action' // Not standard
33+
};
34+
35+
// Helper to stringify args for CLI
36+
function stringify(arg) {
37+
if (arg === undefined) return 'null';
38+
return JSON.stringify(arg);
39+
}
40+
41+
// Helper to parse JSON output
42+
function json2Object(json) {
43+
if (!json) return {};
44+
if (typeof json === 'object') return json;
45+
try {
46+
return JSON.parse(json);
47+
} catch (e) {
48+
return json;
49+
}
50+
}
51+
52+
// Execute PHP bridge
53+
const callPhpMethod = async (filePath, methodName, env, ...args) => {
54+
const phpPath = process.env.PHP_PATH || 'php';
55+
const phpMethodName = methodMapping[methodName] || methodName;
56+
57+
const cliArgs = [
58+
_bridge_path,
59+
filePath,
60+
phpMethodName,
61+
JSON.stringify(env),
62+
...args.map(stringify)
63+
];
64+
65+
try {
66+
// fastify.log.info(`Calling PHP: ${phpPath} ${cliArgs.join(' ')}`);
67+
const {stdout, stderr} = await execFileAsync(phpPath, cliArgs, {
68+
encoding: 'utf8',
69+
maxBuffer: 10 * 1024 * 1024, // 10MB buffer
70+
env: {
71+
...process.env,
72+
PYTHONIOENCODING: 'utf-8', // Just in case
73+
// Add any PHP specific env vars if needed
74+
}
75+
});
76+
77+
if (stderr) {
78+
// Log stderr but don't fail immediately unless stdout is empty or error
79+
// fastify.log.warn(`PHP Stderr: ${stderr}`);
80+
console.error(`PHP Stderr: ${stderr}`);
81+
}
82+
83+
const result = json2Object(stdout.trim());
84+
85+
if (result && result.error) {
86+
throw new Error(`PHP Error: ${result.error}\nTrace: ${result.traceback}`);
87+
}
88+
89+
return result;
90+
91+
} catch (error) {
92+
console.error(`Error calling PHP method ${methodName}:`, error);
93+
throw error;
94+
}
95+
};
96+
97+
const loadEsmWithHash = async function (filePath, fileHash, env) {
98+
const spiderProxy = {};
99+
const spiderMethods = Object.keys(methodMapping);
100+
101+
spiderMethods.forEach(method => {
102+
spiderProxy[method] = async (...args) => {
103+
return callPhpMethod(filePath, method, env, ...args);
104+
};
105+
});
106+
107+
return spiderProxy;
108+
};
109+
110+
const init = async function (filePath, env = {}, refresh) {
111+
try {
112+
const fileContent = await readFile(filePath, 'utf-8');
113+
const fileHash = computeHash(fileContent);
114+
const moduleName = path.basename(filePath, '.php'); // .php extension
115+
let moduleExt = env.ext || '';
116+
117+
// Logic for SitesMap and moduleExt (similar to hipy.js)
118+
let SitesMap = getSitesMap(_config_path);
119+
if (moduleExt && SitesMap[moduleName]) {
120+
// ... logic for compressed ext ...
121+
// Simplified for now, assuming plain string or handled by caller
122+
}
123+
124+
let hashMd5 = md5(filePath + '#php#' + moduleExt);
125+
126+
if (moduleCache.has(hashMd5) && !refresh) {
127+
const cached = moduleCache.get(hashMd5);
128+
if (cached.hash === fileHash) {
129+
return cached.moduleObject;
130+
}
131+
}
132+
133+
fastify.log.info(`Loading PHP module: ${filePath}`);
134+
let t1 = getNowTime();
135+
136+
const module = await loadEsmWithHash(filePath, fileHash, env);
137+
const rule = module;
138+
139+
// Initialize the spider
140+
const initValue = await rule.init(moduleExt) || {};
141+
142+
let t2 = getNowTime();
143+
const moduleObject = deepCopy(rule);
144+
moduleObject.cost = t2 - t1;
145+
146+
moduleCache.set(hashMd5, {moduleObject, hash: fileHash});
147+
return {...moduleObject, ...initValue};
148+
149+
} catch (error) {
150+
fastify.log.error(`Error in php.init :${filePath}`, error);
151+
throw new Error(`Failed to initialize PHP module:${error.message}`);
152+
}
153+
};
154+
155+
const getRule = async function (filePath, env) {
156+
const moduleObject = await init(filePath, env);
157+
return JSON.stringify(moduleObject);
158+
};
159+
160+
const home = async function (filePath, env, filter = 1) {
161+
const moduleObject = await init(filePath, env);
162+
return json2Object(await moduleObject.home(filter));
163+
};
164+
165+
const homeVod = async function (filePath, env) {
166+
const moduleObject = await init(filePath, env);
167+
const homeVodResult = json2Object(await moduleObject.homeVod());
168+
return homeVodResult && homeVodResult.list ? homeVodResult.list : homeVodResult;
169+
};
170+
171+
const category = async function (filePath, env, tid, pg = 1, filter = 1, extend = {}) {
172+
const moduleObject = await init(filePath, env);
173+
return json2Object(await moduleObject.category(tid, pg, filter, extend));
174+
};
175+
176+
const detail = async function (filePath, env, ids) {
177+
const moduleObject = await init(filePath, env);
178+
return json2Object(await moduleObject.detail(ids));
179+
};
180+
181+
const search = async function (filePath, env, wd, quick = 0, pg = 1) {
182+
const moduleObject = await init(filePath, env);
183+
return json2Object(await moduleObject.search(wd, quick, pg));
184+
};
185+
186+
const play = async function (filePath, env, flag, id, flags) {
187+
const moduleObject = await init(filePath, env);
188+
return json2Object(await moduleObject.play(flag, id, flags));
189+
};
190+
191+
const proxy = async function (filePath, env, params) {
192+
const moduleObject = await init(filePath, env);
193+
return json2Object(await moduleObject.proxy(params));
194+
};
195+
196+
const action = async function (filePath, env, action, value) {
197+
const moduleObject = await init(filePath, env);
198+
return json2Object(await moduleObject.action(action, value));
199+
};
200+
201+
export default {
202+
getRule,
203+
init,
204+
home,
205+
homeVod,
206+
category,
207+
detail,
208+
search,
209+
play,
210+
proxy,
211+
action
212+
};

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "drpy-node",
3-
"version": "1.3.19",
3+
"version": "1.3.20",
44
"main": "index.js",
55
"type": "module",
66
"scripts": {

public/index.html

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,8 @@ <h3 id="免费壳子推荐">免费壳子推荐</h3>
7070
<li><a href="https://github.com/ingriddaleusag-dotcom/PeekPiliRelease">皮卡丘</a></li>
7171
</ul>
7272
<h2 id="更新记录">更新记录</h2>
73+
<h3 id="20260127">20260127</h3>
74+
<p>更新至V1.3.20</p>
7375
<h3 id="20260125">20260125</h3>
7476
<p>更新至V1.3.19</p>
7577
<h3 id="20260118">20260118</h3>

0 commit comments

Comments
 (0)