Files
2026-08-09 16:51:01 -05:00

4.4 KiB

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++ (the Discord library).

# 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

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:

./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.