diff --git a/package-lock.json b/package-lock.json index 4b0c3db..4430a70 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "scrapebadger", - "version": "0.7.0", + "version": "0.14.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "scrapebadger", - "version": "0.7.0", + "version": "0.14.0", "license": "MIT", "dependencies": { "ws": "^8.18.0" diff --git a/package.json b/package.json index 0776bfb..9fa3f51 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "scrapebadger", - "version": "0.13.1", + "version": "0.14.0", "description": "Official Node.js SDK for ScrapeBadger - Async web scraping APIs for Twitter, Google, Vinted, Reddit, and more", "type": "module", "main": "./dist/index.js", diff --git a/src/google/flights.ts b/src/google/flights.ts index dd644aa..534e13b 100644 --- a/src/google/flights.ts +++ b/src/google/flights.ts @@ -3,7 +3,7 @@ */ import type { BaseClient } from "../internal/client.js"; -import type { FlightsSearchParams, GoogleResponse } from "./types.js"; +import type { FlightsBookingOptionsParams, FlightsSearchParams, GoogleResponse } from "./types.js"; /** * Client for Google Flights search. @@ -26,6 +26,8 @@ import type { FlightsSearchParams, GoogleResponse } from "./types.js"; * }); * for (const offer of flights.best_flights) { * console.log(offer.price, offer.currency, offer.total_duration_minutes); + * // offer.booking_url deep-links to the booking page for that flight; + * // offer.selection_token feeds bookingOptions() below. * } * if (flights.price_insights) { * console.log("Price level:", flights.price_insights.price_level); @@ -35,10 +37,46 @@ import type { FlightsSearchParams, GoogleResponse } from "./types.js"; export class FlightsClient { constructor(private readonly client: BaseClient) {} - /** Search Google Flights for available itineraries. */ + /** + * Search Google Flights for available itineraries. + * + * Each offer includes a `booking_url` (deep link that pre-selects the + * flight) and a `selection_token` for {@link bookingOptions}. The response + * also carries a `search_url` for the whole search, and each leg carries + * the flight number, departure/arrival times, airport names and aircraft. + */ async search(params: FlightsSearchParams): Promise { return this.client.request("/v1/google/flights/search", { params: { ...params }, }); } + + /** + * Retrieve the provider booking list (airline + OTAs, with prices) for a + * selected itinerary, given the `selection_token` from a search offer. + * + * Works for one-way / fully-selected itineraries. This renders the Google + * Flights booking page, so it is slower than `search`. The actual + * per-provider booking links open from the returned `booking_url`. + * + * @example + * ```typescript + * const flights = await client.google.flights.search({ + * departure_id: "CNF", arrival_id: "GRU", + * outbound_date: "2026-06-25", trip_type: "one_way", currency: "BRL", + * }); + * const token = flights.best_flights[0].selection_token; + * const options = await client.google.flights.bookingOptions({ + * selection_token: token, currency: "BRL", gl: "br", + * }); + * for (const opt of options.booking_options) { + * console.log(opt.book_with, opt.price, opt.currency); + * } + * ``` + */ + async bookingOptions(params: FlightsBookingOptionsParams): Promise { + return this.client.request("/v1/google/flights/booking_options", { + params: { ...params }, + }); + } } diff --git a/src/google/index.ts b/src/google/index.ts index 67b5b7f..df1a546 100644 --- a/src/google/index.ts +++ b/src/google/index.ts @@ -31,6 +31,7 @@ export type { AiTextBlock, AutocompleteParams, FinanceQuoteParams, + FlightsBookingOptionsParams, FlightsSearchParams, FlightsStopsFilter, FlightsTravelClass, diff --git a/src/google/types.ts b/src/google/types.ts index ebf5bd0..c761b2e 100644 --- a/src/google/types.ts +++ b/src/google/types.ts @@ -669,3 +669,16 @@ export interface FlightsSearchParams { /** Upper price filter. */ max_price?: number; } + +export interface FlightsBookingOptionsParams { + /** + * `selection_token` from a flights search offer (one-way or + * fully-selected itinerary). Pass it to retrieve the provider booking list. + */ + selection_token: string; + /** ISO-4217 currency code (default "USD"). */ + currency?: string; + gl?: string; + /** Language for the returned `booking_url`. */ + hl?: string; +} diff --git a/src/index.ts b/src/index.ts index 5eeca08..206d91a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -115,6 +115,7 @@ export type { AiTextBlock, AutocompleteParams, FinanceQuoteParams, + FlightsBookingOptionsParams, FlightsSearchParams, FlightsStopsFilter, FlightsTravelClass, diff --git a/tests/google.test.ts b/tests/google.test.ts index ce3c63e..67de900 100644 --- a/tests/google.test.ts +++ b/tests/google.test.ts @@ -123,9 +123,7 @@ describe("MapsClient", () => { it("place throws when no identifier is supplied", async () => { const client = makeClient(); - await expect(client.google.maps.place({})).rejects.toThrow( - "place_id or data_id" - ); + await expect(client.google.maps.place({})).rejects.toThrow("place_id or data_id"); }); it("reviews forwards all params", async () => { @@ -214,6 +212,45 @@ describe("HotelsClient", () => { }); }); +// --------------------------------------------------------------------------- +// Flights +// --------------------------------------------------------------------------- + +describe("FlightsClient", () => { + it("search forwards itinerary params", async () => { + mockFetch({ best_flights: [], other_flights: [] }); + const client = makeClient(); + await client.google.flights.search({ + departure_id: "CNF", + arrival_id: "GRU", + outbound_date: "2026-06-25", + trip_type: "one_way", + currency: "BRL", + gl: "br", + hl: "pt-BR", + }); + const url = capturedUrl(); + expect(url.pathname).toBe("/v1/google/flights/search"); + expect(url.searchParams.get("departure_id")).toBe("CNF"); + expect(url.searchParams.get("trip_type")).toBe("one_way"); + expect(url.searchParams.get("currency")).toBe("BRL"); + }); + + it("bookingOptions forwards selection_token", async () => { + mockFetch({ booking_options: [] }); + const client = makeClient(); + await client.google.flights.bookingOptions({ + selection_token: "CBwQAhxx", + currency: "BRL", + gl: "br", + }); + const url = capturedUrl(); + expect(url.pathname).toBe("/v1/google/flights/booking_options"); + expect(url.searchParams.get("selection_token")).toBe("CBwQAhxx"); + expect(url.searchParams.get("currency")).toBe("BRL"); + }); +}); + // --------------------------------------------------------------------------- // Trends // ---------------------------------------------------------------------------