eslint fix

This commit is contained in:
mtvpls
2026-01-23 16:41:49 +08:00
parent a167763324
commit 6eaed34b14
107 changed files with 311 additions and 318 deletions
+1 -1
View File
@@ -11224,7 +11224,7 @@ function AdminPageClient() {
const userLimit = 10; const userLimit = 10;
// 获取新版本用户列表 // 获取新版本用户列表
const fetchUsersV2 = useCallback(async (page: number = 1) => { const fetchUsersV2 = useCallback(async (page = 1) => {
try { try {
setUserListLoading(true); setUserListLoading(true);
const response = await fetch(`/api/admin/users?page=${page}&limit=${userLimit}`); const response = await fetch(`/api/admin/users?page=${page}&limit=${userLimit}`);
+4 -4
View File
@@ -1,10 +1,10 @@
import { NextRequest, NextResponse } from 'next/server'; import { NextRequest, NextResponse } from 'next/server';
import { getAuthInfoFromCookie } from '@/lib/auth';
import { getStorage } from '@/lib/db';
import { getConfig } from '@/lib/config';
import { EmailService } from '@/lib/email.service';
import type { AdminConfig } from '@/lib/admin.types'; import type { AdminConfig } from '@/lib/admin.types';
import { getAuthInfoFromCookie } from '@/lib/auth';
import { getConfig } from '@/lib/config';
import { getStorage } from '@/lib/db';
import { EmailService } from '@/lib/email.service';
export const runtime = 'nodejs'; export const runtime = 'nodejs';
+1
View File
@@ -1,4 +1,5 @@
import { NextRequest, NextResponse } from 'next/server'; import { NextRequest, NextResponse } from 'next/server';
import { getAuthInfoFromCookie } from '@/lib/auth'; import { getAuthInfoFromCookie } from '@/lib/auth';
import { getConfig } from '@/lib/config'; import { getConfig } from '@/lib/config';
+1 -1
View File
@@ -1,4 +1,5 @@
import { NextRequest, NextResponse } from 'next/server'; import { NextRequest, NextResponse } from 'next/server';
import { getAuthInfoFromCookie } from '@/lib/auth'; import { getAuthInfoFromCookie } from '@/lib/auth';
import { getConfig } from '@/lib/config'; import { getConfig } from '@/lib/config';
import { db } from '@/lib/db'; import { db } from '@/lib/db';
@@ -37,7 +38,6 @@ export async function POST(request: NextRequest) {
// 追加和覆盖:合并Sources数组 // 追加和覆盖:合并Sources数组
if (data.Sources && Array.isArray(data.Sources)) { if (data.Sources && Array.isArray(data.Sources)) {
const existingSources = adminConfig.EmbyConfig?.Sources || []; const existingSources = adminConfig.EmbyConfig?.Sources || [];
const existingKeys = new Set(existingSources.map(s => s.key));
// 覆盖已存在的,追加新的 // 覆盖已存在的,追加新的
const mergedSources = [...existingSources]; const mergedSources = [...existingSources];
+1 -1
View File
@@ -87,7 +87,7 @@ export async function POST(request: NextRequest) {
} }
// 验证扫描间隔 // 验证扫描间隔
let scanInterval = parseInt(ScanInterval) || 0; const scanInterval = parseInt(ScanInterval) || 0;
if (scanInterval > 0 && scanInterval < 60) { if (scanInterval > 0 && scanInterval < 60) {
return NextResponse.json( return NextResponse.json(
{ error: '定时扫描间隔最低为 60 分钟' }, { error: '定时扫描间隔最低为 60 分钟' },
-4
View File
@@ -2,7 +2,6 @@
import { NextRequest, NextResponse } from 'next/server'; import { NextRequest, NextResponse } from 'next/server';
import { getAuthInfoFromCookie } from '@/lib/auth'; import { getAuthInfoFromCookie } from '@/lib/auth';
import { getConfig } from '@/lib/config';
import { db } from '@/lib/db'; import { db } from '@/lib/db';
export const runtime = 'nodejs'; export const runtime = 'nodejs';
@@ -24,9 +23,6 @@ export async function GET(request: NextRequest) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
} }
// 获取配置
const adminConfig = await getConfig();
// 判定操作者角色 // 判定操作者角色
let operatorRole: 'owner' | 'admin' | 'user' = 'user'; let operatorRole: 'owner' | 'admin' | 'user' = 'user';
if (authInfo.username === process.env.USERNAME) { if (authInfo.username === process.env.USERNAME) {
+1
View File
@@ -1,4 +1,5 @@
import { NextRequest, NextResponse } from 'next/server'; import { NextRequest, NextResponse } from 'next/server';
import { getAuthInfoFromCookie } from '@/lib/auth'; import { getAuthInfoFromCookie } from '@/lib/auth';
import { getConfig } from '@/lib/config'; import { getConfig } from '@/lib/config';
import { db } from '@/lib/db'; import { db } from '@/lib/db';
+2 -41
View File
@@ -2,11 +2,11 @@
import { NextRequest, NextResponse } from 'next/server'; import { NextRequest, NextResponse } from 'next/server';
import { getAuthInfoFromCookie } from '@/lib/auth';
import { import {
orchestrateDataSources, orchestrateDataSources,
VideoContext, VideoContext,
} from '@/lib/ai-orchestrator'; } from '@/lib/ai-orchestrator';
import { getAuthInfoFromCookie } from '@/lib/auth';
import { getConfig } from '@/lib/config'; import { getConfig } from '@/lib/config';
import { db } from '@/lib/db'; import { db } from '@/lib/db';
@@ -35,7 +35,7 @@ async function streamOpenAIChat(
temperature: number; temperature: number;
maxTokens: number; maxTokens: number;
}, },
enableStreaming: boolean = true enableStreaming = true
): Promise<ReadableStream | Response> { ): Promise<ReadableStream | Response> {
const response = await fetch(`${config.baseURL}/chat/completions`, { const response = await fetch(`${config.baseURL}/chat/completions`, {
method: 'POST', method: 'POST',
@@ -61,45 +61,6 @@ async function streamOpenAIChat(
return enableStreaming ? response.body! : response; return enableStreaming ? response.body! : response;
} }
/**
* Claude API流式聊天请求
*/
async function streamClaudeChat(
messages: ChatMessage[],
systemPrompt: string,
config: {
apiKey: string;
model: string;
temperature: number;
maxTokens: number;
}
): Promise<ReadableStream> {
const response = await fetch('https://api.anthropic.com/v1/messages', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': config.apiKey,
'anthropic-version': '2023-06-01',
},
body: JSON.stringify({
model: config.model,
max_tokens: config.maxTokens,
temperature: config.temperature,
system: systemPrompt,
messages: messages,
stream: true,
}),
});
if (!response.ok) {
throw new Error(
`Claude API error: ${response.status} ${response.statusText}`
);
}
return response.body!;
}
/** /**
* 转换流为SSE格式 * 转换流为SSE格式
*/ */
@@ -123,7 +123,7 @@ export async function POST(request: NextRequest) {
} }
// 检查用户名是否已存在(优先使用新版本) // 检查用户名是否已存在(优先使用新版本)
let userExists = await db.checkUserExistV2(username); const userExists = await db.checkUserExistV2(username);
if (userExists) { if (userExists) {
return NextResponse.json( return NextResponse.json(
{ error: '用户名已存在' }, { error: '用户名已存在' },
+1 -9
View File
@@ -4,7 +4,6 @@ import { NextRequest, NextResponse } from 'next/server';
import { getConfig } from '@/lib/config'; import { getConfig } from '@/lib/config';
import { db } from '@/lib/db'; import { db } from '@/lib/db';
import { OpenListClient } from '@/lib/openlist.client';
import { import {
getCachedMetaInfo, getCachedMetaInfo,
MetaInfo, MetaInfo,
@@ -260,13 +259,6 @@ async function handleOpenListProxy(request: NextRequest) {
); );
} }
const rootPath = openListConfig.RootPath || '/';
const client = new OpenListClient(
openListConfig.URL,
openListConfig.Username,
openListConfig.Password
);
// 读取 metainfo (从数据库或缓存) // 读取 metainfo (从数据库或缓存)
let metaInfo: MetaInfo | null = getCachedMetaInfo(); let metaInfo: MetaInfo | null = getCachedMetaInfo();
@@ -296,7 +288,7 @@ async function handleOpenListProxy(request: NextRequest) {
if (wd) { if (wd) {
const results = Object.entries(metaInfo.folders) const results = Object.entries(metaInfo.folders)
.filter( .filter(
([key, info]) => ([_key, info]) =>
info.folderName.toLowerCase().includes(wd.toLowerCase()) || info.folderName.toLowerCase().includes(wd.toLowerCase()) ||
info.title.toLowerCase().includes(wd.toLowerCase()) info.title.toLowerCase().includes(wd.toLowerCase())
) )
+2 -2
View File
@@ -4,12 +4,12 @@ import { NextRequest, NextResponse } from 'next/server';
import { getConfig, refineConfig } from '@/lib/config'; import { getConfig, refineConfig } from '@/lib/config';
import { db, getStorage } from '@/lib/db'; import { db, getStorage } from '@/lib/db';
import { EmailService } from '@/lib/email.service';
import { FavoriteUpdate,getBatchFavoriteUpdateEmailTemplate } from '@/lib/email.templates';
import { fetchVideoDetail } from '@/lib/fetchVideoDetail'; import { fetchVideoDetail } from '@/lib/fetchVideoDetail';
import { refreshLiveChannels } from '@/lib/live'; import { refreshLiveChannels } from '@/lib/live';
import { startOpenListRefresh } from '@/lib/openlist-refresh'; import { startOpenListRefresh } from '@/lib/openlist-refresh';
import { SearchResult } from '@/lib/types'; import { SearchResult } from '@/lib/types';
import { EmailService } from '@/lib/email.service';
import { getBatchFavoriteUpdateEmailTemplate, FavoriteUpdate } from '@/lib/email.templates';
export const runtime = 'nodejs'; export const runtime = 'nodejs';
-1
View File
@@ -3,7 +3,6 @@
import { NextRequest, NextResponse } from 'next/server'; import { NextRequest, NextResponse } from 'next/server';
import { getAuthInfoFromCookie } from '@/lib/auth'; import { getAuthInfoFromCookie } from '@/lib/auth';
import { getConfig } from '@/lib/config';
import { db } from '@/lib/db'; import { db } from '@/lib/db';
import { DanmakuFilterConfig } from '@/lib/types'; import { DanmakuFilterConfig } from '@/lib/types';
+2 -1
View File
@@ -1,5 +1,6 @@
import { NextRequest, NextResponse } from 'next/server';
import * as cheerio from 'cheerio'; import * as cheerio from 'cheerio';
import { NextRequest, NextResponse } from 'next/server';
import { fetchDoubanWithVerification } from '@/lib/douban-anti-crawler'; import { fetchDoubanWithVerification } from '@/lib/douban-anti-crawler';
export const runtime = 'nodejs'; export const runtime = 'nodejs';
+2 -1
View File
@@ -1,5 +1,6 @@
import { NextRequest, NextResponse } from 'next/server';
import * as cheerio from 'cheerio'; import * as cheerio from 'cheerio';
import { NextRequest, NextResponse } from 'next/server';
import { fetchDoubanData } from '@/lib/douban'; import { fetchDoubanData } from '@/lib/douban';
import { fetchDoubanWithVerification } from '@/lib/douban-anti-crawler'; import { fetchDoubanWithVerification } from '@/lib/douban-anti-crawler';
+1 -1
View File
@@ -2,8 +2,8 @@
import { NextRequest, NextResponse } from 'next/server'; import { NextRequest, NextResponse } from 'next/server';
import { embyManager } from '@/lib/emby-manager';
import { getCachedEmbyList, setCachedEmbyList } from '@/lib/emby-cache'; import { getCachedEmbyList, setCachedEmbyList } from '@/lib/emby-cache';
import { embyManager } from '@/lib/emby-manager';
export const runtime = 'nodejs'; export const runtime = 'nodejs';
+1 -1
View File
@@ -2,8 +2,8 @@
import { NextRequest, NextResponse } from 'next/server'; import { NextRequest, NextResponse } from 'next/server';
import { embyManager } from '@/lib/emby-manager';
import { getCachedEmbyViews, setCachedEmbyViews } from '@/lib/emby-cache'; import { getCachedEmbyViews, setCachedEmbyViews } from '@/lib/emby-cache';
import { embyManager } from '@/lib/emby-manager';
export const runtime = 'nodejs'; export const runtime = 'nodejs';
-1
View File
@@ -3,7 +3,6 @@
import { NextRequest, NextResponse } from 'next/server'; import { NextRequest, NextResponse } from 'next/server';
import { getAuthInfoFromCookie } from '@/lib/auth'; import { getAuthInfoFromCookie } from '@/lib/auth';
import { getConfig } from '@/lib/config';
import { db } from '@/lib/db'; import { db } from '@/lib/db';
import { Favorite } from '@/lib/types'; import { Favorite } from '@/lib/types';
+1 -1
View File
@@ -1,5 +1,5 @@
import { NextRequest, NextResponse } from 'next/server';
import { nanoid } from 'nanoid'; import { nanoid } from 'nanoid';
import { NextRequest, NextResponse } from 'next/server';
import { getAuthInfoFromCookie } from '@/lib/auth'; import { getAuthInfoFromCookie } from '@/lib/auth';
import { getStorage } from '@/lib/db'; import { getStorage } from '@/lib/db';
@@ -3,11 +3,12 @@
* 路径格式: /api/offline-download/local/[source]/[videoId]/[episodeIndex]/[file] * 路径格式: /api/offline-download/local/[source]/[videoId]/[episodeIndex]/[file]
*/ */
import { NextRequest, NextResponse } from 'next/server';
import { getAuthInfoFromCookie } from '@/lib/auth';
import * as fs from 'fs'; import * as fs from 'fs';
import { NextRequest, NextResponse } from 'next/server';
import * as path from 'path'; import * as path from 'path';
import { getAuthInfoFromCookie } from '@/lib/auth';
// 检查是否启用离线下载功能 // 检查是否启用离线下载功能
const OFFLINE_DOWNLOAD_ENABLED = process.env.NEXT_PUBLIC_ENABLE_OFFLINE_DOWNLOAD === 'true'; const OFFLINE_DOWNLOAD_ENABLED = process.env.NEXT_PUBLIC_ENABLE_OFFLINE_DOWNLOAD === 'true';
const OFFLINE_DOWNLOAD_DIR = process.env.OFFLINE_DOWNLOAD_DIR || '/data'; const OFFLINE_DOWNLOAD_DIR = process.env.OFFLINE_DOWNLOAD_DIR || '/data';
+3 -2
View File
@@ -2,11 +2,12 @@
* 本地下载视频播放代理 API * 本地下载视频播放代理 API
*/ */
import { NextRequest, NextResponse } from 'next/server';
import { getAuthInfoFromCookie } from '@/lib/auth';
import * as fs from 'fs'; import * as fs from 'fs';
import { NextRequest, NextResponse } from 'next/server';
import * as path from 'path'; import * as path from 'path';
import { getAuthInfoFromCookie } from '@/lib/auth';
// 检查是否启用离线下载功能 // 检查是否启用离线下载功能
const OFFLINE_DOWNLOAD_ENABLED = process.env.NEXT_PUBLIC_ENABLE_OFFLINE_DOWNLOAD === 'true'; const OFFLINE_DOWNLOAD_ENABLED = process.env.NEXT_PUBLIC_ENABLE_OFFLINE_DOWNLOAD === 'true';
const OFFLINE_DOWNLOAD_DIR = process.env.OFFLINE_DOWNLOAD_DIR || '/data'; const OFFLINE_DOWNLOAD_DIR = process.env.OFFLINE_DOWNLOAD_DIR || '/data';
+3 -2
View File
@@ -2,11 +2,12 @@
* 离线下载任务管理 API * 离线下载任务管理 API
*/ */
import * as fs from 'fs';
import { NextRequest, NextResponse } from 'next/server'; import { NextRequest, NextResponse } from 'next/server';
import * as path from 'path';
import { getAuthInfoFromCookie } from '@/lib/auth'; import { getAuthInfoFromCookie } from '@/lib/auth';
import { OfflineDownloader, OfflineDownloadTask } from '@/lib/offline-downloader'; import { OfflineDownloader, OfflineDownloadTask } from '@/lib/offline-downloader';
import * as fs from 'fs';
import * as path from 'path';
// 检查是否启用离线下载功能 // 检查是否启用离线下载功能
const OFFLINE_DOWNLOAD_ENABLED = process.env.NEXT_PUBLIC_ENABLE_OFFLINE_DOWNLOAD === 'true'; const OFFLINE_DOWNLOAD_ENABLED = process.env.NEXT_PUBLIC_ENABLE_OFFLINE_DOWNLOAD === 'true';
-1
View File
@@ -6,7 +6,6 @@ import { getAuthInfoFromCookie } from '@/lib/auth';
import { getConfig } from '@/lib/config'; import { getConfig } from '@/lib/config';
import { db } from '@/lib/db'; import { db } from '@/lib/db';
import { import {
getCachedMetaInfo,
invalidateMetaInfoCache, invalidateMetaInfoCache,
MetaInfo, MetaInfo,
setCachedMetaInfo, setCachedMetaInfo,
-1
View File
@@ -3,7 +3,6 @@
import { NextRequest, NextResponse } from 'next/server'; import { NextRequest, NextResponse } from 'next/server';
import { getAuthInfoFromCookie } from '@/lib/auth'; import { getAuthInfoFromCookie } from '@/lib/auth';
import { getConfig } from '@/lib/config';
import { db } from '@/lib/db'; import { db } from '@/lib/db';
import { PlayRecord } from '@/lib/types'; import { PlayRecord } from '@/lib/types';
-1
View File
@@ -3,7 +3,6 @@
import { NextRequest, NextResponse } from 'next/server'; import { NextRequest, NextResponse } from 'next/server';
import { getAuthInfoFromCookie } from '@/lib/auth'; import { getAuthInfoFromCookie } from '@/lib/auth';
import { getConfig } from '@/lib/config';
import { db } from '@/lib/db'; import { db } from '@/lib/db';
export const runtime = 'nodejs'; export const runtime = 'nodejs';
-1
View File
@@ -3,7 +3,6 @@
import { NextRequest, NextResponse } from 'next/server'; import { NextRequest, NextResponse } from 'next/server';
import { getAuthInfoFromCookie } from '@/lib/auth'; import { getAuthInfoFromCookie } from '@/lib/auth';
import { getConfig } from '@/lib/config';
import { db } from '@/lib/db'; import { db } from '@/lib/db';
import { SkipConfig } from '@/lib/types'; import { SkipConfig } from '@/lib/types';
+1
View File
@@ -3,6 +3,7 @@
import { NextRequest, NextResponse } from 'next/server'; import { NextRequest, NextResponse } from 'next/server';
import { getConfig } from '@/lib/config'; import { getConfig } from '@/lib/config';
import { getThemeCSS } from '@/styles/themes'; import { getThemeCSS } from '@/styles/themes';
export const runtime = 'nodejs'; export const runtime = 'nodejs';
+4 -3
View File
@@ -1,11 +1,12 @@
import { NextRequest, NextResponse } from 'next/server'; import { NextRequest, NextResponse } from 'next/server';
import { getConfig } from '@/lib/config';
import { import {
searchTMDBMulti, getTMDBImageUrl,
getTMDBMovieDetails, getTMDBMovieDetails,
getTMDBTVDetails, getTMDBTVDetails,
getTMDBImageUrl, searchTMDBMulti,
} from '@/lib/tmdb.client'; } from '@/lib/tmdb.client';
import { getConfig } from '@/lib/config';
// 服务器端缓存(内存) // 服务器端缓存(内存)
const searchCache = new Map< const searchCache = new Map<
+4 -3
View File
@@ -1,11 +1,12 @@
import { NextRequest, NextResponse } from 'next/server'; import { NextRequest, NextResponse } from 'next/server';
import { getConfig } from '@/lib/config';
import { import {
searchTMDBMulti, getTMDBImageUrl,
getTMDBMovieRecommendations, getTMDBMovieRecommendations,
getTMDBTVRecommendations, getTMDBTVRecommendations,
getTMDBImageUrl, searchTMDBMulti,
} from '@/lib/tmdb.client'; } from '@/lib/tmdb.client';
import { getConfig } from '@/lib/config';
// 服务器端缓存(1天) // 服务器端缓存(1天)
const searchCache = new Map<string, { data: any; timestamp: number }>(); const searchCache = new Map<string, { data: any; timestamp: number }>();
+2 -2
View File
@@ -1,12 +1,12 @@
/* eslint-disable @typescript-eslint/no-explicit-any, no-console */ /* eslint-disable @typescript-eslint/no-explicit-any, no-console */
import { HttpsProxyAgent } from 'https-proxy-agent';
import { NextRequest, NextResponse } from 'next/server'; import { NextRequest, NextResponse } from 'next/server';
import nodeFetch from 'node-fetch';
import { getAuthInfoFromCookie } from '@/lib/auth'; import { getAuthInfoFromCookie } from '@/lib/auth';
import { getConfig } from '@/lib/config'; import { getConfig } from '@/lib/config';
import { getNextApiKey } from '@/lib/tmdb.client'; import { getNextApiKey } from '@/lib/tmdb.client';
import { HttpsProxyAgent } from 'https-proxy-agent';
import nodeFetch from 'node-fetch';
export const runtime = 'nodejs'; export const runtime = 'nodejs';
+2 -2
View File
@@ -1,11 +1,11 @@
/* eslint-disable @typescript-eslint/no-explicit-any, no-console */ /* eslint-disable @typescript-eslint/no-explicit-any, no-console */
import { HttpsProxyAgent } from 'https-proxy-agent';
import { NextRequest, NextResponse } from 'next/server'; import { NextRequest, NextResponse } from 'next/server';
import nodeFetch from 'node-fetch';
import { getAuthInfoFromCookie } from '@/lib/auth'; import { getAuthInfoFromCookie } from '@/lib/auth';
import { getConfig } from '@/lib/config'; import { getConfig } from '@/lib/config';
import { HttpsProxyAgent } from 'https-proxy-agent';
import nodeFetch from 'node-fetch';
import { getNextApiKey } from '@/lib/tmdb.client'; import { getNextApiKey } from '@/lib/tmdb.client';
export const runtime = 'nodejs'; export const runtime = 'nodejs';
+2 -2
View File
@@ -1,12 +1,12 @@
/* eslint-disable @typescript-eslint/no-explicit-any, no-console */ /* eslint-disable @typescript-eslint/no-explicit-any, no-console */
import { HttpsProxyAgent } from 'https-proxy-agent';
import { NextRequest, NextResponse } from 'next/server'; import { NextRequest, NextResponse } from 'next/server';
import nodeFetch from 'node-fetch';
import { getAuthInfoFromCookie } from '@/lib/auth'; import { getAuthInfoFromCookie } from '@/lib/auth';
import { getConfig } from '@/lib/config'; import { getConfig } from '@/lib/config';
import { getNextApiKey } from '@/lib/tmdb.client'; import { getNextApiKey } from '@/lib/tmdb.client';
import { HttpsProxyAgent } from 'https-proxy-agent';
import nodeFetch from 'node-fetch';
export const runtime = 'nodejs'; export const runtime = 'nodejs';
+2 -1
View File
@@ -1,9 +1,10 @@
/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-explicit-any */
import { NextResponse } from 'next/server'; import { NextResponse } from 'next/server';
import { getTMDBTrendingContent, getTMDBVideos } from '@/lib/tmdb.client';
import { getConfig } from '@/lib/config'; import { getConfig } from '@/lib/config';
import { fetchDoubanData } from '@/lib/douban'; import { fetchDoubanData } from '@/lib/douban';
import { getTMDBTrendingContent, getTMDBVideos } from '@/lib/tmdb.client';
// 缓存配置 - 服务器内存缓存3小时 // 缓存配置 - 服务器内存缓存3小时
const CACHE_DURATION = 3 * 60 * 60 * 1000; // 3小时 const CACHE_DURATION = 3 * 60 * 60 * 1000; // 3小时
+2 -1
View File
@@ -1,6 +1,7 @@
import { NextRequest, NextResponse } from 'next/server'; import { NextRequest, NextResponse } from 'next/server';
import { getTMDBUpcomingContent } from '@/lib/tmdb.client';
import { getConfig } from '@/lib/config'; import { getConfig } from '@/lib/config';
import { getTMDBUpcomingContent } from '@/lib/tmdb.client';
// 内存缓存对象 // 内存缓存对象
interface CacheItem { interface CacheItem {
+1
View File
@@ -1,4 +1,5 @@
import { NextRequest, NextResponse } from 'next/server'; import { NextRequest, NextResponse } from 'next/server';
import { getConfig } from '@/lib/config'; import { getConfig } from '@/lib/config';
export const dynamic = 'force-dynamic'; // 禁用缓存 export const dynamic = 'force-dynamic'; // 禁用缓存
+1 -1
View File
@@ -1,5 +1,5 @@
import { NextRequest, NextResponse } from 'next/server';
import crypto from 'crypto'; import crypto from 'crypto';
import { NextRequest, NextResponse } from 'next/server';
function getAntiCode(oldAntiCode: string, streamName: string): string { function getAntiCode(oldAntiCode: string, streamName: string): string {
const paramsT = 100; const paramsT = 100;
+6 -6
View File
@@ -7,16 +7,16 @@ import './globals.css';
import { getConfig } from '@/lib/config'; import { getConfig } from '@/lib/config';
import { DanmakuCacheCleanup } from '../components/DanmakuCacheCleanup';
import { DownloadBubble } from '../components/DownloadBubble';
import { DownloadPanel } from '../components/DownloadPanel';
import { GlobalErrorIndicator } from '../components/GlobalErrorIndicator'; import { GlobalErrorIndicator } from '../components/GlobalErrorIndicator';
import { SiteProvider } from '../components/SiteProvider'; import { SiteProvider } from '../components/SiteProvider';
import { ThemeProvider } from '../components/ThemeProvider'; import { ThemeProvider } from '../components/ThemeProvider';
import { WatchRoomProvider } from '../components/WatchRoomProvider';
import ChatFloatingWindow from '../components/watch-room/ChatFloatingWindow';
import { DownloadProvider } from '../contexts/DownloadContext';
import { DownloadBubble } from '../components/DownloadBubble';
import { DownloadPanel } from '../components/DownloadPanel';
import { DanmakuCacheCleanup } from '../components/DanmakuCacheCleanup';
import TopProgressBar from '../components/TopProgressBar'; import TopProgressBar from '../components/TopProgressBar';
import ChatFloatingWindow from '../components/watch-room/ChatFloatingWindow';
import { WatchRoomProvider } from '../components/WatchRoomProvider';
import { DownloadProvider } from '../contexts/DownloadContext';
const inter = Inter({ subsets: ['latin'] }); const inter = Inter({ subsets: ['latin'] });
export const dynamic = 'force-dynamic'; export const dynamic = 'force-dynamic';
+2 -3
View File
@@ -6,8 +6,6 @@ import { Heart, Radio, Tv } from 'lucide-react';
import { useRouter, useSearchParams } from 'next/navigation'; import { useRouter, useSearchParams } from 'next/navigation';
import { useEffect, useRef, useState } from 'react'; import { useEffect, useRef, useState } from 'react';
import { useLiveSync } from '@/hooks/useLiveSync';
import { import {
deleteFavorite, deleteFavorite,
generateStorageKey, generateStorageKey,
@@ -17,6 +15,7 @@ import {
subscribeToDataUpdates, subscribeToDataUpdates,
} from '@/lib/db.client'; } from '@/lib/db.client';
import { parseCustomTimeFormat } from '@/lib/time'; import { parseCustomTimeFormat } from '@/lib/time';
import { useLiveSync } from '@/hooks/useLiveSync';
import EpgScrollableRow from '@/components/EpgScrollableRow'; import EpgScrollableRow from '@/components/EpgScrollableRow';
import PageLayout from '@/components/PageLayout'; import PageLayout from '@/components/PageLayout';
@@ -1150,7 +1149,7 @@ function LivePageClient() {
if (!selectedGroup) return; if (!selectedGroup) return;
// 先在当前分组搜索 // 先在当前分组搜索
let filtered = filterChannels(selectedGroup, keyword); const filtered = filterChannels(selectedGroup, keyword);
// 如果当前分组没有匹配的频道,且有搜索关键词,轮询所有分组 // 如果当前分组没有匹配的频道,且有搜索关键词,轮询所有分组
if (filtered.length === 0 && keyword.trim() && groupedChannels) { if (filtered.length === 0 && keyword.trim() && groupedChannels) {
+4 -3
View File
@@ -1,14 +1,15 @@
'use client'; 'use client';
import { useState, useEffect } from 'react'; import { AlertCircle, CheckCircle } from 'lucide-react';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
import { useEffect,useState } from 'react';
import { createPortal } from 'react-dom'; import { createPortal } from 'react-dom';
import { CheckCircle, AlertCircle, Plus } from 'lucide-react';
import PageLayout from '@/components/PageLayout';
import { getTMDBImageUrl } from '@/lib/tmdb.client'; import { getTMDBImageUrl } from '@/lib/tmdb.client';
import { processImageUrl } from '@/lib/utils'; import { processImageUrl } from '@/lib/utils';
import PageLayout from '@/components/PageLayout';
interface TMDBResult { interface TMDBResult {
id: number; id: number;
title?: string; title?: string;
+9 -7
View File
@@ -2,9 +2,9 @@
'use client'; 'use client';
import { ChevronRight, Bot, ListVideo } from 'lucide-react'; import { Bot, ChevronRight, ListVideo } from 'lucide-react';
import Link from 'next/link'; import Link from 'next/link';
import { Suspense, useCallback, useEffect, useRef, useState } from 'react'; import { Suspense, useEffect, useState } from 'react';
import { import {
BangumiCalendarData, BangumiCalendarData,
@@ -15,15 +15,15 @@ import { getTMDBImageUrl, TMDBItem } from '@/lib/tmdb.client';
import { DoubanItem } from '@/lib/types'; import { DoubanItem } from '@/lib/types';
import { processImageUrl } from '@/lib/utils'; import { processImageUrl } from '@/lib/utils';
import AIChatPanel from '@/components/AIChatPanel';
import BannerCarousel from '@/components/BannerCarousel';
import ContinueWatching from '@/components/ContinueWatching'; import ContinueWatching from '@/components/ContinueWatching';
import FireworksCanvas from '@/components/FireworksCanvas';
import HttpWarningDialog from '@/components/HttpWarningDialog';
import PageLayout from '@/components/PageLayout'; import PageLayout from '@/components/PageLayout';
import ScrollableRow from '@/components/ScrollableRow'; import ScrollableRow from '@/components/ScrollableRow';
import { useSite } from '@/components/SiteProvider'; import { useSite } from '@/components/SiteProvider';
import VideoCard from '@/components/VideoCard'; import VideoCard from '@/components/VideoCard';
import HttpWarningDialog from '@/components/HttpWarningDialog';
import BannerCarousel from '@/components/BannerCarousel';
import AIChatPanel from '@/components/AIChatPanel';
import FireworksCanvas from '@/components/FireworksCanvas';
// 首页模块配置接口 // 首页模块配置接口
interface HomeModule { interface HomeModule {
@@ -151,7 +151,9 @@ function HomeClient() {
const setCache = (key: string, data: any) => { const setCache = (key: string, data: any) => {
try { try {
localStorage.setItem(key, JSON.stringify({ data, timestamp: Date.now() })); localStorage.setItem(key, JSON.stringify({ data, timestamp: Date.now() }));
} catch {} } catch {
// Ignore localStorage errors
}
}; };
const moviesCache = getCache('homepage_movies'); const moviesCache = getCache('homepage_movies');
+55 -54
View File
@@ -2,70 +2,69 @@
'use client'; 'use client';
import { Heart, Search, X, Cloud, Sparkles, AlertCircle } from 'lucide-react'; import { AlertCircle,Cloud, Heart, Sparkles, X } from 'lucide-react';
import { useRouter, useSearchParams } from 'next/navigation'; import { useRouter, useSearchParams } from 'next/navigation';
import { Suspense, useEffect, useRef, useState } from 'react'; import { Suspense, useEffect, useRef, useState } from 'react';
import { usePlaySync } from '@/hooks/usePlaySync';
import { getDoubanDetail } from '@/lib/douban.client';
import { useDownload } from '@/contexts/DownloadContext';
import { getAuthInfoFromBrowserCookie } from '@/lib/auth'; import { getAuthInfoFromBrowserCookie } from '@/lib/auth';
import { useSite } from '@/components/SiteProvider'; import {
convertDanmakuFormat,
getDanmakuById,
getDanmakuFromCache,
getEpisodes,
initDanmakuModule,
loadDanmakuDisplayState,
loadDanmakuSettings,
saveDanmakuDisplayState,
saveDanmakuSettings,
searchAnime,
} from '@/lib/danmaku/api';
import {
getDanmakuAnimeId,
getDanmakuSearchKeyword,
getDanmakuSourceIndex,
getManualDanmakuSelection,
saveDanmakuAnimeId,
saveDanmakuSearchKeyword,
saveDanmakuSourceIndex,
saveManualDanmakuSelection,
} from '@/lib/danmaku/selection-memory';
import type { DanmakuAnime, DanmakuComment,DanmakuSelection, DanmakuSettings } from '@/lib/danmaku/types';
import { import {
deleteFavorite, deleteFavorite,
deletePlayRecord, deletePlayRecord,
deleteSkipConfig, deleteSkipConfig,
generateStorageKey, generateStorageKey,
getAllPlayRecords, getAllPlayRecords,
getDanmakuFilterConfig,
getEpisodeFilterConfig,
getSkipConfig, getSkipConfig,
isFavorited, isFavorited,
saveFavorite, saveFavorite,
savePlayRecord, savePlayRecord,
saveSkipConfig, saveSkipConfig,
subscribeToDataUpdates, subscribeToDataUpdates,
getDanmakuFilterConfig,
getEpisodeFilterConfig,
} from '@/lib/db.client'; } from '@/lib/db.client';
import { import { getDoubanDetail } from '@/lib/douban.client';
convertDanmakuFormat,
getDanmakuById,
getEpisodes,
loadDanmakuSettings,
saveDanmakuSettings,
searchAnime,
initDanmakuModule,
getDanmakuFromCache,
saveDanmakuDisplayState,
loadDanmakuDisplayState,
} from '@/lib/danmaku/api';
import {
getDanmakuSourceIndex,
saveDanmakuSourceIndex,
getManualDanmakuSelection,
saveManualDanmakuSelection,
saveDanmakuSearchKeyword,
getDanmakuSearchKeyword,
saveDanmakuAnimeId,
getDanmakuAnimeId,
} from '@/lib/danmaku/selection-memory';
import type { DanmakuAnime, DanmakuSelection, DanmakuSettings, DanmakuComment } from '@/lib/danmaku/types';
import { SearchResult, DanmakuFilterConfig, EpisodeFilterConfig } from '@/lib/types';
import { getVideoResolutionFromM3u8, processImageUrl } from '@/lib/utils';
import { getTMDBImageUrl } from '@/lib/tmdb.search'; import { getTMDBImageUrl } from '@/lib/tmdb.search';
import { DanmakuFilterConfig, EpisodeFilterConfig,SearchResult } from '@/lib/types';
import EpisodeSelector from '@/components/EpisodeSelector'; import { getVideoResolutionFromM3u8, processImageUrl } from '@/lib/utils';
import DownloadEpisodeSelector from '@/components/DownloadEpisodeSelector';
import PageLayout from '@/components/PageLayout';
import DoubanComments from '@/components/DoubanComments';
import SmartRecommendations from '@/components/SmartRecommendations';
import DanmakuFilterSettings from '@/components/DanmakuFilterSettings';
import Toast, { ToastProps } from '@/components/Toast';
import AIChatPanel from '@/components/AIChatPanel';
import { useEnableComments } from '@/hooks/useEnableComments'; import { useEnableComments } from '@/hooks/useEnableComments';
import PansouSearch from '@/components/PansouSearch'; import { usePlaySync } from '@/hooks/usePlaySync';
import CustomHeatmap from '@/components/CustomHeatmap';
import AIChatPanel from '@/components/AIChatPanel';
import CorrectDialog from '@/components/CorrectDialog'; import CorrectDialog from '@/components/CorrectDialog';
import DanmakuFilterSettings from '@/components/DanmakuFilterSettings';
import DoubanComments from '@/components/DoubanComments';
import DownloadEpisodeSelector from '@/components/DownloadEpisodeSelector';
import EpisodeSelector from '@/components/EpisodeSelector';
import PageLayout from '@/components/PageLayout';
import PansouSearch from '@/components/PansouSearch';
import { useSite } from '@/components/SiteProvider';
import SmartRecommendations from '@/components/SmartRecommendations';
import Toast, { ToastProps } from '@/components/Toast';
import { useDownload } from '@/contexts/DownloadContext';
// 扩展 HTMLVideoElement 类型以支持 hls 属性 // 扩展 HTMLVideoElement 类型以支持 hls 属性
declare global { declare global {
@@ -545,7 +544,7 @@ function PlayPageClient() {
// 只有用户主动点击推荐时才会添加 _reload 参数 // 只有用户主动点击推荐时才会添加 _reload 参数
if (reloadParam && urlTitle && urlTitle !== videoTitle && !isSourceChangingRef.current) { if (reloadParam && urlTitle && urlTitle !== videoTitle && !isSourceChangingRef.current) {
console.log('[PlayPage] User clicked recommendation, reloading page'); console.log('[PlayPage] User clicked recommendation, reloading page');
window.location.href = window.location.href; window.location.reload();
} }
// 重置换源标记 // 重置换源标记
@@ -1526,7 +1525,7 @@ function PlayPageClient() {
const refreshXiaoyaUrl = async ( const refreshXiaoyaUrl = async (
hls: any, hls: any,
video: HTMLVideoElement, video: HTMLVideoElement,
isScheduled: boolean = false isScheduled = false
) => { ) => {
// 防抖:距离上次刷新不足3秒则不刷新 // 防抖:距离上次刷新不足3秒则不刷新
const now = Date.now(); const now = Date.now();
@@ -3911,7 +3910,7 @@ function PlayPageClient() {
}; };
// 处理弹幕选择 // 处理弹幕选择
const handleDanmakuSelect = async (selection: DanmakuSelection, isManual: boolean = false) => { const handleDanmakuSelect = async (selection: DanmakuSelection, isManual = false) => {
console.log(`[弹幕选择] isManual=${isManual}, selection:`, selection); console.log(`[弹幕选择] isManual=${isManual}, selection:`, selection);
setCurrentDanmakuSelection(selection); setCurrentDanmakuSelection(selection);
@@ -3944,7 +3943,7 @@ function PlayPageClient() {
}; };
// 处理用户选择弹幕源 // 处理用户选择弹幕源
const handleDanmakuSourceSelect = async (selectedAnime: DanmakuAnime, selectedIndex?: number, isManualSearch: boolean = false) => { const handleDanmakuSourceSelect = async (selectedAnime: DanmakuAnime, selectedIndex?: number, isManualSearch = false) => {
setShowDanmakuSourceSelector(false); setShowDanmakuSourceSelector(false);
try { try {
@@ -4863,12 +4862,14 @@ function PlayPageClient() {
return; return;
} }
// 检查其他 HTTP 错误状态码 // 检查其他 HTTP 错误状态码
const statusCode = data.response?.code || data.response?.status; {
if (statusCode && statusCode >= 400) { const statusCode = data.response?.code || data.response?.status;
console.log(`HTTP ${statusCode} 错误`); if (statusCode && statusCode >= 400) {
hls.destroy(); console.log(`HTTP ${statusCode} 错误`);
setVideoError(`HTTP ${statusCode} 错误`); hls.destroy();
return; setVideoError(`HTTP ${statusCode} 错误`);
return;
}
} }
console.log('网络错误,尝试恢复...'); console.log('网络错误,尝试恢复...');
hls.startLoad(); hls.startLoad();
+4 -3
View File
@@ -2,15 +2,16 @@
'use client'; 'use client';
import { ArrowDownWideNarrow, ArrowUpNarrowWide,Film } from 'lucide-react';
import { useRouter, useSearchParams } from 'next/navigation'; import { useRouter, useSearchParams } from 'next/navigation';
import { useEffect, useState, useRef, useMemo } from 'react'; import { useEffect, useMemo,useRef, useState } from 'react';
import { Film, ArrowUpDown, ArrowDownWideNarrow, ArrowUpNarrowWide } from 'lucide-react';
import { createPortal } from 'react-dom'; import { createPortal } from 'react-dom';
import { base58Encode } from '@/lib/utils';
import CapsuleSwitch from '@/components/CapsuleSwitch'; import CapsuleSwitch from '@/components/CapsuleSwitch';
import PageLayout from '@/components/PageLayout'; import PageLayout from '@/components/PageLayout';
import VideoCard from '@/components/VideoCard'; import VideoCard from '@/components/VideoCard';
import { base58Encode } from '@/lib/utils';
type LibrarySourceType = 'openlist' | 'emby' | 'xiaoya' | `emby:${string}` | `emby_${string}`; type LibrarySourceType = 'openlist' | 'emby' | 'xiaoya' | `emby:${string}` | `emby_${string}`;
+5 -5
View File
@@ -1,10 +1,11 @@
/* eslint-disable react-hooks/exhaustive-deps, @typescript-eslint/no-explicit-any,@typescript-eslint/no-non-null-assertion,no-empty */ /* eslint-disable react-hooks/exhaustive-deps, @typescript-eslint/no-explicit-any,@typescript-eslint/no-non-null-assertion,no-empty */
'use client'; 'use client';
import { ChevronUp, RefreshCw, Search, X, Film, HardDrive, Magnet } from 'lucide-react'; import { ChevronUp, Film, HardDrive, Magnet,RefreshCw, Search, X } from 'lucide-react';
import { useRouter, useSearchParams } from 'next/navigation'; import { useRouter, useSearchParams } from 'next/navigation';
import React, { startTransition, Suspense, useEffect, useMemo, useRef, useState } from 'react'; import React, { startTransition, Suspense, useEffect, useMemo, useRef, useState } from 'react';
import { getAuthInfoFromBrowserCookie } from '@/lib/auth';
import { import {
addSearchHistory, addSearchHistory,
clearSearchHistory, clearSearchHistory,
@@ -13,15 +14,14 @@ import {
subscribeToDataUpdates, subscribeToDataUpdates,
} from '@/lib/db.client'; } from '@/lib/db.client';
import { SearchResult } from '@/lib/types'; import { SearchResult } from '@/lib/types';
import { getAuthInfoFromBrowserCookie } from '@/lib/auth';
import AcgSearch from '@/components/AcgSearch';
import CapsuleSwitch from '@/components/CapsuleSwitch';
import PageLayout from '@/components/PageLayout'; import PageLayout from '@/components/PageLayout';
import PansouSearch from '@/components/PansouSearch';
import SearchResultFilter, { SearchFilterCategory } from '@/components/SearchResultFilter'; import SearchResultFilter, { SearchFilterCategory } from '@/components/SearchResultFilter';
import SearchSuggestions from '@/components/SearchSuggestions'; import SearchSuggestions from '@/components/SearchSuggestions';
import VideoCard, { VideoCardHandle } from '@/components/VideoCard'; import VideoCard, { VideoCardHandle } from '@/components/VideoCard';
import PansouSearch from '@/components/PansouSearch';
import AcgSearch from '@/components/AcgSearch';
import CapsuleSwitch from '@/components/CapsuleSwitch';
function SearchPageClient() { function SearchPageClient() {
// 搜索历史 // 搜索历史
+7 -4
View File
@@ -1,12 +1,15 @@
// 观影室首页 - 选项卡式界面 // 观影室首页 - 选项卡式界面
'use client'; 'use client';
import { useState, useEffect } from 'react'; import { List as ListIcon, Lock, RefreshCw,UserPlus, Users } from 'lucide-react';
import { Users, UserPlus, List as ListIcon, Lock, RefreshCw } from 'lucide-react';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
import { useWatchRoomContext } from '@/components/WatchRoomProvider'; import { useEffect,useState } from 'react';
import PageLayout from '@/components/PageLayout';
import { getAuthInfoFromBrowserCookie } from '@/lib/auth'; import { getAuthInfoFromBrowserCookie } from '@/lib/auth';
import PageLayout from '@/components/PageLayout';
import { useWatchRoomContext } from '@/components/WatchRoomProvider';
import type { Room } from '@/types/watch-room'; import type { Room } from '@/types/watch-room';
type TabType = 'create' | 'join' | 'list'; type TabType = 'create' | 'join' | 'list';
+2 -2
View File
@@ -1,9 +1,9 @@
'use client'; 'use client';
import { AlertTriangle,Radio } from 'lucide-react';
import { useEffect, useRef, useState } from 'react'; import { useEffect, useRef, useState } from 'react';
import PageLayout from '@/components/PageLayout'; import PageLayout from '@/components/PageLayout';
import { Radio, AlertTriangle } from 'lucide-react';
import Head from 'next/head';
let Artplayer: any = null; let Artplayer: any = null;
let Hls: any = null; let Hls: any = null;
+3 -2
View File
@@ -1,11 +1,12 @@
/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-explicit-any */
'use client'; 'use client';
import React, { useState, useRef, useEffect } from 'react'; import { Bot, Loader2, Send, Sparkles, Trash2,X } from 'lucide-react';
import React, { useEffect,useRef, useState } from 'react';
import { createPortal } from 'react-dom'; import { createPortal } from 'react-dom';
import { X, Send, Bot, Loader2, Sparkles, Trash2 } from 'lucide-react';
import ReactMarkdown from 'react-markdown'; import ReactMarkdown from 'react-markdown';
import remarkGfm from 'remark-gfm'; import remarkGfm from 'remark-gfm';
import { VideoContext } from '@/lib/ai-orchestrator'; import { VideoContext } from '@/lib/ai-orchestrator';
interface ChatMessage { interface ChatMessage {
+2 -2
View File
@@ -2,10 +2,10 @@
'use client'; 'use client';
import { AlertCircle, Download, ExternalLink, Loader2 } from 'lucide-react'; import { AlertCircle, Download, ExternalLink, Loader2 } from 'lucide-react';
import { useEffect, useState, useRef, useCallback } from 'react'; import { useCallback,useEffect, useRef, useState } from 'react';
import Toast, { ToastProps } from '@/components/Toast';
import CapsuleSwitch from '@/components/CapsuleSwitch'; import CapsuleSwitch from '@/components/CapsuleSwitch';
import Toast, { ToastProps } from '@/components/Toast';
interface AcgSearchItem { interface AcgSearchItem {
title: string; title: string;
+4 -3
View File
@@ -1,11 +1,12 @@
'use client'; 'use client';
import { useEffect, useState, useCallback, useRef } from 'react'; import { ChevronLeft, ChevronRight, Play } from 'lucide-react';
import Image from 'next/image'; import Image from 'next/image';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
import { getTMDBImageUrl, getGenreNames, type TMDBItem } from '@/lib/tmdb.client'; import { useCallback, useEffect, useRef,useState } from 'react';
import { type TMDBItem,getGenreNames, getTMDBImageUrl } from '@/lib/tmdb.client';
import { processImageUrl } from '@/lib/utils'; import { processImageUrl } from '@/lib/utils';
import { ChevronLeft, ChevronRight, Play } from 'lucide-react';
interface BannerCarouselProps { interface BannerCarouselProps {
autoPlayInterval?: number; // 自动播放间隔(毫秒) autoPlayInterval?: number; // 自动播放间隔(毫秒)
+1 -1
View File
@@ -1,9 +1,9 @@
/* eslint-disable no-console */ /* eslint-disable no-console */
'use client'; 'use client';
import { AlertTriangle } from 'lucide-react';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { createPortal } from 'react-dom'; import { createPortal } from 'react-dom';
import { AlertTriangle } from 'lucide-react';
import type { PlayRecord } from '@/lib/db.client'; import type { PlayRecord } from '@/lib/db.client';
import { import {
+1 -1
View File
@@ -225,7 +225,7 @@ export default function CorrectDialog({
try { try {
// 构建标题和ID:如果是第二季及以后,在标题后加上季度名称,并使用季度ID // 构建标题和ID:如果是第二季及以后,在标题后加上季度名称,并使用季度ID
let finalTitle = result.title || result.name; let finalTitle = result.title || result.name;
let finalTmdbId = result.id; const finalTmdbId = result.id;
if (season && season.season_number > 1) { if (season && season.season_number > 1) {
finalTitle = `${finalTitle} ${season.name}`; finalTitle = `${finalTitle} ${season.name}`;
+1 -1
View File
@@ -1,6 +1,6 @@
'use client'; 'use client';
import React, { useEffect, useRef, useState, useCallback } from 'react'; import React, { useCallback,useEffect, useRef, useState } from 'react';
interface DanmakuData { interface DanmakuData {
time: number; time: number;
+1
View File
@@ -1,6 +1,7 @@
'use client'; 'use client';
import { useEffect } from 'react'; import { useEffect } from 'react';
import { initDanmakuModule } from '@/lib/danmaku/api'; import { initDanmakuModule } from '@/lib/danmaku/api';
/** /**
+4 -3
View File
@@ -1,10 +1,11 @@
/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-explicit-any */
'use client'; 'use client';
import { useState, useEffect, useRef } from 'react'; import { Plus, ToggleLeft, ToggleRight,Trash2, X } from 'lucide-react';
import { X, Plus, Trash2, ToggleLeft, ToggleRight } from 'lucide-react'; import { useEffect, useRef,useState } from 'react';
import { DanmakuFilterConfig, DanmakuFilterRule } from '@/lib/types';
import { getDanmakuFilterConfig, saveDanmakuFilterConfig } from '@/lib/db.client'; import { getDanmakuFilterConfig, saveDanmakuFilterConfig } from '@/lib/db.client';
import { DanmakuFilterConfig, DanmakuFilterRule } from '@/lib/types';
interface DanmakuFilterSettingsProps { interface DanmakuFilterSettingsProps {
isOpen: boolean; isOpen: boolean;
+1 -1
View File
@@ -1,7 +1,7 @@
'use client'; 'use client';
import { MagnifyingGlassIcon } from '@heroicons/react/24/outline'; import { MagnifyingGlassIcon } from '@heroicons/react/24/outline';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useCallback, useEffect, useRef, useState } from 'react';
import { getEpisodes, searchAnime } from '@/lib/danmaku/api'; import { getEpisodes, searchAnime } from '@/lib/danmaku/api';
import type { import type {
+2 -1
View File
@@ -1,9 +1,10 @@
'use client'; 'use client';
import { X, Calendar, Star, Clock, Tag, Users, Globe, Film } from 'lucide-react'; import { Calendar, Clock, Film,Globe, Star, Tag, Users, X } from 'lucide-react';
import Image from 'next/image'; import Image from 'next/image';
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { createPortal } from 'react-dom'; import { createPortal } from 'react-dom';
import { getTMDBImageUrl } from '@/lib/tmdb.client'; import { getTMDBImageUrl } from '@/lib/tmdb.client';
import { processImageUrl } from '@/lib/utils'; import { processImageUrl } from '@/lib/utils';
+2 -1
View File
@@ -1,6 +1,7 @@
'use client'; 'use client';
import { useEffect, useState, useCallback } from 'react'; import { useCallback,useEffect, useState } from 'react';
import { useEnableComments } from '@/hooks/useEnableComments'; import { useEnableComments } from '@/hooks/useEnableComments';
interface DoubanComment { interface DoubanComment {
+4 -2
View File
@@ -1,9 +1,11 @@
'use client'; 'use client';
import { useEffect, useState, useCallback } from 'react'; import { useCallback,useEffect, useState } from 'react';
import { useEnableComments } from '@/hooks/useEnableComments'; import { useEnableComments } from '@/hooks/useEnableComments';
import VideoCard from '@/components/VideoCard';
import ScrollableRow from '@/components/ScrollableRow'; import ScrollableRow from '@/components/ScrollableRow';
import VideoCard from '@/components/VideoCard';
interface DoubanRecommendation { interface DoubanRecommendation {
doubanId: string; doubanId: string;
+1
View File
@@ -1,6 +1,7 @@
'use client'; 'use client';
import React from 'react'; import React from 'react';
import { useDownload } from '@/contexts/DownloadContext'; import { useDownload } from '@/contexts/DownloadContext';
export function DownloadBubble() { export function DownloadBubble() {
+1 -1
View File
@@ -1,6 +1,6 @@
/* eslint-disable @next/next/no-img-element */ /* eslint-disable @next/next/no-img-element */
import React, { useState, useMemo } from 'react'; import React, { useMemo,useState } from 'react';
interface DownloadEpisodeSelectorProps { interface DownloadEpisodeSelectorProps {
/** 是否显示弹窗 */ /** 是否显示弹窗 */
+3 -1
View File
@@ -1,9 +1,11 @@
'use client'; 'use client';
import React from 'react'; import React from 'react';
import { useDownload } from '@/contexts/DownloadContext';
import { M3U8DownloadTask } from '@/lib/m3u8-downloader'; import { M3U8DownloadTask } from '@/lib/m3u8-downloader';
import { useDownload } from '@/contexts/DownloadContext';
export function DownloadPanel() { export function DownloadPanel() {
const { tasks, showDownloadPanel, setShowDownloadPanel, startTask, pauseTask, cancelTask, retryFailedSegments, getProgress } = useDownload(); const { tasks, showDownloadPanel, setShowDownloadPanel, startTask, pauseTask, cancelTask, retryFailedSegments, getProgress } = useDownload();
+1 -1
View File
@@ -1,6 +1,6 @@
/* eslint-disable react-hooks/exhaustive-deps */ /* eslint-disable react-hooks/exhaustive-deps */
import { Clock, Target, Tv, List, BarChart3 } from 'lucide-react'; import { BarChart3,Clock, List, Target, Tv } from 'lucide-react';
import { useEffect, useRef, useState } from 'react'; import { useEffect, useRef, useState } from 'react';
import { formatTimeToHHMM, parseCustomTimeFormat } from '@/lib/time'; import { formatTimeToHHMM, parseCustomTimeFormat } from '@/lib/time';
+4 -3
View File
@@ -1,10 +1,11 @@
/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-explicit-any */
'use client'; 'use client';
import { useState, useEffect, useRef } from 'react'; import { Plus, ToggleLeft, ToggleRight,Trash2, X } from 'lucide-react';
import { X, Plus, Trash2, ToggleLeft, ToggleRight } from 'lucide-react'; import { useEffect, useRef,useState } from 'react';
import { EpisodeFilterConfig, EpisodeFilterRule } from '@/lib/types';
import { getEpisodeFilterConfig, saveEpisodeFilterConfig } from '@/lib/db.client'; import { getEpisodeFilterConfig, saveEpisodeFilterConfig } from '@/lib/db.client';
import { EpisodeFilterConfig, EpisodeFilterRule } from '@/lib/types';
interface EpisodeFilterSettingsProps { interface EpisodeFilterSettingsProps {
isOpen: boolean; isOpen: boolean;
+5 -4
View File
@@ -1,5 +1,6 @@
/* eslint-disable @next/next/no-img-element */ /* eslint-disable @next/next/no-img-element */
import { Settings } from 'lucide-react';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
import React, { import React, {
useCallback, useCallback,
@@ -8,13 +9,13 @@ import React, {
useRef, useRef,
useState, useState,
} from 'react'; } from 'react';
import { Settings } from 'lucide-react';
import type { DanmakuComment,DanmakuSelection } from '@/lib/danmaku/types';
import { EpisodeFilterConfig,SearchResult } from '@/lib/types';
import { getVideoResolutionFromM3u8, processImageUrl } from '@/lib/utils';
import DanmakuPanel from '@/components/DanmakuPanel'; import DanmakuPanel from '@/components/DanmakuPanel';
import EpisodeFilterSettings from '@/components/EpisodeFilterSettings'; import EpisodeFilterSettings from '@/components/EpisodeFilterSettings';
import type { DanmakuSelection, DanmakuComment } from '@/lib/danmaku/types';
import { SearchResult, EpisodeFilterConfig } from '@/lib/types';
import { getVideoResolutionFromM3u8, processImageUrl } from '@/lib/utils';
// 定义视频信息类型 // 定义视频信息类型
interface VideoInfo { interface VideoInfo {
+2 -1
View File
@@ -2,7 +2,7 @@
'use client'; 'use client';
import { Star, X, AlertTriangle } from 'lucide-react'; import { AlertTriangle,Star, X } from 'lucide-react';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { createPortal } from 'react-dom'; import { createPortal } from 'react-dom';
@@ -12,6 +12,7 @@ import {
getAllPlayRecords, getAllPlayRecords,
subscribeToDataUpdates, subscribeToDataUpdates,
} from '@/lib/db.client'; } from '@/lib/db.client';
import VideoCard from '@/components/VideoCard'; import VideoCard from '@/components/VideoCard';
interface FavoriteItem { interface FavoriteItem {
+1
View File
@@ -3,6 +3,7 @@
import { AlertTriangle } from 'lucide-react'; import { AlertTriangle } from 'lucide-react';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { createPortal } from 'react-dom'; import { createPortal } from 'react-dom';
import { getAuthInfoFromBrowserCookie } from '@/lib/auth'; import { getAuthInfoFromBrowserCookie } from '@/lib/auth';
interface HttpWarningDialogProps { interface HttpWarningDialogProps {
+1 -1
View File
@@ -66,7 +66,7 @@ const MobileBottomNav = ({ activePath }: MobileBottomNavProps) => {
const runtimeConfig = (window as any).RUNTIME_CONFIG; const runtimeConfig = (window as any).RUNTIME_CONFIG;
// 基础导航项(不包括观影室) // 基础导航项(不包括观影室)
let items = [ const items = [
{ icon: Home, label: '首页', href: '/' }, { icon: Home, label: '首页', href: '/' },
{ {
icon: Film, icon: Film,
+1 -1
View File
@@ -3,8 +3,8 @@
'use client'; 'use client';
import { Bell, Check, Trash2, X } from 'lucide-react'; import { Bell, Check, Trash2, X } from 'lucide-react';
import { useEffect, useState } from 'react';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
import { useEffect, useState } from 'react';
import { Notification } from '@/lib/types'; import { Notification } from '@/lib/types';
+1 -1
View File
@@ -158,7 +158,7 @@ const Sidebar = ({ onToggle, activePath = '/' }: SidebarProps) => {
const runtimeConfig = (window as any).RUNTIME_CONFIG; const runtimeConfig = (window as any).RUNTIME_CONFIG;
// 基础菜单项(不包括观影室) // 基础菜单项(不包括观影室)
let items = [ const items = [
{ {
icon: Film, icon: Film,
label: '电影', label: '电影',
+4 -2
View File
@@ -1,10 +1,12 @@
'use client'; 'use client';
import { useEffect, useState, useCallback } from 'react'; import { useCallback,useEffect, useState } from 'react';
import { useEnableComments } from '@/hooks/useEnableComments'; import { useEnableComments } from '@/hooks/useEnableComments';
import { useRecommendationDataSource } from '@/hooks/useRecommendationDataSource'; import { useRecommendationDataSource } from '@/hooks/useRecommendationDataSource';
import VideoCard from '@/components/VideoCard';
import ScrollableRow from '@/components/ScrollableRow'; import ScrollableRow from '@/components/ScrollableRow';
import VideoCard from '@/components/VideoCard';
interface Recommendation { interface Recommendation {
doubanId?: string; doubanId?: string;
+1 -1
View File
@@ -1,7 +1,7 @@
'use client'; 'use client';
import { CheckCircle, Info, X,XCircle } from 'lucide-react';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { CheckCircle, XCircle, Info, X } from 'lucide-react';
export interface ToastProps { export interface ToastProps {
message: string; message: string;
+2 -2
View File
@@ -1,8 +1,8 @@
'use client'; 'use client';
import { useEffect, useRef } from 'react'; import { usePathname, useRouter,useSearchParams } from 'next/navigation';
import { usePathname, useSearchParams, useRouter } from 'next/navigation';
import NProgress from 'nprogress'; import NProgress from 'nprogress';
import { useEffect, useRef } from 'react';
// 创建全局钩子来拦截 router // 创建全局钩子来拦截 router
let globalRouterRef: any = null; let globalRouterRef: any = null;
+4 -4
View File
@@ -30,7 +30,7 @@ import {
X, X,
} from 'lucide-react'; } from 'lucide-react';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
import { useEffect, useRef, useState } from 'react'; import { useEffect, useState } from 'react';
import { createPortal } from 'react-dom'; import { createPortal } from 'react-dom';
import { getAuthInfoFromBrowserCookie } from '@/lib/auth'; import { getAuthInfoFromBrowserCookie } from '@/lib/auth';
@@ -38,11 +38,11 @@ import { clearAllDanmakuCache } from '@/lib/danmaku/api';
import { CURRENT_VERSION } from '@/lib/version'; import { CURRENT_VERSION } from '@/lib/version';
import { UpdateStatus } from '@/lib/version_check'; import { UpdateStatus } from '@/lib/version_check';
import { FavoritesPanel } from './FavoritesPanel';
import { NotificationPanel } from './NotificationPanel';
import { OfflineDownloadPanel } from './OfflineDownloadPanel';
import { useVersionCheck } from './VersionCheckProvider'; import { useVersionCheck } from './VersionCheckProvider';
import { VersionPanel } from './VersionPanel'; import { VersionPanel } from './VersionPanel';
import { OfflineDownloadPanel } from './OfflineDownloadPanel';
import { NotificationPanel } from './NotificationPanel';
import { FavoritesPanel } from './FavoritesPanel';
interface AuthInfo { interface AuthInfo {
username?: string; username?: string;
+2 -2
View File
@@ -24,10 +24,10 @@ import {
import { processImageUrl } from '@/lib/utils'; import { processImageUrl } from '@/lib/utils';
import { useLongPress } from '@/hooks/useLongPress'; import { useLongPress } from '@/hooks/useLongPress';
import { ImagePlaceholder } from '@/components/ImagePlaceholder';
import MobileActionSheet from '@/components/MobileActionSheet';
import AIChatPanel from '@/components/AIChatPanel'; import AIChatPanel from '@/components/AIChatPanel';
import DetailPanel from '@/components/DetailPanel'; import DetailPanel from '@/components/DetailPanel';
import { ImagePlaceholder } from '@/components/ImagePlaceholder';
import MobileActionSheet from '@/components/MobileActionSheet';
export interface VideoCardProps { export interface VideoCardProps {
id?: string; id?: string;
+1 -1
View File
@@ -1,7 +1,7 @@
'use client'; 'use client';
import { useRef, useEffect, useState } from 'react';
import { ChevronLeft, ChevronRight } from 'lucide-react'; import { ChevronLeft, ChevronRight } from 'lucide-react';
import { useEffect, useRef, useState } from 'react';
interface VirtualScrollableRowProps { interface VirtualScrollableRowProps {
children: React.ReactNode[]; children: React.ReactNode[];
+5 -2
View File
@@ -1,11 +1,14 @@
// WatchRoom 全局状态管理 Provider // WatchRoom 全局状态管理 Provider
'use client'; 'use client';
import React, { createContext, useContext, useEffect, useState, useCallback } from 'react'; import React, { createContext, useCallback,useContext, useEffect, useState } from 'react';
import { useWatchRoom } from '@/hooks/useWatchRoom'; import { useWatchRoom } from '@/hooks/useWatchRoom';
import type { Room, Member, ChatMessage, WatchRoomConfig } from '@/types/watch-room';
import Toast, { ToastProps } from '@/components/Toast'; import Toast, { ToastProps } from '@/components/Toast';
import type { ChatMessage, Member, Room, WatchRoomConfig } from '@/types/watch-room';
// Import type from watch-room-socket // Import type from watch-room-socket
type WatchRoomSocket = import('@/lib/watch-room-socket').WatchRoomSocket; type WatchRoomSocket = import('@/lib/watch-room-socket').WatchRoomSocket;
@@ -1,11 +1,13 @@
// 全局聊天悬浮窗和房间信息按钮 // 全局聊天悬浮窗和房间信息按钮
'use client'; 'use client';
import { useState, useEffect, useRef } from 'react'; import { AlertCircle,Info, LogOut, Maximize2, MessageCircle, Mic, MicOff, Minimize2, Send, Smile, Users, Volume2, VolumeX, X, XCircle } from 'lucide-react';
import { MessageCircle, X, Send, Smile, Minimize2, Maximize2, Info, Users, LogOut, XCircle, Mic, MicOff, Volume2, VolumeX, AlertCircle } from 'lucide-react'; import { useEffect, useRef,useState } from 'react';
import { useWatchRoomContextSafe } from '@/components/WatchRoomProvider';
import { useVoiceChat } from '@/hooks/useVoiceChat'; import { useVoiceChat } from '@/hooks/useVoiceChat';
import { useWatchRoomContextSafe } from '@/components/WatchRoomProvider';
const EMOJI_LIST = ['😀', '😂', '😍', '🥰', '😎', '🤔', '👍', '👏', '🎉', '❤️', '🔥', '⭐']; const EMOJI_LIST = ['😀', '😂', '😍', '🥰', '😎', '🤔', '👍', '👏', '🎉', '❤️', '🔥', '⭐'];
export default function ChatFloatingWindow() { export default function ChatFloatingWindow() {
+2 -1
View File
@@ -1,6 +1,7 @@
'use client'; 'use client';
import React, { createContext, useContext, useState, useCallback, useEffect } from 'react'; import React, { createContext, useCallback, useContext,useState } from 'react';
import { M3U8Downloader, M3U8DownloadTask } from '@/lib/m3u8-downloader'; import { M3U8Downloader, M3U8DownloadTask } from '@/lib/m3u8-downloader';
interface DownloadContextType { interface DownloadContextType {
+1 -1
View File
@@ -1,4 +1,4 @@
import { useState, useEffect } from 'react'; import { useEffect,useState } from 'react';
interface RuntimeConfig { interface RuntimeConfig {
EnableComments: boolean; EnableComments: boolean;
+3 -1
View File
@@ -1,8 +1,10 @@
// React Hook for Live Page Synchronization // React Hook for Live Page Synchronization
'use client'; 'use client';
import { useEffect, useRef, useCallback } from 'react'; import { useCallback,useEffect, useRef } from 'react';
import { useWatchRoomContextSafe } from '@/components/WatchRoomProvider'; import { useWatchRoomContextSafe } from '@/components/WatchRoomProvider';
import type { LiveState } from '@/types/watch-room'; import type { LiveState } from '@/types/watch-room';
interface UseLiveSyncOptions { interface UseLiveSyncOptions {
+3 -1
View File
@@ -1,9 +1,11 @@
// React Hook for Play Page Synchronization // React Hook for Play Page Synchronization
'use client'; 'use client';
import { useEffect, useRef, useCallback } from 'react';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
import { useCallback,useEffect, useRef } from 'react';
import { useWatchRoomContextSafe } from '@/components/WatchRoomProvider'; import { useWatchRoomContextSafe } from '@/components/WatchRoomProvider';
import type { PlayState } from '@/types/watch-room'; import type { PlayState } from '@/types/watch-room';
interface UsePlaySyncOptions { interface UsePlaySyncOptions {
+4 -2
View File
@@ -1,8 +1,10 @@
// React Hook for Voice Chat in Watch Room // React Hook for Voice Chat in Watch Room
'use client'; 'use client';
import { useEffect, useRef, useCallback, useState } from 'react'; import { useCallback, useEffect, useRef, useState } from 'react';
import type { WatchRoomSocket } from '@/lib/watch-room-socket'; import type { WatchRoomSocket } from '@/lib/watch-room-socket';
import type { Member } from '@/types/watch-room'; import type { Member } from '@/types/watch-room';
interface UseVoiceChatOptions { interface UseVoiceChatOptions {
@@ -520,7 +522,7 @@ export function useVoiceChat({
}, [switchToServerRelay]); }, [switchToServerRelay]);
// 播放服务器中转的音频 - 使用Web Audio API播放PCM数据 // 播放服务器中转的音频 - 使用Web Audio API播放PCM数据
const playServerRelayAudio = useCallback(async (userId: string, audioData: number[], sampleRate: number = 16000) => { const playServerRelayAudio = useCallback(async (userId: string, audioData: number[], sampleRate = 16000) => {
if (!isSpeakerEnabled) return; if (!isSpeakerEnabled) return;
try { try {
+8 -6
View File
@@ -1,16 +1,18 @@
// React Hook for Watch Room // React Hook for Watch Room
'use client'; 'use client';
import { useEffect, useState, useCallback, useRef } from 'react'; import { useCallback, useEffect, useRef,useState } from 'react';
import { watchRoomSocketManager, type WatchRoomSocket } from '@/lib/watch-room-socket';
import { type WatchRoomSocket,watchRoomSocketManager } from '@/lib/watch-room-socket';
import type { import type {
Room, ChatMessage,
LiveState,
Member, Member,
PlayState, PlayState,
LiveState, Room,
ChatMessage,
WatchRoomConfig,
StoredRoomInfo, StoredRoomInfo,
WatchRoomConfig,
} from '@/types/watch-room'; } from '@/types/watch-room';
const STORAGE_KEY = 'watch_room_info'; const STORAGE_KEY = 'watch_room_info';
+1 -1
View File
@@ -139,7 +139,7 @@ export class AESDecryptor {
const invSubMix2 = invSubMix[2]; const invSubMix2 = invSubMix[2];
const invSubMix3 = invSubMix[3]; const invSubMix3 = invSubMix[3];
let prev: number = 0; let prev = 0;
let t: number; let t: number;
for (ksRow = 0; ksRow < ksRows; ksRow++) { for (ksRow = 0; ksRow < ksRows; ksRow++) {
-1
View File
@@ -5,7 +5,6 @@
*/ */
import { fetchDoubanData as fetchDoubanAPI } from '@/lib/douban'; import { fetchDoubanData as fetchDoubanAPI } from '@/lib/douban';
import { searchTMDB, getTVSeasons } from '@/lib/tmdb.search';
import { getNextApiKey } from '@/lib/tmdb.client'; import { getNextApiKey } from '@/lib/tmdb.client';
export interface VideoContext { export interface VideoContext {
+10 -12
View File
@@ -1,6 +1,14 @@
// 弹幕 API 服务封装(通过本地代理转发) // 弹幕 API 服务封装(通过本地代理转发)
import {
clearAllDanmakuCache,
clearDanmakuCache,
clearExpiredDanmakuCache,
generateCacheKey,
getDanmakuCacheStats,
getDanmakuFromCache,
saveDanmakuToCache,
} from './cache';
import type { import type {
DanmakuAnime,
DanmakuComment, DanmakuComment,
DanmakuCommentsResponse, DanmakuCommentsResponse,
DanmakuEpisodesResponse, DanmakuEpisodesResponse,
@@ -10,16 +18,6 @@ import type {
DanmakuSettings, DanmakuSettings,
} from './types'; } from './types';
import {
getDanmakuFromCache,
saveDanmakuToCache,
clearExpiredDanmakuCache,
clearAllDanmakuCache,
clearDanmakuCache,
getDanmakuCacheStats,
generateCacheKey,
} from './cache';
// 初始化弹幕模块(清理过期缓存) // 初始化弹幕模块(清理过期缓存)
let _cacheCleanupInitialized = false; let _cacheCleanupInitialized = false;
@@ -46,8 +44,8 @@ export {
clearAllDanmakuCache, clearAllDanmakuCache,
clearDanmakuCache, clearDanmakuCache,
clearExpiredDanmakuCache, clearExpiredDanmakuCache,
getDanmakuCacheStats,
generateCacheKey, generateCacheKey,
getDanmakuCacheStats,
getDanmakuFromCache, getDanmakuFromCache,
}; };
+1 -1
View File
@@ -15,7 +15,7 @@
*/ */
import { getAuthInfoFromBrowserCookie } from './auth'; import { getAuthInfoFromBrowserCookie } from './auth';
import { SkipConfig, DanmakuFilterConfig, EpisodeFilterConfig } from './types'; import { DanmakuFilterConfig, EpisodeFilterConfig,SkipConfig } from './types';
// 全局错误触发函数 // 全局错误触发函数
function triggerGlobalError(message: string) { function triggerGlobalError(message: string) {
+3 -3
View File
@@ -3,7 +3,7 @@
import { AdminConfig } from './admin.types'; import { AdminConfig } from './admin.types';
import { KvrocksStorage } from './kvrocks.db'; import { KvrocksStorage } from './kvrocks.db';
import { RedisStorage } from './redis.db'; import { RedisStorage } from './redis.db';
import { Favorite, IStorage, PlayRecord, SkipConfig, DanmakuFilterConfig } from './types'; import { DanmakuFilterConfig,Favorite, IStorage, PlayRecord, SkipConfig } from './types';
import { UpstashRedisStorage } from './upstash.db'; import { UpstashRedisStorage } from './upstash.db';
// storage type 常量: 'localstorage' | 'redis' | 'upstash',默认 'localstorage' // storage type 常量: 'localstorage' | 'redis' | 'upstash',默认 'localstorage'
@@ -224,8 +224,8 @@ export class DbManager {
} }
async getUserListV2( async getUserListV2(
offset: number = 0, offset = 0,
limit: number = 20, limit = 20,
ownerUsername?: string ownerUsername?: string
): Promise<{ ): Promise<{
users: Array<{ users: Array<{
+1
View File
@@ -1,4 +1,5 @@
import nodemailer from 'nodemailer'; import nodemailer from 'nodemailer';
import type { AdminConfig } from './admin.types'; import type { AdminConfig } from './admin.types';
export interface EmailOptions { export interface EmailOptions {
+2 -2
View File
@@ -72,7 +72,7 @@ export function clearEmbyCache(): { cleared: number } {
/** /**
* Emby * Emby
*/ */
export function getCachedEmbyViews(embyKey: string = 'default'): any | null { export function getCachedEmbyViews(embyKey = 'default'): any | null {
const cacheKey = `${EMBY_VIEWS_CACHE_KEY}:${embyKey}`; const cacheKey = `${EMBY_VIEWS_CACHE_KEY}:${embyKey}`;
const entry = EMBY_CACHE.get(cacheKey); const entry = EMBY_CACHE.get(cacheKey);
if (!entry) return null; if (!entry) return null;
@@ -89,7 +89,7 @@ export function getCachedEmbyViews(embyKey: string = 'default'): any | null {
/** /**
* Emby * Emby
*/ */
export function setCachedEmbyViews(embyKey: string = 'default', data: any): void { export function setCachedEmbyViews(embyKey = 'default', data: any): void {
const now = Date.now(); const now = Date.now();
const cacheKey = `${EMBY_VIEWS_CACHE_KEY}:${embyKey}`; const cacheKey = `${EMBY_VIEWS_CACHE_KEY}:${embyKey}`;
EMBY_CACHE.set(cacheKey, { EMBY_CACHE.set(cacheKey, {
+2 -2
View File
@@ -1,8 +1,8 @@
/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-explicit-any */
import { EmbyClient } from './emby.client';
import { getConfig } from './config';
import { AdminConfig } from './admin.types'; import { AdminConfig } from './admin.types';
import { getConfig } from './config';
import { EmbyClient } from './emby.client';
interface EmbySourceConfig { interface EmbySourceConfig {
key: string; key: string;
+8 -12
View File
@@ -165,19 +165,15 @@ export class EmbyClient {
const url = `${this.serverUrl}/Users/Me`; const url = `${this.serverUrl}/Users/Me`;
const headers = this.getHeaders(); const headers = this.getHeaders();
try { const response = await fetch(url, { headers });
const response = await fetch(url, { headers });
if (!response.ok) { if (!response.ok) {
const errorText = await response.text(); const errorText = await response.text();
throw new Error(`获取当前用户信息失败 (${response.status}): ${errorText}`); throw new Error(`获取当前用户信息失败 (${response.status}): ${errorText}`);
}
const data = await response.json();
return data;
} catch (error) {
throw error;
} }
const data = await response.json();
return data;
} }
async getUserViews(): Promise<EmbyView[]> { async getUserViews(): Promise<EmbyView[]> {
@@ -467,7 +463,7 @@ export class EmbyClient {
} }
} }
async getStreamUrl(itemId: string, direct: boolean = true, forceDirectUrl: boolean = false): Promise<string> { async getStreamUrl(itemId: string, direct = true, forceDirectUrl = false): Promise<string> {
const token = this.apiKey || this.authToken; const token = this.apiKey || this.authToken;
// 如果启用了代理播放且不是强制获取直接URL,返回代理URL // 如果启用了代理播放且不是强制获取直接URL,返回代理URL
+1 -1
View File
@@ -1,7 +1,7 @@
/* eslint-disable no-console, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */ /* eslint-disable no-console, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */
import { BaseRedisStorage, createRedisClient, createRetryWrapper } from './redis-base.db';
import { StandardRedisAdapter } from './redis-adapter'; import { StandardRedisAdapter } from './redis-adapter';
import { BaseRedisStorage, createRedisClient, createRetryWrapper } from './redis-base.db';
export class KvrocksStorage extends BaseRedisStorage { export class KvrocksStorage extends BaseRedisStorage {
constructor() { constructor() {
+2 -1
View File
@@ -3,10 +3,11 @@
* M3U8Download TypeScript * M3U8Download TypeScript
*/ */
import { AESDecryptor } from './aes-decryptor';
// @ts-ignore - mux.js 没有类型定义 // @ts-ignore - mux.js 没有类型定义
import * as muxjs from 'mux.js'; import * as muxjs from 'mux.js';
import { AESDecryptor } from './aes-decryptor';
export interface M3U8DownloadTask { export interface M3U8DownloadTask {
id: string; id: string;
url: string; url: string;
+9 -5
View File
@@ -4,11 +4,11 @@
*/ */
import * as fs from 'fs'; import * as fs from 'fs';
import * as path from 'path';
import * as https from 'https';
import * as http from 'http'; import * as http from 'http';
import * as zlib from 'zlib'; import * as https from 'https';
import * as path from 'path';
import { URL } from 'url'; import { URL } from 'url';
import * as zlib from 'zlib';
export interface OfflineDownloadTask { export interface OfflineDownloadTask {
id: string; id: string;
@@ -467,12 +467,16 @@ export class OfflineDownloader {
}); });
fileStream.on('error', (err) => { fileStream.on('error', (err) => {
fs.unlink(savePath, () => {}); fs.unlink(savePath, () => {
// Ignore unlink errors
});
reject(err); reject(err);
}); });
stream.on('error', (err) => { stream.on('error', (err) => {
fs.unlink(savePath, () => {}); fs.unlink(savePath, () => {
// Ignore unlink errors
});
reject(err); reject(err);
}); });
}); });
+6 -6
View File
@@ -1,11 +1,13 @@
/* eslint-disable @typescript-eslint/no-explicit-any, no-console */ /* eslint-disable @typescript-eslint/no-explicit-any, no-console */
import parseTorrentName from 'parse-torrent-name';
import type { AdminConfig } from '@/lib/admin.types';
import { getConfig } from '@/lib/config'; import { getConfig } from '@/lib/config';
import { generateFolderKey } from '@/lib/crypto'; import { generateFolderKey } from '@/lib/crypto';
import { db } from '@/lib/db'; import { db } from '@/lib/db';
import { OpenListClient } from '@/lib/openlist.client'; import { OpenListClient } from '@/lib/openlist.client';
import { import {
getCachedMetaInfo,
invalidateMetaInfoCache, invalidateMetaInfoCache,
MetaInfo, MetaInfo,
setCachedMetaInfo, setCachedMetaInfo,
@@ -18,9 +20,7 @@ import {
updateScanTaskProgress, updateScanTaskProgress,
} from '@/lib/scan-task'; } from '@/lib/scan-task';
import { parseSeasonFromTitle } from '@/lib/season-parser'; import { parseSeasonFromTitle } from '@/lib/season-parser';
import { searchTMDB, getTVSeasonDetails } from '@/lib/tmdb.search'; import { getTVSeasonDetails,searchTMDB } from '@/lib/tmdb.search';
import parseTorrentName from 'parse-torrent-name';
import type { AdminConfig } from '@/lib/admin.types';
/** /**
* *
@@ -58,7 +58,7 @@ async function migrateToMultiRoot(openListConfig: NonNullable<AdminConfig['OpenL
const metaInfo: MetaInfo = JSON.parse(metainfoContent); const metaInfo: MetaInfo = JSON.parse(metainfoContent);
// 2. 迁移 folderName:加上原根路径前缀 // 2. 迁移 folderName:加上原根路径前缀
for (const [key, info] of Object.entries(metaInfo.folders)) { for (const [_key, info] of Object.entries(metaInfo.folders)) {
const oldFolderName = info.folderName; const oldFolderName = info.folderName;
const newFolderName = `${oldRootPath}${oldRootPath.endsWith('/') ? '' : '/'}${oldFolderName}`; const newFolderName = `${oldRootPath}${oldRootPath.endsWith('/') ? '' : '/'}${oldFolderName}`;
info.folderName = newFolderName; info.folderName = newFolderName;
@@ -83,7 +83,7 @@ async function migrateToMultiRoot(openListConfig: NonNullable<AdminConfig['OpenL
/** /**
* OpenList * OpenList
*/ */
export async function startOpenListRefresh(clearMetaInfo: boolean = false): Promise<{ taskId: string }> { export async function startOpenListRefresh(clearMetaInfo = false): Promise<{ taskId: string }> {
const config = await getConfig(); const config = await getConfig();
const openListConfig = config.OpenListConfig; const openListConfig = config.OpenListConfig;
+1 -1
View File
@@ -33,7 +33,7 @@ export interface OpenListGetResponse {
} }
export class OpenListClient { export class OpenListClient {
private token: string = ''; private token = '';
constructor( constructor(
private baseURL: string, private baseURL: string,
+1 -1
View File
@@ -1,7 +1,7 @@
/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-explicit-any */
import { RedisClientType } from 'redis';
import { Redis } from '@upstash/redis'; import { Redis } from '@upstash/redis';
import { RedisClientType } from 'redis';
/** /**
* Redis * Redis
+2 -2
View File
@@ -796,8 +796,8 @@ export abstract class BaseRedisStorage implements IStorage {
// 获取用户列表(分页,新版本) // 获取用户列表(分页,新版本)
async getUserListV2( async getUserListV2(
offset: number = 0, offset = 0,
limit: number = 20, limit = 20,
ownerUsername?: string ownerUsername?: string
): Promise<{ ): Promise<{
users: Array<{ users: Array<{
+1 -1
View File
@@ -1,7 +1,7 @@
/* eslint-disable no-console, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */ /* eslint-disable no-console, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */
import { BaseRedisStorage, createRedisClient, createRetryWrapper } from './redis-base.db';
import { StandardRedisAdapter } from './redis-adapter'; import { StandardRedisAdapter } from './redis-adapter';
import { BaseRedisStorage, createRedisClient, createRetryWrapper } from './redis-base.db';
export class RedisStorage extends BaseRedisStorage { export class RedisStorage extends BaseRedisStorage {
constructor() { constructor() {
+5 -5
View File
@@ -85,8 +85,8 @@ interface TMDBTVAiringTodayResponse {
*/ */
export async function getTMDBUpcomingMovies( export async function getTMDBUpcomingMovies(
apiKey: string, apiKey: string,
page: number = 1, page = 1,
region: string = 'CN', region = 'CN',
proxy?: string, proxy?: string,
reverseProxyBaseUrl?: string reverseProxyBaseUrl?: string
): Promise<{ code: number; list: TMDBMovie[] }> { ): Promise<{ code: number; list: TMDBMovie[] }> {
@@ -140,7 +140,7 @@ export async function getTMDBUpcomingMovies(
*/ */
export async function getTMDBUpcomingTVShows( export async function getTMDBUpcomingTVShows(
apiKey: string, apiKey: string,
page: number = 1, page = 1,
proxy?: string, proxy?: string,
reverseProxyBaseUrl?: string reverseProxyBaseUrl?: string
): Promise<{ code: number; list: TMDBTVShow[] }> { ): Promise<{ code: number; list: TMDBTVShow[] }> {
@@ -400,7 +400,7 @@ export async function getTMDBTrendingContent(
*/ */
export function getTMDBImageUrl( export function getTMDBImageUrl(
path: string | null, path: string | null,
size: string = 'w500' size = 'w500'
): string { ): string {
if (!path) return ''; if (!path) return '';
const baseUrl = typeof window !== 'undefined' const baseUrl = typeof window !== 'undefined'
@@ -450,7 +450,7 @@ export const TMDB_GENRES: Record<number, string> = {
* @param limit - 2 * @param limit - 2
* @returns * @returns
*/ */
export function getGenreNames(genreIds: number[] = [], limit: number = 2): string[] { export function getGenreNames(genreIds: number[] = [], limit = 2): string[] {
return genreIds return genreIds
.map(id => TMDB_GENRES[id]) .map(id => TMDB_GENRES[id])
.filter(Boolean) .filter(Boolean)
+2 -1
View File
@@ -2,6 +2,7 @@
import { HttpsProxyAgent } from 'https-proxy-agent'; import { HttpsProxyAgent } from 'https-proxy-agent';
import nodeFetch from 'node-fetch'; import nodeFetch from 'node-fetch';
import { getNextApiKey } from './tmdb.client'; import { getNextApiKey } from './tmdb.client';
// TMDB API 默认 Base URL(不包含 /3/,由程序拼接) // TMDB API 默认 Base URL(不包含 /3/,由程序拼接)
@@ -226,7 +227,7 @@ export async function getTVSeasonDetails(
*/ */
export function getTMDBImageUrl( export function getTMDBImageUrl(
path: string | null, path: string | null,
size: string = 'w500' size = 'w500'
): string { ): string {
if (!path) return ''; if (!path) return '';
+1 -1
View File
@@ -2,8 +2,8 @@
import { Redis } from '@upstash/redis'; import { Redis } from '@upstash/redis';
import { BaseRedisStorage } from './redis-base.db';
import { UpstashRedisAdapter } from './redis-adapter'; import { UpstashRedisAdapter } from './redis-adapter';
import { BaseRedisStorage } from './redis-base.db';
// 添加Upstash Redis操作重试包装器 // 添加Upstash Redis操作重试包装器
async function withRetry<T>( async function withRetry<T>(
+2 -2
View File
@@ -1,7 +1,7 @@
/* eslint-disable @typescript-eslint/no-explicit-any,no-console */ /* eslint-disable @typescript-eslint/no-explicit-any,no-console */
import bs58 from 'bs58';
import he from 'he'; import he from 'he';
import Hls from 'hls.js'; import Hls from 'hls.js';
import bs58 from 'bs58';
function getDoubanImageProxyConfig(): { function getDoubanImageProxyConfig(): {
proxyType: proxyType:
@@ -155,7 +155,7 @@ export function processVideoUrl(originalUrl: string): string {
*/ */
export async function getVideoResolutionFromM3u8( export async function getVideoResolutionFromM3u8(
m3u8Url: string, m3u8Url: string,
timeoutMs: number = 4000 timeoutMs = 4000
): Promise<{ ): Promise<{
quality: string; // 如720p、1080p等 quality: string; // 如720p、1080p等
loadSpeed: string; // 自动转换为KB/s或MB/s loadSpeed: string; // 自动转换为KB/s或MB/s
+2 -2
View File
@@ -34,13 +34,13 @@ export function parseVideoFileName(fileName: string): ParsedVideoInfo {
// S01E01, s01e01, S01E1234, S01E01.5 (支持1-4位数字和小数) - 最具体 // S01E01, s01e01, S01E1234, S01E01.5 (支持1-4位数字和小数) - 最具体
{ pattern: /[Ss](\d+)[Ee](\d{1,4}(?:\.\d+)?)/, extractSeason: true }, { pattern: /[Ss](\d+)[Ee](\d{1,4}(?:\.\d+)?)/, extractSeason: true },
// [01], (01), [01.5], (01.5) (支持小数,但要排除中文括号内容) - 很具体 // [01], (01), [01.5], (01.5) (支持小数,但要排除中文括号内容) - 很具体
{ pattern: /[\[\(](\d+(?:\.\d+)?)[\]\)]/ }, { pattern: /[[(](\d+(?:\.\d+)?)[)\]]/ },
// E01, E1, e01, e1, E01.5 (支持小数) // E01, E1, e01, e1, E01.5 (支持小数)
{ pattern: /[Ee](\d+(?:\.\d+)?)/ }, { pattern: /[Ee](\d+(?:\.\d+)?)/ },
// 第01集, 第1集, 第01话, 第1话, 第1.5集 (支持小数) // 第01集, 第1集, 第01话, 第1话, 第1.5集 (支持小数)
{ pattern: /第(\d+(?:\.\d+)?)[集话]/ }, { pattern: /第(\d+(?:\.\d+)?)[集话]/ },
// _01_, -01-, _01.5_, -01.5- (支持小数) // _01_, -01-, _01.5_, -01.5- (支持小数)
{ pattern: /[_\-](\d+(?:\.\d+)?)[_\-]/ }, { pattern: /[_-](\d+(?:\.\d+)?)[_-]/ },
// 01.mp4, 001.mp4, 01.5.mp4 (纯数字开头,支持小数) - 最不具体 // 01.mp4, 001.mp4, 01.5.mp4 (纯数字开头,支持小数) - 最不具体
{ pattern: /^(\d+(?:\.\d+)?)[^\d.]/ }, { pattern: /^(\d+(?:\.\d+)?)[^\d.]/ },
]; ];
+4 -5
View File
@@ -1,14 +1,13 @@
// Socket.IO 观影室服务器逻辑(共享代码) // Socket.IO 观影室服务器逻辑(共享代码)
import { Server as SocketIOServer, Socket } from 'socket.io'; import { Server as SocketIOServer, Socket } from 'socket.io';
import type { import type {
Room,
Member,
PlayState,
LiveState,
ChatMessage, ChatMessage,
ServerToClientEvents,
ClientToServerEvents, ClientToServerEvents,
Member,
Room,
RoomMemberInfo, RoomMemberInfo,
ServerToClientEvents,
} from '@/types/watch-room'; } from '@/types/watch-room';
type TypedSocket = Socket<ClientToServerEvents, ServerToClientEvents>; type TypedSocket = Socket<ClientToServerEvents, ServerToClientEvents>;
+1 -1
View File
@@ -153,7 +153,7 @@ class WatchRoomSocketManager {
}); });
// 监听心跳响应 // 监听心跳响应
this.socket.on('heartbeat:pong', (data: { timestamp: number }) => { this.socket.on('heartbeat:pong', (_data: { timestamp: number }) => {
this.lastHeartbeatResponse = Date.now(); this.lastHeartbeatResponse = Date.now();
}); });
+4 -4
View File
@@ -1,8 +1,8 @@
/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-explicit-any */
import { XiaoyaClient } from './xiaoya.client'; import { NFOMetadata,parseNFO } from './nfo-parser';
import { parseNFO, NFOMetadata } from './nfo-parser';
import { parseVideoFileName } from './video-parser'; import { parseVideoFileName } from './video-parser';
import { XiaoyaClient } from './xiaoya.client';
export interface XiaoyaMetadata { export interface XiaoyaMetadata {
tmdbId?: number; tmdbId?: number;
@@ -208,7 +208,7 @@ export async function getXiaoyaMetadata(
const fileName = pathParts[pathParts.length - 1]; const fileName = pathParts[pathParts.length - 1];
const searchQuery = fileName const searchQuery = fileName
.replace(/\.(mp4|mkv|avi|m3u8|flv|ts)$/i, '') .replace(/\.(mp4|mkv|avi|m3u8|flv|ts)$/i, '')
.replace(/[\[\]()]/g, ' ') .replace(/[[\]()]/g, ' ')
.trim(); .trim();
// 如果文件名是纯数字(可能带小数点)或者是 SxxExx 格式,跳过文件名搜索,直接使用文件夹名 // 如果文件名是纯数字(可能带小数点)或者是 SxxExx 格式,跳过文件名搜索,直接使用文件夹名
@@ -240,7 +240,7 @@ export async function getXiaoyaMetadata(
// 优先级 4: 实时搜索 TMDb(使用文件夹名) // 优先级 4: 实时搜索 TMDb(使用文件夹名)
if (tmdbApiKey) { if (tmdbApiKey) {
const searchQuery = folderName const searchQuery = folderName
.replace(/[\[\](){}]/g, ' ') .replace(/[[\](){}]/g, ' ')
.replace(/\d{4}/g, '') .replace(/\d{4}/g, '')
.trim(); .trim();
+1 -1
View File
@@ -16,7 +16,7 @@ export interface XiaoyaListResponse {
} }
export class XiaoyaClient { export class XiaoyaClient {
private token: string = ''; private token = '';
constructor( constructor(
private baseURL: string, private baseURL: string,