Come scaricare le e-Qsl sul proprio PC

Alcuni mesi fa preparai, per Radio Rivista, l'organo ufficiale dell'ARI, un articolo che descriveva un modo, tutto sommato comodo, di scaricare sul proprio PC le e-QSL e, dopo un po` di attesa, il corposo testo, è stato dato alle stampe con il numero di Maggio 2021.

L'esigenza nasceva dal fatto che, dopo alcune migliaia di QSO, il processo di salvataggio è particolarmente lungo se fatto manualmente: in pratica, se pensi a scaricare le e-Qsl, smetti di fare nuovi QSO.

Ma lasciamo i preamboli e veniamo al nocciolo del discorso...

Una (facile) soluzione per scaricare tutte le e-qsl sul PC (Linux)

Al giorno d'oggi, frenetici tempi a base di Internet, reti e computers esistono valide e veloci alternative alla conferma mediante QSL Bureau; anzi, dovrebbe essere proprio il requisito della velocità a spingere tutti i radioamatori ad utilizzarle ed aggiungerle al Burò, nel caso sia comunque gradita la conferma tradizionale, lenta e cartacea.

Sì, perché il cartaceo è lento ed anche costoso: due motivi in più per convincersi ad usare i metodi di conferma elettronici.

Esistono due metodi riconosciuti di conferma che, però, "non si parlano" tra di loro (e sarebbe bello che lo facessero): LOTW, acronimo di "Log of the World", gestito dalla ARRL americana e "e-Qsl" legata al sito eqsl.cc un sistema alternativo e molto gradito che prevede, oltre alla conferma in elettronico mediante scambio di file, anche una simpatica "Cartolina elettronica" da scaricare dal sito: ed è qui arrivano i problemi!

Scaricare dal sito la QSL significa accedere a eqsl.cc con la propria utenza, scrivere la password (sperando di non averla dimenticata...), accedere alla lista delle conferme, cliccare il QSO, scaricare la cartolina, salvarla con un nome degno (ad esempio con Call, data, banda e modo) rispetto all'anonima nomeclatura assegnata dal sistema e, magari, dislocarla in directory (cioè "cartelle") in modo che sia facilmente reperibile (ad esempio, una "cartella" per il continente ed una per ogni sua "Entità"), in modo che sia facile, in un secondo momento, andarla a ritrovare...

Ma per fare tutto questo popò di operazioni non basta una vita... specie se le QSL sono molte: allora, come fare senza sottrarre tempo al già poco "tempo libero" a disposizione?

Diciamo che il mio normale lavoro consiste nel fare in modo che altri impieghino poco tempo per compiere operazioni ripetitive. Avendo ciò in mente ed avvalendomi di Linux e di alcuni righi di programmazione in Perl, ho realizzato un piccolo script che mi permette di scaricare automaticamente le E-Qsl dei miei QSO confermati, di rinominarle e di salvarle in maniera appropriata in modalità del tutto automatica, gestendo anche eventuali errori restituiti dal sistema... quindi, se la storia vi interessa, mettetevi comodi che si comincia!

Scaricare dal sito eqsl.cc

Il download automatico (non massivo, mi raccomando) è già previsto dal sito: nessuna novità, quindi. In pratica è necessario passare alcune informazioni ad una pagina internet del sito e gestire la risposta ottenuta.

Vediamo cosa succede nel dettaglio richiamando la pagina:

https://www.eqsl.cc/qslcard/GeteQSL.cfm

Richiamandola, viene attivato un processo di generazione grafica della conferma; la QSL, infatti, non esiste normalmente: sarà l'attivazione di questo programma a generarla ed a renderla disponibile per la visualizzazione ed il salvataggio sul nostro computer. Si tratta, come è facile immaginare, di un processo che appesantisce il server di eqsl.cc: pertanto, i gestori raccomandano, in caso di processo automatizzato, di "rallentare" la generazione ed il download dei file, in modo da non gravare il server di questa attività; inoltre, se si avvia un processo "massivo" di scaricamento, sarà il server a bloccare l'operazione per evitare il sovraccarico. Inoltre, è necessario tenere conto delle QSL già scaricate in modo da evitare di scaricarle di nuovo: questa è una cosa molto importante per l'ottimizzazione del processo.

Quindi, bisogna tener ben presente che o si va lenti non non si scarica.

Per avviare la generazione della cartolina, ho quindi richiamato la pagina non con un normale "browser" (Mozilla Firefox o Chrome, ad esempio) ma con un browser a riga di comando del mondo Linux: curl e non in modalità "GET" (tutti i parametri uno dietro l'altro nell'URL) ma in modalità POST (mediante campi passati al server). I campi che servono sono tanti ma sono abbastanza comprensibili leggendo la riga di comando che segue (la barra retroversa è usata per "spezzare" il comando su più righe):

  $ curl -s -d Username='nome_utente' -d Password='Password_utente' \
  -d CallsignFrom='CALL_QSO' -d QSOBand='Banda' -d QSOMode='Modo' \
  -d QSOYear='Anno' -d QSOMonth='$Mese' -d QSODay='Giorno' \
  -d QSOHour='Ora' -d QSOMinute='Min' \
  https://www.eqsl.cc/qslcard/GeteQSL.cfm

In questo modo Curl richiama la pagina passando i dati della mia utenza (attenzione: la password è in chiaro) ed i dati del QSO di cui desidero la cartolina elettronica di conferma... Se la transazione va a buon fine (cioè senza errori), avrò in risposta una pagina HTML con, all'interno, l'immagine della QSL nel classico TAG html "<img src=". Ad esempio, ipotizzando d'aver passato dei parametri validi alla richiesta dell'esempio, composta come segue:

curl -s -d Username='ik7xja' -d Password='mia_password' \
>   -d CallsignFrom='DL8AWK' -d QSOBand='30M' -d QSOMode='CW' \
>   -d QSOYear='2020' -d QSOMonth='10' -d QSODay='22' \
>   -d QSOHour='18' -d QSOMinute='52' \
>   https://www.eqsl.cc/qslcard/GeteQSL.cfm

e, la risposta, sarà:

<-- If there have been no errors to this point,
     parse for tag <IMG SRC= and the URL path to
     the graphic will follow in double quotes -->
<-- The .. should be replaced with https://www.eQSL.cc
     in the full URL specification -->
<-- Card Design HamID: 1387742--->
<-- 523 -->
<-- 523 -->
<-- 523 -->
<-- 523 -->
<-- 523 -->
<-- 260 -->
<-- 523 -->
<-- Resized OK -->
<img src="/CFFileServlet/_cf_image/_cfimg894087922025843640.JPG" alt="" />
<-- End of the eQSL file location -->

Come si vede, si tratta di una pagina molto semplice dove l'unica cosa che ci interessa è il valore inserito nel TAG "<img src=" che dovremo completare con il dominio del server https (https://www.eqsl.cc) da mettere avanti all'URL, e dove l'immagine della QSL è rappresentata da "_cfimg894087922025843640.JPG". Questa immagine, generata -ripeto- dinamicamente, resta disponibile per qualche minuto per poi essere rimossa: quindi, bisogna essere veloci a scaricarla... Come fare?

Per scaricare l'immagine, basta usare un altro piccolo programma Linux da riga di comando: wget. WGET è un "downloader" cioè uno scaricatore di file da Internet che può salvare anche con un nome assegnato arbitrariamente dall'utente. Nel mio caso, basta richiamarlo passandogli, come primo parametro, l'URL dell'immagine e, come secondo, il nome (e, se necessario, anche il path) con cui salvarla:

$ wget https://www.eQSL.cc/CFFileServlet/_cf_image/_cfimg7570451824606228254.JPG \
  -O DL8AWK_20201022_30M_CW.jpg

Nel giro di qualche frazione di secondo, avremo la risposta:

--2020-11-15 09:51:11--  https://www.eqsl.cc/CFFileServlet/_cf_image/_cfimg7570451824606228254.JPG
   Risoluzione di www.eqsl.cc (www.eqsl.cc)... 52.117.199.107
   Connessione a www.eqsl.cc (www.eqsl.cc)|52.117.199.107|:443... connesso.
   Richiesta HTTP inviata, in attesa di risposta... 200 OK
   Lunghezza: 52561 (51K) [image/jpeg]
   Salvataggio in: "DL8AWK_20201022_30M_CW.jpg"

   100%[================================>] 52.561       128KB/s   in 0,4s

   2020-11-15 09:51:13 (128 KB/s) - "DL8AWK_20201022_30M_CW.jpg" salvato [52561/52561]

Come si vede il buon "wget" si è preoccupato di scaricare l'immagine dal link fornito e di salvarla con un nome che contiene i dati salienti del nostro QSO. Facile vero?

Ma, sino a questo punto, non ho fatto altro che mostrare come salvare a mano e da riga di comando la cartolina QSL elettronica: ora dobbiamo vedere come fare a recuperare i dati dei nostri QSO per automatizzare il processo.

Estrazione dati da CQRLOG

Uso CQRLOG come log di stazione; un log elettronico molto versatile che consente anche varie estrazioni dati: lo ritengo un programma fantastico ed un "must" per chi usa Linux.

Tra le varie funzionalità esiste una fantastica "SQL Console", cioè la possibilità di impartire comandi SQL al database MySQL del software: un modo molto più sicuro di quello di accedere direttamente al DB, in quanto, una vota estratti i dati che servono, si possono salvare in un comodo file "CSV" e lavorare su di esso, senza possibilità di rovinare la base dati del software.

Per agevolare l'accesso ai (tantissimi) dati a disposizione, i progettisti di CQRLOG hanno predisposto una "vista" sul Db: pertanto, interrogheremo la "vista" e non direttamente le tabelle. La vista si chiama "view_cqrlog_main_by_qsodate" e, per interrogarla, basta scrivere nella casella in alto della SQL Console il rigo che segue:

select * from view_cqrlog_main_by_qsodate where eqsl_qsl_rcvd = 'E';

Questa istruzione troverà nella "vista" tutti i QSO che sono stati confermati mediante E-QSL e mostrerà tutti i campi disponibili, da cui noi sceglieremo solo quelli che ci servono per richiedere i file grafici al sito di eqsl.cc; in pratica, la finestra della QSL Console di CQRLOG si presenta nel modo che segue:

La SQL Console di CQRLog

La finestra è raggiungibile cliccando "QSO List" nel menu "Window" della finestra principale e, da qui, la voce "SQL Console" del menu "Filter"; nella finestra della SQL Console, la freccina verde esegue il comando, la cartella gialla col foglio carica una comando SQL da file, il disco lo salva su un file e, infine, l'icona con il grafo rosso salva i dati in un file "CSV" (di quelli che possiamo importare in un foglio di calcolo, per intenderci).

Ora non resta che ottimizzare la query SQL e salvarla per usi successivi. Scriveremo un comando come quello che segue, che preleva esclusivamente i dati che servono per automatizzare il nostro processo di scarico delle QSL elettroniche:

select callsign, DATE_FORMAT(qsodate, "%Y%m%d")qso_date, time_on,
freq, band, mode, CONCAT('"', country, '"') as country,
DXCC_REF, CONT
from view_cqrlog_main_by_qsodate
where eqsl_qsl_rcvd = 'E'

Cliccando la freccina verde la query SQL viene eseguita ed i dati sono estratti; cliccando l'icona con il grafo rosso, viene salvato un file che, per comodità, chiameremo "estratto_qso_confermati_eqsl.csv".

Ora non resta che scrivere un programma che, leggendo rigo per rigo il file CSV, prenda i dati salvati, li salvi in una lista interna, crei il nome del file da salvare, predisponga i PATH dove salvare i file (ricordate? Per continente e entità), verifichi le QSL già salvate e dia una giusta pausa tra un salvataggio e l'altro per evitare il sovraccarico del server... Semplice, no?

Lo script in Perl

Uno script di questo tipo può essere realizzato in molti linguaggi, a seconda delle proprie preferenze e a seconda di cosa si è installato sul proprio PC; ad esempio, si può usare l'onnipresente shell o anche il gradito (da molti) Python... personalmente, preferisco Perl, un linguaggio estremamente versatile ed in grado di fare da ottimo collante tra i vari comandi di Linux, tra cui quelli prima spiegati curl e wget.

Spiegherò, nel proseguo, passo per passo, lo script realizzato che potrà essere scaricato da questo sito internet; lo script non è molto lungo (appena 152 righe compresi i commenti) ma spiegandolo bene può essere un invito allo studio di questo interessante linguaggio di programmazione. Allora, cominciamo...

#!/usr/bin/perl
use warnings ;
use strict ;

Qui indico allo script dove reperire l'eseguibile Perl e due moduli per il controllo degli eventuali errori di programmazione.

my $Quante_qsl = $ARGV[0] ;
if (!$Quante_qsl) {
   $Quante_qsl = 15 ;
}

In questo caso leggo il numero di righe del file da processare, passandolo come parametro subito dopo l'invocazione dello script da riga di comando; se non lo passo, è fissato di default a 15. Ho preferito contare le righe del log e non le QSL scaricate: in pratica, se il numero è inferiore alle righe processate ed il file non ha subito modifiche, non scaricherò nessuna nuova QSL.

my $File_log = './estratto_qso_confermati_eqsl.csv' ;
my $Path_eqsl = '/home/ik7xja/Perl_RBN/eqsl-cards' ;
my $Utente = 'ik7xja' ;
my $Pass = 'mia_password' ;

Qui passo alcuni parametri del programma: il nome del file CSV estratto da CQRLOG, la directory dove salvare i file, il mio nominativo e la password di accesso a eqsl.cc; tutte le righe oltre questi parametri non dovrebbero essere interessate da modifiche di routine.

undef my @Lista_qso_confermati ;
undef my %QSL_scaricate ;
my ($Err, $Descerrore) = (0, '') ;

Dichiarazione di alcune variabili globali nel programma: definendole in testa allo script, avranno valore nell'intero processo.

&Trova_scaricate() ;

Qui viene richiamata la funzione che effettua il riscontro delle QSL già salvate sul disco in quanto è possibile bloccare il programma ed eseguire il download in momenti diversi. Il rigo richiama una funzione scritta più avanti che mostro qui di seguito:

sub Trova_scaricate {
   my $Comando = qq{find $Path_eqsl -name "*.jpg" -exec basename \{\} \\\;} ;
   my $Res = qx{$Comando} ;
   my @Righe = split /\n/, $Res ;
   foreach my $R(@Righe) {
      $QSL_scaricate{$R} = 1 ;
   }
}

In pratica questa funzione lancia il comando Linux "find" che trova, in questo caso, tutti i file "JPG" salvati nel percorso specificato dal parametro globale "$Path_eqsl"; per ogni file trovato, find esegue il comando "basename" che restituisce il file senza il percorso (che sarebbe "Continente/Entità", per rendere agevole il ritrovamento "manuale" delle cartoline). Il comando viene salvato nella variabile "$Comando" che viene eseguita dalla notazione "qx{$Comando}". Le righe ottenute sono caricate in un Array, dividendole mediante il carattere "\n" (nuova linea); infine, ogni riga dell'array contente il file immagine che è stato salvato sul mio disco, viene memorizzato come indice in un HASH. L'Hash, in Perl, è una struttura dati indicizzata costituita da due elementi di tipo "chiave-valore" e permette un accesso velocissimo ai suoi elementi. In questo caso il campo "valore" non ha nessuna importanza pertanto è settato, semplicemente, ad "1".

($Err, $Descerrore) = &carica_qso($File_log, $Quante_qsl) ;

Dopo aver trovato le QSL scaricate, è necessario caricare in un Array i dati del QSO; gli Array sono tipi di dato paragonabili alle liste e, di solito, vanno lette in sequenza mediante i soliti cicli for, foreach e while. In questo caso, la funzione "carica_qso" accetta due parametri (il file di log ed il numero di righe da prendere dal log). Vediamola in dettaglio:

sub carica_qso {
   # Carico in LISTA solo i QSO confermati di cui
   # non ho ancora scaricato la cartolina e-qsl.
   my ($File_csv, $Quante) = @_ ;
   my ($Err, $Descerrore) = (0, '') ;
   my $Righe_totali = 0 ;
   if (-e $File_csv) {
      open(IN, "< $File_csv") || ($Err = 1);
      if ($Err) {
         $Descerrore = 'Impossibile leggere il file NOT001GS.CSV' ;
      } else {
         while (<IN>) {
            $Righe_totali++ ;
            $_ =~ s/\r//g ;
            $_ =~ s/\n//g ;
            $_ =~ s/"//g ;
            $_ =~ tr/[\x0D,\x20-\x7E]//dc ;
            # Tracciato:
            # callsign;qso_date;time_on;freq;band;mode;country;dxcc_ref;cont;
            # DL8AWK;20201022;12:16;10.115;30M;CW;"Germany";DL;EU;
            if ($_ =~ /^callsign/) {
               next ;
            }
            my ($callsign, $qso_date, $time_on, $freq,
                $band, $mode, $country, $dxcc_ref,
                $cont) = split /;/, $_ ;
            my $Card_name = join('_', $callsign, $qso_date, $band, "$mode.jpg") ;
            $Card_name =~ s/\//\-/g ;
            if (!defined($QSL_scaricate{$Card_name})) {
               if ($dxcc_ref =~ /\(/) {
                  $dxcc_ref = (split /\(/, $dxcc_ref)[0] ;
               }
               my ($Anno, $Mese, $Giorno) = unpack("A4 A2 A2", $qso_date) ;
               my ($Ora, $Min) = split /\:/, $time_on ;
               my $Rec = join(';', $callsign, $Anno, $Mese,
                                   $Giorno, $Ora, $Min, $band,
                                   $mode, $Card_name, $dxcc_ref, $cont) ;
               push(@Lista_qso_confermati, $Rec) ;
            }
            last if ($Righe_totali > $Quante) ;
         }
         close(IN) ;
      }
   } else {
      ($Err, $Descerrore) = (1, 'File di log non trovato') ;
   }
   return ($Err, $Descerrore) ;
}

In pratica questa funzione non fa altro che recuperare i parametri passati, restituire l'eventuale errore e la sua descrizione, aprire il file, leggere ogni singolo rigo saltando quello che comincia per "callsign", comporre il nome del file della QSL secondo i dati del QSO (callsign, data, banda e modo), verificare che non sia stata già scaricata ( if (!defined($QSL_scaricate{$Card_name})) { ), estrarre i dati del giorno e dell'ora del QSO, comporre un nuovo record con il comando "join" separando i campi con ";" e comprendendo anche il nome della QSL. Inoltre, se il numero delle righe processate è maggiore de numero di righe impostate per il recupero, esce dal ciclo. Come si vede, la variabile $Righe_totali viene incrementata ad ogni rigo a prescindere se la QSL sia stata o meno già salvata: in questo modo il numero delle QSL da salvare deve essere superiore a quelle già salvate in modo da salvare quelle "in eccedenza".

Una volta aver letto il file, tutti dati dei QSO da processare sono caricati nell'Array "@Lista_qso_confermati" e il rigo:

my $Da_scaricare = @Lista_qso_confermati ;

verifica proprio che ci siano elementi al suo interno; se non ce ne sono, (la seconda parte dell'IF) viene stampato a video il seguente messaggio:

print "Gia` confermati i $Quante_qsl QSO richiesti... mi fermo...\n" ;

