2025-08-12 21:50:58 +08:00
|
|
|
|
/* eslint-disable no-console, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */
|
|
|
|
|
|
|
|
|
|
|
|
import { AdminConfig } from './admin.types';
|
2025-08-15 19:26:13 +08:00
|
|
|
|
import { KvrocksStorage } from './kvrocks.db';
|
2025-08-12 21:50:58 +08:00
|
|
|
|
import { RedisStorage } from './redis.db';
|
2026-01-23 16:41:49 +08:00
|
|
|
|
import { DanmakuFilterConfig,Favorite, IStorage, PlayRecord, SkipConfig } from './types';
|
2025-08-12 21:50:58 +08:00
|
|
|
|
import { UpstashRedisStorage } from './upstash.db';
|
|
|
|
|
|
|
2026-01-30 14:27:43 +08:00
|
|
|
|
// storage type 常量: 'localstorage' | 'redis' | 'upstash' | 'kvrocks' | 'd1',默认 'localstorage'
|
2025-08-12 21:50:58 +08:00
|
|
|
|
const STORAGE_TYPE =
|
|
|
|
|
|
(process.env.NEXT_PUBLIC_STORAGE_TYPE as
|
|
|
|
|
|
| 'localstorage'
|
|
|
|
|
|
| 'redis'
|
|
|
|
|
|
| 'upstash'
|
2025-08-15 19:26:13 +08:00
|
|
|
|
| 'kvrocks'
|
2026-01-30 14:27:43 +08:00
|
|
|
|
| 'd1'
|
2025-08-12 21:50:58 +08:00
|
|
|
|
| undefined) || 'localstorage';
|
|
|
|
|
|
|
|
|
|
|
|
// 创建存储实例
|
|
|
|
|
|
function createStorage(): IStorage {
|
|
|
|
|
|
switch (STORAGE_TYPE) {
|
|
|
|
|
|
case 'redis':
|
|
|
|
|
|
return new RedisStorage();
|
|
|
|
|
|
case 'upstash':
|
|
|
|
|
|
return new UpstashRedisStorage();
|
2025-08-15 19:26:13 +08:00
|
|
|
|
case 'kvrocks':
|
|
|
|
|
|
return new KvrocksStorage();
|
2026-01-30 14:27:43 +08:00
|
|
|
|
case 'd1':
|
|
|
|
|
|
// D1Storage 只能在服务端使用,客户端会报错
|
|
|
|
|
|
if (typeof window !== 'undefined') {
|
|
|
|
|
|
throw new Error('D1Storage can only be used on the server side');
|
|
|
|
|
|
}
|
|
|
|
|
|
const adapter = getD1Adapter();
|
|
|
|
|
|
// 动态导入 D1Storage 以避免客户端打包
|
|
|
|
|
|
const { D1Storage } = require('./d1.db');
|
|
|
|
|
|
return new D1Storage(adapter);
|
2025-08-12 21:50:58 +08:00
|
|
|
|
case 'localstorage':
|
|
|
|
|
|
default:
|
|
|
|
|
|
return null as unknown as IStorage;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-30 14:27:43 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 获取 D1 适配器
|
|
|
|
|
|
* 开发环境:使用 better-sqlite3
|
|
|
|
|
|
* 生产环境:使用 Cloudflare D1
|
|
|
|
|
|
*/
|
|
|
|
|
|
function getD1Adapter(): any {
|
|
|
|
|
|
// 动态导入适配器以避免客户端打包
|
|
|
|
|
|
const { CloudflareD1Adapter, SQLiteAdapter } = require('./d1-adapter');
|
|
|
|
|
|
|
|
|
|
|
|
// 生产环境:Cloudflare Workers/Pages
|
|
|
|
|
|
if (typeof process !== 'undefined' && (process as any).env?.DB) {
|
|
|
|
|
|
console.log('Using Cloudflare D1 database');
|
|
|
|
|
|
return new CloudflareD1Adapter((process as any).env.DB);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 开发环境:better-sqlite3
|
|
|
|
|
|
if (process.env.NODE_ENV === 'development' || process.env.NODE_ENV === 'test') {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const Database = require('better-sqlite3');
|
|
|
|
|
|
const path = require('path');
|
|
|
|
|
|
const fs = require('fs');
|
|
|
|
|
|
|
|
|
|
|
|
const dbPath = path.join(process.cwd(), '.data', 'moontv.db');
|
|
|
|
|
|
|
|
|
|
|
|
// 检查数据库文件是否存在
|
|
|
|
|
|
if (!fs.existsSync(dbPath)) {
|
|
|
|
|
|
console.error('❌ SQLite database not found. Please run: npm run init:sqlite');
|
|
|
|
|
|
throw new Error('SQLite database not initialized');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const db = new Database(dbPath);
|
|
|
|
|
|
db.pragma('journal_mode = WAL'); // 启用 WAL 模式提升性能
|
|
|
|
|
|
|
|
|
|
|
|
console.log('Using SQLite database (development mode)');
|
|
|
|
|
|
console.log('Database location:', dbPath);
|
|
|
|
|
|
|
|
|
|
|
|
return new SQLiteAdapter(db);
|
|
|
|
|
|
} catch (err) {
|
|
|
|
|
|
console.error('Failed to initialize SQLite:', err);
|
|
|
|
|
|
throw err;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
throw new Error('D1 database not available. Set NEXT_PUBLIC_STORAGE_TYPE to another option or configure D1.');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-12 21:50:58 +08:00
|
|
|
|
// 单例存储实例
|
|
|
|
|
|
let storageInstance: IStorage | null = null;
|
|
|
|
|
|
|
2025-12-18 23:31:10 +08:00
|
|
|
|
export function getStorage(): IStorage {
|
2025-08-12 21:50:58 +08:00
|
|
|
|
if (!storageInstance) {
|
|
|
|
|
|
storageInstance = createStorage();
|
|
|
|
|
|
}
|
|
|
|
|
|
return storageInstance;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 工具函数:生成存储key
|
|
|
|
|
|
export function generateStorageKey(source: string, id: string): string {
|
|
|
|
|
|
return `${source}+${id}`;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 导出便捷方法
|
|
|
|
|
|
export class DbManager {
|
|
|
|
|
|
private storage: IStorage;
|
|
|
|
|
|
|
|
|
|
|
|
constructor() {
|
|
|
|
|
|
this.storage = getStorage();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 播放记录相关方法
|
|
|
|
|
|
async getPlayRecord(
|
|
|
|
|
|
userName: string,
|
|
|
|
|
|
source: string,
|
|
|
|
|
|
id: string
|
|
|
|
|
|
): Promise<PlayRecord | null> {
|
|
|
|
|
|
const key = generateStorageKey(source, id);
|
|
|
|
|
|
return this.storage.getPlayRecord(userName, key);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async savePlayRecord(
|
|
|
|
|
|
userName: string,
|
|
|
|
|
|
source: string,
|
|
|
|
|
|
id: string,
|
|
|
|
|
|
record: PlayRecord
|
|
|
|
|
|
): Promise<void> {
|
|
|
|
|
|
const key = generateStorageKey(source, id);
|
|
|
|
|
|
await this.storage.setPlayRecord(userName, key, record);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async getAllPlayRecords(userName: string): Promise<{
|
|
|
|
|
|
[key: string]: PlayRecord;
|
|
|
|
|
|
}> {
|
|
|
|
|
|
return this.storage.getAllPlayRecords(userName);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async deletePlayRecord(
|
|
|
|
|
|
userName: string,
|
|
|
|
|
|
source: string,
|
|
|
|
|
|
id: string
|
|
|
|
|
|
): Promise<void> {
|
|
|
|
|
|
const key = generateStorageKey(source, id);
|
|
|
|
|
|
await this.storage.deletePlayRecord(userName, key);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 收藏相关方法
|
|
|
|
|
|
async getFavorite(
|
|
|
|
|
|
userName: string,
|
|
|
|
|
|
source: string,
|
|
|
|
|
|
id: string
|
|
|
|
|
|
): Promise<Favorite | null> {
|
|
|
|
|
|
const key = generateStorageKey(source, id);
|
|
|
|
|
|
return this.storage.getFavorite(userName, key);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async saveFavorite(
|
|
|
|
|
|
userName: string,
|
|
|
|
|
|
source: string,
|
|
|
|
|
|
id: string,
|
|
|
|
|
|
favorite: Favorite
|
|
|
|
|
|
): Promise<void> {
|
|
|
|
|
|
const key = generateStorageKey(source, id);
|
|
|
|
|
|
await this.storage.setFavorite(userName, key, favorite);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async getAllFavorites(
|
|
|
|
|
|
userName: string
|
|
|
|
|
|
): Promise<{ [key: string]: Favorite }> {
|
|
|
|
|
|
return this.storage.getAllFavorites(userName);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async deleteFavorite(
|
|
|
|
|
|
userName: string,
|
|
|
|
|
|
source: string,
|
|
|
|
|
|
id: string
|
|
|
|
|
|
): Promise<void> {
|
|
|
|
|
|
const key = generateStorageKey(source, id);
|
|
|
|
|
|
await this.storage.deleteFavorite(userName, key);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async isFavorited(
|
|
|
|
|
|
userName: string,
|
|
|
|
|
|
source: string,
|
|
|
|
|
|
id: string
|
|
|
|
|
|
): Promise<boolean> {
|
|
|
|
|
|
const favorite = await this.getFavorite(userName, source, id);
|
|
|
|
|
|
return favorite !== null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-31 23:27:15 +08:00
|
|
|
|
|
2025-08-12 21:50:58 +08:00
|
|
|
|
async verifyUser(userName: string, password: string): Promise<boolean> {
|
|
|
|
|
|
return this.storage.verifyUser(userName, password);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查用户是否已存在
|
|
|
|
|
|
async checkUserExist(userName: string): Promise<boolean> {
|
|
|
|
|
|
return this.storage.checkUserExist(userName);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-15 18:47:51 +08:00
|
|
|
|
async changePassword(userName: string, newPassword: string): Promise<void> {
|
|
|
|
|
|
await this.storage.changePassword(userName, newPassword);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async deleteUser(userName: string): Promise<void> {
|
|
|
|
|
|
await this.storage.deleteUser(userName);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-24 00:24:50 +08:00
|
|
|
|
// ---------- 用户相关(新版本) ----------
|
|
|
|
|
|
async createUserV2(
|
|
|
|
|
|
userName: string,
|
|
|
|
|
|
password: string,
|
|
|
|
|
|
role: 'owner' | 'admin' | 'user' = 'user',
|
|
|
|
|
|
tags?: string[],
|
|
|
|
|
|
oidcSub?: string,
|
|
|
|
|
|
enabledApis?: string[]
|
|
|
|
|
|
): Promise<void> {
|
|
|
|
|
|
if (typeof (this.storage as any).createUserV2 === 'function') {
|
|
|
|
|
|
await (this.storage as any).createUserV2(userName, password, role, tags, oidcSub, enabledApis);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async verifyUserV2(userName: string, password: string): Promise<boolean> {
|
|
|
|
|
|
if (typeof (this.storage as any).verifyUserV2 === 'function') {
|
|
|
|
|
|
return (this.storage as any).verifyUserV2(userName, password);
|
|
|
|
|
|
}
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async getUserInfoV2(userName: string): Promise<{
|
|
|
|
|
|
role: 'owner' | 'admin' | 'user';
|
|
|
|
|
|
banned: boolean;
|
|
|
|
|
|
tags?: string[];
|
|
|
|
|
|
oidcSub?: string;
|
|
|
|
|
|
enabledApis?: string[];
|
|
|
|
|
|
created_at: number;
|
2025-12-30 21:00:10 +08:00
|
|
|
|
playrecord_migrated?: boolean;
|
2025-12-31 01:11:47 +08:00
|
|
|
|
favorite_migrated?: boolean;
|
2026-01-01 10:01:35 +08:00
|
|
|
|
skip_migrated?: boolean;
|
2025-12-24 00:24:50 +08:00
|
|
|
|
} | null> {
|
|
|
|
|
|
if (typeof (this.storage as any).getUserInfoV2 === 'function') {
|
|
|
|
|
|
return (this.storage as any).getUserInfoV2(userName);
|
|
|
|
|
|
}
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async updateUserInfoV2(
|
|
|
|
|
|
userName: string,
|
|
|
|
|
|
updates: {
|
|
|
|
|
|
role?: 'owner' | 'admin' | 'user';
|
|
|
|
|
|
banned?: boolean;
|
|
|
|
|
|
tags?: string[];
|
|
|
|
|
|
oidcSub?: string;
|
|
|
|
|
|
enabledApis?: string[];
|
|
|
|
|
|
}
|
|
|
|
|
|
): Promise<void> {
|
|
|
|
|
|
if (typeof (this.storage as any).updateUserInfoV2 === 'function') {
|
|
|
|
|
|
await (this.storage as any).updateUserInfoV2(userName, updates);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async changePasswordV2(userName: string, newPassword: string): Promise<void> {
|
|
|
|
|
|
if (typeof (this.storage as any).changePasswordV2 === 'function') {
|
|
|
|
|
|
await (this.storage as any).changePasswordV2(userName, newPassword);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async checkUserExistV2(userName: string): Promise<boolean> {
|
|
|
|
|
|
if (typeof (this.storage as any).checkUserExistV2 === 'function') {
|
|
|
|
|
|
return (this.storage as any).checkUserExistV2(userName);
|
|
|
|
|
|
}
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async getUserByOidcSub(oidcSub: string): Promise<string | null> {
|
|
|
|
|
|
if (typeof (this.storage as any).getUserByOidcSub === 'function') {
|
|
|
|
|
|
return (this.storage as any).getUserByOidcSub(oidcSub);
|
|
|
|
|
|
}
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async getUserListV2(
|
2026-01-23 16:41:49 +08:00
|
|
|
|
offset = 0,
|
|
|
|
|
|
limit = 20,
|
2025-12-24 00:24:50 +08:00
|
|
|
|
ownerUsername?: string
|
|
|
|
|
|
): Promise<{
|
|
|
|
|
|
users: Array<{
|
|
|
|
|
|
username: string;
|
|
|
|
|
|
role: 'owner' | 'admin' | 'user';
|
|
|
|
|
|
banned: boolean;
|
|
|
|
|
|
tags?: string[];
|
2025-12-24 00:42:28 +08:00
|
|
|
|
oidcSub?: string;
|
2025-12-24 00:24:50 +08:00
|
|
|
|
enabledApis?: string[];
|
|
|
|
|
|
created_at: number;
|
|
|
|
|
|
}>;
|
|
|
|
|
|
total: number;
|
|
|
|
|
|
}> {
|
|
|
|
|
|
if (typeof (this.storage as any).getUserListV2 === 'function') {
|
|
|
|
|
|
return (this.storage as any).getUserListV2(offset, limit, ownerUsername);
|
|
|
|
|
|
}
|
|
|
|
|
|
return { users: [], total: 0 };
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async deleteUserV2(userName: string): Promise<void> {
|
|
|
|
|
|
if (typeof (this.storage as any).deleteUserV2 === 'function') {
|
|
|
|
|
|
await (this.storage as any).deleteUserV2(userName);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async getUsersByTag(tagName: string): Promise<string[]> {
|
|
|
|
|
|
if (typeof (this.storage as any).getUsersByTag === 'function') {
|
|
|
|
|
|
return (this.storage as any).getUsersByTag(tagName);
|
|
|
|
|
|
}
|
|
|
|
|
|
return [];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-30 21:00:10 +08:00
|
|
|
|
// ---------- 播放记录迁移 ----------
|
|
|
|
|
|
async migratePlayRecords(userName: string): Promise<void> {
|
|
|
|
|
|
if (typeof (this.storage as any).migratePlayRecords === 'function') {
|
|
|
|
|
|
await (this.storage as any).migratePlayRecords(userName);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-30 22:00:36 +08:00
|
|
|
|
// ---------- 收藏迁移 ----------
|
|
|
|
|
|
async migrateFavorites(userName: string): Promise<void> {
|
|
|
|
|
|
if (typeof (this.storage as any).migrateFavorites === 'function') {
|
|
|
|
|
|
await (this.storage as any).migrateFavorites(userName);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-01 10:01:35 +08:00
|
|
|
|
// ---------- 跳过配置迁移 ----------
|
|
|
|
|
|
async migrateSkipConfigs(userName: string): Promise<void> {
|
|
|
|
|
|
if (typeof (this.storage as any).migrateSkipConfigs === 'function') {
|
|
|
|
|
|
await (this.storage as any).migrateSkipConfigs(userName);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-24 00:24:50 +08:00
|
|
|
|
// ---------- 数据迁移 ----------
|
|
|
|
|
|
async migrateUsersFromConfig(adminConfig: AdminConfig): Promise<void> {
|
|
|
|
|
|
if (typeof (this.storage as any).createUserV2 !== 'function') {
|
|
|
|
|
|
throw new Error('当前存储类型不支持新版用户存储');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const users = adminConfig.UserConfig.Users;
|
|
|
|
|
|
if (!users || users.length === 0) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
console.log(`开始迁移 ${users.length} 个用户...`);
|
|
|
|
|
|
|
|
|
|
|
|
for (const user of users) {
|
|
|
|
|
|
try {
|
2025-12-24 20:13:09 +08:00
|
|
|
|
// 跳过环境变量中的站长(站长使用环境变量认证,不需要迁移)
|
|
|
|
|
|
if (user.username === process.env.USERNAME) {
|
2025-12-24 00:24:50 +08:00
|
|
|
|
console.log(`跳过站长 ${user.username} 的迁移`);
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查用户是否已经迁移
|
|
|
|
|
|
const exists = await this.checkUserExistV2(user.username);
|
|
|
|
|
|
if (exists) {
|
|
|
|
|
|
console.log(`用户 ${user.username} 已存在,跳过迁移`);
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取密码
|
|
|
|
|
|
let password = '';
|
|
|
|
|
|
|
|
|
|
|
|
// 如果是OIDC用户,生成随机密码(OIDC用户不需要密码登录)
|
|
|
|
|
|
if ((user as any).oidcSub) {
|
|
|
|
|
|
password = crypto.randomUUID();
|
|
|
|
|
|
console.log(`用户 ${user.username} (OIDC用户) 使用随机密码迁移`);
|
|
|
|
|
|
}
|
|
|
|
|
|
// 尝试从旧的存储中获取密码
|
|
|
|
|
|
else {
|
|
|
|
|
|
try {
|
|
|
|
|
|
if ((this.storage as any).client) {
|
|
|
|
|
|
const storedPassword = await (this.storage as any).client.get(`u:${user.username}:pwd`);
|
|
|
|
|
|
if (storedPassword) {
|
|
|
|
|
|
password = storedPassword;
|
|
|
|
|
|
console.log(`用户 ${user.username} 使用旧密码迁移`);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 没有旧密码,使用默认密码
|
|
|
|
|
|
password = 'defaultPassword123';
|
|
|
|
|
|
console.log(`用户 ${user.username} 没有旧密码,使用默认密码`);
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
password = 'defaultPassword123';
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (err) {
|
|
|
|
|
|
console.error(`获取用户 ${user.username} 的密码失败,使用默认密码`, err);
|
|
|
|
|
|
password = 'defaultPassword123';
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-24 20:13:09 +08:00
|
|
|
|
// 将站长角色转换为普通角色
|
|
|
|
|
|
const migratedRole = user.role === 'owner' ? 'user' : user.role;
|
|
|
|
|
|
if (user.role === 'owner') {
|
|
|
|
|
|
console.log(`用户 ${user.username} 的角色从 owner 转换为 user`);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-24 00:24:50 +08:00
|
|
|
|
// 创建新用户
|
|
|
|
|
|
await this.createUserV2(
|
|
|
|
|
|
user.username,
|
|
|
|
|
|
password,
|
2025-12-24 20:13:09 +08:00
|
|
|
|
migratedRole,
|
2025-12-24 00:24:50 +08:00
|
|
|
|
user.tags,
|
|
|
|
|
|
(user as any).oidcSub,
|
|
|
|
|
|
user.enabledApis
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
// 如果用户被封禁,更新状态
|
|
|
|
|
|
if (user.banned) {
|
|
|
|
|
|
await this.updateUserInfoV2(user.username, { banned: true });
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
console.log(`用户 ${user.username} 迁移成功`);
|
|
|
|
|
|
} catch (err) {
|
|
|
|
|
|
console.error(`迁移用户 ${user.username} 失败:`, err);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
console.log('用户迁移完成');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-12 21:50:58 +08:00
|
|
|
|
// ---------- 搜索历史 ----------
|
|
|
|
|
|
async getSearchHistory(userName: string): Promise<string[]> {
|
|
|
|
|
|
return this.storage.getSearchHistory(userName);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async addSearchHistory(userName: string, keyword: string): Promise<void> {
|
|
|
|
|
|
await this.storage.addSearchHistory(userName, keyword);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async deleteSearchHistory(userName: string, keyword?: string): Promise<void> {
|
|
|
|
|
|
await this.storage.deleteSearchHistory(userName, keyword);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取全部用户名
|
|
|
|
|
|
async getAllUsers(): Promise<string[]> {
|
|
|
|
|
|
if (typeof (this.storage as any).getAllUsers === 'function') {
|
|
|
|
|
|
return (this.storage as any).getAllUsers();
|
|
|
|
|
|
}
|
|
|
|
|
|
return [];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ---------- 管理员配置 ----------
|
|
|
|
|
|
async getAdminConfig(): Promise<AdminConfig | null> {
|
|
|
|
|
|
if (typeof (this.storage as any).getAdminConfig === 'function') {
|
|
|
|
|
|
return (this.storage as any).getAdminConfig();
|
|
|
|
|
|
}
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async saveAdminConfig(config: AdminConfig): Promise<void> {
|
|
|
|
|
|
if (typeof (this.storage as any).setAdminConfig === 'function') {
|
|
|
|
|
|
await (this.storage as any).setAdminConfig(config);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ---------- 跳过片头片尾配置 ----------
|
|
|
|
|
|
async getSkipConfig(
|
|
|
|
|
|
userName: string,
|
|
|
|
|
|
source: string,
|
|
|
|
|
|
id: string
|
|
|
|
|
|
): Promise<SkipConfig | null> {
|
|
|
|
|
|
if (typeof (this.storage as any).getSkipConfig === 'function') {
|
|
|
|
|
|
return (this.storage as any).getSkipConfig(userName, source, id);
|
|
|
|
|
|
}
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async setSkipConfig(
|
|
|
|
|
|
userName: string,
|
|
|
|
|
|
source: string,
|
|
|
|
|
|
id: string,
|
|
|
|
|
|
config: SkipConfig
|
|
|
|
|
|
): Promise<void> {
|
|
|
|
|
|
if (typeof (this.storage as any).setSkipConfig === 'function') {
|
|
|
|
|
|
await (this.storage as any).setSkipConfig(userName, source, id, config);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async deleteSkipConfig(
|
|
|
|
|
|
userName: string,
|
|
|
|
|
|
source: string,
|
|
|
|
|
|
id: string
|
|
|
|
|
|
): Promise<void> {
|
|
|
|
|
|
if (typeof (this.storage as any).deleteSkipConfig === 'function') {
|
|
|
|
|
|
await (this.storage as any).deleteSkipConfig(userName, source, id);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async getAllSkipConfigs(
|
|
|
|
|
|
userName: string
|
|
|
|
|
|
): Promise<{ [key: string]: SkipConfig }> {
|
|
|
|
|
|
if (typeof (this.storage as any).getAllSkipConfigs === 'function') {
|
|
|
|
|
|
return (this.storage as any).getAllSkipConfigs(userName);
|
|
|
|
|
|
}
|
|
|
|
|
|
return {};
|
|
|
|
|
|
}
|
2025-08-15 13:49:08 +08:00
|
|
|
|
|
2025-12-03 21:34:05 +08:00
|
|
|
|
// ---------- 弹幕过滤配置 ----------
|
|
|
|
|
|
async getDanmakuFilterConfig(userName: string): Promise<DanmakuFilterConfig | null> {
|
|
|
|
|
|
if (typeof (this.storage as any).getDanmakuFilterConfig === 'function') {
|
|
|
|
|
|
return (this.storage as any).getDanmakuFilterConfig(userName);
|
|
|
|
|
|
}
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async setDanmakuFilterConfig(
|
|
|
|
|
|
userName: string,
|
|
|
|
|
|
config: DanmakuFilterConfig
|
|
|
|
|
|
): Promise<void> {
|
|
|
|
|
|
if (typeof (this.storage as any).setDanmakuFilterConfig === 'function') {
|
|
|
|
|
|
await (this.storage as any).setDanmakuFilterConfig(userName, config);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async deleteDanmakuFilterConfig(userName: string): Promise<void> {
|
|
|
|
|
|
if (typeof (this.storage as any).deleteDanmakuFilterConfig === 'function') {
|
|
|
|
|
|
await (this.storage as any).deleteDanmakuFilterConfig(userName);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-15 13:49:08 +08:00
|
|
|
|
// ---------- 数据清理 ----------
|
|
|
|
|
|
async clearAllData(): Promise<void> {
|
|
|
|
|
|
if (typeof (this.storage as any).clearAllData === 'function') {
|
|
|
|
|
|
await (this.storage as any).clearAllData();
|
|
|
|
|
|
} else {
|
|
|
|
|
|
throw new Error('存储类型不支持清空数据操作');
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-12-18 21:36:31 +08:00
|
|
|
|
|
|
|
|
|
|
// ---------- 通用键值存储 ----------
|
|
|
|
|
|
async getGlobalValue(key: string): Promise<string | null> {
|
|
|
|
|
|
if (typeof (this.storage as any).getGlobalValue === 'function') {
|
|
|
|
|
|
return (this.storage as any).getGlobalValue(key);
|
|
|
|
|
|
}
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async setGlobalValue(key: string, value: string): Promise<void> {
|
|
|
|
|
|
if (typeof (this.storage as any).setGlobalValue === 'function') {
|
|
|
|
|
|
await (this.storage as any).setGlobalValue(key, value);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async deleteGlobalValue(key: string): Promise<void> {
|
|
|
|
|
|
if (typeof (this.storage as any).deleteGlobalValue === 'function') {
|
|
|
|
|
|
await (this.storage as any).deleteGlobalValue(key);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-08-12 21:50:58 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 导出默认实例
|
|
|
|
|
|
export const db = new DbManager();
|