/* eslint-disable no-console, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */ import { AdminConfig } from './admin.types'; import { EpisodeSkipConfig, Favorite, IStorage, PlayRecord, User, UserSettings } from './types'; // 搜索历史最大条数 const SEARCH_HISTORY_LIMIT = 20; // D1 数据库接口 interface D1Database { prepare(sql: string): D1PreparedStatement; exec(sql: string): Promise; batch(statements: D1PreparedStatement[]): Promise; } interface D1PreparedStatement { bind(...values: any[]): D1PreparedStatement; first(colName?: string): Promise; run(): Promise; all(): Promise>; } interface D1Result { results: T[]; success: boolean; error?: string; meta: { changed_db: boolean; changes: number; last_row_id: number; duration: number; }; } interface D1ExecResult { count: number; duration: number; } // 获取全局D1数据库实例 function getD1Database(): D1Database { // 在 Cloudflare Pages/Workers 环境中,DB 是全局变量 if (typeof globalThis !== 'undefined' && (globalThis as any).DB) { return (globalThis as any).DB as D1Database; } // 回退到 process.env(用于本地开发) if ((process.env as any).DB) { return (process.env as any).DB as D1Database; } // 最后尝试从 globalThis.process.env if (typeof globalThis !== 'undefined' && (globalThis as any).process?.env?.DB) { return (globalThis as any).process.env.DB as D1Database; } throw new Error('D1 database not available. Make sure DB binding is configured.'); } export class D1Storage implements IStorage { private db: D1Database | null = null; private async getDatabase(): Promise { if (!this.db) { this.db = getD1Database(); } return this.db; } // 播放记录相关 async getPlayRecord( userName: string, key: string ): Promise { try { const db = await this.getDatabase(); const result = await db .prepare('SELECT * FROM play_records WHERE username = ? AND key = ?') .bind(userName, key) .first(); if (!result) return null; return { title: result.title, source_name: result.source_name, cover: result.cover, year: result.year, index: result.index_episode, total_episodes: result.total_episodes, play_time: result.play_time, total_time: result.total_time, save_time: result.save_time, search_title: result.search_title || undefined, }; } catch (err) { console.error('Failed to get play record:', err); throw err; } } async setPlayRecord( userName: string, key: string, record: PlayRecord ): Promise { try { const db = await this.getDatabase(); await db .prepare( ` INSERT OR REPLACE INTO play_records (username, key, title, source_name, cover, year, index_episode, total_episodes, play_time, total_time, save_time, search_title) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ` ) .bind( userName, key, record.title, record.source_name, record.cover, record.year, record.index, record.total_episodes, record.play_time, record.total_time, record.save_time, record.search_title || null ) .run(); } catch (err) { console.error('Failed to set play record:', err); throw err; } } async getAllPlayRecords( userName: string ): Promise> { try { const db = await this.getDatabase(); const result = await db .prepare( 'SELECT * FROM play_records WHERE username = ? ORDER BY save_time DESC' ) .bind(userName) .all(); const records: Record = {}; result.results.forEach((row: any) => { records[row.key] = { title: row.title, source_name: row.source_name, cover: row.cover, year: row.year, index: row.index_episode, total_episodes: row.total_episodes, play_time: row.play_time, total_time: row.total_time, save_time: row.save_time, search_title: row.search_title || undefined, }; }); return records; } catch (err) { console.error('Failed to get all play records:', err); throw err; } } async deletePlayRecord(userName: string, key: string): Promise { try { const db = await this.getDatabase(); await db .prepare('DELETE FROM play_records WHERE username = ? AND key = ?') .bind(userName, key) .run(); } catch (err) { console.error('Failed to delete play record:', err); throw err; } } // 收藏相关 async getFavorite(userName: string, key: string): Promise { try { const db = await this.getDatabase(); const result = await db .prepare('SELECT * FROM favorites WHERE username = ? AND key = ?') .bind(userName, key) .first(); if (!result) return null; return { title: result.title, source_name: result.source_name, cover: result.cover, year: result.year, total_episodes: result.total_episodes, save_time: result.save_time, search_title: result.search_title, }; } catch (err) { console.error('Failed to get favorite:', err); throw err; } } async setFavorite( userName: string, key: string, favorite: Favorite ): Promise { try { const db = await this.getDatabase(); await db .prepare( ` INSERT OR REPLACE INTO favorites (username, key, title, source_name, cover, year, total_episodes, save_time) VALUES (?, ?, ?, ?, ?, ?, ?, ?) ` ) .bind( userName, key, favorite.title, favorite.source_name, favorite.cover, favorite.year, favorite.total_episodes, favorite.save_time ) .run(); } catch (err) { console.error('Failed to set favorite:', err); throw err; } } async getAllFavorites(userName: string): Promise> { try { const db = await this.getDatabase(); const result = await db .prepare( 'SELECT * FROM favorites WHERE username = ? ORDER BY save_time DESC' ) .bind(userName) .all(); const favorites: Record = {}; result.results.forEach((row: any) => { favorites[row.key] = { title: row.title, source_name: row.source_name, cover: row.cover, year: row.year, total_episodes: row.total_episodes, save_time: row.save_time, search_title: row.search_title, }; }); return favorites; } catch (err) { console.error('Failed to get all favorites:', err); throw err; } } async deleteFavorite(userName: string, key: string): Promise { try { const db = await this.getDatabase(); await db .prepare('DELETE FROM favorites WHERE username = ? AND key = ?') .bind(userName, key) .run(); } catch (err) { console.error('Failed to delete favorite:', err); throw err; } } // 用户相关 async registerUser(userName: string, password: string): Promise { try { const db = await this.getDatabase(); await db .prepare('INSERT INTO users (username, password) VALUES (?, ?)') .bind(userName, password) .run(); } catch (err) { console.error('Failed to register user:', err); throw err; } } async verifyUser(userName: string, password: string): Promise { try { const db = await this.getDatabase(); const result = await db .prepare('SELECT password FROM users WHERE username = ?') .bind(userName) .first<{ password: string }>(); return result?.password === password; } catch (err) { console.error('Failed to verify user:', err); throw err; } } async checkUserExist(userName: string): Promise { try { const db = await this.getDatabase(); const result = await db .prepare('SELECT 1 FROM users WHERE username = ?') .bind(userName) .first(); return result !== null; } catch (err) { console.error('Failed to check user existence:', err); throw err; } } async changePassword(userName: string, newPassword: string): Promise { try { const db = await this.getDatabase(); await db .prepare('UPDATE users SET password = ? WHERE username = ?') .bind(newPassword, userName) .run(); } catch (err) { console.error('Failed to change password:', err); throw err; } } async deleteUser(userName: string): Promise { try { const db = await this.getDatabase(); const statements = [ db.prepare('DELETE FROM users WHERE username = ?').bind(userName), db .prepare('DELETE FROM play_records WHERE username = ?') .bind(userName), db.prepare('DELETE FROM favorites WHERE username = ?').bind(userName), db .prepare('DELETE FROM search_history WHERE username = ?') .bind(userName), ]; await db.batch(statements); } catch (err) { console.error('Failed to delete user:', err); throw err; } } // 搜索历史相关 async getSearchHistory(userName: string): Promise { try { const db = await this.getDatabase(); const result = await db .prepare( 'SELECT keyword FROM search_history WHERE username = ? ORDER BY created_at DESC LIMIT ?' ) .bind(userName, SEARCH_HISTORY_LIMIT) .all<{ keyword: string }>(); return result.results.map((row) => row.keyword); } catch (err) { console.error('Failed to get search history:', err); throw err; } } async addSearchHistory(userName: string, keyword: string): Promise { try { const db = await this.getDatabase(); // 先删除可能存在的重复记录 await db .prepare( 'DELETE FROM search_history WHERE username = ? AND keyword = ?' ) .bind(userName, keyword) .run(); // 添加新记录 await db .prepare('INSERT INTO search_history (username, keyword) VALUES (?, ?)') .bind(userName, keyword) .run(); // 保持历史记录条数限制 await db .prepare( ` DELETE FROM search_history WHERE username = ? AND id NOT IN ( SELECT id FROM search_history WHERE username = ? ORDER BY created_at DESC LIMIT ? ) ` ) .bind(userName, userName, SEARCH_HISTORY_LIMIT) .run(); } catch (err) { console.error('Failed to add search history:', err); throw err; } } async deleteSearchHistory(userName: string, keyword?: string): Promise { try { const db = await this.getDatabase(); if (keyword) { await db .prepare( 'DELETE FROM search_history WHERE username = ? AND keyword = ?' ) .bind(userName, keyword) .run(); } else { await db .prepare('DELETE FROM search_history WHERE username = ?') .bind(userName) .run(); } } catch (err) { console.error('Failed to delete search history:', err); throw err; } } // 用户列表 async getAllUsers(): Promise { try { const db = await this.getDatabase(); const result = await db .prepare('SELECT username, created_at FROM users ORDER BY created_at ASC') .all<{ username: string; created_at: string }>(); const ownerUsername = process.env.USERNAME || 'admin'; return result.results.map((row) => ({ username: row.username, role: row.username === ownerUsername ? 'owner' : 'user', created_at: row.created_at })); } catch (err) { console.error('Failed to get all users:', err); throw err; } } // 管理员配置相关 async getAdminConfig(): Promise { try { const db = await this.getDatabase(); const result = await db .prepare('SELECT config_value as config FROM admin_configs WHERE config_key = ? LIMIT 1') .bind('main_config') .first<{ config: string }>(); if (!result) return null; return JSON.parse(result.config) as AdminConfig; } catch (err) { console.error('Failed to get admin config:', err); throw err; } } async setAdminConfig(config: AdminConfig): Promise { try { const db = await this.getDatabase(); await db .prepare( 'INSERT OR REPLACE INTO admin_configs (config_key, config_value, description) VALUES (?, ?, ?)' ) .bind('main_config', JSON.stringify(config), '主要管理员配置') .run(); } catch (err) { console.error('Failed to set admin config:', err); throw err; } } // 跳过配置相关 async getSkipConfig( userName: string, key: string ): Promise { try { const db = await this.getDatabase(); const result = await db .prepare('SELECT * FROM skip_configs WHERE username = ? AND key = ?') .bind(userName, key) .first(); if (!result) return null; return { source: result.source, id: result.video_id, title: result.title, segments: JSON.parse(result.segments), updated_time: result.updated_time, }; } catch (err) { console.error('Failed to get skip config:', err); throw err; } } async setSkipConfig( userName: string, key: string, config: EpisodeSkipConfig ): Promise { try { const db = await this.getDatabase(); await db .prepare( ` INSERT OR REPLACE INTO skip_configs (username, key, source, video_id, title, segments, updated_time) VALUES (?, ?, ?, ?, ?, ?, ?) ` ) .bind( userName, key, config.source, config.id, config.title, JSON.stringify(config.segments), config.updated_time ) .run(); } catch (err) { console.error('Failed to set skip config:', err); throw err; } } async getAllSkipConfigs( userName: string ): Promise<{ [key: string]: EpisodeSkipConfig }> { try { const db = await this.getDatabase(); const result = await db .prepare('SELECT * FROM skip_configs WHERE username = ?') .bind(userName) .all(); const configs: { [key: string]: EpisodeSkipConfig } = {}; for (const row of result.results) { configs[row.key] = { source: row.source, id: row.video_id, title: row.title, segments: JSON.parse(row.segments), updated_time: row.updated_time, }; } return configs; } catch (err) { console.error('Failed to get all skip configs:', err); throw err; } } async deleteSkipConfig(userName: string, key: string): Promise { try { const db = await this.getDatabase(); await db .prepare('DELETE FROM skip_configs WHERE username = ? AND key = ?') .bind(userName, key) .run(); } catch (err) { console.error('Failed to delete skip config:', err); throw err; } } // ---------- 用户设置 ---------- async getUserSettings(userName: string): Promise { try { const db = await this.getDatabase(); const row = await db .prepare('SELECT settings FROM user_settings WHERE username = ?') .bind(userName) .first(); if (row && row.settings) { return JSON.parse(row.settings as string) as UserSettings; } return null; } catch (err) { console.error('Failed to get user settings:', err); throw err; } } async setUserSettings( userName: string, settings: UserSettings ): Promise { try { const db = await this.getDatabase(); await db .prepare(` INSERT OR REPLACE INTO user_settings (username, settings, updated_time) VALUES (?, ?, ?) `) .bind(userName, JSON.stringify(settings), Date.now()) .run(); } catch (err) { console.error('Failed to set user settings:', err); throw err; } } async updateUserSettings( userName: string, settings: Partial ): Promise { const current = await this.getUserSettings(userName); const defaultSettings: UserSettings = { filter_adult_content: true, theme: 'auto', language: 'zh-CN', auto_play: false, video_quality: 'auto' }; const updated: UserSettings = { ...defaultSettings, ...current, ...settings, filter_adult_content: settings.filter_adult_content ?? current?.filter_adult_content ?? true }; await this.setUserSettings(userName, updated); } }