Files
Mimante/README.md
T
Alby96 7ca504a70a Add utility classes for theme management, waiting, watched products, and notifications
- Implement ThemeManager for dynamic light/dark theme switching in the application.
- Create Wait class for cancellable delays without exceptions for smoother user experience.
- Introduce WatchedProductsStore to manage and persist watched products in JSON format.
- Add WindowsNotifier for system notifications to inform users of important events.
- Develop ProductViewModel to encapsulate product data and manage UI interactions effectively.
2026-08-04 21:49:53 +02:00

655 lines
36 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# AutoBidder — Bot desktop per aste Bidoo
App **desktop Windows nativa (WPF, .NET 10)** per monitorare e puntare automaticamente
sulle aste di [Bidoo](https://it.bidoo.com). Reimplementazione desktop, leggera e veloce,
del bot web/Docker "Mimante": stessa logica di puntata e stesse strategie, ma **senza
server, Docker, database o login** — un unico `.exe` che si avvia con un doppio click.
> ⚠️ Uso personale/educativo. Rispetta i Termini di Servizio di Bidoo e usa un account tuo.
---
## Caratteristiche
- **Monitor aste** — barra con contatori (in elenco / attive / osserva / ferme / vinte /
perse) e stato del conto e del motore: puntate, credito, aste vinte da confermare,
richieste inviate e **pallino di stato** con il ritardo medio di rete (verde sotto i
150 ms, giallo fino a 400, rosso oltre — o giallo finché l'orologio non è agganciato).
Questi ultimi sono numeri nudi: passandoci sopra il suggerimento dice cosa sono. Griglia
ordinabile con badge di stato — *In testa*, *Attiva*, *Osserva*, *Ferma*, *Sospesa*,
*Vinta*, *Persa* — prezzo, timer e ping colorati per urgenza.
- **Pannello dettagli a schede** — *Puntate automatiche* (anticipo, prezzo min/max, puntate
massime, verifica asta aperta, contatori del motore su quell'asta), *Prodotto* (valore,
spedizione, costo totale se vinci, verdetto di convenienza), *Puntate*, *Puntatori* (con
quota di ciascuno) e *Log* dell'asta.
- **Tre modi per asta** — **Ferma** (nessuna richiesta), **Osserva** (segue prezzo,
scadenza e avversari ma non punta mai: utile per studiare un'asta prima di entrare),
**Attiva** (segue e punta secondo le regole). Raggiungibili direttamente l'uno dall'altro.
- **Motore di precisione** — un motore **indipendente per ogni asta**, con due anelli
paralleli: polling a cadenza adattiva e "cecchino" che dorme fino all'istante di fuoco.
Le strategie decidono *se* puntare, non *quando*. Vedi [Come funziona il motore](#come-funziona-il-motore).
- **Strategie avanzate** — controllo convenienza (valore prodotto vs "Compra Subito"),
anti-bot, rilevamento competizione/heat, soft-retreat, profiling avversari e duelli,
bankroll/budget, puntata probabilistica. Tutte configurabili.
- **Esplora** — due modi. **Catalogo** nativo: barra laterale delle categorie e schede
prodotto con immagine, prezzo, timer, valore e ultimo puntatore, con **ricerca per nome**,
filtro *nascondi aste manuali* e aggiornamento prezzi facoltativo. Il listato si sfoglia a
pagine come sul sito, quindi si va **ben oltre le cinquanta aste** della prima schermata.
**Browser** Bidoo integrato (WebView2) per navigare e fare login — il cookie viene
estratto automaticamente.
- **Prodotti seguiti** — la **stellina** su una scheda segue quel prodotto: da lì in poi le
aste nuove dello stesso articolo entrano nel monitor da sole. Vedi
[Prodotti seguiti](#prodotti-seguiti).
- **Storico** — le aste concluse in due letture: **per prodotto** (aste, vinte, % vittorie,
chiusura media, min/max, prezzo massimo consigliato) e **asta per asta**. Salvato su file
**JSON**, nessun database.
- **Registri e dossier** — registro applicativo e del riscatto su file di testo, e un
**dossier per ogni asta** con ogni interrogazione, ogni puntata di ogni utente con l'ora al
millisecondo, ping e anticipo effettivo. È il materiale su cui si affinano le strategie:
Bidoo non espone nulla di tutto questo a posteriori. Vedi
[Registri e dossier](#registri-e-dossier).
- **Esporta** — una scheda per mettere in un file unico le aste scelte con i filtri
(periodo, prodotto, esito, solo quelle seguite dall'inizio alla fine…), pronte da dare in
pasto a un'analisi. Vedi [Esporta](#esporta).
- **Taratura dell'anticipo** — l'applicazione misura l'anticipo *effettivo* di ogni
puntata (quanto mancava davvero alla scadenza) e, con abbastanza dati, dice se c'è
margine per stringere. Il consiglio non viene mai applicato da solo.
- **Puntate gratis automatiche** — l'applicazione raccoglie i **collegamenti promozionali
pubblicati** (`?promocode=…&sign=…`), scarta quelli già presi e apre i nuovi con la tua
sessione, uno alla volta; e riscuote le **ricompense in attesa** sul conto. Indirizzi,
selettori e parametri stanno in un **file di configurazione**, non nel codice: se un sito
cambia, si corregge il file. Vedi [Riscatto puntate](#riscatto-puntate).
- **Notifiche di Windows** — avviso quando vinci un'asta, così te ne accorgi anche a
finestra chiusa.
- **Esportazione dello storico** — CSV per il foglio di calcolo, JSON con serie prezzi e
puntate per singolo utente per le analisi.
- **Impostazioni** — sessione, cartelle dei dati, parametri di default, motore di
precisione, aste programmate, notifiche e strategie.
- **Tema chiaro/scuro** — palette Material 3 a densità compatta, con interruttore in
*Impostazioni ▸ Aspetto*. Il cambio è immediato (nessun riavvio) e viene ricordato.
---
## Requisiti
- **Windows 10/11 x64**.
- **WebView2 Runtime** — già preinstallato su Windows 11 (e sulla maggior parte dei
Windows 10 aggiornati). Nient'altro da installare: il runtime .NET è incluso nell'exe.
---
## Avvio rapido (utente finale)
1. Scarica/copia `AutoBidder.exe` (versione pubblicata, self-contained).
2. **Doppio click.** Nessuna installazione.
3. **Autenticati** (vedi sotto).
4. In **Monitor** aggiungi le aste (pulsante , dal catalogo in *Esplora* o dal browser),
imposta l'anticipo puntata e scegli il modo:
- **Osserva** per seguirla senza rischiare nulla e capire come si comporta;
- **Attiva** quando vuoi che punti davvero.
### Autenticazione (cookie di sessione)
Si accede **solo dalla scheda Browser**: apri Bidoo lì dentro, fai il login, e il cookie
viene rilevato e importato da solo. Vale per tutta l'applicazione.
Non c'è più un campo per incollare il cookie a mano: due modi di accedere significavano due
sessioni possibili e diverse — quella digitata e quella del browser — con la domanda "quale
sta usando adesso?" senza una risposta visibile.
In **Impostazioni ▸ Sessione Bidoo** trovi lo stato corrente (utente, puntate residue,
credito), il pulsante per aprire il browser e *"Verifica connessione"*, che ricontrolla la
sessione salvata contro il sito.
Il cookie viene salvato in `%AppData%\AutoBidder\session.dat` protetto con **DPAPI**:
Windows lo lega al tuo account, quindi nessun altro utente della macchina può leggerlo e
l'applicazione non custodisce alcuna chiave. *"Disconnetti"* cancella la sessione.
> Le sessioni salvate dalle versioni precedenti (cifrate con una chiave contenuta
> nell'eseguibile) vengono lette una volta sola e riscritte subito con DPAPI.
---
## Build da sorgente (sviluppatore)
Prerequisito: **.NET SDK 10** (`dotnet --version` ≥ 10).
```powershell
# Compila ed esegui in debug
dotnet run --project AutoBidder.csproj
# Oppure apri AutoBidder.sln in Visual Studio 2022+ e premi F5
```
### Verifica dopo ogni modifica
```powershell
dotnet msbuild build/Release.proj -t:Verifica
```
Compila la soluzione e lancia i test, restituendo un codice di uscita diverso da zero se
qualcosa non va — utilizzabile anche in un hook.
I test coprono la logica pura e deterministica, cioè quella che può rompersi **senza dare
segnali**: `ServerClock` (compreso l'invariante che l'errore cada sempre verso l'anticipo,
mai verso il ritardo), il parsing di `data.php` (formato posizionale e non documentato,
dove una risposta malformata produrrebbe solo uno stato sbagliato), le pagine del listato
(dove confondere "fine dell'elenco" con "risposta incomprensibile" fermerebbe la discesa
alla prima pagina), `ProductKeyHelper` (che decide come si raggruppano le statistiche) e la
taratura dell'anticipo (che non deve mai proporre un valore sotto il margine di rete).
I test scrivono in una cartella temporanea, mai nei tuoi dati reali.
### Rigiocata sui dossier (backtest)
```powershell
powershell -ExecutionPolicy Bypass -File .\build/Release.proj
powershell -ExecutionPolicy Bypass -File .\build/Release.proj -Max 200 # giro veloce
```
Rigioca le aste già concluse a partire dai loro dossier e conta **cosa avrebbe fatto il
motore**: quanti cicli sarebbero arrivati fino all'anticipo (cioè quante puntate si
sarebbero spese) e quante volte una strategia avrebbe rifiutato la puntata. Serve a tarare
l'anticipo e come controllo di regressione: se una strategia comincia a bloccare puntate
che prima passavano, qui si vede come un numero invece che come aste perse una alla volta.
Sui 2.095 dossier raccolti finora (1.007.850 cicli di timer):
| Anticipo | Cicli raggiunti | Puntate per asta (mediana) | p90 |
|---------:|----------------:|---------------------------:|----:|
| 1000 ms | 5.999 (0,6%) | **1** | 6 |
| 2000 ms | 59.375 (5,9%) | 15 | 63 |
| 3000 ms | 205.758 (20,4%) | 46 | 220 |
| 4000 ms | 800.891 (79,5%) | 97 | 734 |
Il grosso degli avversari punta con circa tre secondi ancora sul cronometro; sotto i due
secondi la concorrenza si dirada. Ogni secondo di anticipo in più moltiplica per dieci le
puntate spese, quindi l'anticipo va tenuto stretto — ma non sotto i 600 ms, perché il ping
tocca 444 ms nel p99,9 e una puntata tardiva costa l'asta intera.
> La rigiocata misura i **costi**, non l'esito. Una nostra puntata rimette in gioco l'asta,
> e come avrebbero reagito gli avversari non è registrato da nessuna parte: dichiarare
> delle vittorie sarebbe inventarle.
### Creare l'eseguibile unico self-contained
```powershell
# Genera bin\publish\win-x64\AutoBidder.exe (un unico file, ~65 MB, nessuna installazione)
dotnet msbuild build/Release.proj -t:Pubblica
```
Lo script usa pubblicazione `self-contained`, `PublishSingleFile`, `win-x64`, senza
trimming (WPF). Il runtime .NET è incluso; il file `.pdb` accanto all'exe è solo per il
debug e **non** serve per l'esecuzione.
### Installatore e rilascio su Gitea
```powershell
# Solo il pacchetto: bin\installer\AutoBidder-<versione>-setup.exe
dotnet msbuild build/Release.proj -t:Pacchetto
# Tutto: verifica, pubblica, installatore, tag, release su Gitea con i file allegati
dotnet msbuild build/Release.proj -t:Rilascia
```
Da VS Code gli stessi passi sono attività pronte (**Terminale ▸ Esegui attività…**):
`verifica`, `backtest`, `crea installatore`, `rilascia su Gitea`.
L'installatore è generato con [Inno Setup 6](https://jrsoftware.org/isinfo.php)
(`winget install -e --id JRSoftware.InnoSetup`) a partire da `build/AutoBidder.iss`.
Installa per utente — nessuna richiesta di amministratore — crea i collegamenti, registra
la voce in *App installate* e offre l'avvio automatico all'accesso. **I dati in
`%LocalAppData%\AutoBidder` non vengono mai toccati**, né dall'aggiornamento né dalla
disinstallazione: sono aste, statistiche e sessione, cioè mesi di raccolta.
La versione ha una sola origine, `<Version>` in `AutoBidder.csproj`; lo script la legge da
lì e si ferma se l'eseguibile compilato ne dichiara un'altra.
Per Gitea servono quattro valori — copia `build/gitea.example.json` in `build/gitea.json` e riempilo,
oppure imposta `GITEA_URL`, `GITEA_OWNER`, `GITEA_REPO`, `GITEA_TOKEN`. Il file `build/gitea.json`
è escluso dal controllo di versione perché contiene un token. Nella release vengono caricati
sia l'installatore sia l'eseguibile nudo: chi non vuole installare nulla continua a poter
scaricare il solo `.exe`.
---
## Dove vengono salvati i dati
In `%AppData%\AutoBidder\`:
| File | Contenuto |
|------|-----------|
| `settings.json` | Impostazioni e strategie |
| `session.dat` | Cookie di sessione Bidoo, protetto con **DPAPI** (legato al tuo account Windows) |
| `site-config.json` | **Riferimenti del riscatto puntate**: indirizzi, intestazioni, parametri e chiavi di risposta. Vedi [Riscatto puntate](#riscatto-puntate) |
E nelle cartelle dati, **configurabili** in *Impostazioni ▸ Cartelle dei Dati*:
| File | Contenuto |
|------|-----------|
| `Dati/auctions.json` | Aste attualmente nel monitor |
| `Dati/watched-products.json` | **Prodotti seguiti** e aste già proposte |
| `Dati/products.json` | **Scheda di ogni prodotto**: opzioni scelte e statistiche accumulate |
| `Dati/claimed-promos.json` | Collegamenti promozionali già aperti (evita di riaprire gli stessi) |
| `Dati/Registri/app-AAAA-MM-GG.log` | **Registro applicativo**, scritto mentre l'app lavora |
| `Dati/Registri/puntate-AAAA-MM-GG.log` | **Registro del riscatto puntate** |
| `Dati/Registri/Aste/*.jsonl` | **Dossier di ogni asta**: vedi [Registri e dossier](#registri-e-dossier) |
| `Statistiche/completed-auctions.json` | Riepilogo delle aste concluse (alimenta lo Storico) |
| `Statistiche/aste-AAAA-MM.jsonl` | **Scheda dettagliata** di ogni asta: serie prezzi, puntate per utente, durata |
| `Statistiche/bid-lead-stats.json` | Misure dell'anticipo effettivo delle puntate |
| `Statistiche/Esportazioni/` | File generati dalla scheda **Esporta** |
I tre percorsi (dati, statistiche, registri) sono configurabili e i campi mostrano sempre
quello **in uso**: svuotarne uno lo riporta al predefinito. La cartella dei registri ha un
campo suo perché è quella che cresce di più — con i poll grezzi accesi un'asta lunga vale
qualche megabyte.
---
## Registri e dossier
Tutto si scrive **mentre l'applicazione lavora**, non alla chiusura: quello che vedi a video
è già sul disco, e un blocco improvviso non porta via il registro con sé. La scrittura passa
da una coda svuotata da un thread di fondo, quindi non tocca mai i percorsi caldi del
motore: l'anello di polling accoda una riga e prosegue.
**Il dossier di ogni asta** (`Dati/Registri/Aste/`) è il pezzo che conta per migliorare le
strategie. Un file per asta, in **JSON Lines**: intestazione, un evento per riga, riepilogo.
Ogni riga è JSON valido per conto suo, quindi il file si legge a pezzi e un'interruzione
perde al massimo l'ultima riga.
```jsonc
{"type":"header","auctionId":"85559716","name":"Cuffie Sony WH-1000XM5",
"product":{"buyNowPrice":299,"shippingCost":6.90,"bidCostEuro":0.20},
"config":{"bidBeforeDeadlineMs":800,"maxPrice":0,"pollCriticalMs":220}}
{"t":0.07,"type":"poll","at":"19:30:14.512","price":1.24,"timer":14.6,
"status":"Running","lastBidder":"marco_82","pingMs":73,"expiryUnix":1785000000}
{"t":0.08,"type":"bid","at":"19:30:14.520","user":"marco_82","price":1.24,
"bidAt":"2026-07-28T19:30:14+02:00","bidType":"Auto"}
{"t":0.09,"type":"my_bid","at":"19:30:15.203","price":1.25,"plannedLeadMs":800,
"actualLeadMs":762.4,"leadErrorMs":-37.6,"pingMs":68,"result":"ok","remainingBids":255}
{"type":"summary","outcome":"Persa","finalPrice":4.87,
"coverage":{"observedFromStart":true,"observedToEnd":true,"complete":true},
"participation":{"myBids":9,"totalObservedBids":387,"distinctBidders":11,
"topBidderShare":34.1,"bidsByUser":{"marco_82":132}},
"value":{"buyNowPrice":299,"totalCostIfWon":13.57,"finalPriceRatio":0.0163},
"network":{"avgPingMs":71.4,"minPingMs":54,"medianPingMs":69,"p95PingMs":118,"polls":8412},
"priceSeries":[{"t":0,"price":0.01},{"t":12.4,"price":1.24}]}
```
Tre cose da sapere:
- **`coverage.complete`** distingue le aste seguite dall'inizio alla fine da quelle prese a
metà corsa. Sono le uniche su cui i confronti reggono: su un'asta raccolta a metà non si sa
quante puntate erano già state spese, quindi né la velocità del prezzo né le quote dei
puntatori sono paragonabili alle altre. Il filtro dell'esportazione le seleziona da sole.
- **`plannedLeadMs` / `actualLeadMs`** sono la coppia con cui si tara l'anticipo. Da soli non
dicono nulla: è la differenza sistematica fra i due, su molte puntate, a dire se conviene
stringere o allargare.
- **I poll grezzi** (una riga per interrogazione, ~4 al secondo nella finestra critica) si
possono spegnere in *Impostazioni ▸ Registri su file*. Il ping continua a essere misurato
lo stesso: si perde la risoluzione al millisecondo, non le statistiche di rete.
I registri scaduti si cancellano da soli (predefinito: 90 giorni, 0 = per sempre).
---
## Esporta
La scheda **Esporta** mette in un unico file JSON le aste che rispondono ai filtri —
periodo, prodotto, prezzo finale, esito, solo quelle seguite dall'inizio alla fine, solo
quelle su cui hai puntato, solo quelle col dossier — completo degli eventi registrati. Il
conteggio si aggiorna mentre imposti i filtri, così non scopri a file scritto che non
selezionavano nulla.
Il file porta con sé una legenda dei campi e i filtri con cui è stato prodotto: un'analisi
si rilegge mesi dopo, quando non si ha più davanti né il codice né la memoria di com'era
impostata l'applicazione quel giorno.
---
## Come funziona il motore
Piazzare una puntata *poco prima* della scadenza richiede di sapere **quando** scade
davvero e di **arrivarci puntuali**. Entrambe le cose sono meno ovvie di quanto sembri.
**1. Quando scade (`Engine/ServerClock.cs`).** `data.php` dichiara i timestamp in secondi
interi: un singolo campione vale ±1 s, che è enorme rispetto a un anticipo di 200 ms. Ma
ogni risposta dice anche "il mio secondo corrente è S", ed è stata generata prima di
arrivare qui: ogni campione fornisce quindi un *limite superiore* per l'origine
dell'orologio remoto, e il minimo su molti campioni converge al valore vero (filtro alla
NTP, finestra di 256 campioni). L'errore residuo è per costruzione verso l'anticipo: si
punta un filo prima, mai dopo.
> In *Impostazioni ▸ Motore di Precisione* la diagnostica mostra la "finestra campioni":
> un valore intorno a **1000 ms è normale** — è l'ampiezza della quantizzazione al secondo,
> non l'errore della stima. Molto oltre, invece, indica una rete instabile.
**2. Arrivarci puntuali (`Engine/PrecisionWait.cs`).** `Task.Delay` si appoggia al timer di
sistema, che di default scatta ogni 15,6 ms. Qui si dorme finché mancano più di 22 ms, poi
si copre l'ultimo tratto in attesa attiva (`Thread.Yield()`, e `SpinWait` negli ultimi
2 ms). Costo: qualche decina di millisecondi di CPU per puntata. Guadagno: errore sotto il
millisecondo. In più il processo richiede la granularità timer a 1 ms (`timeBeginPeriod`),
come fanno browser e riproduttori multimediali.
**3. Un motore per asta (`Engine/AuctionRunner.cs`).** Ogni asta ha due anelli indipendenti:
il **polling**, che aggiorna prezzo e scadenza a cadenza adattiva, e il **cecchino**, che
dorme fino a "scadenza meno anticipo" e spara lì. Tenerli separati è la differenza
sostanziale rispetto a un ticker unico condiviso: una risposta lenta del server su un'asta
non sposta di un millisecondo la puntata sulle altre, e il numero di aste seguite non
degrada la precisione. Una sola puntata per ciclo è garantita ancorandosi alla scadenza
del ciclo stesso.
**4. Cadenza adattiva.** Un'asta a otto minuti dalla scadenza non ha bisogno di quattro
chiamate al secondo; una a tre secondi sì.
| Tempo alla scadenza | Cadenza |
|---|---|
| oltre 60 s | 2000 ms |
| 1060 s | 900 ms |
| sotto 10 s | 400 ms |
| finestra critica (< 4 s) | 220 ms |
La finestra critica si attiva **solo in stato Attiva**: in Osserva non c'è puntata da
azzeccare, quindi si risparmiano chiamate.
**5. Trasporto (`Net/BidooHttpClient.cs`).** Un solo pool di connessioni tenute calde
(HTTP/2 con fallback 1.1, 64 connessioni), header costanti impostati una volta, e un
limitatore a token bucket. Le puntate viaggiano su **corsia preferenziale**: `bid.php`
scavalca limitatore e coda: è l'unica richiesta il cui ritardo si paga in aste perse.
---
## Riscatto puntate
La scheda **Puntate** raccoglie puntate gratis da **due fonti indipendenti**, percorse
entrambe a ogni giro (predefinito: ogni 30 minuti). Un guasto sull'una non ferma l'altra.
**1. Collegamenti promozionali pubblicati.** È il meccanismo principale. Bidoo regala
puntate con collegamenti **già firmati** della forma
`it.bidoo.com/index.php?promocode=…&sign=…`, che i siti di raccolta ripubblicano.
L'applicazione legge la pagina sorgente (predefinito **puntateaste.it**), prende i
`<a class="aste_links">`, tiene solo gli indirizzi che puntano davvero al dominio e al
percorso del riscatto e portano **tutti** i parametri richiesti, scarta quelli già presi e
apre i nuovi con la tua sessione, **uno alla volta con una pausa di 1,5 s**.
Tre scelte che contano:
- la pagina sorgente è di terzi e viene letta **senza il cookie di Bidoo** — non le serve, e
mandarglielo significherebbe consegnarle la tua sessione;
- i collegamenti presi restano in `claimed-promos.json`: senza quella memoria ogni giro
riaprirebbe gli stessi indirizzi, perché sulla pagina restano pubblicati per giorni. Un
codice fallito si ritenta al massimo tre volte, poi si lascia perdere;
- l'indirizzo si apre **esattamente com'è**: porta una firma, e aggiungerci parametri la
invaliderebbe.
**2. Ricompense in attesa sul tuo account**, dalla pagina dei premi di Bidoo.
Resta poi il campo per un **collegamento singolo** ricevuto per email o per messaggio, che
la pagina di raccolta non ha: incollalo intero e premi *Riscatta*. Un codice da solo di
norma non basta — il `sign` non si può ricostruire.
**Tutti i riferimenti stanno in `%LocalAppData%\AutoBidder\site-config.json`**, non nel
codice: se il sito cambia un indirizzo o il nome di un campo, si corregge il file — il giro
successivo usa già i valori nuovi, senza ricompilare e senza riavviare. Il pulsante *Apri i
riferimenti* nella scheda lo apre con l'editor predefinito; cancellandolo si torna ai valori
di fabbrica.
```jsonc
{
"SiteSettings": { "BaseUrl": "https://it.bidoo.com", "TimeoutSeconds": 8 },
// ── Raccolta dei collegamenti pubblicati: il meccanismo principale ──
"PromoHarvest": {
"Enabled": true,
"SourceUrl": "https://www.puntateaste.it", // letta SENZA il cookie di Bidoo
"LinkClass": "aste_links", // <a class="aste_links" href="…">
"TargetDomain": "it.bidoo.com",
"TargetPath": "/index.php",
"RequiredQueryParams": ["promocode", "sign"], // senza "sign" non è un riscatto
"CodeQueryParam": "promocode", // identità del codice, per i doppioni
"DelayBetweenClaimsMs": 1500,
"MaxLinksPerRound": 15,
"MaxAttemptsPerCode": 3,
"SuccessMarkers": ["puntate gratis", "hai ricevuto", "complimenti"],
"ExpiredMarkers": ["già utilizzat", "non valido", "scadut"]
},
"Endpoints": {
"Rewards": "/my_chests.php", // pagina dei premi: da qui si leggono premi e token
"Login": "/login.php", // serve a riconoscere una sessione scaduta
"ClaimDailyReward": "/open_chest.php", // usato solo se la pagina non offre il collegamento
"ClaimPromoLink": "/index.php", // riscatto promozionale (?promocode=…&sign=…)
"Vouchers": "/my_vouchers.php",
"BuyBids": "/buy_bids.php"
},
// Applicate alle chiamate di riscatto. Cookie e Referer non stanno qui: il primo è la
// sessione vera, il secondo è la pagina dei premi.
"DefaultHeaders": {
"Accept": "application/json, text/javascript, */*; q=0.01",
"X-Requested-With": "XMLHttpRequest"
},
"ClaimParameters": {
// QueryKey è il nome del parametro con GET e del campo con POST.
"Daily": { "HttpMethod": "GET", "QueryKey": "id", "FormFields": { "action": "open_chest" },
"TokenField": "csrf_token", "CacheBusterKey": "" },
// Niente token né anti-cache: questi indirizzi portano una firma, e modificarli la invalida.
"PromoCode": { "HttpMethod": "GET", "QueryKey": "promocode", "FormFields": {},
"TokenField": "", "CacheBusterKey": "" }
},
// Come si giudica la risposta. Le parole di rifiuto valgono solo per le risposte brevi.
"ResponseValidation": {
"SuccessKey": "success", "SuccessValue": "true",
"BidsCountKey": "bids", "MessageKey": "message",
"FailureMarkers": ["già riscatt", "non disponibile"],
"BidWords": ["puntate", "bids", "crediti"]
},
// Parole, non espressioni regolari: la forma dell'espressione la mette il codice.
"PageRecognition": {
"ClaimLinkKeywords": ["open_chest", "riscuoti", "redeem"],
"ClaimScriptFunctions": ["openChest", "apriBaule"],
"ClaimIdAttributes": ["data-chest-id"],
"CsrfFieldNames": ["csrf_token", "_token"]
}
}
```
**Le puntate contate sono la differenza di saldo prima/dopo**, non il messaggio del sito.
Vale soprattutto per i collegamenti promozionali: Bidoo risponde con la stessa pagina sia
quando accredita le puntate sia quando il codice era già stato usato, e i testi in
`SuccessMarkers`/`ExpiredMarkers` sono l'unico appiglio per distinguerli.
> **Cosa è verificato.** Il meccanismo dei collegamenti (`a.aste_links` su puntateaste.it →
> `it.bidoo.com/index.php?promocode=…&sign=…`) e gli indirizzi delle pagine
> (`my_chests.php`, `my_vouchers.php`, `buy_bids.php`) sono riferimenti reali. Restano
> **ipotesi** i testi di conferma e `ClaimDailyReward` con i suoi riconoscitori, scritti
> senza aver visto quelle pagine da loggato. Se nel registro della scheda compare *"pagina
> delle ricompense non riconosciuta"* o *"la pagina è cambiata"*, serve una correzione **del
> solo file**.
---
## Prodotti seguiti
Bidoo rimette all'asta lo stesso articolo di continuo. Invece di cercare ogni volta la
nuova asta, in *Esplora* si preme la **stellina** sulla scheda: il prodotto diventa
"seguito" e le aste future dello stesso articolo entrano nel monitor da sole.
Il riconoscimento usa l'**id prodotto** di Bidoo quando c'è (il nome può cambiare fra
un'asta e l'altra, l'id no) e ripiega sul nome normalizzato quando manca.
Le aste già aggiunte — o rimosse a mano — vengono ricordate: un'asta che hai tolto dal
monitor non ci torna alla scansione successiva.
**Le aste entrano in stato *Osserva*.** È il valore predefinito e la scelta ha una ragione
precisa: un'asta che entra da sola e comincia subito a puntare spenderebbe puntate vere
senza che tu l'abbia mai vista. In *Osserva* la segui, la valuti e decidi tu quando
attivarla. Si può cambiare in *Impostazioni ▸ Prodotti Seguiti*, insieme alla frequenza di
ricerca e al numero massimo di aste aggiunte insieme.
### Puntate spese dai vincitori
Per ogni asta conclusa l'applicazione registra **quante puntate ha speso chi l'ha vinta**.
È il numero che il sito mostra come *Puntate utilizzate* sulla pagina dell'asta chiusa, e
arriva dal server insieme allo stato finale: in `data.php`, quando l'asta passa a `OFF`, i
dati correnti portano in coda `|pagate|gratis|…`.
Perché non si contano da soli: lo storico che il server restituisce si ferma alle **ultime
cinquanta puntate**. Su un'asta chiusa a 15 € — cioè 1.500 puntate in tutto — contarle
osservando darebbe un numero sbagliato senza dare alcun segnale. Il valore del server è
invece esatto e disponibile anche mesi dopo.
Ogni conteggio passa da un controllo di coerenza prima di entrare nelle statistiche
(`Utilities/AuctionIntegrity`): ogni puntata alza il prezzo di un centesimo, quindi il
prezzo finale in centesimi *è* il totale delle puntate dell'asta. Il vincitore non può
averne fatte di più, e non può averne fatte zero. Quello che non torna viene **scartato e
detto nel registro**: una statistica sbagliata è peggio di una assente, perché entra nelle
medie e sposta i limiti di prezzo senza che nessuno se ne accorga.
Le aste registrate prima che l'applicazione sapesse leggere questo dato non sono perse:
**Storico ▸ Recupera puntate dei vincitori** le richiede al server una alla volta, in corsia
di fondo, senza rubare banda alle aste in corso.
### Valore e risparmio reale
Nella scheda *Prodotti*, accanto ai prezzi, ci sono **Valore**, **Punt. vinc.**, **Costo
reale** e **Risparmio**.
Il costo reale è `prezzo tipico di aggiudicazione + puntate tipiche × costo puntata`. È la
cifra che conta: un buono da 50 € aggiudicato a 4 € con 200 puntate è costato 44 €, non 4.
Il risparmio si calcola su quella, non sul prezzo — e diventa **rosso quando è negativo**,
cioè quando vincere costa più che comprare. Se le puntate del vincitore non sono note per
nessuna asta del prodotto, le due colonne restano vuote invece di mostrare un numero
lusinghiero e falso.
### Limiti consigliati per prodotto
La scheda *Prodotti* propone, per ogni articolo, un **prezzo minimo e uno massimo** ricavati
da come sono andate le sue aste. Si applicano con il segno di spunta sulla riga (un prodotto)
oppure con **Usa consigliati su tutti** dalla barra, che dice prima quante righe cambierebbe.
Il massimo nasce da due vincoli, e vince il piu' stretto:
1. **Dove il prodotto si vende davvero** — il prezzo che copre l'85% delle chiusure passate
(regolabile in *Impostazioni ▸ Limiti Consigliati*).
2. **Quanto si puo' spendere** — `valore × (1 + tolleranza) puntate massime × costo puntata
spedizione`. E' qui che entra il numero massimo di puntate: alzarlo abbassa il prezzo
che ci si puo' permettere di raggiungere.
Il minimo e' il decimo percentile delle chiusure: sotto quel prezzo l'asta non si chiude
quasi mai, quindi puntare li' e' quasi sempre speso a vuoto.
> **Perche' non basta la moda.** Il prezzo a cui un articolo si aggiudica piu' spesso copre
> fra il 15% e il 44% delle sue aste, a seconda del prodotto: fermandosi li' si resterebbe
> fuori dalla maggior parte delle occasioni. La moda serve a dire *dove sta la massa*, e
> compare nella spiegazione; il tetto lo decide la copertura.
Il criterio precedente era `massimo storico × 0,7`. Non andava: le chiusure di Bidoo hanno
una coda lunghissima — il massimo e' mediamente **8,3 volte** la chiusura tipica — quindi
quel numero inseguiva l'asta anomala. Su *250€ Idea Shopping* diceva 142,66 €, dove il
consiglio attuale dice 4,8218,13 € su una mediana di 6,86 €.
Passando il puntatore sui valori consigliati compare il ragionamento completo: campione,
moda, mediana, copertura raggiunta e — quando e' il budget a comandare — il conto che l'ha
prodotto. In arancione quando il tetto non arriva al prezzo tipico: con quel numero di
puntate, quell'articolo non conviene.
**Orizzonte temporale.** Le aste che cominciano fra più di un'ora (predefinito) non entrano
ancora: vengono riprese alla scansione successiva, quando si sono avvicinate. Senza questo
limite la ricerca — che scende in fondo al listato apposta per trovare le aste programmate —
riempirebbe il monitor di aste che non succederanno per mezza giornata, consumando il tetto
delle aste aggiunte e nascondendo quelle vicine. Il valore si cambia in *Impostazioni ▸
Prodotti Seguiti* (`0` = nessun limite), e ogni prodotto può averne uno suo nella colonna
**Entro min** della scheda *Prodotti*: utile per gli articoli rari, che passano una volta al
giorno e conviene prendere in anticipo. **A mano puoi sempre aggiungere qualunque asta**: il
limite vale solo per l'aggiunta automatica.
**Solo aste non ancora cominciate.** Con questa opzione (spenta di predefinito) entrano nel
monitor soltanto le aste su cui nessuno ha ancora puntato, così le si può seguire dal primo
istante all'ultimo. Un'asta presa a metà si può comunque seguire, ma la sua storia è già
scritta per buona parte: non si sa quanto è durata davvero, chi c'era prima, come è salito
il prezzo — e i dossier, che sono il materiale su cui si tarano le strategie, raccontano
solo la seconda metà. Il prezzo da pagare è che si perdono le occasioni già in corso.
> **Nota sul catalogo.** Il listato si sfoglia come sul sito quando si scorre verso il
> basso: cinquanta aste per richiesta, in ordine di scadenza. Non c'è un numero di pagina —
> il client manda l'elenco di ciò che ha già e il server risponde con le successive — e la
> fine si riconosce da una pagina vuota. Le categorie piccole si esauriscono da sole, le
> grandi si fermano al tetto impostato in *Impostazioni ▸ Catalogo*.
---
## Architettura
```
AutoBidder/
├─ App.xaml / MainWindow.xaml # Guscio WPF, sidebar a tab (Aste/Cerca/Prodotti/Puntate/Storico/Esporta/Impostazioni)
├─ Controls/ # UserControl: AuctionMonitorControl, SettingsControl, BrowserControl
├─ Dialogs/ # Aggiungi asta, Sessione
├─ ViewModels/AuctionViewModel.cs # Riga della griglia aste (binding)
├─ Core/ # Code-behind MainWindow (partial): aste, UI, WebView, catalogo, log...
├─ Engine/ # Motore di precisione
│ ├─ ServerClock.cs # Aggancio all'orologio del server (filtro al minimo)
│ ├─ PrecisionWait.cs # Attesa sotto il millisecondo + timer di sistema a 1 ms
│ └─ AuctionRunner.cs # Un motore per asta: anello polling + anello cecchino
├─ Net/BidooHttpClient.cs # Trasporto: pool caldo, HTTP/2, token bucket, corsia puntate
├─ Services/
│ ├─ BidooApiClient.cs # Chiamate Bidoo (polling, puntata, info utente)
│ ├─ BidooCatalogClient.cs # Catalogo pubblico: categorie + listato paginato (Esplora)
│ ├─ CatalogPageParser.cs # Interpreta le pagine del listato (puro, sotto test)
│ ├─ AuctionMonitor.cs # Coordina i motori per asta, strategie ed eventi
│ ├─ BidStrategyService.cs # Strategie (se puntare)
│ ├─ SessionService/SessionManager # Sessione + cookie cifrato
│ └─ HtmlCacheService.cs # Fetch HTML con cache e rate-limit
├─ Models/ # AuctionInfo, AuctionState, CatalogAuction, CompletedAuctionRecord, ...
├─ Themes/ # Design system dell'interfaccia
│ ├─ Tokens.Dark.xaml # Token colore tema scuro
│ ├─ Tokens.Light.xaml # Token colore tema chiaro (stesse chiavi)
│ └─ Controls.xaml # Stili controlli (compatti, DynamicResource)
└─ Utilities/
├─ ThemeManager.cs # Switch tema a runtime + barra titolo
├─ SettingsManager.cs # AppSettings (JSON, cache 2s)
├─ PersistenceManager.cs # Salva/carica aste attive (JSON)
├─ CompletedAuctionsStore.cs # Storico aste concluse (JSON) + aggregazioni
├─ ProductStatsStore.cs # Scheda per prodotto: opzioni + statistiche accumulate
├─ FileLogWriter.cs # Scrittura su file accodata, fuori dai percorsi caldi
├─ TextLogService.cs # Registro applicativo e del riscatto, un file per giorno
├─ AuctionDossier.cs # Dossier JSONL di ogni asta (eventi, poll, riepilogo)
├─ AuctionExporter.cs # Esportazione filtrata in un file unico
└─ ProductValueCalculator.cs # Calcolo convenienza prodotto
```
Rispetto alla versione web "Mimante" sono stati rimossi: Blazor Server, Kestrel,
ASP.NET Identity/login, PostgreSQL, SignalR e Docker. Le strategie sono le stesse; il
motore di timing è stato invece riscritto (vedi [Come funziona il motore](#come-funziona-il-motore)).
### Tema e stili
Nessun colore è cablato nella UI: la XAML referenzia **solo token semantici** via
`DynamicResource` (es. `{DynamicResource Brush.Surface}`). `Tokens.Dark.xaml` e
`Tokens.Light.xaml` espongono le stesse chiavi; `ThemeManager` sostituisce a runtime lo
slot 0 dei `MergedDictionaries` di `App.xaml`, quindi l'intera interfaccia (barra del
titolo inclusa, via DWM) cambia istantaneamente.
Per modificare la grafica:
- **cambiare i colori** → edita solo i due file `Tokens.*.xaml`;
- **cambiare forma/densità dei controlli** → `Themes/Controls.xaml` (stili impliciti +
`RoundedButton`, `SmallRoundedButton`, `CardBorder`, `InfoBox`, `SectionHeader`, …).
Convenzione: il colore ha significato. Neutro = azione secondaria; accento = azione
primaria; verde/ambra/rosso = successo/attenzione/distruttivo; **oro = asta vinta** (per
distinguerla da un semplice successo).
Le icone vengono da **Segoe MDL2 Assets**, font di sistema: monocromatiche, coerenti col
tema e — soprattutto — senza file da distribuire, che sotto `PublishSingleFile` non
verrebbero estratti.