hotelzero 1.13.0 → 1.15.1
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 +247 -10
- package/dist/browser.d.ts +96 -0
- package/dist/browser.js +1471 -143
- package/dist/debug-checkin.d.ts +1 -0
- package/dist/debug-checkin.js +31 -0
- package/dist/debug-extraction.d.ts +1 -0
- package/dist/debug-extraction.js +49 -0
- package/dist/debug-search.d.ts +1 -0
- package/dist/debug-search.js +47 -0
- package/dist/explore-cache.d.ts +1 -0
- package/dist/explore-cache.js +78 -0
- package/dist/index.js +2 -2
- package/dist/intercept-test.d.ts +1 -0
- package/dist/intercept-test.js +161 -0
- package/dist/verify-api-extraction.d.ts +1 -0
- package/dist/verify-api-extraction.js +116 -0
- package/package.json +4 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Matt Legrand
|
|
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
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
|
|
599
|
-
- Verify destination
|
|
600
|
-
-
|
|
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
|
-
-
|
|
606
|
-
-
|
|
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
|
|
611
|
-
|
|
612
|
-
-
|
|
613
|
-
|
|
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>;
|