eslint fix
This commit is contained in:
@@ -11224,7 +11224,7 @@ function AdminPageClient() {
|
||||
const userLimit = 10;
|
||||
|
||||
// 获取新版本用户列表
|
||||
const fetchUsersV2 = useCallback(async (page: number = 1) => {
|
||||
const fetchUsersV2 = useCallback(async (page = 1) => {
|
||||
try {
|
||||
setUserListLoading(true);
|
||||
const response = await fetch(`/api/admin/users?page=${page}&limit=${userLimit}`);
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
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 { getAuthInfoFromCookie } from '@/lib/auth';
|
||||
import { getConfig } from '@/lib/config';
|
||||
import { getStorage } from '@/lib/db';
|
||||
import { EmailService } from '@/lib/email.service';
|
||||
|
||||
export const runtime = 'nodejs';
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
|
||||
import { getAuthInfoFromCookie } from '@/lib/auth';
|
||||
import { getConfig } from '@/lib/config';
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
|
||||
import { getAuthInfoFromCookie } from '@/lib/auth';
|
||||
import { getConfig } from '@/lib/config';
|
||||
import { db } from '@/lib/db';
|
||||
@@ -37,7 +38,6 @@ export async function POST(request: NextRequest) {
|
||||
// 追加和覆盖:合并Sources数组
|
||||
if (data.Sources && Array.isArray(data.Sources)) {
|
||||
const existingSources = adminConfig.EmbyConfig?.Sources || [];
|
||||
const existingKeys = new Set(existingSources.map(s => s.key));
|
||||
|
||||
// 覆盖已存在的,追加新的
|
||||
const mergedSources = [...existingSources];
|
||||
|
||||
@@ -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) {
|
||||
return NextResponse.json(
|
||||
{ error: '定时扫描间隔最低为 60 分钟' },
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
|
||||
import { getAuthInfoFromCookie } from '@/lib/auth';
|
||||
import { getConfig } from '@/lib/config';
|
||||
import { db } from '@/lib/db';
|
||||
|
||||
export const runtime = 'nodejs';
|
||||
@@ -24,9 +23,6 @@ export async function GET(request: NextRequest) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
}
|
||||
|
||||
// 获取配置
|
||||
const adminConfig = await getConfig();
|
||||
|
||||
// 判定操作者角色
|
||||
let operatorRole: 'owner' | 'admin' | 'user' = 'user';
|
||||
if (authInfo.username === process.env.USERNAME) {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
|
||||
import { getAuthInfoFromCookie } from '@/lib/auth';
|
||||
import { getConfig } from '@/lib/config';
|
||||
import { db } from '@/lib/db';
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
|
||||
import { getAuthInfoFromCookie } from '@/lib/auth';
|
||||
import {
|
||||
orchestrateDataSources,
|
||||
VideoContext,
|
||||
} from '@/lib/ai-orchestrator';
|
||||
import { getAuthInfoFromCookie } from '@/lib/auth';
|
||||
import { getConfig } from '@/lib/config';
|
||||
import { db } from '@/lib/db';
|
||||
|
||||
@@ -35,7 +35,7 @@ async function streamOpenAIChat(
|
||||
temperature: number;
|
||||
maxTokens: number;
|
||||
},
|
||||
enableStreaming: boolean = true
|
||||
enableStreaming = true
|
||||
): Promise<ReadableStream | Response> {
|
||||
const response = await fetch(`${config.baseURL}/chat/completions`, {
|
||||
method: 'POST',
|
||||
@@ -61,45 +61,6 @@ async function streamOpenAIChat(
|
||||
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格式
|
||||
*/
|
||||
|
||||
@@ -123,7 +123,7 @@ export async function POST(request: NextRequest) {
|
||||
}
|
||||
|
||||
// 检查用户名是否已存在(优先使用新版本)
|
||||
let userExists = await db.checkUserExistV2(username);
|
||||
const userExists = await db.checkUserExistV2(username);
|
||||
if (userExists) {
|
||||
return NextResponse.json(
|
||||
{ error: '用户名已存在' },
|
||||
|
||||
@@ -4,7 +4,6 @@ import { NextRequest, NextResponse } from 'next/server';
|
||||
|
||||
import { getConfig } from '@/lib/config';
|
||||
import { db } from '@/lib/db';
|
||||
import { OpenListClient } from '@/lib/openlist.client';
|
||||
import {
|
||||
getCachedMetaInfo,
|
||||
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 (从数据库或缓存)
|
||||
let metaInfo: MetaInfo | null = getCachedMetaInfo();
|
||||
|
||||
@@ -296,7 +288,7 @@ async function handleOpenListProxy(request: NextRequest) {
|
||||
if (wd) {
|
||||
const results = Object.entries(metaInfo.folders)
|
||||
.filter(
|
||||
([key, info]) =>
|
||||
([_key, info]) =>
|
||||
info.folderName.toLowerCase().includes(wd.toLowerCase()) ||
|
||||
info.title.toLowerCase().includes(wd.toLowerCase())
|
||||
)
|
||||
|
||||
@@ -4,12 +4,12 @@ import { NextRequest, NextResponse } from 'next/server';
|
||||
|
||||
import { getConfig, refineConfig } from '@/lib/config';
|
||||
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 { refreshLiveChannels } from '@/lib/live';
|
||||
import { startOpenListRefresh } from '@/lib/openlist-refresh';
|
||||
import { SearchResult } from '@/lib/types';
|
||||
import { EmailService } from '@/lib/email.service';
|
||||
import { getBatchFavoriteUpdateEmailTemplate, FavoriteUpdate } from '@/lib/email.templates';
|
||||
|
||||
export const runtime = 'nodejs';
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
|
||||
import { getAuthInfoFromCookie } from '@/lib/auth';
|
||||
import { getConfig } from '@/lib/config';
|
||||
import { db } from '@/lib/db';
|
||||
import { DanmakuFilterConfig } from '@/lib/types';
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import * as cheerio from 'cheerio';
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
|
||||
import { fetchDoubanWithVerification } from '@/lib/douban-anti-crawler';
|
||||
|
||||
export const runtime = 'nodejs';
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import * as cheerio from 'cheerio';
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
|
||||
import { fetchDoubanData } from '@/lib/douban';
|
||||
import { fetchDoubanWithVerification } from '@/lib/douban-anti-crawler';
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
|
||||
import { embyManager } from '@/lib/emby-manager';
|
||||
import { getCachedEmbyList, setCachedEmbyList } from '@/lib/emby-cache';
|
||||
import { embyManager } from '@/lib/emby-manager';
|
||||
|
||||
export const runtime = 'nodejs';
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
|
||||
import { embyManager } from '@/lib/emby-manager';
|
||||
import { getCachedEmbyViews, setCachedEmbyViews } from '@/lib/emby-cache';
|
||||
import { embyManager } from '@/lib/emby-manager';
|
||||
|
||||
export const runtime = 'nodejs';
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
|
||||
import { getAuthInfoFromCookie } from '@/lib/auth';
|
||||
import { getConfig } from '@/lib/config';
|
||||
import { db } from '@/lib/db';
|
||||
import { Favorite } from '@/lib/types';
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { nanoid } from 'nanoid';
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
|
||||
import { getAuthInfoFromCookie } from '@/lib/auth';
|
||||
import { getStorage } from '@/lib/db';
|
||||
|
||||
+3
-2
@@ -3,11 +3,12 @@
|
||||
* 路径格式: /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 { NextRequest, NextResponse } from 'next/server';
|
||||
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_DIR = process.env.OFFLINE_DOWNLOAD_DIR || '/data';
|
||||
|
||||
@@ -2,11 +2,12 @@
|
||||
* 本地下载视频播放代理 API
|
||||
*/
|
||||
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { getAuthInfoFromCookie } from '@/lib/auth';
|
||||
import * as fs from 'fs';
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
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_DIR = process.env.OFFLINE_DOWNLOAD_DIR || '/data';
|
||||
|
||||
@@ -2,11 +2,12 @@
|
||||
* 离线下载任务管理 API
|
||||
*/
|
||||
|
||||
import * as fs from 'fs';
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import * as path from 'path';
|
||||
|
||||
import { getAuthInfoFromCookie } from '@/lib/auth';
|
||||
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';
|
||||
|
||||
@@ -6,7 +6,6 @@ import { getAuthInfoFromCookie } from '@/lib/auth';
|
||||
import { getConfig } from '@/lib/config';
|
||||
import { db } from '@/lib/db';
|
||||
import {
|
||||
getCachedMetaInfo,
|
||||
invalidateMetaInfoCache,
|
||||
MetaInfo,
|
||||
setCachedMetaInfo,
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
|
||||
import { getAuthInfoFromCookie } from '@/lib/auth';
|
||||
import { getConfig } from '@/lib/config';
|
||||
import { db } from '@/lib/db';
|
||||
import { PlayRecord } from '@/lib/types';
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
|
||||
import { getAuthInfoFromCookie } from '@/lib/auth';
|
||||
import { getConfig } from '@/lib/config';
|
||||
import { db } from '@/lib/db';
|
||||
|
||||
export const runtime = 'nodejs';
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
|
||||
import { getAuthInfoFromCookie } from '@/lib/auth';
|
||||
import { getConfig } from '@/lib/config';
|
||||
import { db } from '@/lib/db';
|
||||
import { SkipConfig } from '@/lib/types';
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
|
||||
import { getConfig } from '@/lib/config';
|
||||
|
||||
import { getThemeCSS } from '@/styles/themes';
|
||||
|
||||
export const runtime = 'nodejs';
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
|
||||
import { getConfig } from '@/lib/config';
|
||||
import {
|
||||
searchTMDBMulti,
|
||||
getTMDBImageUrl,
|
||||
getTMDBMovieDetails,
|
||||
getTMDBTVDetails,
|
||||
getTMDBImageUrl,
|
||||
searchTMDBMulti,
|
||||
} from '@/lib/tmdb.client';
|
||||
import { getConfig } from '@/lib/config';
|
||||
|
||||
// 服务器端缓存(内存)
|
||||
const searchCache = new Map<
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
|
||||
import { getConfig } from '@/lib/config';
|
||||
import {
|
||||
searchTMDBMulti,
|
||||
getTMDBImageUrl,
|
||||
getTMDBMovieRecommendations,
|
||||
getTMDBTVRecommendations,
|
||||
getTMDBImageUrl,
|
||||
searchTMDBMulti,
|
||||
} from '@/lib/tmdb.client';
|
||||
import { getConfig } from '@/lib/config';
|
||||
|
||||
// 服务器端缓存(1天)
|
||||
const searchCache = new Map<string, { data: any; timestamp: number }>();
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any, no-console */
|
||||
|
||||
import { HttpsProxyAgent } from 'https-proxy-agent';
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import nodeFetch from 'node-fetch';
|
||||
|
||||
import { getAuthInfoFromCookie } from '@/lib/auth';
|
||||
import { getConfig } from '@/lib/config';
|
||||
import { getNextApiKey } from '@/lib/tmdb.client';
|
||||
import { HttpsProxyAgent } from 'https-proxy-agent';
|
||||
import nodeFetch from 'node-fetch';
|
||||
|
||||
export const runtime = 'nodejs';
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any, no-console */
|
||||
|
||||
import { HttpsProxyAgent } from 'https-proxy-agent';
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import nodeFetch from 'node-fetch';
|
||||
|
||||
import { getAuthInfoFromCookie } from '@/lib/auth';
|
||||
import { getConfig } from '@/lib/config';
|
||||
import { HttpsProxyAgent } from 'https-proxy-agent';
|
||||
import nodeFetch from 'node-fetch';
|
||||
import { getNextApiKey } from '@/lib/tmdb.client';
|
||||
|
||||
export const runtime = 'nodejs';
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any, no-console */
|
||||
|
||||
import { HttpsProxyAgent } from 'https-proxy-agent';
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import nodeFetch from 'node-fetch';
|
||||
|
||||
import { getAuthInfoFromCookie } from '@/lib/auth';
|
||||
import { getConfig } from '@/lib/config';
|
||||
import { getNextApiKey } from '@/lib/tmdb.client';
|
||||
import { HttpsProxyAgent } from 'https-proxy-agent';
|
||||
import nodeFetch from 'node-fetch';
|
||||
|
||||
export const runtime = 'nodejs';
|
||||
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
|
||||
import { NextResponse } from 'next/server';
|
||||
import { getTMDBTrendingContent, getTMDBVideos } from '@/lib/tmdb.client';
|
||||
|
||||
import { getConfig } from '@/lib/config';
|
||||
import { fetchDoubanData } from '@/lib/douban';
|
||||
import { getTMDBTrendingContent, getTMDBVideos } from '@/lib/tmdb.client';
|
||||
|
||||
// 缓存配置 - 服务器内存缓存3小时
|
||||
const CACHE_DURATION = 3 * 60 * 60 * 1000; // 3小时
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { getTMDBUpcomingContent } from '@/lib/tmdb.client';
|
||||
|
||||
import { getConfig } from '@/lib/config';
|
||||
import { getTMDBUpcomingContent } from '@/lib/tmdb.client';
|
||||
|
||||
// 内存缓存对象
|
||||
interface CacheItem {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
|
||||
import { getConfig } from '@/lib/config';
|
||||
|
||||
export const dynamic = 'force-dynamic'; // 禁用缓存
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import crypto from 'crypto';
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
|
||||
function getAntiCode(oldAntiCode: string, streamName: string): string {
|
||||
const paramsT = 100;
|
||||
|
||||
+6
-6
@@ -7,16 +7,16 @@ import './globals.css';
|
||||
|
||||
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 { SiteProvider } from '../components/SiteProvider';
|
||||
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 ChatFloatingWindow from '../components/watch-room/ChatFloatingWindow';
|
||||
import { WatchRoomProvider } from '../components/WatchRoomProvider';
|
||||
import { DownloadProvider } from '../contexts/DownloadContext';
|
||||
|
||||
const inter = Inter({ subsets: ['latin'] });
|
||||
export const dynamic = 'force-dynamic';
|
||||
|
||||
@@ -6,8 +6,6 @@ import { Heart, Radio, Tv } from 'lucide-react';
|
||||
import { useRouter, useSearchParams } from 'next/navigation';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
|
||||
import { useLiveSync } from '@/hooks/useLiveSync';
|
||||
|
||||
import {
|
||||
deleteFavorite,
|
||||
generateStorageKey,
|
||||
@@ -17,6 +15,7 @@ import {
|
||||
subscribeToDataUpdates,
|
||||
} from '@/lib/db.client';
|
||||
import { parseCustomTimeFormat } from '@/lib/time';
|
||||
import { useLiveSync } from '@/hooks/useLiveSync';
|
||||
|
||||
import EpgScrollableRow from '@/components/EpgScrollableRow';
|
||||
import PageLayout from '@/components/PageLayout';
|
||||
@@ -1150,7 +1149,7 @@ function LivePageClient() {
|
||||
if (!selectedGroup) return;
|
||||
|
||||
// 先在当前分组搜索
|
||||
let filtered = filterChannels(selectedGroup, keyword);
|
||||
const filtered = filterChannels(selectedGroup, keyword);
|
||||
|
||||
// 如果当前分组没有匹配的频道,且有搜索关键词,轮询所有分组
|
||||
if (filtered.length === 0 && keyword.trim() && groupedChannels) {
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
'use client';
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import { AlertCircle, CheckCircle } from 'lucide-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useEffect,useState } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import { CheckCircle, AlertCircle, Plus } from 'lucide-react';
|
||||
|
||||
import PageLayout from '@/components/PageLayout';
|
||||
import { getTMDBImageUrl } from '@/lib/tmdb.client';
|
||||
import { processImageUrl } from '@/lib/utils';
|
||||
|
||||
import PageLayout from '@/components/PageLayout';
|
||||
|
||||
interface TMDBResult {
|
||||
id: number;
|
||||
title?: string;
|
||||
|
||||
+9
-7
@@ -2,9 +2,9 @@
|
||||
|
||||
'use client';
|
||||
|
||||
import { ChevronRight, Bot, ListVideo } from 'lucide-react';
|
||||
import { Bot, ChevronRight, ListVideo } from 'lucide-react';
|
||||
import Link from 'next/link';
|
||||
import { Suspense, useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { Suspense, useEffect, useState } from 'react';
|
||||
|
||||
import {
|
||||
BangumiCalendarData,
|
||||
@@ -15,15 +15,15 @@ import { getTMDBImageUrl, TMDBItem } from '@/lib/tmdb.client';
|
||||
import { DoubanItem } from '@/lib/types';
|
||||
import { processImageUrl } from '@/lib/utils';
|
||||
|
||||
import AIChatPanel from '@/components/AIChatPanel';
|
||||
import BannerCarousel from '@/components/BannerCarousel';
|
||||
import ContinueWatching from '@/components/ContinueWatching';
|
||||
import FireworksCanvas from '@/components/FireworksCanvas';
|
||||
import HttpWarningDialog from '@/components/HttpWarningDialog';
|
||||
import PageLayout from '@/components/PageLayout';
|
||||
import ScrollableRow from '@/components/ScrollableRow';
|
||||
import { useSite } from '@/components/SiteProvider';
|
||||
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 {
|
||||
@@ -151,7 +151,9 @@ function HomeClient() {
|
||||
const setCache = (key: string, data: any) => {
|
||||
try {
|
||||
localStorage.setItem(key, JSON.stringify({ data, timestamp: Date.now() }));
|
||||
} catch {}
|
||||
} catch {
|
||||
// Ignore localStorage errors
|
||||
}
|
||||
};
|
||||
|
||||
const moviesCache = getCache('homepage_movies');
|
||||
|
||||
+55
-54
@@ -2,70 +2,69 @@
|
||||
|
||||
'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 { 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 { 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 {
|
||||
deleteFavorite,
|
||||
deletePlayRecord,
|
||||
deleteSkipConfig,
|
||||
generateStorageKey,
|
||||
getAllPlayRecords,
|
||||
getDanmakuFilterConfig,
|
||||
getEpisodeFilterConfig,
|
||||
getSkipConfig,
|
||||
isFavorited,
|
||||
saveFavorite,
|
||||
savePlayRecord,
|
||||
saveSkipConfig,
|
||||
subscribeToDataUpdates,
|
||||
getDanmakuFilterConfig,
|
||||
getEpisodeFilterConfig,
|
||||
} from '@/lib/db.client';
|
||||
import {
|
||||
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 { getDoubanDetail } from '@/lib/douban.client';
|
||||
import { getTMDBImageUrl } from '@/lib/tmdb.search';
|
||||
|
||||
import EpisodeSelector from '@/components/EpisodeSelector';
|
||||
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 { DanmakuFilterConfig, EpisodeFilterConfig,SearchResult } from '@/lib/types';
|
||||
import { getVideoResolutionFromM3u8, processImageUrl } from '@/lib/utils';
|
||||
import { useEnableComments } from '@/hooks/useEnableComments';
|
||||
import PansouSearch from '@/components/PansouSearch';
|
||||
import CustomHeatmap from '@/components/CustomHeatmap';
|
||||
import { usePlaySync } from '@/hooks/usePlaySync';
|
||||
|
||||
import AIChatPanel from '@/components/AIChatPanel';
|
||||
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 属性
|
||||
declare global {
|
||||
@@ -545,7 +544,7 @@ function PlayPageClient() {
|
||||
// 只有用户主动点击推荐时才会添加 _reload 参数
|
||||
if (reloadParam && urlTitle && urlTitle !== videoTitle && !isSourceChangingRef.current) {
|
||||
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 (
|
||||
hls: any,
|
||||
video: HTMLVideoElement,
|
||||
isScheduled: boolean = false
|
||||
isScheduled = false
|
||||
) => {
|
||||
// 防抖:距离上次刷新不足3秒则不刷新
|
||||
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);
|
||||
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);
|
||||
|
||||
try {
|
||||
@@ -4863,12 +4862,14 @@ function PlayPageClient() {
|
||||
return;
|
||||
}
|
||||
// 检查其他 HTTP 错误状态码
|
||||
const statusCode = data.response?.code || data.response?.status;
|
||||
if (statusCode && statusCode >= 400) {
|
||||
console.log(`HTTP ${statusCode} 错误`);
|
||||
hls.destroy();
|
||||
setVideoError(`HTTP ${statusCode} 错误`);
|
||||
return;
|
||||
{
|
||||
const statusCode = data.response?.code || data.response?.status;
|
||||
if (statusCode && statusCode >= 400) {
|
||||
console.log(`HTTP ${statusCode} 错误`);
|
||||
hls.destroy();
|
||||
setVideoError(`HTTP ${statusCode} 错误`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
console.log('网络错误,尝试恢复...');
|
||||
hls.startLoad();
|
||||
|
||||
@@ -2,15 +2,16 @@
|
||||
|
||||
'use client';
|
||||
|
||||
import { ArrowDownWideNarrow, ArrowUpNarrowWide,Film } from 'lucide-react';
|
||||
import { useRouter, useSearchParams } from 'next/navigation';
|
||||
import { useEffect, useState, useRef, useMemo } from 'react';
|
||||
import { Film, ArrowUpDown, ArrowDownWideNarrow, ArrowUpNarrowWide } from 'lucide-react';
|
||||
import { useEffect, useMemo,useRef, useState } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
|
||||
import { base58Encode } from '@/lib/utils';
|
||||
|
||||
import CapsuleSwitch from '@/components/CapsuleSwitch';
|
||||
import PageLayout from '@/components/PageLayout';
|
||||
import VideoCard from '@/components/VideoCard';
|
||||
import { base58Encode } from '@/lib/utils';
|
||||
|
||||
type LibrarySourceType = 'openlist' | 'emby' | 'xiaoya' | `emby:${string}` | `emby_${string}`;
|
||||
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps, @typescript-eslint/no-explicit-any,@typescript-eslint/no-non-null-assertion,no-empty */
|
||||
'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 React, { startTransition, Suspense, useEffect, useMemo, useRef, useState } from 'react';
|
||||
|
||||
import { getAuthInfoFromBrowserCookie } from '@/lib/auth';
|
||||
import {
|
||||
addSearchHistory,
|
||||
clearSearchHistory,
|
||||
@@ -13,15 +14,14 @@ import {
|
||||
subscribeToDataUpdates,
|
||||
} from '@/lib/db.client';
|
||||
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 PansouSearch from '@/components/PansouSearch';
|
||||
import SearchResultFilter, { SearchFilterCategory } from '@/components/SearchResultFilter';
|
||||
import SearchSuggestions from '@/components/SearchSuggestions';
|
||||
import VideoCard, { VideoCardHandle } from '@/components/VideoCard';
|
||||
import PansouSearch from '@/components/PansouSearch';
|
||||
import AcgSearch from '@/components/AcgSearch';
|
||||
import CapsuleSwitch from '@/components/CapsuleSwitch';
|
||||
|
||||
function SearchPageClient() {
|
||||
// 搜索历史
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
// 观影室首页 - 选项卡式界面
|
||||
'use client';
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import { Users, UserPlus, List as ListIcon, Lock, RefreshCw } from 'lucide-react';
|
||||
import { List as ListIcon, Lock, RefreshCw,UserPlus, Users } from 'lucide-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useWatchRoomContext } from '@/components/WatchRoomProvider';
|
||||
import PageLayout from '@/components/PageLayout';
|
||||
import { useEffect,useState } from 'react';
|
||||
|
||||
import { getAuthInfoFromBrowserCookie } from '@/lib/auth';
|
||||
|
||||
import PageLayout from '@/components/PageLayout';
|
||||
import { useWatchRoomContext } from '@/components/WatchRoomProvider';
|
||||
|
||||
import type { Room } from '@/types/watch-room';
|
||||
|
||||
type TabType = 'create' | 'join' | 'list';
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
'use client';
|
||||
|
||||
import { AlertTriangle,Radio } from 'lucide-react';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
|
||||
import PageLayout from '@/components/PageLayout';
|
||||
import { Radio, AlertTriangle } from 'lucide-react';
|
||||
import Head from 'next/head';
|
||||
|
||||
let Artplayer: any = null;
|
||||
let Hls: any = null;
|
||||
|
||||
Reference in New Issue
Block a user