Some checks failed
ci / ci (22, ubuntu-latest) (push) Has been cancelled
Nuxt 4 + Supabase + Flightics API. Incluye búsqueda de vuelos, inspiraciones, watchlist, tracking de precios y mapa interactivo. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
301 lines
7.1 KiB
TypeScript
301 lines
7.1 KiB
TypeScript
const BASE_URL = 'https://www.flightics.com/api/v1'
|
|
const USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/147.0.0.0 Safari/537.36'
|
|
|
|
const HEADERS = {
|
|
'Content-Type': 'application/json; charset=utf-8',
|
|
'User-Agent': USER_AGENT,
|
|
'sec-ch-ua': '"Chromium";v="147", "Not.A/Brand";v="8"',
|
|
'sec-ch-ua-mobile': '?0',
|
|
'sec-ch-ua-platform': '"macOS"',
|
|
'Referer': 'https://www.flightics.com/'
|
|
}
|
|
|
|
// --- Types ---
|
|
|
|
export interface PassengersCount {
|
|
adult: number
|
|
child: number
|
|
infant: number
|
|
}
|
|
|
|
export interface StopConfig {
|
|
locations: string[]
|
|
stayRange: { min: number, max: number }
|
|
stayDateRange: { begin: string, end: string }
|
|
continueFromAny: boolean
|
|
}
|
|
|
|
export interface SearchParams {
|
|
departures: string[]
|
|
local: string
|
|
departureDateInterval: { begin: string, end: string }
|
|
stops: StopConfig[]
|
|
endInSameLocation: boolean
|
|
maxStops: number | null
|
|
fixStopsOrder: boolean
|
|
stopLength: { min: number, max: number, isSet: boolean }
|
|
maxResults: number
|
|
passengersCount: PassengersCount
|
|
}
|
|
|
|
export interface Company {
|
|
name: string | null
|
|
code: string
|
|
icao: string
|
|
}
|
|
|
|
export interface Segment {
|
|
id: string
|
|
departureCode: string
|
|
departureCity: string
|
|
departureDate: string
|
|
departureTimestamp: number
|
|
departureDateUtc: string
|
|
departureUtcTimestamp: number
|
|
arrivalCode: string
|
|
arrivalCity: string
|
|
arrivalDate: string
|
|
arrivalTimestamp: number
|
|
arrivalDateUtc: string
|
|
arrivalUtcTimestamp: number
|
|
number: number
|
|
company: Company
|
|
isHidden: boolean
|
|
}
|
|
|
|
export interface Leg {
|
|
segments: Segment[]
|
|
}
|
|
|
|
export interface Trip {
|
|
legs: Leg[]
|
|
totalCost: number
|
|
bookingToken: string
|
|
currency: string
|
|
deepLink: string | null
|
|
}
|
|
|
|
export interface SearchResponse {
|
|
trips: Trip[]
|
|
notComplete: boolean
|
|
contractVersion: number
|
|
responseId: string
|
|
}
|
|
|
|
export interface DetailResponse {
|
|
trip: Trip
|
|
}
|
|
|
|
export interface Country {
|
|
isoCode2: string
|
|
isoCode3: string
|
|
nameEng: string
|
|
nameNative: string
|
|
phonePreselection: string
|
|
}
|
|
|
|
export interface InspirationItem {
|
|
from: string
|
|
to: string[]
|
|
minPrice: number
|
|
minStops: number
|
|
}
|
|
|
|
export interface InspirationsResponse {
|
|
items: InspirationItem[]
|
|
}
|
|
|
|
// --- API: Search ---
|
|
|
|
export async function searchTrips(params: SearchParams): Promise<SearchResponse> {
|
|
return $fetch(`${BASE_URL}/trips/search`, {
|
|
method: 'POST',
|
|
headers: HEADERS,
|
|
body: params
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Poll search until notComplete is false or maxPolls reached.
|
|
* The API returns partial results with notComplete: true.
|
|
* Re-calling with the same params fetches more results.
|
|
*/
|
|
export async function searchTripsComplete(params: SearchParams, maxPolls: number = 3): Promise<SearchResponse> {
|
|
let allTrips: Trip[] = []
|
|
let lastResponse: SearchResponse | null = null
|
|
|
|
for (let i = 0; i < maxPolls; i++) {
|
|
const res = await searchTrips(params)
|
|
allTrips = [...allTrips, ...res.trips]
|
|
lastResponse = res
|
|
if (!res.notComplete) break
|
|
// Small delay between polls
|
|
await new Promise(r => setTimeout(r, 500))
|
|
}
|
|
|
|
return {
|
|
trips: allTrips,
|
|
notComplete: lastResponse?.notComplete ?? false,
|
|
contractVersion: lastResponse?.contractVersion ?? 0,
|
|
responseId: lastResponse?.responseId ?? ''
|
|
}
|
|
}
|
|
|
|
// --- API: Trip Detail & Check ---
|
|
|
|
export async function getTripDetail(bookingToken: string, local: string, passengersCount: PassengersCount): Promise<DetailResponse> {
|
|
return $fetch(`${BASE_URL}/trip/detail`, {
|
|
method: 'POST',
|
|
headers: HEADERS,
|
|
body: { bookingToken, local, passengersCount }
|
|
})
|
|
}
|
|
|
|
export async function checkTrip(bookingToken: string, local: string, passengersCount: PassengersCount): Promise<DetailResponse> {
|
|
return $fetch(`${BASE_URL}/trip/check`, {
|
|
method: 'POST',
|
|
headers: HEADERS,
|
|
body: { bookingToken, local, passengersCount }
|
|
})
|
|
}
|
|
|
|
// --- API: Inspirations ---
|
|
|
|
export async function getInspirations(from: string, take: number = 100, locale: string = 'en'): Promise<InspirationsResponse> {
|
|
return $fetch(`${BASE_URL}/inspirations/search`, {
|
|
method: 'GET',
|
|
headers: { 'User-Agent': USER_AGENT },
|
|
query: { from, take, locale }
|
|
})
|
|
}
|
|
|
|
// --- API: Weekend Search ---
|
|
|
|
export async function searchWeekendTrips(params: SearchParams): Promise<SearchResponse> {
|
|
return $fetch(`${BASE_URL}/trips/weekend/search`, {
|
|
method: 'POST',
|
|
headers: HEADERS,
|
|
body: params
|
|
})
|
|
}
|
|
|
|
// --- API: Route Flights ---
|
|
|
|
export interface RouteFlightsResponse {
|
|
contractVersion: number
|
|
returnResults: {
|
|
trips: Trip[]
|
|
}[]
|
|
}
|
|
|
|
export async function getRouteFlights(from: string, to: string, locale: string = 'en', passengersCount?: PassengersCount): Promise<RouteFlightsResponse> {
|
|
return $fetch(`${BASE_URL}/route-flights`, {
|
|
method: 'POST',
|
|
headers: HEADERS,
|
|
body: { from, to, locale, passengersCount }
|
|
})
|
|
}
|
|
|
|
// --- API: Multi-City Inspirations ---
|
|
|
|
export interface MultiCityInspirationItem {
|
|
from: string
|
|
stops: string[]
|
|
minPrice: number
|
|
}
|
|
|
|
export interface MultiCityInspirationsResponse {
|
|
currency: { name: string, code: string, symbol: string }
|
|
items: MultiCityInspirationItem[]
|
|
}
|
|
|
|
export async function getMultiCityInspirations(startLocationsCodes: string[], locale: string = 'en', take: number = 20): Promise<MultiCityInspirationsResponse> {
|
|
return $fetch(`${BASE_URL}/inspirations/search-multi-city`, {
|
|
method: 'POST',
|
|
headers: HEADERS,
|
|
body: { startLocationsCodes, locale, take }
|
|
})
|
|
}
|
|
|
|
// --- API: Locations ---
|
|
|
|
export interface Airport {
|
|
id: string
|
|
iata: string
|
|
icao: string
|
|
nameEng: string
|
|
lat: number
|
|
lon: number
|
|
cityId: string
|
|
timeZone: string
|
|
timeZoneOffset: string
|
|
}
|
|
|
|
export interface City {
|
|
id: string
|
|
name: string
|
|
nameEng: string
|
|
countryId: string
|
|
lat: number
|
|
lon: number
|
|
}
|
|
|
|
export interface LocationCountry {
|
|
id: string
|
|
name: string
|
|
nameEng: string
|
|
isoCode2: string
|
|
}
|
|
|
|
export interface LocationsResponse {
|
|
contractVersion: number
|
|
locale: string
|
|
airports: Airport[]
|
|
cities: City[]
|
|
countries: LocationCountry[]
|
|
}
|
|
|
|
export async function getLocations(): Promise<LocationsResponse> {
|
|
return $fetch(`${BASE_URL}/locations`, {
|
|
method: 'GET',
|
|
headers: { 'User-Agent': USER_AGENT }
|
|
})
|
|
}
|
|
|
|
// --- API: Route Data ---
|
|
|
|
export async function getRouteData(departureCode: string, arrivalCode: string): Promise<any> {
|
|
return $fetch(`${BASE_URL}/route-data`, {
|
|
method: 'GET',
|
|
headers: { 'User-Agent': USER_AGENT },
|
|
query: { departureCode, arrivalCode }
|
|
})
|
|
}
|
|
|
|
// --- API: Countries ---
|
|
|
|
export async function getCountries(): Promise<{ countries: Country[] }> {
|
|
return $fetch(`${BASE_URL}/countries`, {
|
|
method: 'GET',
|
|
headers: { 'User-Agent': USER_AGENT }
|
|
})
|
|
}
|
|
|
|
// --- API: Booking ---
|
|
|
|
export async function getBookingInfo(bookingToken: string, passengersCount: PassengersCount): Promise<any> {
|
|
return $fetch(`${BASE_URL}/booking/info`, {
|
|
method: 'POST',
|
|
headers: HEADERS,
|
|
body: { bookingToken, passengersCount }
|
|
})
|
|
}
|
|
|
|
export async function initBooking(bookingToken: string, passengersCount: PassengersCount): Promise<any> {
|
|
return $fetch(`${BASE_URL}/booking/init`, {
|
|
method: 'POST',
|
|
headers: HEADERS,
|
|
body: { bookingToken, passengersCount }
|
|
})
|
|
}
|