La prima funzione da costruire è quella che si occupa di inviare messaggi Telegram. Naturalmente, prima di tutto il programma deve occuparsi di leggere i messaggi ricevuti (utilizzando il metodo urlfetch). I messaggi vengono forniti al programma sotto forma di un testo abbastanza complesso, scritto nel formato JSON, con una serie di informazioni aggiuntive come la data del messaggio ed il mittente, oltre owiamente al testo del messaggio. Prima di tutto si inseriscono tutte queste informazioni nell’oggetto body.
update_id = body[‘update_id’]
message = body[‘message’]
message_id = message.get(‘message_id’)
date = message.get(‘date’)
text = message.get(‘text’)
fr = message.get(‘from’)
chat = messagerchat’]
chat_id = chat[‘id’]
Poi si estraggono tutte le informazioni, una per una, inserendole in variabili di facile utilizzo. Per esempio, adesso la variabile text contiene il testo del messaggio.
if not text:
logging.info(‘no text’)
return
Ora si verifica se il messaggio contiene del testo: nel caso la variabile text sia vuota, non ha senso procedere e la funzione può essere chiusa.
def reply(msg=None, img=None):
In caso contrario si può procedere con la scrittura di una risposta (reply in inglese) al messaggio.
try:
if msg:
resp = urllib2.urlopen(BASE_URL
_________ + ‘sendMessage’, urllib.urlencode({
‘chat_id’: str( chat_id),
___ ___’t_ e_xt’:_m–s’g.encode(‘utf-8’),
‘disable_web_page_preview’: ‘true’,
#’reply to_message_id’: str( message_id),
} )) .read()
La risposta da inviare può essere un messaggio di testo oppure una immagine. Nel caso del testo, basta indicare la conversazione su cui si sta lavorando (owero chat_id), ed il contenuto del messaggio con codifica UTF8 (che supporta anche i caratteri con accento della lingua italiana). Volendo è possibile specificare che il messaggio in questione è una risposta ad un messaggio precedente: basta togliere il cancelletto alla riga che inizia con reply_to_message_id. In genere è consigliabile non farlo, perché diventa molto fastidioso durante le conversazioni.
elif img:
resp = multipart.post_multipart(
BASE_URL + ‘sendPhoto’, [
(‘chat_id’, str(cha_t __i d~)-‘)-, _______
#(‘reply to_message_id’, str(message_id) ), ———-
], [
(‘photo’, ‘image.jpg’,_i~mg), _ _ ____
])
else:
logging.error(‘no msg or img specified’)
resp = None
Per inviare una immagine, si utilizza la libreria multipart per costruire un messaggio contenente l’immagine che vogliamo spedire (dando all’immagine un nome standard, ovvero “image.jpg”). Anche in questo caso, si può decidere di
La risposta, a prescindere dal fatto che sia testo od immagine, viene inviata. Abbiamo utilizz.ito anche il costrutto try-except: grazie a questo metodo, se qualcosa dovesse andare storto ed il messaggio non potesse essere inviato, il programma non si bloccherà in modo anomalo. L’eventuale errore riscontrato verrà anche inserito nel log, che possiamo leggere direttamente nella Console di Google App Engine.
if text.startswith(‘/’):
if text = = ‘/start’:
reply(‘Bot enabled’)
setEnabled(chat_id, True)
elif text == ‘/stop’:
reply(‘Bot disabled’)
setEnabled(chat_id, False)
Non dimentichiamo che, in questo momento, la variabile text contiene il messaggio a cui il bot deve rispondere. In genere, in comandi del bot vengono scritti con il simbolo I all’inizio. Quindi, per capire se è stato inviato un comando, basta verificare se la variabile text comincia proprio con il simbolo /. In tale condizione, si può verificare se, per esempio, siano stati inviati i comandi start o stop, per awiare o fermare il bot. Naturalmente, .possiamo inventarci degli altri comandi per fare tutto ciò che vogliamo.
elif text = = ‘/image’:
img = Image.new(‘RGB’, (512, 512))
base = random.randint(O, 16777216)
pixels = [base+i*j fori in range(512)
for j in range(512)] # generate sample image
img.putdata(pixels)
output = String!O.String!O()
img.save( output, ‘JPEG’)
reply(img=output.getvalue())
Possiamo verificare il funzionamento dell’invio di immagini inserendo nel nostro bot un comando chiamato /image, che restituisce una immagine casuale. Prima di tutto, si costruisce una nuova immagine da 512×512 pixel. Poi si sceglie il valore di ogni pixel ut ilizzando come base un numero casuale tra O e 16777216 (16 milioni di colori). L’insieme dei valori dei pixel è raccolto in un array bidimensionale con 512 righe e 512 colonne. L’array viene inserito nell’immagine con la funzione putdata. L’immagine deve poi essere salvata in formato Jpeg ed il suo valore viene direttamente inviato alla funzione reply, quindi non viene scritto sul disco e non occupa spazio, la quale si occupa di inviare il messaggio all’utente.
elif ‘orari’ in text.lower():
IL DIALOGO CON L’UTENTE
Ora cominciamo a scrivere i comandi che seNono alla realizzazione del “bot cameriere”. Dobbiamo prima di tutto capire che cosa l’utente desideri: il problema è che l’utente utilizzerà un linguaggio comune, quindi non possiamo controllare l’intero messaggio, perché l’utente pu scrivere la stessa frase in molte forme diverse. Piuttosto, dobbiamo cercare di capire il significato del messaggio, verificando se siano presenti alcune particolari parole: questo si può fare utilizzando la forma if PAROLA in variabile di Python. Per semplificare le cose, non cerchiamo le parole nella variabile text, ma in text.JowerO, cioè la versione della variabile che ha tutte le lettere in minuscolo. Per fare un esempio, se la frase scritta dall’utente contiene la parola “orari”, molto probabilmente vuole conoscere l’orario di apertura del locale.
if (database != “”):
reply(“L’orario è:\n”+database[‘orari’])
In tal caso, dunque, non dobbiamo fare altro che rispondere con un messaggio che contiene l’elemento orari dell’array database. Prima, però, si deve verificare che l’array in questione non sia vuoto, cosa che potrebbe accadere nel caso sbagliamo qualcosa e rendiamo inutilizzabile il file JSON che contiene tutte le informazioni.
elif (‘menu’ in text.lower()) or (‘menù’
in text.lower()):
È poi probabile che se la frase dell’utente contiene la parola menù, questi voglia conoscere il menù del giorno. Alcune persone omettono l’accento, scrivendo quindi menu al posto di menù. Dobbiamo tenere conto della cosa, e possiamo farlo utilizzando l’operatore logico OR. Potremmo anche aggiungere altri sinonimi, per esempio inserendo or (‘piatti’ in text.JowerO ), per rendere valida l’affermazione anche nel caso in cui l’utente chieda “Quali piatti avete?”.
if (database == “”):
reply(“Nessun database”)
retum
menu=””
Se riteniamo che l’utente voglia conoscere il menu, dobbiamo prima di tutto verificare che esista un database, come abbiamo fatto prima, e poi preparare una variabile vuota che andrà a contenere l’elenco del menù del giorno.
for (riga) in database[‘menu’]:
menu = menu + riga[‘piatto’) + “\n”
reply(‘II menù di oggi è:\n’+menu)
Il menù del giorno è costituito dal semplice elenco di tutte le pietanze disponibili. Per costruirlo è sufficiente un ciclo for che scorre tutti gli elementi della colonna piatto della tabella menu presente nell’array database. Infine, si invia come messaggio di risposta il menù.
elif (‘ingredienti’ in text.lower()):
if (database == “”):
reply(“Nessun database”)
retum
È possibile che un utente voglia conoscere gli ingredienti che costituiscono un piatto: magari è allergico a qualcosa, oppure semplicemente non gradisce il sapore di alcune spezie e vuole giustamente essere informato prima di comprare qualcosa che non vorrebbe mangiare. Se è questo il caso, la frase dell’utente conterrà certamente la parola ingredienti.
ingr = “”
for (riga) in database[‘menu’]:
if (riga[‘piatto’] in text.lower()):
Ora abbiamo un problema: come facciamo a sapere quale sia il piatto di cui l’utente vuole conoscere gli ingredienti? È owio che l’utente abbia inserito il nome del piatto nel proprio messaggio, ma non sappiamo esattamente dove.
La soluzione più semplice consiste nello scorrere l’elenco dei piatti disponibili nel nostra database e verificare uno per uno se il nome sia presente nel messaggio dell’utente. In poche parole, se il database contiene il piatto “spaghetti al ragù” e se queste parole sono contenute anche da qualche parte nel messaggio dell’utente, è owio che l’utente voglia conoscere gli ingredienti di questo piatto.
piatto = riga[‘piatto’]
ingr = riga[‘ingredienti1
reply(‘II piatto ‘ + riga[‘piatto] + ‘ è realizzato con ‘ + ingr)
Se abbiamo identificato nella riga attuale del database (siamo all’interno del ciclo for che scorre tutte le righe del database) un piatto indicato nel messaggio dell’utente, il suo nome sarà memorizzato nella colonna piatto della riga attuale, mentre i suoi ingredienti sono memorizzati nella colonna ingredienti. Ottenute queste due informazioni, basta rispondere al messaggio specificandole.
if (ingr == “”):
reply(“Non ho trovato il piatto che hai indicato.”)
Abbiamo scritto l’elenco degli ingredienti del piatto trovato nella variabile ingr. Ciò significa che se non abbiamo trovato alcuna corrispondenza tra l’elenco dei piatti del database ed il contenuto del messaggio dell’utente, tale variabile sarà vuota. In tal caso, awisiamo l’utente di non avere trovato il piatto che ci ha indicato.
elif (‘consegnare in’ in text.lower()):
if (database = = “”):
reply(“Nessun database”)
retum
È anche possibile che l’utente voglia ordinare del cibo per asporto: in tal caso, ci farà sapere dove consegnarlo con una locuzione del tipo “consegnare in”.
piatto=””
indirizzo = “”
for (riga) in database[‘menu’]:
if (rìga[‘piatto’] in text.lower()):
piatto = riga[‘piatto’]
Anche in questo caso dobbiamo prima di tutto identificare il piatto e desidera ordinare, e lo facciamo utilizzando il meccanismo del ciclo for che scorre tutti i piatti disponibili per verificare quale di essi sia presente nella frase che ci ha inviato l’utente. Una volta trovato, lo inseriamo nella variabile piatto. Tra l’altro, il vantaggio di questo meccanismo sta nel fatto che se l’utente ha specificato più piatti, il ciclo for li identificherà tutti automaticamente e ripeterà la procedura per ciascuno di essi.
pos = int(text.lower().find(“consegnare in”)}+ 14
indirizzo= text[int(pos):]
Ora dobbiamo identificare l’indirizzo a cui consegnare il piatto. Qualunque esso sia, si trova certamente dopo le parole “consegnare in”. Quindi identifichiamo la posizione del carattere immediatamente successivo a tali parole, e poi selezioniamo la porzione della variabile text (che contiene il messaggio dell’utente) successiva a tale carattere. Una soluzione alternativa consisterebbe nell’identificare l’inizio dell’indirizzo con una serie di parole chiave del tipo “via”, “piazza”, ·’strada” … Il problema è che queste parole possono essere molte, e risulta quindi più semplice stabilire una unica parola chiave standard come “consegnare in”
. msg = ‘Consegnare ‘ + riga[‘piatto’] + ” in ” + indirizzo
email = “email@gmail.com”
Il messaggio da inviare alla cucina del ristorante è semplice:
basta indicare il piatto da cucinare e l’indirizzo a cui consegnarlo, specificando l’indirizzo mail della cucina.
message = mail.EmailMessage=.e'(sender
= email,
to =email,
subject = ‘ordine ‘+indirizzo,
html = msg)
Costruiamo la mail: mittente e destinatario sono lo stesso indirizzo mail per semplicità, owiamente rappresentano l’indirizzo che viene controllato nella cucina del ristorante.
L’oggetto della mail è l’indirizzo a cui consegnare il cibo.
try:
message.send()
reply(‘II piatto ‘ + riga[‘piatto’] + ” verrà consegnato
quanto prima possibile all’indirizzo” + indirizzo)
except:
reply{“lmpossibile inviare il tuo ordine.”)
Grazie al costrutto try-except ci assicuriamo che l’invio del messaggio sia andato a buon fine, ed in caso contrario avvisiamo l’utente di avere incontrato un errore di qualche tipo.
if (piatto = = “”):
reply(“Non ho trovato il piatto che hai indicato.”)
if (indirizzo == “”):
reply(“Non hai indicato un indirizzo
a cui consegnare il piatto.”)
Nel caso in cui il ciclo for non avesse identificato alcun piatto od alcun indirizzo a cui inviare il cibo, awisiamo l’utente del fatto che ci mancano le informazioni necessarie per procedere.
Il bot è completo: naturalmente possiamo aggiungere altre funzioni, come la possibilità per l’utente di conoscere il prezzo del piatto che vuole acquistare. li meccanismo di base è sempre lo stesso: basta scorrere l’elenco dei piatti disponibili con un ciclo for e, quando se ne trova uno che è stato inserito nel messaggio dell’utente, presentare il suo prezzo.