A Rocket-based REST API for managing Black Desert Online node war data, guild member profiles, and weapon metadata for the NodewarBot project. The service persists data in SQLite, exposes ingestion and query endpoints, and offloads OCR-based screenshot parsing to a companion Python script.
- Rust (Rocket 0.5) API with JSON endpoints for guild stats, player profiles, and weapons
- SQLite
weapons.dbdatastore auto-initialised with tables for weapons, node war logs, and profiles - Stats ingestion from node war screenshots via OpenCV + Tesseract (Python) pipeline
- Multipart weapon upload flow that stores metadata locally and forwards images to an external uploader
- Ready-to-run Docker image bundling Rust binary, Python runtime, and OCR dependencies
src/– Rocket entrypoint (main.rs) and endpoint modules for weapons, node wars, and profilespython_image_recognition/– OCR script invoked during screenshot ingestionweapons.db– SQLite database file; created/populated on first launch if missingDockerfile,docker-compose.yml– Container build and local orchestration definitions
- Rust toolchain (edition 2021)
- Python 3.9+
- SQLite 3
- Tesseract OCR & OpenCV libraries (used by the Python helper)
Ubuntu/Debian example:
sudo apt-get update
sudo apt-get install -y tesseract-ocr libtesseract-dev libopencv-dev python3-opencv sqlite3
pip install numpy pytesseract opencv-python- Ensure
pythonon your PATH points to Python 3 and that the dependencies above are installed. - Launch the API:
cargo run
- Rocket serves on
http://localhost:8000by default (ROCKET_ADDRESS/PORTenv vars override this).
The first launch creates the weapon, nodewar, and profile tables in weapons.db if they do not yet exist.
docker compose up --buildThe compose file publishes port 8000, mounts a named volume for persisting weapons.db, and runs the compiled Rust binary inside a Python base image with OCR tooling pre-installed.
- API keys are in-memory (
AppState::valid_api_keys) and default toapi_key_1andapi_key_2. Adjust them insrc/main.rsbefore deploying. handle_image_uploadposts weapon images tohttp://localhost:8080/uploadsand expects a plain-text response containing a line beginning withURL:. Provide a compatible uploader service or swap the implementation.- The Python OCR script is executed via
python ./python_image_recognition/main.py <image_path>and must be reachable from the running container or host.
weapon
: id INTEGER PRIMARY KEY, name TEXT, accuracy, evasion, damage_reduction, power, defence, durability, image_url
nodewar
: per-war player stats (name, base, stick, castle, horse, structure, kills, deaths, guild_id, date)
profile
: guild member profile data (name, ap, aap, dp, discord_id, class_name, spec, guild_id)
Rocket accepts and returns JSON (unless noted). All endpoints live under /weapon, /nodewar, or /profile.
Most weapon endpoints accept a JSON envelope shaped as:
{
"api_key": "api_key_1",
"data": { /* endpoint-specific payload */ }
}Supply one of the valid keys in every request that requires authorization.
GET /weapon/– returns all stored weapons. Include a JSON body with the auth wrapper and an emptydataobject.GET /weapon/<id>– fetch a single weapon by numeric ID. Include the auth wrapper body.POST /weapon/– multipart form upload. Fields:api_key: valid keydata: JSON string matching{ "name": "...", "accuracy": 0, "evasion": 0, "damage_reduction": 0, "power": 0, "defence": 0, "durability": 0 }image: file upload sent to the external uploader; the returned URL is stored inimage_urlResponds with the created weapon JSON.
POST /nodewar/– multipart form ingestion of screenshot statsapi_key: valid keyguild_id: numeric guild identifierimage: screenshot file The server saves the file, runs the OCR script, and stores each parsed player row (skipping duplicates for the current date). Responds with the parsed rows.
GET /nodewar/<guild_id>– aggregate totals for a guild: total kills/deaths, KDA, structures destroyed, and count of unique war dates.GET /nodewar/top/<guild_id>– per-player KDA leaderboard ordered descending.GET /nodewar/<player_name>/<guild_id>– detailed history for a player within a guild, including per-war breakdowns and aggregate sums.GET /nodewar/clear– deletes all rows from thenodewartable (no auth). Use cautiously.
GET /profile/id/<discord_id>– fetch a profile by Discord ID.GET /profile/name/<name>/<guild_id>– case-insensitive profile lookup by name within a guild.POST /profile/– JSON body matchingPlayerProfile(name,ap,aap,dp,discord_id,class_name,spec,guild_id). Rejects missing combat stats and conflicts on duplicate name/Discord ID.
- The OCR pipeline expects the in-game “Family Name / Node War” scoreboard layout. Low-quality or modified HUD captures may trigger a
Bad ScreenShoterror. - Parsed rows are tagged with the current date. The API skips inserting multiple entries for the same player on the same day.
- Ensure
pytesseractcan locate the Tesseract binary (TESSDATA_PREFIXmay be required in custom environments).
- Run
cargo fmt/cargo clippyfor style and lint checks during contributions. - When modifying the OCR script, rebuild the Docker image to bake updated Python dependencies.
- Replace the hard-coded API keys and external uploader URL before deploying to production.
Upload failed with status: start or configure the image uploader expected athttp://localhost:8080/uploads.Bad ScreenShot/UnprocessableEntityon/nodewar/: verify screenshot clarity and that the Python dependencies (OpenCV, Tesseract) are installed correctly.- SQLite locking errors: reduce concurrent writes or investigate long-running readers; the connection is shared via a Rocket-managed async mutex.