Moonraker, a Facebook Marketplace bot
A bot that watches one Warhammer Facebook group for Drukhari listings and sends alerts to my phone.
- Keywords watched
- 13
- Check interval
- ~45
- min
- Posts seen per check
- 5–10
- Group size
- 36,000
- members
- Source files
- 1
- Python
What it is
I collect Drukhari, also known as Dark Eldar, a Warhammer 40,000 army. Many of the listings I want appear in a Facebook buy, swap and sell group with about 36,000 members. I wanted to know the moment one appeared, without refreshing the group.
Moonraker watches the Warhammer Australia Buy/Swap/Sell group for keywords and sends a Telegram alert to my iPhone. I built it for myself.
It’s the first of three versions:
- Moonraker. Live, and stable at v1.8. A proof of concept for one group.
- Starscraper. In development. It will search Facebook Marketplace as a whole, for any kind of item.
- Voidwalker. A concept. One bot that watches multiple services.
How it’s built
Moonraker runs in Docker on a Synology DS918+ (Celeron J3455) through Container Manager.
The whole bot is one Python file, warhammer_bot.py. Playwright drives a headless Chromium browser, and Telegram’s Bot API (polling) delivers the alerts. There’s no LLM in it.
Logging in. I export cookies from Edge with export_cookies.py into fb_cookies.json. If the cookies fail, the bot tries to log in again on its own.
Reading the group.
- Each check scrolls with
mouse.wheel(), 20 times. - Post text is the outermost
div[dir='auto']. - Post URLs come from intercepting Facebook’s GraphQL responses, with a DOM function,
findPostUrl(), filling the gaps. - Checks run about every 45 minutes, give or take 5, and pause overnight.
- Images, video, fonts and CSS are blocked, and the browser restarts every 32 checks.
Matching and alerts.
- 13 keywords, matched on whole words.
- Seen posts are stored as MD5 hashes, pruned after 7 days and capped at 5,000.
- Each alert is tagged WTB, SELLING or POST, with up to 4 prices pulled out by regex.
Smaller scripts handle the rest: url_test.py checks URL extraction inside the container, deploy.sh ships a build, and watchers.json holds the config for each watcher. Price and condition filters (max_price, min_price, condition) are planned for Starscraper, not built.
Numbers. Moonraker sees 5 to 10 posts per check. In testing before I switched to Patchright, checks averaged 6.1 to 6.5 posts and found 2 matches in 143 posts. I haven’t recorded total listings, uptime or running cost. Power here is about 45c/kWh.
What went wrong or changed
- Facebook throttled me to 4 to 6 posts per check. Refreshing the cookies changed nothing, so I swapped Playwright for Patchright, a fork built to avoid bot detection. I don’t yet know if that fixed it, and Patchright isn’t pinned to a version.
- Post links have no
hrefin the DOM. I intercept GraphQL instead, withfindPostUrl()as backup. I haven’t validated how often it works. - Non-ASCII text crashed Chromium inside
page.evaluate(). A scan now runs before every delivery. - “venom” matched “venomcrawler”. Word-boundary matching fixed it.
window.scrollBydidn’t trigger infinite scroll.mouse.wheel()does.- Selenium doesn’t support ARM64. I chose Playwright.
- The NAS CPU is too weak to detect login pages. Cookie auth is the primary route.
- SCP is unreliable on the NAS. I use File Station for transfers.
- I said no to two things. I didn’t split the bot into modules, and I didn’t commit a test suite.
If I started again, I’d begin with Patchright, keep secrets in environment variables from day one, and audit the code before shipping. Moving the secrets out of the script is already Phase 1 of the next plan.