Migrazione a FormFav API, UI aggiornata e pagine doc HTML
- Migrato il client e tutte le chiamate da TheRacingAPI a FormFav API, con autenticazione tramite API Key e gestione avanzata di rate-limiting e retry. - Refactoring del download corse: ora vengono scaricati tutti i meeting, corse e runners con dettagli estesi e gestione robusta degli errori. - Interfaccia utente aggiornata: DatePicker, selezione tipo corsa, impostazioni semplificate e miglioramenti grafici. - Download asincrono e annullabile, con gestione dello stato UI. - Pulizia del codice e adattamento al nuovo formato dati FormFav. - Aggiunti numerosi file HTML statici per la documentazione e le sezioni dell’app, con struttura SEO/social ottimizzata e caricamento dinamico tramite JS/CSS condivisi. - Aggiornato il file HAR con nuove richieste di immagini (avatar Google e logo FormFav) per migliorare la tracciabilità delle risorse grafiche.
This commit is contained in:
@@ -0,0 +1,29 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1" />
|
||||
<title>FormFav - Horse Racing Form Stats & Data Feed API</title>
|
||||
<meta name="description" content="Free horse racing form stats and data feed API. RESTful API for betting platforms, sports apps, and analytics tools. Simple 3-parameter integration with comprehensive JSON responses." />
|
||||
<meta name="keywords" content="horse racing API, racing data API, race form API, horse racing data feed, racing statistics API, betting API, horse racing form data, racing API for developers, thoroughbred racing data, racing form stats" />
|
||||
<meta property="og:title" content="FormFav - Horse Racing Form Stats & Data Feed API" />
|
||||
<meta property="og:description" content="Free horse racing form stats and data feed API. RESTful API for betting platforms, sports apps, and analytics tools." />
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta name="twitter:title" content="FormFav - Horse Racing Form Stats & Data Feed API" />
|
||||
<meta name="twitter:description" content="Free horse racing form stats and data feed API. Perfect for betting platforms and racing applications." />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:image" content="https://formfav.com/og-image.png" />
|
||||
<meta property="og:url" content="https://formfav.com" />
|
||||
<meta name="twitter:image" content="https://formfav.com/og-image.png" />
|
||||
<link rel="canonical" href="https://formfav.com" />
|
||||
<link rel="icon" type="image/png" href="/favicon.png" />
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Space+Grotesk:wght@500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
|
||||
<script type="module" crossorigin src="/assets/index-D96MyUpl.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-Fnp_N1rK.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
</html>
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1" />
|
||||
<title>FormFav - Horse Racing Form Stats & Data Feed API</title>
|
||||
<meta name="description" content="Free horse racing form stats and data feed API. RESTful API for betting platforms, sports apps, and analytics tools. Simple 3-parameter integration with comprehensive JSON responses." />
|
||||
<meta name="keywords" content="horse racing API, racing data API, race form API, horse racing data feed, racing statistics API, betting API, horse racing form data, racing API for developers, thoroughbred racing data, racing form stats" />
|
||||
<meta property="og:title" content="FormFav - Horse Racing Form Stats & Data Feed API" />
|
||||
<meta property="og:description" content="Free horse racing form stats and data feed API. RESTful API for betting platforms, sports apps, and analytics tools." />
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta name="twitter:title" content="FormFav - Horse Racing Form Stats & Data Feed API" />
|
||||
<meta name="twitter:description" content="Free horse racing form stats and data feed API. Perfect for betting platforms and racing applications." />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:image" content="https://formfav.com/og-image.png" />
|
||||
<meta property="og:url" content="https://formfav.com" />
|
||||
<meta name="twitter:image" content="https://formfav.com/og-image.png" />
|
||||
<link rel="canonical" href="https://formfav.com" />
|
||||
<link rel="icon" type="image/png" href="/favicon.png" />
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Space+Grotesk:wght@500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
|
||||
<script type="module" crossorigin src="/assets/index-D96MyUpl.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-Fnp_N1rK.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
</html>
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1" />
|
||||
<title>FormFav - Horse Racing Form Stats & Data Feed API</title>
|
||||
<meta name="description" content="Free horse racing form stats and data feed API. RESTful API for betting platforms, sports apps, and analytics tools. Simple 3-parameter integration with comprehensive JSON responses." />
|
||||
<meta name="keywords" content="horse racing API, racing data API, race form API, horse racing data feed, racing statistics API, betting API, horse racing form data, racing API for developers, thoroughbred racing data, racing form stats" />
|
||||
<meta property="og:title" content="FormFav - Horse Racing Form Stats & Data Feed API" />
|
||||
<meta property="og:description" content="Free horse racing form stats and data feed API. RESTful API for betting platforms, sports apps, and analytics tools." />
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta name="twitter:title" content="FormFav - Horse Racing Form Stats & Data Feed API" />
|
||||
<meta name="twitter:description" content="Free horse racing form stats and data feed API. Perfect for betting platforms and racing applications." />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:image" content="https://formfav.com/og-image.png" />
|
||||
<meta property="og:url" content="https://formfav.com" />
|
||||
<meta name="twitter:image" content="https://formfav.com/og-image.png" />
|
||||
<link rel="canonical" href="https://formfav.com" />
|
||||
<link rel="icon" type="image/png" href="/favicon.png" />
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Space+Grotesk:wght@500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
|
||||
<script type="module" crossorigin src="/assets/index-D96MyUpl.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-Fnp_N1rK.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
</html>
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1" />
|
||||
<title>FormFav - Horse Racing Form Stats & Data Feed API</title>
|
||||
<meta name="description" content="Free horse racing form stats and data feed API. RESTful API for betting platforms, sports apps, and analytics tools. Simple 3-parameter integration with comprehensive JSON responses." />
|
||||
<meta name="keywords" content="horse racing API, racing data API, race form API, horse racing data feed, racing statistics API, betting API, horse racing form data, racing API for developers, thoroughbred racing data, racing form stats" />
|
||||
<meta property="og:title" content="FormFav - Horse Racing Form Stats & Data Feed API" />
|
||||
<meta property="og:description" content="Free horse racing form stats and data feed API. RESTful API for betting platforms, sports apps, and analytics tools." />
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta name="twitter:title" content="FormFav - Horse Racing Form Stats & Data Feed API" />
|
||||
<meta name="twitter:description" content="Free horse racing form stats and data feed API. Perfect for betting platforms and racing applications." />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:image" content="https://formfav.com/og-image.png" />
|
||||
<meta property="og:url" content="https://formfav.com" />
|
||||
<meta name="twitter:image" content="https://formfav.com/og-image.png" />
|
||||
<link rel="canonical" href="https://formfav.com" />
|
||||
<link rel="icon" type="image/png" href="/favicon.png" />
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Space+Grotesk:wght@500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
|
||||
<script type="module" crossorigin src="/assets/index-D96MyUpl.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-Fnp_N1rK.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
</html>
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1" />
|
||||
<title>FormFav - Horse Racing Form Stats & Data Feed API</title>
|
||||
<meta name="description" content="Free horse racing form stats and data feed API. RESTful API for betting platforms, sports apps, and analytics tools. Simple 3-parameter integration with comprehensive JSON responses." />
|
||||
<meta name="keywords" content="horse racing API, racing data API, race form API, horse racing data feed, racing statistics API, betting API, horse racing form data, racing API for developers, thoroughbred racing data, racing form stats" />
|
||||
<meta property="og:title" content="FormFav - Horse Racing Form Stats & Data Feed API" />
|
||||
<meta property="og:description" content="Free horse racing form stats and data feed API. RESTful API for betting platforms, sports apps, and analytics tools." />
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta name="twitter:title" content="FormFav - Horse Racing Form Stats & Data Feed API" />
|
||||
<meta name="twitter:description" content="Free horse racing form stats and data feed API. Perfect for betting platforms and racing applications." />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:image" content="https://formfav.com/og-image.png" />
|
||||
<meta property="og:url" content="https://formfav.com" />
|
||||
<meta name="twitter:image" content="https://formfav.com/og-image.png" />
|
||||
<link rel="canonical" href="https://formfav.com" />
|
||||
<link rel="icon" type="image/png" href="/favicon.png" />
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Space+Grotesk:wght@500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
|
||||
<script type="module" crossorigin src="/assets/index-D96MyUpl.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-Fnp_N1rK.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
</html>
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1" />
|
||||
<title>FormFav - Horse Racing Form Stats & Data Feed API</title>
|
||||
<meta name="description" content="Free horse racing form stats and data feed API. RESTful API for betting platforms, sports apps, and analytics tools. Simple 3-parameter integration with comprehensive JSON responses." />
|
||||
<meta name="keywords" content="horse racing API, racing data API, race form API, horse racing data feed, racing statistics API, betting API, horse racing form data, racing API for developers, thoroughbred racing data, racing form stats" />
|
||||
<meta property="og:title" content="FormFav - Horse Racing Form Stats & Data Feed API" />
|
||||
<meta property="og:description" content="Free horse racing form stats and data feed API. RESTful API for betting platforms, sports apps, and analytics tools." />
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta name="twitter:title" content="FormFav - Horse Racing Form Stats & Data Feed API" />
|
||||
<meta name="twitter:description" content="Free horse racing form stats and data feed API. Perfect for betting platforms and racing applications." />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:image" content="https://formfav.com/og-image.png" />
|
||||
<meta property="og:url" content="https://formfav.com" />
|
||||
<meta name="twitter:image" content="https://formfav.com/og-image.png" />
|
||||
<link rel="canonical" href="https://formfav.com" />
|
||||
<link rel="icon" type="image/png" href="/favicon.png" />
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Space+Grotesk:wght@500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
|
||||
<script type="module" crossorigin src="/assets/index-D96MyUpl.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-Fnp_N1rK.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
</html>
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1" />
|
||||
<title>FormFav - Horse Racing Form Stats & Data Feed API</title>
|
||||
<meta name="description" content="Free horse racing form stats and data feed API. RESTful API for betting platforms, sports apps, and analytics tools. Simple 3-parameter integration with comprehensive JSON responses." />
|
||||
<meta name="keywords" content="horse racing API, racing data API, race form API, horse racing data feed, racing statistics API, betting API, horse racing form data, racing API for developers, thoroughbred racing data, racing form stats" />
|
||||
<meta property="og:title" content="FormFav - Horse Racing Form Stats & Data Feed API" />
|
||||
<meta property="og:description" content="Free horse racing form stats and data feed API. RESTful API for betting platforms, sports apps, and analytics tools." />
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta name="twitter:title" content="FormFav - Horse Racing Form Stats & Data Feed API" />
|
||||
<meta name="twitter:description" content="Free horse racing form stats and data feed API. Perfect for betting platforms and racing applications." />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:image" content="https://formfav.com/og-image.png" />
|
||||
<meta property="og:url" content="https://formfav.com" />
|
||||
<meta name="twitter:image" content="https://formfav.com/og-image.png" />
|
||||
<link rel="canonical" href="https://formfav.com" />
|
||||
<link rel="icon" type="image/png" href="/favicon.png" />
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Space+Grotesk:wght@500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
|
||||
<script type="module" crossorigin src="/assets/index-D96MyUpl.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-Fnp_N1rK.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
</html>
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1" />
|
||||
<title>FormFav - Horse Racing Form Stats & Data Feed API</title>
|
||||
<meta name="description" content="Free horse racing form stats and data feed API. RESTful API for betting platforms, sports apps, and analytics tools. Simple 3-parameter integration with comprehensive JSON responses." />
|
||||
<meta name="keywords" content="horse racing API, racing data API, race form API, horse racing data feed, racing statistics API, betting API, horse racing form data, racing API for developers, thoroughbred racing data, racing form stats" />
|
||||
<meta property="og:title" content="FormFav - Horse Racing Form Stats & Data Feed API" />
|
||||
<meta property="og:description" content="Free horse racing form stats and data feed API. RESTful API for betting platforms, sports apps, and analytics tools." />
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta name="twitter:title" content="FormFav - Horse Racing Form Stats & Data Feed API" />
|
||||
<meta name="twitter:description" content="Free horse racing form stats and data feed API. Perfect for betting platforms and racing applications." />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:image" content="https://formfav.com/og-image.png" />
|
||||
<meta property="og:url" content="https://formfav.com" />
|
||||
<meta name="twitter:image" content="https://formfav.com/og-image.png" />
|
||||
<link rel="canonical" href="https://formfav.com" />
|
||||
<link rel="icon" type="image/png" href="/favicon.png" />
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Space+Grotesk:wght@500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
|
||||
<script type="module" crossorigin src="/assets/index-D96MyUpl.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-Fnp_N1rK.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
</html>
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1" />
|
||||
<title>FormFav - Horse Racing Form Stats & Data Feed API</title>
|
||||
<meta name="description" content="Free horse racing form stats and data feed API. RESTful API for betting platforms, sports apps, and analytics tools. Simple 3-parameter integration with comprehensive JSON responses." />
|
||||
<meta name="keywords" content="horse racing API, racing data API, race form API, horse racing data feed, racing statistics API, betting API, horse racing form data, racing API for developers, thoroughbred racing data, racing form stats" />
|
||||
<meta property="og:title" content="FormFav - Horse Racing Form Stats & Data Feed API" />
|
||||
<meta property="og:description" content="Free horse racing form stats and data feed API. RESTful API for betting platforms, sports apps, and analytics tools." />
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta name="twitter:title" content="FormFav - Horse Racing Form Stats & Data Feed API" />
|
||||
<meta name="twitter:description" content="Free horse racing form stats and data feed API. Perfect for betting platforms and racing applications." />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:image" content="https://formfav.com/og-image.png" />
|
||||
<meta property="og:url" content="https://formfav.com" />
|
||||
<meta name="twitter:image" content="https://formfav.com/og-image.png" />
|
||||
<link rel="canonical" href="https://formfav.com" />
|
||||
<link rel="icon" type="image/png" href="/favicon.png" />
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Space+Grotesk:wght@500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
|
||||
<script type="module" crossorigin src="/assets/index-D96MyUpl.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-Fnp_N1rK.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
</html>
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1" />
|
||||
<title>FormFav - Horse Racing Form Stats & Data Feed API</title>
|
||||
<meta name="description" content="Free horse racing form stats and data feed API. RESTful API for betting platforms, sports apps, and analytics tools. Simple 3-parameter integration with comprehensive JSON responses." />
|
||||
<meta name="keywords" content="horse racing API, racing data API, race form API, horse racing data feed, racing statistics API, betting API, horse racing form data, racing API for developers, thoroughbred racing data, racing form stats" />
|
||||
<meta property="og:title" content="FormFav - Horse Racing Form Stats & Data Feed API" />
|
||||
<meta property="og:description" content="Free horse racing form stats and data feed API. RESTful API for betting platforms, sports apps, and analytics tools." />
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta name="twitter:title" content="FormFav - Horse Racing Form Stats & Data Feed API" />
|
||||
<meta name="twitter:description" content="Free horse racing form stats and data feed API. Perfect for betting platforms and racing applications." />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:image" content="https://formfav.com/og-image.png" />
|
||||
<meta property="og:url" content="https://formfav.com" />
|
||||
<meta name="twitter:image" content="https://formfav.com/og-image.png" />
|
||||
<link rel="canonical" href="https://formfav.com" />
|
||||
<link rel="icon" type="image/png" href="/favicon.png" />
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Space+Grotesk:wght@500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
|
||||
<script type="module" crossorigin src="/assets/index-D96MyUpl.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-Fnp_N1rK.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
</html>
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1" />
|
||||
<title>FormFav - Horse Racing Form Stats & Data Feed API</title>
|
||||
<meta name="description" content="Free horse racing form stats and data feed API. RESTful API for betting platforms, sports apps, and analytics tools. Simple 3-parameter integration with comprehensive JSON responses." />
|
||||
<meta name="keywords" content="horse racing API, racing data API, race form API, horse racing data feed, racing statistics API, betting API, horse racing form data, racing API for developers, thoroughbred racing data, racing form stats" />
|
||||
<meta property="og:title" content="FormFav - Horse Racing Form Stats & Data Feed API" />
|
||||
<meta property="og:description" content="Free horse racing form stats and data feed API. RESTful API for betting platforms, sports apps, and analytics tools." />
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta name="twitter:title" content="FormFav - Horse Racing Form Stats & Data Feed API" />
|
||||
<meta name="twitter:description" content="Free horse racing form stats and data feed API. Perfect for betting platforms and racing applications." />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:image" content="https://formfav.com/og-image.png" />
|
||||
<meta property="og:url" content="https://formfav.com" />
|
||||
<meta name="twitter:image" content="https://formfav.com/og-image.png" />
|
||||
<link rel="canonical" href="https://formfav.com" />
|
||||
<link rel="icon" type="image/png" href="/favicon.png" />
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Space+Grotesk:wght@500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
|
||||
<script type="module" crossorigin src="/assets/index-D96MyUpl.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-Fnp_N1rK.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
</html>
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1" />
|
||||
<title>FormFav - Horse Racing Form Stats & Data Feed API</title>
|
||||
<meta name="description" content="Free horse racing form stats and data feed API. RESTful API for betting platforms, sports apps, and analytics tools. Simple 3-parameter integration with comprehensive JSON responses." />
|
||||
<meta name="keywords" content="horse racing API, racing data API, race form API, horse racing data feed, racing statistics API, betting API, horse racing form data, racing API for developers, thoroughbred racing data, racing form stats" />
|
||||
<meta property="og:title" content="FormFav - Horse Racing Form Stats & Data Feed API" />
|
||||
<meta property="og:description" content="Free horse racing form stats and data feed API. RESTful API for betting platforms, sports apps, and analytics tools." />
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta name="twitter:title" content="FormFav - Horse Racing Form Stats & Data Feed API" />
|
||||
<meta name="twitter:description" content="Free horse racing form stats and data feed API. Perfect for betting platforms and racing applications." />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:image" content="https://formfav.com/og-image.png" />
|
||||
<meta property="og:url" content="https://formfav.com" />
|
||||
<meta name="twitter:image" content="https://formfav.com/og-image.png" />
|
||||
<link rel="canonical" href="https://formfav.com" />
|
||||
<link rel="icon" type="image/png" href="/favicon.png" />
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Space+Grotesk:wght@500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
|
||||
<script type="module" crossorigin src="/assets/index-D96MyUpl.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-Fnp_N1rK.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
</html>
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1" />
|
||||
<title>FormFav - Horse Racing Form Stats & Data Feed API</title>
|
||||
<meta name="description" content="Free horse racing form stats and data feed API. RESTful API for betting platforms, sports apps, and analytics tools. Simple 3-parameter integration with comprehensive JSON responses." />
|
||||
<meta name="keywords" content="horse racing API, racing data API, race form API, horse racing data feed, racing statistics API, betting API, horse racing form data, racing API for developers, thoroughbred racing data, racing form stats" />
|
||||
<meta property="og:title" content="FormFav - Horse Racing Form Stats & Data Feed API" />
|
||||
<meta property="og:description" content="Free horse racing form stats and data feed API. RESTful API for betting platforms, sports apps, and analytics tools." />
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta name="twitter:title" content="FormFav - Horse Racing Form Stats & Data Feed API" />
|
||||
<meta name="twitter:description" content="Free horse racing form stats and data feed API. Perfect for betting platforms and racing applications." />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:image" content="https://formfav.com/og-image.png" />
|
||||
<meta property="og:url" content="https://formfav.com" />
|
||||
<meta name="twitter:image" content="https://formfav.com/og-image.png" />
|
||||
<link rel="canonical" href="https://formfav.com" />
|
||||
<link rel="icon" type="image/png" href="/favicon.png" />
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Space+Grotesk:wght@500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
|
||||
<script type="module" crossorigin src="/assets/index-D96MyUpl.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-Fnp_N1rK.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
</html>
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1" />
|
||||
<title>FormFav - Horse Racing Form Stats & Data Feed API</title>
|
||||
<meta name="description" content="Free horse racing form stats and data feed API. RESTful API for betting platforms, sports apps, and analytics tools. Simple 3-parameter integration with comprehensive JSON responses." />
|
||||
<meta name="keywords" content="horse racing API, racing data API, race form API, horse racing data feed, racing statistics API, betting API, horse racing form data, racing API for developers, thoroughbred racing data, racing form stats" />
|
||||
<meta property="og:title" content="FormFav - Horse Racing Form Stats & Data Feed API" />
|
||||
<meta property="og:description" content="Free horse racing form stats and data feed API. RESTful API for betting platforms, sports apps, and analytics tools." />
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta name="twitter:title" content="FormFav - Horse Racing Form Stats & Data Feed API" />
|
||||
<meta name="twitter:description" content="Free horse racing form stats and data feed API. Perfect for betting platforms and racing applications." />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:image" content="https://formfav.com/og-image.png" />
|
||||
<meta property="og:url" content="https://formfav.com" />
|
||||
<meta name="twitter:image" content="https://formfav.com/og-image.png" />
|
||||
<link rel="canonical" href="https://formfav.com" />
|
||||
<link rel="icon" type="image/png" href="/favicon.png" />
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Space+Grotesk:wght@500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
|
||||
<script type="module" crossorigin src="/assets/index-D96MyUpl.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-Fnp_N1rK.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,29 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1" />
|
||||
<title>FormFav - Horse Racing Form Stats & Data Feed API</title>
|
||||
<meta name="description" content="Free horse racing form stats and data feed API. RESTful API for betting platforms, sports apps, and analytics tools. Simple 3-parameter integration with comprehensive JSON responses." />
|
||||
<meta name="keywords" content="horse racing API, racing data API, race form API, horse racing data feed, racing statistics API, betting API, horse racing form data, racing API for developers, thoroughbred racing data, racing form stats" />
|
||||
<meta property="og:title" content="FormFav - Horse Racing Form Stats & Data Feed API" />
|
||||
<meta property="og:description" content="Free horse racing form stats and data feed API. RESTful API for betting platforms, sports apps, and analytics tools." />
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta name="twitter:title" content="FormFav - Horse Racing Form Stats & Data Feed API" />
|
||||
<meta name="twitter:description" content="Free horse racing form stats and data feed API. Perfect for betting platforms and racing applications." />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:image" content="https://formfav.com/og-image.png" />
|
||||
<meta property="og:url" content="https://formfav.com" />
|
||||
<meta name="twitter:image" content="https://formfav.com/og-image.png" />
|
||||
<link rel="canonical" href="https://formfav.com" />
|
||||
<link rel="icon" type="image/png" href="/favicon.png" />
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Space+Grotesk:wght@500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
|
||||
<script type="module" crossorigin src="/assets/index-D96MyUpl.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-Fnp_N1rK.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
</html>
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1" />
|
||||
<title>FormFav - Horse Racing Form Stats & Data Feed API</title>
|
||||
<meta name="description" content="Free horse racing form stats and data feed API. RESTful API for betting platforms, sports apps, and analytics tools. Simple 3-parameter integration with comprehensive JSON responses." />
|
||||
<meta name="keywords" content="horse racing API, racing data API, race form API, horse racing data feed, racing statistics API, betting API, horse racing form data, racing API for developers, thoroughbred racing data, racing form stats" />
|
||||
<meta property="og:title" content="FormFav - Horse Racing Form Stats & Data Feed API" />
|
||||
<meta property="og:description" content="Free horse racing form stats and data feed API. RESTful API for betting platforms, sports apps, and analytics tools." />
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta name="twitter:title" content="FormFav - Horse Racing Form Stats & Data Feed API" />
|
||||
<meta name="twitter:description" content="Free horse racing form stats and data feed API. Perfect for betting platforms and racing applications." />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:image" content="https://formfav.com/og-image.png" />
|
||||
<meta property="og:url" content="https://formfav.com" />
|
||||
<meta name="twitter:image" content="https://formfav.com/og-image.png" />
|
||||
<link rel="canonical" href="https://formfav.com" />
|
||||
<link rel="icon" type="image/png" href="/favicon.png" />
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Space+Grotesk:wght@500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
|
||||
<script type="module" crossorigin src="/assets/index-D96MyUpl.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-Fnp_N1rK.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
</html>
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1" />
|
||||
<title>FormFav - Horse Racing Form Stats & Data Feed API</title>
|
||||
<meta name="description" content="Free horse racing form stats and data feed API. RESTful API for betting platforms, sports apps, and analytics tools. Simple 3-parameter integration with comprehensive JSON responses." />
|
||||
<meta name="keywords" content="horse racing API, racing data API, race form API, horse racing data feed, racing statistics API, betting API, horse racing form data, racing API for developers, thoroughbred racing data, racing form stats" />
|
||||
<meta property="og:title" content="FormFav - Horse Racing Form Stats & Data Feed API" />
|
||||
<meta property="og:description" content="Free horse racing form stats and data feed API. RESTful API for betting platforms, sports apps, and analytics tools." />
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta name="twitter:title" content="FormFav - Horse Racing Form Stats & Data Feed API" />
|
||||
<meta name="twitter:description" content="Free horse racing form stats and data feed API. Perfect for betting platforms and racing applications." />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:image" content="https://formfav.com/og-image.png" />
|
||||
<meta property="og:url" content="https://formfav.com" />
|
||||
<meta name="twitter:image" content="https://formfav.com/og-image.png" />
|
||||
<link rel="canonical" href="https://formfav.com" />
|
||||
<link rel="icon" type="image/png" href="/favicon.png" />
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Space+Grotesk:wght@500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
|
||||
<script type="module" crossorigin src="/assets/index-D96MyUpl.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-Fnp_N1rK.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
</html>
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1" />
|
||||
<title>FormFav - Horse Racing Form Stats & Data Feed API</title>
|
||||
<meta name="description" content="Free horse racing form stats and data feed API. RESTful API for betting platforms, sports apps, and analytics tools. Simple 3-parameter integration with comprehensive JSON responses." />
|
||||
<meta name="keywords" content="horse racing API, racing data API, race form API, horse racing data feed, racing statistics API, betting API, horse racing form data, racing API for developers, thoroughbred racing data, racing form stats" />
|
||||
<meta property="og:title" content="FormFav - Horse Racing Form Stats & Data Feed API" />
|
||||
<meta property="og:description" content="Free horse racing form stats and data feed API. RESTful API for betting platforms, sports apps, and analytics tools." />
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta name="twitter:title" content="FormFav - Horse Racing Form Stats & Data Feed API" />
|
||||
<meta name="twitter:description" content="Free horse racing form stats and data feed API. Perfect for betting platforms and racing applications." />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:image" content="https://formfav.com/og-image.png" />
|
||||
<meta property="og:url" content="https://formfav.com" />
|
||||
<meta name="twitter:image" content="https://formfav.com/og-image.png" />
|
||||
<link rel="canonical" href="https://formfav.com" />
|
||||
<link rel="icon" type="image/png" href="/favicon.png" />
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Space+Grotesk:wght@500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
|
||||
<script type="module" crossorigin src="/assets/index-D96MyUpl.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-Fnp_N1rK.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
</html>
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1" />
|
||||
<title>FormFav - Horse Racing Form Stats & Data Feed API</title>
|
||||
<meta name="description" content="Free horse racing form stats and data feed API. RESTful API for betting platforms, sports apps, and analytics tools. Simple 3-parameter integration with comprehensive JSON responses." />
|
||||
<meta name="keywords" content="horse racing API, racing data API, race form API, horse racing data feed, racing statistics API, betting API, horse racing form data, racing API for developers, thoroughbred racing data, racing form stats" />
|
||||
<meta property="og:title" content="FormFav - Horse Racing Form Stats & Data Feed API" />
|
||||
<meta property="og:description" content="Free horse racing form stats and data feed API. RESTful API for betting platforms, sports apps, and analytics tools." />
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta name="twitter:title" content="FormFav - Horse Racing Form Stats & Data Feed API" />
|
||||
<meta name="twitter:description" content="Free horse racing form stats and data feed API. Perfect for betting platforms and racing applications." />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:image" content="https://formfav.com/og-image.png" />
|
||||
<meta property="og:url" content="https://formfav.com" />
|
||||
<meta name="twitter:image" content="https://formfav.com/og-image.png" />
|
||||
<link rel="canonical" href="https://formfav.com" />
|
||||
<link rel="icon" type="image/png" href="/favicon.png" />
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Space+Grotesk:wght@500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
|
||||
<script type="module" crossorigin src="/assets/index-D96MyUpl.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-Fnp_N1rK.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
</html>
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1" />
|
||||
<title>FormFav - Horse Racing Form Stats & Data Feed API</title>
|
||||
<meta name="description" content="Free horse racing form stats and data feed API. RESTful API for betting platforms, sports apps, and analytics tools. Simple 3-parameter integration with comprehensive JSON responses." />
|
||||
<meta name="keywords" content="horse racing API, racing data API, race form API, horse racing data feed, racing statistics API, betting API, horse racing form data, racing API for developers, thoroughbred racing data, racing form stats" />
|
||||
<meta property="og:title" content="FormFav - Horse Racing Form Stats & Data Feed API" />
|
||||
<meta property="og:description" content="Free horse racing form stats and data feed API. RESTful API for betting platforms, sports apps, and analytics tools." />
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta name="twitter:title" content="FormFav - Horse Racing Form Stats & Data Feed API" />
|
||||
<meta name="twitter:description" content="Free horse racing form stats and data feed API. Perfect for betting platforms and racing applications." />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:image" content="https://formfav.com/og-image.png" />
|
||||
<meta property="og:url" content="https://formfav.com" />
|
||||
<meta name="twitter:image" content="https://formfav.com/og-image.png" />
|
||||
<link rel="canonical" href="https://formfav.com" />
|
||||
<link rel="icon" type="image/png" href="/favicon.png" />
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Space+Grotesk:wght@500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
|
||||
<script type="module" crossorigin src="/assets/index-D96MyUpl.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-Fnp_N1rK.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
</html>
|
||||
File diff suppressed because one or more lines are too long
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using RestSharp;
|
||||
@@ -6,131 +7,139 @@ using RestSharp;
|
||||
namespace HorseRacingPredictor.HorseRacing.API
|
||||
{
|
||||
/// <summary>
|
||||
/// Client per The Racing API (theracingapi.com)
|
||||
/// Utilizza HTTP Basic Authentication
|
||||
/// Client per FormFav Racing API (api.formfav.com)
|
||||
/// Autenticazione tramite header X-API-Key.
|
||||
/// Include rate-limiting automatico e retry con backoff esponenziale per HTTP 429.
|
||||
/// </summary>
|
||||
internal class RacingApiClient
|
||||
{
|
||||
private const string BaseUrl = "https://api.theracingapi.com/v1";
|
||||
private const int DefaultDelay = 1100; // Rate limit: 1 req/sec per Free plan
|
||||
private const string BaseUrl = "https://api.formfav.com/v1";
|
||||
private const int MinIntervalMs = 600;
|
||||
private const int MaxRetries = 3;
|
||||
private const int InitialBackoffMs = 2000;
|
||||
|
||||
private readonly string _username;
|
||||
private readonly string _password;
|
||||
private readonly string _apiKey;
|
||||
private DateTime _lastRequestTime = DateTime.MinValue;
|
||||
private readonly object _rateLock = new object();
|
||||
|
||||
public RacingApiClient(string username, string password)
|
||||
public RacingApiClient(string apiKey)
|
||||
{
|
||||
_username = username;
|
||||
_password = password;
|
||||
_apiKey = apiKey;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Esegue una richiesta GET autenticata con HTTP Basic Auth
|
||||
/// Esegue una richiesta GET autenticata con X-API-Key header.
|
||||
/// Rispetta un intervallo minimo tra richieste e gestisce HTTP 429 con retry.
|
||||
/// </summary>
|
||||
private RestResponse ExecuteRequest(string endpoint, int delay = DefaultDelay)
|
||||
private RestResponse ExecuteRequest(string endpoint, CancellationToken ct = default)
|
||||
{
|
||||
string url = $"{BaseUrl}/{endpoint}";
|
||||
var client = new RestClient(url);
|
||||
var request = new RestRequest();
|
||||
ct.ThrowIfCancellationRequested();
|
||||
|
||||
string credentials = Convert.ToBase64String(
|
||||
Encoding.ASCII.GetBytes($"{_username}:{_password}"));
|
||||
request.AddHeader("Authorization", $"Basic {credentials}");
|
||||
|
||||
try
|
||||
// Rate-limit: attendi se necessario
|
||||
lock (_rateLock)
|
||||
{
|
||||
var response = client.Execute(request);
|
||||
if (!response.IsSuccessful)
|
||||
var elapsed = (DateTime.UtcNow - _lastRequestTime).TotalMilliseconds;
|
||||
if (elapsed < MinIntervalMs)
|
||||
{
|
||||
throw new Exception(
|
||||
$"Errore API Racing ({(int)response.StatusCode}): {response.StatusDescription}");
|
||||
int waitMs = (int)(MinIntervalMs - elapsed);
|
||||
if (waitMs > 0)
|
||||
ct.WaitHandle.WaitOne(waitMs);
|
||||
}
|
||||
|
||||
if (delay > 0)
|
||||
Thread.Sleep(delay);
|
||||
|
||||
return response;
|
||||
_lastRequestTime = DateTime.UtcNow;
|
||||
}
|
||||
catch (Exception ex) when (!(ex.Message.StartsWith("Errore API Racing")))
|
||||
|
||||
ct.ThrowIfCancellationRequested();
|
||||
|
||||
string url = $"{BaseUrl}/{endpoint}";
|
||||
int attempt = 0;
|
||||
int backoff = InitialBackoffMs;
|
||||
|
||||
while (true)
|
||||
{
|
||||
throw new Exception($"Errore durante la richiesta a Racing API: {ex.Message}", ex);
|
||||
attempt++;
|
||||
var client = new RestClient(url);
|
||||
var request = new RestRequest();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(_apiKey))
|
||||
request.AddHeader("X-API-Key", _apiKey);
|
||||
|
||||
try
|
||||
{
|
||||
var response = client.Execute(request);
|
||||
|
||||
// HTTP 429 Too Many Requests – backoff e riprova
|
||||
if (response.StatusCode == (HttpStatusCode)429 && attempt <= MaxRetries)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine(
|
||||
$"[FormFav] 429 ricevuto, attendo {backoff}ms prima di riprovare (tentativo {attempt}/{MaxRetries})");
|
||||
|
||||
ct.WaitHandle.WaitOne(backoff);
|
||||
ct.ThrowIfCancellationRequested();
|
||||
|
||||
backoff *= 2;
|
||||
lock (_rateLock) { _lastRequestTime = DateTime.UtcNow; }
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!response.IsSuccessful)
|
||||
{
|
||||
throw new Exception(
|
||||
$"Errore FormFav API ({(int)response.StatusCode}): {response.StatusDescription}\n{response.Content}");
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
catch (Exception ex) when (!(ex is OperationCanceledException) &&
|
||||
!(ex.Message.StartsWith("Errore FormFav API")))
|
||||
{
|
||||
throw new Exception($"Errore durante la richiesta a FormFav API: {ex.Message}", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ottiene le racecard (programma corse) per oggi o domani
|
||||
/// Ottiene l'elenco dei meeting per una data
|
||||
/// </summary>
|
||||
/// <param name="day">"today" oppure "tomorrow"</param>
|
||||
/// <param name="regionCodes">Codici regione opzionali (es. "gb", "ire")</param>
|
||||
public RestResponse GetRacecardsFree(string day = "today", string[] regionCodes = null)
|
||||
public RestResponse GetMeetings(DateTime date, string raceCode = "gallops",
|
||||
string timezone = "Europe/Rome", CancellationToken ct = default)
|
||||
{
|
||||
var sb = new StringBuilder("racecards/free?");
|
||||
sb.Append($"day={day}");
|
||||
var sb = new StringBuilder("form/meetings?");
|
||||
sb.Append($"date={date:yyyy-MM-dd}");
|
||||
if (!string.IsNullOrEmpty(raceCode))
|
||||
sb.Append($"&race_code={raceCode}");
|
||||
if (!string.IsNullOrEmpty(timezone))
|
||||
sb.Append($"&timezone={Uri.EscapeDataString(timezone)}");
|
||||
|
||||
if (regionCodes != null && regionCodes.Length > 0)
|
||||
{
|
||||
foreach (var rc in regionCodes)
|
||||
sb.Append($"®ion_codes={rc}");
|
||||
}
|
||||
|
||||
return ExecuteRequest(sb.ToString());
|
||||
return ExecuteRequest(sb.ToString(), ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ottiene le racecard standard per oggi o domani
|
||||
/// Ottiene i dati di forma per una singola corsa
|
||||
/// </summary>
|
||||
public RestResponse GetRacecardsStandard(string day = "today", string[] regionCodes = null)
|
||||
public RestResponse GetRaceForm(DateTime date, string track, int raceNumber,
|
||||
string raceCode = "gallops", string country = "au",
|
||||
string timezone = "Europe/Rome", CancellationToken ct = default)
|
||||
{
|
||||
var sb = new StringBuilder("racecards/standard?");
|
||||
sb.Append($"day={day}");
|
||||
var sb = new StringBuilder("form?");
|
||||
sb.Append($"date={date:yyyy-MM-dd}");
|
||||
sb.Append($"&track={Uri.EscapeDataString(track)}");
|
||||
sb.Append($"&race={raceNumber}");
|
||||
if (!string.IsNullOrEmpty(raceCode))
|
||||
sb.Append($"&race_code={raceCode}");
|
||||
if (!string.IsNullOrEmpty(country))
|
||||
sb.Append($"&country={country}");
|
||||
if (!string.IsNullOrEmpty(timezone))
|
||||
sb.Append($"&timezone={Uri.EscapeDataString(timezone)}");
|
||||
|
||||
if (regionCodes != null && regionCodes.Length > 0)
|
||||
{
|
||||
foreach (var rc in regionCodes)
|
||||
sb.Append($"®ion_codes={rc}");
|
||||
}
|
||||
|
||||
return ExecuteRequest(sb.ToString());
|
||||
return ExecuteRequest(sb.ToString(), ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ottiene i risultati per un intervallo di date
|
||||
/// Ottiene l'elenco delle piste/venue disponibili
|
||||
/// </summary>
|
||||
public RestResponse GetResults(DateTime startDate, DateTime endDate, string[] regionCodes = null)
|
||||
public RestResponse GetVenues(CancellationToken ct = default)
|
||||
{
|
||||
var sb = new StringBuilder("results?");
|
||||
sb.Append($"start_date={startDate:yyyy-MM-dd}");
|
||||
sb.Append($"&end_date={endDate:yyyy-MM-dd}");
|
||||
|
||||
if (regionCodes != null && regionCodes.Length > 0)
|
||||
{
|
||||
foreach (var rc in regionCodes)
|
||||
sb.Append($"®ion={rc}");
|
||||
}
|
||||
|
||||
return ExecuteRequest(sb.ToString());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ottiene l'elenco delle regioni disponibili
|
||||
/// </summary>
|
||||
public RestResponse GetRegions()
|
||||
{
|
||||
return ExecuteRequest("courses/regions");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ottiene l'elenco dei corsi per le regioni specificate
|
||||
/// </summary>
|
||||
public RestResponse GetCourses(string[] regionCodes = null)
|
||||
{
|
||||
var sb = new StringBuilder("courses?");
|
||||
if (regionCodes != null && regionCodes.Length > 0)
|
||||
{
|
||||
foreach (var rc in regionCodes)
|
||||
sb.Append($"®ion_codes={rc}");
|
||||
}
|
||||
|
||||
return ExecuteRequest(sb.ToString());
|
||||
return ExecuteRequest("form/venues", ct);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,118 +1,172 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using HorseRacingPredictor.HorseRacing.API;
|
||||
|
||||
namespace HorseRacingPredictor.HorseRacing
|
||||
{
|
||||
/// <summary>
|
||||
/// Gestore centralizzato per la sezione Corse dei Cavalli.
|
||||
/// Scarica i dati da The Racing API e li converte in DataTable.
|
||||
/// Scarica i dati da FormFav Racing API e li converte in DataTable.
|
||||
/// </summary>
|
||||
public class Main
|
||||
{
|
||||
private RacingApiClient _client;
|
||||
|
||||
public Main(string username, string password)
|
||||
public Main(string apiKey)
|
||||
{
|
||||
_client = new RacingApiClient(username, password);
|
||||
_client = new RacingApiClient(apiKey);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Aggiorna le credenziali API
|
||||
/// Aggiorna la API key
|
||||
/// </summary>
|
||||
public void UpdateCredentials(string username, string password)
|
||||
public void UpdateApiKey(string apiKey)
|
||||
{
|
||||
_client = new RacingApiClient(username, password);
|
||||
_client = new RacingApiClient(apiKey);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Scarica le racecard (programma corse) per oggi o domani e le restituisce come DataTable
|
||||
/// Scarica tutti i meeting per una data, poi per ciascun meeting scarica tutte le corse
|
||||
/// e le restituisce come DataTable con una riga per runner.
|
||||
/// La progress bar avanza per singola corsa scaricata.
|
||||
/// </summary>
|
||||
public DataTable GetRacecards(string day, IProgress<int> progress = null, IProgress<string> status = null)
|
||||
public DataTable GetAllRacesForDate(DateTime date, string raceCode = "gallops",
|
||||
IProgress<int> progress = null, IProgress<string> status = null,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
var dt = CreateRunnerTable();
|
||||
|
||||
try
|
||||
{
|
||||
status?.Report("Connessione a The Racing API...");
|
||||
progress?.Report(10);
|
||||
status?.Report("Connessione a FormFav API...");
|
||||
progress?.Report(2);
|
||||
|
||||
var response = _client.GetRacecardsFree(day);
|
||||
progress?.Report(60);
|
||||
// 1. Ottieni l'elenco dei meeting per la data
|
||||
var meetingsResp = _client.GetMeetings(date, raceCode, ct: ct);
|
||||
progress?.Report(8);
|
||||
|
||||
var meetings = ParseMeetings(meetingsResp.Content);
|
||||
if (meetings.Count == 0)
|
||||
{
|
||||
status?.Report("Nessun meeting trovato per questa data");
|
||||
progress?.Report(100);
|
||||
return dt;
|
||||
}
|
||||
|
||||
// 2. Calcola il totale corse per un progresso granulare
|
||||
int totalRaces = 0;
|
||||
foreach (var m in meetings)
|
||||
if (!m.Abandoned) totalRaces += m.NumberOfRaces;
|
||||
|
||||
if (totalRaces == 0)
|
||||
{
|
||||
status?.Report("Tutti i meeting sono stati annullati");
|
||||
progress?.Report(100);
|
||||
return dt;
|
||||
}
|
||||
|
||||
status?.Report($"Trovati {meetings.Count} meeting ({totalRaces} corse). Scaricamento...");
|
||||
int completedRaces = 0;
|
||||
int errors = 0;
|
||||
|
||||
// 3. Scarica ogni singola corsa
|
||||
foreach (var meeting in meetings)
|
||||
{
|
||||
ct.ThrowIfCancellationRequested();
|
||||
if (meeting.Abandoned) continue;
|
||||
|
||||
for (int raceNum = 1; raceNum <= meeting.NumberOfRaces; raceNum++)
|
||||
{
|
||||
ct.ThrowIfCancellationRequested();
|
||||
|
||||
try
|
||||
{
|
||||
status?.Report($"{meeting.Track} R{raceNum}/{meeting.NumberOfRaces} " +
|
||||
$"({completedRaces + 1}/{totalRaces})");
|
||||
|
||||
var formResp = _client.GetRaceForm(date, meeting.TrackSlug, raceNum,
|
||||
raceCode, meeting.Country, ct: ct);
|
||||
|
||||
ParseRaceFormIntoTable(dt, formResp.Content);
|
||||
}
|
||||
catch (OperationCanceledException) { throw; }
|
||||
catch (Exception ex)
|
||||
{
|
||||
errors++;
|
||||
System.Diagnostics.Debug.WriteLine(
|
||||
$"Errore scaricamento {meeting.Track} R{raceNum}: {ex.Message}");
|
||||
}
|
||||
|
||||
completedRaces++;
|
||||
// Progresso: 8% per meetings, 8-98% per le corse singole, 100% alla fine
|
||||
int pct = 8 + (int)((double)completedRaces / totalRaces * 90);
|
||||
progress?.Report(Math.Min(pct, 98));
|
||||
}
|
||||
}
|
||||
|
||||
status?.Report("Elaborazione racecard...");
|
||||
var table = ParseRacecardsResponse(response.Content);
|
||||
progress?.Report(100);
|
||||
|
||||
status?.Report($"Trovate {table.Rows.Count} corse");
|
||||
return table;
|
||||
string errMsg = errors > 0 ? $" ({errors} errori)" : "";
|
||||
status?.Report($"Trovati {dt.Rows.Count} corridori in {meetings.Count} meeting{errMsg}");
|
||||
return dt;
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
status?.Report("Scaricamento annullato");
|
||||
return dt;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
status?.Report($"Errore: {ex.Message}");
|
||||
return CreateEmptyRacecardsTable();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Scarica i risultati per un intervallo di date
|
||||
/// </summary>
|
||||
public DataTable GetResults(DateTime startDate, DateTime endDate,
|
||||
IProgress<int> progress = null, IProgress<string> status = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
status?.Report("Scaricamento risultati...");
|
||||
progress?.Report(10);
|
||||
|
||||
var response = _client.GetResults(startDate, endDate);
|
||||
progress?.Report(60);
|
||||
|
||||
status?.Report("Elaborazione risultati...");
|
||||
var table = ParseResultsResponse(response.Content);
|
||||
progress?.Report(100);
|
||||
|
||||
status?.Report($"Trovati {table.Rows.Count} risultati");
|
||||
return table;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
status?.Report($"Errore: {ex.Message}");
|
||||
return CreateEmptyResultsTable();
|
||||
return dt;
|
||||
}
|
||||
}
|
||||
|
||||
#region DataTable creation
|
||||
|
||||
private DataTable CreateEmptyRacecardsTable()
|
||||
private DataTable CreateRunnerTable()
|
||||
{
|
||||
var dt = new DataTable();
|
||||
dt.Columns.Add("Ora", typeof(string));
|
||||
// Campi corsa
|
||||
dt.Columns.Add("Ippodromo", typeof(string));
|
||||
dt.Columns.Add("Regione", typeof(string));
|
||||
dt.Columns.Add("Corsa", typeof(string));
|
||||
dt.Columns.Add("Corsa N.", typeof(int));
|
||||
dt.Columns.Add("Nome Corsa", typeof(string));
|
||||
dt.Columns.Add("Orario", typeof(string));
|
||||
dt.Columns.Add("Distanza", typeof(string));
|
||||
dt.Columns.Add("Tipo", typeof(string));
|
||||
dt.Columns.Add("Terreno", typeof(string));
|
||||
dt.Columns.Add("Classe", typeof(string));
|
||||
dt.Columns.Add("Terreno", typeof(string));
|
||||
dt.Columns.Add("N. Corridori", typeof(int));
|
||||
dt.Columns.Add("Età", typeof(string));
|
||||
dt.Columns.Add("Meteo", typeof(string));
|
||||
dt.Columns.Add("Premio", typeof(string));
|
||||
return dt;
|
||||
}
|
||||
|
||||
private DataTable CreateEmptyResultsTable()
|
||||
{
|
||||
var dt = new DataTable();
|
||||
dt.Columns.Add("Data", typeof(string));
|
||||
dt.Columns.Add("Ippodromo", typeof(string));
|
||||
dt.Columns.Add("Corsa", typeof(string));
|
||||
dt.Columns.Add("Distanza", typeof(string));
|
||||
dt.Columns.Add("Terreno", typeof(string));
|
||||
dt.Columns.Add("1° Classificato", typeof(string));
|
||||
dt.Columns.Add("2° Classificato", typeof(string));
|
||||
dt.Columns.Add("3° Classificato", typeof(string));
|
||||
dt.Columns.Add("Fantino 1°", typeof(string));
|
||||
dt.Columns.Add("SP 1°", typeof(string));
|
||||
dt.Columns.Add("N. Corridori", typeof(int));
|
||||
// Campi corridore
|
||||
dt.Columns.Add("Num", typeof(int));
|
||||
dt.Columns.Add("Cavallo", typeof(string));
|
||||
dt.Columns.Add("Fantino", typeof(string));
|
||||
dt.Columns.Add("Allenatore", typeof(string));
|
||||
dt.Columns.Add("Peso", typeof(string));
|
||||
dt.Columns.Add("Claim", typeof(string));
|
||||
dt.Columns.Add("Box", typeof(string));
|
||||
dt.Columns.Add("Età", typeof(string));
|
||||
dt.Columns.Add("Forma", typeof(string));
|
||||
dt.Columns.Add("Ultimi 20", typeof(string));
|
||||
dt.Columns.Add("Colori", typeof(string));
|
||||
dt.Columns.Add("Cambio Equip.", typeof(string));
|
||||
// Statistiche overall
|
||||
dt.Columns.Add("Vitt.", typeof(string));
|
||||
dt.Columns.Add("Piazz.", typeof(string));
|
||||
dt.Columns.Add("Partenze", typeof(string));
|
||||
dt.Columns.Add("% Vitt.", typeof(string));
|
||||
dt.Columns.Add("% Piazz.", typeof(string));
|
||||
// Statistiche pista
|
||||
dt.Columns.Add("Pista V/P/S", typeof(string));
|
||||
// Statistiche distanza
|
||||
dt.Columns.Add("Dist. V/P/S", typeof(string));
|
||||
// Statistiche condizione
|
||||
dt.Columns.Add("Cond. V/P/S", typeof(string));
|
||||
// Stato
|
||||
dt.Columns.Add("Ritirato", typeof(string));
|
||||
return dt;
|
||||
}
|
||||
|
||||
@@ -120,10 +174,19 @@ namespace HorseRacingPredictor.HorseRacing
|
||||
|
||||
#region JSON Parsing
|
||||
|
||||
private DataTable ParseRacecardsResponse(string json)
|
||||
private class MeetingInfo
|
||||
{
|
||||
var dt = CreateEmptyRacecardsTable();
|
||||
if (string.IsNullOrEmpty(json)) return dt;
|
||||
public string Track { get; set; }
|
||||
public string TrackSlug { get; set; }
|
||||
public string Country { get; set; }
|
||||
public int NumberOfRaces { get; set; }
|
||||
public bool Abandoned { get; set; }
|
||||
}
|
||||
|
||||
private List<MeetingInfo> ParseMeetings(string json)
|
||||
{
|
||||
var meetings = new List<MeetingInfo>();
|
||||
if (string.IsNullOrEmpty(json)) return meetings;
|
||||
|
||||
try
|
||||
{
|
||||
@@ -131,62 +194,57 @@ namespace HorseRacingPredictor.HorseRacing
|
||||
{
|
||||
var root = doc.RootElement;
|
||||
|
||||
if (!root.TryGetProperty("racecards", out var racecardsEl))
|
||||
return dt;
|
||||
JsonElement arr;
|
||||
if (root.ValueKind == JsonValueKind.Array)
|
||||
arr = root;
|
||||
else if (root.TryGetProperty("meetings", out var meetingsEl) &&
|
||||
meetingsEl.ValueKind == JsonValueKind.Array)
|
||||
arr = meetingsEl;
|
||||
else
|
||||
return meetings;
|
||||
|
||||
foreach (var rc in racecardsEl.EnumerateArray())
|
||||
foreach (var m in arr.EnumerateArray())
|
||||
{
|
||||
try
|
||||
{
|
||||
var row = dt.NewRow();
|
||||
|
||||
row["Ora"] = GetString(rc, "off_time", "");
|
||||
row["Ippodromo"] = GetString(rc, "course", "");
|
||||
row["Regione"] = GetString(rc, "region", "");
|
||||
row["Corsa"] = GetString(rc, "race_name", "");
|
||||
row["Distanza"] = GetString(rc, "distance", "");
|
||||
row["Tipo"] = GetString(rc, "type", "");
|
||||
row["Classe"] = GetString(rc, "race_class", "");
|
||||
row["Terreno"] = GetString(rc, "going", "");
|
||||
row["Età"] = GetString(rc, "age_band", "");
|
||||
row["Premio"] = GetString(rc, "prize", "");
|
||||
|
||||
if (rc.TryGetProperty("runners", out var runnersEl) &&
|
||||
runnersEl.ValueKind == JsonValueKind.Array)
|
||||
var info = new MeetingInfo
|
||||
{
|
||||
row["N. Corridori"] = runnersEl.GetArrayLength();
|
||||
}
|
||||
else if (rc.TryGetProperty("field_size", out var fsEl) &&
|
||||
fsEl.ValueKind == JsonValueKind.Number)
|
||||
{
|
||||
row["N. Corridori"] = fsEl.GetInt32();
|
||||
}
|
||||
Track = GetString(m, "track", GetString(m, "venue", "")),
|
||||
TrackSlug = GetString(m, "trackSlug",
|
||||
GetString(m, "track", GetString(m, "venue", ""))
|
||||
.ToLowerInvariant().Replace(" ", "-")),
|
||||
Country = GetString(m, "country", "au"),
|
||||
Abandoned = false
|
||||
};
|
||||
|
||||
if (m.TryGetProperty("abandoned", out var abEl) &&
|
||||
abEl.ValueKind == JsonValueKind.True)
|
||||
info.Abandoned = true;
|
||||
|
||||
if (m.TryGetProperty("numberOfRaces", out var nrEl) &&
|
||||
nrEl.ValueKind == JsonValueKind.Number)
|
||||
info.NumberOfRaces = nrEl.GetInt32();
|
||||
else if (m.TryGetProperty("races", out var racesEl) &&
|
||||
racesEl.ValueKind == JsonValueKind.Number)
|
||||
info.NumberOfRaces = racesEl.GetInt32();
|
||||
else
|
||||
{
|
||||
row["N. Corridori"] = 0;
|
||||
}
|
||||
info.NumberOfRaces = 10;
|
||||
|
||||
dt.Rows.Add(row);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Salta righe problematiche
|
||||
if (info.NumberOfRaces > 0 && !string.IsNullOrEmpty(info.Track))
|
||||
meetings.Add(info);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Restituisci tabella vuota in caso di errore di parsing
|
||||
}
|
||||
catch { }
|
||||
|
||||
return dt;
|
||||
return meetings;
|
||||
}
|
||||
|
||||
private DataTable ParseResultsResponse(string json)
|
||||
private void ParseRaceFormIntoTable(DataTable dt, string json)
|
||||
{
|
||||
var dt = CreateEmptyResultsTable();
|
||||
if (string.IsNullOrEmpty(json)) return dt;
|
||||
if (string.IsNullOrEmpty(json)) return;
|
||||
|
||||
try
|
||||
{
|
||||
@@ -194,73 +252,169 @@ namespace HorseRacingPredictor.HorseRacing
|
||||
{
|
||||
var root = doc.RootElement;
|
||||
|
||||
if (!root.TryGetProperty("results", out var resultsEl))
|
||||
return dt;
|
||||
string track = GetString(root, "track", "");
|
||||
int raceNumber = GetInt(root, "raceNumber");
|
||||
string raceName = GetString(root, "raceName", "");
|
||||
string distance = GetString(root, "distance", "");
|
||||
string condition = GetString(root, "condition", "");
|
||||
string weather = GetString(root, "weather", "");
|
||||
string raceClass = GetString(root, "raceClass", "");
|
||||
string prizeMoney = GetString(root, "prizeMoney", "");
|
||||
string startTime = GetString(root, "startTime", "");
|
||||
int numberOfRunners = GetInt(root, "numberOfRunners");
|
||||
|
||||
foreach (var res in resultsEl.EnumerateArray())
|
||||
// Formatta orario se è un ISO datetime
|
||||
string orario = "";
|
||||
if (!string.IsNullOrEmpty(startTime))
|
||||
{
|
||||
try
|
||||
{
|
||||
var dto = DateTimeOffset.Parse(startTime);
|
||||
var romeTz = TimeZoneInfo.FindSystemTimeZoneById("W. Europe Standard Time");
|
||||
orario = TimeZoneInfo.ConvertTime(dto, romeTz).ToString("HH:mm");
|
||||
}
|
||||
catch
|
||||
{
|
||||
orario = startTime;
|
||||
}
|
||||
}
|
||||
|
||||
if (!root.TryGetProperty("runners", out var runnersEl) ||
|
||||
runnersEl.ValueKind != JsonValueKind.Array)
|
||||
return;
|
||||
|
||||
foreach (var runner in runnersEl.EnumerateArray())
|
||||
{
|
||||
try
|
||||
{
|
||||
var row = dt.NewRow();
|
||||
|
||||
row["Data"] = GetString(res, "date", "");
|
||||
row["Ippodromo"] = GetString(res, "course", "");
|
||||
row["Corsa"] = GetString(res, "race_name", "");
|
||||
row["Distanza"] = GetString(res, "distance", "");
|
||||
row["Terreno"] = GetString(res, "going", "");
|
||||
// Campi corsa
|
||||
row["Ippodromo"] = track;
|
||||
row["Corsa N."] = raceNumber;
|
||||
row["Nome Corsa"] = raceName;
|
||||
row["Orario"] = orario;
|
||||
row["Distanza"] = distance;
|
||||
row["Terreno"] = condition;
|
||||
row["Classe"] = raceClass;
|
||||
row["Meteo"] = weather;
|
||||
row["Premio"] = prizeMoney;
|
||||
row["N. Corridori"] = numberOfRunners;
|
||||
|
||||
if (res.TryGetProperty("runners", out var runnersEl) &&
|
||||
runnersEl.ValueKind == JsonValueKind.Array)
|
||||
// Campi corridore
|
||||
row["Num"] = GetInt(runner, "number");
|
||||
row["Cavallo"] = GetString(runner, "name", "");
|
||||
row["Fantino"] = GetString(runner, "jockey", "");
|
||||
row["Allenatore"] = GetString(runner, "trainer", "");
|
||||
row["Peso"] = GetDouble(runner, "weight") > 0
|
||||
? GetDouble(runner, "weight").ToString("F1")
|
||||
: GetString(runner, "weight", "");
|
||||
row["Claim"] = GetDouble(runner, "claim") > 0
|
||||
? GetDouble(runner, "claim").ToString("F1")
|
||||
: "";
|
||||
row["Box"] = GetInt(runner, "barrier") > 0
|
||||
? GetInt(runner, "barrier").ToString()
|
||||
: GetString(runner, "barrier", "");
|
||||
row["Età"] = GetInt(runner, "age") > 0
|
||||
? GetInt(runner, "age").ToString()
|
||||
: GetString(runner, "age", "");
|
||||
row["Forma"] = GetString(runner, "form", "");
|
||||
row["Ultimi 20"] = GetString(runner, "last20Starts", "");
|
||||
row["Colori"] = GetString(runner, "racingColours", "");
|
||||
row["Cambio Equip."] = GetString(runner, "gearChange", "");
|
||||
|
||||
// Statistiche overall
|
||||
if (runner.TryGetProperty("stats", out var statsEl))
|
||||
{
|
||||
int idx = 0;
|
||||
foreach (var runner in runnersEl.EnumerateArray())
|
||||
{
|
||||
var pos = GetString(runner, "position", "");
|
||||
if (pos == "1" || idx == 0)
|
||||
{
|
||||
row["1° Classificato"] = GetString(runner, "horse", "");
|
||||
row["Fantino 1°"] = GetString(runner, "jockey", "");
|
||||
row["SP 1°"] = GetString(runner, "sp", "");
|
||||
}
|
||||
else if (pos == "2" || idx == 1)
|
||||
{
|
||||
row["2° Classificato"] = GetString(runner, "horse", "");
|
||||
}
|
||||
else if (pos == "3" || idx == 2)
|
||||
{
|
||||
row["3° Classificato"] = GetString(runner, "horse", "");
|
||||
}
|
||||
idx++;
|
||||
if (idx >= 3) break;
|
||||
}
|
||||
ParseStatGroup(statsEl, "overall", row, "Vitt.", "Piazz.", "Partenze", "% Vitt.", "% Piazz.");
|
||||
|
||||
row["Pista V/P/S"] = FormatStatSummary(statsEl, "track");
|
||||
row["Dist. V/P/S"] = FormatStatSummary(statsEl, "distance");
|
||||
row["Cond. V/P/S"] = FormatStatSummary(statsEl, "condition");
|
||||
}
|
||||
|
||||
// Ritirato
|
||||
bool scratched = false;
|
||||
if (runner.TryGetProperty("scratched", out var scEl) && scEl.ValueKind == JsonValueKind.True)
|
||||
scratched = true;
|
||||
row["Ritirato"] = scratched ? "Sì" : "";
|
||||
|
||||
dt.Rows.Add(row);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Salta righe problematiche
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Restituisci tabella vuota in caso di errore di parsing
|
||||
}
|
||||
|
||||
return dt;
|
||||
catch { }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Estrae wins, places, starts, winPercent, placePercent da un sotto-oggetto stats
|
||||
/// </summary>
|
||||
private static void ParseStatGroup(JsonElement statsEl, string group,
|
||||
DataRow row, string winsCol, string placesCol, string startsCol,
|
||||
string winPctCol, string placePctCol)
|
||||
{
|
||||
if (!statsEl.TryGetProperty(group, out var g)) return;
|
||||
|
||||
int wins = GetInt(g, "wins");
|
||||
int places = GetInt(g, "places");
|
||||
int starts = GetInt(g, "starts");
|
||||
double winPct = GetDouble(g, "winPercent");
|
||||
double placePct = GetDouble(g, "placePercent");
|
||||
|
||||
row[winsCol] = wins.ToString();
|
||||
row[placesCol] = places.ToString();
|
||||
row[startsCol] = starts.ToString();
|
||||
row[winPctCol] = (winPct * 100).ToString("F0") + "%";
|
||||
row[placePctCol] = (placePct * 100).ToString("F0") + "%";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Formatta un riassunto "V-P/S" per un sotto-gruppo statistico (es. track, distance, condition)
|
||||
/// </summary>
|
||||
private static string FormatStatSummary(JsonElement statsEl, string group)
|
||||
{
|
||||
if (!statsEl.TryGetProperty(group, out var g)) return "";
|
||||
|
||||
int wins = GetInt(g, "wins");
|
||||
int places = GetInt(g, "places");
|
||||
int starts = GetInt(g, "starts");
|
||||
|
||||
if (starts == 0) return "";
|
||||
return $"{wins}-{places}/{starts}";
|
||||
}
|
||||
|
||||
#region Helpers
|
||||
|
||||
private static string GetString(JsonElement el, string property, string defaultValue)
|
||||
{
|
||||
if (el.TryGetProperty(property, out var prop) && prop.ValueKind == JsonValueKind.String)
|
||||
return prop.GetString() ?? defaultValue;
|
||||
if (el.TryGetProperty(property, out prop) && prop.ValueKind == JsonValueKind.Number)
|
||||
return prop.ToString();
|
||||
if (el.TryGetProperty(property, out var prop))
|
||||
{
|
||||
if (prop.ValueKind == JsonValueKind.String)
|
||||
return prop.GetString() ?? defaultValue;
|
||||
if (prop.ValueKind == JsonValueKind.Number)
|
||||
return prop.ToString();
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
private static int GetInt(JsonElement el, string property)
|
||||
{
|
||||
if (el.TryGetProperty(property, out var prop) && prop.ValueKind == JsonValueKind.Number)
|
||||
return prop.GetInt32();
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static double GetDouble(JsonElement el, string property)
|
||||
{
|
||||
if (el.TryGetProperty(property, out var prop) && prop.ValueKind == JsonValueKind.Number)
|
||||
return prop.GetDouble();
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -636,14 +636,19 @@
|
||||
<!-- Source selector + controls -->
|
||||
<StackPanel Grid.Row="0" Margin="0,0,0,14">
|
||||
<StackPanel Orientation="Horizontal" Margin="0,0,0,10">
|
||||
<RadioButton x:Name="rbRcApi" Content="API" IsChecked="True"
|
||||
<RadioButton x:Name="rbRcApi" Content="API (FormFav)" IsChecked="True"
|
||||
GroupName="RcSource" Checked="rbRcSource_Checked"/>
|
||||
<RadioButton x:Name="rbRcCsv" Content="CSV (Punters)"
|
||||
Margin="20,0,0,0"
|
||||
GroupName="RcSource" Checked="rbRcSource_Checked"/>
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<ComboBox x:Name="cmbDay" Width="140"/>
|
||||
<DatePicker x:Name="dpRacing" Width="160"/>
|
||||
<ComboBox x:Name="cmbRaceCode" Width="120" Margin="8,0,0,0" SelectedIndex="0">
|
||||
<ComboBoxItem Content="Galoppo"/>
|
||||
<ComboBoxItem Content="Trotto"/>
|
||||
<ComboBoxItem Content="Levrieri"/>
|
||||
</ComboBox>
|
||||
<Button x:Name="btnDownloadRc" Content="Scarica Corse"
|
||||
Style="{StaticResource AccentBtn}"
|
||||
Background="{StaticResource BrBlue}" Margin="12,0,0,0"
|
||||
@@ -681,60 +686,79 @@
|
||||
<!-- ???????? PAGE: SETTINGS ???????? -->
|
||||
<ScrollViewer x:Name="pageSettings" Visibility="Collapsed"
|
||||
VerticalScrollBarVisibility="Auto" Padding="28,20">
|
||||
<StackPanel MaxWidth="620" HorizontalAlignment="Left">
|
||||
<StackPanel Width="560" HorizontalAlignment="Left">
|
||||
|
||||
<!-- FOOTBALL SECTION -->
|
||||
<TextBlock Text="Calcio — API" FontSize="15" FontFamily="Segoe UI Semibold"
|
||||
Foreground="{StaticResource BrBlue}" Margin="0,0,0,10"/>
|
||||
<Border Background="{StaticResource BrSurface0}" CornerRadius="10"
|
||||
<Border Background="{StaticResource BrSurface0}" CornerRadius="12"
|
||||
BorderBrush="{StaticResource BrSurface1}" BorderThickness="1"
|
||||
Padding="20,18" Margin="0,0,0,24">
|
||||
Padding="24,20" Margin="0,0,0,20">
|
||||
<StackPanel>
|
||||
<TextBlock Text="API Key (api-football)" Foreground="{StaticResource BrSubtext0}"
|
||||
FontSize="12" Margin="0,0,0,6"/>
|
||||
<TextBox x:Name="txtApiKey" Style="{StaticResource FlatTb}"/>
|
||||
<StackPanel Orientation="Horizontal" Margin="0,0,0,16">
|
||||
<TextBlock Text="" FontFamily="Segoe MDL2 Assets" FontSize="16"
|
||||
Foreground="{StaticResource BrBlue}" VerticalAlignment="Center" Margin="0,0,8,0"/>
|
||||
<TextBlock Text="Calcio" FontSize="15" FontFamily="Segoe UI Semibold"
|
||||
Foreground="{StaticResource BrText}" VerticalAlignment="Center"/>
|
||||
<TextBlock Text=" API Football" FontSize="11"
|
||||
Foreground="{StaticResource BrSubtext0}" VerticalAlignment="Center" Margin="4,1,0,0"/>
|
||||
</StackPanel>
|
||||
|
||||
<TextBlock Text="API Key" Foreground="{StaticResource BrSubtext0}"
|
||||
FontSize="11" Margin="0,0,0,4"/>
|
||||
<TextBox x:Name="txtApiKey" Style="{StaticResource FlatTb}" MaxWidth="510" HorizontalAlignment="Left"/>
|
||||
|
||||
<Grid Margin="0,14,0,0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="12"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="245"/>
|
||||
<ColumnDefinition Width="22"/>
|
||||
<ColumnDefinition Width="245"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<StackPanel Grid.Column="0">
|
||||
<TextBlock Text="Prefisso" Foreground="{StaticResource BrSubtext0}" FontSize="12" Margin="0,0,0,6"/>
|
||||
<TextBlock Text="Prefisso file" Foreground="{StaticResource BrSubtext0}" FontSize="11" Margin="0,0,0,4"/>
|
||||
<TextBox x:Name="txtFbPrefix" Style="{StaticResource FlatTb}"/>
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Column="2">
|
||||
<TextBlock Text="Suffisso" Foreground="{StaticResource BrSubtext0}" FontSize="12" Margin="0,0,0,6"/>
|
||||
<TextBlock Text="Suffisso file" Foreground="{StaticResource BrSubtext0}" FontSize="11" Margin="0,0,0,4"/>
|
||||
<TextBox x:Name="txtFbSuffix" Style="{StaticResource FlatTb}"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
<StackPanel Orientation="Horizontal" Margin="0,14,0,0">
|
||||
<CheckBox x:Name="chkFbIncludeDate" IsChecked="True">Includi data</CheckBox>
|
||||
<ComboBox x:Name="cmbFbDateFormat" Width="180" Margin="16,0,0,0">
|
||||
<Border Height="1" Background="{StaticResource BrBorder}" Margin="0,16,0,14"/>
|
||||
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="16"/>
|
||||
<ColumnDefinition Width="170"/>
|
||||
<ColumnDefinition Width="16"/>
|
||||
<ColumnDefinition Width="140"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<CheckBox x:Name="chkFbIncludeDate" Grid.Column="0" IsChecked="True">Includi data</CheckBox>
|
||||
<ComboBox x:Name="cmbFbDateFormat" Grid.Column="2" SelectedIndex="0">
|
||||
<ComboBoxItem Content="yyyy-MM-dd"/>
|
||||
<ComboBoxItem Content="dd-MM-yyyy"/>
|
||||
<ComboBoxItem Content="yyyyMMdd"/>
|
||||
<ComboBoxItem Content="ddMMyyyy"/>
|
||||
<ComboBoxItem Content="yyyy-MM-dd_HH-mm"/>
|
||||
</ComboBox>
|
||||
</StackPanel>
|
||||
<ComboBox x:Name="cmbFbFormat" Grid.Column="4" SelectedIndex="0">
|
||||
<ComboBoxItem Content="CSV"/>
|
||||
<ComboBoxItem Content="JSON"/>
|
||||
<ComboBoxItem Content="XML"/>
|
||||
</ComboBox>
|
||||
</Grid>
|
||||
|
||||
<TextBlock Text="Anteprima nome file:" Foreground="{StaticResource BrOverlay0}"
|
||||
FontSize="11" Margin="0,12,0,3"/>
|
||||
<TextBlock x:Name="txtFbPreview" FontSize="13" Foreground="{StaticResource BrText}"/>
|
||||
|
||||
<TextBlock Text="Formato esportazione" Foreground="{StaticResource BrSubtext0}"
|
||||
FontSize="12" Margin="0,14,0,6"/>
|
||||
<ComboBox x:Name="cmbFbFormat" Width="160" SelectedIndex="0">
|
||||
<ComboBoxItem Content="CSV"/>
|
||||
<ComboBoxItem Content="JSON"/>
|
||||
<ComboBoxItem Content="XML"/>
|
||||
</ComboBox>
|
||||
<Border Background="{StaticResource BrSurface1}" CornerRadius="6"
|
||||
Padding="10,6" Margin="0,12,0,0">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Text="" FontFamily="Segoe MDL2 Assets" FontSize="11"
|
||||
Foreground="{StaticResource BrOverlay0}" VerticalAlignment="Center" Margin="0,0,6,0"/>
|
||||
<TextBlock x:Name="txtFbPreview" FontSize="12" Foreground="{StaticResource BrSubtext0}"
|
||||
VerticalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<TextBlock Text="Cartella esportazione" Foreground="{StaticResource BrSubtext0}"
|
||||
FontSize="12" Margin="0,14,0,6"/>
|
||||
FontSize="11" Margin="0,14,0,4"/>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
@@ -750,61 +774,76 @@
|
||||
</Border>
|
||||
|
||||
<!-- HORSE RACING SECTION -->
|
||||
<TextBlock Text="Corse Cavalli — API" FontSize="15" FontFamily="Segoe UI Semibold"
|
||||
Foreground="{StaticResource BrBlue}" Margin="0,0,0,10"/>
|
||||
<Border Background="{StaticResource BrSurface0}" CornerRadius="10"
|
||||
<Border Background="{StaticResource BrSurface0}" CornerRadius="12"
|
||||
BorderBrush="{StaticResource BrSurface1}" BorderThickness="1"
|
||||
Padding="20,18" Margin="0,0,0,24">
|
||||
Padding="24,20" Margin="0,0,0,20">
|
||||
<StackPanel>
|
||||
<TextBlock Text="Username" Foreground="{StaticResource BrSubtext0}"
|
||||
FontSize="12" Margin="0,0,0,6"/>
|
||||
<TextBox x:Name="txtRacingUser" Style="{StaticResource FlatTb}"/>
|
||||
<StackPanel Orientation="Horizontal" Margin="0,0,0,16">
|
||||
<TextBlock Text="" FontFamily="Segoe MDL2 Assets" FontSize="16"
|
||||
Foreground="{StaticResource BrBlue}" VerticalAlignment="Center" Margin="0,0,8,0"/>
|
||||
<TextBlock Text="Corse Cavalli" FontSize="15" FontFamily="Segoe UI Semibold"
|
||||
Foreground="{StaticResource BrText}" VerticalAlignment="Center"/>
|
||||
<TextBlock Text=" FormFav API" FontSize="11"
|
||||
Foreground="{StaticResource BrSubtext0}" VerticalAlignment="Center" Margin="4,1,0,0"/>
|
||||
</StackPanel>
|
||||
|
||||
<TextBlock Text="API Key" Foreground="{StaticResource BrSubtext0}"
|
||||
FontSize="11" Margin="0,0,0,4"/>
|
||||
<TextBox x:Name="txtRacingApiKey" Style="{StaticResource FlatTb}" MaxWidth="510" HorizontalAlignment="Left"/>
|
||||
|
||||
<Grid Margin="0,14,0,0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="12"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="245"/>
|
||||
<ColumnDefinition Width="22"/>
|
||||
<ColumnDefinition Width="245"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<StackPanel Grid.Column="0">
|
||||
<TextBlock Text="Prefisso" Foreground="{StaticResource BrSubtext0}" FontSize="12" Margin="0,0,0,6"/>
|
||||
<TextBlock Text="Prefisso file" Foreground="{StaticResource BrSubtext0}" FontSize="11" Margin="0,0,0,4"/>
|
||||
<TextBox x:Name="txtRcPrefix" Style="{StaticResource FlatTb}"/>
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Column="2">
|
||||
<TextBlock Text="Suffisso" Foreground="{StaticResource BrSubtext0}" FontSize="12" Margin="0,0,0,6"/>
|
||||
<TextBlock Text="Suffisso file" Foreground="{StaticResource BrSubtext0}" FontSize="11" Margin="0,0,0,4"/>
|
||||
<TextBox x:Name="txtRcSuffix" Style="{StaticResource FlatTb}"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
<StackPanel Orientation="Horizontal" Margin="0,14,0,0">
|
||||
<CheckBox x:Name="chkRcIncludeDate" IsChecked="True">Includi data</CheckBox>
|
||||
<ComboBox x:Name="cmbRcDateFormat" Width="180" Margin="16,0,0,0">
|
||||
<Border Height="1" Background="{StaticResource BrBorder}" Margin="0,16,0,14"/>
|
||||
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="16"/>
|
||||
<ColumnDefinition Width="170"/>
|
||||
<ColumnDefinition Width="16"/>
|
||||
<ColumnDefinition Width="140"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<CheckBox x:Name="chkRcIncludeDate" Grid.Column="0" IsChecked="True">Includi data</CheckBox>
|
||||
<ComboBox x:Name="cmbRcDateFormat" Grid.Column="2" SelectedIndex="0">
|
||||
<ComboBoxItem Content="yyyy-MM-dd"/>
|
||||
<ComboBoxItem Content="dd-MM-yyyy"/>
|
||||
<ComboBoxItem Content="yyyyMMdd"/>
|
||||
<ComboBoxItem Content="ddMMyyyy"/>
|
||||
<ComboBoxItem Content="yyyy-MM-dd_HH-mm"/>
|
||||
</ComboBox>
|
||||
</StackPanel>
|
||||
<ComboBox x:Name="cmbRcFormat" Grid.Column="4" SelectedIndex="0">
|
||||
<ComboBoxItem Content="CSV"/>
|
||||
<ComboBoxItem Content="JSON"/>
|
||||
<ComboBoxItem Content="XML"/>
|
||||
</ComboBox>
|
||||
</Grid>
|
||||
|
||||
<TextBlock Text="Anteprima nome file:" Foreground="{StaticResource BrOverlay0}"
|
||||
FontSize="11" Margin="0,12,0,3"/>
|
||||
<TextBlock x:Name="txtRcPreview" FontSize="13" Foreground="{StaticResource BrText}"/>
|
||||
|
||||
<TextBlock Text="Formato esportazione" Foreground="{StaticResource BrSubtext0}"
|
||||
FontSize="12" Margin="0,14,0,6"/>
|
||||
<ComboBox x:Name="cmbRcFormat" Width="160" SelectedIndex="0">
|
||||
<ComboBoxItem Content="CSV"/>
|
||||
<ComboBoxItem Content="JSON"/>
|
||||
<ComboBoxItem Content="XML"/>
|
||||
</ComboBox>
|
||||
|
||||
<TextBlock Text="Password" Foreground="{StaticResource BrSubtext0}"
|
||||
FontSize="12" Margin="0,14,0,6"/>
|
||||
<PasswordBox x:Name="txtRacingPass" Style="{StaticResource FlatPb}"/>
|
||||
<Border Background="{StaticResource BrSurface1}" CornerRadius="6"
|
||||
Padding="10,6" Margin="0,12,0,0">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Text="" FontFamily="Segoe MDL2 Assets" FontSize="11"
|
||||
Foreground="{StaticResource BrOverlay0}" VerticalAlignment="Center" Margin="0,0,6,0"/>
|
||||
<TextBlock x:Name="txtRcPreview" FontSize="12" Foreground="{StaticResource BrSubtext0}"
|
||||
VerticalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<TextBlock Text="Cartella esportazione" Foreground="{StaticResource BrSubtext0}"
|
||||
FontSize="12" Margin="0,14,0,6"/>
|
||||
FontSize="11" Margin="0,14,0,4"/>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
|
||||
@@ -5,6 +5,7 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Collections.ObjectModel;
|
||||
@@ -19,18 +20,18 @@ namespace HorseRacingPredictor
|
||||
private HorseRacing.Main _racingManager;
|
||||
private DataTable _footballData;
|
||||
private DataTable _racingData;
|
||||
private CancellationTokenSource _racingCts;
|
||||
|
||||
// Virtual Football
|
||||
private readonly ObservableCollection<VirtualFootball.VirtualMatch> _vfbResults = new ObservableCollection<VirtualFootball.VirtualMatch>();
|
||||
|
||||
private const string DefaultRacingUser = "qi1mHOHPquDY9KNDASAeGipy";
|
||||
private const string DefaultRacingPass = "RXNFU1YX27R9rTnk8Vop8ZfH";
|
||||
private const string DefaultRacingApiKey = "";
|
||||
|
||||
public MainWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
_footballManager = new Football.Main();
|
||||
_racingManager = new HorseRacing.Main(DefaultRacingUser, DefaultRacingPass);
|
||||
_racingManager = new HorseRacing.Main(DefaultRacingApiKey);
|
||||
// Wire preview update events
|
||||
txtFbPrefix.TextChanged += (s, e) => UpdateFbPreview();
|
||||
txtFbSuffix.TextChanged += (s, e) => UpdateFbPreview();
|
||||
@@ -171,9 +172,7 @@ namespace HorseRacingPredictor
|
||||
private void Window_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
dpFootball.SelectedDate = DateTime.Today;
|
||||
cmbDay.Items.Add("Oggi");
|
||||
cmbDay.Items.Add("Domani");
|
||||
cmbDay.SelectedIndex = 0;
|
||||
dpRacing.SelectedDate = DateTime.Today;
|
||||
LoadSettings();
|
||||
}
|
||||
|
||||
@@ -338,9 +337,10 @@ namespace HorseRacingPredictor
|
||||
private void rbRcSource_Checked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
// Toggle visibility of API vs CSV controls
|
||||
if (cmbDay == null || btnDownloadRc == null || btnBrowseCsvRc == null) return;
|
||||
if (dpRacing == null || btnDownloadRc == null || btnBrowseCsvRc == null) return;
|
||||
bool isApi = rbRcApi.IsChecked == true;
|
||||
cmbDay.Visibility = isApi ? Visibility.Visible : Visibility.Collapsed;
|
||||
dpRacing.Visibility = isApi ? Visibility.Visible : Visibility.Collapsed;
|
||||
if (cmbRaceCode != null) cmbRaceCode.Visibility = isApi ? Visibility.Visible : Visibility.Collapsed;
|
||||
btnDownloadRc.Visibility = isApi ? Visibility.Visible : Visibility.Collapsed;
|
||||
btnBrowseCsvRc.Visibility = isApi ? Visibility.Collapsed : Visibility.Visible;
|
||||
}
|
||||
@@ -528,41 +528,72 @@ namespace HorseRacingPredictor
|
||||
r[col] = n++;
|
||||
}
|
||||
|
||||
private string GetSelectedRaceCode()
|
||||
{
|
||||
int idx = cmbRaceCode?.SelectedIndex ?? 0;
|
||||
switch (idx)
|
||||
{
|
||||
case 1: return "harness";
|
||||
case 2: return "greyhounds";
|
||||
default: return "gallops";
|
||||
}
|
||||
}
|
||||
|
||||
private async Task DownloadRacecardsAsync()
|
||||
{
|
||||
// Se è già in corso, annulla
|
||||
if (_racingCts != null)
|
||||
{
|
||||
_racingCts.Cancel();
|
||||
_racingCts = null;
|
||||
btnDownloadRc.Content = "Scarica Corse";
|
||||
lblStatusRc.Text = "Annullato";
|
||||
return;
|
||||
}
|
||||
|
||||
_racingCts = new CancellationTokenSource();
|
||||
var ct = _racingCts.Token;
|
||||
|
||||
try
|
||||
{
|
||||
pbRacing.Value = 0;
|
||||
lblStatusRc.Text = "Scaricamento racecard…";
|
||||
btnDownloadRc.IsEnabled = false;
|
||||
cmbDay.IsEnabled = false;
|
||||
lblStatusRc.Text = "Scaricamento corse da FormFav…";
|
||||
btnDownloadRc.Content = "Annulla";
|
||||
dpRacing.IsEnabled = false;
|
||||
cmbRaceCode.IsEnabled = false;
|
||||
btnExportRcCsv.IsEnabled = false;
|
||||
|
||||
var progress = new Progress<int>(v => pbRacing.Value = v);
|
||||
var status = new Progress<string>(s => lblStatusRc.Text = s);
|
||||
|
||||
string day = cmbDay.SelectedIndex == 0 ? "today" : "tomorrow";
|
||||
var date = dpRacing.SelectedDate ?? DateTime.Today;
|
||||
string raceCode = GetSelectedRaceCode();
|
||||
|
||||
var table = await Task.Run(() =>
|
||||
_racingManager.GetRacecards(day, progress, status));
|
||||
_racingManager.GetAllRacesForDate(date, raceCode, progress, status, ct), ct);
|
||||
|
||||
_racingData = table;
|
||||
|
||||
// Add only row numbers for racing (do not add an "Inizio" column — meeting name already contains time)
|
||||
InjectRomeStartTimeColumn(_racingData, null);
|
||||
// Add row numbers
|
||||
InjectRowNumbers(_racingData);
|
||||
|
||||
dgRacing.ItemsSource = _racingData?.DefaultView;
|
||||
|
||||
if (_racingData != null && _racingData.Rows.Count > 0)
|
||||
{
|
||||
btnExportRcCsv.IsEnabled = true;
|
||||
lblStatusRc.Text = $"Trovate {_racingData.Rows.Count} corse";
|
||||
lblStatusRc.Text = $"Trovati {_racingData.Rows.Count} corridori";
|
||||
}
|
||||
else
|
||||
{
|
||||
lblStatusRc.Text = "Nessuna corsa trovata";
|
||||
lblStatusRc.Text = "Nessuna corsa trovata per la data selezionata";
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
lblStatusRc.Text = "Scaricamento annullato";
|
||||
pbRacing.Value = 0;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show($"Errore durante lo scaricamento:\n{ex.Message}",
|
||||
@@ -572,17 +603,19 @@ namespace HorseRacingPredictor
|
||||
}
|
||||
finally
|
||||
{
|
||||
btnDownloadRc.IsEnabled = true;
|
||||
cmbDay.IsEnabled = true;
|
||||
_racingCts = null;
|
||||
btnDownloadRc.Content = "Scarica Corse";
|
||||
dpRacing.IsEnabled = true;
|
||||
cmbRaceCode.IsEnabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void btnExportRcCsv_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
string dayLabel = cmbDay.SelectedIndex == 0 ? "oggi" : "domani";
|
||||
var rcDate = dpRacing.SelectedDate ?? DateTime.Today;
|
||||
var format = (cmbRcFormat?.SelectedItem as ComboBoxItem)?.Content?.ToString() ?? "CSV";
|
||||
var defaultName = $"Corse_{dayLabel}_{DateTime.Now:yyyy-MM-dd}.{format.ToLower()}";
|
||||
var filename = BuildFilename(txtRcPrefix?.Text, chkRcIncludeDate?.IsChecked == true ? GetSelectedDateString(cmbRcDateFormat, DateTime.Now) : null, txtRcSuffix?.Text, null, defaultName);
|
||||
var defaultName = $"Corse_{rcDate:yyyy-MM-dd}.{format.ToLower()}";
|
||||
var filename = BuildFilename(txtRcPrefix?.Text, chkRcIncludeDate?.IsChecked == true ? GetSelectedDateString(cmbRcDateFormat, rcDate) : null, txtRcSuffix?.Text, null, defaultName);
|
||||
filename = EnsureFileExtension(SanitizeFileName(filename), "." + format.ToLower());
|
||||
|
||||
switch (format.ToUpper())
|
||||
@@ -706,8 +739,7 @@ namespace HorseRacingPredictor
|
||||
{
|
||||
try
|
||||
{
|
||||
txtRacingUser.Text = DefaultRacingUser;
|
||||
txtRacingPass.Password = DefaultRacingPass;
|
||||
txtRacingApiKey.Text = DefaultRacingApiKey;
|
||||
|
||||
if (!File.Exists(SettingsFilePath)) return;
|
||||
foreach (var line in File.ReadAllLines(SettingsFilePath))
|
||||
@@ -730,15 +762,14 @@ namespace HorseRacingPredictor
|
||||
else if (key == "RcIncludeDate") chkRcIncludeDate.IsChecked = val == "1" || val.Equals("true", StringComparison.OrdinalIgnoreCase);
|
||||
else if (key == "RcDateFormat") { try { SetComboBoxSelectionByContent(cmbRcDateFormat, val); } catch { } }
|
||||
else if (key == "RcFormat") { try { SetComboBoxSelectionByContent(cmbRcFormat, val); } catch { } }
|
||||
else if (key == "RacingUser") txtRacingUser.Text = val;
|
||||
else if (key == "RacingPass") txtRacingPass.Password = val;
|
||||
else if (key == "RacingApiKey") txtRacingApiKey.Text = val;
|
||||
}
|
||||
|
||||
// Update preview UI after loading values
|
||||
UpdateFbPreview();
|
||||
UpdateRcPreview();
|
||||
|
||||
_racingManager.UpdateCredentials(txtRacingUser.Text, txtRacingPass.Password);
|
||||
_racingManager.UpdateApiKey(txtRacingApiKey.Text);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
@@ -823,8 +854,9 @@ namespace HorseRacingPredictor
|
||||
try
|
||||
{
|
||||
var format = (cmbRcFormat?.SelectedItem as ComboBoxItem)?.Content?.ToString() ?? "CSV";
|
||||
var datePart = chkRcIncludeDate?.IsChecked == true ? GetSelectedDateString(cmbRcDateFormat, DateTime.Now) : null;
|
||||
var defaultName = $"Corse_{(cmbDay.SelectedIndex==0?"oggi":"domani")}_{DateTime.Now:yyyy-MM-dd}.{format.ToLower()}";
|
||||
var rcDate = dpRacing?.SelectedDate ?? DateTime.Today;
|
||||
var datePart = chkRcIncludeDate?.IsChecked == true ? GetSelectedDateString(cmbRcDateFormat, rcDate) : null;
|
||||
var defaultName = $"Corse_{rcDate:yyyy-MM-dd}.{format.ToLower()}";
|
||||
var name = BuildFilename(txtRcPrefix?.Text, datePart, txtRcSuffix?.Text, null, defaultName);
|
||||
name = SanitizeFileName(name);
|
||||
name = EnsureFileExtension(name, "." + format.ToLower());
|
||||
@@ -982,15 +1014,14 @@ namespace HorseRacingPredictor
|
||||
sb.AppendLine($"RcIncludeDate={(chkRcIncludeDate.IsChecked==true?"1":"0")}");
|
||||
sb.AppendLine($"RcDateFormat={(cmbRcDateFormat?.SelectedItem as ComboBoxItem)?.Content?.ToString() ?? "yyyy-MM-dd"}");
|
||||
sb.AppendLine($"RcFormat={(cmbRcFormat?.SelectedItem as ComboBoxItem)?.Content?.ToString() ?? "CSV"}");
|
||||
sb.AppendLine($"RacingUser={txtRacingUser.Text.Trim()}");
|
||||
sb.AppendLine($"RacingPass={txtRacingPass.Password.Trim()}");
|
||||
sb.AppendLine($"RacingApiKey={txtRacingApiKey.Text.Trim()}");
|
||||
File.WriteAllText(SettingsFilePath, sb.ToString(), Encoding.UTF8);
|
||||
|
||||
// update previews after save
|
||||
UpdateFbPreview();
|
||||
UpdateRcPreview();
|
||||
|
||||
_racingManager.UpdateCredentials(txtRacingUser.Text.Trim(), txtRacingPass.Password.Trim());
|
||||
_racingManager.UpdateApiKey(txtRacingApiKey.Text.Trim());
|
||||
|
||||
MessageBox.Show("Impostazioni salvate con successo.",
|
||||
"Salvato", MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
|
||||
Reference in New Issue
Block a user