diff --git a/src/app/admin/page.tsx b/src/app/admin/page.tsx
index 9424738..d7cdc28 100644
--- a/src/app/admin/page.tsx
+++ b/src/app/admin/page.tsx
@@ -3708,6 +3708,8 @@ const NetDiskConfigComponent = ({
const [openListTempPath, setOpenListTempPath] = useState('/');
const [mobileEnabled, setMobileEnabled] = useState(false);
const [mobileAuthorization, setMobileAuthorization] = useState('');
+ const [baiduEnabled, setBaiduEnabled] = useState(false);
+ const [baiduCookie, setBaiduCookie] = useState('');
useEffect(() => {
const quark = config?.NetDiskConfig?.Quark;
@@ -3719,6 +3721,8 @@ const NetDiskConfigComponent = ({
setOpenListTempPath(quark?.OpenListTempPath || '/');
setMobileEnabled(mobile?.Enabled || false);
setMobileAuthorization(mobile?.Authorization || '');
+ setBaiduEnabled(config?.NetDiskConfig?.Baidu?.Enabled || false);
+ setBaiduCookie(config?.NetDiskConfig?.Baidu?.Cookie || '');
}, [config]);
const handleSave = async () => {
@@ -3739,6 +3743,10 @@ const NetDiskConfigComponent = ({
Enabled: mobileEnabled,
Authorization: mobileAuthorization,
},
+ Baidu: {
+ Enabled: baiduEnabled,
+ Cookie: baiduCookie,
+ },
}),
});
@@ -3809,6 +3817,34 @@ const NetDiskConfigComponent = ({
});
};
+ const handleValidateBaidu = async () => {
+ await withLoading('validateBaiduNetDisk', async () => {
+ try {
+ const response = await fetch('/api/admin/netdisk', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({
+ action: 'validate',
+ provider: 'baidu',
+ Baidu: {
+ Cookie: baiduCookie,
+ },
+ }),
+ });
+
+ const data = await response.json();
+ if (!response.ok) {
+ throw new Error(data.error || '校验失败');
+ }
+
+ showSuccess(data.message || '百度网盘 Cookie 格式正常', showAlert);
+ } catch (error) {
+ showError(error instanceof Error ? error.message : '校验失败', showAlert);
+ throw error;
+ }
+ });
+ };
+
return (
@@ -3986,6 +4022,64 @@ const NetDiskConfigComponent = ({
+
+
+ 百度网盘
+
+
+
+
+
+ 启用百度网盘
+
+
+ 开启后,网盘搜索中的百度网盘资源会显示“立即播放”按钮
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{
+ const value = upstream.headers.get(name);
+ if (value) responseHeaders.set(name, value);
+ });
+ responseHeaders.set('Cache-Control', 'private, no-store');
+
+ return new Response(upstream.body, {
+ status: range && upstream.headers.get('content-range') ? 206 : 200,
+ headers: responseHeaders,
+ });
+ } catch (error) {
+ return NextResponse.json(
+ { error: error instanceof Error ? error.message : '百度网盘代理失败' },
+ { status: 500 }
+ );
+ }
+}
diff --git a/src/app/api/source-detail/route.ts b/src/app/api/source-detail/route.ts
index 4385ed6..c53d7b4 100644
--- a/src/app/api/source-detail/route.ts
+++ b/src/app/api/source-detail/route.ts
@@ -6,13 +6,19 @@ import { getAuthInfoFromCookie } from '@/lib/auth';
import { getAvailableApiSites, getCacheTime, getConfig } from '@/lib/config';
import { getDetailFromApiV2 } from '@/lib/downstream';
import { getProxyToken } from '@/lib/emby-token';
+import {
+ createBaiduNetdiskSession,
+ getBaiduNetdiskSession,
+ parseBaiduNetdiskId,
+ refreshBaiduNetdiskSession,
+} from '@/lib/netdisk/baidu-session-cache';
import {
createMobileNetdiskSession,
getMobileNetdiskSession,
parseMobileNetdiskId,
refreshMobileNetdiskSession,
} from '@/lib/netdisk/mobile-session-cache';
-import { LEGACY_QUARK_TEMP_SOURCE, NETDISK_MOBILE_SOURCE, NETDISK_QUARK_SOURCE, normalizeNetdiskSource } from '@/lib/netdisk/source';
+import { LEGACY_QUARK_TEMP_SOURCE, NETDISK_BAIDU_SOURCE, NETDISK_MOBILE_SOURCE, NETDISK_QUARK_SOURCE, normalizeNetdiskSource } from '@/lib/netdisk/source';
import {
executeSavedSourceScript,
normalizeScriptDetailResult,
@@ -354,6 +360,76 @@ export async function GET(request: NextRequest) {
}
}
+ if (sourceCode === NETDISK_BAIDU_SOURCE) {
+ try {
+ const config = await getConfig();
+ const baiduConfig = config.NetDiskConfig?.Baidu;
+ if (!baiduConfig?.Enabled || !baiduConfig.Cookie) {
+ throw new Error('百度网盘未配置或未启用');
+ }
+
+ let session = refreshBaiduNetdiskSession(id) || getBaiduNetdiskSession(id);
+ if (!session) {
+ const payload = parseBaiduNetdiskId(id);
+ const { listBaiduShareVideos } = await import('@/lib/netdisk/baidu.client');
+ const result = await listBaiduShareVideos(payload.shareUrl, baiduConfig.Cookie, payload.passcode || '');
+ session = createBaiduNetdiskSession({
+ title: title || result.title,
+ shareUrl: payload.shareUrl,
+ passcode: payload.passcode,
+ files: result.files,
+ meta: result.meta,
+ cookie: result.cookie,
+ });
+ }
+ if (!session) {
+ throw new Error('百度网盘播放信息恢复失败');
+ }
+ const baiduSession = session;
+ const { parseVideoFileName } = await import('@/lib/video-parser');
+ const parsedFiles = baiduSession.files
+ .map((file, index) => {
+ const parsed = parseVideoFileName(file.name);
+ return {
+ ...file,
+ originalIndex: index,
+ sortEpisode: parsed.episode || index + 1,
+ isOVA: parsed.isOVA,
+ displayTitle:
+ parsed.title || (parsed.episode ? `第${parsed.episode}集` : file.name),
+ };
+ })
+ .sort((a, b) => {
+ if (a.isOVA && !b.isOVA) return 1;
+ if (!a.isOVA && b.isOVA) return -1;
+ return a.sortEpisode !== b.sortEpisode
+ ? a.sortEpisode - b.sortEpisode
+ : a.name.localeCompare(b.name, 'zh-Hans-CN', { numeric: true, sensitivity: 'base' });
+ });
+
+ return NextResponse.json({
+ source: NETDISK_BAIDU_SOURCE,
+ source_name: '百度网盘',
+ id: baiduSession.id,
+ title: title || baiduSession.title,
+ poster: '',
+ year: '',
+ douban_id: 0,
+ desc: `百度网盘分享:${baiduSession.shareUrl}`,
+ episodes: parsedFiles.map((file) => (
+ `/api/netdisk/baidu/play?id=${encodeURIComponent(baiduSession.id)}&episodeIndex=${file.originalIndex}`
+ )),
+ episodes_titles: parsedFiles.map((file) => file.displayTitle),
+ proxyMode: false,
+ });
+ } catch (error) {
+ return NextResponse.json(
+ { error: (error as Error).message },
+ { status: 500 }
+ );
+ }
+ }
+
if (sourceCode === NETDISK_QUARK_SOURCE || sourceCode === LEGACY_QUARK_TEMP_SOURCE) {
try {
const config = await getConfig();
diff --git a/src/app/play/page.tsx b/src/app/play/page.tsx
index f691ee4..b958905 100644
--- a/src/app/play/page.tsx
+++ b/src/app/play/page.tsx
@@ -2504,6 +2504,7 @@ function PlayPageClient() {
const isSpecialLazyPlayUrl =
isXiaoyaLazyPlayUrl ||
newUrl.startsWith('/api/openlist/play') ||
+ newUrl.startsWith('/api/netdisk/baidu/play') ||
newUrl.startsWith('/api/source-script/play');
if (isSpecialLazyPlayUrl) {
diff --git a/src/components/PansouSearch.tsx b/src/components/PansouSearch.tsx
index a249f00..39197c5 100644
--- a/src/components/PansouSearch.tsx
+++ b/src/components/PansouSearch.tsx
@@ -163,7 +163,13 @@ export default function PansouSearch({
const handleNetdiskInstantPlay = async (cloudType: string, link: PansouLink) => {
try {
setPlayingUrl(link.url);
- const response = await fetch(cloudType === 'mobile' ? '/api/netdisk/mobile/instant-play' : '/api/netdisk/quark/instant-play', {
+ const instantPlayApi =
+ cloudType === 'mobile'
+ ? '/api/netdisk/mobile/instant-play'
+ : cloudType === 'baidu'
+ ? '/api/netdisk/baidu/instant-play'
+ : '/api/netdisk/quark/instant-play';
+ const response = await fetch(instantPlayApi, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
@@ -181,7 +187,7 @@ export default function PansouSearch({
}
router.push(
- `/play?source=${encodeURIComponent(data.source || (cloudType === 'mobile' ? 'netdisk-mobile' : 'netdisk-quark'))}&id=${encodeURIComponent(data.id)}&title=${encodeURIComponent(data.title || keyword)}`
+ `/play?source=${encodeURIComponent(data.source || (cloudType === 'mobile' ? 'netdisk-mobile' : cloudType === 'baidu' ? 'netdisk-baidu' : 'netdisk-quark'))}&id=${encodeURIComponent(data.id)}&title=${encodeURIComponent(data.title || keyword)}`
);
} catch (err: any) {
setToast({
@@ -339,7 +345,7 @@ export default function PansouSearch({
{/* 操作按钮 */}
- {(cloudType === 'quark' || cloudType === 'mobile') && (
+ {(cloudType === 'quark' || cloudType === 'mobile' || cloudType === 'baidu') && (
<>