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.
This commit is contained in:
Aculix Technologies committed 2026-08-26 17:54:22 +05:30
1 parent 608221fe18
commit 5f86252df1
4 files changed
+28 -13

No files matched your search

+4 -3
View File
@@ -60,7 +60,8 @@ CURRENCY=₹
STATE_PATH=./state.json # Docker image defaults this to /data/state.json STATE_PATH=./state.json # Docker image defaults this to /data/state.json
PORT=4733 # status endpoint (--loop mode only) PORT=4733 # status endpoint (--loop mode only)
# Routes District checks through a SOCKS5 proxy; notifications stay direct. # Routes both sites' checks through a SOCKS5 proxy; notifications stay direct.
# Needed on datacenter hosts, whose IPs District's WAF blocks. It does nothing for # Both District and BookMyShow block datacenter IPs, so a cloud host usually needs this.
# BookMyShow, which blocks on TLS fingerprint rather than IP (impit handles that). # 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 # PROXY_URL=socks5://user:pass@proxy.example.net:1080
+14 -6
View File
@@ -181,7 +181,7 @@ with comments in [`.env.example`](.env.example).
| `EXPIRY_UTC` | *auto* | When to retire. Defaults to local midnight after `TARGET_DATE`. | | `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 | | `STATE_PATH` | `./state.json` | Where dedupe state lives, `/data/state.json` in Docker |
| `PORT` | `4733` | Status endpoint | | `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 v1 used flat names (`MOVIE_CODE`, `CINEMA_ID` and friends) for District. Those still work, so an
existing `.env` keeps running unchanged. 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. 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. 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 returns HTTP 403
BookMyShow blocks on the TLS fingerprint of the client, not on your IP, which is why plain BookMyShow blocks on two things at once. First the TLS fingerprint of the client, which is why
`curl` and Node's `fetch` both get 403 no matter which headers or cookies you send. A proxy plain `curl` and Node's `fetch` get 403 no matter which headers or cookies you send. That part is
won't help either. This is handled with [impit](https://github.com/apify/impit), which performs handled by [impit](https://github.com/apify/impit), which performs the request with a real Chrome
the request with a real Chrome fingerprint. If you see 403s here, the impersonation has probably fingerprint. Second your IP, if it belongs to a datacenter: BookMyShow 403s cloud hosts even with
stopped matching, so try updating the image or `npm update impit`. 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 ### ntfy returns HTTP 429
+5 -3
View File
@@ -33,9 +33,11 @@ function writeState(cfg, state) {
/** /**
* Routes provider requests through a SOCKS5 proxy. Notifications stay direct, so the * 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 * alerting path never depends on the proxy.
* IP the WAF blocks. It does nothing for BookMyShow, which blocks on TLS fingerprint *
* rather than IP; impit handles that instead. * 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) { async function attachProxy(cfg) {
if (!cfg.proxyUrl) return; if (!cfg.proxyUrl) return;
+5 -1
View File
@@ -18,9 +18,13 @@ let client = null;
async function getClient(cfg) { async function getClient(cfg) {
if (client) return client; if (client) return client;
if (!Impit) ({ Impit } = await import("impit")); 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({ client = new Impit({
browser: "chrome", browser: "chrome",
...(cfg.proxyUrl ? { proxyUrl: cfg.proxyUrl } : {}), ...(proxyUrl ? { proxyUrl } : {}),
}); });
return client; return client;
} }