Aggiunte opzioni avanzate download calcio API-Football

Introdotta la classe FootballDownloadOptions per la gestione centralizzata delle opzioni di download e filtri. L’utente può ora scegliere quali endpoint scaricare (partite, quote, previsioni, classifiche, H2H, eventi, formazioni, statistiche, infortuni) e impostare parametri avanzati (ID bookmaker, pagine quote, leghe, delay API, quota minima residua, ecc.) tramite la nuova sezione UI.

Rifattorizzata la logica di download per supportare il caricamento condizionale e sequenziale degli endpoint, con progress bar proporzionale e callback di stato dettagliate. Aggiunti nuovi client API per endpoint aggiuntivi e metodi di enrichment per arricchire dinamicamente la tabella delle partite.

Aggiornate le impostazioni utente e la UI per supportare tutte le nuove opzioni. Aggiunto il controllo della quota API residua prima del download. Inseriti nuovi file sorgente per i client API mancanti e la gestione delle opzioni.
This commit is contained in:
2026-04-07 18:18:06 +02:00
parent 51568df264
commit d6db312f73
13 changed files with 1433 additions and 61 deletions
@@ -54,4 +54,9 @@
<ItemGroup Label="EmbeddedResource items now included by globbing that were not in the original project file">
<EmbeddedResource Remove="Main.resx" />
</ItemGroup>
<ItemGroup>
<Content Include="HorseRacingPredictor\appsettings.json" Link="appsettings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
</Project>
@@ -0,0 +1,29 @@
using System;
using RestSharp;
namespace HorseRacingPredictor.Football.API
{
/// <summary>
/// Client per l'endpoint "fixtures/events" dell'API-Football.
/// Restituisce gli eventi di una partita (gol, cartellini, sostituzioni, VAR).
/// </summary>
internal class Events : HorseRacingPredictor.Football.Manager.API
{
private const string Endpoint = "fixtures/events";
/// <summary>
/// Ottiene tutti gli eventi per una partita
/// </summary>
public RestResponse GetEventsByFixture(int fixtureId)
{
try
{
return ExecuteRequest($"{Endpoint}?fixture={fixtureId}", ApiTypes.Fixtures);
}
catch (Exception ex)
{
throw new Exception($"Errore durante il recupero degli eventi per la partita {fixtureId}: {ex.Message}", ex);
}
}
}
}
@@ -0,0 +1,29 @@
using System;
using RestSharp;
namespace HorseRacingPredictor.Football.API
{
/// <summary>
/// Client per l'endpoint "fixtures/statistics" dell'API-Football.
/// Restituisce le statistiche di una partita (possesso, tiri, falli, ecc.).
/// </summary>
internal class FixtureStatistics : HorseRacingPredictor.Football.Manager.API
{
private const string Endpoint = "fixtures/statistics";
/// <summary>
/// Ottiene le statistiche per una partita
/// </summary>
public RestResponse GetStatisticsByFixture(int fixtureId)
{
try
{
return ExecuteRequest($"{Endpoint}?fixture={fixtureId}", ApiTypes.Fixtures);
}
catch (Exception ex)
{
throw new Exception($"Errore durante il recupero delle statistiche per la partita {fixtureId}: {ex.Message}", ex);
}
}
}
}
@@ -0,0 +1,32 @@
using System;
using RestSharp;
namespace HorseRacingPredictor.Football.API
{
/// <summary>
/// Client per l'endpoint "fixtures/headtohead" dell'API-Football.
/// Restituisce lo storico degli scontri diretti tra due squadre.
/// </summary>
internal class HeadToHead : HorseRacingPredictor.Football.Manager.API
{
private const string Endpoint = "fixtures/headtohead";
/// <summary>
/// Ottiene gli scontri diretti tra due squadre
/// </summary>
/// <param name="teamId1">ID della prima squadra</param>
/// <param name="teamId2">ID della seconda squadra</param>
/// <param name="last">Numero di ultimi scontri da recuperare (default 5)</param>
public RestResponse GetH2H(int teamId1, int teamId2, int last = 5)
{
try
{
return ExecuteRequest($"{Endpoint}?h2h={teamId1}-{teamId2}&last={last}", ApiTypes.Fixtures);
}
catch (Exception ex)
{
throw new Exception($"Errore durante il recupero H2H per {teamId1} vs {teamId2}: {ex.Message}", ex);
}
}
}
}
@@ -0,0 +1,60 @@
using System;
using RestSharp;
namespace HorseRacingPredictor.Football.API
{
/// <summary>
/// Client per l'endpoint "injuries" dell'API-Football.
/// Restituisce la lista di giocatori infortunati o squalificati.
/// </summary>
internal class Injuries : HorseRacingPredictor.Football.Manager.API
{
private const string Endpoint = "injuries";
/// <summary>
/// Ottiene gli infortunati per una data specifica
/// </summary>
public RestResponse GetInjuriesByDate(DateTime date)
{
try
{
string dateStr = date.ToString("yyyy-MM-dd");
return ExecuteRequest($"{Endpoint}?date={dateStr}", ApiTypes.Fixtures);
}
catch (Exception ex)
{
throw new Exception($"Errore durante il recupero degli infortunati per la data {date.ToShortDateString()}: {ex.Message}", ex);
}
}
/// <summary>
/// Ottiene gli infortunati per una partita specifica
/// </summary>
public RestResponse GetInjuriesByFixture(int fixtureId)
{
try
{
return ExecuteRequest($"{Endpoint}?fixture={fixtureId}", ApiTypes.Fixtures);
}
catch (Exception ex)
{
throw new Exception($"Errore durante il recupero degli infortunati per la partita {fixtureId}: {ex.Message}", ex);
}
}
/// <summary>
/// Ottiene gli infortunati per una lega e stagione
/// </summary>
public RestResponse GetInjuriesByLeague(int leagueId, int season)
{
try
{
return ExecuteRequest($"{Endpoint}?league={leagueId}&season={season}", ApiTypes.Fixtures);
}
catch (Exception ex)
{
throw new Exception($"Errore durante il recupero degli infortunati per lega {leagueId}, stagione {season}: {ex.Message}", ex);
}
}
}
}
@@ -0,0 +1,29 @@
using System;
using RestSharp;
namespace HorseRacingPredictor.Football.API
{
/// <summary>
/// Client per l'endpoint "fixtures/lineups" dell'API-Football.
/// Restituisce formazioni, modulo e titolari di una partita.
/// </summary>
internal class Lineups : HorseRacingPredictor.Football.Manager.API
{
private const string Endpoint = "fixtures/lineups";
/// <summary>
/// Ottiene le formazioni per una partita
/// </summary>
public RestResponse GetLineupsByFixture(int fixtureId)
{
try
{
return ExecuteRequest($"{Endpoint}?fixture={fixtureId}", ApiTypes.Fixtures);
}
catch (Exception ex)
{
throw new Exception($"Errore durante il recupero delle formazioni per la partita {fixtureId}: {ex.Message}", ex);
}
}
}
}
@@ -0,0 +1,44 @@
using System;
using RestSharp;
namespace HorseRacingPredictor.Football.API
{
/// <summary>
/// Client per l'endpoint "standings" dell'API-Football.
/// Restituisce la classifica di un campionato per una stagione.
/// </summary>
internal class Standings : HorseRacingPredictor.Football.Manager.API
{
private const string Endpoint = "standings";
/// <summary>
/// Ottiene la classifica per una lega e una stagione
/// </summary>
public RestResponse GetStandings(int leagueId, int season)
{
try
{
return ExecuteRequest($"{Endpoint}?league={leagueId}&season={season}", ApiTypes.Leagues);
}
catch (Exception ex)
{
throw new Exception($"Errore durante il recupero della classifica per lega {leagueId}, stagione {season}: {ex.Message}", ex);
}
}
/// <summary>
/// Ottiene la classifica per un team e una stagione
/// </summary>
public RestResponse GetStandingsByTeam(int teamId, int season)
{
try
{
return ExecuteRequest($"{Endpoint}?team={teamId}&season={season}", ApiTypes.Teams);
}
catch (Exception ex)
{
throw new Exception($"Errore durante il recupero della classifica per team {teamId}, stagione {season}: {ex.Message}", ex);
}
}
}
}
@@ -0,0 +1,30 @@
using System;
using RestSharp;
namespace HorseRacingPredictor.Football.API
{
/// <summary>
/// Client per l'endpoint "status" dell'API-Football.
/// Restituisce la quota residua giornaliera e le informazioni sull'account.
/// Non conta come chiamata nella quota giornaliera.
/// </summary>
internal class Status : HorseRacingPredictor.Football.Manager.API
{
private const string Endpoint = "status";
/// <summary>
/// Ottiene lo stato dell'account e la quota residua
/// </summary>
public RestResponse GetStatus()
{
try
{
return ExecuteRequest(Endpoint);
}
catch (Exception ex)
{
throw new Exception($"Errore durante il recupero dello stato API: {ex.Message}", ex);
}
}
}
}
@@ -0,0 +1,102 @@
using System.Collections.Generic;
namespace HorseRacingPredictor.Football
{
/// <summary>
/// Parametri configurabili per controllare quali endpoint API-Football scaricare
/// e come filtrare i dati risultanti.
/// </summary>
public class FootballDownloadOptions
{
// ?? Endpoint da scaricare ?????????????????????????????????
public bool DownloadFixtures { get; set; } = true;
public bool DownloadOdds { get; set; } = true;
public bool DownloadPredictions { get; set; } = true;
public bool DownloadStandings { get; set; } = false;
public bool DownloadH2H { get; set; } = false;
public bool DownloadEvents { get; set; } = false;
public bool DownloadLineups { get; set; } = false;
public bool DownloadStatistics { get; set; } = false;
public bool DownloadInjuries { get; set; } = false;
// ?? Filtri ????????????????????????????????????????????????
/// <summary>
/// Se non vuoto, scarica fixture solo per queste leghe (league IDs).
/// Lista vuota = tutte le leghe.
/// </summary>
public List<int> LeagueIds { get; set; } = new();
/// <summary>
/// ID del bookmaker per le quote (default 8 = Bet365).
/// </summary>
public int BookmakerId { get; set; } = 8;
/// <summary>
/// Numero massimo di pagine da scaricare per le quote.
/// </summary>
public int OddsMaxPages { get; set; } = 3;
/// <summary>
/// Timezone IANA per le date delle fixture (es. "Europe/Rome").
/// </summary>
public string Timezone { get; set; } = "Europe/Rome";
/// <summary>
/// Stagione corrente per le classifiche (calcolata automaticamente se 0).
/// </summary>
public int Season { get; set; } = 0;
/// <summary>
/// Numero massimo di fixture per cui scaricare dati aggiuntivi
/// (H2H, events, lineups, statistics) per evitare troppe chiamate.
/// 0 = nessun limite.
/// </summary>
public int MaxFixturesForDetails { get; set; } = 50;
/// <summary>
/// Ritardo in millisecondi tra una chiamata API e l'altra per rispettare il rate limit.
/// </summary>
public int ApiDelayMs { get; set; } = 300;
/// <summary>
/// Se true, controlla la quota residua prima di iniziare e si ferma quando insufficiente.
/// </summary>
public bool CheckQuota { get; set; } = true;
/// <summary>
/// Numero minimo di richieste residue prima di fermare il download.
/// </summary>
public int MinRemainingQuota { get; set; } = 10;
/// <summary>
/// Restituisce la stagione corrente calcolata (anno corrente o anno-1 se prima di luglio).
/// </summary>
public int GetEffectiveSeason()
{
if (Season > 0) return Season;
var now = System.DateTime.Now;
return now.Month >= 7 ? now.Year : now.Year - 1;
}
/// <summary>
/// Conta il numero approssimativo di chiamate API previste per una data.
/// Utile per mostrare all'utente una stima.
/// </summary>
public int EstimateApiCalls(int fixtureCount)
{
int calls = 0;
if (DownloadFixtures) calls += 1;
if (DownloadOdds) calls += OddsMaxPages;
if (DownloadPredictions) calls += fixtureCount;
if (DownloadStandings && LeagueIds.Count > 0) calls += LeagueIds.Count;
else if (DownloadStandings) calls += 5; // stima
if (DownloadH2H) calls += fixtureCount;
if (DownloadEvents) calls += fixtureCount;
if (DownloadLineups) calls += fixtureCount;
if (DownloadStatistics) calls += fixtureCount;
if (DownloadInjuries) calls += 1;
if (CheckQuota) calls += 1; // status endpoint
return calls;
}
}
}
File diff suppressed because it is too large Load Diff
@@ -21,7 +21,7 @@ namespace HorseRacingPredictor
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
};
// ?? Football ????????????????????????????????????????????
// ?? Football ??????????????????????????????????????????
public string ApiKey { get; set; } = string.Empty;
public string FbExportPath { get; set; } = string.Empty;
public string FbPrefix { get; set; } = string.Empty;
@@ -30,7 +30,27 @@ namespace HorseRacingPredictor
public string FbDateFormat { get; set; } = "yyyy-MM-dd";
public string FbFormat { get; set; } = "CSV";
// ?? Racing ??????????????????????????????????????????????
// ?? Football Download Options ????????????????????????????
public bool FbDownloadFixtures { get; set; } = true;
public bool FbDownloadOdds { get; set; } = true;
public bool FbDownloadPredictions { get; set; } = true;
public bool FbDownloadStandings { get; set; } = false;
public bool FbDownloadH2H { get; set; } = false;
public bool FbDownloadEvents { get; set; } = false;
public bool FbDownloadLineups { get; set; } = false;
public bool FbDownloadStatistics { get; set; } = false;
public bool FbDownloadInjuries { get; set; } = false;
public List<int> FbLeagueIds { get; set; } = new();
public int FbBookmakerId { get; set; } = 8;
public int FbOddsMaxPages { get; set; } = 3;
public string FbTimezone { get; set; } = "Europe/Rome";
public int FbSeason { get; set; } = 0;
public int FbMaxFixturesForDetails { get; set; } = 50;
public int FbApiDelayMs { get; set; } = 300;
public bool FbCheckQuota { get; set; } = true;
public int FbMinRemainingQuota { get; set; } = 10;
// ?? Racing ???????????????????????????????????????????????
public string RacingApiKey { get; set; } = string.Empty;
public string RcExportPath { get; set; } = string.Empty;
public string RcPrefix { get; set; } = string.Empty;
@@ -41,7 +61,35 @@ namespace HorseRacingPredictor
public string RcTimezone { get; set; } = "Australia/Sydney";
public List<string> RcCountries { get; set; } = new() { "au", "nz" };
// ?? Persistence ?????????????????????????????????????????
// ?? Persistence ??????????????????????????????????????
/// <summary>
/// Costruisce un FootballDownloadOptions a partire dalle impostazioni salvate.
/// </summary>
public Football.FootballDownloadOptions ToFootballDownloadOptions()
{
return new Football.FootballDownloadOptions
{
DownloadFixtures = FbDownloadFixtures,
DownloadOdds = FbDownloadOdds,
DownloadPredictions = FbDownloadPredictions,
DownloadStandings = FbDownloadStandings,
DownloadH2H = FbDownloadH2H,
DownloadEvents = FbDownloadEvents,
DownloadLineups = FbDownloadLineups,
DownloadStatistics = FbDownloadStatistics,
DownloadInjuries = FbDownloadInjuries,
LeagueIds = new List<int>(FbLeagueIds),
BookmakerId = FbBookmakerId,
OddsMaxPages = FbOddsMaxPages,
Timezone = FbTimezone,
Season = FbSeason,
MaxFixturesForDetails = FbMaxFixturesForDetails,
ApiDelayMs = FbApiDelayMs,
CheckQuota = FbCheckQuota,
MinRemainingQuota = FbMinRemainingQuota
};
}
public static UserSettings Load()
{
@@ -765,6 +765,85 @@
Foreground="{StaticResource BrText}"
Click="btnBrowseFbExport_Click"/>
</Grid>
<!-- ?? OPZIONI DOWNLOAD ENDPOINT ?? -->
<Border Height="1" Background="{StaticResource BrBorder}" Margin="0,16,0,14"/>
<TextBlock Text="Endpoint da scaricare" FontSize="13" FontFamily="Segoe UI Semibold"
Foreground="{StaticResource BrText}" Margin="0,0,0,10"/>
<WrapPanel Orientation="Horizontal">
<CheckBox x:Name="chkFbFixtures" IsChecked="True" Margin="0,0,16,6">Partite</CheckBox>
<CheckBox x:Name="chkFbOdds" IsChecked="True" Margin="0,0,16,6">Quote</CheckBox>
<CheckBox x:Name="chkFbPredictions" IsChecked="True" Margin="0,0,16,6">Previsioni</CheckBox>
<CheckBox x:Name="chkFbStandings" Margin="0,0,16,6">Classifiche</CheckBox>
<CheckBox x:Name="chkFbH2H" Margin="0,0,16,6">Scontri diretti</CheckBox>
<CheckBox x:Name="chkFbEvents" Margin="0,0,16,6">Eventi</CheckBox>
<CheckBox x:Name="chkFbLineups" Margin="0,0,16,6">Formazioni</CheckBox>
<CheckBox x:Name="chkFbStatistics" Margin="0,0,16,6">Statistiche</CheckBox>
<CheckBox x:Name="chkFbInjuries" Margin="0,0,16,6">Infortuni</CheckBox>
</WrapPanel>
<!-- ?? PARAMETRI AVANZATI ?? -->
<Border Height="1" Background="{StaticResource BrBorder}" Margin="0,10,0,14"/>
<TextBlock Text="Parametri avanzati" FontSize="13" FontFamily="Segoe UI Semibold"
Foreground="{StaticResource BrText}" Margin="0,0,0,10"/>
<Grid Margin="0,0,0,8">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="165"/>
<ColumnDefinition Width="22"/>
<ColumnDefinition Width="165"/>
<ColumnDefinition Width="22"/>
<ColumnDefinition Width="165"/>
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0">
<TextBlock Text="ID Bookmaker" Foreground="{StaticResource BrSubtext0}" FontSize="11" Margin="0,0,0,4"/>
<TextBox x:Name="txtFbBookmakerId" Style="{StaticResource FlatTb}" Text="8"/>
</StackPanel>
<StackPanel Grid.Column="2">
<TextBlock Text="Max pagine quote" Foreground="{StaticResource BrSubtext0}" FontSize="11" Margin="0,0,0,4"/>
<TextBox x:Name="txtFbOddsMaxPages" Style="{StaticResource FlatTb}" Text="3"/>
</StackPanel>
<StackPanel Grid.Column="4">
<TextBlock Text="Max fixture dettagli" Foreground="{StaticResource BrSubtext0}" FontSize="11" Margin="0,0,0,4"/>
<TextBox x:Name="txtFbMaxFixtures" Style="{StaticResource FlatTb}" Text="50"/>
</StackPanel>
</Grid>
<Grid Margin="0,0,0,8">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="245"/>
<ColumnDefinition Width="22"/>
<ColumnDefinition Width="245"/>
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0">
<TextBlock Text="Timezone" Foreground="{StaticResource BrSubtext0}" FontSize="11" Margin="0,0,0,4"/>
<TextBox x:Name="txtFbTimezone" Style="{StaticResource FlatTb}" Text="Europe/Rome"/>
</StackPanel>
<StackPanel Grid.Column="2">
<TextBlock Text="ID Leghe (separati da virgola, vuoto = tutte)" Foreground="{StaticResource BrSubtext0}" FontSize="11" Margin="0,0,0,4"/>
<TextBox x:Name="txtFbLeagueIds" Style="{StaticResource FlatTb}"/>
</StackPanel>
</Grid>
<Grid Margin="0,0,0,4">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="22"/>
<ColumnDefinition Width="165"/>
<ColumnDefinition Width="22"/>
<ColumnDefinition Width="165"/>
</Grid.ColumnDefinitions>
<CheckBox x:Name="chkFbCheckQuota" Grid.Column="0" IsChecked="True">Controlla quota</CheckBox>
<StackPanel Grid.Column="2">
<TextBlock Text="Quota minima residua" Foreground="{StaticResource BrSubtext0}" FontSize="11" Margin="0,0,0,4"/>
<TextBox x:Name="txtFbMinQuota" Style="{StaticResource FlatTb}" Text="10"/>
</StackPanel>
<StackPanel Grid.Column="4">
<TextBlock Text="Delay API (ms)" Foreground="{StaticResource BrSubtext0}" FontSize="11" Margin="0,0,0,4"/>
<TextBox x:Name="txtFbApiDelay" Style="{StaticResource FlatTb}" Text="300"/>
</StackPanel>
</Grid>
</StackPanel>
</Border>
@@ -266,23 +266,32 @@ namespace HorseRacingPredictor
try
{
pbFootball.Value = 0;
lblStatusFb.Text = "Scaricamento elenco partite…";
lblStatusFb.Text = "Preparazione opzioni di download…";
btnDownloadFb.IsEnabled = false;
dpFootball.IsEnabled = false;
btnExportFbCsv.IsEnabled = false;
// Costruisci le opzioni di download dalla UI
var options = BuildFootballDownloadOptions();
var progress = new Progress<int>(v => pbFootball.Value = v);
var status = new Progress<string>(s => lblStatusFb.Text = s);
var table = await Task.Run(() =>
_footballManager.GetTodayFixtures(date, progress, status));
_footballManager.GetTodayFixtures(date, options, progress, status));
_footballData = table;
// Ensure the start time column exists and populate it (no timezone label)
InjectRomeStartTimeColumn(_footballData, "Inizio");
// Nascondi le colonne interne (ID di supporto) dal DataGrid
dgFootball.ItemsSource = _footballData?.DefaultView;
dgFootball.AutoGeneratingColumn += (s, e) =>
{
if (e.PropertyName is "LeagueId" or "HomeTeamId" or "AwayTeamId")
e.Cancel = true;
};
if (_footballData != null && _footballData.Rows.Count > 0)
{
@@ -847,6 +856,26 @@ namespace HorseRacingPredictor
SetComboBoxSelectionByContent(cmbFbDateFormat, s.FbDateFormat);
SetComboBoxSelectionByContent(cmbFbFormat, s.FbFormat);
// Football Download Options
chkFbFixtures.IsChecked = s.FbDownloadFixtures;
chkFbOdds.IsChecked = s.FbDownloadOdds;
chkFbPredictions.IsChecked = s.FbDownloadPredictions;
chkFbStandings.IsChecked = s.FbDownloadStandings;
chkFbH2H.IsChecked = s.FbDownloadH2H;
chkFbEvents.IsChecked = s.FbDownloadEvents;
chkFbLineups.IsChecked = s.FbDownloadLineups;
chkFbStatistics.IsChecked = s.FbDownloadStatistics;
chkFbInjuries.IsChecked = s.FbDownloadInjuries;
txtFbBookmakerId.Text = s.FbBookmakerId.ToString();
txtFbOddsMaxPages.Text = s.FbOddsMaxPages.ToString();
txtFbMaxFixtures.Text = s.FbMaxFixturesForDetails.ToString();
if (txtFbTimezone != null) txtFbTimezone.Text = s.FbTimezone;
txtFbLeagueIds.Text = s.FbLeagueIds.Count > 0 ? string.Join(",", s.FbLeagueIds) : "";
chkFbCheckQuota.IsChecked = s.FbCheckQuota;
txtFbMinQuota.Text = s.FbMinRemainingQuota.ToString();
txtFbApiDelay.Text = s.FbApiDelayMs.ToString();
// Racing
txtRcExportPath.Text = s.RcExportPath;
txtRcPrefix.Text = s.RcPrefix;
txtRcSuffix.Text = s.RcSuffix;
@@ -1091,6 +1120,10 @@ namespace HorseRacingPredictor
{
try
{
// Costruisci e salva le opzioni di download in un FootballDownloadOptions
// per validazione prima del salvataggio
var options = BuildFootballDownloadOptions();
var s = new UserSettings
{
ApiKey = txtApiKey.Text.Trim(),
@@ -1100,6 +1133,25 @@ namespace HorseRacingPredictor
FbIncludeDate = chkFbIncludeDate.IsChecked == true,
FbDateFormat = (cmbFbDateFormat?.SelectedItem as ComboBoxItem)?.Content?.ToString() ?? "yyyy-MM-dd",
FbFormat = (cmbFbFormat?.SelectedItem as ComboBoxItem)?.Content?.ToString() ?? "CSV",
// Football Download Options
FbDownloadFixtures = chkFbFixtures.IsChecked == true,
FbDownloadOdds = chkFbOdds.IsChecked == true,
FbDownloadPredictions = chkFbPredictions.IsChecked == true,
FbDownloadStandings = chkFbStandings.IsChecked == true,
FbDownloadH2H = chkFbH2H.IsChecked == true,
FbDownloadEvents = chkFbEvents.IsChecked == true,
FbDownloadLineups = chkFbLineups.IsChecked == true,
FbDownloadStatistics = chkFbStatistics.IsChecked == true,
FbDownloadInjuries = chkFbInjuries.IsChecked == true,
FbBookmakerId = int.TryParse(txtFbBookmakerId.Text.Trim(), out var bId) ? bId : 8,
FbOddsMaxPages = int.TryParse(txtFbOddsMaxPages.Text.Trim(), out var omp) ? omp : 3,
FbMaxFixturesForDetails = int.TryParse(txtFbMaxFixtures.Text.Trim(), out var mf) ? mf : 50,
FbTimezone = txtFbTimezone?.Text?.Trim() ?? "Europe/Rome",
FbLeagueIds = ParseIntList(txtFbLeagueIds?.Text),
FbCheckQuota = chkFbCheckQuota.IsChecked == true,
FbMinRemainingQuota = int.TryParse(txtFbMinQuota.Text.Trim(), out var mq) ? mq : 10,
FbApiDelayMs = int.TryParse(txtFbApiDelay.Text.Trim(), out var ad) ? ad : 300,
// Racing
RcExportPath = txtRcExportPath.Text.Trim(),
RcPrefix = txtRcPrefix.Text.Trim(),
RcSuffix = txtRcSuffix.Text.Trim(),
@@ -1126,6 +1178,50 @@ namespace HorseRacingPredictor
}
}
// ???????????? FOOTBALL DOWNLOAD OPTIONS HELPERS ????????????
/// <summary>
/// Costruisce un FootballDownloadOptions leggendo i valori dalla UI.
/// </summary>
private Football.FootballDownloadOptions BuildFootballDownloadOptions()
{
return new Football.FootballDownloadOptions
{
DownloadFixtures = chkFbFixtures.IsChecked == true,
DownloadOdds = chkFbOdds.IsChecked == true,
DownloadPredictions = chkFbPredictions.IsChecked == true,
DownloadStandings = chkFbStandings.IsChecked == true,
DownloadH2H = chkFbH2H.IsChecked == true,
DownloadEvents = chkFbEvents.IsChecked == true,
DownloadLineups = chkFbLineups.IsChecked == true,
DownloadStatistics = chkFbStatistics.IsChecked == true,
DownloadInjuries = chkFbInjuries.IsChecked == true,
BookmakerId = int.TryParse(txtFbBookmakerId.Text.Trim(), out var bId) ? bId : 8,
OddsMaxPages = int.TryParse(txtFbOddsMaxPages.Text.Trim(), out var omp) ? omp : 3,
MaxFixturesForDetails = int.TryParse(txtFbMaxFixtures.Text.Trim(), out var mf) ? mf : 50,
Timezone = txtFbTimezone?.Text?.Trim() ?? "Europe/Rome",
LeagueIds = ParseIntList(txtFbLeagueIds?.Text),
CheckQuota = chkFbCheckQuota.IsChecked == true,
MinRemainingQuota = int.TryParse(txtFbMinQuota.Text.Trim(), out var mq) ? mq : 10,
ApiDelayMs = int.TryParse(txtFbApiDelay.Text.Trim(), out var ad) ? ad : 300
};
}
/// <summary>
/// Analizza una stringa di interi separati da virgola e restituisce una lista.
/// </summary>
private static List<int> ParseIntList(string text)
{
var result = new List<int>();
if (string.IsNullOrWhiteSpace(text)) return result;
foreach (var part in text.Split(',', ';', ' '))
{
if (int.TryParse(part.Trim(), out var val))
result.Add(val);
}
return result;
}
// ???????????????????????? VIRTUAL FOOTBALL ????????????????????????
private void btnVfbNavigate_Click(object sender, RoutedEventArgs e)