hotelzero 1.3.0 → 1.3.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/dist/browser.d.ts +0 -1
- package/dist/browser.js +26 -21
- package/dist/index.js +5 -14
- package/package.json +1 -1
package/dist/browser.d.ts
CHANGED
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
|
|
254
|
+
const { destination, checkIn, checkOut, guests, rooms, currency, sortBy } = 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,10 +259,6 @@ 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
|
-
}
|
|
266
262
|
// Sort order
|
|
267
263
|
if (sortBy) {
|
|
268
264
|
const sortMap = {
|
|
@@ -625,33 +621,42 @@ export class HotelBrowser {
|
|
|
625
621
|
async scrollToLoadMore(targetResults = 25) {
|
|
626
622
|
if (!this.page)
|
|
627
623
|
return;
|
|
628
|
-
//
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
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++) {
|
|
635
|
-
await this.page.evaluate(() => {
|
|
636
|
-
window.scrollBy(0, window.innerHeight);
|
|
637
|
-
});
|
|
624
|
+
// If we only need 25 or fewer, minimal scrolling
|
|
625
|
+
if (targetResults <= 25) {
|
|
626
|
+
// Just one scroll to ensure initial results are loaded
|
|
627
|
+
await this.page.evaluate(() => window.scrollBy(0, window.innerHeight));
|
|
638
628
|
await this.page.waitForTimeout(1000);
|
|
639
|
-
|
|
629
|
+
await this.page.evaluate(() => window.scrollTo(0, 0));
|
|
630
|
+
return;
|
|
631
|
+
}
|
|
632
|
+
// For larger limits, we need to scroll and click "Load more" multiple times
|
|
633
|
+
// Booking.com loads ~25 results initially, then ~25 more per "Load more" click
|
|
634
|
+
const clicksNeeded = Math.ceil((targetResults - 25) / 25);
|
|
635
|
+
const maxClicks = Math.min(clicksNeeded, 4); // Cap at 4 clicks (~125 results max)
|
|
636
|
+
for (let i = 0; i < maxClicks; i++) {
|
|
637
|
+
// Scroll to bottom to trigger lazy loading and find "Load more" button
|
|
638
|
+
await this.page.evaluate(() => window.scrollTo(0, document.body.scrollHeight));
|
|
639
|
+
await this.page.waitForTimeout(1500);
|
|
640
|
+
// Try to click "Load more" button
|
|
640
641
|
try {
|
|
641
642
|
const loadMoreBtn = await this.page.$('button[data-testid="load-more-results"]');
|
|
642
643
|
if (loadMoreBtn) {
|
|
643
644
|
await loadMoreBtn.click();
|
|
644
|
-
await this.page.waitForTimeout(
|
|
645
|
+
await this.page.waitForTimeout(2000); // Wait for results to load
|
|
646
|
+
}
|
|
647
|
+
else {
|
|
648
|
+
// No more "Load more" button, we've loaded all available results
|
|
649
|
+
break;
|
|
645
650
|
}
|
|
646
651
|
}
|
|
647
652
|
catch {
|
|
648
|
-
//
|
|
653
|
+
// Button not found or click failed
|
|
654
|
+
break;
|
|
649
655
|
}
|
|
650
656
|
}
|
|
651
657
|
// Scroll back to top
|
|
652
|
-
await this.page.evaluate(() =>
|
|
653
|
-
|
|
654
|
-
});
|
|
658
|
+
await this.page.evaluate(() => window.scrollTo(0, 0));
|
|
659
|
+
await this.page.waitForTimeout(500);
|
|
655
660
|
}
|
|
656
661
|
async extractHotelDetails() {
|
|
657
662
|
if (!this.page)
|
package/dist/index.js
CHANGED
|
@@ -42,7 +42,6 @@ const FindHotelsSchema = z.object({
|
|
|
42
42
|
sortBy: z.enum(["popularity", "price_lowest", "price_highest", "rating", "distance"]).optional().describe("Sort results by"),
|
|
43
43
|
// Pagination
|
|
44
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)"),
|
|
46
45
|
// Rating & Price
|
|
47
46
|
minRating: z.number().optional().describe("Minimum rating (6=Pleasant, 7=Good, 8=Very Good, 9=Wonderful)"),
|
|
48
47
|
minPrice: z.number().optional().describe("Minimum price per night"),
|
|
@@ -168,7 +167,7 @@ function formatHotelResult(hotel, index) {
|
|
|
168
167
|
// Create MCP server
|
|
169
168
|
const server = new Server({
|
|
170
169
|
name: "hotelzero",
|
|
171
|
-
version: "1.3.
|
|
170
|
+
version: "1.3.1",
|
|
172
171
|
}, {
|
|
173
172
|
capabilities: {
|
|
174
173
|
tools: {},
|
|
@@ -197,7 +196,6 @@ const findHotelsInputSchema = {
|
|
|
197
196
|
},
|
|
198
197
|
// Pagination
|
|
199
198
|
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 },
|
|
201
199
|
// Property Type
|
|
202
200
|
propertyType: {
|
|
203
201
|
type: "string",
|
|
@@ -360,12 +358,11 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
360
358
|
currency: parsed.currency,
|
|
361
359
|
sortBy: parsed.sortBy,
|
|
362
360
|
limit: parsed.limit,
|
|
363
|
-
offset: parsed.offset,
|
|
364
361
|
};
|
|
365
362
|
// Build filters object from all parsed parameters
|
|
366
363
|
const filters = {};
|
|
367
364
|
// Copy all filter properties (exclude search params)
|
|
368
|
-
const filterKeys = Object.keys(parsed).filter(k => !['destination', 'checkIn', 'checkOut', 'guests', 'rooms', 'currency', 'sortBy', 'limit'
|
|
365
|
+
const filterKeys = Object.keys(parsed).filter(k => !['destination', 'checkIn', 'checkOut', 'guests', 'rooms', 'currency', 'sortBy', 'limit'].includes(k));
|
|
369
366
|
for (const key of filterKeys) {
|
|
370
367
|
const value = parsed[key];
|
|
371
368
|
if (value !== undefined && value !== null) {
|
|
@@ -436,20 +433,14 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
436
433
|
const filtersLine = activeFilters.length > 0
|
|
437
434
|
? `Filters: ${activeFilters.join(", ")}\n\n`
|
|
438
435
|
: "\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
|
-
: "";
|
|
445
436
|
const hotelList = results
|
|
446
|
-
.map((h, i) => formatHotelResult(h, i
|
|
437
|
+
.map((h, i) => formatHotelResult(h, i))
|
|
447
438
|
.join("\n\n");
|
|
448
439
|
return {
|
|
449
440
|
content: [
|
|
450
441
|
{
|
|
451
442
|
type: "text",
|
|
452
|
-
text: header + filtersLine +
|
|
443
|
+
text: header + filtersLine + hotelList,
|
|
453
444
|
},
|
|
454
445
|
],
|
|
455
446
|
};
|
|
@@ -557,7 +548,7 @@ process.on("SIGTERM", async () => {
|
|
|
557
548
|
async function main() {
|
|
558
549
|
const transport = new StdioServerTransport();
|
|
559
550
|
await server.connect(transport);
|
|
560
|
-
console.error("HotelZero v1.3.
|
|
551
|
+
console.error("HotelZero v1.3.1 running on stdio");
|
|
561
552
|
}
|
|
562
553
|
main().catch((error) => {
|
|
563
554
|
console.error("Fatal error:", error);
|