103 lines
4.4 KiB
Markdown
103 lines
4.4 KiB
Markdown
# LZT Market Sniper — C++ rewrite
|
|
|
|
A from-scratch C++ port of the original single-file `app.py` (Flask +
|
|
Selenium + a mobile web dashboard). This version is a native background
|
|
service with no browser/webpage at all — you control it entirely from
|
|
Discord with slash commands.
|
|
|
|
## What changed vs. the Python version
|
|
|
|
| Python (`app.py`) | C++ rewrite |
|
|
|---|---|
|
|
| Flask web server + mobile HTML/CSS/JS dashboard | Gone. Control is via Discord slash commands instead. |
|
|
| Selenium driving a real (headless) Chrome profile | `libcurl` HTTP GET of the search page's raw HTML (no JS execution — see **Scraping caveat** below). |
|
|
| Discord **webhook** only | Bot posts through its own REST API (`/channels/{id}/messages`) using the bot token; a legacy webhook URL is still supported as a fallback. |
|
|
| In-memory `state` dict, SSE stream to browser | Thread-safe `SniperState`, no streaming needed since Discord is the UI. |
|
|
| One 1185-line file | Split into `Config`, `State`, `Logger`, `HttpClient`, `Scraper`, `DiscordNotifier`, `SniperEngine`, `DiscordBot`, `main`. |
|
|
|
|
## Layout
|
|
|
|
```
|
|
include/ headers (one per component)
|
|
Config.hpp JSON config load/save (bot token, links, filters, interval)
|
|
State.hpp thread-safe shared state (running flag, last offers, history)
|
|
Logger.hpp timestamped console logger
|
|
HttpClient.hpp libcurl GET/POST wrapper
|
|
Scraper.hpp page fetch + offer-card text extraction + price/keyword filter
|
|
DiscordNotifier.hpp builds/sends the stock-update embed
|
|
SniperEngine.hpp the watch loop + all mutating operations (start/stop/add/remove/pause/scan)
|
|
DiscordBot.hpp D++ slash-command bot wired into SniperEngine
|
|
src/ same files, implementations
|
|
CMakeLists.txt
|
|
config.example.json
|
|
```
|
|
|
|
## Building
|
|
|
|
Dependencies: a C++17 compiler, CMake, `libcurl`, `nlohmann-json`, and
|
|
[D++](https://dpp.dev/) (the Discord library).
|
|
|
|
```bash
|
|
# Debian/Ubuntu
|
|
sudo apt install build-essential cmake libcurl4-openssl-dev nlohmann-json3-dev
|
|
|
|
mkdir build && cd build
|
|
cmake ..
|
|
make -j$(nproc)
|
|
```
|
|
|
|
CMake first looks for an installed D++ (`find_package(DPP)`); if it's not
|
|
found it downloads and builds D++ v10.0.35 from source automatically via
|
|
`FetchContent` the first time you configure (this takes a few minutes — D++
|
|
is a fairly large library). If you'd rather build D++ once and reuse it,
|
|
follow the install instructions at https://dpp.dev/installing.html and
|
|
CMake will pick up the installed copy instead.
|
|
|
|
## Configuring
|
|
|
|
```bash
|
|
cp config.example.json config.json
|
|
```
|
|
|
|
Fill in:
|
|
- `discord_bot_token` — from the Discord Developer Portal, with the
|
|
`applications.commands` and `bot` scopes and the "Send Messages" permission.
|
|
- `discord_channel_id` — the channel the bot should post stock-change alerts to.
|
|
- `command_guild_id` *(optional)* — set this to your server's ID while
|
|
testing so slash commands sync instantly; leave blank for global commands
|
|
(Discord can take up to an hour to propagate those).
|
|
- `links` — your initial watch list (you can also manage this entirely from
|
|
Discord with `/addlink`, `/removelink`, `/pause` once the bot is running).
|
|
|
|
Run it:
|
|
|
|
```bash
|
|
./build/lzt_sniper config.json
|
|
```
|
|
|
|
## Discord commands
|
|
|
|
- `/start` — begin watching all configured links
|
|
- `/stop` — stop watching
|
|
- `/status` — running state, total scans/changes, current stock per link
|
|
- `/links` — list watched links with pause state and filters
|
|
- `/addlink url [label] [min_price] [max_price] [keywords]`
|
|
- `/removelink url`
|
|
- `/pause url` — toggle pause on one link
|
|
- `/scan url` — force an immediate one-off scan
|
|
- `/interval seconds` — change the polling interval
|
|
- `/history` — last 15 scan results
|
|
|
|
## Scraping caveat
|
|
|
|
The Python version drove a real headless Chrome browser through a
|
|
logged-in profile, so it saw the fully JS-rendered, authenticated page. This
|
|
C++ version fetches the raw page HTML over HTTP instead — no JavaScript is
|
|
executed. LZT Market's search results are server-rendered, so public
|
|
listings work fine; if a page ever needs an authenticated session, copy your
|
|
browser's `Cookie:` header value into `cookie_header` in `config.json`. The
|
|
offer-card extraction (`Scraper::extractOfferBlocks`) is a heuristic text
|
|
parser tuned to reproduce the original's "any div/li/article whose text
|
|
mentions 'followers'" logic — inspect it and adjust the tag/keyword filters
|
|
in `src/Scraper.cpp` if LZT Market's markup changes.
|