Dunque, iniziamo dal problema:
In automatico si vuole salutare - con un "Notte <nickname>" - gli utenti che scrivono "buonanotte", "buona notte" o semplicemente "notte", tra le 22 e le 4, solo una volta al giorno, solo se l'utente locale non è AWAY e solo in determinati canali.
Con BuDuScRiPt è possibile fare questo sfruttando le sentinelle, i comandi condizionali e le funzioni avanzate.
Dunque, io ho risolto il problema con due sentinelle create con questi rispettivi comandi:
Codice: Seleziona tutto
/event add 0 text ^([Bb]uona($_)?)?[Nn]otte($_.*)?:#(buduscript|miocanale)='$data:$channel' /budus if $nick.away = 0 /budus if "$(date +%%H)" = /(2[23]|0[0-4]) /event event spcl_saluto_notte $event_params
/event add 0 spcl_saluto_notte * /event addh 1 spcl_$ru * /TIMER 3 SAY notte $ru_BUDUS_SEPARATOR_/TIMER 18000 event delh ^number\;/event event spcl_$ru $event_params
Scritte così possono sembrare incomprensibili ma, analizzate e "risolte" una per volta, vedrete che è un meccanismo abbastanza banale.
Iniziamo con la prima sentinella:
Codice: Seleziona tutto
0 text ^([Bb]uona($_)?)?[Nn]otte($_.*)?:#(buduscript|miocanale)='$data:$channel' /budus if $nick.away = 0 /budus if "$(date +%%H)" = /(2[23]|0[0-4]) /event event spcl_saluto_notte $event_params
Lo '0' iniziale rappresenta il numero di volte che la sentinella deve essere presa in esame al verificarsi dell'evento 'text' (il quale avviene quando un altro utente scrive); il valore '0' è speciale perché viene interpretato come "sempre". In altri termini: verifica la condizione della sentinella ogni volta che si verifica un evento di tipo text.
Ora c'è la condizione
Codice: Seleziona tutto
^([Bb]uona($_)?)?[Nn]otte($_.*)?:#(buduscript|miocanale)='$data:$channel'
E' probabilmente il pezzo più complicato perché contiene un espressione regolare il cui scopo è quello di soddisfare più condizioni possibili in due specifici canali (nel nostro esempio sono "#buduscript" e "#miocanale").
Come desidera il problema iniziale, la sentinella deve reagire quando viene scritto "buonanotte", "buona notte" o anche solo "notte", ma non solo: deve essere la prima cosa che viene scritta e dopo "notte" ci può essere qualsiasi altra cosa (a patto che sia separata da uno spazio).
L'espressione regolare che soddisfa tutte queste condizioni è:
Codice: Seleziona tutto
^([Bb]uona($_)?)?[Nn]otte($_.*)?che significa: la frase può iniziare con "buona" o "Buona" e, se presente, contenere anche uno spazio, quindi deve essere presente "notte" o "Notte" ed eventualmente uno spazio seguito da qualsiasi cosa.
Il carattere '^' indica che la frase deve partire da ciò che segue, i caratteri racchiusi tra '[' e ']' sono un elenco di ciò che il sistema dovrà aspettarsi, il '?' indica la possibilità che l'espressione precedente (contenuta tra parentesi tonde) sia presente una volta o non ci sia e il '$_' rappresenta il carattere di spazio.
Separata da ':' abbiamo una seconda espressione, il cui scopo è verificare che il canale nel quale è stato generato l'evento sia uno di quelli desiderati:
Codice: Seleziona tutto
#(buduscript|miocanale)Qui abbiamo sempre '(' e ')' che racchiudono un'espressione; in questo caso, viene usato '|' per separare due possibilità; volendo la si poteva scrivere anche così, era la stessa cosa:
Codice: Seleziona tutto
(#buduscript|#miocanale)avendo più canali da controllare potremmo usare:
Codice: Seleziona tutto
#(buduscript|miocanale1|miocanale2|miocanaleN)Ora che abbiamo analizzato l'espressione regolare, dobbiamo analizzare la parte a destra della condizione:
Codice: Seleziona tutto
'$data:$channel'la quale è costituita da due variabili dove:
- $data sarà sostituita da ciò che scrive l'utente.
- $channel sarà sostituita dal nome del canale nel quale l'utente scrive.
Codice: Seleziona tutto
notte a tutti quanti:#buduscriptCon il programma udaregexp, fornito in dotazione a BuDuScRiPt e presente nella cartella .xchat2/buduscript possiamo testare la condizione in questo modo:
Codice: Seleziona tutto
./udaregexp '^([Bb]uona($_)?)?[Nn]otte($_.*)?:#(buduscript|miocanale)' 'notte a tutti quanti:#buduscript'
il cui risultato sarà questo:
Codice: Seleziona tutto
regexp ^([Bb]uona( )?)?[Nn]otte( .*)?:#(buduscript|miocanale) on: notte a tutti quanti:#buduscript = 1
E' un risultato confortante - se avesse scritto "= 0" significava che l'espressione non era soddisfatta - perché ci conferma che la condizione lavora come previsto, anzi, sarà opportuno sfruttare udaregexp per fare ulteriori tentativi e capire se tutto funziona come ci aspettiamo (con le espressioni regolari è facile "cadere in buca").
Bene, ora dedichiamoci all'azione della sentinella:
Codice: Seleziona tutto
/budus if $nick.away = 0 /budus if "$(date +%%H)" = /(2[23]|0[0-4]) /event event spcl_saluto_notte $event_params
quando viene inserita, automaticamente diventa così:
Codice: Seleziona tutto
/budus if $nick.away = 0 /budus if "$(date +%%H)" = /(2[23]|0[0-4]) /event event spcl_saluto_notte $lu $ru $rhost $channel $server $data
perché la variabile "$event_params" viene sostituita con "$lu $ru $rhost $channel $server $data" (è solo una facilitazione dello script).
Abbiamo che "$nick", "$lu", "$ru", "$rhost", "$channel", "$server" e "$data" sono tutte variabili che, a condizione soddisfatta, vengono risolte prima di eseguire il comando.
In particolare "$nick" assume il nickname dell'utente locale, "$lu" il nick di chi riceve l'azione (in questo caso combacerà sempre con il nick dell'utente locale), "$ru" il nick di chi ha generato l'evento (e quindi di chi ha scritto la frase), "$rhost" è l'hostname di quest'ultimo, "$channel" il nome della sessione dove l'evento è stato generato così come "$server" è il nome del relativo server ed, infine, "$data" contiene ciò che l'utente ha scritto.
Se risolviamo - parzialmente - questo comando, supponendo che l'utente locale sia "pluto" e quello che ha scritto sia "pippo" con la frase "notte a tutti quanti" (host, canale e server pure loro sono "di fantasia") otteniamo:
Codice: Seleziona tutto
/budus if pluto.away = 0 /budus if "$(date +%%H)" = /(2[23]|0[0-4]) /event event spcl_saluto_notte pluto pippo pippo@disney.com #buduscript irc.unserver.org notte a tutti quanti
che significa:
Se (if) l'utente pluto non risulta AWAY (pluto.away = 0) esegui /budus if "$(date +%%H)" = /(2[23]|0[0-4])
Ciò che viene eseguito è un'ulteriore condizione che deve verificare se l'orario è quello corretto (più avanti spiego come funziona); se l'orario viene soddisfatto abbiamo l'esecuzione di questo comando:
Codice: Seleziona tutto
/event event spcl_saluto_notte pluto pippo pippo@disney.com #buduscript irc.unserver.org notte a tutti quanti
e serve per generare un evento; nel nostro caso viene generato un evento di tipo spcl_saluto_notte e gli passa i 6 parametri standard delle sentinelle.
Abbiamo per un attimo tralasciato il controllo dell'orario; affrontiamolo ora:
Codice: Seleziona tutto
/budus if "$(date +%%H)" = /(2[23]|0[0-4]) /budus ...
Qui abbiamo un "comando condizionale" che significa:
Per capire cosa viene restituito dal comando "date +%H" invito a sperimentarlo in un terminale; nel momento in cui scrivo mi ha scritto:
- Esegui in un terminale (invisibile) del sistema operativo - e questo avviene perché usiamo la sintassi "$( .... )" - il comando "date +%H" (lo so, è sparito un '%', ciò avviene per una questione di interpretazione dei caratteri speciali da parte di X-Chat, in altri termini: quando si deve usare '%' in X-Chat, bisogna metterne 2)
- Il risultato confrontalo con l'espressione regolare (dato che la parte a destra dell'uguale inizia con '/') "(2[23]|0[0-4])".
Codice: Seleziona tutto
uda@localhost ~ $ date +%H
16
e questo perché, mentre sto scrivendo, sono le ore 16:12:00.
Se provassi a verificare se questo risultato soddisfa l'espressione regolare (con udaregexp) otterrei:Codice: Seleziona tutto
./udaregexp '(2[23]|0[0-4])' '16'
regexp (2[23]|0[0-4]) on: 16 = 0
e questo significa che non sarà soddisfatta; immaginando che il comando "date" lo do alle 23, ottengo:Codice: Seleziona tutto
./udaregexp '(2[23]|0[0-4])' '23'
regexp (2[23]|0[0-4]) on: 23 = 1
e questo significa che il comando sarà eseguito solo se l'ora corrente è compresa tra le 22 e le 04, dato che l'espressione regolare significa: deve esserci un numero che inizia con 2 e poi un 2 o un 3, oppure, un numero che inizia con 0 e poi un altro numero che va da 0 a 4 (quindi 0, 1, 2, 3 e 4). In altri termini l'espressione è vera solo se l'orario combacia con uno dei seguenti valori: 22, 23, 00, 01, 02, 03 o 04.
Ora, immaginando che il tutto sia avvenuto alle 23, ne consegue che entra in gioco la seconda sentinella che abbiamo inizialmente creato, la ripeto:
Codice: Seleziona tutto
0 spcl_saluto_notte * /event addh 1 spcl_$ru * /TIMER 3 SAY notte $ru_BUDUS_SEPARATOR_/TIMER 18000 event delh ^number\;/event event spcl_$ru $lu $ru $rhost $channel $server $data
dove:
Codice: Seleziona tutto
0 spcl_saluto_notte * /event ...
rappresenta:
Ogni volta (parametro '0') che viene generato un evento di tipo spcl_saluto_notte esegui il comando /event ...
Qui la condizione sostanzialmente non c'è perché, avendo usato '*', risulta "sempre vera".
In altri termini: nel momento in cui viene generato l'evento "spcl_saluto_notte" (e ciò si verifica solo se l'utente locale non è away e viene scritta "la frase magica" da qualcuno) esegui un determinato comando.
Il comando in questione è:
Codice: Seleziona tutto
/event addh 1 spcl_$ru * /TIMER 3 SAY notte $ru_BUDUS_SEPARATOR_/TIMER 18000 event delh ^number\;/event event spcl_$ru $lu $ru $rhost $channel $server $datascritto così può risultare ancora criptico ma, scomponendolo, diventa molto più facile capire cosa fa. Iniziamo con il risolverlo:
Codice: Seleziona tutto
/event addh 1 spcl_pippo * /TIMER 3 SAY notte pippo_BUDUS_SEPARATOR_/TIMER 18000 event delh $number\;/event event spcl_pippo pluto pippo pippo@disney.com #buduscript irc.unserver.org notte a tutti quanti
Notare come "^number" sia stato risolto in "$number" e che, con le sentinelle, la sequenza '\;' indica "comandi multipli".
Trattandosi di un comando multiplo, prendiamoli in esame separatamente per semplicità, partendo dal primo che verrà eseguito:
Codice: Seleziona tutto
/TIMER 3 SAY notte pippo_BUDUS_SEPARATOR_/TIMER 18000 event delh $number
verrà risolto in questo modo:
Codice: Seleziona tutto
/event addh 1 spcl_pippo * /TIMER 3 SAY notte pippo\;/TIMER 18000 event delh $number
Notare come "_BUDUS_SEPARATOR_" venga sostituito da '\;' e il comando risultante sia a sua volta l'inserimento di una sentinella con azioni multiple.
Il significato di questo comando è:
Aggiungi una sentinella nascosta (addh, ovvero "add hide") che dovrà essere valutata una sola volta (parametro "1") nel caso in cui sia stato generato l'evento spcl_pippo (notare che il nome di questo evento contiene il nickname dell'utente) e, senza che ci sia condizione (parametro '*'), esegui il comando /TIMER 3 SAY notte pippo (che, a sua volta, significa: dopo 3 secondi scrivi - comando "SAY" - nel canale la frase "notte pippo").
Dopodiché devi eseguire il comando /TIMER 18000 event delh $number successivamente alla risoluzione della variabile "$number", che deve diventare il numero della sentinella stessa.
In realtà questo comando non è detto venga realmente eseguito: se è già stato eseguito in precedenza, la sentinella esiste già e - per via di una delle regole delle sentinelle, che non permette l'inserimento di più sentinelle identiche - non sarà ulteriormente inserita. Questo determina che il "saluto al nostro utente pippo" può avvenire una sola volta, almeno finché esiste questa sentinella.
Il secondo sotto-comando:
Codice: Seleziona tutto
/TIMER 18000 event delh $number
potrebbe essere risolto in una cosa del tipo:
Codice: Seleziona tutto
/TIMER 18000 event delh 5
e significa:
Dopo 18000 secondi (cioè 5 ore) elimina la sentinella nascosta (event delh) corrispondente al numero "5".
Il "5" dipenderà dal numero che la sentinella avrà nel momento in cui viene inserita; dato che non è possibile stabilirlo a priori, ecco che viene usata la variabile "$number", risolta dallo script al momento giusto.
Eliminare la sentinella dopo 5 ore comporta che, quando il medesimo utente darà nuovamente la "buonanotte", tutto il meccanismo ripartirà dall'esecuzione di questo comando:
Codice: Seleziona tutto
/event addh 1 spcl_$ru * /TIMER 3 SAY notte $ru_BUDUS_SEPARATOR_/TIMER 18000 event delh ^number\;/event event spcl_$ru $lu $ru $rhost $channel $server $data
e tutto quello che ne consegue (vedi sopra).
Aldilà dell'interpretazione delle espressioni regolari (argomento ostico, ma che si comprende facendo delle prove, quindi invito a sbizzarrirsi con udaregexp e chiedere qui sul forum se qualcosa non è chiaro) sembra che un altro aspetto poco chiaro delle sentinelle siano gli "eventi speciali".
Per chiarirlo, analizziamo i seguenti punti:
- Quando in IRC avviene qualcosa (qualcuno entra in canale, qualcuno scrive, un KICK, ecc...) BuDuScRiPt genera un evento.
- Un evento ha un nome e può essere uno di quelli standrard (jchannel, text, mode, ecc...) o speciale (spcl_).
- Gli eventi generati da BuDuScRiPt possono essere di 2 tipi: standard (auto-generati, come quelli del punto precedente) o speciali (generati dall'utente)
- L'utente ha facoltà di generare sia eventi standard che speciali; ad esempio questo comporta che l'utente può anche simulare che sia entrato un utente nel canale semplicemente generando un evento di tipo jchannel. Allo stesso modo può generare un evento speciale, con un "nome di fantasia" che inizi con "spcl_".
- Indipendentemente dalla tipologia, non è possibile inserire più volte la stessa identica sentinella.
In altri termini:
- Se entra un utente lo script genera un evento jchannel
- Se un utente scrive qualcosa, lo script genera un evento text
- ecc...
E' possibile generare questi eventi anche se "non è entrato nessuno" o "nessuno ha scritto", semplicemente con:
Codice: Seleziona tutto
/event event jchannel nick_utente_locale nick_di_chi_entra host_di_chi_entra #nomecanale nomersrver -
/event event text nick_utente_locale nick_di_scrive host_di_chi_scrive #nomecanale nomersrver testo
Sì, hai capito bene, gli eventi che normalmente genera BuDuScRiPt in risposta a qualcosa che è successo su IRC li puoi generare in "modo forzato".
Generare eventi standard raramente ha utilità, se non quando si vuole testare una sentinella.
Generare eventi speciali ha invece una grossa utilità perché:
- Consente di "concatenare sentinelle", così da avere condizioni multiple; se ancora non lo si è notato, è sostanzialmente possibile avere una sola condizione in una sentinella, non è come nei linguaggi di programmazione dove si hanno cose del tipo "espressione1 = espressione2 && espressione3 = espressione4".
- Fare in modo che una sentinella ne generi un'altra e, successivamente, crei l'evento che la soddisfa; impostando opportunamente il numero di ripetizioni della sentinella creata è così possibile fare in modo che l'azione di quest'ultima sia eseguita solo un numero finito di volte ed, essendo il nome dell'evento gestito personalizzabile (ad esempio con il nickname dell'utente coinvolto), per una determinata situazione.
Ad esempio, si prenda in considerazione questa sentinella:
Codice: Seleziona tutto
/event add 0 text '$nick'!'$ru' /event add 3 spcl_$ru * /ECHO $ru ha scritto qualcosa (^data)\;/event event spcl_$ru $event_params
supponendo che a scrivere - la scritta "ciao" - sia sempre il solito "pippo", la sentinella si risolve così:
Codice: Seleziona tutto
0 text 'pluto'!'pippo' /event add 3 spcl_pippo * /ECHO pippo ha scritto qualcosa ($data)\;/event event spcl_pippo pluto pippo pippo@disney.com #buduscript irc.unserver.org ciao
L'evento (text) viene preso in considerazione sempre (0) e dato che la condizione "'pluto'!'pippo'" viene soddisfatta (pluto è diverso da pippo) vengono eseguiti questi due comandi:
Codice: Seleziona tutto
/event add 3 spcl_pippo * /ECHO pippo ha scritto qualcosa ($data)
/event event spcl_pippo pluto pippo pippo@disney.com #buduscript irc.unserver.org ciao
Il primo aggiunge la sentinella - se già non è stata inserita - che reagirà per 3 volte all'evento "spcl_pippo" eseguendo il comando:
Codice: Seleziona tutto
/ECHO pippo ha scritto qualcosa ($data)
Ciò avverrà sempre perché la condizione (*) è sempre vera.
Il secondo genera l'evento "spcl_pippo"; notare che l'unica possibilità che questo evento sia generato è che sia l'utente stesso, in qualche modo, a generarlo, lo script - di suo - genera solo "eventi standard".
Il risultato sarà:
Codice: Seleziona tutto
pippo ha scritto qualcosa (ciao)
perché la variabile "$data" sarà risolta; notare che, inizialmente, si è usato "^data" in modo che non fosse risolta in quel contesto.
Quanto esposto sopra è sufficiente per implementare sentinelle anche piuttosto complicate. Il mio consiglio è di:
- Provare, anche se non si è ben capito come il tutto funziona; provando, poco per volta, e leggendo la guida quando "si ha in mente una domanda precisa" aiuta molto.
- Non partire da sentinelle complicate: iniziare da un "problema semplice" o comunque semplificando quello che poi si potrà complicare quando già si ha qualcosa che funziona come previsto.
- Sfruttare udaregexp per analizzare le condizioni di una sentinella, anche quando non si usano le espressioni regolari.
- Utilizzare /budus eventdebug 2 per avere dei messaggi di aiuto quando si creano sentinelle; consiglio di stare in un solo canale, dove non c'è nessuno, creare la sentinella e generare l'evento che la fa reagire.
Da notare che "eventdebug" fa in modo che lo script reagisca anche a ciò che scrive l'utente locale, cosa che normalmente non avviene. - Sfruttare il comando "/ECHO" per ottenere informazioni su come vengono risolte le variabili, ad esempio:
Codice: Seleziona tutto
/event add 0 text * /ECHO $lu $ru $rhost $channel $server $data - Usare il forum per fare domande !
Nella speranza che questa guida vi sia di aiuto, vi saluto e auguro Buona Chat con BuDuScRit !