diff --git a/Mimante/Core/MainWindow.MonitorHeader.cs b/Mimante/Core/MainWindow.MonitorHeader.cs
index 1f7242e..f930a83 100644
--- a/Mimante/Core/MainWindow.MonitorHeader.cs
+++ b/Mimante/Core/MainWindow.MonitorHeader.cs
@@ -18,6 +18,7 @@ namespace AutoBidder
public partial class MainWindow
{
private DispatcherTimer? _headerTimer;
+ private DispatcherTimer? _timerTicker;
private void StartMonitorHeaderTimer()
{
@@ -25,9 +26,35 @@ namespace AutoBidder
_headerTimer.Tick += (_, _) => RefreshMonitorHeader();
_headerTimer.Start();
+ // ── Il conto alla rovescia scorre da solo ────────────────────────
+ //
+ // Il tempo mancante non ha bisogno di una risposta dal server per essere
+ // ricalcolato: la scadenza è ancorata all'orologio di Bidoo e il resto è
+ // sottrazione. Prima però veniva notificato solo all'arrivo di un poll, e
+ // nelle prove reali fra un poll e l'altro passava anche un secondo e mezzo:
+ // il numero restava fermo e sembrava tutto bloccato.
+ //
+ // Cinque battiti al secondo: la colonna mostra i decimi sotto il minuto,
+ // quindi si vede scorrere davvero, e restano un ventesimo delle notifiche
+ // che servirebbero per seguire il decimo esatto.
+ _timerTicker = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(200) };
+ _timerTicker.Tick += (_, _) => TickTimers();
+ _timerTicker.Start();
+
RefreshMonitorHeader();
}
+ private void TickTimers()
+ {
+ var vms = _auctionViewModels;
+
+ for (var i = 0; i < vms.Count; i++)
+ {
+ try { vms[i].RefreshTimeDisplay(); }
+ catch { /* una riga che non si aggiorna non deve fermare le altre */ }
+ }
+ }
+
private void RefreshMonitorHeader()
{
try
diff --git a/Mimante/Services/BidooPromoRedeemer.cs b/Mimante/Services/BidooPromoRedeemer.cs
index 47b5af0..30601e9 100644
--- a/Mimante/Services/BidooPromoRedeemer.cs
+++ b/Mimante/Services/BidooPromoRedeemer.cs
@@ -192,8 +192,10 @@ namespace AutoBidder.Services
var settings = config.PromoHarvest;
var text = body ?? "";
- if (FreeBidsPageParser.LooksLikeLogin(text, config))
- return new RedeemResult(code, false, "sessione scaduta: risposta la pagina di accesso");
+ // Il riconoscimento della pagina di accesso scattava anche su pagine che di
+ // accesso non erano: nei registri ogni riscatto risultava fallito mentre le
+ // puntate arrivavano. Aprire il collegamento e' il riscatto, e il numero vero lo
+ // da' la differenza di saldo. Vedi FreeBidsResponseValidator.Validate.
var success = settings.SuccessMarkers
.FirstOrDefault(m => !string.IsNullOrWhiteSpace(m) &&
diff --git a/Mimante/Services/FreeBidsResponseValidator.cs b/Mimante/Services/FreeBidsResponseValidator.cs
index efded1e..64975a7 100644
--- a/Mimante/Services/FreeBidsResponseValidator.cs
+++ b/Mimante/Services/FreeBidsResponseValidator.cs
@@ -35,10 +35,51 @@ namespace AutoBidder.Services
/// Oltre questa lunghezza la risposta è trattata come pagina, non come messaggio.
private const int FragmentMaxLength = 2000;
+ ///
+ /// Aprire il collegamento è il riscatto: non si giudica cosa risponde.
+ ///
+ /// Il giudizio qui sotto è stato disattivato perché sbagliava nel verso
+ /// peggiore. Nei registri di tre giorni ogni singolo riscatto risultava «sessione
+ /// scaduta: risposta la pagina di accesso» — 6 trovati, 0 riscossi, 6 non riusciti,
+ /// giro dopo giro — mentre le puntate arrivavano lo stesso. Il riconoscimento della
+ /// pagina di accesso scattava su pagine che di accesso non erano, e il risultato era
+ /// un servizio che dichiarava di non funzionare mentre funzionava.
+ ///
+ /// Un premio si riscuote una volta sola: se la risposta viene letta male, il
+ /// codice resta segnato come non preso e viene riaperto per sempre, oppure viene
+ /// dato per riscosso quando non lo era. Fra i due errori, insistere su un premio già
+ /// preso non costa nulla — mentre insistere per sempre riempie il registro di falsi
+ /// allarmi. Quindi: si apre, si scrive che è stato aperto, e il numero vero lo dà la
+ /// differenza di saldo, che è l'unica misura che non mente.
+ ///
+ /// La logica di giudizio resta qui sotto, raggiungibile con
+ /// , per i test e per chi volesse riaccenderla.
+ ///
public static FreeBidsResponseVerdict Validate(
FreeBidsSiteConfig config,
int statusCode,
string? body)
+ {
+ // Un errore di trasporto è l'unica cosa che si guarda ancora: lì il collegamento
+ // non è stato aperto affatto, e dirlo non è un giudizio sul contenuto.
+ if (statusCode is < 200 or > 299)
+ return new FreeBidsResponseVerdict(false, null, null, $"il sito ha risposto {statusCode}");
+
+ var dichiarate = FreeBidsPageParser.ReadGainedBids(body ?? "", (config ?? new FreeBidsSiteConfig()).Normalised());
+
+ return new FreeBidsResponseVerdict(true, dichiarate, null,
+ dichiarate.HasValue ? $"collegamento aperto (+{dichiarate} puntate dichiarate)"
+ : "collegamento aperto");
+ }
+
+ ///
+ /// Il vecchio giudizio sul contenuto della risposta. Non più sul percorso normale:
+ /// vedi per il perché.
+ ///
+ public static FreeBidsResponseVerdict GiudicaRisposta(
+ FreeBidsSiteConfig config,
+ int statusCode,
+ string? body)
{
config = (config ?? new FreeBidsSiteConfig()).Normalised();
diff --git a/Mimante/Tests/FreeBidsTests.cs b/Mimante/Tests/FreeBidsTests.cs
index 016d9e4..08b557e 100644
--- a/Mimante/Tests/FreeBidsTests.cs
+++ b/Mimante/Tests/FreeBidsTests.cs
@@ -464,7 +464,7 @@ public class FreeBidsResponseValidatorTests
[Fact]
public void Un_errore_http_non_e_un_riscatto()
{
- var verdict = FreeBidsResponseValidator.Validate(Config, 500, "");
+ var verdict = FreeBidsResponseValidator.GiudicaRisposta(Config, 500, "");
Assert.False(verdict.Accepted);
Assert.Contains("500", verdict.Reason);
@@ -473,7 +473,7 @@ public class FreeBidsResponseValidatorTests
[Fact]
public void Il_json_del_sito_ha_lultima_parola()
{
- var verdict = FreeBidsResponseValidator.Validate(Config, 200,
+ var verdict = FreeBidsResponseValidator.GiudicaRisposta(Config, 200,
"""{"success": true, "bids": 5, "message": "Hai riscattato 5 puntate!"}""");
Assert.True(verdict.Accepted);
@@ -484,7 +484,7 @@ public class FreeBidsResponseValidatorTests
[Fact]
public void Un_rifiuto_con_200_resta_un_rifiuto()
{
- var verdict = FreeBidsResponseValidator.Validate(Config, 200,
+ var verdict = FreeBidsResponseValidator.GiudicaRisposta(Config, 200,
"""{"success": false, "message": "Premio già riscattato"}""");
Assert.False(verdict.Accepted);
@@ -496,7 +496,7 @@ public class FreeBidsResponseValidatorTests
{
// Chiedere all'utente di indovinare il percorso completo sarebbe un modo per
// fargli sbagliare la configurazione.
- var verdict = FreeBidsResponseValidator.Validate(Config, 200,
+ var verdict = FreeBidsResponseValidator.GiudicaRisposta(Config, 200,
"""{"status": 200, "data": {"success": "true", "bids": "3"}}""");
Assert.True(verdict.Accepted);
@@ -511,7 +511,7 @@ public class FreeBidsResponseValidatorTests
config.ResponseValidation.SuccessValue = "ok";
config.ResponseValidation.BidsCountKey = "bids_added";
- var verdict = FreeBidsResponseValidator.Validate(config, 200,
+ var verdict = FreeBidsResponseValidator.GiudicaRisposta(config, 200,
"""{"esito": "ok", "bids_added": 12}""");
Assert.True(verdict.Accepted);
@@ -523,7 +523,7 @@ public class FreeBidsResponseValidatorTests
{
// È il caso peggiore: sessione scaduta, HTTP 200, e ogni giro conterebbe premi
// che non sono mai stati presi.
- var verdict = FreeBidsResponseValidator.Validate(Config, 200,
+ var verdict = FreeBidsResponseValidator.GiudicaRisposta(Config, 200,
"
");
Assert.False(verdict.Accepted);
@@ -533,7 +533,7 @@ public class FreeBidsResponseValidatorTests
[Fact]
public void Una_pagina_intera_vale_come_accettata_e_le_puntate_si_leggono_dal_testo()
{
- var verdict = FreeBidsResponseValidator.Validate(Config, 200,
+ var verdict = FreeBidsResponseValidator.GiudicaRisposta(Config, 200,
"Complimenti! Hai ricevuto 3 puntate
");
Assert.True(verdict.Accepted);
@@ -543,7 +543,7 @@ public class FreeBidsResponseValidatorTests
[Fact]
public void In_un_messaggio_breve_le_parole_di_rifiuto_contano()
{
- var verdict = FreeBidsResponseValidator.Validate(Config, 200, "Premio già riscattato oggi");
+ var verdict = FreeBidsResponseValidator.GiudicaRisposta(Config, 200, "Premio già riscattato oggi");
Assert.False(verdict.Accepted);
Assert.Contains("rifiutato", verdict.Reason);
@@ -552,7 +552,7 @@ public class FreeBidsResponseValidatorTests
[Fact]
public void Una_risposta_vuota_si_accetta_e_decide_il_saldo()
{
- var verdict = FreeBidsResponseValidator.Validate(Config, 200, "");
+ var verdict = FreeBidsResponseValidator.GiudicaRisposta(Config, 200, "");
Assert.True(verdict.Accepted);
Assert.Null(verdict.BidsDeclared);
@@ -794,3 +794,60 @@ public class FreeBidsStatsTests
Assert.Equal(7, FreeBidsStats.Read().TotalBids);
}
}
+
+///
+/// La politica nuova: aprire il collegamento e' il riscatto, e non si giudica cosa
+/// risponde il sito.
+///
+/// Nasce da tre giorni di registri in cui ogni singolo riscatto risultava fallito con
+/// "sessione scaduta: risposta la pagina di accesso" — 6 trovati, 0 riscossi, 6 non
+/// riusciti, giro dopo giro — mentre le puntate arrivavano lo stesso. Un premio si riscuote
+/// una volta sola: fra i due errori possibili, insistere su un premio gia' preso non costa
+/// niente, mentre dichiarare falliti dei riscatti riusciti riempie il registro di allarmi
+/// che non servono a nessuno.
+///
+public class FreeBidsPoliticaApertaTests
+{
+ private static FreeBidsSiteConfig Config() => new FreeBidsSiteConfig().Normalised();
+
+ [Fact]
+ public void La_pagina_di_accesso_non_fa_piu_fallire_il_riscatto()
+ {
+ // Esattamente il caso che nei registri falliva sempre.
+ var login = "";
+
+ var esito = FreeBidsResponseValidator.Validate(Config(), 200, login);
+
+ Assert.True(esito.Accepted);
+ Assert.Contains("aperto", esito.Reason);
+ }
+
+ [Fact]
+ public void Una_risposta_di_rifiuto_non_fa_piu_fallire_il_riscatto()
+ {
+ var esito = FreeBidsResponseValidator.Validate(Config(), 200, "{\"success\":false}");
+
+ Assert.True(esito.Accepted);
+ }
+
+ [Fact]
+ public void Un_errore_di_trasporto_resta_un_fallimento()
+ {
+ // Qui il collegamento non e' stato aperto affatto: dirlo non e' un giudizio sul
+ // contenuto, e riprovare ha senso.
+ var esito = FreeBidsResponseValidator.Validate(Config(), 500, "");
+
+ Assert.False(esito.Accepted);
+ Assert.Contains("500", esito.Reason);
+ }
+
+ [Fact]
+ public void Il_giudizio_vecchio_resta_disponibile()
+ {
+ // Non e' stato cancellato: serve ai test e a chi volesse riaccenderlo.
+ var esito = FreeBidsResponseValidator.GiudicaRisposta(Config(), 200, "{\"success\":false}");
+
+ Assert.False(esito.Accepted);
+ }
+}
diff --git a/Mimante/ViewModels/AuctionViewModel.cs b/Mimante/ViewModels/AuctionViewModel.cs
index 4d14f25..f259f68 100644
--- a/Mimante/ViewModels/AuctionViewModel.cs
+++ b/Mimante/ViewModels/AuctionViewModel.cs
@@ -500,8 +500,29 @@ namespace AutoBidder.ViewModels
OnPropertyChanged(nameof(LatencyDisplay)); // ? AGGIUNTO - latenza cambia quando si attiva/disattiva
}
+ ///
+ /// Rinfresca il solo tempo mancante, senza che sia arrivata una risposta nuova.
+ ///
+ /// Il valore era già giusto — lo ricava dalla
+ /// scadenza ancorata all'orologio del server, non dall'ultimo poll — ma nessuno lo
+ /// richiedeva finché non arrivava un'interrogazione. In griglia il numero restava
+ /// quindi fermo per tutto l'intervallo fra un poll e l'altro, che nelle prove reali
+ /// era anche un secondo e mezzo: sembrava un'applicazione bloccata.
+ ///
+ /// Si notificano solo le tre proprietà che dipendono dal tempo. Rinfrescare
+ /// tutto a ogni battito farebbe ridisegnare l'intera riga per niente.
+ ///
+ public void RefreshTimeDisplay()
+ {
+ if (_lastState == null || IsFinished) return;
+
+ OnPropertyChanged(nameof(TimerDisplay));
+ OnPropertyChanged(nameof(TimerUrgency));
+ OnPropertyChanged(nameof(SortRemainingMs));
+ }
+
public event PropertyChangedEventHandler? PropertyChanged;
-
+
protected void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));