google-fli 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +158 -0
- package/dist/core/airports.d.ts +31 -0
- package/dist/core/airports.d.ts.map +1 -0
- package/dist/core/airports.js +168 -0
- package/dist/core/airports.js.map +1 -0
- package/dist/core/builders.d.ts +30 -0
- package/dist/core/builders.d.ts.map +1 -0
- package/dist/core/builders.js +113 -0
- package/dist/core/builders.js.map +1 -0
- package/dist/core/currency.d.ts +21 -0
- package/dist/core/currency.d.ts.map +1 -0
- package/dist/core/currency.js +172 -0
- package/dist/core/currency.js.map +1 -0
- package/dist/core/dates.d.ts +22 -0
- package/dist/core/dates.d.ts.map +1 -0
- package/dist/core/dates.js +41 -0
- package/dist/core/dates.js.map +1 -0
- package/dist/core/index.d.ts +7 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +7 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/links.d.ts +39 -0
- package/dist/core/links.d.ts.map +1 -0
- package/dist/core/links.js +53 -0
- package/dist/core/links.js.map +1 -0
- package/dist/core/parsers.d.ts +34 -0
- package/dist/core/parsers.d.ts.map +1 -0
- package/dist/core/parsers.js +161 -0
- package/dist/core/parsers.js.map +1 -0
- package/dist/index.d.ts +30 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +30 -0
- package/dist/index.js.map +1 -0
- package/dist/models/airline.d.ts +1120 -0
- package/dist/models/airline.d.ts.map +1 -0
- package/dist/models/airline.js +2224 -0
- package/dist/models/airline.js.map +1 -0
- package/dist/models/airport.d.ts +7899 -0
- package/dist/models/airport.d.ts.map +1 -0
- package/dist/models/airport.js +15782 -0
- package/dist/models/airport.js.map +1 -0
- package/dist/models/google-flights/base.d.ts +232 -0
- package/dist/models/google-flights/base.d.ts.map +1 -0
- package/dist/models/google-flights/base.js +188 -0
- package/dist/models/google-flights/base.js.map +1 -0
- package/dist/models/google-flights/dates.d.ts +51 -0
- package/dist/models/google-flights/dates.d.ts.map +1 -0
- package/dist/models/google-flights/dates.js +222 -0
- package/dist/models/google-flights/dates.js.map +1 -0
- package/dist/models/google-flights/flights.d.ts +57 -0
- package/dist/models/google-flights/flights.d.ts.map +1 -0
- package/dist/models/google-flights/flights.js +224 -0
- package/dist/models/google-flights/flights.js.map +1 -0
- package/dist/models/google-flights/index.d.ts +4 -0
- package/dist/models/google-flights/index.d.ts.map +1 -0
- package/dist/models/google-flights/index.js +4 -0
- package/dist/models/google-flights/index.js.map +1 -0
- package/dist/models/index.d.ts +4 -0
- package/dist/models/index.d.ts.map +1 -0
- package/dist/models/index.js +4 -0
- package/dist/models/index.js.map +1 -0
- package/dist/search/client.d.ts +66 -0
- package/dist/search/client.d.ts.map +1 -0
- package/dist/search/client.js +205 -0
- package/dist/search/client.js.map +1 -0
- package/dist/search/concurrency.d.ts +45 -0
- package/dist/search/concurrency.d.ts.map +1 -0
- package/dist/search/concurrency.js +148 -0
- package/dist/search/concurrency.js.map +1 -0
- package/dist/search/dates.d.ts +33 -0
- package/dist/search/dates.d.ts.map +1 -0
- package/dist/search/dates.js +159 -0
- package/dist/search/dates.js.map +1 -0
- package/dist/search/decoders.d.ts +22 -0
- package/dist/search/decoders.d.ts.map +1 -0
- package/dist/search/decoders.js +333 -0
- package/dist/search/decoders.js.map +1 -0
- package/dist/search/exceptions.d.ts +21 -0
- package/dist/search/exceptions.d.ts.map +1 -0
- package/dist/search/exceptions.js +37 -0
- package/dist/search/exceptions.js.map +1 -0
- package/dist/search/flights.d.ts +57 -0
- package/dist/search/flights.d.ts.map +1 -0
- package/dist/search/flights.js +298 -0
- package/dist/search/flights.js.map +1 -0
- package/dist/search/helpers.d.ts +10 -0
- package/dist/search/helpers.d.ts.map +1 -0
- package/dist/search/helpers.js +30 -0
- package/dist/search/helpers.js.map +1 -0
- package/dist/search/index.d.ts +10 -0
- package/dist/search/index.d.ts.map +1 -0
- package/dist/search/index.js +10 -0
- package/dist/search/index.js.map +1 -0
- package/dist/search/proto.d.ts +69 -0
- package/dist/search/proto.d.ts.map +1 -0
- package/dist/search/proto.js +355 -0
- package/dist/search/proto.js.map +1 -0
- package/dist/search/urls.d.ts +9 -0
- package/dist/search/urls.d.ts.map +1 -0
- package/dist/search/urls.js +9 -0
- package/dist/search/urls.js.map +1 -0
- package/dist/search/wire.d.ts +14 -0
- package/dist/search/wire.d.ts.map +1 -0
- package/dist/search/wire.js +120 -0
- package/dist/search/wire.js.map +1 -0
- package/package.json +91 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Punit Arani
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
# fli (TypeScript)
|
|
2
|
+
|
|
3
|
+
A 1:1 TypeScript / JavaScript port of the [Python `fli` library](https://github.com/punitarani/fli).
|
|
4
|
+
|
|
5
|
+
Programmatic access to Google Flights data via direct API interaction (no
|
|
6
|
+
scraping). The API surface mirrors the Python package — same models, same
|
|
7
|
+
filter encoding, same wire-format decoders.
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm i fli-js # or: pnpm add fli-js / yarn add fli-js / bun add fli-js
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Quick start
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
import {
|
|
19
|
+
Airport,
|
|
20
|
+
FlightSearchFilters,
|
|
21
|
+
FlightSegment,
|
|
22
|
+
MaxStops,
|
|
23
|
+
PassengerInfo,
|
|
24
|
+
SearchFlights,
|
|
25
|
+
SeatType,
|
|
26
|
+
SortBy,
|
|
27
|
+
} from "fli-js";
|
|
28
|
+
|
|
29
|
+
const filters = new FlightSearchFilters({
|
|
30
|
+
passenger_info: { adults: 1, children: 0, infants_in_seat: 0, infants_on_lap: 0 },
|
|
31
|
+
flight_segments: [
|
|
32
|
+
new FlightSegment({
|
|
33
|
+
departure_airport: [[[Airport.JFK, 0]]],
|
|
34
|
+
arrival_airport: [[[Airport.LAX, 0]]],
|
|
35
|
+
travel_date: "2026-12-25",
|
|
36
|
+
}),
|
|
37
|
+
],
|
|
38
|
+
seat_type: SeatType.ECONOMY,
|
|
39
|
+
stops: MaxStops.NON_STOP,
|
|
40
|
+
sort_by: SortBy.CHEAPEST,
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
const results = await new SearchFlights().search(filters, { currency: "USD" });
|
|
44
|
+
console.log(results);
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Date-range search
|
|
48
|
+
|
|
49
|
+
```ts
|
|
50
|
+
import { Airport, DateSearchFilters, FlightSegment, SearchDates } from "fli-js";
|
|
51
|
+
|
|
52
|
+
const filters = new DateSearchFilters({
|
|
53
|
+
passenger_info: { adults: 1, children: 0, infants_in_seat: 0, infants_on_lap: 0 },
|
|
54
|
+
flight_segments: [
|
|
55
|
+
new FlightSegment({
|
|
56
|
+
departure_airport: [[[Airport.JFK, 0]]],
|
|
57
|
+
arrival_airport: [[[Airport.LAX, 0]]],
|
|
58
|
+
travel_date: "2026-12-01",
|
|
59
|
+
}),
|
|
60
|
+
],
|
|
61
|
+
from_date: "2026-12-01",
|
|
62
|
+
to_date: "2026-12-31",
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
const dates = await new SearchDates().search(filters);
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Booking links
|
|
69
|
+
|
|
70
|
+
Turn search results into clickable Google Flights links — no extra network
|
|
71
|
+
call, fully deterministic.
|
|
72
|
+
|
|
73
|
+
```ts
|
|
74
|
+
import { googleFlightsUrl, SearchFlights } from "fli-js";
|
|
75
|
+
|
|
76
|
+
const search = new SearchFlights();
|
|
77
|
+
const results = await search.search(filters, { currency: "USD" });
|
|
78
|
+
|
|
79
|
+
// Per-flight deep link: opens the specific itinerary's booking page
|
|
80
|
+
// (vendor fares + "Continue" CTA) via a `tfs` protobuf token.
|
|
81
|
+
const first = results?.[0];
|
|
82
|
+
if (first) {
|
|
83
|
+
console.log(search.buildFlightBookingUrl(first, { currency: "USD" }));
|
|
84
|
+
// https://www.google.com/travel/flights/booking?tfs=…&curr=USD
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Search-level deep link: route + dates pre-filled (handy for a date result
|
|
88
|
+
// or a quick "view on Google Flights" link).
|
|
89
|
+
console.log(googleFlightsUrl("JFK", "LHR", "2026-12-25", null, { currency: "USD" }));
|
|
90
|
+
// https://www.google.com/travel/flights?q=Flights%20from%20JFK%20to%20LHR%20on%202026-12-25&curr=USD
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
`buildFlightBookingUrl` accepts a single `FlightResult` (one-way) or an array
|
|
94
|
+
of them (round-trip / multi-city). It never throws — on malformed input it
|
|
95
|
+
falls back to the generic Google Flights URL.
|
|
96
|
+
|
|
97
|
+
## HTTP / proxy configuration
|
|
98
|
+
|
|
99
|
+
The TypeScript port uses native `fetch` (Bun's built-in) and replaces
|
|
100
|
+
`curl_cffi`'s TLS impersonation with:
|
|
101
|
+
|
|
102
|
+
- realistic Chrome `User-Agent` + `Sec-CH-*` headers,
|
|
103
|
+
- automatic rate-limiting at 10 req/s,
|
|
104
|
+
- 3-attempt exponential backoff on transient errors,
|
|
105
|
+
- proxy support via the `HTTPS_PROXY` / `HTTP_PROXY` env vars (or via the
|
|
106
|
+
explicit `proxy` option on `new Client({...})`).
|
|
107
|
+
|
|
108
|
+
```ts
|
|
109
|
+
import { Client, SearchFlights } from "fli-js";
|
|
110
|
+
|
|
111
|
+
const search = new SearchFlights(
|
|
112
|
+
new Client({ proxy: "http://user:pass@proxy.example.com:8080" }),
|
|
113
|
+
);
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Set the per-request timeout with `FLI_TIMEOUT=30` (seconds) or via the
|
|
117
|
+
`timeoutMs` option on `new Client({...})`.
|
|
118
|
+
|
|
119
|
+
## Modules
|
|
120
|
+
|
|
121
|
+
- `fli-js/models` — `Airport`, `Airline`, `FlightSearchFilters`, `DateSearchFilters`,
|
|
122
|
+
`FlightSegment`, `FlightResult`, `BookingOption`, all enums.
|
|
123
|
+
- `fli-js/core` — string-to-enum parsers, segment builders, airport search,
|
|
124
|
+
currency token decoders, deep-link builder (`googleFlightsUrl`).
|
|
125
|
+
- `fli-js/search` — `SearchFlights` (incl. `buildFlightBookingUrl`),
|
|
126
|
+
`SearchDates`, `Client`, error classes, protobuf token helpers
|
|
127
|
+
(`buildBookingToken`, `buildTfsToken`, `extractBookingTokenFromTfu`).
|
|
128
|
+
|
|
129
|
+
## Development
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
bun install
|
|
133
|
+
bun run generate:enums # regenerate airport.ts / airline.ts from data/*.csv
|
|
134
|
+
bun run typecheck
|
|
135
|
+
bun run lint # biome + oxlint
|
|
136
|
+
bun run format # biome format
|
|
137
|
+
bun test # unit + integration tests (no network)
|
|
138
|
+
bun run test:e2e # live tests (FLI_E2E=1; talks to Google Flights)
|
|
139
|
+
bun run ci # format-check + lint + typecheck + tests
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## Parity with the Python library
|
|
143
|
+
|
|
144
|
+
The TypeScript port preserves byte-perfect wire compatibility with the
|
|
145
|
+
Python upstream:
|
|
146
|
+
|
|
147
|
+
- `FlightSearchFilters.format()` produces structurally identical
|
|
148
|
+
nested-list payloads (see `tests/integration/filter_format_snapshots.test.ts`).
|
|
149
|
+
- `buildBookingToken(...)` and `buildTfsToken(...)` reproduce captured live
|
|
150
|
+
booking-page tokens byte-for-byte (see `tests/search/proto.test.ts`); the
|
|
151
|
+
resulting `buildFlightBookingUrl(...)` output matches the Python library
|
|
152
|
+
character-for-character.
|
|
153
|
+
- The wire-format parser handles both the legacy single-chunk JSONP
|
|
154
|
+
shape and the multi-chunk format used by `GetBookingResults`.
|
|
155
|
+
|
|
156
|
+
## License
|
|
157
|
+
|
|
158
|
+
MIT — same as the upstream Python project.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Airport search by IATA code, city name, or airport name.
|
|
3
|
+
* 1:1 port of fli/core/airports.py — same 5-priority cascade.
|
|
4
|
+
*/
|
|
5
|
+
import { type Airport } from "../models/airport.ts";
|
|
6
|
+
export type MatchType = "iata_exact" | "iata_prefix" | "city" | "name";
|
|
7
|
+
export interface AirportMatch {
|
|
8
|
+
code: Airport;
|
|
9
|
+
name: string;
|
|
10
|
+
match_type: MatchType;
|
|
11
|
+
score: number;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Curated multi-airport city aliases (e.g., "new york" → JFK / LGA / EWR).
|
|
15
|
+
* Mirrors the Python CITY_AIRPORTS dict.
|
|
16
|
+
*/
|
|
17
|
+
export declare const CITY_AIRPORTS: Record<string, string[]>;
|
|
18
|
+
/**
|
|
19
|
+
* Search airports by city name, airport name, or IATA code.
|
|
20
|
+
*
|
|
21
|
+
* Results are ranked 0-100 (higher = better) via the same 5-priority
|
|
22
|
+
* cascade used in the Python implementation:
|
|
23
|
+
*
|
|
24
|
+
* 1. iata_exact (100) — exact IATA code
|
|
25
|
+
* 2. city (90) — exact city/alias
|
|
26
|
+
* 3. city (80) — prefix of a city/alias
|
|
27
|
+
* 4. name (≤70) — substring of an airport's name (position-weighted)
|
|
28
|
+
* 5. iata_prefix (60) — prefix of an IATA code (only for ≤3-char queries)
|
|
29
|
+
*/
|
|
30
|
+
export declare function searchAirports(query: string, limit?: number): AirportMatch[];
|
|
31
|
+
//# sourceMappingURL=airports.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"airports.d.ts","sourceRoot":"","sources":["../../src/core/airports.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAiB,KAAK,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAEnE,MAAM,MAAM,SAAS,GAAG,YAAY,GAAG,aAAa,GAAG,MAAM,GAAG,MAAM,CAAC;AAEvE,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,OAAO,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,SAAS,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;;GAGG;AACH,eAAO,MAAM,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAoDlD,CAAC;AAYF;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,SAAK,GAAG,YAAY,EAAE,CAsFxE"}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Airport search by IATA code, city name, or airport name.
|
|
3
|
+
* 1:1 port of fli/core/airports.py — same 5-priority cascade.
|
|
4
|
+
*/
|
|
5
|
+
import { AIRPORT_NAMES } from "../models/airport.js";
|
|
6
|
+
/**
|
|
7
|
+
* Curated multi-airport city aliases (e.g., "new york" → JFK / LGA / EWR).
|
|
8
|
+
* Mirrors the Python CITY_AIRPORTS dict.
|
|
9
|
+
*/
|
|
10
|
+
export const CITY_AIRPORTS = {
|
|
11
|
+
"new york": ["JFK", "LGA", "EWR"],
|
|
12
|
+
nyc: ["JFK", "LGA", "EWR"],
|
|
13
|
+
chicago: ["ORD", "MDW"],
|
|
14
|
+
washington: ["IAD", "DCA", "BWI"],
|
|
15
|
+
"washington dc": ["IAD", "DCA", "BWI"],
|
|
16
|
+
london: ["LHR", "LGW", "STN", "LTN", "LCY"],
|
|
17
|
+
paris: ["CDG", "ORY"],
|
|
18
|
+
tokyo: ["NRT", "HND"],
|
|
19
|
+
osaka: ["KIX", "ITM"],
|
|
20
|
+
seoul: ["ICN", "GMP"],
|
|
21
|
+
beijing: ["PEK", "PKX"],
|
|
22
|
+
shanghai: ["PVG", "SHA"],
|
|
23
|
+
bangkok: ["BKK", "DMK"],
|
|
24
|
+
istanbul: ["IST", "SAW"],
|
|
25
|
+
moscow: ["SVO", "DME", "VKO"],
|
|
26
|
+
milan: ["MXP", "LIN"],
|
|
27
|
+
rome: ["FCO", "CIA"],
|
|
28
|
+
berlin: ["BER"],
|
|
29
|
+
mumbai: ["BOM"],
|
|
30
|
+
delhi: ["DEL"],
|
|
31
|
+
"sao paulo": ["GRU", "CGH"],
|
|
32
|
+
rio: ["GIG", "SDU"],
|
|
33
|
+
"rio de janeiro": ["GIG", "SDU"],
|
|
34
|
+
toronto: ["YYZ", "YTZ"],
|
|
35
|
+
montreal: ["YUL"],
|
|
36
|
+
"mexico city": ["MEX"],
|
|
37
|
+
"buenos aires": ["EZE", "AEP"],
|
|
38
|
+
dubai: ["DXB", "DWC"],
|
|
39
|
+
singapore: ["SIN"],
|
|
40
|
+
"hong kong": ["HKG"],
|
|
41
|
+
taipei: ["TPE", "TSA"],
|
|
42
|
+
sydney: ["SYD"],
|
|
43
|
+
melbourne: ["MEL"],
|
|
44
|
+
"san francisco": ["SFO", "OAK", "SJC"],
|
|
45
|
+
sf: ["SFO", "OAK", "SJC"],
|
|
46
|
+
"bay area": ["SFO", "OAK", "SJC"],
|
|
47
|
+
"los angeles": ["LAX", "BUR", "SNA", "ONT", "LGB"],
|
|
48
|
+
la: ["LAX", "BUR", "SNA", "ONT", "LGB"],
|
|
49
|
+
dallas: ["DFW", "DAL"],
|
|
50
|
+
houston: ["IAH", "HOU"],
|
|
51
|
+
atlanta: ["ATL"],
|
|
52
|
+
denver: ["DEN"],
|
|
53
|
+
seattle: ["SEA"],
|
|
54
|
+
boston: ["BOS"],
|
|
55
|
+
miami: ["MIA", "FLL"],
|
|
56
|
+
detroit: ["DTW"],
|
|
57
|
+
minneapolis: ["MSP"],
|
|
58
|
+
phoenix: ["PHX"],
|
|
59
|
+
orlando: ["MCO"],
|
|
60
|
+
"las vegas": ["LAS"],
|
|
61
|
+
honolulu: ["HNL"],
|
|
62
|
+
};
|
|
63
|
+
// Validate at module load — surfaces typos in CITY_AIRPORTS immediately
|
|
64
|
+
// instead of returning silently-empty results at search time.
|
|
65
|
+
for (const [city, codes] of Object.entries(CITY_AIRPORTS)) {
|
|
66
|
+
for (const code of codes) {
|
|
67
|
+
if (!(code in AIRPORT_NAMES)) {
|
|
68
|
+
throw new Error(`CITY_AIRPORTS[${city}] references unknown IATA code '${code}'`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Search airports by city name, airport name, or IATA code.
|
|
74
|
+
*
|
|
75
|
+
* Results are ranked 0-100 (higher = better) via the same 5-priority
|
|
76
|
+
* cascade used in the Python implementation:
|
|
77
|
+
*
|
|
78
|
+
* 1. iata_exact (100) — exact IATA code
|
|
79
|
+
* 2. city (90) — exact city/alias
|
|
80
|
+
* 3. city (80) — prefix of a city/alias
|
|
81
|
+
* 4. name (≤70) — substring of an airport's name (position-weighted)
|
|
82
|
+
* 5. iata_prefix (60) — prefix of an IATA code (only for ≤3-char queries)
|
|
83
|
+
*/
|
|
84
|
+
export function searchAirports(query, limit = 10) {
|
|
85
|
+
const queryLower = query.trim().toLowerCase();
|
|
86
|
+
if (!queryLower || limit < 1)
|
|
87
|
+
return [];
|
|
88
|
+
const results = [];
|
|
89
|
+
const seen = new Set();
|
|
90
|
+
// 1. Exact IATA code match
|
|
91
|
+
const queryUpper = query.trim().toUpperCase();
|
|
92
|
+
if (queryUpper in AIRPORT_NAMES) {
|
|
93
|
+
results.push({
|
|
94
|
+
code: queryUpper,
|
|
95
|
+
name: queryUpper,
|
|
96
|
+
match_type: "iata_exact",
|
|
97
|
+
score: 100.0,
|
|
98
|
+
});
|
|
99
|
+
seen.add(queryUpper);
|
|
100
|
+
}
|
|
101
|
+
// 2. Exact city alias match
|
|
102
|
+
if (queryLower in CITY_AIRPORTS) {
|
|
103
|
+
for (const code of CITY_AIRPORTS[queryLower] ?? []) {
|
|
104
|
+
if (!seen.has(code)) {
|
|
105
|
+
results.push({
|
|
106
|
+
code: code,
|
|
107
|
+
name: code,
|
|
108
|
+
match_type: "city",
|
|
109
|
+
score: 90.0,
|
|
110
|
+
});
|
|
111
|
+
seen.add(code);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
// 3. City alias prefix match
|
|
116
|
+
if (!(queryLower in CITY_AIRPORTS)) {
|
|
117
|
+
for (const [city, codes] of Object.entries(CITY_AIRPORTS)) {
|
|
118
|
+
if (city.startsWith(queryLower)) {
|
|
119
|
+
for (const code of codes) {
|
|
120
|
+
if (!seen.has(code)) {
|
|
121
|
+
results.push({
|
|
122
|
+
code: code,
|
|
123
|
+
name: code,
|
|
124
|
+
match_type: "city",
|
|
125
|
+
score: 80.0,
|
|
126
|
+
});
|
|
127
|
+
seen.add(code);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
// 4. Airport name substring match (position-weighted score)
|
|
134
|
+
for (const [code, name] of Object.entries(AIRPORT_NAMES)) {
|
|
135
|
+
if (seen.has(code))
|
|
136
|
+
continue;
|
|
137
|
+
const nameLower = name.toLowerCase();
|
|
138
|
+
const pos = nameLower.indexOf(queryLower);
|
|
139
|
+
if (pos !== -1) {
|
|
140
|
+
const score = 70.0 - pos * 0.1;
|
|
141
|
+
results.push({ code: code, name, match_type: "name", score });
|
|
142
|
+
seen.add(code);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
// 5. IATA prefix match (≤3-char query)
|
|
146
|
+
if (queryUpper.length <= 3) {
|
|
147
|
+
for (const [code, name] of Object.entries(AIRPORT_NAMES)) {
|
|
148
|
+
if (seen.has(code))
|
|
149
|
+
continue;
|
|
150
|
+
if (code.startsWith(queryUpper)) {
|
|
151
|
+
results.push({
|
|
152
|
+
code: code,
|
|
153
|
+
name,
|
|
154
|
+
match_type: "iata_prefix",
|
|
155
|
+
score: 60.0,
|
|
156
|
+
});
|
|
157
|
+
seen.add(code);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
results.sort((a, b) => {
|
|
162
|
+
if (a.score !== b.score)
|
|
163
|
+
return b.score - a.score;
|
|
164
|
+
return a.code < b.code ? -1 : a.code > b.code ? 1 : 0;
|
|
165
|
+
});
|
|
166
|
+
return results.slice(0, limit);
|
|
167
|
+
}
|
|
168
|
+
//# sourceMappingURL=airports.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"airports.js","sourceRoot":"","sources":["../../src/core/airports.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Builder utilities for constructing search filters. 1:1 port of
|
|
3
|
+
* fli/core/builders.py.
|
|
4
|
+
*/
|
|
5
|
+
import type { Airport } from "../models/airport.ts";
|
|
6
|
+
import { FlightSegment, type TimeRestrictions, TripType } from "../models/google-flights/base.ts";
|
|
7
|
+
/** Zero-pad a YYYY-MM-DD date string. */
|
|
8
|
+
export declare function normalizeDate(dateStr: string): string;
|
|
9
|
+
/** Build a `TimeRestrictions` from "HH-HH"-style window strings, or `null`. */
|
|
10
|
+
export declare function buildTimeRestrictions(departureWindow?: string | null, arrivalWindow?: string | null): TimeRestrictions | null;
|
|
11
|
+
/** Build flight segments for a one-way or round-trip search. */
|
|
12
|
+
export declare function buildFlightSegments(origin: Airport | Airport[], destination: Airport | Airport[], departureDate: string, returnDate?: string | null, timeRestrictions?: TimeRestrictions | null): {
|
|
13
|
+
segments: FlightSegment[];
|
|
14
|
+
tripType: TripType;
|
|
15
|
+
};
|
|
16
|
+
/** Build flight segments for a multi-city search from a list of legs. */
|
|
17
|
+
export declare function buildMultiCitySegments(legs: Array<[Airport, Airport, string]>, timeRestrictions?: TimeRestrictions | null): {
|
|
18
|
+
segments: FlightSegment[];
|
|
19
|
+
tripType: TripType;
|
|
20
|
+
};
|
|
21
|
+
/** Build flight segments for a date-range search. */
|
|
22
|
+
export declare function buildDateSearchSegments(origin: Airport | Airport[], destination: Airport | Airport[], startDate: string, options?: {
|
|
23
|
+
tripDuration?: number | null;
|
|
24
|
+
isRoundTrip?: boolean;
|
|
25
|
+
timeRestrictions?: TimeRestrictions | null;
|
|
26
|
+
}): {
|
|
27
|
+
segments: FlightSegment[];
|
|
28
|
+
tripType: TripType;
|
|
29
|
+
};
|
|
30
|
+
//# sourceMappingURL=builders.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"builders.d.ts","sourceRoot":"","sources":["../../src/core/builders.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AACpD,OAAO,EAAE,aAAa,EAAE,KAAK,gBAAgB,EAAE,QAAQ,EAAE,MAAM,kCAAkC,CAAC;AAKlG,yCAAyC;AACzC,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAgBrD;AAED,+EAA+E;AAC/E,wBAAgB,qBAAqB,CACnC,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,EAC/B,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,GAC5B,gBAAgB,GAAG,IAAI,CAqBzB;AAED,gEAAgE;AAChE,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,OAAO,GAAG,OAAO,EAAE,EAC3B,WAAW,EAAE,OAAO,GAAG,OAAO,EAAE,EAChC,aAAa,EAAE,MAAM,EACrB,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,EAC1B,gBAAgB,CAAC,EAAE,gBAAgB,GAAG,IAAI,GACzC;IAAE,QAAQ,EAAE,aAAa,EAAE,CAAC;IAAC,QAAQ,EAAE,QAAQ,CAAA;CAAE,CA6BnD;AAED,yEAAyE;AACzE,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,KAAK,CAAC,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC,EACvC,gBAAgB,CAAC,EAAE,gBAAgB,GAAG,IAAI,GACzC;IAAE,QAAQ,EAAE,aAAa,EAAE,CAAC;IAAC,QAAQ,EAAE,QAAQ,CAAA;CAAE,CAWnD;AAED,qDAAqD;AACrD,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,OAAO,GAAG,OAAO,EAAE,EAC3B,WAAW,EAAE,OAAO,GAAG,OAAO,EAAE,EAChC,SAAS,EAAE,MAAM,EACjB,OAAO,GAAE;IACP,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,gBAAgB,CAAC,EAAE,gBAAgB,GAAG,IAAI,CAAC;CACvC,GACL;IAAE,QAAQ,EAAE,aAAa,EAAE,CAAC;IAAC,QAAQ,EAAE,QAAQ,CAAA;CAAE,CAoCnD"}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Builder utilities for constructing search filters. 1:1 port of
|
|
3
|
+
* fli/core/builders.py.
|
|
4
|
+
*/
|
|
5
|
+
import { FlightSegment, TripType } from "../models/google-flights/base.js";
|
|
6
|
+
import { parseTimeRange } from "./parsers.js";
|
|
7
|
+
const ISO_DATE_RE = /^\d{4}-\d{1,2}-\d{1,2}$/;
|
|
8
|
+
/** Zero-pad a YYYY-MM-DD date string. */
|
|
9
|
+
export function normalizeDate(dateStr) {
|
|
10
|
+
if (!ISO_DATE_RE.test(dateStr)) {
|
|
11
|
+
throw new Error(`Invalid date string: ${dateStr}`);
|
|
12
|
+
}
|
|
13
|
+
const parts = dateStr.split("-").map((p) => Number.parseInt(p, 10));
|
|
14
|
+
const [y, m, d] = parts;
|
|
15
|
+
if (y == null || m == null || d == null) {
|
|
16
|
+
throw new Error(`Invalid date string: ${dateStr}`);
|
|
17
|
+
}
|
|
18
|
+
const date = new Date(Date.UTC(y, m - 1, d));
|
|
19
|
+
if (date.getUTCFullYear() !== y || date.getUTCMonth() !== m - 1 || date.getUTCDate() !== d) {
|
|
20
|
+
throw new Error(`Invalid date: ${dateStr}`);
|
|
21
|
+
}
|
|
22
|
+
const mm = String(m).padStart(2, "0");
|
|
23
|
+
const dd = String(d).padStart(2, "0");
|
|
24
|
+
return `${y}-${mm}-${dd}`;
|
|
25
|
+
}
|
|
26
|
+
/** Build a `TimeRestrictions` from "HH-HH"-style window strings, or `null`. */
|
|
27
|
+
export function buildTimeRestrictions(departureWindow, arrivalWindow) {
|
|
28
|
+
if (!departureWindow && !arrivalWindow)
|
|
29
|
+
return null;
|
|
30
|
+
let earliestDeparture = null;
|
|
31
|
+
let latestDeparture = null;
|
|
32
|
+
let earliestArrival = null;
|
|
33
|
+
let latestArrival = null;
|
|
34
|
+
if (departureWindow) {
|
|
35
|
+
[earliestDeparture, latestDeparture] = parseTimeRange(departureWindow);
|
|
36
|
+
}
|
|
37
|
+
if (arrivalWindow) {
|
|
38
|
+
[earliestArrival, latestArrival] = parseTimeRange(arrivalWindow);
|
|
39
|
+
}
|
|
40
|
+
return {
|
|
41
|
+
earliest_departure: earliestDeparture ?? undefined,
|
|
42
|
+
latest_departure: latestDeparture ?? undefined,
|
|
43
|
+
earliest_arrival: earliestArrival ?? undefined,
|
|
44
|
+
latest_arrival: latestArrival ?? undefined,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
/** Build flight segments for a one-way or round-trip search. */
|
|
48
|
+
export function buildFlightSegments(origin, destination, departureDate, returnDate, timeRestrictions) {
|
|
49
|
+
const depDate = normalizeDate(departureDate);
|
|
50
|
+
const origins = Array.isArray(origin) ? origin : [origin];
|
|
51
|
+
const destinations = Array.isArray(destination) ? destination : [destination];
|
|
52
|
+
const segments = [
|
|
53
|
+
new FlightSegment({
|
|
54
|
+
departure_airport: [origins.map((apt) => [apt, 0])],
|
|
55
|
+
arrival_airport: [destinations.map((apt) => [apt, 0])],
|
|
56
|
+
travel_date: depDate,
|
|
57
|
+
time_restrictions: timeRestrictions ?? null,
|
|
58
|
+
}),
|
|
59
|
+
];
|
|
60
|
+
let tripType = TripType.ONE_WAY;
|
|
61
|
+
if (returnDate) {
|
|
62
|
+
const retDate = normalizeDate(returnDate);
|
|
63
|
+
tripType = TripType.ROUND_TRIP;
|
|
64
|
+
segments.push(new FlightSegment({
|
|
65
|
+
departure_airport: [destinations.map((apt) => [apt, 0])],
|
|
66
|
+
arrival_airport: [origins.map((apt) => [apt, 0])],
|
|
67
|
+
travel_date: retDate,
|
|
68
|
+
time_restrictions: timeRestrictions ?? null,
|
|
69
|
+
}));
|
|
70
|
+
}
|
|
71
|
+
return { segments, tripType };
|
|
72
|
+
}
|
|
73
|
+
/** Build flight segments for a multi-city search from a list of legs. */
|
|
74
|
+
export function buildMultiCitySegments(legs, timeRestrictions) {
|
|
75
|
+
const segments = legs.map(([origin, destination, date]) => new FlightSegment({
|
|
76
|
+
departure_airport: [[[origin, 0]]],
|
|
77
|
+
arrival_airport: [[[destination, 0]]],
|
|
78
|
+
travel_date: normalizeDate(date),
|
|
79
|
+
time_restrictions: timeRestrictions ?? null,
|
|
80
|
+
}));
|
|
81
|
+
return { segments, tripType: TripType.MULTI_CITY };
|
|
82
|
+
}
|
|
83
|
+
/** Build flight segments for a date-range search. */
|
|
84
|
+
export function buildDateSearchSegments(origin, destination, startDate, options = {}) {
|
|
85
|
+
const startNormalized = normalizeDate(startDate);
|
|
86
|
+
const origins = Array.isArray(origin) ? origin : [origin];
|
|
87
|
+
const destinations = Array.isArray(destination) ? destination : [destination];
|
|
88
|
+
const segments = [
|
|
89
|
+
new FlightSegment({
|
|
90
|
+
departure_airport: [origins.map((apt) => [apt, 0])],
|
|
91
|
+
arrival_airport: [destinations.map((apt) => [apt, 0])],
|
|
92
|
+
travel_date: startNormalized,
|
|
93
|
+
time_restrictions: options.timeRestrictions ?? null,
|
|
94
|
+
}),
|
|
95
|
+
];
|
|
96
|
+
let tripType = TripType.ONE_WAY;
|
|
97
|
+
if (options.isRoundTrip) {
|
|
98
|
+
tripType = TripType.ROUND_TRIP;
|
|
99
|
+
const startMs = Date.parse(`${startNormalized}T00:00:00Z`);
|
|
100
|
+
const days = options.tripDuration ?? 3;
|
|
101
|
+
const returnMs = startMs + days * 24 * 60 * 60 * 1000;
|
|
102
|
+
const returnDate = new Date(returnMs);
|
|
103
|
+
const ret = `${returnDate.getUTCFullYear()}-${String(returnDate.getUTCMonth() + 1).padStart(2, "0")}-${String(returnDate.getUTCDate()).padStart(2, "0")}`;
|
|
104
|
+
segments.push(new FlightSegment({
|
|
105
|
+
departure_airport: [destinations.map((apt) => [apt, 0])],
|
|
106
|
+
arrival_airport: [origins.map((apt) => [apt, 0])],
|
|
107
|
+
travel_date: ret,
|
|
108
|
+
time_restrictions: options.timeRestrictions ?? null,
|
|
109
|
+
}));
|
|
110
|
+
}
|
|
111
|
+
return { segments, tripType };
|
|
112
|
+
}
|
|
113
|
+
//# sourceMappingURL=builders.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"builders.js","sourceRoot":"","sources":["../../src/core/builders.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Currency extraction from Google Flights price tokens.
|
|
3
|
+
*
|
|
4
|
+
* 1:1 port of fli/core/currency.py — varint walk over a base64-urlsafe
|
|
5
|
+
* protobuf payload to extract the nested ISO currency code, cached to
|
|
6
|
+
* avoid repeated decoding for the common one-currency-per-response case.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Extract the ISO currency code from a Google Flights price token.
|
|
10
|
+
*
|
|
11
|
+
* Cached so the same token returned for every row in a response decodes
|
|
12
|
+
* the protobuf payload exactly once.
|
|
13
|
+
*/
|
|
14
|
+
export declare function extractCurrencyFromPriceToken(token: string | null | undefined): string | null;
|
|
15
|
+
/** Currency-code-aware formatter built on `Intl.NumberFormat`. */
|
|
16
|
+
export declare function formatPrice(amount: number | null | undefined, currencyCode: string | null | undefined): string;
|
|
17
|
+
/** Build a chart-axis label for one or more result currencies. */
|
|
18
|
+
export declare function formatPriceAxisLabel(currencies: Iterable<string | null | undefined>): string;
|
|
19
|
+
/** Clear the in-memory token-decode cache (test helper). */
|
|
20
|
+
export declare function _clearCurrencyCache(): void;
|
|
21
|
+
//# sourceMappingURL=currency.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"currency.d.ts","sourceRoot":"","sources":["../../src/core/currency.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAgGH;;;;;GAKG;AACH,wBAAgB,6BAA6B,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,GAAG,IAAI,CAiB7F;AAED,kEAAkE;AAClE,wBAAgB,WAAW,CACzB,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EACjC,YAAY,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GACtC,MAAM,CAsBR;AAED,kEAAkE;AAClE,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,QAAQ,CAAC,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,GAAG,MAAM,CAU5F;AAED,4DAA4D;AAC5D,wBAAgB,mBAAmB,IAAI,IAAI,CAE1C"}
|