diff --git a/.env.development b/.env.development
index 6edf51d2..6aba1651 100644
--- a/.env.development
+++ b/.env.development
@@ -34,6 +34,7 @@ QQ_SMTP_AUTH_CODE =
# 调试猫源-推荐开启
CAT_DEBUG=1
+PHP_PATH=
PYTHON_PATH=
VIRTUAL_ENV=
daemonMode=0
diff --git a/.plugins.example.js b/.plugins.example.js
index c61674e6..6b429d01 100644
--- a/.plugins.example.js
+++ b/.plugins.example.js
@@ -35,14 +35,14 @@ const plugins = [
path: 'plugins/pup-sniffer', // 插件路径
params: '-port 57573', // 启动参数:端口57573
desc: 'drplayer嗅探服务', // 插件描述:提供视频适配代理功能
- active: true // 是否激活:true表示启用此插件
+ active: false // 是否激活:true表示启用此插件
},
{
name: 'mediaProxy', // 插件名称
path: 'plugins/mediaProxy', // 插件路径
params: '-port 57574', // 启动参数:端口57574
desc: 'go媒体代理服务', // 插件描述:提供视频适配代理功能
- active: true // 是否激活:true表示启用此插件
+ active: false // 是否激活:true表示启用此插件
},
]
diff --git a/Dockerfile b/Dockerfile
index 0377fc19..63bb1162 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -43,13 +43,28 @@ RUN cp /app/.env.development /app/.env && \
# 但是,我们仍然需要安装Node.js运行时本身(除非drpys项目是一个纯静态资源服务,不需要Node.js运行时)
RUN apk add --no-cache nodejs
+# 安装php8.3及其扩展
+RUN apk add --no-cache \
+ php83 \
+ php83-cli \
+ php83-curl \
+ php83-mbstring \
+ php83-xml \
+ php83-pdo \
+ php83-pdo_mysql \
+ php83-pdo_sqlite \
+ php83-openssl \
+ php83-sqlite3 \
+ php83-json
+RUN ln -sf /usr/bin/php83 /usr/bin/php
+
# 安装python3依赖
RUN apk add --no-cache python3 \
py3-pip \
py3-setuptools \
py3-wheel
-# 激活python3虚拟环境并安装依pip3赖
+# 激活python3虚拟环境并安装requirements依赖
RUN python3 -m venv /app/.venv && \
. /app/.venv/bin/activate && \
pip3 install -r /app/spider/py/base/requirements.txt
diff --git a/README.md b/README.md
index 06e9eb78..558b3c0e 100644
--- a/README.md
+++ b/README.md
@@ -20,6 +20,7 @@ nodejs作为服务端的drpy实现。全面升级异步写法
* [本地配置接口-动态外网/局域网](/config/1?healthy=1&pwd=$pwd)
* [其他配置接口-订阅过滤](/docs/sub.md)
* [python环境](/docs/pyenv.md) | [DS项目环境变量说明](/docs/envdoc.md)
+* php环境(详见 spider/php/readme.md) 不在这里赘述
* [猫源调试教程](/docs/catDebug.md)
* [接口压测教程](/docs/httpTest.md)
* [AI编程工具 trae](https://www.trae.ai/account-setting#subscription) | 邮编ZIP输入: 518000
@@ -46,6 +47,7 @@ nodejs作为服务端的drpy实现。全面升级异步写法
* [DS时钟插件-白色时钟](/apps/clock/white_clock.html)|[日历时钟](/apps/clock/index.html)
* [DS庆祝页面-完结撒花](/apps/happy/index.html)
* [bookReader](/apps/book-reader)
+* [系统备份与恢复](/apps/backup-restore/index.html)
* [代码加解密工具](/admin/encoder)
* [央视点播解析工具](/proxy/央视大全[官]/index.html)
* [在线猫ds源主页](/cat/index.html)
@@ -68,6 +70,14 @@ nodejs作为服务端的drpy实现。全面升级异步写法
## 更新记录
+### 20260131
+
+更新至V1.3.21
+
+### 20260127
+
+更新至V1.3.20
+
### 20260125
更新至V1.3.19
diff --git a/apps/backup-restore/index.html b/apps/backup-restore/index.html
new file mode 100644
index 00000000..98a03231
--- /dev/null
+++ b/apps/backup-restore/index.html
@@ -0,0 +1,373 @@
+
+
+
+
+
+ 系统备份与恢复
+
+
+
+
+
📂 系统备份与恢复
+
+
+
+
备份说明: 点击“立即备份”将系统配置、插件及脚本备份到同级 backup 目录。
+
恢复说明: 点击“恢复备份”将从 backup 目录恢复文件覆盖当前系统。
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/controllers/api.js b/controllers/api.js
index 1f24be45..6e878bd8 100644
--- a/controllers/api.js
+++ b/controllers/api.js
@@ -24,16 +24,18 @@ import {validatePwd} from "../utils/api_validate.js";
import {startJsonWatcher, getApiEngine} from "../utils/api_helper.js";
import * as drpyS from '../libs/drpyS.js';
import hipy from '../libs/hipy.js';
+import php from '../libs/php.js';
import xbpq from '../libs/xbpq.js';
import catvod from '../libs/catvod.js';
/**
* 支持的引擎映射表
- * 包含drpyS、hipy、xbpq、catvod四种引擎
+ * 包含drpyS、hipy、phipy、xbpq、catvod五种引擎
*/
const ENGINES = {
drpyS,
hipy,
+ php,
xbpq,
catvod,
};
diff --git a/controllers/config.js b/controllers/config.js
index 1b8aae62..3fa9b978 100644
--- a/controllers/config.js
+++ b/controllers/config.js
@@ -23,6 +23,7 @@ import {validateBasicAuth, validatePwd} from "../utils/api_validate.js";
import {getSitesMap} from "../utils/sites-map.js";
import {getParsesDict} from "../utils/file.js";
import batchExecute from '../libs_drpy/batchExecute.js';
+import {isPhpAvailable} from '../utils/phpEnv.js';
const {jsEncoder} = drpyS;
@@ -76,6 +77,7 @@ async function generateSiteJSON(options, requestHost, sub, pwd) {
const jsDir = options.jsDir;
const dr2Dir = options.dr2Dir;
const pyDir = options.pyDir;
+ const phpDir = options.phpDir;
const catDir = options.catDir;
const configDir = options.configDir;
const jsonDir = options.jsonDir;
@@ -417,6 +419,11 @@ async function generateSiteJSON(options, requestHost, sub, pwd) {
filterable: 1, // 固定值
quickSearch: 1, // 固定值
};
+ if (baseName.includes('[画]')) {
+ ruleObject.类型 = '漫画'
+ } else if (baseName.includes('[书]')) {
+ ruleObject.类型 = '小说'
+ }
let ruleMeta = {...ruleObject};
const filePath = path.join(pyDir, file);
const header = await FileHeaderManager.readHeader(filePath);
@@ -490,6 +497,68 @@ async function generateSiteJSON(options, requestHost, sub, pwd) {
await batchExecute(py_tasks, listener);
}
+
+ // 根据用户是否启用php源去生成对应配置
+ const enable_php = ENV.get('enable_php', '1');
+ console.log('isPhpAvailable:', isPhpAvailable);
+ if ((enable_php === '1' && isPhpAvailable) || enable_php === '2') {
+ const php_files = readdirSync(phpDir);
+ const api_type = enable_php === '2' ? 3 : 4;
+ let php_valid_files = php_files.filter((file) => file.endsWith('.php') && !file.startsWith('_') && !['config.php', 'index.php', 'test_runner.php'].includes(file));
+ log(`开始生成php的T${api_type}配置,phpDir:${phpDir},源数量: ${php_valid_files.length}`);
+
+ const php_tasks = php_valid_files.map((file) => {
+ return {
+ func: async ({file, phpDir, requestHost, pwd, SitesMap}) => {
+ const baseName = path.basename(file, '.php');
+ let api = enable_php === '2' ? `${requestHost}/php/${file}` : `${requestHost}/api/${baseName}?do=php`;
+ let ext = '';
+ if (pwd) {
+ api += enable_php === '2' ? `?pwd=${pwd}` : `&pwd=${pwd}`;
+ }
+ let ruleObject = {
+ searchable: 1,
+ filterable: 1,
+ quickSearch: 1,
+ };
+ if (baseName.includes('[画]')) {
+ ruleObject.类型 = '漫画'
+ } else if (baseName.includes('[书]')) {
+ ruleObject.类型 = '小说'
+ }
+ let ruleMeta = {...ruleObject};
+ const filePath = path.join(phpDir, file);
+
+ Object.assign(ruleMeta, {
+ title: baseName,
+ lang: 'php',
+ });
+ ruleMeta.title = enableRuleName ? ruleMeta.title || baseName : baseName;
+
+ let fileSites = [];
+ let key = `php_${ruleMeta.title}`;
+ let name = `${ruleMeta.title}(PHP)`;
+ fileSites.push({key, name, ext});
+
+ fileSites.forEach((fileSite) => {
+ const site = {
+ key: fileSite.key,
+ name: fileSite.name,
+ type: api_type,
+ api,
+ ...ruleMeta,
+ ext: fileSite.ext || "",
+ };
+ sites.push(site);
+ });
+ },
+ param: {file, phpDir, requestHost, pwd, SitesMap},
+ id: file,
+ };
+ });
+ await batchExecute(php_tasks, listener);
+ }
+
const enable_cat = ENV.get('enable_cat', '1');
// 根据用户是否启用cat源去生成对应配置
if (enable_cat === '1' || enable_cat === '2') {
@@ -520,6 +589,11 @@ async function generateSiteJSON(options, requestHost, sub, pwd) {
filterable: 1, // 固定值
quickSearch: 1, // 固定值
};
+ if (baseName.includes('[画]')) {
+ ruleObject.类型 = '漫画'
+ } else if (baseName.includes('[书]')) {
+ ruleObject.类型 = '小说'
+ }
let ruleMeta = {...ruleObject};
const filePath = path.join(catDir, file);
const header = await FileHeaderManager.readHeader(filePath);
@@ -660,78 +734,82 @@ async function generateSiteJSON(options, requestHost, sub, pwd) {
* @returns {Promise