diff --git a/src/app/play/page.tsx b/src/app/play/page.tsx
index 2a13d21..c624917 100644
--- a/src/app/play/page.tsx
+++ b/src/app/play/page.tsx
@@ -2,7 +2,7 @@
'use client';
-import { Heart, Search, X, Cloud, Sparkles } from 'lucide-react';
+import { Heart, Search, X, Cloud, Sparkles, AlertCircle } from 'lucide-react';
import { useRouter, useSearchParams } from 'next/navigation';
import { Suspense, useEffect, useRef, useState } from 'react';
@@ -51,6 +51,7 @@ import {
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 EpisodeSelector from '@/components/EpisodeSelector';
import DownloadEpisodeSelector from '@/components/DownloadEpisodeSelector';
@@ -63,6 +64,7 @@ import AIChatPanel from '@/components/AIChatPanel';
import { useEnableComments } from '@/hooks/useEnableComments';
import PansouSearch from '@/components/PansouSearch';
import CustomHeatmap from '@/components/CustomHeatmap';
+import CorrectDialog from '@/components/CorrectDialog';
// 扩展 HTMLVideoElement 类型以支持 hls 属性
declare global {
@@ -122,6 +124,9 @@ function PlayPageClient() {
const [aiEnabled, setAiEnabled] = useState(false);
const [aiDefaultMessageWithVideo, setAiDefaultMessageWithVideo] = useState('');
+ // 纠错弹窗状态
+ const [showCorrectDialog, setShowCorrectDialog] = useState(false);
+
// 检查AI功能是否启用
useEffect(() => {
if (typeof window !== 'undefined') {
@@ -2624,10 +2629,22 @@ function PlayPageClient() {
// 直接使用 detailData.source(已经是完整格式)
setCurrentSource(detailData.source);
setCurrentId(detailData.id);
+
+ // 如果是小雅源,检查并应用纠错信息
+ if (detailData.source === 'xiaoya') {
+ const correction = getXiaoyaCorrection(detailData.source, detailData.id);
+ if (correction) {
+ console.log('发现小雅源纠错信息,正在应用...', correction);
+ detailData = applyCorrection(detailData, correction);
+ }
+ }
+
+ // 更新所有相关状态(在应用纠错信息之后)
setVideoYear(detailData.year);
setVideoTitle(detailData.title || videoTitleRef.current);
setVideoCover(detailData.poster);
setVideoDoubanId(detailData.douban_id || 0);
+
setDetail(detailData);
setSourceProxyMode(detailData.proxyMode || false); // 从 detail 数据中读取代理模式
if (currentEpisodeIndex >= detailData.episodes.length) {
@@ -3902,6 +3919,40 @@ function PlayPageClient() {
}
};
+ // 纠错成功后的回调
+ const handleCorrectSuccess = () => {
+ if (!detail || detail.source !== 'xiaoya') return;
+
+ // 从 localStorage 读取纠错信息
+ const correction = getXiaoyaCorrection(detail.source, detail.id);
+ if (correction) {
+ console.log('应用纠错信息:', correction);
+
+ // 只更新显示相关的状态,不更新 detail(避免触发播放器刷新)
+ if (correction.title) {
+ setVideoTitle(correction.title);
+ }
+ if (correction.posterPath) {
+ // 补全 TMDB 图片 URL
+ const fullPosterUrl = getTMDBImageUrl(correction.posterPath);
+ setVideoCover(fullPosterUrl);
+ }
+ if (correction.doubanId) {
+ const doubanIdNum = typeof correction.doubanId === 'string'
+ ? parseInt(correction.doubanId, 10)
+ : correction.doubanId;
+ setVideoDoubanId(doubanIdNum);
+ }
+
+ // 更新 detailRef,这样其他地方使用 detailRef 时能获取到最新信息
+ if (detailRef.current) {
+ detailRef.current = applyCorrection(detailRef.current, correction);
+ }
+
+ console.log('已应用纠错信息(页面刷新后将完全生效)');
+ }
+ };
+
useEffect(() => {
if (
!videoUrl ||
@@ -6866,6 +6917,19 @@ function PlayPageClient() {
)}
+ {/* 纠错按钮 - 仅小雅源显示 */}
+ {detail && detail.source === 'xiaoya' && (
+
+ )}
{/* 豆瓣评分显示 */}
{doubanRating && doubanRating.value > 0 && (
@@ -7140,10 +7204,63 @@ function PlayPageClient() {
welcomeMessage={aiDefaultMessageWithVideo ? aiDefaultMessageWithVideo.replace('{title}', detail.title || '') : `想了解《${detail.title}》的更多信息吗?我可以帮你查询剧情、演员、评价等。`}
/>
)}
+
+ {/* 纠错弹窗 - 仅小雅源显示 */}
+ {detail && detail.source === 'xiaoya' && (
+
setShowCorrectDialog(false)}
+ videoKey={`${detail.source}_${detail.id}`}
+ currentTitle={detail.title}
+ currentVideo={{
+ tmdbId: detail.tmdb_id,
+ doubanId: detail.douban_id ? String(detail.douban_id) : undefined,
+ poster: detail.poster,
+ releaseDate: detail.year,
+ overview: detail.desc,
+ voteAverage: detail.rating,
+ mediaType: detail.type_name === '电影' ? 'movie' : 'tv',
+ }}
+ source="xiaoya"
+ onCorrect={() => {
+ // 纠错成功后的回调
+ handleCorrectSuccess();
+ }}
+ />
+ )}
);
}
+// 从 localStorage 读取小雅源的纠错信息
+const getXiaoyaCorrection = (source: string, id: string) => {
+ try {
+ const storageKey = `xiaoya_correction_${source}_${id}`;
+ const correctionJson = localStorage.getItem(storageKey);
+ if (correctionJson) {
+ return JSON.parse(correctionJson);
+ }
+ } catch (error) {
+ console.error('读取纠错信息失败:', error);
+ }
+ return null;
+};
+
+// 应用纠错信息到 detail 对象
+const applyCorrection = (detail: SearchResult, correction: any): SearchResult => {
+ return {
+ ...detail,
+ title: correction.title || detail.title,
+ poster: correction.posterPath ? getTMDBImageUrl(correction.posterPath) : detail.poster,
+ year: correction.releaseDate || detail.year,
+ desc: correction.overview || detail.desc,
+ rating: correction.voteAverage || detail.rating,
+ tmdb_id: correction.tmdbId || detail.tmdb_id,
+ douban_id: correction.doubanId ? (typeof correction.doubanId === 'string' ? parseInt(correction.doubanId, 10) : correction.doubanId) : detail.douban_id,
+ type_name: correction.mediaType === 'movie' ? '电影' : (correction.mediaType === 'tv' ? '电视剧' : detail.type_name),
+ };
+};
+
// FavoriteIcon 组件
const FavoriteIcon = ({ filled }: { filled: boolean }) => {
if (filled) {
diff --git a/src/components/CorrectDialog.tsx b/src/components/CorrectDialog.tsx
index d302bde..6e2a85a 100644
--- a/src/components/CorrectDialog.tsx
+++ b/src/components/CorrectDialog.tsx
@@ -49,6 +49,7 @@ interface CorrectDialogProps {
seasonName?: string;
};
onCorrect: () => void;
+ source?: string; // 新增:视频源类型(xiaoya, openlist等)
}
export default function CorrectDialog({
@@ -58,6 +59,7 @@ export default function CorrectDialog({
currentTitle,
currentVideo,
onCorrect,
+ source = 'openlist', // 默认为 openlist
}: CorrectDialogProps) {
const [searchQuery, setSearchQuery] = useState(currentTitle);
const [searching, setSearching] = useState(false);
@@ -229,8 +231,7 @@ export default function CorrectDialog({
finalTitle = `${finalTitle} ${season.name}`;
}
- const body: any = {
- key: videoKey,
+ const correctionData: any = {
tmdbId: finalTmdbId,
title: finalTitle,
posterPath: season?.poster_path || result.poster_path,
@@ -240,20 +241,38 @@ export default function CorrectDialog({
mediaType: result.media_type,
};
- // 如果有季度信息,添加到请求中
+ // 如果有季度信息,添加到数据中
if (season) {
- body.seasonNumber = season.season_number;
- body.seasonName = season.name;
+ correctionData.seasonNumber = season.season_number;
+ correctionData.seasonName = season.name;
}
- const response = await fetch('/api/openlist/correct', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify(body),
- });
+ // 根据源类型选择不同的存储方式
+ if (source === 'xiaoya') {
+ // 小雅源:存储到 localStorage
+ const storageKey = `xiaoya_correction_${videoKey}`;
+ const correctionInfo = {
+ ...correctionData,
+ correctedAt: Date.now(),
+ };
+ localStorage.setItem(storageKey, JSON.stringify(correctionInfo));
+ console.log('小雅源纠错信息已存储到 localStorage:', storageKey, correctionInfo);
+ } else {
+ // openlist 等其他源:调用 API
+ const body: any = {
+ key: videoKey,
+ ...correctionData,
+ };
- if (!response.ok) {
- throw new Error('纠错失败');
+ const response = await fetch('/api/openlist/correct', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify(body),
+ });
+
+ if (!response.ok) {
+ throw new Error('纠错失败');
+ }
}
onCorrect();
@@ -313,8 +332,7 @@ export default function CorrectDialog({
setError('');
try {
- const body: any = {
- key: videoKey,
+ const correctionData: any = {
title: manualData.title.trim(),
posterPath: manualData.posterPath.trim() || null,
releaseDate: manualData.releaseDate.trim() || '',
@@ -325,28 +343,46 @@ export default function CorrectDialog({
// 添加 TMDB ID(如果提供)
if (manualData.tmdbId.trim()) {
- body.tmdbId = Number(manualData.tmdbId);
+ correctionData.tmdbId = Number(manualData.tmdbId);
}
// 添加豆瓣 ID(如果提供)
if (manualData.doubanId.trim()) {
- body.doubanId = manualData.doubanId.trim();
+ correctionData.doubanId = manualData.doubanId.trim();
}
// 如果是电视剧且有季度信息
if (manualData.mediaType === 'tv' && manualData.seasonNumber) {
- body.seasonNumber = Number(manualData.seasonNumber);
- body.seasonName = manualData.seasonName.trim() || `第 ${manualData.seasonNumber} 季`;
+ correctionData.seasonNumber = Number(manualData.seasonNumber);
+ correctionData.seasonName = manualData.seasonName.trim() || `第 ${manualData.seasonNumber} 季`;
}
- const response = await fetch('/api/openlist/correct', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify(body),
- });
+ // 根据源类型选择不同的存储方式
+ if (source === 'xiaoya') {
+ // 小雅源:存储到 localStorage
+ const storageKey = `xiaoya_correction_${videoKey}`;
+ const correctionInfo = {
+ ...correctionData,
+ correctedAt: Date.now(),
+ };
+ localStorage.setItem(storageKey, JSON.stringify(correctionInfo));
+ console.log('小雅源纠错信息已存储到 localStorage:', storageKey, correctionInfo);
+ } else {
+ // openlist 等其他源:调用 API
+ const body: any = {
+ key: videoKey,
+ ...correctionData,
+ };
- if (!response.ok) {
- throw new Error('纠错失败');
+ const response = await fetch('/api/openlist/correct', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify(body),
+ });
+
+ if (!response.ok) {
+ throw new Error('纠错失败');
+ }
}
onCorrect();
diff --git a/src/lib/types.ts b/src/lib/types.ts
index 19a4508..02802fa 100644
--- a/src/lib/types.ts
+++ b/src/lib/types.ts
@@ -137,6 +137,8 @@ export interface SearchResult {
vod_total?: number; // 总集数
proxyMode?: boolean; // 代理模式:启用后由服务器代理m3u8和ts分片
subtitles?: Array>; // 字幕列表(按集数索引)
+ tmdb_id?: number; // TMDB ID
+ rating?: number; // 评分
}
// 豆瓣数据结构