Skip to content

Commit d12c228

Browse files
author
Taois
committed
feat: 发布新版本
1 parent c34271d commit d12c228

File tree

21 files changed

+4582
-67
lines changed

21 files changed

+4582
-67
lines changed

.env.development

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,4 +47,10 @@ CLIPBOARD_SECURITY_CODE=drpys
4747
# 允许字符集,默认utf-8
4848
CLIPBOARD_ALLOWED_CHARSET=
4949
# 最大可读取问文本体积,默认2mb
50-
CLIPBOARD_MAX_READ_SIZE=
50+
CLIPBOARD_MAX_READ_SIZE=
51+
52+
# API超时配置(秒)
53+
# 默认API超时时间
54+
API_TIMEOUT=20
55+
# action接口专用超时时间
56+
API_ACTION_TIMEOUT=60

README.md

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
# drpyS(drpy-node)
22

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

88
### 常用超链接
99

1010
* [接口文档](docs/apidoc.md) | [接口列表如定时任务](docs/apiList.md) | [小猫影视-待对接T4](https://github.com/waifu-project/movie/pull/135)
11-
* [本地配置接口-动态本地](/config?pwd=$pwd)
12-
* [本地配置接口-动态外网/局域网](/config/1?pwd=$pwd)
11+
* [本地配置接口-动态本地](/config?healthy=1&pwd=$pwd)
12+
* [本地配置接口-动态外网/局域网](/config/1?healthy=1&pwd=$pwd)
1313
* [其他配置接口-订阅过滤](/docs/sub.md)
1414
* [python环境](/docs/pyenv.md) | [DS项目环境变量说明](/docs/envdoc.md)
1515
* [猫源调试教程](/docs/catDebug.md)
@@ -32,12 +32,17 @@ nodejs作为服务端的drpy实现。全面升级异步写法
3232
* [cookie管理插件](/apps/cookie-butler/index.html)
3333
* [cron表达式插件](/apps/cron-generator/index.html)
3434
* [剪切板智能推送插件](/apps/clipboard-pusher/index.html)
35+
* [DS源可用性检测插件](/apps/source-checker/index.html)
3536
* [代码加解密工具](/admin/encoder)
3637
* [央视点播解析工具](/proxy/央视大全[官]/index.html)
3738
* [在线猫ds源主页](/cat/index.html)
3839

3940
## 更新记录
4041

42+
### 20250908
43+
44+
更新至V1.2.27
45+
4146
### 20250907
4247

4348
更新至V1.2.26

apps/clipboard-pusher/package.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"name": "clipboard-pusher",
3+
"pluginName": "剪切板推送内容管理工具",
4+
"version": "0.0.1",
5+
"description": "管理推送内容,可实时刷新查看,清空等操作!",
6+
"main": "index.html",
7+
"logo": "",
8+
"pluginType": "ui",
9+
"author": "hjdhnx",
10+
"dependencies": {}
11+
}

apps/source-checker/index.html

Lines changed: 1139 additions & 0 deletions
Large diffs are not rendered by default.

apps/source-checker/package.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"name": "source-checker",
3+
"pluginName": "源可用性检测器",
4+
"version": "1.0.0",
5+
"description": "检测drpyS源站的可用性,支持快速检测和全量检测模式",
6+
"main": "index.html",
7+
"logo": "",
8+
"pluginType": "ui",
9+
"author": "drpy-node",
10+
"dependencies": {}
11+
}

controllers/api.js

Lines changed: 95 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,31 @@ const ENGINES = {
1818
catvod,
1919
};
2020

