修复无网盘权限可以直链播放网盘链接
This commit is contained in:
+298
-105
@@ -6,6 +6,7 @@ import { getAuthInfoFromCookie } from '@/lib/auth';
|
|||||||
import { getAvailableApiSites, getCacheTime, getConfig } from '@/lib/config';
|
import { getAvailableApiSites, getCacheTime, getConfig } from '@/lib/config';
|
||||||
import { getDetailFromApiV2 } from '@/lib/downstream';
|
import { getDetailFromApiV2 } from '@/lib/downstream';
|
||||||
import { getProxyToken } from '@/lib/emby-token';
|
import { getProxyToken } from '@/lib/emby-token';
|
||||||
|
import { hasFeaturePermission } from '@/lib/permissions';
|
||||||
import {
|
import {
|
||||||
createBaiduNetdiskSession,
|
createBaiduNetdiskSession,
|
||||||
getBaiduNetdiskSession,
|
getBaiduNetdiskSession,
|
||||||
@@ -45,6 +46,7 @@ import {
|
|||||||
NETDISK_QUARK_SOURCE,
|
NETDISK_QUARK_SOURCE,
|
||||||
NETDISK_TIANYI_SOURCE,
|
NETDISK_TIANYI_SOURCE,
|
||||||
NETDISK_UC_SOURCE,
|
NETDISK_UC_SOURCE,
|
||||||
|
isNetdiskSource,
|
||||||
normalizeNetdiskSource,
|
normalizeNetdiskSource,
|
||||||
} from '@/lib/netdisk/source';
|
} from '@/lib/netdisk/source';
|
||||||
import {
|
import {
|
||||||
@@ -68,17 +70,19 @@ import {
|
|||||||
|
|
||||||
export const runtime = 'nodejs';
|
export const runtime = 'nodejs';
|
||||||
|
|
||||||
function formatNetdiskEpisodeTitle(parsed: {
|
function formatNetdiskEpisodeTitle(
|
||||||
season?: number;
|
parsed: {
|
||||||
episode?: number;
|
season?: number;
|
||||||
}, fallback: string) {
|
episode?: number;
|
||||||
|
},
|
||||||
|
fallback: string
|
||||||
|
) {
|
||||||
if (parsed.season && parsed.episode) {
|
if (parsed.season && parsed.episode) {
|
||||||
const season = String(Math.trunc(parsed.season)).padStart(2, '0');
|
const season = String(Math.trunc(parsed.season)).padStart(2, '0');
|
||||||
const episodeValue = parsed.episode;
|
const episodeValue = parsed.episode;
|
||||||
const episode =
|
const episode = Number.isInteger(episodeValue)
|
||||||
Number.isInteger(episodeValue)
|
? String(Math.trunc(episodeValue)).padStart(2, '0')
|
||||||
? String(Math.trunc(episodeValue)).padStart(2, '0')
|
: String(episodeValue);
|
||||||
: String(episodeValue);
|
|
||||||
return `S${season}E${episode}`;
|
return `S${season}E${episode}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,6 +116,19 @@ export async function GET(request: NextRequest) {
|
|||||||
return NextResponse.json({ error: '缺少必要参数' }, { status: 400 });
|
return NextResponse.json({ error: '缺少必要参数' }, { status: 400 });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isNetdiskSource(sourceCode)) {
|
||||||
|
const allowed = await hasFeaturePermission(
|
||||||
|
authInfo.username,
|
||||||
|
'netdisk_temp_play'
|
||||||
|
);
|
||||||
|
if (!allowed) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: '无权限使用临时播放' },
|
||||||
|
{ status: 403 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const parsedScriptSource = parseScriptSourceValue(sourceCode);
|
const parsedScriptSource = parseScriptSourceValue(sourceCode);
|
||||||
if (parsedScriptSource) {
|
if (parsedScriptSource) {
|
||||||
try {
|
try {
|
||||||
@@ -121,11 +138,12 @@ export async function GET(request: NextRequest) {
|
|||||||
payload: {},
|
payload: {},
|
||||||
});
|
});
|
||||||
const sources = normalizeScriptSources(sourcesExecution.result);
|
const sources = normalizeScriptSources(sourcesExecution.result);
|
||||||
const sourceInfo =
|
const sourceInfo = sources.find(
|
||||||
sources.find((item) => item.id === parsedScriptSource.sourceId) || {
|
(item) => item.id === parsedScriptSource.sourceId
|
||||||
id: parsedScriptSource.sourceId,
|
) || {
|
||||||
name: parsedScriptSource.sourceId,
|
id: parsedScriptSource.sourceId,
|
||||||
};
|
name: parsedScriptSource.sourceId,
|
||||||
|
};
|
||||||
|
|
||||||
const detailExecution = await executeSavedSourceScript({
|
const detailExecution = await executeSavedSourceScript({
|
||||||
key: parsedScriptSource.scriptKey,
|
key: parsedScriptSource.scriptKey,
|
||||||
@@ -161,7 +179,10 @@ export async function GET(request: NextRequest) {
|
|||||||
const config = await getConfig();
|
const config = await getConfig();
|
||||||
|
|
||||||
// 检查是否有启用的 Emby 源
|
// 检查是否有启用的 Emby 源
|
||||||
if (!config.EmbyConfig?.Sources || config.EmbyConfig.Sources.length === 0) {
|
if (
|
||||||
|
!config.EmbyConfig?.Sources ||
|
||||||
|
config.EmbyConfig.Sources.length === 0
|
||||||
|
) {
|
||||||
throw new Error('Emby 未配置或未启用');
|
throw new Error('Emby 未配置或未启用');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -174,13 +195,15 @@ export async function GET(request: NextRequest) {
|
|||||||
// 使用 EmbyManager 获取客户端和配置
|
// 使用 EmbyManager 获取客户端和配置
|
||||||
const { embyManager } = await import('@/lib/emby-manager');
|
const { embyManager } = await import('@/lib/emby-manager');
|
||||||
const sources = await embyManager.getEnabledSources();
|
const sources = await embyManager.getEnabledSources();
|
||||||
const sourceConfig = sources.find(s => s.key === embyKey);
|
const sourceConfig = sources.find((s) => s.key === embyKey);
|
||||||
const sourceName = sourceConfig?.name || 'Emby';
|
const sourceName = sourceConfig?.name || 'Emby';
|
||||||
|
|
||||||
const client = await embyManager.getClient(embyKey);
|
const client = await embyManager.getClient(embyKey);
|
||||||
|
|
||||||
// 获取代理 token(如果启用了代理)
|
// 获取代理 token(如果启用了代理)
|
||||||
const proxyToken = client.isProxyEnabled() ? await getProxyToken(request) : null;
|
const proxyToken = client.isProxyEnabled()
|
||||||
|
? await getProxyToken(request)
|
||||||
|
: null;
|
||||||
|
|
||||||
// 获取媒体详情
|
// 获取媒体详情
|
||||||
const item = await client.getItem(id);
|
const item = await client.getItem(id);
|
||||||
@@ -195,7 +218,12 @@ export async function GET(request: NextRequest) {
|
|||||||
source_name: sourceName,
|
source_name: sourceName,
|
||||||
id: item.Id,
|
id: item.Id,
|
||||||
title: item.Name,
|
title: item.Name,
|
||||||
poster: client.getImageUrl(item.Id, 'Primary', undefined, proxyToken || undefined),
|
poster: client.getImageUrl(
|
||||||
|
item.Id,
|
||||||
|
'Primary',
|
||||||
|
undefined,
|
||||||
|
proxyToken || undefined
|
||||||
|
),
|
||||||
year: item.ProductionYear?.toString() || '',
|
year: item.ProductionYear?.toString() || '',
|
||||||
douban_id: 0,
|
douban_id: 0,
|
||||||
desc: item.Overview || '',
|
desc: item.Overview || '',
|
||||||
@@ -229,15 +257,24 @@ export async function GET(request: NextRequest) {
|
|||||||
source_name: sourceName,
|
source_name: sourceName,
|
||||||
id: item.Id,
|
id: item.Id,
|
||||||
title: item.Name,
|
title: item.Name,
|
||||||
poster: client.getImageUrl(item.Id, 'Primary', undefined, proxyToken || undefined),
|
poster: client.getImageUrl(
|
||||||
|
item.Id,
|
||||||
|
'Primary',
|
||||||
|
undefined,
|
||||||
|
proxyToken || undefined
|
||||||
|
),
|
||||||
year: item.ProductionYear?.toString() || '',
|
year: item.ProductionYear?.toString() || '',
|
||||||
douban_id: 0,
|
douban_id: 0,
|
||||||
desc: item.Overview || '',
|
desc: item.Overview || '',
|
||||||
episodes: await Promise.all(allEpisodes.map((ep) => client.getStreamUrl(ep.Id))),
|
episodes: await Promise.all(
|
||||||
|
allEpisodes.map((ep) => client.getStreamUrl(ep.Id))
|
||||||
|
),
|
||||||
episodes_titles: allEpisodes.map((ep) => {
|
episodes_titles: allEpisodes.map((ep) => {
|
||||||
const seasonNum = ep.ParentIndexNumber || 1;
|
const seasonNum = ep.ParentIndexNumber || 1;
|
||||||
const episodeNum = ep.IndexNumber || 1;
|
const episodeNum = ep.IndexNumber || 1;
|
||||||
return `S${seasonNum.toString().padStart(2, '0')}E${episodeNum.toString().padStart(2, '0')}`;
|
return `S${seasonNum.toString().padStart(2, '0')}E${episodeNum
|
||||||
|
.toString()
|
||||||
|
.padStart(2, '0')}`;
|
||||||
}),
|
}),
|
||||||
subtitles: allEpisodes.map((ep) => client.getSubtitles(ep)),
|
subtitles: allEpisodes.map((ep) => client.getSubtitles(ep)),
|
||||||
proxyMode: false,
|
proxyMode: false,
|
||||||
@@ -261,16 +298,14 @@ export async function GET(request: NextRequest) {
|
|||||||
const config = await getConfig();
|
const config = await getConfig();
|
||||||
const xiaoyaConfig = config.XiaoyaConfig;
|
const xiaoyaConfig = config.XiaoyaConfig;
|
||||||
|
|
||||||
if (
|
if (!xiaoyaConfig || !xiaoyaConfig.Enabled || !xiaoyaConfig.ServerURL) {
|
||||||
!xiaoyaConfig ||
|
|
||||||
!xiaoyaConfig.Enabled ||
|
|
||||||
!xiaoyaConfig.ServerURL
|
|
||||||
) {
|
|
||||||
throw new Error('小雅未配置或未启用');
|
throw new Error('小雅未配置或未启用');
|
||||||
}
|
}
|
||||||
|
|
||||||
const { XiaoyaClient } = await import('@/lib/xiaoya.client');
|
const { XiaoyaClient } = await import('@/lib/xiaoya.client');
|
||||||
const { getXiaoyaMetadata, getXiaoyaEpisodes } = await import('@/lib/xiaoya-metadata');
|
const { getXiaoyaMetadata, getXiaoyaEpisodes } = await import(
|
||||||
|
'@/lib/xiaoya-metadata'
|
||||||
|
);
|
||||||
const { base58Decode, base58Encode } = await import('@/lib/utils');
|
const { base58Decode, base58Encode } = await import('@/lib/utils');
|
||||||
|
|
||||||
const client = new XiaoyaClient(
|
const client = new XiaoyaClient(
|
||||||
@@ -299,7 +334,9 @@ export async function GET(request: NextRequest) {
|
|||||||
let clickedFilePath: string | undefined;
|
let clickedFilePath: string | undefined;
|
||||||
if (fileName) {
|
if (fileName) {
|
||||||
// 拼接目录路径和文件名
|
// 拼接目录路径和文件名
|
||||||
clickedFilePath = `${decodedDirPath}${decodedDirPath.endsWith('/') ? '' : '/'}${fileName}`;
|
clickedFilePath = `${decodedDirPath}${
|
||||||
|
decodedDirPath.endsWith('/') ? '' : '/'
|
||||||
|
}${fileName}`;
|
||||||
console.log('[xiaoya] 用户点击的文件路径:', clickedFilePath);
|
console.log('[xiaoya] 用户点击的文件路径:', clickedFilePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -319,7 +356,9 @@ export async function GET(request: NextRequest) {
|
|||||||
// 如果有点击的文件路径,找到对应的集数索引
|
// 如果有点击的文件路径,找到对应的集数索引
|
||||||
let clickedFileIndex = -1;
|
let clickedFileIndex = -1;
|
||||||
if (clickedFilePath) {
|
if (clickedFilePath) {
|
||||||
clickedFileIndex = episodes.findIndex(ep => ep.path === clickedFilePath);
|
clickedFileIndex = episodes.findIndex(
|
||||||
|
(ep) => ep.path === clickedFilePath
|
||||||
|
);
|
||||||
console.log('[xiaoya] 文件在集数列表中的索引:', clickedFileIndex);
|
console.log('[xiaoya] 文件在集数列表中的索引:', clickedFileIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -332,12 +371,16 @@ export async function GET(request: NextRequest) {
|
|||||||
year: metadata.year || '',
|
year: metadata.year || '',
|
||||||
douban_id: 0,
|
douban_id: 0,
|
||||||
desc: metadata.plot || '',
|
desc: metadata.plot || '',
|
||||||
episodes: episodes.map(ep => `/api/xiaoya/play?path=${encodeURIComponent(base58Encode(ep.path))}`),
|
episodes: episodes.map(
|
||||||
episodes_titles: episodes.map(ep => ep.title),
|
(ep) =>
|
||||||
|
`/api/xiaoya/play?path=${encodeURIComponent(base58Encode(ep.path))}`
|
||||||
|
),
|
||||||
|
episodes_titles: episodes.map((ep) => ep.title),
|
||||||
subtitles: [],
|
subtitles: [],
|
||||||
proxyMode: false,
|
proxyMode: false,
|
||||||
// 返回用户点击的文件索引(如果找到的话)
|
// 返回用户点击的文件索引(如果找到的话)
|
||||||
initialEpisodeIndex: clickedFileIndex >= 0 ? clickedFileIndex : undefined,
|
initialEpisodeIndex:
|
||||||
|
clickedFileIndex >= 0 ? clickedFileIndex : undefined,
|
||||||
// 返回元数据来源
|
// 返回元数据来源
|
||||||
metadataSource: metadata.source,
|
metadataSource: metadata.source,
|
||||||
};
|
};
|
||||||
@@ -360,11 +403,17 @@ export async function GET(request: NextRequest) {
|
|||||||
throw new Error('移动云盘未配置或未启用');
|
throw new Error('移动云盘未配置或未启用');
|
||||||
}
|
}
|
||||||
|
|
||||||
let session = refreshMobileNetdiskSession(id) || getMobileNetdiskSession(id);
|
let session =
|
||||||
|
refreshMobileNetdiskSession(id) || getMobileNetdiskSession(id);
|
||||||
if (!session) {
|
if (!session) {
|
||||||
const payload = parseMobileNetdiskId(id);
|
const payload = parseMobileNetdiskId(id);
|
||||||
const { listMobileShareVideos } = await import('@/lib/netdisk/mobile.client');
|
const { listMobileShareVideos } = await import(
|
||||||
const result = await listMobileShareVideos(payload.shareUrl, mobileConfig.Authorization);
|
'@/lib/netdisk/mobile.client'
|
||||||
|
);
|
||||||
|
const result = await listMobileShareVideos(
|
||||||
|
payload.shareUrl,
|
||||||
|
mobileConfig.Authorization
|
||||||
|
);
|
||||||
session = createMobileNetdiskSession({
|
session = createMobileNetdiskSession({
|
||||||
title: title || result.title,
|
title: title || result.title,
|
||||||
shareUrl: payload.shareUrl,
|
shareUrl: payload.shareUrl,
|
||||||
@@ -377,8 +426,9 @@ export async function GET(request: NextRequest) {
|
|||||||
}
|
}
|
||||||
const mobileSession = session;
|
const mobileSession = session;
|
||||||
const { parseVideoFileName } = await import('@/lib/video-parser');
|
const { parseVideoFileName } = await import('@/lib/video-parser');
|
||||||
const parsedFiles = mobileSession.files.map((file, index) => {
|
const parsedFiles = mobileSession.files
|
||||||
const parsed = parseVideoFileName(file.name);
|
.map((file, index) => {
|
||||||
|
const parsed = parseVideoFileName(file.name);
|
||||||
return {
|
return {
|
||||||
...file,
|
...file,
|
||||||
originalIndex: index,
|
originalIndex: index,
|
||||||
@@ -386,20 +436,24 @@ export async function GET(request: NextRequest) {
|
|||||||
isOVA: parsed.isOVA,
|
isOVA: parsed.isOVA,
|
||||||
displayTitle: formatNetdiskEpisodeTitle(parsed, file.name),
|
displayTitle: formatNetdiskEpisodeTitle(parsed, file.name),
|
||||||
};
|
};
|
||||||
}).sort((a, b) => {
|
})
|
||||||
if (a.isOVA && !b.isOVA) return 1;
|
.sort((a, b) => {
|
||||||
if (!a.isOVA && b.isOVA) return -1;
|
if (a.isOVA && !b.isOVA) return 1;
|
||||||
return a.sortEpisode !== b.sortEpisode
|
if (!a.isOVA && b.isOVA) return -1;
|
||||||
? a.sortEpisode - b.sortEpisode
|
return a.sortEpisode !== b.sortEpisode
|
||||||
: a.name.localeCompare(b.name, 'zh-Hans-CN', {
|
? a.sortEpisode - b.sortEpisode
|
||||||
numeric: true,
|
: a.name.localeCompare(b.name, 'zh-Hans-CN', {
|
||||||
sensitivity: 'base',
|
numeric: true,
|
||||||
});
|
sensitivity: 'base',
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
const episodes = parsedFiles.map((file) => (
|
const episodes = parsedFiles.map(
|
||||||
`/api/netdisk/mobile/play?id=${encodeURIComponent(mobileSession.id)}&episodeIndex=${file.originalIndex}`
|
(file) =>
|
||||||
));
|
`/api/netdisk/mobile/play?id=${encodeURIComponent(
|
||||||
|
mobileSession.id
|
||||||
|
)}&episodeIndex=${file.originalIndex}`
|
||||||
|
);
|
||||||
|
|
||||||
return NextResponse.json({
|
return NextResponse.json({
|
||||||
source: NETDISK_MOBILE_SOURCE,
|
source: NETDISK_MOBILE_SOURCE,
|
||||||
@@ -430,11 +484,18 @@ export async function GET(request: NextRequest) {
|
|||||||
throw new Error('百度网盘未配置或未启用');
|
throw new Error('百度网盘未配置或未启用');
|
||||||
}
|
}
|
||||||
|
|
||||||
let session = refreshBaiduNetdiskSession(id) || getBaiduNetdiskSession(id);
|
let session =
|
||||||
|
refreshBaiduNetdiskSession(id) || getBaiduNetdiskSession(id);
|
||||||
if (!session) {
|
if (!session) {
|
||||||
const payload = parseBaiduNetdiskId(id);
|
const payload = parseBaiduNetdiskId(id);
|
||||||
const { listBaiduShareVideos } = await import('@/lib/netdisk/baidu.client');
|
const { listBaiduShareVideos } = await import(
|
||||||
const result = await listBaiduShareVideos(payload.shareUrl, baiduConfig.Cookie, payload.passcode || '');
|
'@/lib/netdisk/baidu.client'
|
||||||
|
);
|
||||||
|
const result = await listBaiduShareVideos(
|
||||||
|
payload.shareUrl,
|
||||||
|
baiduConfig.Cookie,
|
||||||
|
payload.passcode || ''
|
||||||
|
);
|
||||||
session = createBaiduNetdiskSession({
|
session = createBaiduNetdiskSession({
|
||||||
title: title || result.title,
|
title: title || result.title,
|
||||||
shareUrl: payload.shareUrl,
|
shareUrl: payload.shareUrl,
|
||||||
@@ -465,7 +526,10 @@ export async function GET(request: NextRequest) {
|
|||||||
if (!a.isOVA && b.isOVA) return -1;
|
if (!a.isOVA && b.isOVA) return -1;
|
||||||
return a.sortEpisode !== b.sortEpisode
|
return a.sortEpisode !== b.sortEpisode
|
||||||
? a.sortEpisode - b.sortEpisode
|
? a.sortEpisode - b.sortEpisode
|
||||||
: a.name.localeCompare(b.name, 'zh-Hans-CN', { numeric: true, sensitivity: 'base' });
|
: a.name.localeCompare(b.name, 'zh-Hans-CN', {
|
||||||
|
numeric: true,
|
||||||
|
sensitivity: 'base',
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
return NextResponse.json({
|
return NextResponse.json({
|
||||||
@@ -477,9 +541,12 @@ export async function GET(request: NextRequest) {
|
|||||||
year: '',
|
year: '',
|
||||||
douban_id: 0,
|
douban_id: 0,
|
||||||
desc: `百度网盘分享:${baiduSession.shareUrl}`,
|
desc: `百度网盘分享:${baiduSession.shareUrl}`,
|
||||||
episodes: parsedFiles.map((file) => (
|
episodes: parsedFiles.map(
|
||||||
`/api/netdisk/baidu/play?id=${encodeURIComponent(baiduSession.id)}&episodeIndex=${file.originalIndex}`
|
(file) =>
|
||||||
)),
|
`/api/netdisk/baidu/play?id=${encodeURIComponent(
|
||||||
|
baiduSession.id
|
||||||
|
)}&episodeIndex=${file.originalIndex}`
|
||||||
|
),
|
||||||
episodes_titles: parsedFiles.map((file) => file.displayTitle),
|
episodes_titles: parsedFiles.map((file) => file.displayTitle),
|
||||||
proxyMode: false,
|
proxyMode: false,
|
||||||
});
|
});
|
||||||
@@ -495,14 +562,21 @@ export async function GET(request: NextRequest) {
|
|||||||
try {
|
try {
|
||||||
const config = await getConfig();
|
const config = await getConfig();
|
||||||
const tianyiConfig = config.NetDiskConfig?.Tianyi;
|
const tianyiConfig = config.NetDiskConfig?.Tianyi;
|
||||||
if (!tianyiConfig?.Enabled || !tianyiConfig.Account || !tianyiConfig.Password) {
|
if (
|
||||||
|
!tianyiConfig?.Enabled ||
|
||||||
|
!tianyiConfig.Account ||
|
||||||
|
!tianyiConfig.Password
|
||||||
|
) {
|
||||||
throw new Error('天翼云盘未配置或未启用');
|
throw new Error('天翼云盘未配置或未启用');
|
||||||
}
|
}
|
||||||
|
|
||||||
let session = refreshTianyiNetdiskSession(id) || getTianyiNetdiskSession(id);
|
let session =
|
||||||
|
refreshTianyiNetdiskSession(id) || getTianyiNetdiskSession(id);
|
||||||
if (!session) {
|
if (!session) {
|
||||||
const payload = parseTianyiNetdiskId(id);
|
const payload = parseTianyiNetdiskId(id);
|
||||||
const { listTianyiShareVideos } = await import('@/lib/netdisk/tianyi.client');
|
const { listTianyiShareVideos } = await import(
|
||||||
|
'@/lib/netdisk/tianyi.client'
|
||||||
|
);
|
||||||
const result = await listTianyiShareVideos(
|
const result = await listTianyiShareVideos(
|
||||||
payload.shareUrl,
|
payload.shareUrl,
|
||||||
tianyiConfig.Account,
|
tianyiConfig.Account,
|
||||||
@@ -542,7 +616,10 @@ export async function GET(request: NextRequest) {
|
|||||||
if (!a.isOVA && b.isOVA) return -1;
|
if (!a.isOVA && b.isOVA) return -1;
|
||||||
return a.sortEpisode !== b.sortEpisode
|
return a.sortEpisode !== b.sortEpisode
|
||||||
? a.sortEpisode - b.sortEpisode
|
? a.sortEpisode - b.sortEpisode
|
||||||
: a.name.localeCompare(b.name, 'zh-Hans-CN', { numeric: true, sensitivity: 'base' });
|
: a.name.localeCompare(b.name, 'zh-Hans-CN', {
|
||||||
|
numeric: true,
|
||||||
|
sensitivity: 'base',
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
return NextResponse.json({
|
return NextResponse.json({
|
||||||
@@ -554,9 +631,12 @@ export async function GET(request: NextRequest) {
|
|||||||
year: '',
|
year: '',
|
||||||
douban_id: 0,
|
douban_id: 0,
|
||||||
desc: `天翼云盘分享:${tianyiSession.shareUrl}`,
|
desc: `天翼云盘分享:${tianyiSession.shareUrl}`,
|
||||||
episodes: parsedFiles.map((file) => (
|
episodes: parsedFiles.map(
|
||||||
`/api/netdisk/tianyi/play?id=${encodeURIComponent(tianyiSession.id)}&episodeIndex=${file.originalIndex}`
|
(file) =>
|
||||||
)),
|
`/api/netdisk/tianyi/play?id=${encodeURIComponent(
|
||||||
|
tianyiSession.id
|
||||||
|
)}&episodeIndex=${file.originalIndex}`
|
||||||
|
),
|
||||||
episodes_titles: parsedFiles.map((file) => file.displayTitle),
|
episodes_titles: parsedFiles.map((file) => file.displayTitle),
|
||||||
proxyMode: false,
|
proxyMode: false,
|
||||||
});
|
});
|
||||||
@@ -572,15 +652,25 @@ export async function GET(request: NextRequest) {
|
|||||||
try {
|
try {
|
||||||
const config = await getConfig();
|
const config = await getConfig();
|
||||||
const pan123Config = config.NetDiskConfig?.Pan123;
|
const pan123Config = config.NetDiskConfig?.Pan123;
|
||||||
if (!pan123Config?.Enabled || !pan123Config.Account || !pan123Config.Password) {
|
if (
|
||||||
|
!pan123Config?.Enabled ||
|
||||||
|
!pan123Config.Account ||
|
||||||
|
!pan123Config.Password
|
||||||
|
) {
|
||||||
throw new Error('123网盘未配置或未启用');
|
throw new Error('123网盘未配置或未启用');
|
||||||
}
|
}
|
||||||
|
|
||||||
let session = refreshPan123NetdiskSession(id) || getPan123NetdiskSession(id);
|
let session =
|
||||||
|
refreshPan123NetdiskSession(id) || getPan123NetdiskSession(id);
|
||||||
if (!session) {
|
if (!session) {
|
||||||
const payload = parsePan123NetdiskId(id);
|
const payload = parsePan123NetdiskId(id);
|
||||||
const { listPan123ShareVideos } = await import('@/lib/netdisk/pan123.client');
|
const { listPan123ShareVideos } = await import(
|
||||||
const result = await listPan123ShareVideos(payload.shareUrl, payload.passcode || '');
|
'@/lib/netdisk/pan123.client'
|
||||||
|
);
|
||||||
|
const result = await listPan123ShareVideos(
|
||||||
|
payload.shareUrl,
|
||||||
|
payload.passcode || ''
|
||||||
|
);
|
||||||
session = createPan123NetdiskSession({
|
session = createPan123NetdiskSession({
|
||||||
title: title || result.title,
|
title: title || result.title,
|
||||||
shareUrl: payload.shareUrl,
|
shareUrl: payload.shareUrl,
|
||||||
@@ -610,7 +700,10 @@ export async function GET(request: NextRequest) {
|
|||||||
if (!a.isOVA && b.isOVA) return -1;
|
if (!a.isOVA && b.isOVA) return -1;
|
||||||
return a.sortEpisode !== b.sortEpisode
|
return a.sortEpisode !== b.sortEpisode
|
||||||
? a.sortEpisode - b.sortEpisode
|
? a.sortEpisode - b.sortEpisode
|
||||||
: a.fileName.localeCompare(b.fileName, 'zh-Hans-CN', { numeric: true, sensitivity: 'base' });
|
: a.fileName.localeCompare(b.fileName, 'zh-Hans-CN', {
|
||||||
|
numeric: true,
|
||||||
|
sensitivity: 'base',
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
return NextResponse.json({
|
return NextResponse.json({
|
||||||
@@ -622,9 +715,12 @@ export async function GET(request: NextRequest) {
|
|||||||
year: '',
|
year: '',
|
||||||
douban_id: 0,
|
douban_id: 0,
|
||||||
desc: `123网盘分享:${pan123Session.shareUrl}`,
|
desc: `123网盘分享:${pan123Session.shareUrl}`,
|
||||||
episodes: parsedFiles.map((file) => (
|
episodes: parsedFiles.map(
|
||||||
`/api/netdisk/123/play?id=${encodeURIComponent(pan123Session.id)}&episodeIndex=${file.originalIndex}`
|
(file) =>
|
||||||
)),
|
`/api/netdisk/123/play?id=${encodeURIComponent(
|
||||||
|
pan123Session.id
|
||||||
|
)}&episodeIndex=${file.originalIndex}`
|
||||||
|
),
|
||||||
episodes_titles: parsedFiles.map((file) => file.displayTitle),
|
episodes_titles: parsedFiles.map((file) => file.displayTitle),
|
||||||
proxyMode: false,
|
proxyMode: false,
|
||||||
});
|
});
|
||||||
@@ -645,11 +741,17 @@ export async function GET(request: NextRequest) {
|
|||||||
throw new Error('115网盘未配置或未启用');
|
throw new Error('115网盘未配置或未启用');
|
||||||
}
|
}
|
||||||
|
|
||||||
let session = refreshPan115NetdiskSession(id) || getPan115NetdiskSession(id);
|
let session =
|
||||||
|
refreshPan115NetdiskSession(id) || getPan115NetdiskSession(id);
|
||||||
if (!session) {
|
if (!session) {
|
||||||
const payload = parsePan115NetdiskId(id);
|
const payload = parsePan115NetdiskId(id);
|
||||||
const { listPan115ShareVideos } = await import('@/lib/netdisk/pan115.client');
|
const { listPan115ShareVideos } = await import(
|
||||||
const result = await listPan115ShareVideos(payload.shareUrl, payload.passcode || '');
|
'@/lib/netdisk/pan115.client'
|
||||||
|
);
|
||||||
|
const result = await listPan115ShareVideos(
|
||||||
|
payload.shareUrl,
|
||||||
|
payload.passcode || ''
|
||||||
|
);
|
||||||
session = createPan115NetdiskSession({
|
session = createPan115NetdiskSession({
|
||||||
title: title || result.title,
|
title: title || result.title,
|
||||||
shareUrl: payload.shareUrl,
|
shareUrl: payload.shareUrl,
|
||||||
@@ -679,7 +781,10 @@ export async function GET(request: NextRequest) {
|
|||||||
if (!a.isOVA && b.isOVA) return -1;
|
if (!a.isOVA && b.isOVA) return -1;
|
||||||
return a.sortEpisode !== b.sortEpisode
|
return a.sortEpisode !== b.sortEpisode
|
||||||
? a.sortEpisode - b.sortEpisode
|
? a.sortEpisode - b.sortEpisode
|
||||||
: a.name.localeCompare(b.name, 'zh-Hans-CN', { numeric: true, sensitivity: 'base' });
|
: a.name.localeCompare(b.name, 'zh-Hans-CN', {
|
||||||
|
numeric: true,
|
||||||
|
sensitivity: 'base',
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
return NextResponse.json({
|
return NextResponse.json({
|
||||||
@@ -691,9 +796,12 @@ export async function GET(request: NextRequest) {
|
|||||||
year: '',
|
year: '',
|
||||||
douban_id: 0,
|
douban_id: 0,
|
||||||
desc: `115网盘分享:${pan115Session.shareUrl}`,
|
desc: `115网盘分享:${pan115Session.shareUrl}`,
|
||||||
episodes: parsedFiles.map((file) => (
|
episodes: parsedFiles.map(
|
||||||
`/api/netdisk/115/play?id=${encodeURIComponent(pan115Session.id)}&episodeIndex=${file.originalIndex}`
|
(file) =>
|
||||||
)),
|
`/api/netdisk/115/play?id=${encodeURIComponent(
|
||||||
|
pan115Session.id
|
||||||
|
)}&episodeIndex=${file.originalIndex}`
|
||||||
|
),
|
||||||
episodes_titles: parsedFiles.map((file) => file.displayTitle),
|
episodes_titles: parsedFiles.map((file) => file.displayTitle),
|
||||||
proxyMode: false,
|
proxyMode: false,
|
||||||
});
|
});
|
||||||
@@ -705,7 +813,10 @@ export async function GET(request: NextRequest) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sourceCode === NETDISK_QUARK_SOURCE || sourceCode === LEGACY_QUARK_TEMP_SOURCE) {
|
if (
|
||||||
|
sourceCode === NETDISK_QUARK_SOURCE ||
|
||||||
|
sourceCode === LEGACY_QUARK_TEMP_SOURCE
|
||||||
|
) {
|
||||||
try {
|
try {
|
||||||
const config = await getConfig();
|
const config = await getConfig();
|
||||||
const quarkConfig = config.NetDiskConfig?.Quark;
|
const quarkConfig = config.NetDiskConfig?.Quark;
|
||||||
@@ -714,11 +825,18 @@ export async function GET(request: NextRequest) {
|
|||||||
}
|
}
|
||||||
const { parseVideoFileName } = await import('@/lib/video-parser');
|
const { parseVideoFileName } = await import('@/lib/video-parser');
|
||||||
|
|
||||||
let session = refreshQuarkNetdiskSession(id) || getQuarkNetdiskSession(id);
|
let session =
|
||||||
|
refreshQuarkNetdiskSession(id) || getQuarkNetdiskSession(id);
|
||||||
if (!session) {
|
if (!session) {
|
||||||
const payload = parseQuarkNetdiskId(id);
|
const payload = parseQuarkNetdiskId(id);
|
||||||
const { listQuarkShareVideos } = await import('@/lib/netdisk/quark.client');
|
const { listQuarkShareVideos } = await import(
|
||||||
const result = await listQuarkShareVideos(payload.shareUrl, quarkConfig.Cookie, payload.passcode || '');
|
'@/lib/netdisk/quark.client'
|
||||||
|
);
|
||||||
|
const result = await listQuarkShareVideos(
|
||||||
|
payload.shareUrl,
|
||||||
|
quarkConfig.Cookie,
|
||||||
|
payload.passcode || ''
|
||||||
|
);
|
||||||
session = createQuarkNetdiskSession({
|
session = createQuarkNetdiskSession({
|
||||||
title: title || result.title,
|
title: title || result.title,
|
||||||
shareUrl: payload.shareUrl,
|
shareUrl: payload.shareUrl,
|
||||||
@@ -761,9 +879,12 @@ export async function GET(request: NextRequest) {
|
|||||||
year: '',
|
year: '',
|
||||||
douban_id: 0,
|
douban_id: 0,
|
||||||
desc: `夸克网盘分享:${quarkSession.shareUrl}`,
|
desc: `夸克网盘分享:${quarkSession.shareUrl}`,
|
||||||
episodes: episodes.map((ep) => (
|
episodes: episodes.map(
|
||||||
`/api/netdisk/quark/play?id=${encodeURIComponent(quarkSession.id)}&episodeIndex=${ep.originalIndex}`
|
(ep) =>
|
||||||
)),
|
`/api/netdisk/quark/play?id=${encodeURIComponent(
|
||||||
|
quarkSession.id
|
||||||
|
)}&episodeIndex=${ep.originalIndex}`
|
||||||
|
),
|
||||||
episodes_titles: episodes.map((ep) => ep.title),
|
episodes_titles: episodes.map((ep) => ep.title),
|
||||||
proxyMode: false,
|
proxyMode: false,
|
||||||
});
|
});
|
||||||
@@ -788,7 +909,11 @@ export async function GET(request: NextRequest) {
|
|||||||
if (!session) {
|
if (!session) {
|
||||||
const payload = parseUCNetdiskId(id);
|
const payload = parseUCNetdiskId(id);
|
||||||
const { listUCShareVideos } = await import('@/lib/netdisk/uc.client');
|
const { listUCShareVideos } = await import('@/lib/netdisk/uc.client');
|
||||||
const result = await listUCShareVideos(payload.shareUrl, ucConfig.Cookie, payload.passcode || '');
|
const result = await listUCShareVideos(
|
||||||
|
payload.shareUrl,
|
||||||
|
ucConfig.Cookie,
|
||||||
|
payload.passcode || ''
|
||||||
|
);
|
||||||
session = createUCNetdiskSession({
|
session = createUCNetdiskSession({
|
||||||
title: title || result.title,
|
title: title || result.title,
|
||||||
shareUrl: payload.shareUrl,
|
shareUrl: payload.shareUrl,
|
||||||
@@ -831,9 +956,12 @@ export async function GET(request: NextRequest) {
|
|||||||
year: '',
|
year: '',
|
||||||
douban_id: 0,
|
douban_id: 0,
|
||||||
desc: `UC网盘分享:${ucSession.shareUrl}`,
|
desc: `UC网盘分享:${ucSession.shareUrl}`,
|
||||||
episodes: episodes.map((ep) => (
|
episodes: episodes.map(
|
||||||
`/api/netdisk/uc/play?id=${encodeURIComponent(ucSession.id)}&episodeIndex=${ep.originalIndex}`
|
(ep) =>
|
||||||
)),
|
`/api/netdisk/uc/play?id=${encodeURIComponent(
|
||||||
|
ucSession.id
|
||||||
|
)}&episodeIndex=${ep.originalIndex}`
|
||||||
|
),
|
||||||
episodes_titles: episodes.map((ep) => ep.title),
|
episodes_titles: episodes.map((ep) => ep.title),
|
||||||
proxyMode: false,
|
proxyMode: false,
|
||||||
});
|
});
|
||||||
@@ -867,7 +995,9 @@ export async function GET(request: NextRequest) {
|
|||||||
let metaInfo: any = null;
|
let metaInfo: any = null;
|
||||||
let folderMeta: any = null;
|
let folderMeta: any = null;
|
||||||
try {
|
try {
|
||||||
const { getCachedMetaInfo, setCachedMetaInfo } = await import('@/lib/openlist-cache');
|
const { getCachedMetaInfo, setCachedMetaInfo } = await import(
|
||||||
|
'@/lib/openlist-cache'
|
||||||
|
);
|
||||||
const { db } = await import('@/lib/db');
|
const { db } = await import('@/lib/db');
|
||||||
|
|
||||||
metaInfo = getCachedMetaInfo();
|
metaInfo = getCachedMetaInfo();
|
||||||
@@ -891,11 +1021,15 @@ export async function GET(request: NextRequest) {
|
|||||||
|
|
||||||
// 使用 folderName 构建实际路径
|
// 使用 folderName 构建实际路径
|
||||||
const folderName = folderMeta.folderName;
|
const folderName = folderMeta.folderName;
|
||||||
const folderPath = `${rootPath}${rootPath.endsWith('/') ? '' : '/'}${folderName}`;
|
const folderPath = `${rootPath}${
|
||||||
|
rootPath.endsWith('/') ? '' : '/'
|
||||||
|
}${folderName}`;
|
||||||
|
|
||||||
// 2. 直接调用 OpenList 客户端获取视频列表
|
// 2. 直接调用 OpenList 客户端获取视频列表
|
||||||
const { OpenListClient } = await import('@/lib/openlist.client');
|
const { OpenListClient } = await import('@/lib/openlist.client');
|
||||||
const { getCachedVideoInfo, setCachedVideoInfo } = await import('@/lib/openlist-cache');
|
const { getCachedVideoInfo, setCachedVideoInfo } = await import(
|
||||||
|
'@/lib/openlist-cache'
|
||||||
|
);
|
||||||
const { parseVideoFileName } = await import('@/lib/video-parser');
|
const { parseVideoFileName } = await import('@/lib/video-parser');
|
||||||
|
|
||||||
const client = new OpenListClient(
|
const client = new OpenListClient(
|
||||||
@@ -914,7 +1048,11 @@ export async function GET(request: NextRequest) {
|
|||||||
let hasMore = true;
|
let hasMore = true;
|
||||||
|
|
||||||
while (hasMore) {
|
while (hasMore) {
|
||||||
const listResponse = await client.listDirectory(folderPath, currentPage, pageSize);
|
const listResponse = await client.listDirectory(
|
||||||
|
folderPath,
|
||||||
|
currentPage,
|
||||||
|
pageSize
|
||||||
|
);
|
||||||
|
|
||||||
if (listResponse.code !== 200) {
|
if (listResponse.code !== 200) {
|
||||||
throw new Error('OpenList 列表获取失败4');
|
throw new Error('OpenList 列表获取失败4');
|
||||||
@@ -927,10 +1065,35 @@ export async function GET(request: NextRequest) {
|
|||||||
currentPage++;
|
currentPage++;
|
||||||
}
|
}
|
||||||
|
|
||||||
const videoExtensions = ['.mp4', '.mkv', '.avi', '.m3u8', '.flv', '.ts', '.mov', '.wmv', '.webm', '.rmvb', '.rm', '.mpg', '.mpeg', '.3gp', '.f4v', '.m4v', '.vob'];
|
const videoExtensions = [
|
||||||
|
'.mp4',
|
||||||
|
'.mkv',
|
||||||
|
'.avi',
|
||||||
|
'.m3u8',
|
||||||
|
'.flv',
|
||||||
|
'.ts',
|
||||||
|
'.mov',
|
||||||
|
'.wmv',
|
||||||
|
'.webm',
|
||||||
|
'.rmvb',
|
||||||
|
'.rm',
|
||||||
|
'.mpg',
|
||||||
|
'.mpeg',
|
||||||
|
'.3gp',
|
||||||
|
'.f4v',
|
||||||
|
'.m4v',
|
||||||
|
'.vob',
|
||||||
|
];
|
||||||
const videoFiles = allFiles.filter((item) => {
|
const videoFiles = allFiles.filter((item) => {
|
||||||
if (item.is_dir || item.name.startsWith('.') || item.name.endsWith('.json')) return false;
|
if (
|
||||||
return videoExtensions.some(ext => item.name.toLowerCase().endsWith(ext));
|
item.is_dir ||
|
||||||
|
item.name.startsWith('.') ||
|
||||||
|
item.name.endsWith('.json')
|
||||||
|
)
|
||||||
|
return false;
|
||||||
|
return videoExtensions.some((ext) =>
|
||||||
|
item.name.toLowerCase().endsWith(ext)
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!videoInfo) {
|
if (!videoInfo) {
|
||||||
@@ -940,7 +1103,7 @@ export async function GET(request: NextRequest) {
|
|||||||
const file = videoFiles[i];
|
const file = videoFiles[i];
|
||||||
const parsed = parseVideoFileName(file.name);
|
const parsed = parseVideoFileName(file.name);
|
||||||
videoInfo.episodes[file.name] = {
|
videoInfo.episodes[file.name] = {
|
||||||
episode: parsed.episode || (i + 1),
|
episode: parsed.episode || i + 1,
|
||||||
season: parsed.season,
|
season: parsed.season,
|
||||||
title: parsed.title,
|
title: parsed.title,
|
||||||
parsed_from: 'filename',
|
parsed_from: 'filename',
|
||||||
@@ -955,25 +1118,46 @@ export async function GET(request: NextRequest) {
|
|||||||
const parsed = parseVideoFileName(file.name);
|
const parsed = parseVideoFileName(file.name);
|
||||||
let episodeInfo;
|
let episodeInfo;
|
||||||
if (parsed.episode) {
|
if (parsed.episode) {
|
||||||
episodeInfo = { episode: parsed.episode, season: parsed.season, title: parsed.title, parsed_from: 'filename', isOVA: parsed.isOVA };
|
episodeInfo = {
|
||||||
|
episode: parsed.episode,
|
||||||
|
season: parsed.season,
|
||||||
|
title: parsed.title,
|
||||||
|
parsed_from: 'filename',
|
||||||
|
isOVA: parsed.isOVA,
|
||||||
|
};
|
||||||
} else {
|
} else {
|
||||||
episodeInfo = videoInfo!.episodes[file.name] || { episode: index + 1, season: undefined, title: undefined, parsed_from: 'filename' };
|
episodeInfo = videoInfo!.episodes[file.name] || {
|
||||||
|
episode: index + 1,
|
||||||
|
season: undefined,
|
||||||
|
title: undefined,
|
||||||
|
parsed_from: 'filename',
|
||||||
|
};
|
||||||
}
|
}
|
||||||
let displayTitle = episodeInfo.title;
|
let displayTitle = episodeInfo.title;
|
||||||
if (!displayTitle && episodeInfo.episode) {
|
if (!displayTitle && episodeInfo.episode) {
|
||||||
displayTitle = episodeInfo.isOVA ? `OVA ${episodeInfo.episode}` : `第${episodeInfo.episode}集`;
|
displayTitle = episodeInfo.isOVA
|
||||||
|
? `OVA ${episodeInfo.episode}`
|
||||||
|
: `第${episodeInfo.episode}集`;
|
||||||
}
|
}
|
||||||
if (!displayTitle) {
|
if (!displayTitle) {
|
||||||
displayTitle = file.name;
|
displayTitle = file.name;
|
||||||
}
|
}
|
||||||
return { fileName: file.name, episode: episodeInfo.episode || 0, season: episodeInfo.season, title: displayTitle, isOVA: episodeInfo.isOVA };
|
return {
|
||||||
|
fileName: file.name,
|
||||||
|
episode: episodeInfo.episode || 0,
|
||||||
|
season: episodeInfo.season,
|
||||||
|
title: displayTitle,
|
||||||
|
isOVA: episodeInfo.isOVA,
|
||||||
|
};
|
||||||
})
|
})
|
||||||
.sort((a, b) => {
|
.sort((a, b) => {
|
||||||
// OVA 排在最后
|
// OVA 排在最后
|
||||||
if (a.isOVA && !b.isOVA) return 1;
|
if (a.isOVA && !b.isOVA) return 1;
|
||||||
if (!a.isOVA && b.isOVA) return -1;
|
if (!a.isOVA && b.isOVA) return -1;
|
||||||
// 都是 OVA 或都不是 OVA,按集数排序
|
// 都是 OVA 或都不是 OVA,按集数排序
|
||||||
return a.episode !== b.episode ? a.episode - b.episode : a.fileName.localeCompare(b.fileName);
|
return a.episode !== b.episode
|
||||||
|
? a.episode - b.episode
|
||||||
|
: a.fileName.localeCompare(b.fileName);
|
||||||
});
|
});
|
||||||
|
|
||||||
// 3. 从 metainfo 中获取元数据
|
// 3. 从 metainfo 中获取元数据
|
||||||
@@ -984,11 +1168,20 @@ export async function GET(request: NextRequest) {
|
|||||||
source_name: '私人影库',
|
source_name: '私人影库',
|
||||||
id: id,
|
id: id,
|
||||||
title: folderMeta?.title || folderName,
|
title: folderMeta?.title || folderName,
|
||||||
poster: folderMeta?.poster_path ? getTMDBImageUrl(folderMeta.poster_path) : '',
|
poster: folderMeta?.poster_path
|
||||||
year: folderMeta?.release_date ? folderMeta.release_date.split('-')[0] : '',
|
? getTMDBImageUrl(folderMeta.poster_path)
|
||||||
|
: '',
|
||||||
|
year: folderMeta?.release_date
|
||||||
|
? folderMeta.release_date.split('-')[0]
|
||||||
|
: '',
|
||||||
douban_id: 0,
|
douban_id: 0,
|
||||||
desc: folderMeta?.overview || '',
|
desc: folderMeta?.overview || '',
|
||||||
episodes: episodes.map((ep) => `/api/openlist/play?folder=${encodeURIComponent(folderName)}&fileName=${encodeURIComponent(ep.fileName)}`),
|
episodes: episodes.map(
|
||||||
|
(ep) =>
|
||||||
|
`/api/openlist/play?folder=${encodeURIComponent(
|
||||||
|
folderName
|
||||||
|
)}&fileName=${encodeURIComponent(ep.fileName)}`
|
||||||
|
),
|
||||||
episodes_titles: episodes.map((ep) => ep.title),
|
episodes_titles: episodes.map((ep) => ep.title),
|
||||||
proxyMode: false, // openlist 源不使用代理模式
|
proxyMode: false, // openlist 源不使用代理模式
|
||||||
};
|
};
|
||||||
|
|||||||
+156
-51
@@ -2,7 +2,15 @@
|
|||||||
|
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { BookMarked, BookOpen, Bot, ChevronRight, Link as LinkIcon, ListVideo, Music } from 'lucide-react';
|
import {
|
||||||
|
BookMarked,
|
||||||
|
BookOpen,
|
||||||
|
Bot,
|
||||||
|
ChevronRight,
|
||||||
|
Link as LinkIcon,
|
||||||
|
ListVideo,
|
||||||
|
Music,
|
||||||
|
} from 'lucide-react';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { Suspense, useEffect, useState } from 'react';
|
import { Suspense, useEffect, useState } from 'react';
|
||||||
|
|
||||||
@@ -56,23 +64,29 @@ function HomeClient() {
|
|||||||
{ id: 'upcomingContent', name: '即将上映', enabled: true, order: 5 },
|
{ id: 'upcomingContent', name: '即将上映', enabled: true, order: 5 },
|
||||||
]);
|
]);
|
||||||
const [homeBannerEnabled, setHomeBannerEnabled] = useState(true);
|
const [homeBannerEnabled, setHomeBannerEnabled] = useState(true);
|
||||||
const [homeContinueWatchingEnabled, setHomeContinueWatchingEnabled] = useState(true);
|
const [homeContinueWatchingEnabled, setHomeContinueWatchingEnabled] =
|
||||||
|
useState(true);
|
||||||
|
|
||||||
const [showAnnouncement, setShowAnnouncement] = useState(false);
|
const [showAnnouncement, setShowAnnouncement] = useState(false);
|
||||||
const [showHttpWarning, setShowHttpWarning] = useState(true);
|
const [showHttpWarning, setShowHttpWarning] = useState(true);
|
||||||
const [showAIChat, setShowAIChat] = useState(false);
|
const [showAIChat, setShowAIChat] = useState(false);
|
||||||
const [aiEnabled, setAiEnabled] = useState(false);
|
const [aiEnabled, setAiEnabled] = useState(false);
|
||||||
const [aiDefaultMessageNoVideo, setAiDefaultMessageNoVideo] = useState('你好!我是MoonTVPlus的AI影视助手。想看什么电影或剧集?需要推荐吗?');
|
const [aiDefaultMessageNoVideo, setAiDefaultMessageNoVideo] = useState(
|
||||||
|
'你好!我是MoonTVPlus的AI影视助手。想看什么电影或剧集?需要推荐吗?'
|
||||||
|
);
|
||||||
const [sourceSearchEnabled, setSourceSearchEnabled] = useState(true);
|
const [sourceSearchEnabled, setSourceSearchEnabled] = useState(true);
|
||||||
const [musicEnabled, setMusicEnabled] = useState(false);
|
const [musicEnabled, setMusicEnabled] = useState(false);
|
||||||
const [mangaEnabled, setMangaEnabled] = useState(false);
|
const [mangaEnabled, setMangaEnabled] = useState(false);
|
||||||
const [booksEnabled, setBooksEnabled] = useState(false);
|
const [booksEnabled, setBooksEnabled] = useState(false);
|
||||||
|
const [netdiskTempPlayEnabled, setNetdiskTempPlayEnabled] = useState(false);
|
||||||
const [showDirectPlayDialog, setShowDirectPlayDialog] = useState(false);
|
const [showDirectPlayDialog, setShowDirectPlayDialog] = useState(false);
|
||||||
const [directPlayUrl, setDirectPlayUrl] = useState('');
|
const [directPlayUrl, setDirectPlayUrl] = useState('');
|
||||||
const [directPlaySubmitting, setDirectPlaySubmitting] = useState(false);
|
const [directPlaySubmitting, setDirectPlaySubmitting] = useState(false);
|
||||||
const [toast, setToast] = useState<ToastProps | null>(null);
|
const [toast, setToast] = useState<ToastProps | null>(null);
|
||||||
|
|
||||||
const detectNetdiskLink = (url: string): {
|
const detectNetdiskLink = (
|
||||||
|
url: string
|
||||||
|
): {
|
||||||
provider: 'quark' | 'mobile' | 'baidu' | 'tianyi' | '123' | 'uc' | '115';
|
provider: 'quark' | 'mobile' | 'baidu' | 'tianyi' | '123' | 'uc' | '115';
|
||||||
shareUrl: string;
|
shareUrl: string;
|
||||||
passcode?: string;
|
passcode?: string;
|
||||||
@@ -84,11 +98,17 @@ function HomeClient() {
|
|||||||
|
|
||||||
const inlinePasscode = (text: string) =>
|
const inlinePasscode = (text: string) =>
|
||||||
pickPasscode(
|
pickPasscode(
|
||||||
text.match(/(?:提取码|访问码|密码)\s*[::=]?\s*([a-zA-Z0-9]{4,8})/i)?.[1],
|
text.match(
|
||||||
|
/(?:提取码|访问码|密码)\s*[::=]?\s*([a-zA-Z0-9]{4,8})/i
|
||||||
|
)?.[1],
|
||||||
text.match(/[?&](?:pwd|passcode|accessCode)=([^&\s]+)/i)?.[1]
|
text.match(/[?&](?:pwd|passcode|accessCode)=([^&\s]+)/i)?.[1]
|
||||||
);
|
);
|
||||||
|
|
||||||
if (/https:\/\/(?:www\.)?123(?:684|865|912|pan)\.(?:com|cn)\/s\//i.test(trimmed)) {
|
if (
|
||||||
|
/https:\/\/(?:www\.)?123(?:684|865|912|pan)\.(?:com|cn)\/s\//i.test(
|
||||||
|
trimmed
|
||||||
|
)
|
||||||
|
) {
|
||||||
return {
|
return {
|
||||||
provider: '123',
|
provider: '123',
|
||||||
shareUrl: trimmed,
|
shareUrl: trimmed,
|
||||||
@@ -99,7 +119,10 @@ function HomeClient() {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (/https:\/\/cloud\.189\.cn\/(web\/share\?code=|t\/)/i.test(trimmed) || /https:\/\/h5\.cloud\.189\.cn\/share\.html#\/t\//i.test(trimmed)) {
|
if (
|
||||||
|
/https:\/\/cloud\.189\.cn\/(web\/share\?code=|t\/)/i.test(trimmed) ||
|
||||||
|
/https:\/\/h5\.cloud\.189\.cn\/share\.html#\/t\//i.test(trimmed)
|
||||||
|
) {
|
||||||
return {
|
return {
|
||||||
provider: 'tianyi',
|
provider: 'tianyi',
|
||||||
shareUrl: trimmed,
|
shareUrl: trimmed,
|
||||||
@@ -172,21 +195,25 @@ function HomeClient() {
|
|||||||
setDirectPlaySubmitting(true);
|
setDirectPlaySubmitting(true);
|
||||||
try {
|
try {
|
||||||
const netdisk = detectNetdiskLink(trimmed);
|
const netdisk = detectNetdiskLink(trimmed);
|
||||||
|
if (netdisk && !netdiskTempPlayEnabled) {
|
||||||
|
throw new Error('无权限使用临时播放');
|
||||||
|
}
|
||||||
|
|
||||||
if (netdisk) {
|
if (netdisk) {
|
||||||
const source =
|
const source =
|
||||||
netdisk.provider === 'mobile'
|
netdisk.provider === 'mobile'
|
||||||
? 'netdisk-mobile'
|
? 'netdisk-mobile'
|
||||||
: netdisk.provider === 'baidu'
|
: netdisk.provider === 'baidu'
|
||||||
? 'netdisk-baidu'
|
? 'netdisk-baidu'
|
||||||
: netdisk.provider === 'tianyi'
|
: netdisk.provider === 'tianyi'
|
||||||
? 'netdisk-tianyi'
|
? 'netdisk-tianyi'
|
||||||
: netdisk.provider === '115'
|
: netdisk.provider === '115'
|
||||||
? 'netdisk-115'
|
? 'netdisk-115'
|
||||||
: netdisk.provider === 'uc'
|
: netdisk.provider === 'uc'
|
||||||
? 'netdisk-uc'
|
? 'netdisk-uc'
|
||||||
: netdisk.provider === '123'
|
: netdisk.provider === '123'
|
||||||
? 'netdisk-123'
|
? 'netdisk-123'
|
||||||
: 'netdisk-quark';
|
: 'netdisk-quark';
|
||||||
const id = base58Encode(
|
const id = base58Encode(
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
shareUrl: netdisk.shareUrl,
|
shareUrl: netdisk.shareUrl,
|
||||||
@@ -196,7 +223,11 @@ function HomeClient() {
|
|||||||
if (!id) {
|
if (!id) {
|
||||||
throw new Error('网盘链接编码失败');
|
throw new Error('网盘链接编码失败');
|
||||||
}
|
}
|
||||||
const targetUrl = `/play?source=${encodeURIComponent(source)}&id=${encodeURIComponent(id)}&title=${encodeURIComponent('网盘直链播放')}`;
|
const targetUrl = `/play?source=${encodeURIComponent(
|
||||||
|
source
|
||||||
|
)}&id=${encodeURIComponent(id)}&title=${encodeURIComponent(
|
||||||
|
'网盘直链播放'
|
||||||
|
)}`;
|
||||||
setShowDirectPlayDialog(false);
|
setShowDirectPlayDialog(false);
|
||||||
setDirectPlayUrl('');
|
setDirectPlayUrl('');
|
||||||
window.location.assign(targetUrl);
|
window.location.assign(targetUrl);
|
||||||
@@ -205,7 +236,9 @@ function HomeClient() {
|
|||||||
|
|
||||||
const encoded = base58Encode(trimmed);
|
const encoded = base58Encode(trimmed);
|
||||||
if (!encoded) return;
|
if (!encoded) return;
|
||||||
const targetUrl = `/play?source=directplay&id=${encodeURIComponent(encoded)}`;
|
const targetUrl = `/play?source=directplay&id=${encodeURIComponent(
|
||||||
|
encoded
|
||||||
|
)}`;
|
||||||
setShowDirectPlayDialog(false);
|
setShowDirectPlayDialog(false);
|
||||||
setDirectPlayUrl('');
|
setDirectPlayUrl('');
|
||||||
window.location.assign(targetUrl);
|
window.location.assign(targetUrl);
|
||||||
@@ -237,9 +270,13 @@ function HomeClient() {
|
|||||||
setHomeBannerEnabled(savedHomeBannerEnabled === 'true');
|
setHomeBannerEnabled(savedHomeBannerEnabled === 'true');
|
||||||
}
|
}
|
||||||
|
|
||||||
const savedHomeContinueWatchingEnabled = localStorage.getItem('homeContinueWatchingEnabled');
|
const savedHomeContinueWatchingEnabled = localStorage.getItem(
|
||||||
|
'homeContinueWatchingEnabled'
|
||||||
|
);
|
||||||
if (savedHomeContinueWatchingEnabled !== null) {
|
if (savedHomeContinueWatchingEnabled !== null) {
|
||||||
setHomeContinueWatchingEnabled(savedHomeContinueWatchingEnabled === 'true');
|
setHomeContinueWatchingEnabled(
|
||||||
|
savedHomeContinueWatchingEnabled === 'true'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -256,7 +293,10 @@ function HomeClient() {
|
|||||||
|
|
||||||
window.addEventListener('homeModulesUpdated', handleHomeModulesUpdated);
|
window.addEventListener('homeModulesUpdated', handleHomeModulesUpdated);
|
||||||
return () => {
|
return () => {
|
||||||
window.removeEventListener('homeModulesUpdated', handleHomeModulesUpdated);
|
window.removeEventListener(
|
||||||
|
'homeModulesUpdated',
|
||||||
|
handleHomeModulesUpdated
|
||||||
|
);
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@@ -269,7 +309,8 @@ function HomeClient() {
|
|||||||
setAiEnabled(enabled);
|
setAiEnabled(enabled);
|
||||||
|
|
||||||
// 加载AI默认消息配置
|
// 加载AI默认消息配置
|
||||||
const defaultMsg = (window as any).RUNTIME_CONFIG?.AI_DEFAULT_MESSAGE_NO_VIDEO;
|
const defaultMsg = (window as any).RUNTIME_CONFIG
|
||||||
|
?.AI_DEFAULT_MESSAGE_NO_VIDEO;
|
||||||
if (defaultMsg) {
|
if (defaultMsg) {
|
||||||
setAiDefaultMessageNoVideo(defaultMsg);
|
setAiDefaultMessageNoVideo(defaultMsg);
|
||||||
}
|
}
|
||||||
@@ -279,7 +320,8 @@ function HomeClient() {
|
|||||||
// 检查源站寻片功能是否启用
|
// 检查源站寻片功能是否启用
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (typeof window !== 'undefined') {
|
if (typeof window !== 'undefined') {
|
||||||
const enabled = (window as any).RUNTIME_CONFIG?.ENABLE_SOURCE_SEARCH !== false;
|
const enabled =
|
||||||
|
(window as any).RUNTIME_CONFIG?.ENABLE_SOURCE_SEARCH !== false;
|
||||||
setSourceSearchEnabled(enabled);
|
setSourceSearchEnabled(enabled);
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
@@ -308,6 +350,15 @@ function HomeClient() {
|
|||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
// 检查网盘临时播放权限,仅有权限时在直链播放弹窗展示网盘在线播放提示
|
||||||
|
useEffect(() => {
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
const enabled = !!(window as any).RUNTIME_CONFIG
|
||||||
|
?.NETDISK_TEMP_PLAY_ENABLED;
|
||||||
|
setNetdiskTempPlayEnabled(enabled);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
// 检查公告弹窗状态
|
// 检查公告弹窗状态
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (typeof window !== 'undefined' && announcement) {
|
if (typeof window !== 'undefined' && announcement) {
|
||||||
@@ -336,7 +387,10 @@ 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
|
// Ignore localStorage errors
|
||||||
}
|
}
|
||||||
@@ -356,18 +410,43 @@ function HomeClient() {
|
|||||||
if (duanjuCache?.data) setHotDuanju(duanjuCache.data);
|
if (duanjuCache?.data) setHotDuanju(duanjuCache.data);
|
||||||
if (upcomingCache?.data) setUpcomingContent(upcomingCache.data);
|
if (upcomingCache?.data) setUpcomingContent(upcomingCache.data);
|
||||||
|
|
||||||
const hasCache = moviesCache || tvShowsCache || varietyCache || bangumiCache || duanjuCache || upcomingCache;
|
const hasCache =
|
||||||
|
moviesCache ||
|
||||||
|
tvShowsCache ||
|
||||||
|
varietyCache ||
|
||||||
|
bangumiCache ||
|
||||||
|
duanjuCache ||
|
||||||
|
upcomingCache;
|
||||||
if (hasCache) setLoading(false);
|
if (hasCache) setLoading(false);
|
||||||
|
|
||||||
const needsRefresh = !moviesCache || moviesCache.expired || !tvShowsCache || tvShowsCache.expired ||
|
const needsRefresh =
|
||||||
!varietyCache || varietyCache.expired || !bangumiCache || bangumiCache.expired ||
|
!moviesCache ||
|
||||||
!duanjuCache || duanjuCache.expired || !upcomingCache || upcomingCache.expired;
|
moviesCache.expired ||
|
||||||
|
!tvShowsCache ||
|
||||||
|
tvShowsCache.expired ||
|
||||||
|
!varietyCache ||
|
||||||
|
varietyCache.expired ||
|
||||||
|
!bangumiCache ||
|
||||||
|
bangumiCache.expired ||
|
||||||
|
!duanjuCache ||
|
||||||
|
duanjuCache.expired ||
|
||||||
|
!upcomingCache ||
|
||||||
|
upcomingCache.expired;
|
||||||
|
|
||||||
if (needsRefresh) {
|
if (needsRefresh) {
|
||||||
(async () => {
|
(async () => {
|
||||||
try {
|
try {
|
||||||
const [moviesData, tvShowsData, varietyShowsData, bangumiCalendarData] = await Promise.all([
|
const [
|
||||||
getDoubanCategories({ kind: 'movie', category: '热门', type: '全部' }),
|
moviesData,
|
||||||
|
tvShowsData,
|
||||||
|
varietyShowsData,
|
||||||
|
bangumiCalendarData,
|
||||||
|
] = await Promise.all([
|
||||||
|
getDoubanCategories({
|
||||||
|
kind: 'movie',
|
||||||
|
category: '热门',
|
||||||
|
type: '全部',
|
||||||
|
}),
|
||||||
getDoubanCategories({ kind: 'tv', category: 'tv', type: 'tv' }),
|
getDoubanCategories({ kind: 'tv', category: 'tv', type: 'tv' }),
|
||||||
getDoubanCategories({ kind: 'tv', category: 'show', type: 'show' }),
|
getDoubanCategories({ kind: 'tv', category: 'show', type: 'show' }),
|
||||||
GetBangumiCalendarData(),
|
GetBangumiCalendarData(),
|
||||||
@@ -400,7 +479,11 @@ function HomeClient() {
|
|||||||
const duanjuResponse = await fetch('/api/duanju/recommends');
|
const duanjuResponse = await fetch('/api/duanju/recommends');
|
||||||
if (duanjuResponse.ok) {
|
if (duanjuResponse.ok) {
|
||||||
const duanjuResult = await duanjuResponse.json();
|
const duanjuResult = await duanjuResponse.json();
|
||||||
if (duanjuResult.code === 200 && duanjuResult.data && duanjuResult.data.length > 0) {
|
if (
|
||||||
|
duanjuResult.code === 200 &&
|
||||||
|
duanjuResult.data &&
|
||||||
|
duanjuResult.data.length > 0
|
||||||
|
) {
|
||||||
setHotDuanju(duanjuResult.data);
|
setHotDuanju(duanjuResult.data);
|
||||||
setCache('homepage_duanju', duanjuResult.data);
|
setCache('homepage_duanju', duanjuResult.data);
|
||||||
}
|
}
|
||||||
@@ -413,10 +496,18 @@ function HomeClient() {
|
|||||||
const response = await fetch('/api/tmdb/upcoming');
|
const response = await fetch('/api/tmdb/upcoming');
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
const result = await response.json();
|
const result = await response.json();
|
||||||
if (result.code === 200 && result.data && result.data.length > 0) {
|
if (
|
||||||
|
result.code === 200 &&
|
||||||
|
result.data &&
|
||||||
|
result.data.length > 0
|
||||||
|
) {
|
||||||
const sorted = [...result.data].sort((a, b) => {
|
const sorted = [...result.data].sort((a, b) => {
|
||||||
const dateA = new Date(a.release_date || '9999-12-31').getTime();
|
const dateA = new Date(
|
||||||
const dateB = new Date(b.release_date || '9999-12-31').getTime();
|
a.release_date || '9999-12-31'
|
||||||
|
).getTime();
|
||||||
|
const dateB = new Date(
|
||||||
|
b.release_date || '9999-12-31'
|
||||||
|
).getTime();
|
||||||
return dateA - dateB;
|
return dateA - dateB;
|
||||||
});
|
});
|
||||||
setUpcomingContent(sorted);
|
setUpcomingContent(sorted);
|
||||||
@@ -436,8 +527,6 @@ function HomeClient() {
|
|||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const handleCloseAnnouncement = (announcement: string) => {
|
const handleCloseAnnouncement = (announcement: string) => {
|
||||||
setShowAnnouncement(false);
|
setShowAnnouncement(false);
|
||||||
localStorage.setItem('hasSeenAnnouncement', announcement); // 记录已查看弹窗
|
localStorage.setItem('hasSeenAnnouncement', announcement); // 记录已查看弹窗
|
||||||
@@ -448,7 +537,7 @@ function HomeClient() {
|
|||||||
switch (moduleId) {
|
switch (moduleId) {
|
||||||
case 'hotMovies':
|
case 'hotMovies':
|
||||||
return (
|
return (
|
||||||
<section key="hotMovies" className='mb-8'>
|
<section key='hotMovies' className='mb-8'>
|
||||||
<div className='mb-4 flex items-center justify-between'>
|
<div className='mb-4 flex items-center justify-between'>
|
||||||
<h2 className='text-xl font-bold text-gray-800 dark:text-gray-200'>
|
<h2 className='text-xl font-bold text-gray-800 dark:text-gray-200'>
|
||||||
热门电影
|
热门电影
|
||||||
@@ -496,7 +585,7 @@ function HomeClient() {
|
|||||||
case 'hotDuanju':
|
case 'hotDuanju':
|
||||||
if (hotDuanju.length === 0) return null;
|
if (hotDuanju.length === 0) return null;
|
||||||
return (
|
return (
|
||||||
<section key="hotDuanju" className='mb-8'>
|
<section key='hotDuanju' className='mb-8'>
|
||||||
<div className='mb-4 flex items-center justify-between'>
|
<div className='mb-4 flex items-center justify-between'>
|
||||||
<h2 className='text-xl font-bold text-gray-800 dark:text-gray-200'>
|
<h2 className='text-xl font-bold text-gray-800 dark:text-gray-200'>
|
||||||
热播短剧
|
热播短剧
|
||||||
@@ -550,7 +639,7 @@ function HomeClient() {
|
|||||||
|
|
||||||
case 'bangumiCalendar':
|
case 'bangumiCalendar':
|
||||||
return (
|
return (
|
||||||
<section key="bangumiCalendar" className='mb-8'>
|
<section key='bangumiCalendar' className='mb-8'>
|
||||||
<div className='mb-4 flex items-center justify-between'>
|
<div className='mb-4 flex items-center justify-between'>
|
||||||
<h2 className='text-xl font-bold text-gray-800 dark:text-gray-200'>
|
<h2 className='text-xl font-bold text-gray-800 dark:text-gray-200'>
|
||||||
新番放送
|
新番放送
|
||||||
@@ -578,7 +667,15 @@ function HomeClient() {
|
|||||||
))
|
))
|
||||||
: (() => {
|
: (() => {
|
||||||
const today = new Date();
|
const today = new Date();
|
||||||
const weekdays = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
|
const weekdays = [
|
||||||
|
'Sun',
|
||||||
|
'Mon',
|
||||||
|
'Tue',
|
||||||
|
'Wed',
|
||||||
|
'Thu',
|
||||||
|
'Fri',
|
||||||
|
'Sat',
|
||||||
|
];
|
||||||
const currentWeekday = weekdays[today.getDay()];
|
const currentWeekday = weekdays[today.getDay()];
|
||||||
const todayAnimes =
|
const todayAnimes =
|
||||||
bangumiCalendarData
|
bangumiCalendarData
|
||||||
@@ -615,7 +712,7 @@ function HomeClient() {
|
|||||||
|
|
||||||
case 'hotTvShows':
|
case 'hotTvShows':
|
||||||
return (
|
return (
|
||||||
<section key="hotTvShows" className='mb-8'>
|
<section key='hotTvShows' className='mb-8'>
|
||||||
<div className='mb-4 flex items-center justify-between'>
|
<div className='mb-4 flex items-center justify-between'>
|
||||||
<h2 className='text-xl font-bold text-gray-800 dark:text-gray-200'>
|
<h2 className='text-xl font-bold text-gray-800 dark:text-gray-200'>
|
||||||
热门剧集
|
热门剧集
|
||||||
@@ -662,7 +759,7 @@ function HomeClient() {
|
|||||||
|
|
||||||
case 'hotVarietyShows':
|
case 'hotVarietyShows':
|
||||||
return (
|
return (
|
||||||
<section key="hotVarietyShows" className='mb-8'>
|
<section key='hotVarietyShows' className='mb-8'>
|
||||||
<div className='mb-4 flex items-center justify-between'>
|
<div className='mb-4 flex items-center justify-between'>
|
||||||
<h2 className='text-xl font-bold text-gray-800 dark:text-gray-200'>
|
<h2 className='text-xl font-bold text-gray-800 dark:text-gray-200'>
|
||||||
热门综艺
|
热门综艺
|
||||||
@@ -699,7 +796,9 @@ function HomeClient() {
|
|||||||
rate={varietyShow.rate}
|
rate={varietyShow.rate}
|
||||||
type='tv'
|
type='tv'
|
||||||
from='douban'
|
from='douban'
|
||||||
douban_id={varietyShow.id ? parseInt(varietyShow.id) : undefined}
|
douban_id={
|
||||||
|
varietyShow.id ? parseInt(varietyShow.id) : undefined
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
@@ -710,7 +809,7 @@ function HomeClient() {
|
|||||||
case 'upcomingContent':
|
case 'upcomingContent':
|
||||||
if (upcomingContent.length === 0) return null;
|
if (upcomingContent.length === 0) return null;
|
||||||
return (
|
return (
|
||||||
<section key="upcomingContent" className='mb-8'>
|
<section key='upcomingContent' className='mb-8'>
|
||||||
<div className='mb-4 flex items-center justify-between'>
|
<div className='mb-4 flex items-center justify-between'>
|
||||||
<h2 className='text-xl font-bold text-gray-800 dark:text-gray-200'>
|
<h2 className='text-xl font-bold text-gray-800 dark:text-gray-200'>
|
||||||
即将上映
|
即将上映
|
||||||
@@ -762,7 +861,11 @@ function HomeClient() {
|
|||||||
{/* 首页内容 */}
|
{/* 首页内容 */}
|
||||||
<>
|
<>
|
||||||
{/* 源站寻片和AI问片入口 */}
|
{/* 源站寻片和AI问片入口 */}
|
||||||
<div className={`flex items-center justify-end gap-2 mb-4 ${homeBannerEnabled ? '' : 'mt-[30px]'}`}>
|
<div
|
||||||
|
className={`flex items-center justify-end gap-2 mb-4 ${
|
||||||
|
homeBannerEnabled ? '' : 'mt-[30px]'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
<button
|
<button
|
||||||
onClick={handleDirectPlay}
|
onClick={handleDirectPlay}
|
||||||
className='p-1.5 rounded-lg text-blue-500 hover:text-blue-600 transition-colors'
|
className='p-1.5 rounded-lg text-blue-500 hover:text-blue-600 transition-colors'
|
||||||
@@ -833,9 +936,9 @@ function HomeClient() {
|
|||||||
|
|
||||||
{/* 根据配置动态渲染首页模块 */}
|
{/* 根据配置动态渲染首页模块 */}
|
||||||
{homeModules
|
{homeModules
|
||||||
.filter(module => module.enabled)
|
.filter((module) => module.enabled)
|
||||||
.sort((a, b) => a.order - b.order)
|
.sort((a, b) => a.order - b.order)
|
||||||
.map(module => renderModule(module.id))}
|
.map((module) => renderModule(module.id))}
|
||||||
</>
|
</>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -899,9 +1002,11 @@ function HomeClient() {
|
|||||||
<div className='text-sm text-gray-600 dark:text-gray-300'>
|
<div className='text-sm text-gray-600 dark:text-gray-300'>
|
||||||
请输入可直接播放的视频链接。
|
请输入可直接播放的视频链接。
|
||||||
</div>
|
</div>
|
||||||
<div className='text-xs text-gray-500 dark:text-gray-400'>
|
{netdiskTempPlayEnabled && (
|
||||||
支持夸克、UC、百度、天翼、移动、123、115 网盘在线播放。
|
<div className='text-xs text-gray-500 dark:text-gray-400'>
|
||||||
</div>
|
支持夸克、UC、百度、天翼、移动、123、115 网盘在线播放。
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<input
|
<input
|
||||||
value={directPlayUrl}
|
value={directPlayUrl}
|
||||||
onChange={(event) => setDirectPlayUrl(event.target.value)}
|
onChange={(event) => setDirectPlayUrl(event.target.value)}
|
||||||
|
|||||||
Reference in New Issue
Block a user