10 Commits

9 changed files with 63 additions and 18 deletions

View File

@@ -1 +1 @@
0.4.0 0.4.9

View File

@@ -4,7 +4,7 @@ cd "$(dirname "$0")/source_clean"
VERSION=$(cat ../VERSION) VERSION=$(cat ../VERSION)
echo "🔨 Building CloudSearch v${VERSION}..." echo "🔨 Building CloudSearch v${VERSION}..."
cp ../VERSION ./VERSION
docker build -t cloudsearch-app:v${VERSION} -t cloudsearch-app:latest . docker build -t cloudsearch-app:v${VERSION} -t cloudsearch-app:latest .
echo "✅ Built: cloudsearch-app:v${VERSION} + cloudsearch-app:latest" echo "✅ Built: cloudsearch-app:v${VERSION} + cloudsearch-app:latest"

View File

@@ -70,6 +70,7 @@ services:
- CORS_ORIGIN=${CORS_ORIGIN} - CORS_ORIGIN=${CORS_ORIGIN}
# ── 数据库 & 缓存 ── # ── 数据库 & 缓存 ──
- DATA_DIR=/data
- DB_PATH=${DB_PATH:-/data/database.sqlite} - DB_PATH=${DB_PATH:-/data/database.sqlite}
- REDIS_URL=${REDIS_URL:-redis://redis:6379} - REDIS_URL=${REDIS_URL:-redis://redis:6379}

View File

@@ -1 +1 @@
0.4.0 0.4.9

11
source_clean/build.sh Executable file
View File

@@ -0,0 +1,11 @@
#!/bin/bash
set -e
cd "$(dirname "$0")/source_clean"
VERSION=$(cat ../VERSION)
echo "🔨 Building CloudSearch v${VERSION}..."
docker build -t cloudsearch-app:v${VERSION} -t cloudsearch-app:latest .
echo "✅ Built: cloudsearch-app:v${VERSION} + cloudsearch-app:latest"
echo " Run: docker-compose up -d app"

Binary file not shown.

View File

@@ -222,7 +222,8 @@ export async function testCloudConnection(id: number): Promise<{
return { success: false, message: 'Cookie not configured' }; return { success: false, message: 'Cookie not configured' };
} }
const cookie = decryptCookie(config.cookie); // config.cookie is already decrypted by getCloudConfigById
const cookie = config.cookie;
try { try {
let valid = false; let valid = false;

View File

@@ -149,6 +149,8 @@ function migrateSaveRecords(db: Database.Database): void {
{ col: 'request_url', def: 'TEXT' }, { col: 'request_url', def: 'TEXT' },
{ col: 'ip_location', def: 'TEXT' }, { col: 'ip_location', def: 'TEXT' },
{ col: 'original_folder_name', def: 'TEXT' }, { col: 'original_folder_name', def: 'TEXT' },
{ col: 'config_id', def: 'INTEGER' },
{ col: 'promotion_account', def: 'TEXT' },
]; ];
for (const { col, def } of newCols) { for (const { col, def } of newCols) {
try { try {
@@ -289,18 +291,19 @@ function seedSystemConfigs(db: Database.Database): void {
{ key: 'search_proxy_url', value: '', description: '搜索代理地址 (如 http://127.0.0.1:7890)' }, { key: 'search_proxy_url', value: '', description: '搜索代理地址 (如 http://127.0.0.1:7890)' },
{ key: 'search_strategy', value: 'wait_all', description: '搜索结果展示方式: wait_all=等待全部后展示, stream_channel=频道逐步展示' }, { key: 'search_strategy', value: 'wait_all', description: '搜索结果展示方式: wait_all=等待全部后展示, stream_channel=频道逐步展示' },
{ key: 'link_validation_enabled', value: 'true', description: '资源链接有效性检测开关true/false' }, { key: 'link_validation_enabled', value: 'true', description: '资源链接有效性检测开关true/false' },
{ key: 'cloud_enabled_quark', value: 'true', description: '夸克网盘' }, { key: 'link_invalid_keywords', value: '', description: '链接失效关键词(一行一条,命中的链接判定为失效)' },
{ key: 'cloud_enabled_baidu', value: 'true', description: '百度网盘' }, { key: 'cloud_type_quark_enabled', value: 'true', description: '夸克网盘' },
{ key: 'cloud_enabled_aliyun', value: 'true', description: '阿里云盘' }, { key: 'cloud_type_baidu_enabled', value: 'true', description: '百度网盘' },
{ key: 'cloud_enabled_115', value: 'true', description: '115 网盘' }, { key: 'cloud_type_aliyun_enabled', value: 'true', description: '阿里云盘' },
{ key: 'cloud_enabled_tianyi', value: 'true', description: '天翼云盘' }, { key: 'cloud_type_115_enabled', value: 'true', description: '115 网盘' },
{ key: 'cloud_enabled_123pan', value: 'true', description: '123 云盘' }, { key: 'cloud_type_tianyi_enabled', value: 'true', description: '天翼云盘' },
{ key: 'cloud_enabled_uc', value: 'true', description: 'UC 网盘' }, { key: 'cloud_type_123pan_enabled', value: 'true', description: '123 云盘' },
{ key: 'cloud_enabled_xunlei', value: 'true', description: '迅雷网盘' }, { key: 'cloud_type_uc_enabled', value: 'true', description: 'UC 网盘' },
{ key: 'cloud_enabled_pikpak', value: 'true', description: 'PikPak 网盘' }, { key: 'cloud_type_xunlei_enabled', value: 'true', description: '迅雷网盘' },
{ key: 'cloud_enabled_magnet', value: 'true', description: '磁力链接' }, { key: 'cloud_type_pikpak_enabled', value: 'true', description: 'PikPak 网盘' },
{ key: 'cloud_enabled_ed2k', value: 'true', description: '电驴链接' }, { key: 'cloud_type_magnet_enabled', value: 'true', description: '磁力链接' },
{ key: 'cloud_enabled_others', value: 'false', description: '其他类型(默认关闭)' }, { key: 'cloud_type_ed2k_enabled', value: 'true', description: '电驴链接' },
{ key: 'cloud_type_others_enabled', value: 'false', description: '其他类型(默认关闭)' },
{ key: 'search_result_limit', value: '10', description: '每类网盘最多展示的有效结果数' }, { key: 'search_result_limit', value: '10', description: '每类网盘最多展示的有效结果数' },
{ key: 'search_fallback_image', value: '', description: '无图资源的兜底封面图 URL留空使用渐变色' }, { key: 'search_fallback_image', value: '', description: '无图资源的兜底封面图 URL留空使用渐变色' },
{ key: 'site_logo', value: '', description: '网站 LOGO 图片 URL留空使用默认图标/文字)' }, { key: 'site_logo', value: '', description: '网站 LOGO 图片 URL留空使用默认图标/文字)' },
@@ -309,6 +312,7 @@ function seedSystemConfigs(db: Database.Database): void {
{ key: 'site_marquee', value: '📢 欢迎使用CloudSearch所有资源仅供学习交流请于下载后24小时内删除', description: '搜索栏下方滚动通知文字(从右往左滚动显示)' }, { key: 'site_marquee', value: '📢 欢迎使用CloudSearch所有资源仅供学习交流请于下载后24小时内删除', description: '搜索栏下方滚动通知文字(从右往左滚动显示)' },
{ key: 'tmdb_api_token', value: '', description: 'TMDB API 读取令牌(用于增强豆瓣内容信息)' }, { key: 'tmdb_api_token', value: '', description: 'TMDB API 读取令牌(用于增强豆瓣内容信息)' },
{ key: 'ip_geo_api_url', value: '', description: 'IP 归属地查询接口({ip} 会被替换为实际IP留空则禁用' }, { key: 'ip_geo_api_url', value: '', description: 'IP 归属地查询接口({ip} 会被替换为实际IP留空则禁用' },
{ key: 'ip_geo_api_id', value: '', description: 'IP 归属地 API IDapihz.cn 接口)' },
{ key: 'ip_geo_api_key', value: '', description: 'IP 归属地备用 API Key留空使用默认' }, { key: 'ip_geo_api_key', value: '', description: 'IP 归属地备用 API Key留空使用默认' },
{ key: 'title_filter_rules', value: '', description: '搜索结果标题过滤规则(一行一条:纯文本直接移除 / 正则用/包围/' }, { key: 'title_filter_rules', value: '', description: '搜索结果标题过滤规则(一行一条:纯文本直接移除 / 正则用/包围/' },
{ key: 'timezone', value: 'Asia/Shanghai', description: '系统时区(如 Asia/Shanghai、America/New_York、UTC' }, { key: 'timezone', value: 'Asia/Shanghai', description: '系统时区(如 Asia/Shanghai、America/New_York、UTC' },
@@ -328,6 +332,16 @@ function seedSystemConfigs(db: Database.Database): void {
{ key: 'search_all_channels', value: 'false', description: '使用所有频道参与搜索(包含未启用频道)' }, { key: 'search_all_channels', value: 'false', description: '使用所有频道参与搜索(包含未启用频道)' },
{ key: 'ip_geo_provider', value: 'apihz', description: 'IP 归属地查询接口提供商' }, { key: 'ip_geo_provider', value: 'apihz', description: 'IP 归属地查询接口提供商' },
{ key: 'auto_update_enabled', value: 'false', description: '自动更新镜像(预留,暂未实现)' }, { key: 'auto_update_enabled', value: 'false', description: '自动更新镜像(预留,暂未实现)' },
{ key: 'cleanup_auto_refresh_storage', value: 'false', description: '自动刷新网盘空间信息(每天检查一次)' },
{ key: 'cleanup_verify_enabled', value: 'false', description: '启用转存后自动验证链接有效性' },
{ key: 'cleanup_verify_interval', value: '3600', description: '自动验证间隔(秒)' },
{ key: 'cleanup_whitelist_dirs', value: '', description: '清理文件白名单目录(逗号分隔,保留不删)' },
{ key: 'proxy_url', value: '', description: 'HTTP 代理地址(用于搜索请求代理)' },
{ key: 'quark_ad_keywords', value: '', description: '夸克广告文件关键词(逗号分隔)' },
{ key: 'quark_sus_extensions', value: '', description: '夸克可疑文件后缀(逗号分隔)' },
{ key: 'quark_warning_folder_names', value: '', description: '夸克警示文件夹名称(逗号分隔)' },
{ key: 'storage_refresh_interval', value: '86400', description: '空间信息刷新间隔默认24小时' },
]; ];
const insert = db.prepare( const insert = db.prepare(
'INSERT OR IGNORE INTO system_configs (key, value, description) VALUES (?, ?, ?)' 'INSERT OR IGNORE INTO system_configs (key, value, description) VALUES (?, ?, ?)'

View File

@@ -154,7 +154,7 @@ router.get('/admin/cloud-configs', (_req: Request, res: Response) => {
}); });
/** POST /api/admin/cloud-configs — create or smart-replace a cloud config */ /** POST /api/admin/cloud-configs — create or smart-replace a cloud config */
router.post('/admin/cloud-configs', (req: Request, res: Response) => { router.post('/admin/cloud-configs', async (req: Request, res: Response) => {
try { try {
const data = req.body; const data = req.body;
if (!data.cloud_type) { if (!data.cloud_type) {
@@ -164,6 +164,24 @@ router.post('/admin/cloud-configs', (req: Request, res: Response) => {
// Normalize is_active: frontend sends boolean, SQLite needs 0/1 // Normalize is_active: frontend sends boolean, SQLite needs 0/1
if (typeof data.is_active === 'boolean') data.is_active = data.is_active ? 1 : 0; if (typeof data.is_active === 'boolean') data.is_active = data.is_active ? 1 : 0;
const saved = saveCloudConfig(data); const saved = saveCloudConfig(data);
// Auto-validate if cookie was provided (best-effort, non-blocking)
if (data.cookie && saved.id) {
try {
const result = await testCloudConnectionWithCookie(data.cloud_type, data.cookie);
if (result.success) {
const updateData: any = { id: saved.id, cloud_type: data.cloud_type };
if (result.nickname) updateData.nickname = result.nickname;
if (result.storage_used) updateData.storage_used = result.storage_used;
if (result.storage_total) updateData.storage_total = result.storage_total;
saveCloudConfig(updateData);
Object.assign(saved, { nickname: result.nickname, storage_used: result.storage_used, storage_total: result.storage_total });
}
} catch (_) {
// Auto-validation is best-effort, don't fail the save
}
}
res.json(saved); res.json(saved);
} catch (err: any) { } catch (err: any) {
res.status(500).json({ error: err.message || 'Failed to save cloud config' }); res.status(500).json({ error: err.message || 'Failed to save cloud config' });
@@ -508,7 +526,7 @@ router.post('/admin/test-external-service', async (req: Request, res: Response)
break; break;
} }
case 'tmdb': { case 'tmdb': {
const tmdbToken = token || getSystemConfig('tmdb_api_key') || ''; const tmdbToken = token || getSystemConfig('tmdb_api_token') || '';
if (!tmdbToken) { if (!tmdbToken) {
res.json({ ok: false, info: 'TMDB API Key not configured' }); res.json({ ok: false, info: 'TMDB API Key not configured' });
return; return;