hotelzero 1.7.0 → 1.9.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
@@ -25,6 +25,30 @@ Or run directly with npx:
25
25
  npx hotelzero
26
26
  ```
27
27
 
28
+ ## Proxy Support
29
+
30
+ For heavy usage or to avoid IP blocks, you can configure a proxy server via the `HOTELZERO_PROXY` environment variable:
31
+
32
+ ```bash
33
+ # HTTP proxy
34
+ HOTELZERO_PROXY=http://proxy.example.com:8080 npx hotelzero
35
+
36
+ # HTTP proxy with authentication
37
+ HOTELZERO_PROXY=http://user:pass@proxy.example.com:8080 npx hotelzero
38
+
39
+ # SOCKS5 proxy
40
+ HOTELZERO_PROXY=socks5://proxy.example.com:1080 npx hotelzero
41
+
42
+ # SOCKS5 proxy with authentication
43
+ HOTELZERO_PROXY=socks5://user:pass@proxy.example.com:1080 npx hotelzero
44
+ ```
45
+
46
+ When a proxy is configured, you'll see confirmation in the startup logs:
47
+ ```
48
+ Proxy enabled: http://proxy.example.com:8080
49
+ HotelZero v1.8.0 running on stdio
50
+ ```
51
+
28
52
  ## Quick Start
29
53
 
30
54
  ### Run as MCP Server
@@ -516,6 +540,14 @@ Run `npx playwright install chromium` to install the browser.
516
540
 
517
541
  - Wait a few minutes before retrying
518
542
  - The server uses anti-detection measures, but excessive requests may trigger blocks
543
+ - Consider using a proxy server (see [Proxy Support](#proxy-support))
544
+
545
+ ### Proxy not working
546
+
547
+ - Verify the proxy server is running and accessible
548
+ - Check credentials if using authentication
549
+ - Ensure the proxy supports HTTPS connections
550
+ - Try a different proxy or test without proxy first
519
551
 
520
552
  ---
521
553
 
package/dist/browser.d.ts CHANGED
@@ -1,3 +1,8 @@
1
+ export interface ProxyConfig {
2
+ server: string;
3
+ username?: string;
4
+ password?: string;
5
+ }
1
6
  export declare class HotelSearchError extends Error {
2
7
  code: string;
3
8
  retryable: boolean;
@@ -225,7 +230,16 @@ export declare class HotelBrowser {
225
230
  private page;
226
231
  private lastRequestTime;
227
232
  private minRequestIntervalMs;
228
- init(headless?: boolean): Promise<void>;
233
+ private proxyConfig;
234
+ init(headless?: boolean, proxy?: ProxyConfig): Promise<void>;
235
+ /**
236
+ * Check if a proxy is configured
237
+ */
238
+ hasProxy(): boolean;
239
+ /**
240
+ * Get the current proxy server (without credentials)
241
+ */
242
+ getProxyServer(): string | null;
229
243
  close(): Promise<void>;
230
244
  private buildBookingUrl;
231
245
  private enforceRateLimit;
package/dist/browser.js CHANGED
@@ -232,17 +232,42 @@ export class HotelBrowser {
232
232
  page = null;
233
233
  lastRequestTime = 0;
234
234
  minRequestIntervalMs = 2000; // Minimum 2 seconds between requests
235
- async init(headless = true) {
236
- this.browser = await chromium.launch({
235
+ proxyConfig = null;
236
+ async init(headless = true, proxy) {
237
+ // Store proxy config for reference
238
+ this.proxyConfig = proxy || null;
239
+ // Build launch options
240
+ const launchOptions = {
237
241
  headless,
238
242
  args: ["--disable-blink-features=AutomationControlled"],
239
- });
243
+ };
244
+ // Add proxy to launch options if provided
245
+ if (proxy) {
246
+ launchOptions.proxy = {
247
+ server: proxy.server,
248
+ username: proxy.username,
249
+ password: proxy.password,
250
+ };
251
+ }
252
+ this.browser = await chromium.launch(launchOptions);
240
253
  const context = await this.browser.newContext({
241
254
  userAgent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
242
255
  viewport: { width: 1280, height: 900 },
243
256
  });
244
257
  this.page = await context.newPage();
245
258
  }
259
+ /**
260
+ * Check if a proxy is configured
261
+ */
262
+ hasProxy() {
263
+ return this.proxyConfig !== null;
264
+ }
265
+ /**
266
+ * Get the current proxy server (without credentials)
267
+ */
268
+ getProxyServer() {
269
+ return this.proxyConfig?.server || null;
270
+ }
246
271
  async close() {
247
272
  if (this.browser) {
248
273
  await this.browser.close();
package/dist/index.js CHANGED
@@ -149,10 +149,45 @@ const GetPriceCalendarSchema = z.object({
149
149
  });
150
150
  // Global browser instance (reuse for efficiency)
151
151
  let browser = null;
152
+ /**
153
+ * Parse proxy configuration from environment variable
154
+ * Supports formats:
155
+ * - http://proxy.example.com:8080
156
+ * - http://user:pass@proxy.example.com:8080
157
+ * - socks5://proxy.example.com:1080
158
+ * - socks5://user:pass@proxy.example.com:1080
159
+ */
160
+ function parseProxyFromEnv() {
161
+ const proxyUrl = process.env.HOTELZERO_PROXY;
162
+ if (!proxyUrl)
163
+ return undefined;
164
+ try {
165
+ const url = new URL(proxyUrl);
166
+ const config = {
167
+ server: `${url.protocol}//${url.host}`,
168
+ };
169
+ if (url.username) {
170
+ config.username = decodeURIComponent(url.username);
171
+ }
172
+ if (url.password) {
173
+ config.password = decodeURIComponent(url.password);
174
+ }
175
+ return config;
176
+ }
177
+ catch (error) {
178
+ console.error(`Invalid HOTELZERO_PROXY format: ${proxyUrl}`);
179
+ return undefined;
180
+ }
181
+ }
152
182
  async function getBrowser() {
153
183
  if (!browser) {
154
184
  browser = new HotelBrowser();
155
- await browser.init(true);
185
+ const proxyConfig = parseProxyFromEnv();
186
+ await browser.init(true, proxyConfig);
187
+ // Log proxy status (without credentials)
188
+ if (browser.hasProxy()) {
189
+ console.error(`Proxy enabled: ${browser.getProxyServer()}`);
190
+ }
156
191
  }
157
192
  return browser;
158
193
  }
