- 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.
655 lines
36 KiB
Markdown
655 lines
36 KiB
Markdown
# 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 |
|
||
| 10–60 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,82–18,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.
|