#pragma once #include #include #include #include #include #include #include "Config.hpp" #include "State.hpp" #include "Scraper.hpp" #include "DiscordNotifier.hpp" // Owns the background watch loop (equivalent to python's `bot_loop`) plus // all the mutating operations the Flask `/api/*` routes used to expose. // The Discord bot's slash-command handlers call straight into this class. class SniperEngine { public: SniperEngine(AppConfig& config, SniperState& state, DiscordNotifier& notifier); ~SniperEngine(); bool start(std::string* error_out = nullptr); void stop(); bool isRunning() const; // Fire-and-forget manual rescan of a single URL (spawns a detached thread, // matching python's api_scan_now()). void scanNow(const std::string& url); bool addLink(LinkConfig link); bool removeLink(const std::string& url); bool togglePause(const std::string& url, bool* now_paused_out = nullptr); std::vector links(); int intervalSeconds() const; void setIntervalSeconds(int seconds); private: AppConfig& config_; SniperState& state_; DiscordNotifier& notifier_; Scraper scraper_; std::mutex links_mtx_; std::thread worker_; std::atomic stop_requested_{false}; void loop(); void runOneScan(const LinkConfig& cfg, bool isManual); static std::string getChangeText(size_t n, const std::optional>& old); };