feat: DSL 磁力聚合 — 搜索/解析/预览/投递 + cilisousuo 源 + 中英双语 + Docker 部署
This commit is contained in:
@@ -0,0 +1,116 @@
|
||||
import { getDb } from './mongo'
|
||||
|
||||
export type BtClientType = 'qbittorrent' | 'transmission' | 'aria2'
|
||||
|
||||
export interface BtClientConfig {
|
||||
id: string
|
||||
type: BtClientType
|
||||
name: string
|
||||
host: string
|
||||
port: number
|
||||
/** 自定义 RPC / API 路径,留空使用各客户端的默认路径 */
|
||||
path?: string
|
||||
username?: string
|
||||
password?: string
|
||||
downloadDir?: string
|
||||
useHttps?: boolean
|
||||
enabled?: boolean
|
||||
}
|
||||
|
||||
export interface AppSettings {
|
||||
/** 访问搜索源时使用的 HTTP 代理 */
|
||||
searchProxy: string
|
||||
searchTimeout: number
|
||||
metadataTimeout: number
|
||||
enabledProviders: string[]
|
||||
btClients: BtClientConfig[]
|
||||
}
|
||||
|
||||
/** 本机常见 BT 客户端的默认端点,用户没配置时自动探测这些 */
|
||||
export const DEFAULT_CLIENTS: BtClientConfig[] = [
|
||||
{ id: 'qbittorrent-local', type: 'qbittorrent', name: 'qBittorrent(本机)', host: '127.0.0.1', port: 8080, username: 'admin', password: '', enabled: true },
|
||||
{ id: 'transmission-local', type: 'transmission', name: 'Transmission(本机)', host: '127.0.0.1', port: 9091, path: '/transmission/rpc', username: '', password: '', enabled: true },
|
||||
{ id: 'aria2-local', type: 'aria2', name: 'aria2(本机)', host: '127.0.0.1', port: 6800, path: '/jsonrpc', password: '', enabled: true }
|
||||
]
|
||||
|
||||
const SETTINGS_ID = 'app'
|
||||
|
||||
function envDefaults(): AppSettings {
|
||||
return {
|
||||
searchProxy: process.env.SEARCH_PROXY || '',
|
||||
searchTimeout: Number(process.env.SEARCH_TIMEOUT || 12000),
|
||||
metadataTimeout: Number(process.env.METADATA_TIMEOUT || 30000),
|
||||
enabledProviders: ['apibay', 'nyaa', 'cilisousuo'],
|
||||
btClients: DEFAULT_CLIENTS.map((c) => ({ ...c }))
|
||||
}
|
||||
}
|
||||
|
||||
let cache: { value: AppSettings; at: number; ttl: number } | null = null
|
||||
|
||||
export function invalidateSettingsCache() {
|
||||
cache = null
|
||||
}
|
||||
|
||||
export async function loadSettings(): Promise<AppSettings> {
|
||||
if (cache && Date.now() - cache.at < cache.ttl) return cache.value
|
||||
const base = envDefaults()
|
||||
try {
|
||||
const db = await getDb()
|
||||
const doc = await db.collection('settings').findOne({ _id: SETTINGS_ID as never })
|
||||
const stored = (doc?.value || {}) as Partial<AppSettings>
|
||||
const merged: AppSettings = { ...base, ...stored }
|
||||
if (!Array.isArray(merged.btClients) || merged.btClients.length === 0) merged.btClients = base.btClients
|
||||
if (!Array.isArray(merged.enabledProviders) || merged.enabledProviders.length === 0) {
|
||||
merged.enabledProviders = base.enabledProviders
|
||||
}
|
||||
cache = { value: merged, at: Date.now(), ttl: 5000 }
|
||||
return merged
|
||||
} catch {
|
||||
// MongoDB 不可用:短时缓存默认值,避免每个请求都卡 3 秒去连库
|
||||
cache = { value: base, at: Date.now(), ttl: 3000 }
|
||||
return base
|
||||
}
|
||||
}
|
||||
|
||||
export async function saveSettings(patch: Partial<AppSettings>): Promise<AppSettings> {
|
||||
const db = await getDb()
|
||||
const current = await loadSettings()
|
||||
const next: AppSettings = { ...current, ...patch }
|
||||
await db
|
||||
.collection('settings')
|
||||
.updateOne(
|
||||
{ _id: SETTINGS_ID as never },
|
||||
{ $set: { value: next, updatedAt: new Date() } },
|
||||
{ upsert: true }
|
||||
)
|
||||
cache = { value: next, at: Date.now(), ttl: 5000 }
|
||||
return next
|
||||
}
|
||||
|
||||
export function normalizeClient(raw: Partial<BtClientConfig>, index = 0): BtClientConfig {
|
||||
const type = (raw.type || 'qbittorrent') as BtClientType
|
||||
return {
|
||||
id: raw.id || `${type}-${Date.now().toString(36)}-${index}`,
|
||||
type,
|
||||
name: raw.name || type,
|
||||
host: raw.host || '127.0.0.1',
|
||||
port: Number(raw.port) || defaultPort(type),
|
||||
path: raw.path || undefined,
|
||||
username: raw.username || '',
|
||||
password: raw.password || '',
|
||||
downloadDir: raw.downloadDir || '',
|
||||
useHttps: !!raw.useHttps,
|
||||
enabled: raw.enabled !== false
|
||||
}
|
||||
}
|
||||
|
||||
export function defaultPort(type: BtClientType): number {
|
||||
if (type === 'transmission') return 9091
|
||||
if (type === 'aria2') return 6800
|
||||
return 8080
|
||||
}
|
||||
|
||||
export function baseUrlOf(cfg: BtClientConfig): string {
|
||||
const scheme = cfg.useHttps ? 'https' : 'http'
|
||||
return `${scheme}://${cfg.host}:${cfg.port}`
|
||||
}
|
||||
Reference in New Issue
Block a user