From 20434ca273873b06a1fc714827bb14a8f8a4a0e3 Mon Sep 17 00:00:00 2001 From: Alberto Balbo Date: Thu, 6 Aug 2026 21:29:05 +0200 Subject: [PATCH] Riscatta aprendo il collegamento, e fai scorrere il conto alla rovescia MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Due difetti che i registri di tre giorni mostravano chiaramente. Il riscatto dei buoni giudicava la risposta del sito, e la giudicava male: ogni singolo tentativo risultava "sessione scaduta: risposta la pagina di accesso" — 6 trovati, 0 riscossi, 6 non riusciti, giro dopo giro per tre giorni — mentre le puntate arrivavano lo stesso. Il riconoscimento della pagina di accesso scattava su pagine che di accesso non erano, e il servizio dichiarava di non funzionare mentre funzionava. Adesso aprire il collegamento e' il riscatto: si apre, si scrive che e' stato aperto, e il numero vero lo da' la differenza di saldo, che e' l'unica misura che non mente. Resta un solo fallimento possibile, l'errore di trasporto, dove il collegamento non e' stato aperto affatto. Un premio si riscuote una volta sola: fra i due errori, insistere su un premio gia' preso non costa niente, mentre dichiarare falliti dei riscatti riusciti riempie il registro di allarmi inutili. Il giudizio vecchio non e' stato cancellato, e' in GiudicaRisposta. Il conto alla rovescia in griglia restava fermo fra un poll e l'altro. Il valore era gia' giusto — si ricava dalla scadenza ancorata all'orologio del server, non dall'ultimo poll — ma nessuno lo richiedeva finche' non arrivava una risposta, e nelle prove reali fra un poll e l'altro passava anche un secondo e mezzo: sembrava un'applicazione bloccata. Un battito da cinque volte al secondo notifica ora le tre sole proprieta' che dipendono dal tempo. Co-Authored-By: Claude Opus 5 --- Mimante/Core/MainWindow.MonitorHeader.cs | 27 +++++++ Mimante/Services/BidooPromoRedeemer.cs | 6 +- Mimante/Services/FreeBidsResponseValidator.cs | 41 ++++++++++ Mimante/Tests/FreeBidsTests.cs | 75 ++++++++++++++++--- Mimante/ViewModels/AuctionViewModel.cs | 23 +++++- 5 files changed, 160 insertions(+), 12 deletions(-) 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));