openlist和emby异步搜索
This commit is contained in:
+33
-28
@@ -51,13 +51,12 @@ export async function GET(request: NextRequest) {
|
|||||||
config.EmbyConfig?.UserId
|
config.EmbyConfig?.UserId
|
||||||
);
|
);
|
||||||
|
|
||||||
// 搜索 Emby(如果配置了)
|
// 搜索 Emby(如果配置了)- 异步带超时
|
||||||
let embyResults: any[] = [];
|
const embyPromise = hasEmby
|
||||||
if (hasEmby) {
|
? Promise.race([
|
||||||
try {
|
(async () => {
|
||||||
const { EmbyClient } = await import('@/lib/emby.client');
|
const { EmbyClient } = await import('@/lib/emby.client');
|
||||||
const client = new EmbyClient(config.EmbyConfig!);
|
const client = new EmbyClient(config.EmbyConfig!);
|
||||||
|
|
||||||
const searchResult = await client.getItems({
|
const searchResult = await client.getItems({
|
||||||
searchTerm: query,
|
searchTerm: query,
|
||||||
IncludeItemTypes: 'Movie,Series',
|
IncludeItemTypes: 'Movie,Series',
|
||||||
@@ -65,8 +64,7 @@ export async function GET(request: NextRequest) {
|
|||||||
Fields: 'Overview,ProductionYear',
|
Fields: 'Overview,ProductionYear',
|
||||||
Limit: 50,
|
Limit: 50,
|
||||||
});
|
});
|
||||||
|
return searchResult.Items.map((item) => ({
|
||||||
embyResults = searchResult.Items.map((item) => ({
|
|
||||||
id: item.Id,
|
id: item.Id,
|
||||||
source: 'emby',
|
source: 'emby',
|
||||||
source_name: 'Emby',
|
source_name: 'Emby',
|
||||||
@@ -79,15 +77,20 @@ export async function GET(request: NextRequest) {
|
|||||||
type_name: item.Type === 'Movie' ? '电影' : '电视剧',
|
type_name: item.Type === 'Movie' ? '电影' : '电视剧',
|
||||||
douban_id: 0,
|
douban_id: 0,
|
||||||
}));
|
}));
|
||||||
} catch (error) {
|
})(),
|
||||||
|
new Promise<any[]>((_, reject) =>
|
||||||
|
setTimeout(() => reject(new Error('Emby timeout')), 20000)
|
||||||
|
),
|
||||||
|
]).catch((error) => {
|
||||||
console.error('[Search] 搜索 Emby 失败:', error);
|
console.error('[Search] 搜索 Emby 失败:', error);
|
||||||
}
|
return [];
|
||||||
}
|
})
|
||||||
|
: Promise.resolve([]);
|
||||||
|
|
||||||
// 搜索 OpenList(如果配置了)
|
// 搜索 OpenList(如果配置了)- 异步带超时
|
||||||
let openlistResults: any[] = [];
|
const openlistPromise = hasOpenList
|
||||||
if (hasOpenList) {
|
? Promise.race([
|
||||||
try {
|
(async () => {
|
||||||
const { getCachedMetaInfo, setCachedMetaInfo } = await import('@/lib/openlist-cache');
|
const { getCachedMetaInfo, setCachedMetaInfo } = await import('@/lib/openlist-cache');
|
||||||
const { getTMDBImageUrl } = await import('@/lib/tmdb.search');
|
const { getTMDBImageUrl } = await import('@/lib/tmdb.search');
|
||||||
const { db } = await import('@/lib/db');
|
const { db } = await import('@/lib/db');
|
||||||
@@ -95,9 +98,7 @@ export async function GET(request: NextRequest) {
|
|||||||
const rootPath = config.OpenListConfig!.RootPath || '/';
|
const rootPath = config.OpenListConfig!.RootPath || '/';
|
||||||
let metaInfo = getCachedMetaInfo(rootPath);
|
let metaInfo = getCachedMetaInfo(rootPath);
|
||||||
|
|
||||||
// 如果没有缓存,尝试从数据库读取
|
|
||||||
if (!metaInfo) {
|
if (!metaInfo) {
|
||||||
try {
|
|
||||||
const metainfoJson = await db.getGlobalValue('video.metainfo');
|
const metainfoJson = await db.getGlobalValue('video.metainfo');
|
||||||
if (metainfoJson) {
|
if (metainfoJson) {
|
||||||
metaInfo = JSON.parse(metainfoJson);
|
metaInfo = JSON.parse(metainfoJson);
|
||||||
@@ -105,13 +106,10 @@ export async function GET(request: NextRequest) {
|
|||||||
setCachedMetaInfo(rootPath, metaInfo);
|
setCachedMetaInfo(rootPath, metaInfo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
|
||||||
console.error('[Search] 从数据库读取 metainfo 失败:', error);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (metaInfo && metaInfo.folders) {
|
if (metaInfo && metaInfo.folders) {
|
||||||
openlistResults = Object.entries(metaInfo.folders)
|
return Object.entries(metaInfo.folders)
|
||||||
.filter(([folderName, info]: [string, any]) => {
|
.filter(([folderName, info]: [string, any]) => {
|
||||||
const matchFolder = folderName.toLowerCase().includes(query.toLowerCase());
|
const matchFolder = folderName.toLowerCase().includes(query.toLowerCase());
|
||||||
const matchTitle = info.title.toLowerCase().includes(query.toLowerCase());
|
const matchTitle = info.title.toLowerCase().includes(query.toLowerCase());
|
||||||
@@ -131,10 +129,16 @@ export async function GET(request: NextRequest) {
|
|||||||
douban_id: 0,
|
douban_id: 0,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
} catch (error) {
|
return [];
|
||||||
|
})(),
|
||||||
|
new Promise<any[]>((_, reject) =>
|
||||||
|
setTimeout(() => reject(new Error('OpenList timeout')), 20000)
|
||||||
|
),
|
||||||
|
]).catch((error) => {
|
||||||
console.error('[Search] 搜索 OpenList 失败:', error);
|
console.error('[Search] 搜索 OpenList 失败:', error);
|
||||||
}
|
return [];
|
||||||
}
|
})
|
||||||
|
: Promise.resolve([]);
|
||||||
|
|
||||||
// 添加超时控制和错误处理,避免慢接口拖累整体响应
|
// 添加超时控制和错误处理,避免慢接口拖累整体响应
|
||||||
const searchPromises = apiSites.map((site) =>
|
const searchPromises = apiSites.map((site) =>
|
||||||
@@ -150,11 +154,12 @@ export async function GET(request: NextRequest) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const results = await Promise.allSettled(searchPromises);
|
const [embyResults, openlistResults, ...apiResults] = await Promise.all([
|
||||||
const successResults = results
|
embyPromise,
|
||||||
.filter((result) => result.status === 'fulfilled')
|
openlistPromise,
|
||||||
.map((result) => (result as PromiseFulfilledResult<any>).value);
|
...searchPromises,
|
||||||
let flattenedResults = [...embyResults, ...openlistResults, ...successResults.flat()];
|
]);
|
||||||
|
let flattenedResults = [...embyResults, ...openlistResults, ...apiResults.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 || '';
|
||||||
|
|||||||
@@ -89,12 +89,12 @@ export async function GET(request: NextRequest) {
|
|||||||
let completedSources = 0;
|
let completedSources = 0;
|
||||||
const allResults: any[] = [];
|
const allResults: any[] = [];
|
||||||
|
|
||||||
// 搜索 Emby(如果配置了)
|
// 搜索 Emby(如果配置了)- 异步带超时
|
||||||
if (hasEmby) {
|
if (hasEmby) {
|
||||||
try {
|
Promise.race([
|
||||||
|
(async () => {
|
||||||
const { EmbyClient } = await import('@/lib/emby.client');
|
const { EmbyClient } = await import('@/lib/emby.client');
|
||||||
const client = new EmbyClient(config.EmbyConfig!);
|
const client = new EmbyClient(config.EmbyConfig!);
|
||||||
|
|
||||||
const searchResult = await client.getItems({
|
const searchResult = await client.getItems({
|
||||||
searchTerm: query,
|
searchTerm: query,
|
||||||
IncludeItemTypes: 'Movie,Series',
|
IncludeItemTypes: 'Movie,Series',
|
||||||
@@ -102,8 +102,7 @@ export async function GET(request: NextRequest) {
|
|||||||
Fields: 'Overview,ProductionYear',
|
Fields: 'Overview,ProductionYear',
|
||||||
Limit: 50,
|
Limit: 50,
|
||||||
});
|
});
|
||||||
|
return searchResult.Items.map((item) => ({
|
||||||
const embyResults = searchResult.Items.map((item) => ({
|
|
||||||
id: item.Id,
|
id: item.Id,
|
||||||
source: 'emby',
|
source: 'emby',
|
||||||
source_name: 'Emby',
|
source_name: 'Emby',
|
||||||
@@ -116,9 +115,13 @@ export async function GET(request: NextRequest) {
|
|||||||
type_name: item.Type === 'Movie' ? '电影' : '电视剧',
|
type_name: item.Type === 'Movie' ? '电影' : '电视剧',
|
||||||
douban_id: 0,
|
douban_id: 0,
|
||||||
}));
|
}));
|
||||||
|
})(),
|
||||||
|
new Promise((_, reject) =>
|
||||||
|
setTimeout(() => reject(new Error('Emby timeout')), 20000)
|
||||||
|
),
|
||||||
|
])
|
||||||
|
.then((embyResults) => {
|
||||||
completedSources++;
|
completedSources++;
|
||||||
|
|
||||||
if (!streamClosed) {
|
if (!streamClosed) {
|
||||||
const sourceEvent = `data: ${JSON.stringify({
|
const sourceEvent = `data: ${JSON.stringify({
|
||||||
type: 'source_result',
|
type: 'source_result',
|
||||||
@@ -127,20 +130,18 @@ export async function GET(request: NextRequest) {
|
|||||||
results: embyResults,
|
results: embyResults,
|
||||||
timestamp: Date.now()
|
timestamp: Date.now()
|
||||||
})}\n\n`;
|
})}\n\n`;
|
||||||
|
|
||||||
if (!safeEnqueue(encoder.encode(sourceEvent))) {
|
if (!safeEnqueue(encoder.encode(sourceEvent))) {
|
||||||
streamClosed = true;
|
streamClosed = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (embyResults.length > 0) {
|
if (embyResults.length > 0) {
|
||||||
allResults.push(...embyResults);
|
allResults.push(...embyResults);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
})
|
||||||
|
.catch((error) => {
|
||||||
console.error('[Search WS] 搜索 Emby 失败:', error);
|
console.error('[Search WS] 搜索 Emby 失败:', error);
|
||||||
completedSources++;
|
completedSources++;
|
||||||
|
|
||||||
if (!streamClosed) {
|
if (!streamClosed) {
|
||||||
const errorEvent = `data: ${JSON.stringify({
|
const errorEvent = `data: ${JSON.stringify({
|
||||||
type: 'source_error',
|
type: 'source_error',
|
||||||
@@ -149,18 +150,15 @@ export async function GET(request: NextRequest) {
|
|||||||
error: error instanceof Error ? error.message : '搜索失败',
|
error: error instanceof Error ? error.message : '搜索失败',
|
||||||
timestamp: Date.now()
|
timestamp: Date.now()
|
||||||
})}\n\n`;
|
})}\n\n`;
|
||||||
|
safeEnqueue(encoder.encode(errorEvent));
|
||||||
if (!safeEnqueue(encoder.encode(errorEvent))) {
|
|
||||||
streamClosed = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 搜索 OpenList(如果配置了)
|
// 搜索 OpenList(如果配置了)- 异步带超时
|
||||||
if (hasOpenList) {
|
if (hasOpenList) {
|
||||||
try {
|
Promise.race([
|
||||||
|
(async () => {
|
||||||
const { getCachedMetaInfo, setCachedMetaInfo } = await import('@/lib/openlist-cache');
|
const { getCachedMetaInfo, setCachedMetaInfo } = await import('@/lib/openlist-cache');
|
||||||
const { getTMDBImageUrl } = await import('@/lib/tmdb.search');
|
const { getTMDBImageUrl } = await import('@/lib/tmdb.search');
|
||||||
const { db } = await import('@/lib/db');
|
const { db } = await import('@/lib/db');
|
||||||
@@ -168,9 +166,7 @@ export async function GET(request: NextRequest) {
|
|||||||
const rootPath = config.OpenListConfig!.RootPath || '/';
|
const rootPath = config.OpenListConfig!.RootPath || '/';
|
||||||
let metaInfo = getCachedMetaInfo(rootPath);
|
let metaInfo = getCachedMetaInfo(rootPath);
|
||||||
|
|
||||||
// 如果没有缓存,尝试从数据库读取
|
|
||||||
if (!metaInfo) {
|
if (!metaInfo) {
|
||||||
try {
|
|
||||||
const metainfoJson = await db.getGlobalValue('video.metainfo');
|
const metainfoJson = await db.getGlobalValue('video.metainfo');
|
||||||
if (metainfoJson) {
|
if (metainfoJson) {
|
||||||
metaInfo = JSON.parse(metainfoJson);
|
metaInfo = JSON.parse(metainfoJson);
|
||||||
@@ -178,13 +174,10 @@ export async function GET(request: NextRequest) {
|
|||||||
setCachedMetaInfo(rootPath, metaInfo);
|
setCachedMetaInfo(rootPath, metaInfo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
|
||||||
console.error('[Search WS] 从数据库读取 metainfo 失败:', error);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (metaInfo && metaInfo.folders) {
|
if (metaInfo && metaInfo.folders) {
|
||||||
const openlistResults = Object.entries(metaInfo.folders)
|
return Object.entries(metaInfo.folders)
|
||||||
.filter(([key, info]: [string, any]) => {
|
.filter(([key, info]: [string, any]) => {
|
||||||
const matchFolder = info.folderName.toLowerCase().includes(query.toLowerCase());
|
const matchFolder = info.folderName.toLowerCase().includes(query.toLowerCase());
|
||||||
const matchTitle = info.title.toLowerCase().includes(query.toLowerCase());
|
const matchTitle = info.title.toLowerCase().includes(query.toLowerCase());
|
||||||
@@ -203,9 +196,15 @@ export async function GET(request: NextRequest) {
|
|||||||
type_name: info.media_type === 'movie' ? '电影' : '电视剧',
|
type_name: info.media_type === 'movie' ? '电影' : '电视剧',
|
||||||
douban_id: 0,
|
douban_id: 0,
|
||||||
}));
|
}));
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
})(),
|
||||||
|
new Promise((_, reject) =>
|
||||||
|
setTimeout(() => reject(new Error('OpenList timeout')), 20000)
|
||||||
|
),
|
||||||
|
])
|
||||||
|
.then((openlistResults) => {
|
||||||
completedSources++;
|
completedSources++;
|
||||||
|
|
||||||
if (!streamClosed) {
|
if (!streamClosed) {
|
||||||
const sourceEvent = `data: ${JSON.stringify({
|
const sourceEvent = `data: ${JSON.stringify({
|
||||||
type: 'source_result',
|
type: 'source_result',
|
||||||
@@ -214,38 +213,18 @@ export async function GET(request: NextRequest) {
|
|||||||
results: openlistResults,
|
results: openlistResults,
|
||||||
timestamp: Date.now()
|
timestamp: Date.now()
|
||||||
})}\n\n`;
|
})}\n\n`;
|
||||||
|
|
||||||
if (!safeEnqueue(encoder.encode(sourceEvent))) {
|
if (!safeEnqueue(encoder.encode(sourceEvent))) {
|
||||||
streamClosed = true;
|
streamClosed = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (openlistResults.length > 0) {
|
if (openlistResults.length > 0) {
|
||||||
allResults.push(...openlistResults);
|
allResults.push(...openlistResults);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
})
|
||||||
completedSources++;
|
.catch((error) => {
|
||||||
|
|
||||||
if (!streamClosed) {
|
|
||||||
const sourceEvent = `data: ${JSON.stringify({
|
|
||||||
type: 'source_result',
|
|
||||||
source: 'openlist',
|
|
||||||
sourceName: '私人影库',
|
|
||||||
results: [],
|
|
||||||
timestamp: Date.now()
|
|
||||||
})}\n\n`;
|
|
||||||
|
|
||||||
if (!safeEnqueue(encoder.encode(sourceEvent))) {
|
|
||||||
streamClosed = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('[Search WS] 搜索 OpenList 失败:', error);
|
console.error('[Search WS] 搜索 OpenList 失败:', error);
|
||||||
completedSources++;
|
completedSources++;
|
||||||
|
|
||||||
if (!streamClosed) {
|
if (!streamClosed) {
|
||||||
const errorEvent = `data: ${JSON.stringify({
|
const errorEvent = `data: ${JSON.stringify({
|
||||||
type: 'source_error',
|
type: 'source_error',
|
||||||
@@ -254,13 +233,9 @@ export async function GET(request: NextRequest) {
|
|||||||
error: error instanceof Error ? error.message : '搜索失败',
|
error: error instanceof Error ? error.message : '搜索失败',
|
||||||
timestamp: Date.now()
|
timestamp: Date.now()
|
||||||
})}\n\n`;
|
})}\n\n`;
|
||||||
|
safeEnqueue(encoder.encode(errorEvent));
|
||||||
if (!safeEnqueue(encoder.encode(errorEvent))) {
|
|
||||||
streamClosed = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 为每个源创建搜索 Promise
|
// 为每个源创建搜索 Promise
|
||||||
|
|||||||
Reference in New Issue
Block a user