Update to TD Records and new cars
This commit is contained in:
parent
5783bb4826
commit
2fa2966aaa
7 changed files with 526 additions and 1 deletions
|
|
@ -1,10 +1,12 @@
|
|||
use scraper::{Html, Selector};
|
||||
use std::collections::{HashMap};
|
||||
use std::env;
|
||||
use std::fs::read_to_string;
|
||||
use std::str::FromStr;
|
||||
|
||||
use tokio;
|
||||
|
||||
use scraper::{Html, Selector};
|
||||
|
||||
struct Car {
|
||||
class: String,
|
||||
rq: u8,
|
||||
401
src/records.rs
Normal file
401
src/records.rs
Normal file
|
|
@ -0,0 +1,401 @@
|
|||
use reqwest::get;
|
||||
use rusqlite::{params, Connection};
|
||||
use serde_json::Value;
|
||||
use std::env;
|
||||
use std::fs;
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
let args: Vec<_> = env::args().collect();
|
||||
|
||||
match args.get(1) {
|
||||
None => {
|
||||
println!("Please provide a command.")
|
||||
}
|
||||
Some(argument) => {
|
||||
let command = argument.as_str();
|
||||
match command {
|
||||
"init" => {
|
||||
get_cars_file().await;
|
||||
get_tracks_file().await;
|
||||
init_db();
|
||||
parse_cars();
|
||||
}
|
||||
"fetch" => {
|
||||
fetch_times().await;
|
||||
}
|
||||
"parse" => {
|
||||
parse_all_times().await;
|
||||
}
|
||||
"analyze" => {
|
||||
list_best_cars().await;
|
||||
}
|
||||
&_ => {
|
||||
println!("Unknown command '{}'.", argument)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
async fn list_best_cars() {
|
||||
todo!()
|
||||
}
|
||||
async fn parse_car_times(rid: String, connection: &Connection) {
|
||||
let file_name = rid.to_owned() + ".json";
|
||||
let local_file_path = "times/".to_owned() + file_name.as_str();
|
||||
let file_content = fs::read_to_string(local_file_path);
|
||||
|
||||
let json_data: Value =
|
||||
serde_json::from_str(&file_content.unwrap_or(String::default())).unwrap_or(Value::Null);
|
||||
|
||||
if let Some(configurations) = json_data["data"].as_object() {
|
||||
for (config, entry) in configurations {
|
||||
if let Some(times) = entry["times"].as_object() {
|
||||
for (track_id, details) in times {
|
||||
// Remove postfix from track ID
|
||||
if let Some((cleaned_track_id, track_type)) =
|
||||
split_prefix_postfix(track_id, "_a")
|
||||
{
|
||||
// Extract time and configuration
|
||||
let mut time = details["t"].as_f64().unwrap_or(0.0);
|
||||
if track_id.starts_with("testBowl") {
|
||||
time = 2000.0 / time;
|
||||
}
|
||||
|
||||
// Insert into the database
|
||||
if time > 0.0 {
|
||||
let _ = connection.execute(
|
||||
"INSERT INTO car_track_times (car_rid, track_id, track_type, time, configuration)
|
||||
VALUES (?1, ?2, ?3, ?4, ?5)",
|
||||
params![rid, translate_track_name(cleaned_track_id), translate_track_type(track_type), time, config],
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
println!("Track times imported for {}", rid);
|
||||
}
|
||||
async fn parse_all_times() {
|
||||
let connection = Connection::open("top_drives.db").expect("Issue opening DB file!");
|
||||
for car in get_cars_from_db() {
|
||||
parse_car_times(car, &connection).await;
|
||||
}
|
||||
}
|
||||
fn parse_cars() {
|
||||
let connection = Connection::open("top_drives.db").expect("Issue opening DB file!");
|
||||
|
||||
let cars_file = "cars_repo.json";
|
||||
let cars_data: Vec<Value> =
|
||||
serde_json::from_str(&fs::read_to_string(cars_file).unwrap()).unwrap();
|
||||
|
||||
for car in cars_data {
|
||||
let _ = connection.execute(
|
||||
"INSERT OR IGNORE INTO cars
|
||||
(class, rq, onlyName, brand, year, abs, tcs, clearance, country, topSpeed, acel, hand,
|
||||
drive, tyres, mra, weight, name, rid, prize, bodyTypes, fuel, seats, engine, tags, guid, brake)
|
||||
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14, ?15, ?16, ?17, ?18, ?19, ?20, ?21, ?22, ?23, ?24, ?25, ?26)",
|
||||
params![
|
||||
car["class"].as_str(),
|
||||
car["rq"].as_i64(),
|
||||
car["onlyName"].as_str(),
|
||||
car["brand"].as_str(),
|
||||
car["year"].as_i64(),
|
||||
car["abs"].as_bool(),
|
||||
car["tcs"].as_bool(),
|
||||
car["clearance"].as_str(),
|
||||
car["country"].as_str(),
|
||||
car["topSpeed"].as_i64(),
|
||||
car["acel"].as_f64(),
|
||||
car["hand"].as_i64(),
|
||||
car["drive"].as_str(),
|
||||
car["tyres"].as_str(),
|
||||
car["mra"].as_f64(),
|
||||
car["weight"].as_i64(),
|
||||
car["name"].as_str(),
|
||||
car["rid"].as_str(),
|
||||
car["prize"].as_bool(),
|
||||
serde_json::to_string(&car["bodyTypes"]).unwrap(),
|
||||
car["fuel"].as_str(),
|
||||
car["seats"].as_str(),
|
||||
car["engine"].as_str(),
|
||||
serde_json::to_string(&car["tags"]).unwrap(),
|
||||
car["guid"].as_str(),
|
||||
car["brake"].as_str(),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
fn split_prefix_postfix<'a>(input: &'a str, delimiter: &'a str) -> Option<(&'a str, &'a str)> {
|
||||
input.find(delimiter).map(|index| {
|
||||
let prefix = &input[..index];
|
||||
let postfix = &input[index + delimiter.len()..];
|
||||
(prefix, postfix)
|
||||
})
|
||||
}
|
||||
async fn fetch_times() {
|
||||
for car in get_cars_from_db() {
|
||||
fetch_car(car).await;
|
||||
}
|
||||
}
|
||||
async fn fetch_car(rid: String) {
|
||||
let file_name = rid.to_owned() + ".json";
|
||||
let url = "https://api.topdrivesrecords.com/car/".to_owned() + rid.as_str();
|
||||
println!("Fetching {}.", url);
|
||||
let local_file_path = "times/".to_owned() + file_name.as_str();
|
||||
get_file(url.as_str(), local_file_path.as_str()).await;
|
||||
}
|
||||
fn get_cars_from_db() -> Vec<String> {
|
||||
let mut car_list = Vec::new();
|
||||
|
||||
let connection = Connection::open("top_drives.db").expect("Issue opening DB file!");
|
||||
|
||||
let mut stmt = connection
|
||||
.prepare("SELECT rid FROM cars")
|
||||
.expect("Could not prepare query!");
|
||||
let mut rows = stmt.query([]).expect("Could not execute query!");
|
||||
|
||||
while let Ok(Some(row)) = rows.next() {
|
||||
car_list.push(row.get::<usize, String>(0).expect("Could not get column!"));
|
||||
}
|
||||
|
||||
car_list
|
||||
}
|
||||
fn init_db() {
|
||||
let _ = fs::remove_file("top_drives.db");
|
||||
let connection = Connection::open("top_drives.db").expect("Issue opening DB file!");
|
||||
|
||||
let _ = connection.execute(
|
||||
"CREATE TABLE IF NOT EXISTS cars (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
class TEXT,
|
||||
rq INTEGER,
|
||||
onlyName TEXT,
|
||||
brand TEXT,
|
||||
year INTEGER,
|
||||
abs BOOLEAN,
|
||||
tcs BOOLEAN,
|
||||
clearance TEXT,
|
||||
country TEXT,
|
||||
topSpeed INTEGER,
|
||||
acel REAL,
|
||||
hand INTEGER,
|
||||
drive TEXT,
|
||||
tyres TEXT,
|
||||
mra REAL,
|
||||
weight INTEGER,
|
||||
name TEXT,
|
||||
rid TEXT UNIQUE,
|
||||
prize BOOLEAN,
|
||||
bodyTypes TEXT,
|
||||
fuel TEXT,
|
||||
seats TEXT,
|
||||
engine TEXT,
|
||||
tags TEXT,
|
||||
guid TEXT UNIQUE,
|
||||
brake TEXT,
|
||||
owned BOOLEAN
|
||||
);",
|
||||
[],
|
||||
);
|
||||
|
||||
let _ = connection.execute(
|
||||
"CREATE TABLE IF NOT EXISTS car_track_times (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
car_rid TEXT,
|
||||
configuration TEXT,
|
||||
track_id TEXT,
|
||||
track_type INTEGER,
|
||||
time DECIMAL,
|
||||
FOREIGN KEY (car_rid) REFERENCES cars(rid),
|
||||
UNIQUE(car_rid, track_id, track_type, configuration)
|
||||
);",
|
||||
[],
|
||||
);
|
||||
}
|
||||
async fn get_tracks_file() {
|
||||
get_file("https://raw.githubusercontent.com/TiagoXavi/topdrives-records/refs/heads/main/src/database/tracks_repo.json","tracks_repo.json").await;
|
||||
}
|
||||
async fn get_cars_file() {
|
||||
get_file("https://github.com/TiagoXavi/topdrives-records/raw/refs/heads/main/src/database/cars_final_PL24_2-fixed.json", "cars_repo.json").await;
|
||||
}
|
||||
async fn get_file(file_url: &str, local_file_path: &str) {
|
||||
let _ = fs::remove_file(local_file_path);
|
||||
match get(file_url).await {
|
||||
Ok(response) => {
|
||||
fs::write(local_file_path, response.text().await.unwrap())
|
||||
.expect("Unable to write file!");
|
||||
}
|
||||
Err(error) => {
|
||||
println!("Failed to fetch the file. Status: {}", error.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
fn translate_track_type(track_code: &str) -> &str {
|
||||
match track_code {
|
||||
"00" => "Dry",
|
||||
"01" => "Wet",
|
||||
"10" => "Dirt",
|
||||
"11" => "Dirt Wet",
|
||||
"20" => "Gravel",
|
||||
"30" => "Ice",
|
||||
"40" => "Asphalt Dirt",
|
||||
"41" => "Asphalt Dirt Wet",
|
||||
"50" => "Sand",
|
||||
"60" => "Snow",
|
||||
"70" => "Grass",
|
||||
"71" => "Grass Wet",
|
||||
"b0" => "Asphalt Gravel",
|
||||
"c0" => "Asphalt Sand",
|
||||
"c1" => "Asphalt Sand Wet",
|
||||
"d0" => "Asphalt Snow",
|
||||
"e0" => "Sand Dirt",
|
||||
"f0" => "Asphalt Grass Dirt",
|
||||
"g0" => "Ice Snow",
|
||||
"h1" => "Snow Dirt Wet",
|
||||
_ => track_code,
|
||||
}
|
||||
}
|
||||
fn translate_track_name(track_code: &str) -> &str {
|
||||
match track_code {
|
||||
"drag100b" => "0-100-0mph",
|
||||
"drag100" => "0-100mph",
|
||||
"drag120" => "0-120mph",
|
||||
"drag124" => "0-124mph",
|
||||
"drag150b" => "0-150-0mph",
|
||||
"drag150" => "0-150mph",
|
||||
"drag170" => "0-170mph",
|
||||
"drag200" => "0-200mph",
|
||||
"drag60b" => "0-60-0mph",
|
||||
"drag60" => "0-60mph",
|
||||
"drag62" => "0-62mph",
|
||||
"mile1" => "1 Mile Drag",
|
||||
"mile1r" => "1 Mile Drag R",
|
||||
"mile2" => "1/2 Mile Drag",
|
||||
"mile4" => "1/4 Mile Drag",
|
||||
"mile4r" => "1/4 Mile Drag R",
|
||||
"drag100150" => "100-150mph",
|
||||
"drag30130" => "30-130mph",
|
||||
"drag50150" => "50-150mph",
|
||||
"airplaneHangars" => "Airplane Hangars",
|
||||
"airplaneSlalom" => "Airplane Slalom",
|
||||
"smallRoad" => "Back Road",
|
||||
"butte" => "Butte",
|
||||
"canyonButte" => "Canyon Butte",
|
||||
"lookout" => "Lookout",
|
||||
"canyonLookout" => "Canyon Lookout",
|
||||
"carPark" => "Car Park",
|
||||
"townSlalom" => "City Slalom",
|
||||
"townSprint" => "City Sprint",
|
||||
"csMed" => "City Streets Medium",
|
||||
"csSmall" => "City Streets Small",
|
||||
"townTour" => "City Tour",
|
||||
"cross" => "Cross-Country",
|
||||
"dealsGap" => "Deals Gap",
|
||||
"dealsGap1" => "Deals Gap Relay 1",
|
||||
"dealsGap2" => "Deals Gap Relay 2",
|
||||
"dealsGap3" => "Deals Gap Relay 3",
|
||||
"dealsGap4" => "Deals Gap Relay 4",
|
||||
"dealsGap5" => "Deals Gap Relay 5",
|
||||
"dealsGapBack" => "Deals Gap Backroads",
|
||||
"desertBig" => "Desert Rally Big",
|
||||
"desertHill" => "Desert Rally Hill",
|
||||
"desertRallyDirt" => "Desert Rally Dirt",
|
||||
"desertSmall" => "Desert Rally Small",
|
||||
"dockAltCirc" => "Dockyard Alternative Circuit",
|
||||
"dockCirc" => "Dockyard Circuit",
|
||||
"dockCity" => "Dockyard City Streets",
|
||||
"dockGforceDrag" => "Dockyard G-Force Drag",
|
||||
"dockKart" => "Dockyard Karting",
|
||||
"dockUdrag" => "Dockyard U-Drag",
|
||||
"dtRoad" => "Dirt Road",
|
||||
"canyonDtRoad" => "Canyon Dirt Road",
|
||||
"fast" => "Fast Circuit",
|
||||
"fastr" => "Fast Circuit R",
|
||||
"fieldSlalom" => "Field Slalom",
|
||||
"figureEight" => "Figure Of Eight",
|
||||
"forestRiver" => "Forest River Crossing",
|
||||
"forest" => "Forest Road",
|
||||
"forestSlalom" => "Forest Slalom",
|
||||
"frozenLake" => "Frozen Lake",
|
||||
"frozenRivSla" => "Frozen River Slalom",
|
||||
"gForce" => "G-Force Test",
|
||||
"gForcer" => "G-Force Test R",
|
||||
"hairpin" => "Hairpin Road",
|
||||
"hClimb" => "Hill Climb",
|
||||
"hClimbr" => "Hill Climb R",
|
||||
"indoorKart" => "Indoor Karting",
|
||||
"kart" => "Karting Circuit",
|
||||
"draglshape" => "L-Shape Drag",
|
||||
"laguna" => "Laguna Seca",
|
||||
"lumberForest" => "Lumber Mill Forest Rally",
|
||||
"lumberRiver" => "Lumber Mill River Rally",
|
||||
"lumberTwisty" => "Lumber Mill Twisty Circuit",
|
||||
"miCircuit" => "Miami Circuit",
|
||||
"miStreets1" => "Miami Streets 1",
|
||||
"miStreets2" => "Miami Streets 2",
|
||||
"miUdrag" => "Miami U-Drag",
|
||||
"miCause" => "Miami Causeway",
|
||||
"miCauseDrag" => "Miami Causeway Drag",
|
||||
"miBridge" => "Miami Bridge",
|
||||
"miFigure" => "Miami Figure Of Eight",
|
||||
"miSlalom" => "Miami Slalom",
|
||||
"miGforce" => "Miami G-Force",
|
||||
"miBeachDrag" => "Miami Beach Drag",
|
||||
"mnGforce" => "Monaco G-Force Test",
|
||||
"mnHairpin" => "Monaco Hairpin",
|
||||
"mnCityNarrow" => "Monaco Narrow Streets",
|
||||
"mnCity" => "Monaco Streets",
|
||||
"mnCityLong" => "Monaco Streets Long",
|
||||
"moto" => "Motocross Track",
|
||||
"mtHairpin" => "Mountain Hairpin",
|
||||
"mtHill" => "Mountain Hill Climb",
|
||||
"mtIncline" => "Mountain Incline Road",
|
||||
"mtSlalom" => "Mountain Slalom",
|
||||
"mtTour" => "Mountain Tour",
|
||||
"mtTwisty" => "Mountain Twisty Road",
|
||||
"nwCircuit" => "New York Circuit",
|
||||
"nwDrag" => "New York Drag",
|
||||
"nwGforce" => "New York G-Force",
|
||||
"nwLoop" => "New York Times Square Loop",
|
||||
"nwCathedral" => "New York Cathedral",
|
||||
"nwTour" => "New York Central Park Tour",
|
||||
"nwSlalom" => "New York Train Slalom",
|
||||
"northloop" => "North Loop",
|
||||
"northloop1" => "North Loop Relay 1",
|
||||
"northloop2" => "North Loop Relay 2",
|
||||
"northloop3" => "North Loop Relay 3",
|
||||
"northloop4" => "North Loop Relay 4",
|
||||
"northloop5" => "North Loop Relay 5",
|
||||
"oceanSlalom" => "Ocean Beach Slalom",
|
||||
"oceanCity" => "Ocean City Streets",
|
||||
"oceanHighway" => "Ocean Highway",
|
||||
"oceanLongDrag" => "Ocean Long Drag",
|
||||
"oceanParking" => "Ocean Parking Lot",
|
||||
"oceanShortDrag" => "Ocean Short Drag",
|
||||
"rallyMed" => "Rallycross Medium Circuit",
|
||||
"rallySmall" => "Rallycross Small Circuit",
|
||||
"riverSprint" => "River Sprint",
|
||||
"runwayDrag" => "Runway Drag",
|
||||
"runwayDragSlalom" => "Runway Drag & Slalom",
|
||||
"runwayLoop" => "Runway Loop",
|
||||
"serviceRoads" => "Service Roads",
|
||||
"slalom" => "Slalom Test",
|
||||
"slalomr" => "Slalom Test R",
|
||||
"speedbump12km" => "Speedbump 1 km",
|
||||
"speedbump1km" => "Speedbump 1/2 km",
|
||||
"testBowl" => "Test Bowl",
|
||||
"testBowlr" => "Test Bowl R",
|
||||
"tokyoBridge" => "Tokyo Bridge",
|
||||
"tokyoDrag" => "Tokyo Drag",
|
||||
"tokyoGforce" => "Tokyo G-Force Test",
|
||||
"tokyoLoop" => "Tokyo Loop",
|
||||
"tokyoOffRamp" => "Tokyo Off-Ramp",
|
||||
"tokyoOverpass" => "Tokyo Overpass",
|
||||
"tCircuit" => "Twisty Circuit",
|
||||
"tCircuitr" => "Twisty Circuit R",
|
||||
"tRoad" => "Twisty Road",
|
||||
"waterDrag" => "Waterfront Drag",
|
||||
_ => track_code,
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue