import { NextResponse } from 'next/server'; import { API_CONFIG, ApiSite, getApiSites, getCacheTime } from '@/lib/config'; import { SearchResult } from '@/lib/types'; import { cleanHtmlTags } from '@/lib/utils'; export const runtime = 'edge'; // 根据环境变量决定最大搜索页数,默认 5 const MAX_SEARCH_PAGES: number = Number(process.env.NEXT_PUBLIC_SEARCH_MAX_PAGE) || 5; interface ApiSearchItem { vod_id: string; vod_name: string; vod_pic: string; vod_remarks?: string; vod_play_url?: string; vod_class?: string; vod_year?: string; vod_content?: string; vod_douban_id?: number; type_name?: string; } async function searchFromApi( apiSite: ApiSite, query: string ): Promise { try { const apiBaseUrl = apiSite.api; const apiUrl = apiBaseUrl + API_CONFIG.search.path + encodeURIComponent(query); const apiName = apiSite.name; // 添加超时处理 const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), 8000); const response = await fetch(apiUrl, { headers: API_CONFIG.search.headers, signal: controller.signal, }); clearTimeout(timeoutId); if (!response.ok) { return []; } const data = await response.json(); if ( !data || !data.list || !Array.isArray(data.list) || data.list.length === 0 ) { return []; } // 处理第一页结果 const results = data.list.map((item: ApiSearchItem) => { let episodes: string[] = []; // 使用正则表达式从 vod_play_url 提取 m3u8 链接 if (item.vod_play_url) { const m3u8Regex = /\$(https?:\/\/[^"'\s]+?\.m3u8)/g; // 先用 $$$ 分割 const vod_play_url_array = item.vod_play_url.split('$$$'); // 对每个分片做匹配,取匹配到最多的作为结果 vod_play_url_array.forEach((url: string) => { const matches = url.match(m3u8Regex) || []; if (matches.length > episodes.length) { episodes = matches; } }); } episodes = Array.from(new Set(episodes)).map((link: string) => { link = link.substring(1); // 去掉开头的 $ const parenIndex = link.indexOf('('); return parenIndex > 0 ? link.substring(0, parenIndex) : link; }); return { id: item.vod_id, title: item.vod_name.trim().replace(/\s+/g, ' '), poster: item.vod_pic, episodes, source: apiSite.key, source_name: apiName, class: item.vod_class, year: item.vod_year ? item.vod_year.match(/\d{4}/)?.[0] || '' : '', desc: cleanHtmlTags(item.vod_content || ''), type_name: item.type_name, douban_id: item.vod_douban_id, }; }); // 获取总页数 const pageCount = data.pagecount || 1; // 确定需要获取的额外页数 const pagesToFetch = Math.min(pageCount - 1, MAX_SEARCH_PAGES - 1); // 如果有额外页数,获取更多页的结果 if (pagesToFetch > 0) { const additionalPagePromises = []; for (let page = 2; page <= pagesToFetch + 1; page++) { const pageUrl = apiBaseUrl + API_CONFIG.search.pagePath .replace('{query}', encodeURIComponent(query)) .replace('{page}', page.toString()); const pagePromise = (async () => { try { const pageController = new AbortController(); const pageTimeoutId = setTimeout( () => pageController.abort(), 8000 ); const pageResponse = await fetch(pageUrl, { headers: API_CONFIG.search.headers, signal: pageController.signal, }); clearTimeout(pageTimeoutId); if (!pageResponse.ok) return []; const pageData = await pageResponse.json(); if (!pageData || !pageData.list || !Array.isArray(pageData.list)) return []; return pageData.list.map((item: ApiSearchItem) => { let episodes: string[] = []; // 使用正则表达式从 vod_play_url 提取 m3u8 链接 if (item.vod_play_url) { const m3u8Regex = /\$(https?:\/\/[^"'\s]+?\.m3u8)/g; episodes = item.vod_play_url.match(m3u8Regex) || []; } episodes = Array.from(new Set(episodes)).map((link: string) => { link = link.substring(1); // 去掉开头的 $ const parenIndex = link.indexOf('('); return parenIndex > 0 ? link.substring(0, parenIndex) : link; }); return { id: item.vod_id, title: item.vod_name.trim().replace(/\s+/g, ' '), poster: item.vod_pic, episodes, source: apiSite.key, source_name: apiName, class: item.vod_class, year: item.vod_year ? item.vod_year.match(/\d{4}/)?.[0] || '' : '', desc: cleanHtmlTags(item.vod_content || ''), type_name: item.type_name, douban_id: item.vod_douban_id, }; }); } catch (error) { return []; } })(); additionalPagePromises.push(pagePromise); } // 等待所有额外页的结果 const additionalResults = await Promise.all(additionalPagePromises); // 合并所有页的结果 additionalResults.forEach((pageResults) => { if (pageResults.length > 0) { results.push(...pageResults); } }); } return results; } catch (error) { return []; } } export async function GET(request: Request) { const { searchParams } = new URL(request.url); const query = searchParams.get('q'); if (!query) { const cacheTime = getCacheTime(); return NextResponse.json( { results: [] }, { headers: { 'Cache-Control': `public, max-age=${cacheTime}`, }, } ); } const apiSites = getApiSites(); const searchPromises = apiSites.map((site) => searchFromApi(site, query)); try { const results = await Promise.all(searchPromises); const flattenedResults = results.flat(); const cacheTime = getCacheTime(); return NextResponse.json( { results: flattenedResults }, { headers: { 'Cache-Control': `public, max-age=${cacheTime}`, }, } ); } catch (error) { return NextResponse.json({ error: '搜索失败' }, { status: 500 }); } }