hotelzero 1.2.0 → 1.3.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/dist/browser.d.ts +2 -0
- package/dist/browser.js +32 -7
- package/dist/index.js +20 -7
- package/package.json +1 -1
package/dist/browser.d.ts
CHANGED
|
@@ -22,6 +22,8 @@ export interface HotelSearchParams {
|
|
|
22
22
|
rooms: number;
|
|
23
23
|
currency?: string;
|
|
24
24
|
sortBy?: "popularity" | "price_lowest" | "price_highest" | "rating" | "distance";
|
|
25
|
+
limit?: number;
|
|
26
|
+
offset?: number;
|
|
25
27
|
}
|
|
26
28
|
export interface HotelFilters {
|
|
27
29
|
minRating?: number;
|
package/dist/browser.js
CHANGED
|
@@ -251,7 +251,7 @@ export class HotelBrowser {
|
|
|
251
251
|
}
|
|
252
252
|
}
|
|
253
253
|
buildBookingUrl(params, filters) {
|
|
254
|
-
const { destination, checkIn, checkOut, guests, rooms, currency, sortBy } = params;
|
|
254
|
+
const { destination, checkIn, checkOut, guests, rooms, currency, sortBy, offset } = params;
|
|
255
255
|
const url = new URL("https://www.booking.com/searchresults.html");
|
|
256
256
|
url.searchParams.set("ss", destination);
|
|
257
257
|
url.searchParams.set("checkin", checkIn);
|
|
@@ -259,6 +259,10 @@ export class HotelBrowser {
|
|
|
259
259
|
url.searchParams.set("group_adults", guests.toString());
|
|
260
260
|
url.searchParams.set("no_rooms", rooms.toString());
|
|
261
261
|
url.searchParams.set("selected_currency", currency || "USD");
|
|
262
|
+
// Pagination offset
|
|
263
|
+
if (offset && offset > 0) {
|
|
264
|
+
url.searchParams.set("offset", offset.toString());
|
|
265
|
+
}
|
|
262
266
|
// Sort order
|
|
263
267
|
if (sortBy) {
|
|
264
268
|
const sortMap = {
|
|
@@ -576,10 +580,15 @@ export class HotelBrowser {
|
|
|
576
580
|
await this.checkForNoResults();
|
|
577
581
|
// Close any popups/modals
|
|
578
582
|
await this.dismissPopups();
|
|
579
|
-
// Scroll to load more results
|
|
580
|
-
|
|
583
|
+
// Scroll to load more results (pass limit to control how many to load)
|
|
584
|
+
const targetResults = params.limit || 25;
|
|
585
|
+
await this.scrollToLoadMore(targetResults);
|
|
581
586
|
// Extract detailed hotel info
|
|
582
|
-
|
|
587
|
+
let hotels = await this.extractHotelDetails();
|
|
588
|
+
// Apply limit to cap results
|
|
589
|
+
if (params.limit && params.limit > 0) {
|
|
590
|
+
hotels = hotels.slice(0, params.limit);
|
|
591
|
+
}
|
|
583
592
|
// Apply client-side filtering and scoring if we have preferences
|
|
584
593
|
if (filters) {
|
|
585
594
|
return this.scoreAndFilterHotels(hotels, filters);
|
|
@@ -613,15 +622,31 @@ export class HotelBrowser {
|
|
|
613
622
|
}
|
|
614
623
|
}
|
|
615
624
|
}
|
|
616
|
-
async scrollToLoadMore() {
|
|
625
|
+
async scrollToLoadMore(targetResults = 25) {
|
|
617
626
|
if (!this.page)
|
|
618
627
|
return;
|
|
619
|
-
//
|
|
620
|
-
|
|
628
|
+
// Calculate how many scroll iterations needed
|
|
629
|
+
// Each scroll typically loads ~15-25 more results
|
|
630
|
+
// We start with ~25, so to get to targetResults we need (targetResults - 25) / 20 more scrolls
|
|
631
|
+
const scrollsNeeded = Math.max(1, Math.ceil((targetResults - 25) / 20));
|
|
632
|
+
const maxScrolls = Math.min(scrollsNeeded, 5); // Cap at 5 scrolls to avoid excessive loading
|
|
633
|
+
// Scroll down to load more results
|
|
634
|
+
for (let i = 0; i < maxScrolls; i++) {
|
|
621
635
|
await this.page.evaluate(() => {
|
|
622
636
|
window.scrollBy(0, window.innerHeight);
|
|
623
637
|
});
|
|
624
638
|
await this.page.waitForTimeout(1000);
|
|
639
|
+
// Check if "Load more" button exists and click it
|
|
640
|
+
try {
|
|
641
|
+
const loadMoreBtn = await this.page.$('button[data-testid="load-more-results"]');
|
|
642
|
+
if (loadMoreBtn) {
|
|
643
|
+
await loadMoreBtn.click();
|
|
644
|
+
await this.page.waitForTimeout(1500);
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
catch {
|
|
648
|
+
// Ignore if button not found
|
|
649
|
+
}
|
|
625
650
|
}
|
|
626
651
|
// Scroll back to top
|
|
627
652
|
await this.page.evaluate(() => {
|
package/dist/index.js
CHANGED
|
@@ -40,6 +40,9 @@ const FindHotelsSchema = z.object({
|
|
|
40
40
|
// Currency & Sorting
|
|
41
41
|
currency: z.string().optional().describe("Currency code (USD, EUR, GBP, JPY, etc.)"),
|
|
42
42
|
sortBy: z.enum(["popularity", "price_lowest", "price_highest", "rating", "distance"]).optional().describe("Sort results by"),
|
|
43
|
+
// Pagination
|
|
44
|
+
limit: z.number().min(1).max(100).optional().describe("Maximum number of results to return (default: 25, max: 100)"),
|
|
45
|
+
offset: z.number().min(0).optional().describe("Number of results to skip for pagination (default: 0)"),
|
|
43
46
|
// Rating & Price
|
|
44
47
|
minRating: z.number().optional().describe("Minimum rating (6=Pleasant, 7=Good, 8=Very Good, 9=Wonderful)"),
|
|
45
48
|
minPrice: z.number().optional().describe("Minimum price per night"),
|
|
@@ -165,7 +168,7 @@ function formatHotelResult(hotel, index) {
|
|
|
165
168
|
// Create MCP server
|
|
166
169
|
const server = new Server({
|
|
167
170
|
name: "hotelzero",
|
|
168
|
-
version: "1.
|
|
171
|
+
version: "1.3.0",
|
|
169
172
|
}, {
|
|
170
173
|
capabilities: {
|
|
171
174
|
tools: {},
|
|
@@ -192,6 +195,9 @@ const findHotelsInputSchema = {
|
|
|
192
195
|
description: "Sort results by",
|
|
193
196
|
enum: ["popularity", "price_lowest", "price_highest", "rating", "distance"]
|
|
194
197
|
},
|
|
198
|
+
// Pagination
|
|
199
|
+
limit: { type: "number", description: "Maximum results to return (default: 25, max: 100)", default: 25 },
|
|
200
|
+
offset: { type: "number", description: "Number of results to skip for pagination", default: 0 },
|
|
195
201
|
// Property Type
|
|
196
202
|
propertyType: {
|
|
197
203
|
type: "string",
|
|
@@ -353,11 +359,13 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
353
359
|
rooms: parsed.rooms,
|
|
354
360
|
currency: parsed.currency,
|
|
355
361
|
sortBy: parsed.sortBy,
|
|
362
|
+
limit: parsed.limit,
|
|
363
|
+
offset: parsed.offset,
|
|
356
364
|
};
|
|
357
365
|
// Build filters object from all parsed parameters
|
|
358
366
|
const filters = {};
|
|
359
|
-
// Copy all filter properties
|
|
360
|
-
const filterKeys = Object.keys(parsed).filter(k => !['destination', 'checkIn', 'checkOut', 'guests', 'rooms', 'currency', 'sortBy'].includes(k));
|
|
367
|
+
// Copy all filter properties (exclude search params)
|
|
368
|
+
const filterKeys = Object.keys(parsed).filter(k => !['destination', 'checkIn', 'checkOut', 'guests', 'rooms', 'currency', 'sortBy', 'limit', 'offset'].includes(k));
|
|
361
369
|
for (const key of filterKeys) {
|
|
362
370
|
const value = parsed[key];
|
|
363
371
|
if (value !== undefined && value !== null) {
|
|
@@ -428,15 +436,20 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
428
436
|
const filtersLine = activeFilters.length > 0
|
|
429
437
|
? `Filters: ${activeFilters.join(", ")}\n\n`
|
|
430
438
|
: "\n";
|
|
439
|
+
// Pagination info
|
|
440
|
+
const offset = parsed.offset || 0;
|
|
441
|
+
const displayLimit = parsed.limit || 25;
|
|
442
|
+
const paginationLine = offset > 0
|
|
443
|
+
? `Showing results ${offset + 1}-${offset + results.length} of available hotels\n\n`
|
|
444
|
+
: "";
|
|
431
445
|
const hotelList = results
|
|
432
|
-
.
|
|
433
|
-
.map((h, i) => formatHotelResult(h, i))
|
|
446
|
+
.map((h, i) => formatHotelResult(h, i + offset))
|
|
434
447
|
.join("\n\n");
|
|
435
448
|
return {
|
|
436
449
|
content: [
|
|
437
450
|
{
|
|
438
451
|
type: "text",
|
|
439
|
-
text: header + filtersLine + hotelList,
|
|
452
|
+
text: header + filtersLine + paginationLine + hotelList,
|
|
440
453
|
},
|
|
441
454
|
],
|
|
442
455
|
};
|
|
@@ -544,7 +557,7 @@ process.on("SIGTERM", async () => {
|
|
|
544
557
|
async function main() {
|
|
545
558
|
const transport = new StdioServerTransport();
|
|
546
559
|
await server.connect(transport);
|
|
547
|
-
console.error("HotelZero v1.
|
|
560
|
+
console.error("HotelZero v1.3.0 running on stdio");
|
|
548
561
|
}
|
|
549
562
|
main().catch((error) => {
|
|
550
563
|
console.error("Fatal error:", error);
|