automation
Korail does not want bots on its servers, and between mid-September and mid-October it blocked this one four times. This is a Telegram bot and a small web app that watch KTX and SRT trains for an open seat and send a message when one appears. Most of the engineering turned out to be about losing the arms race on purpose. It runs as one container on my home server.
PythonFastAPIasyncioSSETelegram Bot API
- How it works
- Each watch is an asyncio task that checks its route every 30 seconds and pushes changes to the browser over Server-Sent Events and to Telegram. Watches live in JSON files written atomically, so a restart picks them up and drops any whose date has passed. When SRT booking moved onto Korail's system, two logins and two clients collapsed into one Korail account, which also meant one operator to annoy.
- No arms race
- The line is no proxies, no IP rotation and no reverse-engineering how suspensions are decided, because the penalty lands on the user's Korail account, not on my server. So the bot learned to send less. Requests per account are serialized, spaced out and capped at 40 a minute. A watch for one train fetches the one page around its departure instead of up to 12 pages from midnight. Logins are cached for 10 minutes; before that, every poll logged in again. A suspended account stops for 35 minutes, doubling on repeat, and the cooldown is saved to disk so a restart cannot forget it.
- What I got wrong
- In August I made a search with no direct trains return an empty list instead of an error, so the app would fall back to transfer search. In October, during a block, every failed search looked empty, and each one fired about 52 transfer queries at a server that had just said no. Transfer search is a button now, three runs per account every 10 minutes.