Ciao a tutti,
questo è il primo articolo che scrivo per questo blog, quindi spero vi piaccia!
Qualche settimana fa, ho scritto un paio di articoli sul mio blog personale su come usare Python per leggere codici a barre dalle immagini, e su come integrare la lettura dei codici a barre all'interno di un bot Telegram.
Gli articoli sono piaciuti molto, e mi è stato chiesto se si potesse implementare codice simile ma per la lettura di QRCode! Da queste richieste è nata l'idea di questo articolo, e mi è sembrata la perfetta occasione per iniziare questa mia collaborazione con Alla fine del Palo!
Cosa faremo
Lo scopo di questo tutorial è quello di sviluppare un bot telegram in grado di leggere e scrivere codici QRcode. Il funzionamento che vogliamo ottenere è il seguente:
- Se l'utente manda al una foto, questo cerca all'interno dell'immagine la presenza di un codice QR, e risponde con il contenuto del codice in caso positivo.
- Se l'utente invia un testo al bot, questo lo embedda in un codice a barre e ne ritorna il contenuto.
Prerequisiti
Se volete leggere il tutorial senza provarlo, andate pure avanti, cercherò di essere il più chiaro possibile. Tuttavia, se volete riprodurlo da voi, è importante che conosciate (almeno a grandi linee) un paio di tecnologie che utilizzerò qui, e che sono state già ampiamente trattate in questo e nel mio blog.
Sviluppiamo il BOT
Per prima cosa, dobbiamo iniziare a sviluppare il nostro bot! Per fare il setup del progetto, io sono solito instanziare un virtualenv per tenere il tutto pulito, e quindi installare la libreria telepot per lo sviluppo di bot Telegram:
1 2 3 |
$ mkdir qr-bot-telegram && cd qr-bot-telegram $ virtualenv -ppython3 env && source env/bin/activate (env)$ pip install telepot |
Notate che ho creato un ambiente virtuale con Python 3, usando il comando virtualenv -ppython3 env .
A questo punto, creiamo un nuovo file (chiamo ad esempio qr-bot.py ) e implementiamo uno scheletro del bot Telegram.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
import telepot TOKEN = '*** INSERISCI IL TUO TOKEN QUI ***' bot = telepot.Bot(TOKEN) def handle(msg): content_type, chat_type, chat_id = telepot.glance(msg) bot.sendMessage(chat_id, "Sono un bot stupido!") bot.message_loop(handle) import time while 1: time.sleep(10) |
Questo semplice bot non fa altro che rispondere con la stringa "Sono un bot stupido!" a qualsiasi messaggio in entrata. Se non sapete esattamente come funziona il codice, o come generare il vostro TOKEN, forse vi conviene prima di tutto dare un'occhiata qui oppure qui!
Lanciamo il bot con il comando (env)$ python qr-bot.py per vedere che tutto funzioni! Il risultato dovrebbe essere simile a quello della foto sotto!
Fin qui niente di nuovo, vediamo adesso come usare il bot per leggere e generare QRCode!
Generare codici QRCode
Partiamo dal secondo punto, banalmente perchè è leggermente più semplice, e vediamo come generare QRCode in Python e quindi inviarle a Telegram.
La prima parte è veramente semplice! Infatti, esiste una libreria Python molto semplice da utilizzare per eseguire questa operazione, che si chiama (pensate un po') qrcode . Dobbiamo inoltre installare la libreria Pillow per la gestione delle immagini in Python.
Per installarla, digitiamo nella shell di comando
1 |
(env)$ pip install qrcode Pillow |
L'utilizzo è altrettanto semplice! Dopo aver importato la libreria con
1 |
from qrcode import QRCode |
Per generare un qrcode con all'interno un testo contenuto in una variabile msg basta instanziare un oggetto QRCode , aggiungerci il messaggio con il metodo QRCode.add_data() e quindi generare il qrcode con il metodo QRCode.make() .
1 2 3 4 5 |
msg = 'un messaggio da inserire nel QRCode' qr = QRCode() qr.add_data(msg) qr.make(fit=True) |
A questo punto, possiamo generare l'immagine contenente il qrcode con la linea di codice
1 |
qr_image = qr.make_image() |
Il codice è quasi pronto, ma dobbiamo gestire un piccolo intoppo: Telepot non è in grado di inviare (e ricevere) immagini contenute dentro delle variabili, ma lavora direttamente su File. Questo è una piccola scocciatura, perché richiede di leggere e scrivere dati su disco che potrebbero rimanere benissimo in RAM, ma, fortunatamente, Python3 ci viene incontro con una piccola soluzione, che si trova nel modulo standard io.BytesIO : una libreria che permette di creare oggetti che funzionano come file in Python, ma che vivono all'interno della RAM e non del disco rigido.
Quello che dobbiamo fare, quindi, è generare un oggetto di tipo BytesIO , salvare l'immagine qr_image all'interno di questo oggetto e saremo pronti a spedirlo con telepot. Il tutto, ricordandoci di importare il modulo io.BytesIO:
1 2 3 4 5 6 7 |
from io import BytesIO # ... img_io = BytesIO() qr_image.save(img_io) img_io.seek(0) |
Siamo quasi pronti, ma prima mettiamo tutto all'interno di una funzione per migliorare la leggibilità del codice:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
from io import BytesIO from qrcode import QRCode # ... def create_qr_code(msg): # Creo il QRCODE qr = QRCode() qr.add_data(msg) qr.make(fit=True) qr_image = qr.make_image() # Salvo il QRCode su BytesIO img = BytesIO() qr_image.save(img) img.seek(0) return img |
A questo punto, possiamo usare il metodo .sendPhoto del nostro bot Telegram per inviare il qrcode generato! Quello che voglio fare, quindi, è far si che qualsiasi messaggio scritto venga mandato a Telegram sia embeddato bel QRCode. Modifichiamo quindi la funzione handle in questo modo:
1 2 3 4 5 6 7 8 9 |
def handle(msg): content_type, chat_type, chat_id = telepot.glance(msg) if content_type == 'text': bot.sendMessage(chat_id, "msg: "+ msg['text']) bot.sendMessage(chat_id, "Genero il QRCODE....") img = create_qr_code(msg['text']) bot.sendPhoto(chat_id, img) else: bot.sendMessage(chat_id, "Tipo di messaggio non supportato") |
In particolare, nel caso in cui venga inviato un messaggio di tipo testuale (
if content_type == 'text': ) utilizziamo la funzione
create_qr_code appena implementata per generare un'immagine, e quindi la invio con il metodo
bot.sendPhoto . Il risultato è mostrato qui sotto. Notate che non abbiamo usare direttamente la libreria
Pillow . Tuttavia, questa è chiamata all'interno di
qrcode , ma deve essere installata esplicitamente per poter far funzionare il tutto.
Leggere i codici QRCode
La lettura dei codici QRCode funziona in un modo simile, e sfrutta una libraria chiamata pyzbar . Quest'ultima è il wrapper Python di una libreria chiamata zbar , nata inizialmente per la lettura di codici a barre da immagini, ma che si è poi evoluta ed, al momento, include anche i qrcode. Per installarla, dobbiamo prima di tutto installare pyzbar
1 |
(env)$ pip install pyzbar |
Ma, se lavoriamo su macOS o Linux, è anche necessario installare manualmente la libreria zbar con i rispettivi comandi:
1 2 |
brew install zbar #macos |
1 2 |
sudo apt-get install libzbar0 #linux |
Fatto questo, siamo finalmente pronti ad implementare il codice.
Supponiamo di avere foto su file contenente un QRCode ( img_file ), per leggerla in Python, basta eseguire i seguenti comandi
1 2 3 4 5 6 7 |
from pyzbar.pyzbar import decode from PIL import Image # ... img = Image.open(img_file) qrcodes = decode(img) |
La variabile qrcodes è una lista contenente un elemento per ogni codice trovato nell'immagine. Un esempio di contenuto è riportato qui sotto:
1 |
[Decoded(data=b'Ciao a tutti', type='QRCODE')] |
Come vedete, troviamo nel campo data il messaggio contenuto nel qrcode, mentre nel campo type , il tipo di codice (in quanto zbar supporta anche codici a barre e altri tipo di codici).
Notate anche che questa volta usiamo il modulo PIL , che non è altro che la libreria Pillow installa precedentemente.
Il problema, di nuovo, è passare l'immagine da telepot a zbar . E anche stavolta ci viene d'aiuto il modulo BytesIO .
Implementiamo quindi una funzione che, a partire da un messaggio msg in telepot , scarica l'immagine con il metodo .download_file e poi la analizza. Come sopra, invece che caricare l'immagine su file, sfruttiamo BytesIO per salvarla su una variabile chiamata raw_img , e quindi apriamo questa immagine come se fosse un file e passiamola alla funzione decode di pyzbar
1 2 3 4 5 6 |
def read_qrcodes(msg): raw_img = BytesIO() bot.download_file(msg['photo'][-1]['file_id'], raw_img) img = Image.open(raw_img) qrcodes = decode(img) return qrcodes |
A questo punto, non ci resta che usare questa funzione all'interno della funzione handle del bot
1 2 3 4 5 6 7 8 9 10 11 |
def handle(msg): # ... elif content_type == 'photo': bot.sendMessage(chat_id, "Cerco QRCode...") qrcodes = read_qrcodes(msg) if len(qrcodes) > 0: for code in qrcodes: bot.sendMessage(chat_id, "msg: " + str(code.data)) else: bot.sendMessage(chat_id, "Non ho trovato QRCode nella foto...") # ... |
In questo caso, se il messaggio mandato è una foto, uso la funzione read_qrcodes per leggere eventuali qrcode, e invio il contenuto codificato all'utente. Se non trovo qrcode, viene mandato il messaggio "Non ho trovato QRCode nella foto..." .
Il risultato è il seguente.
Conclusioni
Spero che il tutorial sia utile a qualcuno. Per dovere di cronaca, vi informo che è stato testato su un Mac con sistema operativo macOS 10.13 High Sierra! Se qualcuno lo prova su un altro tipo di sistema, mi faccia sapere se va liscio o se ci sono dei problemi!
Vi lascio il codice completo qui sotto:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
import telepot from io import BytesIO from qrcode import QRCode from pyzbar.pyzbar import decode from PIL import Image TOKEN = '*** INSERISCI IL TUO TOKEN QUI ***' bot = telepot.Bot(TOKEN) def create_qr_code(msg): # Creo il QRCODE qr = QRCode() qr.add_data(msg) qr.make(fit=True) qr_image = qr.make_image() # Salvo il QRCode su BytesIO img = BytesIO() qr_image.save(img) img.seek(0) return img def read_qrcodes(msg): raw_img = BytesIO() bot.download_file(msg['photo'][-1]['file_id'], raw_img) img = Image.open(raw_img) qrcodes = decode(img) return qrcodes def handle(msg): content_type, chat_type, chat_id = telepot.glance(msg) if content_type == 'text': bot.sendMessage(chat_id, "msg: "+ msg['text']) bot.sendMessage(chat_id, "Genero il QRCODE....") img = create_qr_code(msg['text']) bot.sendPhoto(chat_id, img) elif content_type == 'photo': bot.sendMessage(chat_id, "Cerco QRCode...") qrcodes = read_qrcodes(msg) if len(qrcodes) > 0: for code in qrcodes: bot.sendMessage(chat_id, "msg: " + str(code.data)) else: bot.sendMessage(chat_id, "Non ho trovato QRCode nella foto...") else: bot.sendMessage(chat_id, "Tipo di messaggio non supportato") bot.message_loop(handle) import time while 1: time.sleep(10) |