The short version
The extension is a timer plus a polite watcher. It waits until the exact second your tickets go on sale, asks the ticket website “is my train open yet?” about once a second, and when the answer is yes it rings an alarm and points at the buttons you need to press. It never presses them for you.
It doesn't try to look like a human, and it doesn't try to hide from bot detection. It stays safe by asking rarely, for a short time, only when there's a reason to, and by staying completely out of the site's bot check.
The pieces
| File | Runs where | Job |
|---|---|---|
popup.html / popup.js | The toolbar panel | Calendar, route/train/seat form, ARM, countdown, saved trips, history, English/Bangla. |
background.js | Service worker | Works out the release time, sets chrome.alarms (main + 1-day + 1-hour reminders), opens the site tab ~75 s early, shows notifications, starts/stops the alarm sound. |
content.js | Inside eticket.railway.gov.bd | The watcher: login check, clock sync, polite search loop, status box, BOOK NOW glow, seat outlining, seat-direction guess. |
offscreen.html / offscreen.js | Hidden extension page | Plays the siren (Web Audio oscillator). |
rail-data.js | Popup, worker, page | Bundled station list, train names, and each train's release time and zone (EAST 2 PM / WEST 8 AM). |
i18n.js | Popup | English and Bangla text. |
Timeline of one run
ARM pressed (popup)
└─ background: opening day = journey date − 10 days
check 1 = 8:00 AM that day (normal-day opening, every train)
check 2 = 2:00 PM, East trains only (the Eid-time opening)
sets alarms at T−24h (reminder) · T−1h (reminder) · T−75s (launch), T = check 1
check 1 fires → check 2's alarm is set; seats found → check 2 is cancelled
T−75s background opens (or reuses) a eticket.railway.gov.bd tab and saves a "mission"
content.js picks up the mission:
1. logged in? no → alarm + notification, stop
2. date released inside this run's window? no → stop (don't search for nothing)
3. one request for a small static file → read the server's Date header
→ estimate the clock difference (about ±0.5 s)
T−0.5s search loop: GET /bookings/search-trips-v2 (the same call the site's Search button makes)
wait 700 ms + random 0–250 ms between searches (≈ 1 per second)
429 "too many requests" → wait 2 s, 4 s, 8 s, 15 s …
401 "logged out" → alarm + notification, stop
5 minutes passed → give up, notify
Seats! save a short "found" note, send a notification,
load the site's own results page for that train and class
Reload content.js sees the note → alarm on, scroll to the train, make BOOK NOW glow
User clicks BOOK NOW → the site loads the seat map (and runs its own bot check — untouched)
content.js watches the page (MutationObserver) → outlines the best free seats
and guesses which way each faces (⬆ / ⬇ / ⬆→⬇)
← zero network requests from the extension in this step
User clicks seats → CONTINUE PURCHASE → OTP → pays
Where the login comes from
You log in on the website as usual. The website keeps your session in the page's localStorage
(a token plus a device ID and key). The watcher reads those and sends them with its searches as the same
headers the website itself uses (Authorization, X-Device-Id, X-Device-Key).
They are only ever sent to the railway's own ticketing API (the one eticket.railway.gov.bd uses), never stored by the extension and never sent anywhere else.
Your password is never seen.
Why the alarm plays from a hidden page
Browsers block sound on a page until you click it (the “autoplay policy”). The tab that opens at release time was opened automatically, so it isn't allowed to make noise. Extensions are allowed to play sound from an offscreen document, so the siren runs there instead. That's also what Tools → Test alarm sound uses, so the test is honest.
“Does it act like a human?” — honestly, no
It helps to be precise here, because “mimicking humans” usually means tricks to fool bot detection. This extension doesn't do those, on purpose.
What it does
- Calls the same search endpoint the site's Search button calls, from the site's own tab, with your own session.
- Searches about once a second — roughly a person hammering refresh — with a small random gap (0–250 ms) so it isn't a perfect metronome.
- Runs only for a short window (a few minutes at most) right at release time.
What it doesn't do
- No fake mouse movement, fake typing, browser-fingerprint changes or user-agent tricks.
- No extra accounts, proxies or IP rotation.
- No solving, skipping, generating or replaying the site's bot-check (Cloudflare Turnstile) token.
- No automatic login, OTP entry, seat reserving or payment.
What it does to avoid getting you blocked
| Measure | Why | Where |
|---|---|---|
| Refuses to start before release | If the date won't open within this run's window, searching is pointless and only adds up requests. The run stops with a message. | content.js · runEngine() |
| Starts 0.5 s before release, on the server's clock | No early searching “just in case”. Clock offset comes from one HTTP Date header. | serverOffsetMs(), startLeadMs |
| ≈ 1 search per second | 700 ms + 0–250 ms random, plus request time. Configurable (minimum 300 ms) — don't lower it. | pollIntervalMs |
| Backs off on 429 | When the server says “too frequent”, waits 2 → 4 → 8 → 15 s before trying again. | search loop |
| Stops at the first hit | As soon as your class shows online seats, searching ends. | search loop |
| Hard time limit | Gives up after 5 minutes (max 15). Worst case ≈ 300 searches. | maxWaitMinutes |
| Stops on 401 | A logged-out session isn't retried. | search loop |
| Seat help is offline | Seat outlining only reads the page that's already loaded. Switching coaches makes no requests. The seat-direction guess is worked out from the route and the map, with no lookups. | suggestSeats() |
| Never touches the bot check | The seat-map request needs a Turnstile token. The extension never calls it — you open the seat map with your click. | — |
| STOP button everywhere | Panel and on-page status box both abort immediately. | stopRun |
Your side of it: don't test repeatedly on dates that aren't on sale, don't arm two computers for the same trip, and don't open many ticket-site tabs during a run.
Limitations
- You are the speed limit after the alarm. BOOK NOW → seat map → seats → CONTINUE → OTP → payment is all you. The extension only saves the seconds of noticing and finding your train. Seats can go while you click.
- It relies on an undocumented website. The search API's response shape and the page's CSS classes
(
.book-now-btn,.btn-seat,seat-available…) can change at any time. If they do, the glow or the seat outlines quietly stop working. Do a Test run now before an important trip. - Release times follow the railway's rules, which can change. Normal days: 8 AM for every train. Eid: WEST 8 AM, EAST 2 PM
(the railway's own data,
/all-trains/info, carries this Eid split). At 12 AM only the calendar date appears (update_counter_on = 00:00:00), with no trains. Zones are a bundled table inrail-data.js— if the rules change, update it or set a fixed time under Advanced. - The computer has to be on. A sleeping laptop or a closed browser means the alarm fires late or not at all.
- One trip per ARM. One route, date and train. The search asks for your first class; the others in your list are picked from the same results.
- Online seats only. The site's numbers mix counter and online quotas; only online seats count.
- Seat direction is a rule of thumb, and it steers the picks.
pickByLayout()ranks seats in the likely-forward half (forwardHalf()) ahead of seats nearer the middle in the other half.facingGuess()uses what Bangla travel sites agree on: leaving Dhaka, the higher-numbered half (31–60) faces forward; going to Dhaka, the lower half (1–30). The coach is split at the seat map's middle gap (pickByLayout()), not at a fixed seat number. Trips to or from the Cox's Bazar line flip at Chattogram. Routes that don't start or end in Dhaka get no guess. Nothing official backs this, and a coach attached the other way round makes it wrong. - Seat outlines see one coach at a time — the one on screen. The “fuller coach” hint comes from the coach dropdown.
- Clock accuracy is about half a second. Plenty for this, but not millisecond-exact.
- Desktop only. No phone browsers.
Drawbacks and risks
- No guarantee against blocks. One search a second for a few minutes is faster than most people click. If the railway's system tightens its limits you can still get a temporary “requesting too frequently” block. Account action is unlikely at this volume, but nobody can promise it.
- It's visible. As described above, the pattern is recognisable. If the railway decides to block tools like this, it will stop working, and there's no plan to fight that.
- Session access. Like any extension that can read the site, it can see your login session. This one only sends it to the railway's ticketing servers. The code is short and open — read it before installing.
- Rules. Check the ticket website's terms yourself. This is meant for buying your own tickets, not reselling.
- Fairness at scale. If everyone ran it, nobody would have an edge and the server would get busier. That's why the extension stays modest, and why a future Web Store listing is planned as unlisted.
Working on the code
- Clone the repo. The extension is in the
shohoz-autobook/folder. brave://extensions→ Developer mode → Load unpacked → choose that folder.- After editing, press ↻ on the extension card and reload the ticket-site tab.
- Test on a date that's already on sale with ▶ Test run now. Open the status box's details ▾ for the log.
Useful constants
| Setting | Default | Where |
|---|---|---|
| Open the tab before release | 75 s | PRELAUNCH_LEAD_MS in background.js |
| Start searching before release | 500 ms | startLeadMs in background.js defaults |
| Gap between searches | 700 ms + 0–250 ms | pollIntervalMs (panel → Advanced) |
| Give up after | 5 min | maxWaitMinutes (panel → Advanced) |
| “Seats open” handoff stays active | 15 min | ASSIST_TTL_MS in content.js |
| Sale window | 10 days | SALE_WINDOW_DAYS |
Contributing
Issues and pull requests are welcome — especially fixes when the site's layout changes, release-time updates and translation fixes. Changes that get around the bot check, automate login/OTP/payment or raise the request rate won't be accepted.