@@ -511,7 +546,7 @@ function formatPriceCalendarResult(result) {
511
546
  // Create MCP server
512
547
  const server = new Server({
513
548
  name: "hotelzero",
514
- version: "1.7.0",
549
+ version: "1.8.0",
515
550
  }, {
516
551
  capabilities: {
517
552
  tools: {},
@@ -1012,7 +1047,7 @@ process.on("SIGTERM", async () => {
1012
1047
  async function main() {
1013
1048
  const transport = new StdioServerTransport();
1014
1049
  await server.connect(transport);
1015
- console.error("HotelZero v1.7.0 running on stdio");
1050
+ console.error("HotelZero v1.8.0 running on stdio");
1016
1051
  }
1017
1052
  main().catch((error) => {
1018
1053
  console.error("Fatal error:", error);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hotelzero",
3
- "version": "1.7.0",
3
+ "version": "1.9.0",
4
4
  "description": "MCP server for searching hotels on Booking.com with 80+ filters",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -15,7 +15,14 @@
15
15
  "build": "tsc",
16
16
  "start": "node dist/index.js",
17
17
  "dev": "tsx src/index.ts",
18
- "test": "tsx src/test.ts",
18
+ "test": "vitest run",
19
+ "test:watch": "vitest",
20
+ "test:ui": "vitest --ui",
21
+ "test:selectors": "vitest run tests/selectors.test.ts",
22
+ "test:search": "vitest run tests/search.test.ts",
23
+ "test:details": "vitest run tests/details.test.ts",
24
+ "test:reviews": "vitest run tests/reviews.test.ts",
25
+ "test:calendar": "vitest run tests/price-calendar.test.ts",
19
26
  "prepublishOnly": "npm run build"
20
27
  },
21
28
  "keywords": [
@@ -48,7 +55,9 @@
48
55
  },
49
56
  "devDependencies": {
50
57
  "@types/node": "^25.2.3",
58
+ "@vitest/ui": "^4.0.18",
51
59
  "tsx": "^4.21.0",
52
- "typescript": "^5.9.3"
60
+ "typescript": "^5.9.3",
61
+ "vitest": "^4.0.18"
53
62
  }
54
63
  }