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 = "
" + + new string('x', 500) + "
"; + + 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));