Tag: python

  • Spotify puzzles: round two

    Some months ago, I began challenging myself with Spotify puzzles: at that time I was dealing with an easy problem; now, the difficulty has increased. The round two consists in the typical “Selection problem“: given an array of values, find the max (or min) k values. I decided to still use Python and to use its heapq module to store values in a binary max-heap data structure, and then remove exactly k values from the top of the heap. This approach will guarantee that the total time complexity of the algorithm will be O(n log k).

    The heapq module implements a min-heap data structure (as happens in Java). In order to use a max-heap, I specified a custom comparator for the Song class I wrote (remember: Python3 deprecates __cmp__ method, so I resort to implement __lt__ and __eq__ methods to specify a custom comparison algorithm):

    # If two songs have the same quality, give precedence to the one
    # appearing first on the album (presumably there was a reason for the
    # producers to put that song before the other).
    def __lt__(self, other):
        if self.quality == other.quality:
            return self.index < other.index
        else:
            # heapq is a min-heap, so a song is lt than another if it has
            # a greater quality (inverted!)
            return self.quality > other.quality
    
    def __eq__(self, other):
        return self.quality == other.quality and \
               self.index == other.index
    

    As always, I used py.test to automatically test my solution against test-cases.

    pytest

    In the end, the honorable (automated) judge confirms the solution:

    spotify_zipfsong

    Code has been published on GitHub.

  • Fun with Python powered telnetd honeypot

    Reason: hardening, serendipity and curiosity

    As you already know, in the past weeks I hardened all of my boxes: while doing it, I flushed all iptables/ipfw rules, changed the default policy to DROP and take it from there to enable every rule as soon as I need it. Whilst Ubuntu uses ufw as a fronted for iptables, Fedora uses firewalld.

    After setting up UFW, by looking at journalctl I can see all rejected connections, like:

    Sep 03 11:54:16 -- kernel: [UFW BLOCK] IN=eth0 OUT= MAC=XX SRC=XX DST=XX LEN=60 TOS=0x00 PREC=0x00 TTL=51 ID=24286 DF PROTO=TCP SPT=36864 DPT=23 WINDOW=5808 RES=0x00 SYN URGP=0
    

    As we all know, Internet is a wild place. In this particular case, someone was trying to connect to my box on port 23/tcp (telnetd, is still there someone who uses it?).

    My curiosity begin to increase. Let’s have a look on which ports I rejected most of the connections:

    /var/log # zgrep -ho "DPT=\w*" syslog* | cut -d "=" -f 2 | sort | uniq -c | sort -nr | head -n 10
        # no. of attempts | port
       1009 23
        969 3128
        330 22
        248 8080
        168 80
        158 5060
        151 3389
        111 1825
         94 1433
         77 123
    

    (as I am running an httpd-ssl server on this host, connections to 443/tcp port are not showed).

    Idea

    Ok, so mostly of the wild connectors out there try to telnetd to my host (and all other hosts on the Internet). Again, my curiosity prevails: how about a fake telnetd server written in Python with twisted? As I always wanted to have a look with twisted, seems a good idea. The main idea here is to write a simple telnetd server that:

    • shows the root prompt and waits for commands
    • answers with fake answers to some UNIX commands (like uname and id)
    • answers to all other commands with Command not found or with nothing (like that the command was accepted). The choice between answering in the former way rather than the latter is random
    • record everything that is written by the attacker

    This is a very simple and diminished concept of an Honeypot and leads to the code I published the code on GitHub under telnetd_honeypot.

    Just for the sake of increased security, I did not want to run this program as root (not because I do not trust it, just because it’s a bad practice!). But we need to run it as root, because we need superuser powers to open a listening socket on all ports <1024, especially 23/tcp.

    Sounds like a dead end? Not at all. Meet rinetd. We set up a bounce redirection from 23/tcp to the port on which our basic honeypot is listening (8023/tcp), launched by an unprivileged user (again: for security. Feel free to spin up a Docker container just to launch the honeypot).

    Demo

    % python telnetd.py &
    [1] 15171
    % telnet localhost 8023
    Trying 127.0.0.1...
    Connected to localhost.
    Escape character is '^]'.
    / # uname -a
    Linux f001 3.13.3-7-high-octane-fueled #3000-LPG SMPx4 Fri Jun 31 25:24:23 UTC 2200 x86_64 x64_86 x13_37 GNU/Linux
    / # id
    uid=0(root) gid=0(root) groups=0(root)
    / # ls
    / # ps auxw
    bash: ps: command not found

    Results

    The results are quite fascinating. By analizing our file produced by our honeypot runned just for 3 days we can see the most typed commands on the other side of the client:

    ~ % cut -d " " -f 6- telnetd_honeypot.log | sort | uniq -c | sort -nr | head -n 3  
    921 sh
    918 /bin/busybox ZORRO
    879 root
    

    We might notice that most of the lines are common passwords guessing, while the busybox invocation with the parameter ZORRO is mysterious: even Googling, I have no clues about it. Sounds like a custom firmware? There are also multiple attempt to inject a shellcode (which I am not publishing for obvious reasons).

    There are also funny entries:

    08/20/2015 12:43:10 AM INFO: 79.x.x.x knock knock neo
    

    Anyone else has seen Neo?

    The results are pretty interesting and deserve a further investigation. Upon running just for 3 days, I gather enough results to satisfy my curiosity. The honeypot described here is just a starting point, hoping to gather more contributors to this fascinating problem. With the addition of Docker containers, the future possibilities are quite infinite. Happy investigating!

  • Formattare i decimali con Python

    Un problema che ho recentemente risolto usando Python e la logica binaria prevedeva di stampare i numeri binari usando lo stesso numero di cifre [ad esempio: nel caso di 8 bit, stampare le parole di 4 bit anteponendo zero per quattro volte]. Tecnicamente, gli zero a sinistra sono ininfluenti ma servono per uniformare la formattazione, e in gergo sono chiamati leading zeros.
    Questo ragionamento non vale solo per i numeri in binario, ma per tutti i casi in cui si vuole formattare l’output di Python.

    Veniamo al codice. Si può formattare la stringa tramite format, specificando il numero di leading zeros.
    Esempio:

    for i in range(1,101,1):
        print(format(i, "03d"))
    
    

    In questo caso vengono stampati i numeri usando 3 cifre: si parte quindi da 000 e si arriva a 100. E in più, abbiamo usato una sintassi già compatibile con Python3.

  • django: come fare il deploy di un’applicazione su Apache

    Una volta che avete terminato lo sviluppo di un’applicazione basata su django, è il momento di installarla in produzione. Nel mio caso, ho utilizzato django 1.3.1 e ho scelto di utilizzare Apache e mod_wsgi. Vediamo come fare il deploy passo-passo:

    • Fortunatamente mod_wsgi richiede Apache mpm-worker (anziché il meno performante prefork) che su Debian/Ubuntu è facilmente installabile tramite sudo apt-get install libapache2-mod-wsgi
    • Spostiamo l’applicazione sotto un path di sistema, tipo /usr/share/app
    • Aggiungere a /etc/httpd.conf la direttiva WSGIScriptAlias / /usr/share/app/django.wsgi dove il primo parametro (/) rappresenta il path a cui django dovrà rispondere (in questo caso la root), mentre il secondo parametro rappresenta un file che scriveremo nel prossimo step, e che descrive a WSGI come interpretare l’applicazione django che abbiamo scritto
    • Aprite il file che abbiamo definito al punto precedente (django.wsgi) e inserite il seguente codice Python:
      “`
      import os
      import sys
      import django.core.handlers.wsgipath = '/usr/share/app'
      if path not in sys.path:
      sys.path.append(path)

      os.environ['DJANGO_SETTINGS_MODULE'] = 'app.settings'

      application = django.core.handlers.wsgi.WSGIHandler()
      “`

      Ricordatevi di settare correttamente il path e i settings (che corrisponde al settings.py di django)

    • Riavviare apache: service apache2 restart

    Aprite il browser andando a settare l’URL in corrispondenza di quanto definito al punto 1 di questo tutorial (il context-path) e controllate che sia tutto a posto (diversamente, controllate l’error log di Apache).
    Una soluzione aggiuntiva sarebbe quella di introdurre un reverse proxy (scelta personale: nginx) davanti ad Apache per servire i files statici (js, css, png, etc.). Ma questo sarà un altro post.

  • Sviluppare applicazioni web con Django: la mia recensione

    Spinto dalla curiosità, mi sono deciso ad imparare il framework Django, basato su Python, per sviluppare applicazioni web. In particolare, mi sono procurato il libro di Marco Beri “Sviluppare applicazioni web con Django”, edito da Apogeo. Comincio subito col dire che il prezzo è decisamente alto (32 Euro), nonostante il libro sia del 2009.

    Il libro svolge un ottimo ruolo introduttivo alle caratteristiche di Django, presentando i concetti fondamentali, tra cui:

    • ORM integrato
    • i modelli
    • i form
    • i template

    Il tutto viene presentato con semplici esempi con cui è possibile capire come il framework può essere sfruttato per sviluppare velocemente un’applicazione web.

    Seppure ben scritto, non rappresenta un sostitutivo alla documentazione ufficiale di Django: per sviluppare efficientemente un’applicazione web ho letto l’ampia ed estesa documentazione (in inglese) che mi ha aiutato a capire meglio come sviluppare meglio l’applicazione facendomi aiutare dal framework; ovviamente non è un demerito del libro, proprio perché il libro è del 2009, mentre la documentazione è aggiornata all’ultima release (attualmente la versione 1.3.1 è marcata come production-ready).

    In definitiva: se vi serve un’infarinatura su Django, comprate il libro. Altrimenti, consultate la documentazione online.

  • Python: scriviamo un generatore di Fibonacci ricorsivo (e poi iterativo) ed analizziamone la complessità asintotica

    Tralasciando la parte matematica su cui potete trovare un’esauriente (ed interessante) spiegazione su Wikipedia, la serie di Fibonacci si definisce:

    F(n) = F(n-1) + F(n-2)

    per n > 1, e F(1) = 1 e F(0) = 0

    Scriviamo un generatore di questa serie in Python:

    #!/usr/bin/python
    
    # fib(n) = fib(n-1)+fib(n-2)
    # fib(0) = 0
    # fib(1) = 1
    # fib(2) = 1
    # fib(3) = 2
    # fib(4) = 3
    # fib(5) = 5
    # fib(6) = 8
    # ...
    
    def fib(n):
        if n == 0:
            return 0;
        if n == 1:
            return 1;
        else:
            return fib(n-1) + fib(n-2)
    

    Abbiamo utilizzato un’implementazione ricorsiva; il vero problema di quest’implementazione è subito chiaro: la complessità O(n^2) data dalla ricorsione e dalle tail-call sullo stack (per n molto grande).
    Decidiamo allora di scriverne una versione equivalente ma iterativa, in modo da eliminare il problema della complessità:

    def fibnr(n):
        fn1 = 1;
        fn2 = 0;
        fib = 0;
        i = 2;
        if n == 0:
             return 0;
        if n == 1:
             return 1;
    
        while (i <= n):
            fib = fn1 + fn2
            tmp = fn1
            fn1 = fib
            fn2 = tmp
            i += 1
        return fib
    

    La vera complessità questa volta, sta nella testa del programmatore: è molto importante, infatti, scrivere nel modo più preciso possibile il corpo del while. Inoltre, è necessario implementare uno swap di variabile per tenere traccia della storicità della serie (per semplicità qui non ho usato lo swap di due variabili senza una variabile temporanea che ho presentato nei post precedenti). La complessità di questa soluzione iterativa è O(n).

    Implementando una sorta di controllo automatico (tramite assert), possiamo capire che la soluzione iterativa è equivalente alla soluzione ricorsiva, ma sappiamo anche che è più veloce e lineare (grazie all’analisi asintotica che abbiamo realizzato):

    #!/usr/bin/python
    
    # fib(n) = fib(n-1)+fib(n-2)
    # fib(0) = 0
    # fib(1) = 1
    # fib(2) = 1
    # fib(3) = 2
    # fib(4) = 3
    # fib(5) = 5
    # fib(6) = 8
    # ...
    
    def fib(n):
        if n == 0:
            return 0;
        if n == 1:
            return 1;
        else:
            return fib(n-1) + fib(n-2)
    
    def fibnr(n):
        fn1 = 1;
        fn2 = 0;
        fib = 0;
        i = 2;
        if n == 0:
             return 0;
        if n == 1:
             return 1;
    
        while (i <= n):
            fib = fn1 + fn2
            tmp = fn1
            fn1 = fib
            fn2 = tmp
            i += 1
        return fib
    
    if __name__ == '__main__':
        for i in xrange(21):
            print i, ',', fib(i), '|', fibnr(i)
            assert (fib(i) == fibnr(i))
    
  • Python: scriviamo un HTTPS downloader simile a wget (con urllib2, optparse e getpass)

    Per ragioni di semplicità di utilizzo e immediatezza (e anche per imparare qualcosa di nuovo), la settimana scorsa ho dovuto scrivere un downloader da linea di comando simile a GNU wget, ma con alcuni requisiti personalizzati:

    • l’accesso alla pagina di download è protetto da userid e password (autenticazione HTTPDigestAuth);
    • il protocollo di accesso è HTTPS;
    • una volta ottenuto l’accesso, è necessario fare il GET di alcuni files ben particolari e crearli su hard disk.

    Per realizzare questo wget-like in Python, usiamo il modulo urllib2 che ci permette di accedere a risorse esterne tramite i protocolli più svariati. Per aprire una connessione HTTPS e autenticarci correttamente usiamo i seguenti statement:

        passman.add_password(None, url, username, password)
        authhandler = urllib2.HTTPDigestAuthHandler(passman)
        opener = urllib2.build_opener(authhandler)
    

    Affinché la gestione delle varie opzioni sia più semplice possibile, usiamo anche il modulo optparse (che permette di passare le opzioni al programma in modo simile alle utility GNU):

    parser = optparse.OptionParser()
    parser.add_option("-u", "–username")
    parser.add_option("-d", "–date")
    parser.add_option("-t", "–target", default="logs")
    options, arguments = parser.parse_args()
    

    Visto che dovremo gestire anche l’autenticazione tramite username e password, la scelta di passare la password come argomento del programma è una scelta non ottimale: una semplice ricerca nella history della shell utilizzata potrebbe permettere ad un attaccante di risalire alla password. Per questo ci viene incontro il pacchetto getpass che, in modo simile ai programmi UNIX che si rispettino, chiede interattivamente la password (senza stamparla, ovviamente!). Per richiedere l’inserimento della password all’utente è sufficiente:

    password = getpass.getpass()
    

    Torniamo ora all’inizio: abbiamo ottenuto dall’utente username (passato come argomento) e password (richiesta interattivamente): ora possiamo aprire una connessione verso l’hostname di riferimento e richiedere il download del file (salvando su file lo stream del file in ricezione tramite i metodi standard di Python):

    passman = urllib2.HTTPPasswordMgrWithDefaultRealm()
    passman.add_password(None, url, options.username, password)
    authhandler = urllib2.HTTPDigestAuthHandler(passman)
    opener = urllib2.build_opener(authhandler)
    download = opener.open(file)
    logging.debug('downloading %s ...' % file)
    fo = open(options.target + os.sep + targetFileName, 'w')
    fo.write(download.read())
    fo.close()
    

    Abbiamo quindi ottenuto un semplice downloader di file scritto in Python, molto più semplice di wget ma al tempo stesso molto più customizzabile.

  • Scambiare il contenuto di due variabili int senza utilizzare una variabile temporanea: XOR swap

    Una domanda interessante che mi è stata posta è: “com’è possibile scambiare il contenuto di due variabili senza utilizzare una variabile temporanea?”.

    Infatti, per scambiare il contenuto di due variabili, di solito si utilizza una variabile temporanea (vediamo un esempio in Python):

    a = 13
    b = 17
    c = a
    a = b
    b = c
    >>> a
    17
    >>> b
    13
    

    Ora, per scambiare due variabili senza una variabile temporanea (nel nostro caso l’abbiamo chiamata ‘c’), ho pensato di utilizzare un procedimento di questo tipo:

    a = 13
    b = 17
    a = a+b
    b = a-b
    a = a-b
    >>> a
    17
    >>> b
    13
    

    La soluzione a cui sono arrivato viene classificata come una variante del più diffuso XOR swap algorithm, che, in forma più generale, permette di scambiare due variabili semplicemente usando l’operazione di XOR:

    a = 13
    b = 17
    a = a ^ b
    b = a ^ b
    a = a ^ b
    >>> a
    17
    >>> b
    13
    

    Lo XOR swap può essere utile in casi molto particolari, come ad esempio quando la memoria disponibile è notevolmente limitata.
    Bonus point: in Python non c’è bisogno di utilizzare l’algoritmo di XOR swap:

    a = 13
    b = 17
    a, b = b, a
    >>> a
    17
    >>> b
    13
    

    E c’è di più: questo “trucco” funziona anche per i tipi di dato non intero!

    a = 'homer'
    b = 'marge'
    a, b = b, a
    >>> a
    'marge'
    >>> b
    'homer'
    
  • Python e il metodo setdefault

    Note to self (pun intended): questo post rappresenta una sorta di appunto mentale per ricordarmi il funzionamento di setdefault in Python, visto che puntualmente mi trovo ad utilizzarlo.

    setdefault è utilizzato sui dizionari (dict) e permette di impostare una chiave di default durante una set. Mi spiego meglio con un esempio:

    >>> D
    {1: 'leonard', 2: 'sheldon', 3: 'wolowitz', 4: 'rajesh'}
    

    Vediamo come funziona il metodo setdefault:

    >>> D.setdefault(1, 'penny')
    'leonard'
    >>> D
    {1: 'leonard', 2: 'sheldon', 3: 'wolowitz', 4: 'rajesh'}
    >>> D.setdefault(5, 'penny')
    'penny'
    >>> D
    {1: 'leonard', 2: 'sheldon', 3: 'wolowitz', 4: 'rajesh', 5: 'penny'}
    

    Ovvero, setdefault valuta la chiave passata come primo argomento:

    • se è già presente nel dizionario, viene ritornato il valore della chiave presente nel dizionario;
    • altrimenti viene aggiunta al dizionario con il valore specificato come argomento.

    in termini pythonici:

    def setdefault(d, key, val):
        if key in d:
            return d[key]
        else:
            d[key] = val
    
  • Trenitalia e Viaggiatreno: come implementare un servizio “fai da te” per avere le informazioni dello stato di un treno via SMS

    Da quando sono diventato un pendolare (per lavoro), ho iniziato a (ri)frequentare assiduamente le stazioni ferroviarie e i treni di Trenitalia.

    station.

    Dopo un paio di settimane passate sui treni, mi sono accorto di un servizio interessante: il sito Viaggiatreno: il sito contiene le informazioni sui treni (informazioni in tempo reale sullo stato di un treno come ritardo o anticipo, ora prevista di arrivo e fermate già effettuate).

    Esigenza

    Dopo aver provato il disagio di un treno soppresso (e quindi essermi svegliato un’ora prima per stare un’ora ad aspettare in stazione), mi sono reso conto di poter, perlomeno, arginare il problema se si fosse presentato in futuro; conoscere lo stato del treno direttamente sul telefonino mi permetterebbe, la mattina, di poter scegliere un mezzo alternativo senza recarmi in stazione nel caso in cui il treno sia in ritardo o soppresso. Oppure, se il treno è in anticipo, di accelerare i tempi.

    Soluzioni esistenti

    • Potrei controllare il sito Viaggiatreno accedendo il computer, connettendomi a Internet e andando sul sito Viaggiatreno. Contro: il tempo richiesto è troppo (~ 5 minuti. E la mattina anche 5 minuti sono preziosissimi).
    • Potrei installare su iPhone l’applicazione ProntoTreno, un’applicazione che fornisce lo stato di tutti i treni in tempo reale. Contro: il mio access point WIFI non è sempre attivo; non ho a disposizione la connessione 3G. Inoltre, se dovessi cambiare telefono per passare ad un telefono senza WIFI/3G, questa soluzione non è più applicabile.

    L’idea

    Il mezzo da utilizzare è sicuramente il mobile. L’incognita era semplicemente trovare il modo per raggiungere i telefoni, ed è stato individuato negli SMS, supportati da tutti i telefoni.

    L’altro fattore da tenere in considerazione è: come ottenere i dati da Viaggiatreno? Il sito utilizza Flash, ed estrarre informazioni testuali e puntuali da Flash penso sia infattibile. La soluzione è presto trovata: fortunatamente, Viaggiatreno offre anche una versione mobile, realizzata in (X)HTML, per la gioia dei parser.

    La realizzazione

    Per la realizzazione del servizio abbiamo bisogno di:

    • Un server Linux attivo e connesso a Internet durante il lasso di tempo in cui vogliamo essere avvisati via SMS. Inoltre, il server Linux deve avere installato crontab, l’interprete python e alcuni pacchetti python che vedremo più avanti.
    • Un servizio per mandare SMS gratuitamente: io mi appoggio a virgilio.it, che mi permette di ricevere via sms tutte le email ricevute all’indirizzo indirizzo@sms.tin.it [per i più impazienti: in questo modo per ricevere SMS è sufficiente mandare una mail]. Se invece non potete usare virgilio.it, vi raccomando di utilizzare MoioSMS per l’invio gratuito di SMS.
    • Un account GMail da cui mandare la mail del punto sopra (se siete degli smanettoni, non avete bisogno di questo e potrete usare il vostro SMTP, a patto di sostituirlo nel codice).
    • Un telefono mobile abilitato alla ricezione di SMS e attivo (ovviamente!)
    • Il numero del treno di nostro interesse: per conoscere il numero del treno, è sufficiente andare su TreniItalia, immettere partenza e arrivo e annotarsi il numero del treno. Il numero del treno è unico e non cambia.
    • Lo script python seguente, che ho scritto in Python utilizzando le librerie python-mechanize e python-beautifulsoup (installatele seguendo la guida per la vostra distribuzione. Per Ubuntu/Debian ho usato apt-get install python-mechanize python-beautifulsoup). Il codice è ancora immaturo, ma funziona (vedi screenshot):
      “`
      #! /usr/bin/python

      import BeautifulSoup
      import mechanize
      import smtplib
      import time
      import datetime
      from email.MIMEMultipart import MIMEMultipart
      from email.MIMEBase import MIMEBase
      from email.MIMEText import MIMEText
      from email import Encoders
      import os
      import sys

      gmail_user = "gmail username"
      gmail_pwd = "gmail pass"

      def mail(to, subject, text):
      msg = MIMEMultipart()

      msg['From'] = gmail_user
      msg['To'] = to
      msg['Subject'] = subject

      msg.attach(MIMEText(text))

      mailServer = smtplib.SMTP("smtp.gmail.com", 587)
      mailServer.ehlo()
      mailServer.starttls()
      mailServer.ehlo()
      mailServer.login(gmail_user, gmail_pwd)
      mailServer.sendmail(gmail_user, to, msg.as_string())
      mailServer.close()

      numtreno = sys.argv[1]
      stazione = sys.argv[2]

      tobeparsed = mechanize.urlopen(str(str('https://mobile.viaggiatreno.it/viaggiatreno/mobile/scheda?numeroTreno=') + str(numtreno) + str('&tipoRicerca=numero&lang=IT')))
      f = BeautifulSoup.BeautifulSoup(tobeparsed)
      f = f.prettify()
      f = f[f.find('<!– SITUAZIONE –>'):]
      f = f[:f.find('</div>')]
      s1 = f[f.find('<strong>')+len('<strong>'):f.find('<br />')]
      s2 = f[f.rfind('–>')+len('–>'):f.find('</strong>')]
      s1 = s1.replace(''',"'")
      s1 = s1.split()
      s2 = s2.split()
      tobeparsed = mechanize.urlopen(str(str('https://mobile.viaggiatreno.it/viaggiatreno/mobile/scheda?dettaglio=visualizza&numeroTreno=') + str(numtreno) + str('&tipoRicerca=numero&lang=IT')))
      f = BeautifulSoup.BeautifulSoup(tobeparsed)
      f = f.prettify()
      f = f[f.find(stazione):]
      f = f[:f.find("</div>")]
      arr1 = f[f.find('')+len('<p>'):f.find('<br />')]
      or1 = f[f.find('<strong>')+len('<strong>'):f.find('</strong>')]
      arr = f[f.rfind('<p>'):f.rfind('</p>')]
      arr2 = arr[arr.find('<p>')+len('<p>'):arr.find('<br />')]
      or2 = arr[arr.find('<strong>')+len('<strong>'):arr.find('</strong>')]
      arr1 = arr1.split()
      or1 = or1.split()
      arr2 = arr2.split()
      or2 = or2.split()
      arr1.extend(or1)
      arr2.extend(or2)

      L = [s1, s2, arr2]
      for i in xrange(len(L)):
      L[i] = ' '.join(L[i])
      message = '\n'.join(L)

      mail("indirizzo@sms.tin.it",
      sys.argv[1],
      message,
      )
      “`

      Il codice estrae le informazioni di interesse da Viaggiatreno mobile (come detto, per fare il parsing occore usare il sito in versione mobile) come lo stato corrente del treno (posizione ed eventuali minuti di ritardo o anticipo) e l’orario di arrivo previsto e manda queste informazioni all’indirizzo mail “speciale” che gira il messaggio al mio numero di cellulare via SMS [per l’invio della mail il servizio usa GMail]. Come detto, potreste usare MoioSMS (via pipe o python import) semplicemente sostituiendo la parte di invio della mail.

    Come si utilizza?

    • Lo script accetta due parametri da command-line: il numero di treno a cui siete interessati e la stazione di cui volete conoscere l’orario di arrivo stimato (es. “LECCO MAGGIANICO”).
    • Modificate, nello script le variabili gmail_user e gmail_password con le credenziali del vostro account GMail che useremo per spedire la mail verso l’indirizzo speciale che girerà le mail in SMS. Sempre nel codice, sostituite appunto indirizzo@sms.tin.it con il vostro indirizzo “speciale” che tramuta il messaggio e-mail in SMS.
    • Usando crontab, inserite una entry del tipo:
      08 07 * * 1-5 python /home/mbologna/parser.py 5033 “LECCO MAGGIANICO”
      Per avere un aggiornamento via SMS alle 07:08 di tutti i giorni feriali del treno 5033 specificando che deve fornirci l’orario di arrivo previsto alla stazione di “LECCO MAGGIANICO”.

    Risultati

    La mattina, prima di recarmi in stazione, controllo sul mio telefono e automaticamente ricevo 3 SMS gratuiti diversi contenenti lo stato del treno man mano che si avvicina alla stazione in cui io salgo sul treno. In questo modo, ancora prima di uscire di casa, posso sapere se il treno è in ritardo o se è stato soppresso.

    Qui accanto, la testimonianza di 2 degli SMS che ricevo quotidianamente.

    Sviluppi futuri

    • Miglioramento del codice di download/parsing
    • Integrazione con MoioSMS

    Curiosità