dopo di ciò, il programma viene terminato e il comando "exit;" conferma l'uscita. Pertanto, tutto il recupero è concentrato della prima parte dell'IF, che analizziamo, nel dettaglio, di seguito.

my $Confermati = $Quante_qsl - $Da_scaricare ;

Viene calcolata la differenza tra $Quante_qsl (quelle della richiesta da processare) e quelle totali trovate, in modo che sia mostrato un contatore che indichi all'utente a che punto si trova lo scaricamento. Successivamente, si procede analizzando, in un ciclo "foreach" ogni singola riga ($L), dividendolo nei campi mediante il comando "split":

my ($callsign, $Anno, $Mese,
    $Giorno, $Ora, $Min, $band,
    $mode, $Card_name, $dxcc_ref, $cont) = split /;/, $L ;

e, con il rigo:

print " $Confermati --> QSO $callsign del $Giorno/$Mese/$Anno in $band $mode:\n" ;

Viene stampata la riga a schermo per indicarmi cosa si sta processando.

if (-e "$Path_eqsl/$cont/$dxcc_ref") {
   print "  --> Il path $Path_eqsl/$cont/$dxcc_ref ESISTE\n" ;
} else {
   print "  --> Il path $Path_eqsl/$cont/$dxcc_ref NON ESISTE: lo creo\n" ;
   my $Comando = qq{mkdir -p $Path_eqsl/$cont/$dxcc_ref} ;
   my $r = qx{$Comando} ;
}

