"use strict"; const { withBrowser, navigateWithHumanBehavior, extractPricesFromPage } = require("./baseCrawler"); /** * Formats a date as YYYYMMDD for Naver Flights URLs. */ function getDStr(date) { const d = new Date(date); const yy = d.getFullYear(); const mm = String(d.getMonth() + 1).padStart(2, "0"); const dd = String(d.getDate()).padStart(2, "0"); return `${yy}${mm}${dd}`; } /** * Maps internal cabin class to Naver fareType code. * economy → Y, premium_economy → P, business → C, first → F */ function getFareType(byCabin) { if (byCabin.first > 0) return "F"; if (byCabin.business > 0) return "C"; if (byCabin.premium_economy > 0) return "P"; return "Y"; } /** * Builds a Naver Flights URL from structured search params. * * Round-trip format: * /flights/international/ICN-NRT-20260315/NRT-ICN-20260322?adult=1&fareType=Y * * Multi-city format: * /flights/multi?itinerary=ICN:NRT:20260315,NRT:ICN:20260322&adult=1&fareType=Y * * One-way format: * /flights/international/ICN-NRT-20260315?adult=1&fareType=Y */ function buildNaverUrl(searchParams) { const segments = searchParams.segments || []; if (segments.length === 0) return "https://flight.naver.com"; const tripType = searchParams.tripType; const adults = searchParams.passengers?.total || 1; const byCabin = searchParams.passengers?.byCabin || {}; const fareType = getFareType(byCabin); const today = new Date(); const tomorrow = new Date(today); tomorrow.setDate(tomorrow.getDate() + 1); if (tripType === "round_trip") { const from = segments[0].from.toUpperCase(); const to = segments[0].to.toUpperCase(); const departDate = searchParams.departureDateWindow?.from || tomorrow; const d1 = getDStr(departDate); // Return date: use stayDurationDays if available, default to 7 days const stayDays = searchParams.stayDurationDays?.minDays || 7; const returnDate = new Date(departDate); returnDate.setDate(returnDate.getDate() + stayDays); const d2 = getDStr(returnDate); // Naver round-trip: outbound segment / return segment (origin/dest reversed) return `https://flight.naver.com/flights/international/${from}-${to}-${d1}/${to}-${from}-${d2}?adult=${adults}&fareType=${fareType}`; } if (tripType === "multi_city") { // Naver multi-city: /flights/multi?itinerary=ICN:NRT:20260315,NRT:ICN:20260322 let currentBaseDate = new Date(searchParams.departureDateWindow?.from || tomorrow); const itineraryParts = []; for (const seg of segments) { itineraryParts.push(`${seg.from.toUpperCase()}:${seg.to.toUpperCase()}:${getDStr(currentBaseDate)}`); currentBaseDate.setDate(currentBaseDate.getDate() + 3); } return `https://flight.naver.com/flights/multi?itinerary=${itineraryParts.join(",")}&adult=${adults}&fareType=${fareType}`; } // one_way const from = segments[0].from.toUpperCase(); const to = segments[0].to.toUpperCase(); const d1 = searchParams.departureDateWindow?.from ? getDStr(searchParams.departureDateWindow.from) : getDStr(tomorrow); return `https://flight.naver.com/flights/international/${from}-${to}-${d1}?adult=${adults}&fareType=${fareType}`; } async function scrapeNaver(searchParams) { const url = buildNaverUrl(searchParams); console.log(`[Naver] Navigating to ${url}`); return withBrowser(async (page) => { await navigateWithHumanBehavior(page, url); // Wait for price elements with generic selectors try { await page.waitForSelector('[class*="price"], [class*="Price"], [class*="fare"]', { timeout: 15000 }); } catch (e) { console.log("[Naver] Timeout waiting for price elements."); } // Extract prices using generic Korean won patterns const prices = await extractPricesFromPage(page); if (prices.length === 0) { console.log("[Naver] No prices found on page."); return []; } return prices.map((price, i) => ({ provider: "naver", price, currency: "KRW", metadata: { url, rank: i + 1 }, })); }); } module.exports = { scrapeNaver, buildNaverUrl };