From 5f86252df1173e2fa7a20197f089b39050a211d1 Mon Sep 17 00:00:00 2001 From: Aculix Technologies Date: Wed, 26 Aug 2026 17:54:22 +0530 Subject: [PATCH] fix: make the SOCKS5 proxy work for BookMyShow too BookMyShow blocks datacenter IPs on top of TLS fingerprinting, confirmed by CI runners getting 403 with a working impersonation, so cloud hosts need the proxy for BMS as well as District. impit resolves DNS locally for socks5:// and hands an IPv6-only exit an unreachable IPv4 address, so the scheme is normalized to socks5h://. Verified end to end: a proxied BMS check returns 200 and alerts. Docs said the proxy did nothing for BookMyShow, which was wrong. --- .env.example | 7 ++++--- README.md | 20 ++++++++++++++------ src/index.js | 8 +++++--- src/providers/bookmyshow.js | 6 +++++- 4 files changed, 28 insertions(+), 13 deletions(-) diff --git a/.env.example b/.env.example index ffa776e..c44b3e5 100644 --- a/.env.example +++ b/.env.example @@ -60,7 +60,8 @@ CURRENCY=₹ STATE_PATH=./state.json # Docker image defaults this to /data/state.json PORT=4733 # status endpoint (--loop mode only) -# Routes District checks through a SOCKS5 proxy; notifications stay direct. -# Needed on datacenter hosts, whose IPs District's WAF blocks. It does nothing for -# BookMyShow, which blocks on TLS fingerprint rather than IP (impit handles that). +# Routes both sites' checks through a SOCKS5 proxy; notifications stay direct. +# Both District and BookMyShow block datacenter IPs, so a cloud host usually needs this. +# socks5:// is rewritten to socks5h:// internally, so the proxy resolves DNS. That is +# required for IPv6-only exits and harmless otherwise. # PROXY_URL=socks5://user:pass@proxy.example.net:1080 diff --git a/README.md b/README.md index a36dff6..9089cac 100644 --- a/README.md +++ b/README.md @@ -181,7 +181,7 @@ with comments in [`.env.example`](.env.example). | `EXPIRY_UTC` | *auto* | When to retire. Defaults to local midnight after `TARGET_DATE`. | | `STATE_PATH` | `./state.json` | Where dedupe state lives, `/data/state.json` in Docker | | `PORT` | `4733` | Status endpoint | -| `PROXY_URL` | | SOCKS5 proxy for District checks, see below | +| `PROXY_URL` | | SOCKS5 proxy for both sites' checks, see below | v1 used flat names (`MOVIE_CODE`, `CINEMA_ID` and friends) for District. Those still work, so an existing `.env` keeps running unchanged. @@ -210,13 +210,21 @@ permanently, while a home connection was never touched once. Either run it from connection, or point `PROXY_URL` at a SOCKS5 proxy. Only the site checks go through the proxy. Notifications stay direct, so your alerts don't depend on it. +One gotcha if your proxy is IPv6-only: a `socks5://` URL makes some clients resolve DNS locally +and hand the proxy an IPv4 address it cannot reach. The monitor rewrites the scheme to `socks5h://` +so the proxy does the resolving, which fixes that and changes nothing for other proxies. + ### BookMyShow returns HTTP 403 -BookMyShow blocks on the TLS fingerprint of the client, not on your IP, which is why plain -`curl` and Node's `fetch` both get 403 no matter which headers or cookies you send. A proxy -won't help either. This is handled with [impit](https://github.com/apify/impit), which performs -the request with a real Chrome fingerprint. If you see 403s here, the impersonation has probably -stopped matching, so try updating the image or `npm update impit`. +BookMyShow blocks on two things at once. First the TLS fingerprint of the client, which is why +plain `curl` and Node's `fetch` get 403 no matter which headers or cookies you send. That part is +handled by [impit](https://github.com/apify/impit), which performs the request with a real Chrome +fingerprint. Second your IP, if it belongs to a datacenter: BookMyShow 403s cloud hosts even with +a perfect fingerprint, which I confirmed from GitHub Actions runners. + +So on a cloud host you need both, and setting `PROXY_URL` covers the second half. If you are still +getting 403s from a residential connection, the impersonation has probably stopped matching the +current Chrome, so update the image or run `npm update impit`. ### ntfy returns HTTP 429 diff --git a/src/index.js b/src/index.js index b04f117..c71f2dc 100644 --- a/src/index.js +++ b/src/index.js @@ -33,9 +33,11 @@ function writeState(cfg, state) { /** * Routes provider requests through a SOCKS5 proxy. Notifications stay direct, so the - * alerting path never depends on the proxy. District needs this on datacenter hosts whose - * IP the WAF blocks. It does nothing for BookMyShow, which blocks on TLS fingerprint - * rather than IP; impit handles that instead. + * alerting path never depends on the proxy. + * + * Both sites block datacenter IPs, so a cloud host generally needs this for both. It is + * only half the story for BookMyShow, which also blocks on TLS fingerprint: impit deals + * with the fingerprint, the proxy deals with the IP, and BMS needs both. */ async function attachProxy(cfg) { if (!cfg.proxyUrl) return; diff --git a/src/providers/bookmyshow.js b/src/providers/bookmyshow.js index 8b30e37..1c187df 100644 --- a/src/providers/bookmyshow.js +++ b/src/providers/bookmyshow.js @@ -18,9 +18,13 @@ let client = null; async function getClient(cfg) { if (client) return client; if (!Impit) ({ Impit } = await import("impit")); + // With socks5:// impit resolves DNS locally and asks the proxy for that address, + // which fails outright on an IPv6-only exit that cannot reach the IPv4 we picked. + // socks5h:// leaves resolution to the proxy, so normalize to it. + const proxyUrl = cfg.proxyUrl ? cfg.proxyUrl.replace(/^socks5:\/\//i, "socks5h://") : ""; client = new Impit({ browser: "chrome", - ...(cfg.proxyUrl ? { proxyUrl: cfg.proxyUrl } : {}), + ...(proxyUrl ? { proxyUrl } : {}), }); return client; }