diff --git a/.gitignore b/.gitignore index 7e2e97c..c7b6a23 100644 --- a/.gitignore +++ b/.gitignore @@ -414,3 +414,9 @@ FodyWeavers.xsd # Built Visual Studio Code Extensions *.vsix + +# Secrets / credentials - never commit +appsettings.json +appsettings.*.json +!appsettings.template.json +settings.ini diff --git a/HorseRacingPredictor/HorseRacingPredictor/App.xaml.cs b/HorseRacingPredictor/HorseRacingPredictor/App.xaml.cs index 103c008..cc06030 100644 --- a/HorseRacingPredictor/HorseRacingPredictor/App.xaml.cs +++ b/HorseRacingPredictor/HorseRacingPredictor/App.xaml.cs @@ -1,8 +1,38 @@ +using System; using System.Windows; +using System.Windows.Threading; namespace HorseRacingPredictor { public partial class App : Application { + protected override void OnStartup(StartupEventArgs e) + { + DispatcherUnhandledException += App_DispatcherUnhandledException; + AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException; + base.OnStartup(e); + } + + private void App_DispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e) + { + MessageBox.Show( + $"Errore non gestito:\n\n{e.Exception.GetType().Name}: {e.Exception.Message}\n\n{e.Exception.StackTrace}", + "Errore applicazione", + MessageBoxButton.OK, + MessageBoxImage.Error); + e.Handled = true; + } + + private void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e) + { + if (e.ExceptionObject is Exception ex) + { + MessageBox.Show( + $"Errore fatale:\n\n{ex.GetType().Name}: {ex.Message}\n\n{ex.StackTrace}", + "Errore fatale", + MessageBoxButton.OK, + MessageBoxImage.Error); + } + } } } diff --git a/HorseRacingPredictor/HorseRacingPredictor/BettingPredictor.csproj b/HorseRacingPredictor/HorseRacingPredictor/BettingPredictor.csproj index 649c76e..86ee796 100644 --- a/HorseRacingPredictor/HorseRacingPredictor/BettingPredictor.csproj +++ b/HorseRacingPredictor/HorseRacingPredictor/BettingPredictor.csproj @@ -27,7 +27,6 @@ - diff --git a/HorseRacingPredictor/HorseRacingPredictor/Football/API/Coaches.cs b/HorseRacingPredictor/HorseRacingPredictor/Football/API/Coaches.cs new file mode 100644 index 0000000..d9b1462 --- /dev/null +++ b/HorseRacingPredictor/HorseRacingPredictor/Football/API/Coaches.cs @@ -0,0 +1,26 @@ +using System; +using RestSharp; + +namespace HorseRacingPredictor.Football.API +{ + /// + /// Client per l'endpoint "coachs" dell'API-Football. + /// Restituisce informazioni sugli allenatori. + /// + internal class Coaches : HorseRacingPredictor.Football.Manager.API + { + private const string Endpoint = "coachs"; + + public RestResponse GetByTeam(int teamId) + { + try + { + return ExecuteRequest($"{Endpoint}?team={teamId}", ApiTypes.Teams); + } + catch (Exception ex) + { + throw new Exception($"Errore recupero allenatore team {teamId}: {ex.Message}", ex); + } + } + } +} diff --git a/HorseRacingPredictor/HorseRacingPredictor/Football/API/PlayerStatistics.cs b/HorseRacingPredictor/HorseRacingPredictor/Football/API/PlayerStatistics.cs new file mode 100644 index 0000000..f84d834 --- /dev/null +++ b/HorseRacingPredictor/HorseRacingPredictor/Football/API/PlayerStatistics.cs @@ -0,0 +1,59 @@ +using System; +using RestSharp; + +namespace HorseRacingPredictor.Football.API +{ + /// + /// Client per l'endpoint "players" dell'API-Football. + /// Restituisce statistiche dettagliate dei giocatori per squadra/lega/stagione. + /// + internal class PlayerStatistics : HorseRacingPredictor.Football.Manager.API + { + private const string Endpoint = "players"; + + /// + /// Ottiene le statistiche dei giocatori per squadra e stagione (paginato, 20 per pagina). + /// + public RestResponse GetByTeamAndSeason(int teamId, int season, int page = 1) + { + try + { + return ExecuteRequest($"{Endpoint}?team={teamId}&season={season}&page={page}", ApiTypes.Teams); + } + catch (Exception ex) + { + throw new Exception($"Errore recupero statistiche giocatori team {teamId}, stagione {season}, pagina {page}: {ex.Message}", ex); + } + } + + /// + /// Ottiene le statistiche di un singolo giocatore per stagione. + /// + public RestResponse GetByPlayerAndSeason(int playerId, int season) + { + try + { + return ExecuteRequest($"{Endpoint}?id={playerId}&season={season}", ApiTypes.Teams); + } + catch (Exception ex) + { + throw new Exception($"Errore recupero statistiche giocatore {playerId}, stagione {season}: {ex.Message}", ex); + } + } + + /// + /// Ottiene le statistiche dei giocatori per lega e stagione (paginato, 20 per pagina). + /// + public RestResponse GetByLeagueAndSeason(int leagueId, int season, int page = 1) + { + try + { + return ExecuteRequest($"{Endpoint}?league={leagueId}&season={season}&page={page}", ApiTypes.Leagues); + } + catch (Exception ex) + { + throw new Exception($"Errore recupero statistiche giocatori lega {leagueId}, stagione {season}, pagina {page}: {ex.Message}", ex); + } + } + } +} diff --git a/HorseRacingPredictor/HorseRacingPredictor/Football/API/Sidelined.cs b/HorseRacingPredictor/HorseRacingPredictor/Football/API/Sidelined.cs new file mode 100644 index 0000000..ea0a7ab --- /dev/null +++ b/HorseRacingPredictor/HorseRacingPredictor/Football/API/Sidelined.cs @@ -0,0 +1,38 @@ +using System; +using RestSharp; + +namespace HorseRacingPredictor.Football.API +{ + /// + /// Client per l'endpoint "sidelined" dell'API-Football. + /// Restituisce lo storico di indisponibilità/squalifiche di un giocatore o allenatore. + /// + internal class Sidelined : HorseRacingPredictor.Football.Manager.API + { + private const string Endpoint = "sidelined"; + + public RestResponse GetByPlayer(int playerId) + { + try + { + return ExecuteRequest($"{Endpoint}?player={playerId}", ApiTypes.Teams); + } + catch (Exception ex) + { + throw new Exception($"Errore recupero sidelined giocatore {playerId}: {ex.Message}", ex); + } + } + + public RestResponse GetByCoach(int coachId) + { + try + { + return ExecuteRequest($"{Endpoint}?coach={coachId}", ApiTypes.Teams); + } + catch (Exception ex) + { + throw new Exception($"Errore recupero sidelined allenatore {coachId}: {ex.Message}", ex); + } + } + } +} diff --git a/HorseRacingPredictor/HorseRacingPredictor/Football/API/Squads.cs b/HorseRacingPredictor/HorseRacingPredictor/Football/API/Squads.cs new file mode 100644 index 0000000..c43a637 --- /dev/null +++ b/HorseRacingPredictor/HorseRacingPredictor/Football/API/Squads.cs @@ -0,0 +1,26 @@ +using System; +using RestSharp; + +namespace HorseRacingPredictor.Football.API +{ + /// + /// Client per l'endpoint "players/squads" dell'API-Football. + /// Restituisce la rosa corrente di una squadra. + /// + internal class Squads : HorseRacingPredictor.Football.Manager.API + { + private const string Endpoint = "players/squads"; + + public RestResponse GetSquad(int teamId) + { + try + { + return ExecuteRequest($"{Endpoint}?team={teamId}", ApiTypes.Teams); + } + catch (Exception ex) + { + throw new Exception($"Errore recupero rosa team {teamId}: {ex.Message}", ex); + } + } + } +} diff --git a/HorseRacingPredictor/HorseRacingPredictor/Football/API/TeamStatistics.cs b/HorseRacingPredictor/HorseRacingPredictor/Football/API/TeamStatistics.cs new file mode 100644 index 0000000..276fe07 --- /dev/null +++ b/HorseRacingPredictor/HorseRacingPredictor/Football/API/TeamStatistics.cs @@ -0,0 +1,33 @@ +using System; +using RestSharp; + +namespace HorseRacingPredictor.Football.API +{ + /// + /// Client per l'endpoint "teams/statistics" dell'API-Football. + /// Restituisce statistiche aggregate di una squadra per campionato e stagione + /// (forma, gol, clean sheet, penalty, cartellini, formazioni usate, ecc.). + /// + internal class TeamStatistics : HorseRacingPredictor.Football.Manager.API + { + private const string Endpoint = "teams/statistics"; + + /// + /// Ottiene le statistiche di una squadra per lega, stagione e opzionalmente fino a una data. + /// + public RestResponse GetTeamStatistics(int teamId, int leagueId, int season, string dateTo = null) + { + try + { + string query = $"{Endpoint}?team={teamId}&league={leagueId}&season={season}"; + if (!string.IsNullOrEmpty(dateTo)) + query += $"&date={dateTo}"; + return ExecuteRequest(query, ApiTypes.Teams); + } + catch (Exception ex) + { + throw new Exception($"Errore recupero statistiche team {teamId}, lega {leagueId}, stagione {season}: {ex.Message}", ex); + } + } + } +} diff --git a/HorseRacingPredictor/HorseRacingPredictor/Football/API/TopAssists.cs b/HorseRacingPredictor/HorseRacingPredictor/Football/API/TopAssists.cs new file mode 100644 index 0000000..8d22a29 --- /dev/null +++ b/HorseRacingPredictor/HorseRacingPredictor/Football/API/TopAssists.cs @@ -0,0 +1,26 @@ +using System; +using RestSharp; + +namespace HorseRacingPredictor.Football.API +{ + /// + /// Client per l'endpoint "players/topassists" dell'API-Football. + /// Restituisce i 20 migliori assistman di una lega/stagione. + /// + internal class TopAssists : HorseRacingPredictor.Football.Manager.API + { + private const string Endpoint = "players/topassists"; + + public RestResponse GetTopAssists(int leagueId, int season) + { + try + { + return ExecuteRequest($"{Endpoint}?league={leagueId}&season={season}", ApiTypes.Leagues); + } + catch (Exception ex) + { + throw new Exception($"Errore recupero top assistman lega {leagueId}, stagione {season}: {ex.Message}", ex); + } + } + } +} diff --git a/HorseRacingPredictor/HorseRacingPredictor/Football/API/TopCards.cs b/HorseRacingPredictor/HorseRacingPredictor/Football/API/TopCards.cs new file mode 100644 index 0000000..cdd8418 --- /dev/null +++ b/HorseRacingPredictor/HorseRacingPredictor/Football/API/TopCards.cs @@ -0,0 +1,39 @@ +using System; +using RestSharp; + +namespace HorseRacingPredictor.Football.API +{ + /// + /// Client per gli endpoint "players/topyellowcards" e "players/topredcards" dell'API-Football. + /// Restituisce i 20 giocatori con più cartellini per lega/stagione. + /// + internal class TopCards : HorseRacingPredictor.Football.Manager.API + { + private const string YellowEndpoint = "players/topyellowcards"; + private const string RedEndpoint = "players/topredcards"; + + public RestResponse GetTopYellowCards(int leagueId, int season) + { + try + { + return ExecuteRequest($"{YellowEndpoint}?league={leagueId}&season={season}", ApiTypes.Leagues); + } + catch (Exception ex) + { + throw new Exception($"Errore recupero top cartellini gialli lega {leagueId}, stagione {season}: {ex.Message}", ex); + } + } + + public RestResponse GetTopRedCards(int leagueId, int season) + { + try + { + return ExecuteRequest($"{RedEndpoint}?league={leagueId}&season={season}", ApiTypes.Leagues); + } + catch (Exception ex) + { + throw new Exception($"Errore recupero top cartellini rossi lega {leagueId}, stagione {season}: {ex.Message}", ex); + } + } + } +} diff --git a/HorseRacingPredictor/HorseRacingPredictor/Football/API/TopScorers.cs b/HorseRacingPredictor/HorseRacingPredictor/Football/API/TopScorers.cs new file mode 100644 index 0000000..e80fbb6 --- /dev/null +++ b/HorseRacingPredictor/HorseRacingPredictor/Football/API/TopScorers.cs @@ -0,0 +1,26 @@ +using System; +using RestSharp; + +namespace HorseRacingPredictor.Football.API +{ + /// + /// Client per l'endpoint "players/topscorers" dell'API-Football. + /// Restituisce i 20 migliori marcatori di una lega/stagione. + /// + internal class TopScorers : HorseRacingPredictor.Football.Manager.API + { + private const string Endpoint = "players/topscorers"; + + public RestResponse GetTopScorers(int leagueId, int season) + { + try + { + return ExecuteRequest($"{Endpoint}?league={leagueId}&season={season}", ApiTypes.Leagues); + } + catch (Exception ex) + { + throw new Exception($"Errore recupero capocannonieri lega {leagueId}, stagione {season}: {ex.Message}", ex); + } + } + } +} diff --git a/HorseRacingPredictor/HorseRacingPredictor/Football/API/Transfers.cs b/HorseRacingPredictor/HorseRacingPredictor/Football/API/Transfers.cs new file mode 100644 index 0000000..d855f09 --- /dev/null +++ b/HorseRacingPredictor/HorseRacingPredictor/Football/API/Transfers.cs @@ -0,0 +1,38 @@ +using System; +using RestSharp; + +namespace HorseRacingPredictor.Football.API +{ + /// + /// Client per l'endpoint "transfers" dell'API-Football. + /// Restituisce la cronologia trasferimenti di un giocatore o di una squadra. + /// + internal class Transfers : HorseRacingPredictor.Football.Manager.API + { + private const string Endpoint = "transfers"; + + public RestResponse GetByTeam(int teamId) + { + try + { + return ExecuteRequest($"{Endpoint}?team={teamId}", ApiTypes.Teams); + } + catch (Exception ex) + { + throw new Exception($"Errore recupero trasferimenti team {teamId}: {ex.Message}", ex); + } + } + + public RestResponse GetByPlayer(int playerId) + { + try + { + return ExecuteRequest($"{Endpoint}?player={playerId}", ApiTypes.Teams); + } + catch (Exception ex) + { + throw new Exception($"Errore recupero trasferimenti giocatore {playerId}: {ex.Message}", ex); + } + } + } +} diff --git a/HorseRacingPredictor/HorseRacingPredictor/Football/FootballDownloadOptions.cs b/HorseRacingPredictor/HorseRacingPredictor/Football/FootballDownloadOptions.cs index 2c0b6d4..f2f360d 100644 --- a/HorseRacingPredictor/HorseRacingPredictor/Football/FootballDownloadOptions.cs +++ b/HorseRacingPredictor/HorseRacingPredictor/Football/FootballDownloadOptions.cs @@ -19,6 +19,39 @@ namespace HorseRacingPredictor.Football public bool DownloadStatistics { get; set; } = false; public bool DownloadInjuries { get; set; } = false; + // ?? Endpoint supplementari (CSV separati) ????????????????? + /// Scarica statistiche giocatori per squadra/stagione. + public bool DownloadPlayerStats { get; set; } = false; + + /// Scarica statistiche aggregate delle squadre per lega/stagione. + public bool DownloadTeamStats { get; set; } = false; + + /// Scarica classifica marcatori della lega. + public bool DownloadTopScorers { get; set; } = false; + + /// Scarica classifica assistman della lega. + public bool DownloadTopAssists { get; set; } = false; + + /// Scarica classifica cartellini (gialli e rossi) della lega. + public bool DownloadTopCards { get; set; } = false; + + /// Scarica le rose attuali delle squadre. + public bool DownloadSquads { get; set; } = false; + + /// Scarica informazioni sugli allenatori delle squadre. + public bool DownloadCoaches { get; set; } = false; + + /// Scarica lo storico trasferimenti delle squadre. + public bool DownloadTransfers { get; set; } = false; + + /// + /// Indica se almeno un endpoint supplementare (CSV separato) è selezionato. + /// + public bool AnySupplementarySelected => + DownloadPlayerStats || DownloadTeamStats || DownloadTopScorers || + DownloadTopAssists || DownloadTopCards || DownloadSquads || + DownloadCoaches || DownloadTransfers; + // ?? Filtri ???????????????????????????????????????????????? /// /// Se non vuoto, scarica fixture solo per queste leghe (league IDs). @@ -96,6 +129,19 @@ namespace HorseRacingPredictor.Football if (DownloadStatistics) calls += fixtureCount; if (DownloadInjuries) calls += 1; if (CheckQuota) calls += 1; // status endpoint + + // Supplementari (una chiamata per squadra coinvolta ? fixtureCount*2 unici, stima fixtureCount) + int teamEstimate = System.Math.Max(fixtureCount, 1); + int leagueEstimate = LeagueIds.Count > 0 ? LeagueIds.Count : 5; + if (DownloadPlayerStats) calls += teamEstimate; // 1 pagina per squadra (minimo) + if (DownloadTeamStats) calls += teamEstimate; + if (DownloadTopScorers) calls += leagueEstimate; + if (DownloadTopAssists) calls += leagueEstimate; + if (DownloadTopCards) calls += leagueEstimate * 2; // gialli + rossi + if (DownloadSquads) calls += teamEstimate; + if (DownloadCoaches) calls += teamEstimate; + if (DownloadTransfers) calls += teamEstimate; + return calls; } } diff --git a/HorseRacingPredictor/HorseRacingPredictor/Football/Main.cs b/HorseRacingPredictor/HorseRacingPredictor/Football/Main.cs index b9a8d23..aa4eab6 100644 --- a/HorseRacingPredictor/HorseRacingPredictor/Football/Main.cs +++ b/HorseRacingPredictor/HorseRacingPredictor/Football/Main.cs @@ -1878,5 +1878,40 @@ namespace HorseRacingPredictor.Football } #endregion + + #region Supplementary Data + + /// + /// Scarica i dati supplementari selezionati (statistiche giocatori, capocannonieri, ecc.) + /// e li esporta come CSV separati nella cartella specificata. + /// Richiede un DataTable di fixture già scaricato per estrarre teamId e leagueId coinvolti. + /// + public List DownloadSupplementaryData( + DataTable fixturesTable, + DateTime date, + string exportFolder, + FootballDownloadOptions options, + IProgress progressCallback = null, + IProgress statusCallback = null) + { + if (fixturesTable == null || fixturesTable.Rows.Count == 0 || !options.AnySupplementarySelected) + return new List(); + + // Estrai team IDs e league IDs unici dal DataTable fixture + var teamIds = new HashSet(); + var leagueIds = new HashSet(); + + foreach (DataRow row in fixturesTable.Rows) + { + if (row["HomeTeamId"] != DBNull.Value) teamIds.Add(Convert.ToInt32(row["HomeTeamId"])); + if (row["AwayTeamId"] != DBNull.Value) teamIds.Add(Convert.ToInt32(row["AwayTeamId"])); + if (row["LeagueId"] != DBNull.Value) leagueIds.Add(Convert.ToInt32(row["LeagueId"])); + } + + var exporter = new SupplementaryDataExporter(); + return exporter.DownloadAndExport(date, exportFolder, options, teamIds, leagueIds, progressCallback, statusCallback); + } + + #endregion } } diff --git a/HorseRacingPredictor/HorseRacingPredictor/Football/SupplementaryDataExporter.cs b/HorseRacingPredictor/HorseRacingPredictor/Football/SupplementaryDataExporter.cs new file mode 100644 index 0000000..58ad2c2 --- /dev/null +++ b/HorseRacingPredictor/HorseRacingPredictor/Football/SupplementaryDataExporter.cs @@ -0,0 +1,733 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.IO; +using System.Linq; +using System.Text; +using System.Text.Json; + +namespace HorseRacingPredictor.Football +{ + /// + /// Scarica dati supplementari dall'API-Football e li esporta come CSV separati. + /// Ogni tipo di dato produce un file CSV indipendente che l'IA potrà poi unire + /// in fase di elaborazione usando le chiavi comuni (team_id, league_id, player_id). + /// + public class SupplementaryDataExporter + { + private readonly API.PlayerStatistics _playerStatsAPI = new(); + private readonly API.TeamStatistics _teamStatsAPI = new(); + private readonly API.TopScorers _topScorersAPI = new(); + private readonly API.TopAssists _topAssistsAPI = new(); + private readonly API.TopCards _topCardsAPI = new(); + private readonly API.Squads _squadsAPI = new(); + private readonly API.Coaches _coachesAPI = new(); + private readonly API.Transfers _transfersAPI = new(); + private readonly API.Status _statusAPI = new(); + private readonly Manager.Database _database = new(); + + /// + /// Scarica tutti i dati supplementari selezionati e li salva come CSV nella cartella specificata. + /// Restituisce la lista di file CSV generati. + /// + public List DownloadAndExport( + DateTime date, + string exportFolder, + FootballDownloadOptions options, + HashSet teamIds, + HashSet leagueIds, + IProgress progressCallback = null, + IProgress statusCallback = null) + { + var generatedFiles = new List(); + int season = options.GetEffectiveSeason(); + string dateTag = date.ToString("yyyy-MM-dd"); + + // Calcola step totali per progress + int totalSteps = CountSteps(options, teamIds.Count, leagueIds.Count); + int currentStep = 0; + + void Report(string msg) + { + currentStep++; + int pct = totalSteps > 0 ? (int)((double)currentStep / totalSteps * 100) : 0; + progressCallback?.Report(Math.Min(pct, 100)); + statusCallback?.Report(msg); + } + + // ?? Top Scorers ?? + if (options.DownloadTopScorers) + { + Report("Scaricamento capocannonieri..."); + var table = DownloadTopScorersTable(leagueIds, season, options.ApiDelayMs); + if (table.Rows.Count > 0) + { + string path = SaveCsv(table, exportFolder, $"TopScorers_{dateTag}.csv"); + generatedFiles.Add(path); + } + } + + // ?? Top Assists ?? + if (options.DownloadTopAssists) + { + Report("Scaricamento top assistman..."); + var table = DownloadTopAssistsTable(leagueIds, season, options.ApiDelayMs); + if (table.Rows.Count > 0) + { + string path = SaveCsv(table, exportFolder, $"TopAssists_{dateTag}.csv"); + generatedFiles.Add(path); + } + } + + // ?? Top Cards ?? + if (options.DownloadTopCards) + { + Report("Scaricamento top cartellini..."); + var table = DownloadTopCardsTable(leagueIds, season, options.ApiDelayMs); + if (table.Rows.Count > 0) + { + string path = SaveCsv(table, exportFolder, $"TopCards_{dateTag}.csv"); + generatedFiles.Add(path); + } + } + + // ?? Team Stats ?? + if (options.DownloadTeamStats) + { + var table = new DataTable(); + table.Columns.Add("TeamId", typeof(int)); + table.Columns.Add("LeagueId", typeof(int)); + table.Columns.Add("Season", typeof(int)); + table.Columns.Add("Forma", typeof(string)); + table.Columns.Add("Partite Totali", typeof(int)); + table.Columns.Add("Vittorie Casa", typeof(int)); + table.Columns.Add("Vittorie Trasf.", typeof(int)); + table.Columns.Add("Pareggi Casa", typeof(int)); + table.Columns.Add("Pareggi Trasf.", typeof(int)); + table.Columns.Add("Sconfitte Casa", typeof(int)); + table.Columns.Add("Sconfitte Trasf.", typeof(int)); + table.Columns.Add("Gol Fatti Casa", typeof(int)); + table.Columns.Add("Gol Fatti Trasf.", typeof(int)); + table.Columns.Add("Gol Subiti Casa", typeof(int)); + table.Columns.Add("Gol Subiti Trasf.", typeof(int)); + table.Columns.Add("Clean Sheet Tot.", typeof(int)); + table.Columns.Add("Penalty Segnati", typeof(int)); + table.Columns.Add("Penalty Falliti", typeof(int)); + + foreach (int teamId in teamIds) + { + foreach (int leagueId in leagueIds) + { + Report($"Statistiche team {teamId} in lega {leagueId}..."); + try + { + var resp = _teamStatsAPI.GetTeamStatistics(teamId, leagueId, season, dateTag); + if (resp?.IsSuccessful == true && !string.IsNullOrEmpty(resp.Content)) + ParseTeamStatsRow(table, resp.Content, teamId, leagueId, season); + } + catch { /* continua */ } + if (options.ApiDelayMs > 0) System.Threading.Thread.Sleep(options.ApiDelayMs); + } + } + if (table.Rows.Count > 0) + { + string path = SaveCsv(table, exportFolder, $"TeamStats_{dateTag}.csv"); + generatedFiles.Add(path); + } + } + + // ?? Player Stats ?? + if (options.DownloadPlayerStats) + { + Report("Scaricamento statistiche giocatori..."); + var table = DownloadPlayerStatsTable(teamIds, season, options.ApiDelayMs, statusCallback); + if (table.Rows.Count > 0) + { + string path = SaveCsv(table, exportFolder, $"PlayerStats_{dateTag}.csv"); + generatedFiles.Add(path); + } + } + + // ?? Squads ?? + if (options.DownloadSquads) + { + Report("Scaricamento rose squadre..."); + var table = DownloadSquadsTable(teamIds, options.ApiDelayMs, statusCallback); + if (table.Rows.Count > 0) + { + string path = SaveCsv(table, exportFolder, $"Squads_{dateTag}.csv"); + generatedFiles.Add(path); + } + } + + // ?? Coaches ?? + if (options.DownloadCoaches) + { + Report("Scaricamento allenatori..."); + var table = DownloadCoachesTable(teamIds, options.ApiDelayMs, statusCallback); + if (table.Rows.Count > 0) + { + string path = SaveCsv(table, exportFolder, $"Coaches_{dateTag}.csv"); + generatedFiles.Add(path); + } + } + + // ?? Transfers ?? + if (options.DownloadTransfers) + { + Report("Scaricamento trasferimenti..."); + var table = DownloadTransfersTable(teamIds, options.ApiDelayMs, statusCallback); + if (table.Rows.Count > 0) + { + string path = SaveCsv(table, exportFolder, $"Transfers_{dateTag}.csv"); + generatedFiles.Add(path); + } + } + + progressCallback?.Report(100); + statusCallback?.Report($"Generati {generatedFiles.Count} CSV supplementari"); + return generatedFiles; + } + + #region Download & Parse Methods + + private DataTable DownloadTopScorersTable(HashSet leagueIds, int season, int delayMs) + { + var table = CreateTopPlayersTable("Gol"); + foreach (int leagueId in leagueIds) + { + try + { + var resp = _topScorersAPI.GetTopScorers(leagueId, season); + if (resp?.IsSuccessful == true && !string.IsNullOrEmpty(resp.Content)) + ParseTopPlayersResponse(table, resp.Content, leagueId, season, "goals", "total"); + } + catch { } + if (delayMs > 0) System.Threading.Thread.Sleep(delayMs); + } + return table; + } + + private DataTable DownloadTopAssistsTable(HashSet leagueIds, int season, int delayMs) + { + var table = CreateTopPlayersTable("Assist"); + foreach (int leagueId in leagueIds) + { + try + { + var resp = _topAssistsAPI.GetTopAssists(leagueId, season); + if (resp?.IsSuccessful == true && !string.IsNullOrEmpty(resp.Content)) + ParseTopPlayersResponse(table, resp.Content, leagueId, season, "goals", "assists"); + } + catch { } + if (delayMs > 0) System.Threading.Thread.Sleep(delayMs); + } + return table; + } + + private DataTable DownloadTopCardsTable(HashSet leagueIds, int season, int delayMs) + { + var table = new DataTable(); + table.Columns.Add("LeagueId", typeof(int)); + table.Columns.Add("Season", typeof(int)); + table.Columns.Add("Posizione", typeof(int)); + table.Columns.Add("PlayerId", typeof(int)); + table.Columns.Add("Giocatore", typeof(string)); + table.Columns.Add("TeamId", typeof(int)); + table.Columns.Add("Squadra", typeof(string)); + table.Columns.Add("Tipo", typeof(string)); + table.Columns.Add("Totale", typeof(int)); + + foreach (int leagueId in leagueIds) + { + try + { + var respY = _topCardsAPI.GetTopYellowCards(leagueId, season); + if (respY?.IsSuccessful == true && !string.IsNullOrEmpty(respY.Content)) + ParseTopCardsResponse(table, respY.Content, leagueId, season, "Yellow"); + } + catch { } + if (delayMs > 0) System.Threading.Thread.Sleep(delayMs); + + try + { + var respR = _topCardsAPI.GetTopRedCards(leagueId, season); + if (respR?.IsSuccessful == true && !string.IsNullOrEmpty(respR.Content)) + ParseTopCardsResponse(table, respR.Content, leagueId, season, "Red"); + } + catch { } + if (delayMs > 0) System.Threading.Thread.Sleep(delayMs); + } + return table; + } + + private DataTable DownloadPlayerStatsTable(HashSet teamIds, int season, int delayMs, IProgress statusCallback) + { + var table = new DataTable(); + table.Columns.Add("PlayerId", typeof(int)); + table.Columns.Add("Giocatore", typeof(string)); + table.Columns.Add("Età", typeof(int)); + table.Columns.Add("Nazionalità", typeof(string)); + table.Columns.Add("TeamId", typeof(int)); + table.Columns.Add("Squadra", typeof(string)); + table.Columns.Add("LeagueId", typeof(int)); + table.Columns.Add("Campionato", typeof(string)); + table.Columns.Add("Presenze", typeof(int)); + table.Columns.Add("Minuti", typeof(int)); + table.Columns.Add("Rating", typeof(string)); + table.Columns.Add("Gol", typeof(int)); + table.Columns.Add("Assist", typeof(int)); + table.Columns.Add("Gialli", typeof(int)); + table.Columns.Add("Rossi", typeof(int)); + table.Columns.Add("Tiri Totali", typeof(int)); + table.Columns.Add("Tiri in Porta", typeof(int)); + table.Columns.Add("Passaggi Chiave", typeof(int)); + table.Columns.Add("Dribbling Riusciti", typeof(int)); + table.Columns.Add("Falli Commessi", typeof(int)); + table.Columns.Add("Falli Subiti", typeof(int)); + table.Columns.Add("Ruolo", typeof(string)); + + int count = 0; + foreach (int teamId in teamIds) + { + count++; + statusCallback?.Report($"Statistiche giocatori team {count}/{teamIds.Count}..."); + try + { + var resp = _playerStatsAPI.GetByTeamAndSeason(teamId, season); + if (resp?.IsSuccessful == true && !string.IsNullOrEmpty(resp.Content)) + ParsePlayerStatsResponse(table, resp.Content); + } + catch { } + if (delayMs > 0) System.Threading.Thread.Sleep(delayMs); + } + return table; + } + + private DataTable DownloadSquadsTable(HashSet teamIds, int delayMs, IProgress statusCallback) + { + var table = new DataTable(); + table.Columns.Add("TeamId", typeof(int)); + table.Columns.Add("Squadra", typeof(string)); + table.Columns.Add("PlayerId", typeof(int)); + table.Columns.Add("Giocatore", typeof(string)); + table.Columns.Add("Età", typeof(int)); + table.Columns.Add("Numero", typeof(int)); + table.Columns.Add("Ruolo", typeof(string)); + + int count = 0; + foreach (int teamId in teamIds) + { + count++; + statusCallback?.Report($"Rosa team {count}/{teamIds.Count}..."); + try + { + var resp = _squadsAPI.GetSquad(teamId); + if (resp?.IsSuccessful == true && !string.IsNullOrEmpty(resp.Content)) + ParseSquadsResponse(table, resp.Content); + } + catch { } + if (delayMs > 0) System.Threading.Thread.Sleep(delayMs); + } + return table; + } + + private DataTable DownloadCoachesTable(HashSet teamIds, int delayMs, IProgress statusCallback) + { + var table = new DataTable(); + table.Columns.Add("CoachId", typeof(int)); + table.Columns.Add("Nome", typeof(string)); + table.Columns.Add("Età", typeof(int)); + table.Columns.Add("Nazionalità", typeof(string)); + table.Columns.Add("TeamId", typeof(int)); + table.Columns.Add("Squadra", typeof(string)); + + int count = 0; + foreach (int teamId in teamIds) + { + count++; + statusCallback?.Report($"Allenatore team {count}/{teamIds.Count}..."); + try + { + var resp = _coachesAPI.GetByTeam(teamId); + if (resp?.IsSuccessful == true && !string.IsNullOrEmpty(resp.Content)) + ParseCoachesResponse(table, resp.Content, teamId); + } + catch { } + if (delayMs > 0) System.Threading.Thread.Sleep(delayMs); + } + return table; + } + + private DataTable DownloadTransfersTable(HashSet teamIds, int delayMs, IProgress statusCallback) + { + var table = new DataTable(); + table.Columns.Add("PlayerId", typeof(int)); + table.Columns.Add("Giocatore", typeof(string)); + table.Columns.Add("Data", typeof(string)); + table.Columns.Add("Tipo", typeof(string)); + table.Columns.Add("Da TeamId", typeof(int)); + table.Columns.Add("Da Squadra", typeof(string)); + table.Columns.Add("A TeamId", typeof(int)); + table.Columns.Add("A Squadra", typeof(string)); + + int count = 0; + foreach (int teamId in teamIds) + { + count++; + statusCallback?.Report($"Trasferimenti team {count}/{teamIds.Count}..."); + try + { + var resp = _transfersAPI.GetByTeam(teamId); + if (resp?.IsSuccessful == true && !string.IsNullOrEmpty(resp.Content)) + ParseTransfersResponse(table, resp.Content); + } + catch { } + if (delayMs > 0) System.Threading.Thread.Sleep(delayMs); + } + return table; + } + + #endregion + + #region JSON Parsing Helpers + + private static DataTable CreateTopPlayersTable(string statName) + { + var table = new DataTable(); + table.Columns.Add("LeagueId", typeof(int)); + table.Columns.Add("Season", typeof(int)); + table.Columns.Add("Posizione", typeof(int)); + table.Columns.Add("PlayerId", typeof(int)); + table.Columns.Add("Giocatore", typeof(string)); + table.Columns.Add("TeamId", typeof(int)); + table.Columns.Add("Squadra", typeof(string)); + table.Columns.Add(statName, typeof(int)); + table.Columns.Add("Presenze", typeof(int)); + table.Columns.Add("Rating", typeof(string)); + return table; + } + + private static void ParseTopPlayersResponse(DataTable table, string json, int leagueId, int season, string statSection, string statField) + { + var doc = JsonDocument.Parse(json).RootElement; + if (!doc.TryGetProperty("response", out var resp)) return; + int pos = 0; + foreach (var item in resp.EnumerateArray()) + { + pos++; + var row = table.NewRow(); + row["LeagueId"] = leagueId; + row["Season"] = season; + row["Posizione"] = pos; + row["PlayerId"] = GetInt(item, "player", "id"); + row["Giocatore"] = GetStr(item, "player", "name"); + + // statistics[0] + if (item.TryGetProperty("statistics", out var stats)) + { + foreach (var s in stats.EnumerateArray()) + { + row["TeamId"] = GetInt(s, "team", "id"); + row["Squadra"] = GetStr(s, "team", "name"); + row[table.Columns[7].ColumnName] = GetIntNested(s, statSection, statField); + row["Presenze"] = GetIntNested(s, "games", "appearences"); + row["Rating"] = GetStrNested(s, "games", "rating"); + break; + } + } + table.Rows.Add(row); + } + } + + private static void ParseTopCardsResponse(DataTable table, string json, int leagueId, int season, string cardType) + { + var doc = JsonDocument.Parse(json).RootElement; + if (!doc.TryGetProperty("response", out var resp)) return; + int pos = 0; + foreach (var item in resp.EnumerateArray()) + { + pos++; + var row = table.NewRow(); + row["LeagueId"] = leagueId; + row["Season"] = season; + row["Posizione"] = pos; + row["PlayerId"] = GetInt(item, "player", "id"); + row["Giocatore"] = GetStr(item, "player", "name"); + row["Tipo"] = cardType; + + if (item.TryGetProperty("statistics", out var stats)) + { + foreach (var s in stats.EnumerateArray()) + { + row["TeamId"] = GetInt(s, "team", "id"); + row["Squadra"] = GetStr(s, "team", "name"); + string section = cardType == "Yellow" ? "yellow" : "red"; + row["Totale"] = GetIntNested(s, "cards", section); + break; + } + } + table.Rows.Add(row); + } + } + + private static void ParseTeamStatsRow(DataTable table, string json, int teamId, int leagueId, int season) + { + var doc = JsonDocument.Parse(json).RootElement; + if (!doc.TryGetProperty("response", out var resp)) return; + + var row = table.NewRow(); + row["TeamId"] = teamId; + row["LeagueId"] = leagueId; + row["Season"] = season; + row["Forma"] = resp.TryGetProperty("form", out var f) ? f.GetString() ?? "" : ""; + row["Partite Totali"] = GetIntNested(resp, "fixtures", "played", "total"); + row["Vittorie Casa"] = GetIntNested(resp, "fixtures", "wins", "home"); + row["Vittorie Trasf."] = GetIntNested(resp, "fixtures", "wins", "away"); + row["Pareggi Casa"] = GetIntNested(resp, "fixtures", "draws", "home"); + row["Pareggi Trasf."] = GetIntNested(resp, "fixtures", "draws", "away"); + row["Sconfitte Casa"] = GetIntNested(resp, "fixtures", "loses", "home"); + row["Sconfitte Trasf."] = GetIntNested(resp, "fixtures", "loses", "away"); + row["Gol Fatti Casa"] = GetIntNested(resp, "goals", "for", "total", "home"); + row["Gol Fatti Trasf."] = GetIntNested(resp, "goals", "for", "total", "away"); + row["Gol Subiti Casa"] = GetIntNested(resp, "goals", "against", "total", "home"); + row["Gol Subiti Trasf."] = GetIntNested(resp, "goals", "against", "total", "away"); + row["Clean Sheet Tot."] = GetIntNested(resp, "clean_sheet", "total"); + row["Penalty Segnati"] = GetIntNested(resp, "penalty", "scored", "total"); + row["Penalty Falliti"] = GetIntNested(resp, "penalty", "missed", "total"); + table.Rows.Add(row); + } + + private static void ParsePlayerStatsResponse(DataTable table, string json) + { + var doc = JsonDocument.Parse(json).RootElement; + if (!doc.TryGetProperty("response", out var resp)) return; + + foreach (var item in resp.EnumerateArray()) + { + int playerId = GetInt(item, "player", "id"); + string name = GetStr(item, "player", "name"); + int age = GetInt(item, "player", "age"); + string nationality = GetStr(item, "player", "nationality"); + + if (!item.TryGetProperty("statistics", out var stats)) continue; + foreach (var s in stats.EnumerateArray()) + { + var row = table.NewRow(); + row["PlayerId"] = playerId; + row["Giocatore"] = name; + row["Età"] = age; + row["Nazionalità"] = nationality; + row["TeamId"] = GetInt(s, "team", "id"); + row["Squadra"] = GetStr(s, "team", "name"); + row["LeagueId"] = GetInt(s, "league", "id"); + row["Campionato"] = GetStr(s, "league", "name"); + row["Presenze"] = GetIntNested(s, "games", "appearences"); + row["Minuti"] = GetIntNested(s, "games", "minutes"); + row["Rating"] = GetStrNested(s, "games", "rating"); + row["Gol"] = GetIntNested(s, "goals", "total"); + row["Assist"] = GetIntNested(s, "goals", "assists"); + row["Gialli"] = GetIntNested(s, "cards", "yellow"); + row["Rossi"] = GetIntNested(s, "cards", "red"); + row["Tiri Totali"] = GetIntNested(s, "shots", "total"); + row["Tiri in Porta"] = GetIntNested(s, "shots", "on"); + row["Passaggi Chiave"] = GetIntNested(s, "passes", "key"); + row["Dribbling Riusciti"] = GetIntNested(s, "dribbles", "success"); + row["Falli Commessi"] = GetIntNested(s, "fouls", "committed"); + row["Falli Subiti"] = GetIntNested(s, "fouls", "drawn"); + row["Ruolo"] = GetStrNested(s, "games", "position"); + table.Rows.Add(row); + } + } + } + + private static void ParseSquadsResponse(DataTable table, string json) + { + var doc = JsonDocument.Parse(json).RootElement; + if (!doc.TryGetProperty("response", out var resp)) return; + + foreach (var teamEntry in resp.EnumerateArray()) + { + int teamId = GetInt(teamEntry, "team", "id"); + string teamName = GetStr(teamEntry, "team", "name"); + + if (!teamEntry.TryGetProperty("players", out var players)) continue; + foreach (var p in players.EnumerateArray()) + { + var row = table.NewRow(); + row["TeamId"] = teamId; + row["Squadra"] = teamName; + row["PlayerId"] = p.TryGetProperty("id", out var id) && id.ValueKind == JsonValueKind.Number ? id.GetInt32() : 0; + row["Giocatore"] = p.TryGetProperty("name", out var n) ? n.GetString() ?? "" : ""; + row["Età"] = p.TryGetProperty("age", out var a) && a.ValueKind == JsonValueKind.Number ? a.GetInt32() : 0; + row["Numero"] = p.TryGetProperty("number", out var num) && num.ValueKind == JsonValueKind.Number ? num.GetInt32() : 0; + row["Ruolo"] = p.TryGetProperty("position", out var pos) ? pos.GetString() ?? "" : ""; + table.Rows.Add(row); + } + } + } + + private static void ParseCoachesResponse(DataTable table, string json, int teamId) + { + var doc = JsonDocument.Parse(json).RootElement; + if (!doc.TryGetProperty("response", out var resp)) return; + + foreach (var c in resp.EnumerateArray()) + { + var row = table.NewRow(); + row["CoachId"] = c.TryGetProperty("id", out var id) && id.ValueKind == JsonValueKind.Number ? id.GetInt32() : 0; + row["Nome"] = c.TryGetProperty("name", out var n) ? n.GetString() ?? "" : ""; + row["Età"] = c.TryGetProperty("age", out var a) && a.ValueKind == JsonValueKind.Number ? a.GetInt32() : 0; + row["Nazionalità"] = c.TryGetProperty("nationality", out var nat) ? nat.GetString() ?? "" : ""; + row["TeamId"] = teamId; + row["Squadra"] = c.TryGetProperty("team", out var t) && t.TryGetProperty("name", out var tn) ? tn.GetString() ?? "" : ""; + table.Rows.Add(row); + } + } + + private static void ParseTransfersResponse(DataTable table, string json) + { + var doc = JsonDocument.Parse(json).RootElement; + if (!doc.TryGetProperty("response", out var resp)) return; + + foreach (var entry in resp.EnumerateArray()) + { + int playerId = GetInt(entry, "player", "id"); + string playerName = GetStr(entry, "player", "name"); + + if (!entry.TryGetProperty("transfers", out var transfers)) continue; + foreach (var t in transfers.EnumerateArray()) + { + var row = table.NewRow(); + row["PlayerId"] = playerId; + row["Giocatore"] = playerName; + row["Data"] = t.TryGetProperty("date", out var d) ? d.GetString() ?? "" : ""; + row["Tipo"] = t.TryGetProperty("type", out var tp) ? tp.GetString() ?? "" : ""; + row["Da TeamId"] = GetInt(t, "teams", "out", "id"); + row["Da Squadra"] = GetStr(t, "teams", "out", "name"); + row["A TeamId"] = GetInt(t, "teams", "in", "id"); + row["A Squadra"] = GetStr(t, "teams", "in", "name"); + table.Rows.Add(row); + } + } + } + + #endregion + + #region JSON Utility Helpers + + private static int GetInt(JsonElement el, string prop1, string prop2) + { + if (el.TryGetProperty(prop1, out var p1) && p1.TryGetProperty(prop2, out var p2) && p2.ValueKind == JsonValueKind.Number) + return p2.GetInt32(); + return 0; + } + + private static int GetInt(JsonElement el, string prop1, string prop2, string prop3) + { + if (el.TryGetProperty(prop1, out var p1) && p1.TryGetProperty(prop2, out var p2) && p2.TryGetProperty(prop3, out var p3) && p3.ValueKind == JsonValueKind.Number) + return p3.GetInt32(); + return 0; + } + + private static string GetStr(JsonElement el, string prop1, string prop2) + { + if (el.TryGetProperty(prop1, out var p1) && p1.TryGetProperty(prop2, out var p2)) + return p2.GetString() ?? ""; + return ""; + } + + private static string GetStr(JsonElement el, string prop1, string prop2, string prop3) + { + if (el.TryGetProperty(prop1, out var p1) && p1.TryGetProperty(prop2, out var p2) && p2.TryGetProperty(prop3, out var p3)) + return p3.GetString() ?? ""; + return ""; + } + + private static int GetIntNested(JsonElement el, string p1, string p2) + { + if (el.TryGetProperty(p1, out var v1) && v1.TryGetProperty(p2, out var v2) && v2.ValueKind == JsonValueKind.Number) + return v2.GetInt32(); + return 0; + } + + private static int GetIntNested(JsonElement el, string p1, string p2, string p3) + { + if (el.TryGetProperty(p1, out var v1) && v1.TryGetProperty(p2, out var v2) && v2.TryGetProperty(p3, out var v3) && v3.ValueKind == JsonValueKind.Number) + return v3.GetInt32(); + return 0; + } + + private static int GetIntNested(JsonElement el, string p1, string p2, string p3, string p4) + { + if (el.TryGetProperty(p1, out var v1) && v1.TryGetProperty(p2, out var v2) && v2.TryGetProperty(p3, out var v3) && v3.TryGetProperty(p4, out var v4) && v4.ValueKind == JsonValueKind.Number) + return v4.GetInt32(); + return 0; + } + + private static string GetStrNested(JsonElement el, string p1, string p2) + { + if (el.TryGetProperty(p1, out var v1) && v1.TryGetProperty(p2, out var v2)) + return v2.GetString() ?? ""; + return ""; + } + + #endregion + + #region CSV & Utility + + private static string SaveCsv(DataTable table, string folder, string fileName) + { + if (!Directory.Exists(folder)) + Directory.CreateDirectory(folder); + + string path = Path.Combine(folder, fileName); + var sb = new StringBuilder(); + + // Header + for (int i = 0; i < table.Columns.Count; i++) + { + if (i > 0) sb.Append(','); + sb.Append(EscapeCsvField(table.Columns[i].ColumnName)); + } + sb.AppendLine(); + + // Rows + foreach (DataRow row in table.Rows) + { + for (int i = 0; i < table.Columns.Count; i++) + { + if (i > 0) sb.Append(','); + sb.Append(EscapeCsvField(row[i]?.ToString() ?? "")); + } + sb.AppendLine(); + } + + File.WriteAllText(path, sb.ToString(), Encoding.UTF8); + return path; + } + + private static string EscapeCsvField(string field) + { + if (field.Contains(',') || field.Contains('"') || field.Contains('\n')) + return "\"" + field.Replace("\"", "\"\"") + "\""; + return field; + } + + private static int CountSteps(FootballDownloadOptions options, int teamCount, int leagueCount) + { + int steps = 0; + if (options.DownloadTopScorers) steps += leagueCount; + if (options.DownloadTopAssists) steps += leagueCount; + if (options.DownloadTopCards) steps += leagueCount * 2; + if (options.DownloadTeamStats) steps += teamCount * leagueCount; + if (options.DownloadPlayerStats) steps += teamCount; + if (options.DownloadSquads) steps += teamCount; + if (options.DownloadCoaches) steps += teamCount; + if (options.DownloadTransfers) steps += teamCount; + return Math.Max(steps, 1); + } + + #endregion + } +} diff --git a/HorseRacingPredictor/HorseRacingPredictor/HorseRacingPredictor/UserSettings.cs b/HorseRacingPredictor/HorseRacingPredictor/HorseRacingPredictor/UserSettings.cs index 5b81a69..eb822de 100644 --- a/HorseRacingPredictor/HorseRacingPredictor/HorseRacingPredictor/UserSettings.cs +++ b/HorseRacingPredictor/HorseRacingPredictor/HorseRacingPredictor/UserSettings.cs @@ -23,6 +23,7 @@ namespace HorseRacingPredictor // ?? Football ?????????????????????????????????????????? public string ApiKey { get; set; } = string.Empty; + public string FbDataSource { get; set; } = "API - API-Football"; public string FbExportPath { get; set; } = string.Empty; public string FbPrefix { get; set; } = string.Empty; public string FbSuffix { get; set; } = string.Empty; @@ -50,8 +51,19 @@ namespace HorseRacingPredictor public bool FbCheckQuota { get; set; } = true; public int FbMinRemainingQuota { get; set; } = 10; + // ?? Football Supplementary Downloads (CSV separati) ?????? + public bool FbDownloadPlayerStats { get; set; } = false; + public bool FbDownloadTeamStats { get; set; } = false; + public bool FbDownloadTopScorers { get; set; } = false; + public bool FbDownloadTopAssists { get; set; } = false; + public bool FbDownloadTopCards { get; set; } = false; + public bool FbDownloadSquads { get; set; } = false; + public bool FbDownloadCoaches { get; set; } = false; + public bool FbDownloadTransfers { get; set; } = false; + // ?? Racing ??????????????????????????????????????????????? public string RacingApiKey { get; set; } = string.Empty; + public string RcDataSource { get; set; } = "API - FormFav"; public string RcExportPath { get; set; } = string.Empty; public string RcPrefix { get; set; } = string.Empty; public string RcSuffix { get; set; } = string.Empty; @@ -87,7 +99,16 @@ namespace HorseRacingPredictor MaxFixturesForDetails = FbMaxFixturesForDetails, ApiDelayMs = FbApiDelayMs, CheckQuota = FbCheckQuota, - MinRemainingQuota = FbMinRemainingQuota + MinRemainingQuota = FbMinRemainingQuota, + // Supplementari + DownloadPlayerStats = FbDownloadPlayerStats, + DownloadTeamStats = FbDownloadTeamStats, + DownloadTopScorers = FbDownloadTopScorers, + DownloadTopAssists = FbDownloadTopAssists, + DownloadTopCards = FbDownloadTopCards, + DownloadSquads = FbDownloadSquads, + DownloadCoaches = FbDownloadCoaches, + DownloadTransfers = FbDownloadTransfers }; } diff --git a/HorseRacingPredictor/HorseRacingPredictor/HorseRacingPredictor/appsettings.template.json b/HorseRacingPredictor/HorseRacingPredictor/HorseRacingPredictor/appsettings.template.json new file mode 100644 index 0000000..232c3e3 --- /dev/null +++ b/HorseRacingPredictor/HorseRacingPredictor/HorseRacingPredictor/appsettings.template.json @@ -0,0 +1,11 @@ +{ + "ConnectionStrings": { + "Football": "Server=YOUR_SERVER;Database=YOUR_DB;User Id=YOUR_USER;Password=YOUR_PASSWORD;TrustServerCertificate=True", + "Horses": "Server=YOUR_SERVER;Database=YOUR_DB;User Id=YOUR_USER;Password=YOUR_PASSWORD;TrustServerCertificate=True" + }, + "Api": { + "FootballApiKey": "", + "FootballApiKeyHeader": "x-rapidapi-key", + "FootballApiHost": "v3.football.api-sports.io" + } +} diff --git a/HorseRacingPredictor/HorseRacingPredictor/Main.cs b/HorseRacingPredictor/HorseRacingPredictor/Main.cs index 3868745..9350b93 100644 --- a/HorseRacingPredictor/HorseRacingPredictor/Main.cs +++ b/HorseRacingPredictor/HorseRacingPredictor/Main.cs @@ -1,4 +1,4 @@ -using BettingPredictor.UI; +using BettingPredictor.UI; using System; using System.Data; using System.IO; @@ -16,8 +16,8 @@ namespace BettingPredictor private DataTable racingData; // Credenziali predefinite Racing API - private const string DefaultRacingUser = "qi1mHOHPquDY9KNDASAeGipy"; - private const string DefaultRacingPass = "RXNFU1YX27R9rTnk8Vop8ZfH"; + private const string DefaultRacingUser = ""; + private const string DefaultRacingPass = ""; // Pagine e nav gestiti come array per semplificare la navigazione private Panel[] pages; diff --git a/HorseRacingPredictor/HorseRacingPredictor/MainWindow.xaml b/HorseRacingPredictor/HorseRacingPredictor/MainWindow.xaml index d16c7ba..89c1db1 100644 --- a/HorseRacingPredictor/HorseRacingPredictor/MainWindow.xaml +++ b/HorseRacingPredictor/HorseRacingPredictor/MainWindow.xaml @@ -1,7 +1,6 @@ - + + + @@ -311,7 +312,7 @@ BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Padding="{TemplateBinding Padding}"> - + @@ -332,7 +333,9 @@ - + + + @@ -342,7 +345,7 @@ BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Padding="{TemplateBinding Padding}"> - + @@ -484,8 +487,10 @@ + + - + @@ -495,7 +500,7 @@ - + + + + + + + + + + + + + - + - + - - + + + + + + + + + - + - - - - - + + + - + + + - + - - + + + + + + + + + + + + + + + - - + + + - - - - + + + + + - - - - - + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + + - - - - - - - - - + + + + + - - - - - + + - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Includi data - - - - - - - - - - - - - - - + + + + + + + + + + + + + + - - + + - - - - - - - - - - - - - - - - - - - - - -