google-flights-mcp-server 0.2.0 → 0.2.2
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/package.json
CHANGED
|
@@ -244,51 +244,87 @@ function parseFareBrand(raw) {
|
|
|
244
244
|
}
|
|
245
245
|
}
|
|
246
246
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
247
|
+
function parseRawOffer(raw, currency) {
|
|
248
|
+
const details = raw[0];
|
|
249
|
+
const priceData = raw[1];
|
|
250
|
+
const rankData = raw[5]; // [is_best (1/0), ?, ?]
|
|
251
|
+
if (!details || !priceData)
|
|
252
|
+
return null;
|
|
253
|
+
// Parse segments
|
|
254
|
+
const segments = [];
|
|
255
|
+
const legs = details[2];
|
|
256
|
+
if (Array.isArray(legs)) {
|
|
257
|
+
for (const leg of legs) {
|
|
258
|
+
const segment = parseSegment(leg);
|
|
259
|
+
if (segment)
|
|
260
|
+
segments.push(segment);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
const price = priceData[0]?.[1];
|
|
264
|
+
if (price === undefined || price === null)
|
|
265
|
+
return null;
|
|
266
|
+
return {
|
|
267
|
+
price,
|
|
268
|
+
currency,
|
|
269
|
+
airline: details[1]?.[0] || '',
|
|
270
|
+
airline_code: details[0] || '',
|
|
271
|
+
is_best: rankData?.[0] === 1,
|
|
272
|
+
fare_brand: parseFareBrand(raw),
|
|
273
|
+
departure: formatTime(details[5]),
|
|
274
|
+
arrival: formatTime(details[8]),
|
|
275
|
+
departure_date: formatDate(details[4]),
|
|
276
|
+
arrival_date: formatDate(details[7]),
|
|
277
|
+
duration_minutes: details[9] || 0,
|
|
278
|
+
stops: segments.length > 0 ? segments.length - 1 : 0,
|
|
279
|
+
segments,
|
|
280
|
+
extensions: parseExtensions(raw),
|
|
281
|
+
booking_token: priceData[1] || '',
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
247
285
|
function parseFlightOffers(ds1, currency) {
|
|
248
286
|
const offers = [];
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
287
|
+
const seenTokens = new Set();
|
|
288
|
+
// Google Flights returns results in two sections:
|
|
289
|
+
// - ds1[2][0]: "Best flights" (featured/highlighted flights, typically 3)
|
|
290
|
+
// - ds1[3][0]: "Other flights" (the main results list)
|
|
291
|
+
// Both sections use the same offer structure. In practice flights are not
|
|
292
|
+
// duplicated between them, but we deduplicate by booking_token defensively
|
|
293
|
+
// since this is an undocumented scraped API that could change.
|
|
294
|
+
//
|
|
295
|
+
// The is_best flag comes from raw[5][0] (per-offer rankData), not from which
|
|
296
|
+
// section the offer appears in. Google sets this flag on all ds1[2][0] offers
|
|
297
|
+
// and sometimes on ds1[3][0] offers too.
|
|
298
|
+
// Parse "best flights" from ds1[2][0]
|
|
299
|
+
const bestFlights = ds1?.[2]?.[0];
|
|
300
|
+
if (Array.isArray(bestFlights)) {
|
|
301
|
+
for (const raw of bestFlights) {
|
|
302
|
+
try {
|
|
303
|
+
const offer = parseRawOffer(raw, currency);
|
|
304
|
+
if (offer && !seenTokens.has(offer.booking_token)) {
|
|
305
|
+
seenTokens.add(offer.booking_token);
|
|
306
|
+
offers.push(offer);
|
|
267
307
|
}
|
|
268
308
|
}
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
offers.push({
|
|
273
|
-
price,
|
|
274
|
-
currency,
|
|
275
|
-
airline: details[1]?.[0] || '',
|
|
276
|
-
airline_code: details[0] || '',
|
|
277
|
-
is_best: rankData?.[0] === 1,
|
|
278
|
-
fare_brand: parseFareBrand(raw),
|
|
279
|
-
departure: formatTime(details[5]),
|
|
280
|
-
arrival: formatTime(details[8]),
|
|
281
|
-
departure_date: formatDate(details[4]),
|
|
282
|
-
arrival_date: formatDate(details[7]),
|
|
283
|
-
duration_minutes: details[9] || 0,
|
|
284
|
-
stops: segments.length > 0 ? segments.length - 1 : 0,
|
|
285
|
-
segments,
|
|
286
|
-
extensions: parseExtensions(raw),
|
|
287
|
-
booking_token: priceData[1] || '',
|
|
288
|
-
});
|
|
309
|
+
catch (e) {
|
|
310
|
+
logDebug('parseFlightOffers', `Skipping malformed best offer: ${e.message}`);
|
|
311
|
+
}
|
|
289
312
|
}
|
|
290
|
-
|
|
291
|
-
|
|
313
|
+
}
|
|
314
|
+
// Parse "other flights" from ds1[3][0]
|
|
315
|
+
const otherFlights = ds1?.[3]?.[0];
|
|
316
|
+
if (Array.isArray(otherFlights)) {
|
|
317
|
+
for (const raw of otherFlights) {
|
|
318
|
+
try {
|
|
319
|
+
const offer = parseRawOffer(raw, currency);
|
|
320
|
+
if (offer && !seenTokens.has(offer.booking_token)) {
|
|
321
|
+
seenTokens.add(offer.booking_token);
|
|
322
|
+
offers.push(offer);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
catch (e) {
|
|
326
|
+
logDebug('parseFlightOffers', `Skipping malformed offer: ${e.message}`);
|
|
327
|
+
}
|
|
292
328
|
}
|
|
293
329
|
}
|
|
294
330
|
return offers;
|
|
@@ -19,9 +19,9 @@ export declare const SearchFlightsSchema: z.ZodObject<{
|
|
|
19
19
|
currency: z.ZodDefault<z.ZodString>;
|
|
20
20
|
exclude_basic_economy: z.ZodDefault<z.ZodBoolean>;
|
|
21
21
|
}, "strip", z.ZodTypeAny, {
|
|
22
|
+
currency: string;
|
|
22
23
|
sort_by: "best" | "price" | "duration" | "departure" | "arrival";
|
|
23
24
|
max_stops: "any" | "nonstop" | "1" | "2";
|
|
24
|
-
currency: string;
|
|
25
25
|
origin: string;
|
|
26
26
|
destination: string;
|
|
27
27
|
departure_date: string;
|
|
@@ -39,9 +39,9 @@ export declare const SearchFlightsSchema: z.ZodObject<{
|
|
|
39
39
|
origin: string;
|
|
40
40
|
destination: string;
|
|
41
41
|
departure_date: string;
|
|
42
|
+
currency?: string | undefined;
|
|
42
43
|
sort_by?: "best" | "price" | "duration" | "departure" | "arrival" | undefined;
|
|
43
44
|
max_stops?: "any" | "nonstop" | "1" | "2" | undefined;
|
|
44
|
-
currency?: string | undefined;
|
|
45
45
|
return_date?: string | undefined;
|
|
46
46
|
trip_type?: "one_way" | "round_trip" | undefined;
|
|
47
47
|
seat_class?: "economy" | "premium_economy" | "business" | "first" | undefined;
|