112 lines
4.2 KiB
JavaScript
112 lines
4.2 KiB
JavaScript
|
|
const rateLimitMap = new Map();
|
||
|
|
|
||
|
|
function checkRateLimit(userId, limit = 5, windowMs = 1000) {
|
||
|
|
const now = Date.now();
|
||
|
|
const key = userId;
|
||
|
|
const entry = rateLimitMap.get(key);
|
||
|
|
|
||
|
|
if (!entry || now > entry.resetTime) {
|
||
|
|
rateLimitMap.set(key, { count: 1, resetTime: now + windowMs });
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (entry.count >= limit) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
entry.count++;
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
import { getSubsiteByHost } from '../../utils/subsites.js';
|
||
|
|
|
||
|
|
export async function GET({ request }) {
|
||
|
|
const host = request.headers.get('host');
|
||
|
|
const subsite = getSubsiteByHost(host);
|
||
|
|
|
||
|
|
if (!subsite || subsite.short !== 'req') {
|
||
|
|
return new Response('Not found', { status: 404 });
|
||
|
|
}
|
||
|
|
|
||
|
|
let userId = request.headers.get('cookie')?.split(';').find(c => c.trim().startsWith('nonce='))?.split('=')[1];
|
||
|
|
const hadCookie = !!userId;
|
||
|
|
if (!userId) {
|
||
|
|
userId = crypto.randomUUID();
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!checkRateLimit(userId)) {
|
||
|
|
const response = new Response(JSON.stringify({ error: 'Rate limit exceeded' }), { status: 429, headers: { 'Content-Type': 'application/json' } });
|
||
|
|
if (!hadCookie) {
|
||
|
|
response.headers.set('Set-Cookie', `nonce=${userId}; HttpOnly; Path=/; Max-Age=31536000`);
|
||
|
|
}
|
||
|
|
return response;
|
||
|
|
}
|
||
|
|
|
||
|
|
const TMDB_API_KEY = import.meta.env.TMDB_API_KEY;
|
||
|
|
|
||
|
|
if (!TMDB_API_KEY) {
|
||
|
|
console.error('TMDB_API_KEY not set');
|
||
|
|
const response = new Response(JSON.stringify([]), { status: 500, headers: { 'Content-Type': 'application/json' } });
|
||
|
|
if (!hadCookie) {
|
||
|
|
response.headers.set('Set-Cookie', `nonce=${userId}; HttpOnly; Path=/; Max-Age=31536000`);
|
||
|
|
}
|
||
|
|
return response;
|
||
|
|
}
|
||
|
|
|
||
|
|
const url = new URL(request.url);
|
||
|
|
const q = url.searchParams.get('q');
|
||
|
|
|
||
|
|
if (!q || typeof q !== 'string' || !q.trim()) {
|
||
|
|
const response = new Response(JSON.stringify([]), { status: 200, headers: { 'Content-Type': 'application/json' } });
|
||
|
|
if (!hadCookie) {
|
||
|
|
response.headers.set('Set-Cookie', `nonce=${userId}; HttpOnly; Path=/; Max-Age=31536000`);
|
||
|
|
}
|
||
|
|
return response;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (q.length > 100) {
|
||
|
|
const response = new Response(JSON.stringify([]), { status: 200, headers: { 'Content-Type': 'application/json' } });
|
||
|
|
if (!hadCookie) {
|
||
|
|
response.headers.set('Set-Cookie', `nonce=${userId}; HttpOnly; Path=/; Max-Age=31536000`);
|
||
|
|
}
|
||
|
|
return response;
|
||
|
|
}
|
||
|
|
|
||
|
|
try {
|
||
|
|
const apiResponse = await fetch(`https://api.themoviedb.org/3/search/multi?api_key=${TMDB_API_KEY}&query=${encodeURIComponent(q)}`);
|
||
|
|
if (!apiResponse.ok) {
|
||
|
|
throw new Error(`TMDB API error: ${apiResponse.status}`);
|
||
|
|
}
|
||
|
|
const data = await apiResponse.json();
|
||
|
|
const seen = new Set();
|
||
|
|
const filtered = data.results
|
||
|
|
.filter(item => {
|
||
|
|
if (item.media_type !== 'movie' && item.media_type !== 'tv') return false;
|
||
|
|
const key = `${item.media_type}-${item.title || item.name}-${item.release_date?.split('-')[0] || item.first_air_date?.split('-')[0] || ''}`;
|
||
|
|
if (seen.has(key)) return false;
|
||
|
|
seen.add(key);
|
||
|
|
return true;
|
||
|
|
})
|
||
|
|
.slice(0, 10) // Limit to 10 suggestions
|
||
|
|
.map(item => ({
|
||
|
|
label: item.title || item.name,
|
||
|
|
value: item.title || item.name,
|
||
|
|
year: item.release_date?.split('-')[0] || item.first_air_date?.split('-')[0],
|
||
|
|
mediaType: item.media_type,
|
||
|
|
overview: item.overview,
|
||
|
|
poster_path: item.poster_path,
|
||
|
|
}));
|
||
|
|
const response = new Response(JSON.stringify(filtered), { headers: { 'Content-Type': 'application/json' } });
|
||
|
|
if (!hadCookie) {
|
||
|
|
response.headers.set('Set-Cookie', `nonce=${userId}; HttpOnly; Path=/; Max-Age=31536000`);
|
||
|
|
}
|
||
|
|
return response;
|
||
|
|
} catch (error) {
|
||
|
|
console.error('Error fetching suggestions:', error);
|
||
|
|
const response = new Response(JSON.stringify([]), { status: 500, headers: { 'Content-Type': 'application/json' } });
|
||
|
|
if (!hadCookie) {
|
||
|
|
response.headers.set('Set-Cookie', `nonce=${userId}; HttpOnly; Path=/; Max-Age=31536000`);
|
||
|
|
}
|
||
|
|
return response;
|
||
|
|
}
|
||
|
|
}
|