Hunting Elephants Again: Reviving a Dead Travian Bot with an AI Agent

I had a side project rotting in a private repo for three years. Last week I brought it back to life in a weekend, and an AI agent did most of the boring parts. The project is travian-elephant-finder, a small Node tool for the browser game Travian. On the map, oases are tiles guarded by wild animals. Clear one and you get hero experience and a production bonus. Elephants are the toughest guards, so they give the most hero XP, and if you attack with Cages equipped you capture them as free defender
I had a side project rotting in a private repo for three years. Last week I brought it back to life in a weekend, and an AI agent did most of the boring parts.
The project is travian-elephant-finder, a small Node tool for the browser game Travian. On the map, oases are tiles guarded by wild animals. Clear one and you get hero experience and a production bonus. Elephants are the toughest guards, so they give the most hero XP, and if you attack with Cages equipped you capture them as free defenders instead of killing them. So elephant oases are the best targets on the map. Finding them by hand means opening hundreds of tiles one by one. The tool scans the area around your village and hands you a sorted report of where the elephants are.
Yes, automating Travian is against its terms of service. I run this on a throwaway account, for fun, and the post is about the revival workflow, not a pitch to go bot your favorite game.
Not Laravel, but the revival workflow is pure backend and it travels: a dead scrape swapped for a real API, an expensive loop trimmed by a cheap upfront query. The same moves you would make in any codebase, Laravel included.
Why it died
Games change their frontend, and scrapers rot. My login used to grab a bearer token out of an obfuscated inline <script> on the dashboard page:
// the old "auth": scrape a token out of eval(atob('...')) on /dorf1.php
const line = scripts.find((s) => s.includes('eval'));
const atobString = regExp.exec(line)[1].replace('atob(', '').replace(/'/g, '');
const decoded = Buffer.from(atobString, 'base64').toString('binary');
const token = decoded.split('&&')[1].trim(); // fingers crossed
Enter fullscreen mode Exit fullscreen mode
Travian moved to a new JS bundle and that eval() pattern vanished from the page. The scrape returned nothing, the token refresh silently failed, and the bot just looped 401s forever. Classic dead side project: not broken loudly, broken quietly.
What the AI got right
I pointed Claude Code at the repo and described the symptom: 401 loop, token never refreshes. It found the dead scrape, traced how the game's own client logs in, and replaced the whole fragile thing with the real login API:
const loginResponse = await axiosDefaultInstance.post(`${server}/api/v1/auth/login`, {
name: username,
password,
});
const { code } = loginResponse.data;
// exchange the code for a JWT session cookie, done
Enter fullscreen mode Exit fullscreen mode
No token scrape, no manual cookie-paste from DevTools. This is the kind of work AI is genuinely good at: mechanical, well-defined, annoying. It also redesigned the ugly HTML report and cleaned up the CLI output, again in minutes.
What I still had to figure out
The agent did not hand me the good ideas. The best win in this rewrite was noticing that Travian publishes a public map.sql dump of every village:
curl -s https://ts5.x1.europe.travian.com/map.sql | head
# INSERT INTO x_world VALUES (...); -- villages only, no oases
Enter fullscreen mode Exit fullscreen mode
Villages never sit on oases, so any tile in that dump can be skipped before the scan even opens it. On my run that trimmed about 13% of the HTTP requests. The AI wrote the parser cleanly once I decided to use it, but the "wait, I can rule these tiles out for free" moment was mine. Same with the search model: I chose the "one center point, one distance" design that scans a circle around the village; the agent just implemented it.
That is the split I keep seeing. AI removes the friction. You still bring the judgment.
Lessons Learned
- Scraping JS-injected values is the first thing to break. If the site has a real API, use it. My token scrape was dead code for who knows how long.
-
Rule out work before you loop. One
map.sqlfetch listed every village, so the scan could skip those tiles instead of opening each one. It did not replace the per-tile loop, it pruned it, the same way filtering rows before a loop beats checking each inside it. - Here the AI did the grunt work and I did the thinking. It resurrected the boring 80%. The 20% that made the tool better was still my call.
- A weekend is enough now. Projects I would have left dead are worth reviving when the tedious part is cheap.
TL;DR
I brought travian-elephant-finder back from three years dead in a weekend. An AI agent handled the mechanical rewrites (dead scrape to a real login API, report cleanup), but the calls that actually made it better, like skipping village tiles from the public map.sql to trim the scan and picking the search model, were still mine.
Author's Note
Thanks for sticking around!
Find me on dev.to, linkedin, or you can check out my work on github.
Laravel, after the happy path.



