@tks/wayfinder 0.4.0 → 0.6.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/README.md +14 -2
- package/package.json +1 -1
- package/src/cli.ts +8 -2
- package/src/format.ts +6 -0
- package/src/parse.ts +148 -5
- package/src/serpapi.ts +52 -1
- package/src/types.ts +12 -0
package/README.md
CHANGED
|
@@ -70,6 +70,12 @@ Search with filters:
|
|
|
70
70
|
wayfinder flights --from LAX --to SEA --date 2026-04-10 --airline AS --max-stops 0 --max-price 250 --depart-after 06:00 --depart-before 12:00
|
|
71
71
|
```
|
|
72
72
|
|
|
73
|
+
Search by cabin:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
wayfinder flights --from JFK --to HND --date 2026-06-15 --cabin premium-economy
|
|
77
|
+
```
|
|
78
|
+
|
|
73
79
|
Exclude basic economy fares:
|
|
74
80
|
|
|
75
81
|
```bash
|
|
@@ -94,10 +100,16 @@ Search hotels:
|
|
|
94
100
|
wayfinder hotels --where "New York, NY" --check-in 2026-04-10 --check-out 2026-04-12
|
|
95
101
|
```
|
|
96
102
|
|
|
97
|
-
Search hotels with filters:
|
|
103
|
+
Search hotels with price and rating filters:
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
wayfinder hotels --where "Tokyo" --check-in 2026-04-10 --check-out 2026-04-13 --adults 2 --min-price 120 --max-price 300 --rating 4
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Search family-friendly hotels with cancellation and class filters:
|
|
98
110
|
|
|
99
111
|
```bash
|
|
100
|
-
wayfinder hotels --where "Tokyo" --check-in 2026-04-10 --check-out 2026-04-13 --adults 2 --
|
|
112
|
+
wayfinder hotels --where "Tokyo" --check-in 2026-04-10 --check-out 2026-04-13 --adults 2 --children 2 --children-ages 4,7 --free-cancellation --hotel-class 4,5
|
|
101
113
|
```
|
|
102
114
|
|
|
103
115
|
Structured hotel output for scripting:
|
package/package.json
CHANGED
package/src/cli.ts
CHANGED
|
@@ -22,7 +22,7 @@ interface RunOptions {
|
|
|
22
22
|
promptImpl?: (prompt: string) => Promise<string>;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
const HELP_TEXT = `wayfinder
|
|
25
|
+
const HELP_TEXT = `wayfinder travel search
|
|
26
26
|
|
|
27
27
|
Usage:
|
|
28
28
|
wayfinder setup [--reset]
|
|
@@ -43,6 +43,7 @@ Flights required:
|
|
|
43
43
|
|
|
44
44
|
Flights optional filters:
|
|
45
45
|
--airline <IATA> Airline code, example UA
|
|
46
|
+
--cabin <CABIN> Cabin: economy, premium-economy, preeco, business, first
|
|
46
47
|
--max-stops <0|1|2> Maximum number of stops
|
|
47
48
|
--max-price <USD> Max price in USD
|
|
48
49
|
--depart-after <HH:MM> Start of departure window
|
|
@@ -63,7 +64,12 @@ Hotels required:
|
|
|
63
64
|
|
|
64
65
|
Hotels optional filters:
|
|
65
66
|
--adults <N> Number of adults (default 2)
|
|
66
|
-
--
|
|
67
|
+
--children <N> Number of children
|
|
68
|
+
--children-ages <A,B> Child ages, comma-separated, example 4,7
|
|
69
|
+
--free-cancellation Only show hotels with free cancellation
|
|
70
|
+
--hotel-class <A,B> Hotel star class, comma-separated: 2,3,4,5
|
|
71
|
+
--min-price <USD> Lower price bound in USD
|
|
72
|
+
--max-price <USD> Upper price bound in USD
|
|
67
73
|
--rating <3.5|4|4.5|5> Minimum guest rating
|
|
68
74
|
|
|
69
75
|
Places required:
|
package/src/format.ts
CHANGED
|
@@ -14,6 +14,7 @@ export function renderFlightTable(options: FlightOption[]): string {
|
|
|
14
14
|
arrive: option.arrivalTime,
|
|
15
15
|
duration: formatDuration(option.durationMinutes),
|
|
16
16
|
stops: String(option.stops),
|
|
17
|
+
cabin: option.cabin ?? "n/a",
|
|
17
18
|
}));
|
|
18
19
|
|
|
19
20
|
const headers = {
|
|
@@ -23,6 +24,7 @@ export function renderFlightTable(options: FlightOption[]): string {
|
|
|
23
24
|
arrive: "ARRIVE",
|
|
24
25
|
duration: "DURATION",
|
|
25
26
|
stops: "STOPS",
|
|
27
|
+
cabin: "CABIN",
|
|
26
28
|
};
|
|
27
29
|
|
|
28
30
|
const widths = {
|
|
@@ -32,6 +34,7 @@ export function renderFlightTable(options: FlightOption[]): string {
|
|
|
32
34
|
arrive: maxWidth(rows, "arrive", headers.arrive),
|
|
33
35
|
duration: maxWidth(rows, "duration", headers.duration),
|
|
34
36
|
stops: maxWidth(rows, "stops", headers.stops),
|
|
37
|
+
cabin: maxWidth(rows, "cabin", headers.cabin),
|
|
35
38
|
};
|
|
36
39
|
|
|
37
40
|
const lines: string[] = [];
|
|
@@ -44,6 +47,7 @@ export function renderFlightTable(options: FlightOption[]): string {
|
|
|
44
47
|
headers.arrive.padEnd(widths.arrive),
|
|
45
48
|
headers.duration.padEnd(widths.duration),
|
|
46
49
|
headers.stops.padEnd(widths.stops),
|
|
50
|
+
headers.cabin.padEnd(widths.cabin),
|
|
47
51
|
].join(" "),
|
|
48
52
|
);
|
|
49
53
|
|
|
@@ -55,6 +59,7 @@ export function renderFlightTable(options: FlightOption[]): string {
|
|
|
55
59
|
"-".repeat(widths.arrive),
|
|
56
60
|
"-".repeat(widths.duration),
|
|
57
61
|
"-".repeat(widths.stops),
|
|
62
|
+
"-".repeat(widths.cabin),
|
|
58
63
|
].join(" "),
|
|
59
64
|
);
|
|
60
65
|
|
|
@@ -67,6 +72,7 @@ export function renderFlightTable(options: FlightOption[]): string {
|
|
|
67
72
|
row.arrive.padEnd(widths.arrive),
|
|
68
73
|
row.duration.padEnd(widths.duration),
|
|
69
74
|
row.stops.padEnd(widths.stops),
|
|
75
|
+
row.cabin.padEnd(widths.cabin),
|
|
70
76
|
].join(" "),
|
|
71
77
|
);
|
|
72
78
|
}
|
package/src/parse.ts
CHANGED
|
@@ -2,7 +2,9 @@ import { CliError } from "./errors";
|
|
|
2
2
|
import {
|
|
3
3
|
ExitCode,
|
|
4
4
|
FlightBookingQuery,
|
|
5
|
+
CabinClass,
|
|
5
6
|
FlightsQuery,
|
|
7
|
+
HotelClass,
|
|
6
8
|
HotelQuery,
|
|
7
9
|
ParsedArgs,
|
|
8
10
|
PlaceQuery,
|
|
@@ -15,6 +17,7 @@ interface FlightRawOptions {
|
|
|
15
17
|
to?: string;
|
|
16
18
|
dates: string[];
|
|
17
19
|
airline?: string;
|
|
20
|
+
cabin?: string;
|
|
18
21
|
maxStops?: string;
|
|
19
22
|
maxPrice?: string;
|
|
20
23
|
departAfter?: string;
|
|
@@ -29,6 +32,11 @@ interface HotelRawOptions {
|
|
|
29
32
|
checkIn?: string;
|
|
30
33
|
checkOut?: string;
|
|
31
34
|
adults?: string;
|
|
35
|
+
children?: string;
|
|
36
|
+
childrenAges?: string;
|
|
37
|
+
freeCancellation: boolean;
|
|
38
|
+
hotelClass?: string;
|
|
39
|
+
minPrice?: string;
|
|
32
40
|
maxPrice?: string;
|
|
33
41
|
rating?: string;
|
|
34
42
|
outputJson: boolean;
|
|
@@ -134,6 +142,9 @@ function parseFlightsArgs(args: string[]): ParsedArgs {
|
|
|
134
142
|
case "--airline":
|
|
135
143
|
raw.airline = value;
|
|
136
144
|
break;
|
|
145
|
+
case "--cabin":
|
|
146
|
+
raw.cabin = value;
|
|
147
|
+
break;
|
|
137
148
|
case "--max-stops":
|
|
138
149
|
raw.maxStops = value;
|
|
139
150
|
break;
|
|
@@ -170,6 +181,7 @@ function parseFlightsArgs(args: string[]): ParsedArgs {
|
|
|
170
181
|
|
|
171
182
|
function parseHotelsArgs(args: string[]): ParsedArgs {
|
|
172
183
|
const raw: HotelRawOptions = {
|
|
184
|
+
freeCancellation: false,
|
|
173
185
|
outputJson: false,
|
|
174
186
|
help: false,
|
|
175
187
|
};
|
|
@@ -187,6 +199,11 @@ function parseHotelsArgs(args: string[]): ParsedArgs {
|
|
|
187
199
|
continue;
|
|
188
200
|
}
|
|
189
201
|
|
|
202
|
+
if (token === "--free-cancellation") {
|
|
203
|
+
raw.freeCancellation = true;
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
|
|
190
207
|
if (!token.startsWith("--")) {
|
|
191
208
|
throw new CliError(`Unexpected argument: ${token}`, ExitCode.InvalidInput);
|
|
192
209
|
}
|
|
@@ -209,6 +226,18 @@ function parseHotelsArgs(args: string[]): ParsedArgs {
|
|
|
209
226
|
case "--adults":
|
|
210
227
|
raw.adults = value;
|
|
211
228
|
break;
|
|
229
|
+
case "--children":
|
|
230
|
+
raw.children = value;
|
|
231
|
+
break;
|
|
232
|
+
case "--children-ages":
|
|
233
|
+
raw.childrenAges = value;
|
|
234
|
+
break;
|
|
235
|
+
case "--hotel-class":
|
|
236
|
+
raw.hotelClass = value;
|
|
237
|
+
break;
|
|
238
|
+
case "--min-price":
|
|
239
|
+
raw.minPrice = value;
|
|
240
|
+
break;
|
|
212
241
|
case "--max-price":
|
|
213
242
|
raw.maxPrice = value;
|
|
214
243
|
break;
|
|
@@ -404,8 +433,13 @@ function buildFlightQuery(raw: FlightRawOptions): FlightsQuery {
|
|
|
404
433
|
}
|
|
405
434
|
|
|
406
435
|
const airlineCode = raw.airline ? normalizeAirlineCode(raw.airline) : undefined;
|
|
436
|
+
const cabin = raw.cabin ? normalizeCabin(raw.cabin) : undefined;
|
|
407
437
|
const maxStops = raw.maxStops ? normalizeMaxStops(raw.maxStops) : undefined;
|
|
408
|
-
const maxPrice = raw.maxPrice ?
|
|
438
|
+
const maxPrice = raw.maxPrice ? normalizePrice(raw.maxPrice, "--max-price") : undefined;
|
|
439
|
+
|
|
440
|
+
if (raw.excludeBasic && cabin && cabin !== "economy") {
|
|
441
|
+
throw new CliError("--exclude-basic can only be used with --cabin economy", ExitCode.InvalidInput);
|
|
442
|
+
}
|
|
409
443
|
|
|
410
444
|
const hasDepartAfter = typeof raw.departAfter === "string";
|
|
411
445
|
const hasDepartBefore = typeof raw.departBefore === "string";
|
|
@@ -436,6 +470,7 @@ function buildFlightQuery(raw: FlightRawOptions): FlightsQuery {
|
|
|
436
470
|
origin,
|
|
437
471
|
destination,
|
|
438
472
|
airlineCode,
|
|
473
|
+
cabin,
|
|
439
474
|
maxStops,
|
|
440
475
|
maxPrice,
|
|
441
476
|
departureAfterMinutes,
|
|
@@ -468,7 +503,12 @@ function buildHotelQuery(raw: HotelRawOptions): HotelQuery {
|
|
|
468
503
|
const checkInDate = normalizeDate(raw.checkIn, "check-in");
|
|
469
504
|
const checkOutDate = normalizeDate(raw.checkOut, "check-out");
|
|
470
505
|
const adults = raw.adults ? normalizeAdults(raw.adults) : 2;
|
|
471
|
-
const
|
|
506
|
+
const children = raw.children ? normalizeChildren(raw.children) : 0;
|
|
507
|
+
const childrenAges = raw.childrenAges ? normalizeChildrenAges(raw.childrenAges) : undefined;
|
|
508
|
+
const freeCancellation = raw.freeCancellation || undefined;
|
|
509
|
+
const hotelClasses = raw.hotelClass ? normalizeHotelClasses(raw.hotelClass) : undefined;
|
|
510
|
+
const minPrice = raw.minPrice ? normalizePrice(raw.minPrice, "--min-price") : undefined;
|
|
511
|
+
const maxPrice = raw.maxPrice ? normalizePrice(raw.maxPrice, "--max-price") : undefined;
|
|
472
512
|
const minRating = raw.rating ? normalizeMinRating(raw.rating) : undefined;
|
|
473
513
|
|
|
474
514
|
const checkIn = parseDateOnly(checkInDate);
|
|
@@ -478,11 +518,31 @@ function buildHotelQuery(raw: HotelRawOptions): HotelQuery {
|
|
|
478
518
|
throw new CliError("Check-out date must be after check-in date", ExitCode.InvalidInput);
|
|
479
519
|
}
|
|
480
520
|
|
|
521
|
+
if (childrenAges && children === 0) {
|
|
522
|
+
throw new CliError("--children-ages requires --children", ExitCode.InvalidInput);
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
if (childrenAges && childrenAges.length !== children) {
|
|
526
|
+
throw new CliError(
|
|
527
|
+
"--children-ages count must match --children",
|
|
528
|
+
ExitCode.InvalidInput,
|
|
529
|
+
);
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
if (typeof minPrice === "number" && typeof maxPrice === "number" && minPrice > maxPrice) {
|
|
533
|
+
throw new CliError("--min-price cannot be greater than --max-price", ExitCode.InvalidInput);
|
|
534
|
+
}
|
|
535
|
+
|
|
481
536
|
return {
|
|
482
537
|
location,
|
|
483
538
|
checkInDate,
|
|
484
539
|
checkOutDate,
|
|
485
540
|
adults,
|
|
541
|
+
children,
|
|
542
|
+
childrenAges,
|
|
543
|
+
freeCancellation,
|
|
544
|
+
hotelClasses,
|
|
545
|
+
minPrice,
|
|
486
546
|
maxPrice,
|
|
487
547
|
minRating,
|
|
488
548
|
};
|
|
@@ -590,6 +650,27 @@ function normalizeAirlineCode(value: string): string {
|
|
|
590
650
|
return upper;
|
|
591
651
|
}
|
|
592
652
|
|
|
653
|
+
function normalizeCabin(value: string): CabinClass {
|
|
654
|
+
const normalized = value.trim().toLowerCase();
|
|
655
|
+
if (normalized === "preeco") {
|
|
656
|
+
return "premium-economy";
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
if (
|
|
660
|
+
normalized === "economy" ||
|
|
661
|
+
normalized === "premium-economy" ||
|
|
662
|
+
normalized === "business" ||
|
|
663
|
+
normalized === "first"
|
|
664
|
+
) {
|
|
665
|
+
return normalized;
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
throw new CliError(
|
|
669
|
+
"--cabin must be one of: economy, premium-economy, preeco, business, first",
|
|
670
|
+
ExitCode.InvalidInput,
|
|
671
|
+
);
|
|
672
|
+
}
|
|
673
|
+
|
|
593
674
|
function normalizeLocation(value: string): string {
|
|
594
675
|
const trimmed = value.trim();
|
|
595
676
|
if (trimmed.length === 0) {
|
|
@@ -640,11 +721,17 @@ function normalizeMaxStops(value: string): number {
|
|
|
640
721
|
return numeric;
|
|
641
722
|
}
|
|
642
723
|
|
|
643
|
-
function
|
|
644
|
-
const
|
|
724
|
+
function normalizePrice(value: string, flagName: "--min-price" | "--max-price"): number {
|
|
725
|
+
const trimmed = value.trim();
|
|
726
|
+
if (!/^\d+$/.test(trimmed)) {
|
|
727
|
+
throw new CliError(`${flagName} must be a positive integer`, ExitCode.InvalidInput);
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
const numeric = Number.parseInt(trimmed, 10);
|
|
645
731
|
if (!Number.isInteger(numeric) || numeric <= 0) {
|
|
646
|
-
throw new CliError(
|
|
732
|
+
throw new CliError(`${flagName} must be a positive integer`, ExitCode.InvalidInput);
|
|
647
733
|
}
|
|
734
|
+
|
|
648
735
|
return numeric;
|
|
649
736
|
}
|
|
650
737
|
|
|
@@ -666,6 +753,62 @@ function normalizeAdults(value: string): number {
|
|
|
666
753
|
return numeric;
|
|
667
754
|
}
|
|
668
755
|
|
|
756
|
+
function normalizeChildren(value: string): number {
|
|
757
|
+
if (!/^\d+$/.test(value.trim())) {
|
|
758
|
+
throw new CliError("--children must be a positive integer", ExitCode.InvalidInput);
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
const numeric = Number.parseInt(value, 10);
|
|
762
|
+
if (!Number.isInteger(numeric) || numeric <= 0) {
|
|
763
|
+
throw new CliError("--children must be a positive integer", ExitCode.InvalidInput);
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
return numeric;
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
function normalizeChildrenAges(value: string): number[] {
|
|
770
|
+
const parts = value.split(",");
|
|
771
|
+
if (parts.length === 0) {
|
|
772
|
+
throw new CliError(
|
|
773
|
+
"--children-ages must be a comma-separated list of ages 1 through 17",
|
|
774
|
+
ExitCode.InvalidInput,
|
|
775
|
+
);
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
return parts.map((part) => {
|
|
779
|
+
const trimmed = part.trim();
|
|
780
|
+
if (!/^\d+$/.test(trimmed)) {
|
|
781
|
+
throw new CliError(
|
|
782
|
+
"--children-ages must be a comma-separated list of ages 1 through 17",
|
|
783
|
+
ExitCode.InvalidInput,
|
|
784
|
+
);
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
const age = Number.parseInt(trimmed, 10);
|
|
788
|
+
if (age < 1 || age > 17) {
|
|
789
|
+
throw new CliError("--children-ages ages must be between 1 and 17", ExitCode.InvalidInput);
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
return age;
|
|
793
|
+
});
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
function normalizeHotelClasses(value: string): HotelClass[] {
|
|
797
|
+
const hotelClasses = value.split(",").map((part) => {
|
|
798
|
+
const trimmed = part.trim();
|
|
799
|
+
if (trimmed !== "2" && trimmed !== "3" && trimmed !== "4" && trimmed !== "5") {
|
|
800
|
+
throw new CliError(
|
|
801
|
+
"--hotel-class must be a comma-separated list of: 2, 3, 4, 5",
|
|
802
|
+
ExitCode.InvalidInput,
|
|
803
|
+
);
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
return Number.parseInt(trimmed, 10) as HotelClass;
|
|
807
|
+
});
|
|
808
|
+
|
|
809
|
+
return [...new Set(hotelClasses)].sort((a, b) => a - b);
|
|
810
|
+
}
|
|
811
|
+
|
|
669
812
|
function normalizeMinRating(value: string): 3.5 | 4 | 4.5 | 5 {
|
|
670
813
|
const numeric = Number.parseFloat(value);
|
|
671
814
|
if (numeric !== 3.5 && numeric !== 4 && numeric !== 4.5 && numeric !== 5) {
|
package/src/serpapi.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { CliError } from "./errors";
|
|
2
2
|
import {
|
|
3
|
+
CabinClass,
|
|
3
4
|
ExitCode,
|
|
4
5
|
FlightDateResult,
|
|
5
6
|
FlightBookingQuery,
|
|
@@ -22,6 +23,7 @@ interface SerpApiAirport {
|
|
|
22
23
|
|
|
23
24
|
interface SerpApiSegment {
|
|
24
25
|
airline?: string;
|
|
26
|
+
travel_class?: string;
|
|
25
27
|
duration?: number;
|
|
26
28
|
departure_airport?: SerpApiAirport;
|
|
27
29
|
arrival_airport?: SerpApiAirport;
|
|
@@ -148,6 +150,7 @@ export async function searchFlightsMultiDate(
|
|
|
148
150
|
destination: query.destination,
|
|
149
151
|
departureDate: date,
|
|
150
152
|
airlineCode: query.airlineCode,
|
|
153
|
+
cabin: query.cabin,
|
|
151
154
|
maxStops: query.maxStops,
|
|
152
155
|
maxPrice: query.maxPrice,
|
|
153
156
|
departureAfterMinutes: query.departureAfterMinutes,
|
|
@@ -291,6 +294,9 @@ function shapeItinerary(itinerary: SerpApiItinerary): FlightOption | null {
|
|
|
291
294
|
}
|
|
292
295
|
|
|
293
296
|
const uniqueAirlines = [...new Set(segments.map((segment) => segment.airline).filter(Boolean))];
|
|
297
|
+
const cabinClasses = [
|
|
298
|
+
...new Set(segments.map((segment) => segment.travel_class).filter(isNonEmptyString)),
|
|
299
|
+
];
|
|
294
300
|
|
|
295
301
|
const option: FlightOption = {
|
|
296
302
|
price: itinerary.price as number,
|
|
@@ -301,6 +307,10 @@ function shapeItinerary(itinerary: SerpApiItinerary): FlightOption | null {
|
|
|
301
307
|
stops: Math.max(0, segments.length - 1),
|
|
302
308
|
};
|
|
303
309
|
|
|
310
|
+
if (cabinClasses.length > 0) {
|
|
311
|
+
option.cabin = cabinClasses.join(", ");
|
|
312
|
+
}
|
|
313
|
+
|
|
304
314
|
if (typeof itinerary.booking_token === "string" && itinerary.booking_token.trim() !== "") {
|
|
305
315
|
option.bookingToken = itinerary.booking_token.trim();
|
|
306
316
|
}
|
|
@@ -351,6 +361,10 @@ function inferDurationMinutes(itinerary: SerpApiItinerary, segments: SerpApiSegm
|
|
|
351
361
|
return segmentDuration;
|
|
352
362
|
}
|
|
353
363
|
|
|
364
|
+
function isNonEmptyString(value: unknown): value is string {
|
|
365
|
+
return typeof value === "string" && value.trim() !== "";
|
|
366
|
+
}
|
|
367
|
+
|
|
354
368
|
function buildFlightRequestUrl(query: FlightQuery, apiKey: string): string {
|
|
355
369
|
const url = new URL("https://serpapi.com/search.json");
|
|
356
370
|
|
|
@@ -367,6 +381,10 @@ function buildFlightRequestUrl(query: FlightQuery, apiKey: string): string {
|
|
|
367
381
|
url.searchParams.set("include_airlines", query.airlineCode);
|
|
368
382
|
}
|
|
369
383
|
|
|
384
|
+
if (query.cabin) {
|
|
385
|
+
url.searchParams.set("travel_class", toSerpApiTravelClass(query.cabin));
|
|
386
|
+
}
|
|
387
|
+
|
|
370
388
|
if (typeof query.maxStops === "number") {
|
|
371
389
|
url.searchParams.set("stops", toSerpApiStopsFilter(query.maxStops));
|
|
372
390
|
}
|
|
@@ -390,7 +408,7 @@ function buildFlightRequestUrl(query: FlightQuery, apiKey: string): string {
|
|
|
390
408
|
|
|
391
409
|
if (query.excludeBasic) {
|
|
392
410
|
url.searchParams.set("exclude_basic", "true");
|
|
393
|
-
url.searchParams.set("travel_class", "
|
|
411
|
+
url.searchParams.set("travel_class", toSerpApiTravelClass("economy"));
|
|
394
412
|
url.searchParams.set("gl", "us");
|
|
395
413
|
}
|
|
396
414
|
|
|
@@ -405,9 +423,26 @@ function buildHotelRequestUrl(query: HotelQuery, apiKey: string): string {
|
|
|
405
423
|
url.searchParams.set("check_in_date", query.checkInDate);
|
|
406
424
|
url.searchParams.set("check_out_date", query.checkOutDate);
|
|
407
425
|
url.searchParams.set("adults", String(query.adults));
|
|
426
|
+
url.searchParams.set("children", String(query.children));
|
|
408
427
|
url.searchParams.set("currency", "USD");
|
|
409
428
|
url.searchParams.set("api_key", apiKey);
|
|
410
429
|
|
|
430
|
+
if (query.childrenAges && query.childrenAges.length > 0) {
|
|
431
|
+
url.searchParams.set("children_ages", query.childrenAges.join(","));
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
if (query.freeCancellation) {
|
|
435
|
+
url.searchParams.set("free_cancellation", "true");
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
if (query.hotelClasses && query.hotelClasses.length > 0) {
|
|
439
|
+
url.searchParams.set("hotel_class", query.hotelClasses.join(","));
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
if (typeof query.minPrice === "number") {
|
|
443
|
+
url.searchParams.set("min_price", String(query.minPrice));
|
|
444
|
+
}
|
|
445
|
+
|
|
411
446
|
if (typeof query.maxPrice === "number") {
|
|
412
447
|
url.searchParams.set("max_price", String(query.maxPrice));
|
|
413
448
|
}
|
|
@@ -462,6 +497,22 @@ function toSerpApiStopsFilter(maxStops: number): string {
|
|
|
462
497
|
return "3";
|
|
463
498
|
}
|
|
464
499
|
|
|
500
|
+
function toSerpApiTravelClass(cabin: CabinClass): string {
|
|
501
|
+
if (cabin === "economy") {
|
|
502
|
+
return "1";
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
if (cabin === "premium-economy") {
|
|
506
|
+
return "2";
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
if (cabin === "business") {
|
|
510
|
+
return "3";
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
return "4";
|
|
514
|
+
}
|
|
515
|
+
|
|
465
516
|
function toSerpApiRating(rating: 3.5 | 4 | 4.5 | 5): string {
|
|
466
517
|
if (rating === 3.5) {
|
|
467
518
|
return "7";
|
package/src/types.ts
CHANGED
|
@@ -9,11 +9,14 @@ export const ExitCode = {
|
|
|
9
9
|
|
|
10
10
|
export type ExitCodeValue = (typeof ExitCode)[keyof typeof ExitCode];
|
|
11
11
|
|
|
12
|
+
export type CabinClass = "economy" | "premium-economy" | "business" | "first";
|
|
13
|
+
|
|
12
14
|
export interface FlightQuery {
|
|
13
15
|
origin: string;
|
|
14
16
|
destination: string;
|
|
15
17
|
departureDate: string;
|
|
16
18
|
airlineCode?: string;
|
|
19
|
+
cabin?: CabinClass;
|
|
17
20
|
maxStops?: number;
|
|
18
21
|
maxPrice?: number;
|
|
19
22
|
departureAfterMinutes?: number;
|
|
@@ -26,6 +29,7 @@ export interface FlightMultiDateQuery {
|
|
|
26
29
|
destination: string;
|
|
27
30
|
departureDates: string[];
|
|
28
31
|
airlineCode?: string;
|
|
32
|
+
cabin?: CabinClass;
|
|
29
33
|
maxStops?: number;
|
|
30
34
|
maxPrice?: number;
|
|
31
35
|
departureAfterMinutes?: number;
|
|
@@ -35,11 +39,18 @@ export interface FlightMultiDateQuery {
|
|
|
35
39
|
|
|
36
40
|
export type FlightsQuery = FlightQuery | FlightMultiDateQuery;
|
|
37
41
|
|
|
42
|
+
export type HotelClass = 2 | 3 | 4 | 5;
|
|
43
|
+
|
|
38
44
|
export interface HotelQuery {
|
|
39
45
|
location: string;
|
|
40
46
|
checkInDate: string;
|
|
41
47
|
checkOutDate: string;
|
|
42
48
|
adults: number;
|
|
49
|
+
children: number;
|
|
50
|
+
childrenAges?: number[];
|
|
51
|
+
freeCancellation?: boolean;
|
|
52
|
+
hotelClasses?: HotelClass[];
|
|
53
|
+
minPrice?: number;
|
|
43
54
|
maxPrice?: number;
|
|
44
55
|
minRating?: 3.5 | 4 | 4.5 | 5;
|
|
45
56
|
}
|
|
@@ -116,6 +127,7 @@ export interface FlightOption {
|
|
|
116
127
|
arrivalTime: string;
|
|
117
128
|
durationMinutes: number;
|
|
118
129
|
stops: number;
|
|
130
|
+
cabin?: string;
|
|
119
131
|
bookingToken?: string;
|
|
120
132
|
}
|
|
121
133
|
|