21+
// 创建带超时的Promise包装函数
22+
function withTimeout(promise, timeoutMs = null, operation = 'API操作', invokeMethod = null) {
23+
let defaultTimeout;
24+
25+
// 根据invokeMethod确定超时时间
26+
if (invokeMethod === 'action') {
27+
// action接口使用专用超时时间,默认60秒
28+
defaultTimeout = parseInt(process.env.API_ACTION_TIMEOUT || '60') * 1000;
29+
} else {
30+
// 其他接口使用默认超时时间,默认20秒
31+
defaultTimeout = parseInt(process.env.API_TIMEOUT || '20') * 1000;
32+
}
33+
34+
const actualTimeout = timeoutMs || defaultTimeout;
35+
36+
return Promise.race([
37+
promise,
38+
new Promise((_, reject) => {
39+
setTimeout(() => {
40+
reject(new Error(`${operation}超时 (${actualTimeout}ms)`));
41+
}, actualTimeout);
42+
})
43+
]);
44+
}
45+
2146
export default (fastify, options, done) => {
2247
// 启动JSON监听
2348
startJsonWatcher(ENGINES, options.jsonDir);
@@ -54,7 +79,7 @@ export default (fastify, options, done) => {
5479

5580
// console.log(`proxyUrl:${proxyUrl}`);
5681
function getEnv(moduleName) {
57-
const proxyUrl = `${protocol}://${hostname}/proxy/${moduleName}/?do=${query.do||'ds'}&extend=${encodeURIComponent(moduleExt)}`;
82+
const proxyUrl = `${protocol}://${hostname}/proxy/${moduleName}/?do=${query.do || 'ds'}&extend=${encodeURIComponent(moduleExt)}`;
5883
const getProxyUrl = function () {
5984
return proxyUrl
6085
};
@@ -79,7 +104,11 @@ export default (fastify, options, done) => {
79104
return null;
80105
}
81106
const _env = getEnv(_moduleName);
82-
const RULE = await apiEngine.getRule(_modulePath, _env);
107+
const RULE = await withTimeout(
108+
apiEngine.getRule(_modulePath, _env),
109+
null,
110+
`获取规则[${_moduleName}]`
111+
);
83112
RULE.callRuleFn = async function (_method, _args) {
84113
let invokeMethod = null;
85114
switch (_method) {
@@ -112,10 +141,19 @@ export default (fastify, options, done) => {
112141
if (typeof RULE[_method] !== 'function') {
113142
return null
114143
} else {
115-
return await RULE[_method]
144+
return await withTimeout(
145+
RULE[_method],
146+
null,
147+
`规则方法[${_method}]`
148+
)
116149
}
117150
}
118-
return await apiEngine[invokeMethod](_modulePath, _env, ..._args)
151+
return await withTimeout(
152+
apiEngine[invokeMethod](_modulePath, _env, ..._args),
153+
null,
154+
`规则调用[${_method}]`,
155+
invokeMethod
156+
)
119157
};
120158
return RULE
121159
};
@@ -125,7 +163,11 @@ export default (fastify, options, done) => {
125163
if ('play' in query) {
126164
// 处理播放逻辑
127165
// console.log('play query:', query);
128-
const result = await apiEngine.play(modulePath, env, query.flag, query.play);
166+
const result = await withTimeout(
167+
apiEngine.play(modulePath, env, query.flag, query.play),
168+
null,
169+
`播放接口[${moduleName}]`
170+
);
129171
return reply.send(result);
130172
}
131173

@@ -141,7 +183,11 @@ export default (fastify, options, done) => {
141183
}
142184
}
143185
// 分类逻辑
144-
const result = await apiEngine.category(modulePath, env, query.t, pg, 1, extend);
186+
const result = await withTimeout(
187+
apiEngine.category(modulePath, env, query.t, pg, 1, extend),
188+
null,
189+
`分类接口[${moduleName}]`
190+
);
145191
return reply.send(result);
146192
}
147193

@@ -150,36 +196,61 @@ export default (fastify, options, done) => {
150196
fastify.log.info(`[${moduleName}] 二级已接收post数据: ${query.ids}`);
151197
}
152198
// 详情逻辑
153-
const result = await apiEngine.detail(modulePath, env, query.ids.split(','));
199+
const result = await withTimeout(
200+
apiEngine.detail(modulePath, env, query.ids.split(',')),
201+
null,
202+
`详情接口[${moduleName}]`
203+
);
154204
return reply.send(result);
155205
}
156206

157207
if ('ac' in query && 'action' in query) {
158208
// 处理动作逻辑
159-
const result = await apiEngine.action(modulePath, env, query.action, query.value);
209+
const result = await withTimeout(
210+
apiEngine.action(modulePath, env, query.action, query.value),
211+
null,
212+
`动作接口[${moduleName}]`,
213+
'action'
214+
);
160215
return reply.send(result);
161216
}
162217

163218

164219
if ('wd' in query) {
165220
// 搜索逻辑
166221
const quick = 'quick' in query ? query.quick : 0;
167-
const result = await apiEngine.search(modulePath, env, query.wd, quick, pg);
222+
const result = await withTimeout(
223+
apiEngine.search(modulePath, env, query.wd, quick, pg),
224+
null,
225+
`搜索接口[${moduleName}]`
226+
);
168227
return reply.send(result);
169228
}
170229

171230
if ('refresh' in query) {
172231
// 强制刷新初始化逻辑
173-
const refreshedObject = await apiEngine.init(modulePath, env, true);
232+
const refreshedObject = await withTimeout(
233+
apiEngine.init(modulePath, env, true),
234+
null,
235+
`初始化接口[${moduleName}]`
236+
);
174237
return reply.send(refreshedObject);
175238
}
176239
if (!('filter' in query)) {
177240
query.filter = 1
178241
}
179242
// 默认逻辑,返回 home + homeVod 接口
180243
const filter = 'filter' in query ? query.filter : 1;
181-
const resultHome = await apiEngine.home(modulePath, env, filter);
182-
const resultHomeVod = await apiEngine.homeVod(modulePath, env);
244+
const resultHome = await withTimeout(
245+
apiEngine.home(modulePath, env, filter),
246+
null,
247+
`首页接口[${moduleName}]`
248+
);
249+
const resultHomeVod = await withTimeout(
250+
apiEngine.homeVod(modulePath, env),
251+
null,
252+
`推荐接口[${moduleName}]`
253+
);
183254
let result = {
184255
...resultHome,
185256
// list: resultHomeVod,
@@ -227,7 +298,7 @@ export default (fastify, options, done) => {
227298
const fServer = fastify.server;
228299

229300
function getEnv(moduleName) {
230-
const proxyUrl = `${protocol}://${hostname}/proxy/${moduleName}/?do=${query.do||'ds'}&extend=${encodeURIComponent(moduleExt)}`;
301+
const proxyUrl = `${protocol}://${hostname}/proxy/${moduleName}/?do=${query.do || 'ds'}&extend=${encodeURIComponent(moduleExt)}`;
231302
const getProxyUrl = function () {
232303
return proxyUrl
233304
};
@@ -248,7 +319,11 @@ export default (fastify, options, done) => {
248319

249320
const env = getEnv(moduleName);
250321
try {
251-
const backRespList = await apiEngine.proxy(modulePath, env, query);
322+
const backRespList = await withTimeout(
323+
apiEngine.proxy(modulePath, env, query),
324+
null,
325+
`代理接口[${moduleName}]`
326+
);
252327
const statusCode = backRespList[0];
253328
const mediaType = backRespList[1] || 'application/octet-stream';
254329
let content = backRespList[2] || '';
@@ -332,7 +407,7 @@ export default (fastify, options, done) => {
332407
const fServer = fastify.server;
333408

334409
function getEnv(moduleName) {
335-
const proxyUrl = `${protocol}://${hostname}${request.url}`.split('?')[0].replace('/parse/', '/proxy/') + `/?do=${query.do||"ds"}&extend=${encodeURIComponent(moduleExt)}`;
410+
const proxyUrl = `${protocol}://${hostname}${request.url}`.split('?')[0].replace('/parse/', '/proxy/') + `/?do=${query.do || "ds"}&extend=${encodeURIComponent(moduleExt)}`;
336411
const getProxyUrl = function () {
337412
return proxyUrl
338413
};
@@ -352,7 +427,11 @@ export default (fastify, options, done) => {
352427

353428
const env = getEnv('');
354429
try {
355-
const backResp = await drpyS.jx(jxPath, env, query);
430+
const backResp = await withTimeout(
431+
drpyS.jx(jxPath, env, query),
432+
null,
433+
`解析接口[${jxName}]`
434+
);
356435
const statusCode = 200;
357436
const mediaType = 'application/json; charset=utf-8';
358437
if (typeof backResp === 'object') {

controllers/config.js

Lines changed: 41 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -739,6 +739,7 @@ export default (fastify, options, done) => {
739739
const query = request.query; // 获取 query 参数
740740
const pwd = query.pwd || '';
741741
const sub_code = query.sub || '';
742+
const healthy = query.healthy || ''; // 新增healthy参数
742743
const cat_sub_code = ENV.get('cat_sub_code', 'all');
743744
const must_sub_code = Number(ENV.get('must_sub_code', '0')) || 0;
744745
const cfg_path = request.params['*']; // 捕获整个路径
@@ -756,36 +757,36 @@ export default (fastify, options, done) => {
756757
// if (cfg_path.includes('index.js')) {
757758
// // return reply.sendFile('index.js', path.join(options.rootDir, 'data/cat'));
758759
// let content = readFileSync(path.join(options.rootDir, 'data/cat/index.js'), 'utf-8');
759-
// // content = jinja.render(content, {config_url: requestUrl.replace(cfg_path, `/1?sub=all&pwd=${process.env.API_PWD || ''}`)});
760-
// content = content.replace('$config_url', requestUrl.replace(cfg_path, `/1?sub=all&pwd=${process.env.API_PWD || ''}`));
760+
// // content = jinja.render(content, {config_url: requestUrl.replace(cfg_path, `/1?sub=all&healthy=1&pwd=${process.env.API_PWD || ''}`)});
761+
// content = content.replace('$config_url', requestUrl.replace(cfg_path, `/1?sub=all&healthy=1&pwd=${process.env.API_PWD || ''}`));
761762
// return reply.type('application/javascript;charset=utf-8').send(content);
762763
// } else if (cfg_path.includes('index.config.js')) {
763764
// let content = readFileSync(path.join(options.rootDir, 'data/cat/index.config.js'), 'utf-8');
764-
// // content = jinja.render(content, {config_url: requestUrl.replace(cfg_path, `/1?sub=all&pwd=${process.env.API_PWD || ''}`)});
765-
// content = content.replace('$config_url', requestUrl.replace(cfg_path, `/1?sub=all&pwd=${process.env.API_PWD || ''}`));
765+
// // content = jinja.render(content, {config_url: requestUrl.replace(cfg_path, `/1?sub=all&healthy=1&pwd=${process.env.API_PWD || ''}`)});
766+
// content = content.replace('$config_url', requestUrl.replace(cfg_path, `/1?sub=all&healthy=1&pwd=${process.env.API_PWD || ''}`));
766767
// return reply.type('application/javascript;charset=utf-8').send(content);
767768
// }
768769
// }
769770
// if (cfg_path.endsWith('.js.md5')) {
770771
// if (cfg_path.includes('index.js')) {
771772
// let content = readFileSync(path.join(options.rootDir, 'data/cat/index.js'), 'utf-8');
772-
// // content = jinja.render(content, {config_url: requestUrl.replace(cfg_path, `/1?sub=all&pwd=${process.env.API_PWD || ''}`)});
773-
// content = content.replace('$config_url', requestUrl.replace(cfg_path, `/1?sub=all&pwd=${process.env.API_PWD || ''}`));
773+
// // content = jinja.render(content, {config_url: requestUrl.replace(cfg_path, `/1?sub=all&healthy=1&pwd=${process.env.API_PWD || ''}`)});
774+
// content = content.replace('$config_url', requestUrl.replace(cfg_path, `/1?sub=all&healthy=1&pwd=${process.env.API_PWD || ''}`));
774775
// let contentHash = md5(content);
775776
// console.log('index.js contentHash:', contentHash);
776777
// return reply.type('text/plain;charset=utf-8').send(contentHash);
777778
// } else if (cfg_path.includes('index.config.js')) {
778779
// let content = readFileSync(path.join(options.rootDir, 'data/cat/index.config.js'), 'utf-8');
779-
// // content = jinja.render(content, {config_url: requestUrl.replace(cfg_path, `/1?sub=all&pwd=${process.env.API_PWD || ''}`)});
780-
// content = content.replace('$config_url', requestUrl.replace(cfg_path, `/1?sub=all&pwd=${process.env.API_PWD || ''}`));
780+
// // content = jinja.render(content, {config_url: requestUrl.replace(cfg_path, `/1?sub=all&healthy=1&pwd=${process.env.API_PWD || ''}`)});
781+
// content = content.replace('$config_url', requestUrl.replace(cfg_path, `/1?sub=all&healthy=1&pwd=${process.env.API_PWD || ''}`));
781782
// let contentHash = md5(content);
782783
// console.log('index.config.js contentHash:', contentHash);
783784
// return reply.type('text/plain;charset=utf-8').send(contentHash);
784785
// }
785786
// }
786787
const getFilePath = (cfgPath, rootDir, fileName) => path.join(rootDir, `data/cat/${fileName}`);
787788
const processContent = (content, cfgPath, requestUrl, requestHost) => {
788-
const $config_url = requestUrl.replace(cfgPath, `/1?sub=${cat_sub_code}&pwd=${process.env.API_PWD || ''}`);
789+
const $config_url = requestUrl.replace(cfgPath, `/1?sub=${cat_sub_code}&healthy=1&pwd=${process.env.API_PWD || ''}`);
789790
return content.replaceAll('$config_url', $config_url).replaceAll('$host', requestHost);
790791
}
791792

@@ -844,7 +845,37 @@ export default (fastify, options, done) => {
844845
return reply.status(500).send({error: `缺少订阅码参数`});
845846
}
846847

847-
const siteJSON = await generateSiteJSON(options, requestHost, sub, pwd);
848+
let siteJSON = await generateSiteJSON(options, requestHost, sub, pwd);
849+
850+
// 处理healthy参数,过滤失效源
851+
if (healthy === '1') {
852+
const reportPath = path.join(options.rootDir, 'data', 'source-checker', 'report.json');
853+
if (existsSync(reportPath)) {
854+
try {
855+
const reportContent = readFileSync(reportPath, 'utf-8');
856+
const reportData = JSON.parse(reportContent);
857+
858+
// 获取失效源的key列表
859+
const failedKeys = new Set();
860+
if (reportData.sources && Array.isArray(reportData.sources)) {
861+
reportData.sources.forEach(source => {
862+
if (source.status === 'error') {
863+
failedKeys.add(source.key);
864+
}
865+
});
866+
}
867+
868+
// 过滤掉失效的源
869+
if (failedKeys.size > 0) {
870+
siteJSON.sites = siteJSON.sites.filter(site => !failedKeys.has(site.key));
871+
console.log(`Filtered out ${failedKeys.size} failed sources, remaining: ${siteJSON.sites.length}`);
872+
}
873+
} catch (error) {
874+
console.error('Failed to process health report:', error.message);
875+
}
876+
}
877+
}
878+
848879
const parseJSON = await generateParseJSON(options.jxDir, requestHost);
849880
const livesJSON = generateLivesJSON(requestHost);
850881
const playerJSON = generatePlayerJSON(options.configDir, requestHost);

0 commit comments

Comments
 (0)