Questa IF si preoccupa di creare il percorso "continente/entità" dove salvare la QSL scaricata; per farlo, uso il comando Linux "mkdir -p" che crea un directory e, se manca, anche il suo genitore; per controllare se la directory esiste, uso "if (-e "$Path_eqsl/$cont/$dxcc_ref") {", dove "-e" testa l'esistenza di un file o di una directory (cartella) sul disco.

Dopo questo, ho tutto per lanciare la generazione del file grafico su eqsl.cc, mediante il comando già visto in precedenza ma, questa volta, con le variabili che sostituiscono i campi che avrei dovuto scrivere a mano. Il comando viene composto nella variabile $Comando ed eseguito mediante "qx{$Comando}":

my $Comando = qq{curl -s -d Username='$Utente'
     -d Password='$Pass' -d CallsignFrom='$callsign'
     -d QSOBand='$band' -d QSOMode='$mode' -d QSOYear='$Anno'
     -d QSOMonth='$Mese' -d QSODay='$Giorno' -d QSOHour='$Ora'
     -d QSOMinute='$Min' https://www.eqsl.cc/qslcard/GeteQSL.cfm} ;
my $Res = qx{$Comando} ;

Tutto il risultato (quella pagina HTML che abbiamo visto prima) viene memorizzata nella variabile $Res: ora è necessario dividerla rigo per rigo e analizzarne il contenuto per trovare quello che serve per scaricare il file grafico: in pratica, realizziamo un semplice "parser" delle righe HTML mediante una espressione regolare che controlla ogni singola riga:

foreach my $R(@Righe) {
    $R =~ s/^ +// ;
    if ($R =~ /^.+img src=\"([a-z0-9\-\_\/\.]+)\".+$/i) {

La riga con la "IF" è da leggersi: "se $R contiene img src, prendi tutto quello tra apici, ignorando il case dei caratteri"; in questo modo, se la riga corrisponde, viene salvato in "$1" l'immagine che sarà assegnata ad una variabile "$Image"; trovata l'immagine, viene data in pasto a "wget" che la recupera e la salva nella giusta locazione:

my $Url_image = 'https://www.eqsl.cc' . $Image ;
my $Wget_command = qq{wget -q $Url_image -O $Path_eqsl/$cont/$dxcc_ref/$Card_name} ;
# print "--> $Wget_command\n" ;
my $Result = qx{$Wget_command} ;
if (-e "$Path_eqsl/$cont/$dxcc_ref/$Card_name") {
   print "   Scaricata $Image salvata come $Card_name\n" ;
} else {
   print "   ERRORE impossibile salvare $Image come $Card_name\n" ;
}

Infine, il comando "last" esce dal ciclo.

La seconda parte della IF si occupa di testare eventuali errori: in questo caso, essi vengono indicati da eqsl.cc con una stringa che comincia per "Error", ed il tutto viene recuperato da una espressione regolare e salvato in un file nella cartella "ERROR"; il file, pur avendo il nome in ".jpg" non è un file immagine ma un file testo che contiene l'errore stesso. Si tratta di un "segnaposto" usato per impedire di continuare a scaricare un'immagine per cui si è già ottenuto un errore:

} elsif ($R =~ /ERROR: (.+)$/i) {
    my $Errore = $1 ;
    if (!(-e "$Path_eqsl/ERROR")) {
       my $Comando = qq{mkdir -p $Path_eqsl/ERROR} ;
       my $r = qx{$Comando} ;
    }
    my $Comando = qq{echo "$Errore" > $Path_eqsl/ERROR/$Card_name} ;
    my $r = qx{$Comando} ;
    last ;
}

Anche in questo caso "last" termina il ciclo.

Ma il comando più importante, assolutamente da non dimenticare, è il seguente:

print "...Mi fermo per 15 secondi... Control+C per interrompere ...\n\n" ;
sleep(15) ;

In questo caso, dopo aver salvato l'immagine (o un errore legato alla generazione della stessa) lo script si ferma per 15 secondi ("sleep(15)") in modo da limitare il carico sui sistemi.

In conclusione

Come sempre, questo articolo è una scusa per parlare di Linux e di programmazione applicata al nostro Hobby; niente di particolarmente complicato: come tutti i linguaggi "umani" anche quelli di programmazione di imparano ricopiando i comandi e verificando il risultato.

Con questo piccolo script, ho cercato di limitare la programmazione e di usare, ove necessario, i programmi della shell di Linux per automatizzare il processo di download e salvataggio delle QSL, in modo da usarle per un piccolo "slide-show" su questo mio sito internet, dove è anche possibile scaricare lo script già fatto.

Buona programmazione!

Valid XHTML 1.0 Transitional