- Python 100%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
| .gitignore | ||
| config.example.json | ||
| README.md | ||
| spawn-bot.service | ||
| spawn_bot.py | ||
spawn_bot
Watches DemonicScans active_wave pages and posts boss alerts to Discord. Two
kinds of message:
- 🔥 SPAWNED — fires the instant a boss goes live in the wave, with a direct
JOIN THE BATTLE link to its boss page (
battle.php?id=<id>) so users click straight into the fight. Deduped by battle id: one alert per boss instance, suppressed for as long as that instance is listed (the card'sdata-expirelifetime — 2h to 48h depending on the boss). A respawn carries a new id, so it alerts again. - 👑 upcoming (optional,
notify_upcoming) — advance "⏳ spawns in Xh" heads-up for bosses whose respawn timer is still in the future. Deduped by wave + boss + spawn-timestamp, so a new respawn cycle re-alerts.
Trash mobs are ignored. Upcoming alerts read the page's "Auto Summon Timers" section. For the SPAWNED alert the boss is identified in the monster grid structurally — a lone card (trash spawn in groups) whose max HP dwarfs every other card — because a boss's grid name/image can differ from its timer entry (e.g. a boss listed as one "form" in the timer fights under a different name in the grid), so name matching is unreliable.
About the "button"
Your webhook is a plain channel incoming webhook (application_id: null), and
Discord only allows interactive button components on application-owned
webhooks. So the join action is a prominent clickable link in the embed
(title + a large "⚔️ JOIN THE BATTLE ▶" line), which drops users straight on the
boss page. If you want a literal button, recreate the webhook through a Discord
bot application and it'll light up automatically.
Why not plain cron?
Boss respawn times shift per cycle, so a fixed cron interval is awkward. The bot
polls on an interval and dedupes via state.json, firing exactly once per
scheduled spawn regardless of timing. Run it as a long-lived watcher
(systemd). A one-shot mode is still provided if you'd rather drive it from cron.
Setup
cp config.example.json config.json(already populated for you) and lock it down:chmod 600 config.json- Fill in
webhook_url,cookie, and thetargetsyou want to watch.
Run
./spawn_bot.py --watch # recommended: poll forever
./spawn_bot.py # one poll cycle then exit (for cron)
./spawn_bot.py --dry-run # parse + log, never post to Discord (test parsing)
./spawn_bot.py --reset-state # forget seen monsters; next run re-baselines
On first run, any boss that is already live is announced as SPAWNED and any with
a scheduled future spawn is announced as upcoming; after that it only fires on
new events. Set notify_upcoming: false to send only the SPAWNED-now join alert.
As a systemd (user) service — recommended
mkdir -p ~/.config/systemd/user
cp spawn-bot.service ~/.config/systemd/user/
systemctl --user daemon-reload
systemctl --user enable --now spawn-bot
journalctl --user -u spawn-bot -f # follow logs
(loginctl enable-linger $USER if you want it running while logged out.)
Via cron (one-shot each minute)
* * * * * cd /home/<USERNAME>/workspace/forgejo/<USERNAME>/spawn_bot && /usr/bin/python3 spawn_bot.py >> spawn_bot.log 2>&1
Cron's floor is 1 minute; a spawn shorter than that could be missed. The watcher is preferred.
The Cloudflare cookie caveat (important)
The page is behind Cloudflare. The cf_clearance cookie in config.json is tied
to your IP + User-Agent and expires (~30–60 min typical). The bot cannot
self-renew it — that needs a real browser to solve the challenge. When it
lapses, the bot detects the challenge page and sends a one-time Discord alert
telling you to refresh cookie. To refresh: reopen the wave page in Firefox,
copy the request as cURL, and paste the new Cookie value into config.json.
Files
spawn_bot.py— the watcher (stdlib only; shells tocurlfor the CF-guarded fetch)config.json— your secrets/targets (chmod 600, git-ignored)config.example.json— annotated templatestate.json— already-announced boss spawns (auto-created)spawn-bot.service— systemd user unit