修复私人影库从搜索进无法播放,优化完结标识判断
This commit is contained in:
@@ -36,6 +36,61 @@ export async function GET(request: NextRequest) {
|
|||||||
const config = await getConfig();
|
const config = await getConfig();
|
||||||
const apiSites = await getAvailableApiSites(authInfo.username);
|
const apiSites = await getAvailableApiSites(authInfo.username);
|
||||||
|
|
||||||
|
// 检查是否配置了 OpenList
|
||||||
|
const hasOpenList = !!(config.OpenListConfig?.URL && config.OpenListConfig?.Username && config.OpenListConfig?.Password);
|
||||||
|
|
||||||
|
// 搜索 OpenList(如果配置了)
|
||||||
|
let openlistResults: any[] = [];
|
||||||
|
if (hasOpenList) {
|
||||||
|
try {
|
||||||
|
const { getCachedMetaInfo, setCachedMetaInfo } = await import('@/lib/openlist-cache');
|
||||||
|
const { getTMDBImageUrl } = await import('@/lib/tmdb.search');
|
||||||
|
const { db } = await import('@/lib/db');
|
||||||
|
|
||||||
|
const rootPath = config.OpenListConfig!.RootPath || '/';
|
||||||
|
let metaInfo = getCachedMetaInfo(rootPath);
|
||||||
|
|
||||||
|
// 如果没有缓存,尝试从数据库读取
|
||||||
|
if (!metaInfo) {
|
||||||
|
try {
|
||||||
|
const metainfoJson = await db.getGlobalValue('video.metainfo');
|
||||||
|
if (metainfoJson) {
|
||||||
|
metaInfo = JSON.parse(metainfoJson);
|
||||||
|
if (metaInfo) {
|
||||||
|
setCachedMetaInfo(rootPath, metaInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[Search] 从数据库读取 metainfo 失败:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (metaInfo && metaInfo.folders) {
|
||||||
|
openlistResults = Object.entries(metaInfo.folders)
|
||||||
|
.filter(([folderName, info]: [string, any]) => {
|
||||||
|
const matchFolder = folderName.toLowerCase().includes(query.toLowerCase());
|
||||||
|
const matchTitle = info.title.toLowerCase().includes(query.toLowerCase());
|
||||||
|
return matchFolder || matchTitle;
|
||||||
|
})
|
||||||
|
.map(([folderName, info]: [string, any]) => ({
|
||||||
|
id: folderName,
|
||||||
|
source: 'openlist',
|
||||||
|
source_name: '私人影库',
|
||||||
|
title: info.title,
|
||||||
|
poster: getTMDBImageUrl(info.poster_path),
|
||||||
|
episodes: [],
|
||||||
|
episodes_titles: [],
|
||||||
|
year: info.release_date.split('-')[0] || '',
|
||||||
|
desc: info.overview,
|
||||||
|
type_name: info.media_type === 'movie' ? '电影' : '电视剧',
|
||||||
|
douban_id: 0,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[Search] 搜索 OpenList 失败:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 添加超时控制和错误处理,避免慢接口拖累整体响应
|
// 添加超时控制和错误处理,避免慢接口拖累整体响应
|
||||||
const searchPromises = apiSites.map((site) =>
|
const searchPromises = apiSites.map((site) =>
|
||||||
Promise.race([
|
Promise.race([
|
||||||
@@ -54,7 +109,7 @@ export async function GET(request: NextRequest) {
|
|||||||
const successResults = results
|
const successResults = results
|
||||||
.filter((result) => result.status === 'fulfilled')
|
.filter((result) => result.status === 'fulfilled')
|
||||||
.map((result) => (result as PromiseFulfilledResult<any>).value);
|
.map((result) => (result as PromiseFulfilledResult<any>).value);
|
||||||
let flattenedResults = successResults.flat();
|
let flattenedResults = [...openlistResults, ...successResults.flat()];
|
||||||
if (!config.SiteConfig.DisableYellowFilter) {
|
if (!config.SiteConfig.DisableYellowFilter) {
|
||||||
flattenedResults = flattenedResults.filter((result) => {
|
flattenedResults = flattenedResults.filter((result) => {
|
||||||
const typeName = result.type_name || '';
|
const typeName = result.type_name || '';
|
||||||
|
|||||||
+50
-21
@@ -606,9 +606,9 @@ function PlayPageClient() {
|
|||||||
// 工具函数(Utils)
|
// 工具函数(Utils)
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
// 判断剧集是否已完结
|
// 判断剧集状态
|
||||||
const isSeriesCompleted = (detail: SearchResult | null): boolean => {
|
const getSeriesStatus = (detail: SearchResult | null): 'completed' | 'ongoing' | 'unknown' => {
|
||||||
if (!detail) return false;
|
if (!detail) return 'unknown';
|
||||||
|
|
||||||
// 方法1:通过 vod_remarks 判断
|
// 方法1:通过 vod_remarks 判断
|
||||||
if (detail.vod_remarks) {
|
if (detail.vod_remarks) {
|
||||||
@@ -620,23 +620,27 @@ function PlayPageClient() {
|
|||||||
|
|
||||||
// 如果包含连载关键词,则为连载中
|
// 如果包含连载关键词,则为连载中
|
||||||
if (ongoingKeywords.some(keyword => remarks.includes(keyword))) {
|
if (ongoingKeywords.some(keyword => remarks.includes(keyword))) {
|
||||||
return false;
|
return 'ongoing';
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果包含完结关键词,则为已完结
|
// 如果包含完结关键词,则为已完结
|
||||||
if (completedKeywords.some(keyword => remarks.includes(keyword))) {
|
if (completedKeywords.some(keyword => remarks.includes(keyword))) {
|
||||||
return true;
|
return 'completed';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 方法2:通过 vod_total 和实际集数对比判断
|
// 方法2:通过 vod_total 和实际集数对比判断
|
||||||
if (detail.vod_total && detail.vod_total > 0 && detail.episodes && detail.episodes.length > 0) {
|
if (detail.vod_total && detail.vod_total > 0 && detail.episodes && detail.episodes.length > 0) {
|
||||||
// 如果实际集数 >= 总集数,则为已完结
|
// 如果实际集数 >= 总集数,则为已完结
|
||||||
return detail.episodes.length >= detail.vod_total;
|
if (detail.episodes.length >= detail.vod_total) {
|
||||||
|
return 'completed';
|
||||||
|
}
|
||||||
|
// 如果实际集数 < 总集数,则为连载中
|
||||||
|
return 'ongoing';
|
||||||
}
|
}
|
||||||
|
|
||||||
// 无法判断,默认返回 false(连载中)
|
// 无法判断,返回 unknown
|
||||||
return false;
|
return 'unknown';
|
||||||
};
|
};
|
||||||
|
|
||||||
// 播放源优选函数
|
// 播放源优选函数
|
||||||
@@ -1776,7 +1780,9 @@ function PlayPageClient() {
|
|||||||
? result.year.toLowerCase() === videoYearRef.current.toLowerCase()
|
? result.year.toLowerCase() === videoYearRef.current.toLowerCase()
|
||||||
: true) &&
|
: true) &&
|
||||||
(searchType
|
(searchType
|
||||||
? (searchType === 'tv' && result.episodes.length > 1) ||
|
? // openlist 源跳过 episodes 长度检查,因为搜索时不返回详细播放列表
|
||||||
|
result.source === 'openlist' ||
|
||||||
|
(searchType === 'tv' && result.episodes.length > 1) ||
|
||||||
(searchType === 'movie' && result.episodes.length === 1)
|
(searchType === 'movie' && result.episodes.length === 1)
|
||||||
: true)
|
: true)
|
||||||
);
|
);
|
||||||
@@ -1829,6 +1835,15 @@ function PlayPageClient() {
|
|||||||
);
|
);
|
||||||
if (target) {
|
if (target) {
|
||||||
detailData = target;
|
detailData = target;
|
||||||
|
|
||||||
|
// 如果是 openlist 源且 episodes 为空,需要调用 detail 接口获取完整信息
|
||||||
|
if (detailData.source === 'openlist' && (!detailData.episodes || detailData.episodes.length === 0)) {
|
||||||
|
console.log('[Play] OpenList source has no episodes, fetching detail...');
|
||||||
|
const detailSources = await fetchSourceDetail(currentSource, currentId);
|
||||||
|
if (detailSources.length > 0) {
|
||||||
|
detailData = detailSources[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
setError('未找到匹配结果');
|
setError('未找到匹配结果');
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
@@ -1849,6 +1864,15 @@ function PlayPageClient() {
|
|||||||
|
|
||||||
console.log(detailData.source, detailData.id);
|
console.log(detailData.source, detailData.id);
|
||||||
|
|
||||||
|
// 如果是 openlist 源且 episodes 为空,需要调用 detail 接口获取完整信息
|
||||||
|
if (detailData.source === 'openlist' && (!detailData.episodes || detailData.episodes.length === 0)) {
|
||||||
|
console.log('[Play] OpenList source has no episodes after selection, fetching detail...');
|
||||||
|
const detailSources = await fetchSourceDetail(detailData.source, detailData.id);
|
||||||
|
if (detailSources.length > 0) {
|
||||||
|
detailData = detailSources[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
setNeedPrefer(false);
|
setNeedPrefer(false);
|
||||||
setCurrentSource(detailData.source);
|
setCurrentSource(detailData.source);
|
||||||
setCurrentId(detailData.id);
|
setCurrentId(detailData.id);
|
||||||
@@ -2841,7 +2865,7 @@ function PlayPageClient() {
|
|||||||
total_episodes: detailRef.current?.episodes.length || 1,
|
total_episodes: detailRef.current?.episodes.length || 1,
|
||||||
save_time: Date.now(),
|
save_time: Date.now(),
|
||||||
search_title: searchTitle,
|
search_title: searchTitle,
|
||||||
is_completed: isSeriesCompleted(detailRef.current),
|
is_completed: getSeriesStatus(detailRef.current) === 'completed',
|
||||||
vod_remarks: detailRef.current?.vod_remarks,
|
vod_remarks: detailRef.current?.vod_remarks,
|
||||||
});
|
});
|
||||||
setFavorited(true);
|
setFavorited(true);
|
||||||
@@ -4408,17 +4432,22 @@ function PlayPageClient() {
|
|||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
{/* 完结状态标识 */}
|
{/* 完结状态标识 */}
|
||||||
{detail && totalEpisodes > 1 && (
|
{detail && totalEpisodes > 1 && (() => {
|
||||||
<span
|
const status = getSeriesStatus(detail);
|
||||||
className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${
|
if (status === 'unknown') return null;
|
||||||
isSeriesCompleted(detail)
|
|
||||||
? 'bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-300'
|
return (
|
||||||
: 'bg-blue-100 text-blue-800 dark:bg-blue-900/30 dark:text-blue-300'
|
<span
|
||||||
}`}
|
className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${
|
||||||
>
|
status === 'completed'
|
||||||
{isSeriesCompleted(detail) ? '已完结' : '连载中'}
|
? 'bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-300'
|
||||||
</span>
|
: 'bg-blue-100 text-blue-800 dark:bg-blue-900/30 dark:text-blue-300'
|
||||||
)}
|
}`}
|
||||||
|
>
|
||||||
|
{status === 'completed' ? '已完结' : '连载中'}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
})()}
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
{/* 第二行:播放器和选集 */}
|
{/* 第二行:播放器和选集 */}
|
||||||
|
|||||||
Reference in New Issue
Block a user