hotelzero 1.12.0 → 1.15.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 CHANGED
@@ -593,24 +593,261 @@ const FILTER_CODES = {
593
593
 
594
594
  Run `npx playwright install chromium` to install the browser.
595
595
 
596
+ ```bash
597
+ npx playwright install chromium
598
+ ```
599
+
600
+ If that doesn't work, try forcing a reinstall:
601
+ ```bash
602
+ npx playwright install chromium --force
603
+ ```
604
+
596
605
  ### No results returned
597
606
 
598
- - Check that dates are in the future
599
- - Verify destination spelling
600
- - Try removing some filters (too restrictive)
607
+ - **Check dates**: Ensure check-in/check-out dates are in the future
608
+ - **Verify destination**: Try more specific locations like "Paris, France" instead of just "Paris"
609
+ - **Reduce filters**: Too many filters can result in zero matches - try removing some
610
+ - **Check availability**: Some destinations may have no availability for your dates
611
+
612
+ ### CAPTCHA or Access Denied
613
+
614
+ If you encounter CAPTCHA challenges:
615
+
616
+ 1. **Wait 5-10 minutes** before retrying
617
+ 2. **Check your session**: Delete the session file and try again
618
+ ```bash
619
+ rm ~/.hotelzero/session.json
620
+ ```
621
+ 3. **Use a proxy**: Consider configuring a proxy server (see [Proxy Support](#proxy-support))
622
+ 4. **Reduce request frequency**: Avoid making many rapid requests
601
623
 
602
624
  ### Blocked by Booking.com
603
625
 
604
- - Wait a few minutes before retrying
605
- - The server uses anti-detection measures, but excessive requests may trigger blocks
606
- - Consider using a proxy server (see [Proxy Support](#proxy-support))
626
+ - **Rate limiting**: Wait a few minutes before retrying
627
+ - **Session issues**: Clear the session file (`rm ~/.hotelzero/session.json`)
628
+ - **IP blocked**: Use a proxy server or try from a different network
629
+ - **User agent rotation**: HotelZero rotates user agents automatically, but excessive use from one IP can still trigger blocks
607
630
 
608
631
  ### Proxy not working
609
632
 
610
- - Verify the proxy server is running and accessible
611
- - Check credentials if using authentication
612
- - Ensure the proxy supports HTTPS connections
613
- - Try a different proxy or test without proxy first
633
+ - **Verify accessibility**: Test the proxy with `curl` first
634
+ ```bash
635
+ curl -x http://proxy:8080 https://www.booking.com
636
+ ```
637
+ - **Check credentials**: Ensure username/password are URL-encoded if they contain special characters
638
+ - **Protocol support**: Ensure the proxy supports HTTPS connections
639
+ - **Connection timeout**: Some proxies may be slow - try a different one
640
+
641
+ ### Prices or Data Missing
642
+
643
+ - **Dynamic loading**: Some data loads asynchronously; try increasing timeouts
644
+ - **Currency issues**: Specify currency explicitly with `currency: "USD"`
645
+ - **Regional differences**: Some properties may not show prices for certain regions
646
+
647
+ ### Session Issues
648
+
649
+ If you experience inconsistent behavior:
650
+
651
+ ```bash
652
+ # Clear the session and start fresh
653
+ rm ~/.hotelzero/session.json
654
+
655
+ # Or disable session persistence entirely
656
+ HOTELZERO_SESSION_PATH="" npx hotelzero
657
+ ```
658
+
659
+ ### Debug Mode
660
+
661
+ Enable debug logging to see detailed information:
662
+
663
+ ```bash
664
+ HOTELZERO_LOG_LEVEL=debug npx hotelzero
665
+ ```
666
+
667
+ This will show:
668
+ - URL being requested
669
+ - Filters being applied
670
+ - Number of results found
671
+ - Any errors or warnings
672
+
673
+ ### Tests Failing
674
+
675
+ If you're running the test suite and tests fail:
676
+
677
+ 1. **Network issues**: Tests hit live Booking.com - check your connection
678
+ 2. **Rate limiting**: Wait a few minutes between test runs
679
+ 3. **Selector changes**: Booking.com may have updated their HTML structure
680
+
681
+ ---
682
+
683
+ ## API Response Schema
684
+
685
+ ### HotelResult (from `find_hotels` / `search_hotels`)
686
+
687
+ ```typescript
688
+ interface HotelResult {
689
+ name: string; // Hotel name
690
+ price: number | null; // Price per night as number (null if not shown)
691
+ priceDisplay: string; // Formatted price string (e.g., "$199")
692
+ rating: number | null; // Review score 0-10 (null if no reviews)
693
+ ratingText: string; // Rating description (e.g., "Excellent", "Very Good")
694
+ reviewCount: number | null; // Number of reviews (null if not shown)
695
+ location: string; // Neighborhood/area name
696
+ distanceToCenter: string; // Distance from center (e.g., "0.5 miles from center")
697
+ amenities: string[]; // Detected amenities (e.g., ["Pool", "Free WiFi", "Spa"])
698
+ highlights: string[]; // Special highlights (e.g., ["Free Cancellation"])
699
+ link: string; // Full Booking.com URL for the hotel
700
+ thumbnailUrl: string | null; // Hotel thumbnail image URL (null if not available)
701
+ availability: string | null; // Availability status (e.g., "Only 2 rooms left!")
702
+ matchScore?: number; // Relevance score based on filters (only with filters)
703
+ matchReasons?: string[]; // Why this hotel matched (only with filters)
704
+ }
705
+ ```
706
+
707
+ ### HotelDetails (from `get_hotel_details` / `compare_hotels`)
708
+
709
+ ```typescript
710
+ interface HotelDetails {
711
+ name: string; // Hotel name
712
+ url: string; // Booking.com URL
713
+ rating: number | null; // Review score 0-10
714
+ ratingText: string; // Rating description
715
+ reviewCount: number | null; // Total number of reviews
716
+ starRating: number | null; // Official star rating 1-5
717
+ address: string; // Full address
718
+ description: string; // Hotel description text
719
+ highlights: string; // Property highlights summary
720
+ pricePerNight: number | null; // Price per night as number
721
+ priceDisplay: string; // Formatted price per night
722
+ totalPrice: string; // Total stay price (formatted)
723
+ checkInTime: string; // Check-in time (e.g., "15:00")
724
+ checkOutTime: string; // Check-out time (e.g., "11:00")
725
+ popularFacilities: string[]; // Top facilities list
726
+ allFacilities: string[]; // Complete facilities list
727
+ roomTypes: string[]; // Available room type names
728
+ photos: string[]; // Photo URLs
729
+ nearbyAttractions: string[]; // Nearby points of interest
730
+ guestReviewHighlights: string[]; // Notable review excerpts
731
+ locationInfo: string; // Location description
732
+ }
733
+ ```
734
+
735
+ ### AvailabilityResult (from `check_availability`)
736
+
737
+ ```typescript
738
+ interface AvailabilityResult {
739
+ available: boolean; // Whether rooms are available
740
+ hotelName: string; // Hotel name
741
+ checkIn: string; // Check-in date (YYYY-MM-DD)
742
+ checkOut: string; // Check-out date (YYYY-MM-DD)
743
+ guests: number; // Number of guests searched
744
+ rooms: number; // Number of rooms searched
745
+ roomOptions: RoomOption[]; // Available room types
746
+ lowestPrice: number | null; // Lowest price found
747
+ lowestPriceDisplay: string; // Formatted lowest price
748
+ message: string; // Status message
749
+ url: string; // Booking URL with dates
750
+ }
751
+
752
+ interface RoomOption {
753
+ name: string; // Room type name
754
+ price: number | null; // Price as number
755
+ priceDisplay: string; // Formatted price
756
+ sleeps: number | null; // Maximum occupancy
757
+ features: string[]; // Room features
758
+ bedType: string; // Bed configuration
759
+ cancellation: string; // Cancellation policy
760
+ breakfast: string; // Meal plan info
761
+ }
762
+ ```
763
+
764
+ ### ReviewsResult (from `get_reviews`)
765
+
766
+ ```typescript
767
+ interface ReviewsResult {
768
+ hotelName: string; // Hotel name
769
+ overallRating: number | null; // Overall score 0-10
770
+ totalReviews: number; // Total review count
771
+ ratingBreakdown: RatingBreakdown; // Scores by category
772
+ reviews: Review[]; // Individual reviews
773
+ url: string; // Hotel URL
774
+ }
775
+
776
+ interface RatingBreakdown {
777
+ staff: number | null; // Staff rating
778
+ facilities: number | null; // Facilities rating
779
+ cleanliness: number | null; // Cleanliness rating
780
+ comfort: number | null; // Comfort rating
781
+ valueForMoney: number | null; // Value rating
782
+ location: number | null; // Location rating
783
+ freeWifi: number | null; // WiFi rating
784
+ }
785
+
786
+ interface Review {
787
+ title: string; // Review title
788
+ rating: number | null; // Individual score 0-10
789
+ date: string; // Review date
790
+ travelerType: string; // Traveler type (e.g., "Couple", "Family")
791
+ country: string; // Reviewer's country
792
+ stayDate: string; // When they stayed
793
+ roomType: string; // Room they booked
794
+ nightsStayed: string; // Length of stay
795
+ positive: string; // Positive comments
796
+ negative: string; // Negative comments
797
+ }
798
+ ```
799
+
800
+ ### PriceCalendarResult (from `get_price_calendar`)
801
+
802
+ ```typescript
803
+ interface PriceCalendarResult {
804
+ hotelName: string; // Hotel name
805
+ startDate: string; // Calendar start (YYYY-MM-DD)
806
+ endDate: string; // Calendar end (YYYY-MM-DD)
807
+ nights: number; // Number of nights checked
808
+ currency: string; // Currency code
809
+ prices: DatePrice[]; // Price for each date
810
+ lowestPrice: number | null; // Lowest price in range
811
+ lowestPriceDate: string | null; // Date with lowest price
812
+ highestPrice: number | null; // Highest price in range
813
+ highestPriceDate: string | null; // Date with highest price
814
+ averagePrice: number | null; // Average price
815
+ url: string; // Hotel URL
816
+ }
817
+
818
+ interface DatePrice {
819
+ date: string; // Date (YYYY-MM-DD)
820
+ price: number | null; // Price as number
821
+ priceDisplay: string; // Formatted price
822
+ available: boolean; // Whether available
823
+ currency: string; // Currency code
824
+ }
825
+ ```
826
+
827
+ ### Error Response
828
+
829
+ When an error occurs, the response includes:
830
+
831
+ ```typescript
832
+ interface ErrorResponse {
833
+ content: [{
834
+ type: "text";
835
+ text: string; // Error message with code and help text
836
+ }];
837
+ isError: true;
838
+ }
839
+ ```
840
+
841
+ Error codes:
842
+ - `BROWSER_NOT_INITIALIZED` - Call init() first
843
+ - `NAVIGATION_FAILED` - Page load failed
844
+ - `RATE_LIMITED` - Too many requests
845
+ - `CAPTCHA_DETECTED` - CAPTCHA challenge encountered
846
+ - `NO_RESULTS` - Search returned no results
847
+ - `DESTINATION_NOT_FOUND` - Invalid destination
848
+ - `NETWORK_ERROR` - Connection issue
849
+ - `TIMEOUT` - Request timed out
850
+ - `BLOCKED` - Access denied by Booking.com
614
851
 
615
852
  ---
616
853
 
package/dist/browser.d.ts CHANGED
@@ -18,6 +18,7 @@ export declare const ErrorCodes: {
18
18
  readonly NETWORK_ERROR: "NETWORK_ERROR";
19
19
  readonly TIMEOUT: "TIMEOUT";
20
20
  readonly BLOCKED: "BLOCKED";
21
+ readonly INVALID_PARAMS: "INVALID_PARAMS";
21
22
  };
22
23
  export interface HotelSearchParams {
23
24
  destination: string;
@@ -152,6 +153,10 @@ export interface HotelDetails {
152
153
  guestReviewHighlights: string[];
153
154
  locationInfo: string;
154
155
  }
156
+ export interface RoomAmenityCategory {
157
+ category: string;
158
+ items: string[];
159
+ }
155
160
  export interface RoomOption {
156
161
  name: string;
157
162
  price: number | null;
@@ -161,6 +166,8 @@ export interface RoomOption {
161
166
  bedType: string;
162
167
  cancellation: string;
163
168
  breakfast: string;
169
+ roomTypeId?: string;
170
+ amenities?: RoomAmenityCategory[];
164
171
  }
165
172
  export interface AvailabilityResult {
166
173
  available: boolean;
@@ -175,6 +182,31 @@ export interface AvailabilityResult {
175
182
  message: string;
176
183
  url: string;
177
184
  }
185
+ export interface HotelRateFilters {
186
+ breakfast?: boolean;
187
+ freeCancellation?: boolean;
188
+ bedType?: "king" | "queen" | "double" | "twin" | "single";
189
+ }
190
+ export interface HotelRateResult {
191
+ hotelName: string;
192
+ hotelId: string;
193
+ hotelUrl: string;
194
+ checkIn: string;
195
+ checkOut: string;
196
+ guests: number;
197
+ rooms: number;
198
+ roomName: string;
199
+ roomId: string;
200
+ price: number;
201
+ priceDisplay: string;
202
+ pricePerNight: number;
203
+ currency: string;
204
+ mealPlan: string;
205
+ cancellationPolicy: string;
206
+ freeCancellationUntil: string | null;
207
+ bedType: string;
208
+ bedCount: number;
209
+ }
178
210
  export interface Review {
179
211
  title: string;
180
212
  rating: number | null;
@@ -278,9 +310,73 @@ export declare class HotelBrowser {
278
310
  private checkForBlocking;
279
311
  private checkForNoResults;
280
312
  searchHotels(params: HotelSearchParams, filters?: HotelFilters): Promise<HotelResult[]>;
313
+ /**
314
+ * Search for a specific hotel's rate via the search API.
315
+ * This is 100% API-based (no HTML scraping) and returns detailed rate info
316
+ * including room type, meal plan, cancellation policy, and bed configuration.
317
+ *
318
+ * The method searches for the hotel by name and extracts rate details from
319
+ * the Apollo cache's `blocks` array and `matchingUnitConfigurations`.
320
+ *
321
+ * @param hotelUrl - The hotel's URL or name/slug (e.g., "la-sanguine" or full URL)
322
+ * @param checkIn - Check-in date (YYYY-MM-DD)
323
+ * @param checkOut - Check-out date (YYYY-MM-DD)
324
+ * @param guests - Number of guests
325
+ * @param rooms - Number of rooms
326
+ * @param filters - Optional rate filters (breakfast, free cancellation, bed type)
327
+ * @returns Rate details or null if hotel not found in results
328
+ */
329
+ searchHotelRates(hotelUrl: string, checkIn: string, checkOut: string, guests?: number, rooms?: number, filters?: HotelRateFilters): Promise<HotelRateResult | null>;
330
+ /**
331
+ * Extract hotel name/slug from a Booking.com URL.
332
+ * Handles formats like:
333
+ * - https://www.booking.com/hotel/fr/la-sanguine.html
334
+ * - /hotel/fr/la-sanguine.html
335
+ * - la-sanguine
336
+ */
337
+ private extractHotelNameFromUrl;
338
+ /**
339
+ * Extract hotel rate details from Apollo cache.
340
+ * Finds the hotel matching the given name and extracts rate info from
341
+ * the `blocks` array and `matchingUnitConfigurations`.
342
+ */
343
+ private extractHotelRateFromAPI;
281
344
  private dismissPopups;
282
345
  private scrollToLoadMore;
283
346
  private extractHotelDetails;
347
+ /**
348
+ * Extract hotel data from Booking.com's Apollo GraphQL cache.
349
+ * This is more reliable than DOM scraping as it uses structured data.
350
+ * Falls back gracefully if the cache structure changes.
351
+ */
352
+ private extractHotelsFromAPI;
353
+ /**
354
+ * Extract hotel details from Booking.com's Apollo GraphQL cache on a hotel detail page.
355
+ * This is more reliable than DOM scraping as it uses structured data.
356
+ * Returns null if extraction fails (triggering DOM fallback).
357
+ */
358
+ private extractHotelDetailsFromAPI;
359
+ /**
360
+ * Fetch room facilities via Booking.com's GraphQL API.
361
+ * This provides detailed amenities for each room type (AC, TV, bathroom details, etc.)
362
+ * Must be called when already on a hotel page with an active session.
363
+ *
364
+ * @param hotelId - The numeric hotel ID (e.g., 6523595)
365
+ * @param checkIn - Check-in date in YYYY-MM-DD format
366
+ * @param checkOut - Check-out date in YYYY-MM-DD format
367
+ * @returns Map of roomId to array of amenity categories
368
+ */
369
+ private fetchRoomFacilitiesGraphQL;
370
+ /**
371
+ * Extract hotel ID from the current page URL or DOM.
372
+ * Booking.com hotel IDs are typically in the URL path or data attributes.
373
+ */
374
+ private extractHotelId;
375
+ /**
376
+ * Extract reviews data from Booking.com's Apollo GraphQL cache.
377
+ * Returns null if extraction fails (triggering DOM fallback).
378
+ */
379
+ private extractReviewsFromAPI;
284
380
  private scoreAndFilterHotels;
285
381
  getHotelDetails(hotelUrl: string): Promise<Record<string, unknown>>;
286
382
  takeScreenshot(path: string): Promise<void>;