Recentemente mi sono trovato a discutere di criptovalute con un caro amico, in particolare argomentavamo sul nostro gruppo Telegram di quanto il loro valore fosse fluttuante. In questo gruppo ci sono diversi amici comuni, più alcuni bot Telegram.
Allora mi sono chiesto: perché non realizzare un bot che fornisce il valore delle valute virtuali? Detto fatto e, per rendere il tutto più interessante, useremo un’architettura serverless.
L’idea di base è semplice: alla ricezione di un messaggio predefinito il bot recupererà il valore della valuta, quindi risponderà all’utente o al gruppo tramite Telegram.
Il tutto potrà essere eventualmente espandibile in futuro ad altre app di messaggistica o ad altre funzionalità: un’idea interessante sarebbe la possibilità di fare trading direttamente dalla chat… ma questa è un’altra storia.
Siamo pronti a cominciare, useremo Python come linguaggio di programmazione per il nostro bot e Amazon Web Services per l’architettura serverless.
Recupero valore criptovaluta
Innanzi tutto dobbiamo scrivere il codice che si occuperà di recuperare il valore di una criptovaluta, idealmente tramite API REST.
Con una veloce ricerca recuperiamo delle API pubbliche adatte a questo compito, abbiamo due alternative:
La prima è interrogabile in questo modo:
https://api.coinmarketcap.com/v1/ticker/bitcoin/
e restituisce:
[
{
"id": "bitcoin",
"name": "Bitcoin",
"symbol": "BTC",
"rank": "1",
"price_usd": "4145.22",
"price_btc": "1.0",
"24h_volume_usd": "2584150000.0",
"market_cap_usd": "68467684788.0",
"available_supply": "16517262.0",
"total_supply": "16517262.0",
"percent_change_1h": "-0.19",
"percent_change_24h": "2.23",
"percent_change_7d": "2.22",
"last_updated": "1503224367"
}
]
La seconda invece:
https://min-api.cryptocompare.com/data/price?fsym=BTC&tsyms=USD,EUR
e restituisce:
{"USD":4136.85,"EUR":3542.48}
La seconda è più comoda per il nostro scopo, con il plus di avere il valore sia in Dollari che in Euro… perfetta.
A questo punto non ci resta che scrivere il codice che, fornita la valuta di partenza e quella di destinazione, ci fornisce il valore di cambio. In Python possiamo scrivere una classe di questo tipo:
import urllib
import json
class Change:
"""Change.class."""
PRICE_API = "https://min-api.cryptocompare.com/data/price"
f = 'BTC'
t = 'USD'
def __init__(self, f='', t=''):
"""Change.__init__."""
if(f != ''):
self.f = f.upper()
if(t != ''):
self.t = t.upper()
def get_value(self):
"""Change.get_value."""
query = "?fsym={}&tsyms={}".format(self.f, self.t)
value = json.loads(get_url(self.PRICE_API + query))
try:
return "1 {} is worth {} {}".format(self.f, value[self.t], self.t)
except KeyError:
return value['Message']
Il costrutture si occupa di inizializzare le variabili d’istanza con degli eventuali default per valuta di partenza e valuta di destinazione (rispettivamente BTC e EUR).
Il metodo “get_value” invia una richiesta all’api descritta sopra e ritorna una breve stringa che indica il cambio (se l’api ritorna il valore correttamente).
Interazione con Telegram
Il secondo passaggio è l’interazione con Telegram. A questo scopo sono disponibili diverse librerie, probabilmente la più famosa per Python è: https://github.com/python-telegram-bot/python-telegram-bot.
Vista la semplicità del nostro progetto e visto lo scopo prettamente didattico ci conviene però sviluppare il tutto da zero: saranno solo poche linee di codice.
Innanzi tutto dobbiamo ricevere i messaggi da Telegram per poterli processare e quindi rispondere, ci sono essenzialmente due strade:
- ascoltare gli aggiornamenti pubblicati da Telegram (es. https://api.telegram.org/botTOKEN/getUpdates);
- sfruttare la possibilità di creare un Webhook che verrà invocato da Telegram ad ogni aggiornamento (https://core.telegram.org/bots/webhooks).
Volendo creare un bot Telegram serverless utilizziamo il secondo metodo, in caso contrario dovremmo invocare il bot manualmente; per ora però non creiamo il Webhook, ci basta sapere il formato in cui riceveremo l’aggiornamento.
La notifica di un nuovo evento arriverà in JSON via POST, il formato è questo: https://core.telegram.org/bots/api#update. L’oggetto “Message” è a sua volta così strutturato: https://core.telegram.org/bots/api#message.
Ma c’è di più, nella pagina di documentazione dei Webhook, Telegram ci offre anche un insieme di esempi (già predisposti per cUrl, per altro) pronti per testare il nostro bot.
Per rispondere ad un messaggio ricevuto ci è sufficiente utilizzare l’apposito comando dell’API: https://core.telegram.org/bots/api#sendmessage. I due dati richiesti sono “chat_id” che identifica appunto la chat a cui rispondere e “text” che è la nostra risposta.
A partire da questi dati siamo in grado di scrivere il codice per gestire la richiesta e la risposta:
import urllib
import json
import os
class Telegram:
"""Telegram.class."""
TELEGRAM_TOKEN = os.environ['TELEGRAM_TOKEN']
TELEGRAM_API = "https://api.telegram.org/bot{}".format(TELEGRAM_TOKEN)
TELEGRAM_REPLY = TELEGRAM_API + '/sendMessage'
update = {}
def __init__(self, update):
"""Telegram.__init__."""
self.update = update
def parse(self):
"""Telegram.parse."""
response = ''
if 'message' in self.update and 'text' in self.update['message']:
words = self.update['message']['text'].split(' ')
mode = words.pop(0)
if mode.lower() == 'value':
text = Change(*words).get_value()
response = self.__reply(text)
return response
def __reply(self, text):
"""Telegram.reply."""
chat = self.update['message']['chat']['id']
return get_url(self.TELEGRAM_REPLY+"?text={}&chat_id={}".format(text, chat))
Il costrutture in questo caso popola semplicemente la variabile d’istanza con i dati ricevuti da Telegram.
Il metodo “parse” verifica se il messaggio corrisponde ad un comando del bot e in caso positivo:
- estrae le variabili e ottiene il valore richesto tramite la classe precedentemente descritta;
- risponde al richiedente tramite il metodo “reply”.
Al momento non abbiamo ancora generato un token per identificarci con Telegram, prevediamo perciò di leggerlo come variabile d’ambiente (in pieno stile “Twelve-factor App”).
Definizione bot Telegram
E’ giunto il momento di informare Telegram della nostra intenzione di creare un nuovo bot.
La creazione e configurazione (privacy, nome, immagine, ecc.) dei bot in Telegram è gestita da… un bot chiamato BotFather, per cominciare iniziamo quindi una chat con lo stesso (https://t.me/BotFather) e per creare un nuovo bot lanciamo il comando:
/newbot
Dopo aver inserito nome e nome-utente del bot (che deve terminare in “bot”), BotFather ci informa che la creazione è andata a buon fine comunicandoci inoltre il token per le richieste tramite API http:
Done! Congratulations on your new bot. You will find it at
t.me/CryptocurrencyChangeBot. You can now add a description, about section and
profile picture for your bot, see /help for a list of commands. By the way,
when you've finished creating your cool bot, ping our Bot Support if you
want a better username for it. Just make sure the bot is fully operational
before you do this.
Use this token to access the HTTP API:
382957193:AAGMyGuQFOQ0vBoNWOQTWd3GLAPwg9le6VU
D’ora in poi (fintanto che non lo cancelleremo) possiamo eseguire comandi per il nostro bot con questa sintassi (sostituendo a “TOKEN” il codice che ci è stato fornito da BotFather):
https://api.telegram.org/botTOKEN/COMMAND
Alcuni comandi sono “informativi”, ad esempio, per ottenere le informazioni di base, usiamo:
https://api.telegram.org/botTOKEN/getMe
Altri sono “dispositivi”, in seguito vedremo, ad esempio, come impostare il nostro webhook con questo comando:
https://api.telegram.org/botTOKEN/setWebhook?url=WEBHOOK_URL
Infine dobbiamo notare che di default i bot ricevono:
- messaggi diretti;
- risposte al bot stesso;
- comandi diretti.
Se volessimo ricevere anche tutti i messaggi dai gruppi dovremmo disabilitare la privacy mode, sempre tramite “BotFather”.
La documentazione completa delle API http per i bot Telegram è disponibile all’indirizzo: https://core.telegram.org/bots/api.
Deploy su AWS serverless
L’architettura serverless è estremamente vantaggiosa per microservizi con carico estremamente variabile (esattamente come il nostro bot). Il concetto è ben dettagliato da Amazon su: https://aws.amazon.com/it/serverless/.
La creazione di una semplice piattaforma serverless senza storage di dati su AWS prevede due passaggi: il primo è la creazione e configurazione di AWS Lambda per il calcolo, il secondo è la configurazione di Amazon API Gateway che richiami la nostra funzione quando invocato.
AWS Lambda
Qui è dove il nostro codice verrà eseguito, un servizio in AWS Lambda è detto funzione; per crearne una accediamo ad AWS (http://aws.amazon.com) e quindi al pannello Lambda.
Clicchiamo “Create a function”, quindi “Author from scratch” e “Next” per saltare, per ora, la creazione di un trigger; inseriamo quindi alcune informazioni di base.
Carichiamo quindi il nostro codice, definiamo i ruoli per i permessi e le variabili d’ambiente inserendo il token fornito da Telegram come “TELEGRAM_TOKEN”.
Scegliamo poi l’“Handler”, questa è la singola funzione che viene eseguita alla chiamata del servizio, deve occuparsi di:
- gestire i parametri;
- eseguire il codice necessario;
- rispondere in maniera appropriata ad AWS.
Abbiamo già visto in dettaglio il formato della chiamata che riceveremo da Telegram, Amazon API Gateway la incapsulerà nei dati della richiesta (“event”) e la inoltrerà alla nostra funzione insieme ad altri dati relativi all’ambiente (“context”), maggiori dettagli qui: http://docs.aws.amazon.com/lambda/latest/dg/python-programming-model-handler-types.html (per Python).
Per ora concentriamoci solo su “event”, il formato è questo (la richiesta Telegram è incapsulata in event[‘body’]): http://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-set-up-simple-proxy.html#api-gateway-simple-proxy-for-lambda-input-format.
La nostra funzione Lambda dovrà inoltre a sua volta rispondere correttamente ad Amazon API Gateway per evitare che le chiamate vengano interpretate come errore, il formato corretto è questo: http://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-set-up-simple-proxy.html#api-gateway-simple-proxy-for-lambda-output-format.
A questo punto abbiamo tutti gli elementi per comporre una semplice funzione handler che si occupi di estrarre i dati di cui abbiamo bisogno e di rispondere a dovere alla richiesta. Eccola:
def lambda_handler(event='', context=''):
"""handler."""
response = Telegram(json.loads(event['body'])).parse()
return {"statusCode": '200', "body": response}
La documentazione completa di AWS Lambda è disponibile qui: http://docs.aws.amazon.com/lambda/latest/dg/welcome.html.
Amazon API gateway
Una volta perfezionata la funzione Lambda è necessario definire l’endpoint che ad una determinata richiesta richiami la funzione stessa, questa è la funzione di Amazon API gateway.
Accediamo quindi al servizio e creiamo una nuova API definendo alcune impostazioni di base.
Dopo averla creata dobbiamo definire la risorsa da pubblicare. Clicchiamo su “Actions” quindi “Create Method”. Il webhook del bot Telegram, come da documentazione, invia una chiamata POST, andiamo perciò a definire una nuova risorsa di questo tipo che imposteremo per richiamare la nostra funzione Lambda:
A questo punto non ci resta altro che rendere disponibile il tutto: clicchiamo quindi nuovamente “Actions” quindi “Deploy API”, definiamo per finire il nome dell’ambiente:
Ora abbiamo a disposizione l’indirizzo che useremo come webhook su Telegram per richiamare il nostro servizio:
La documentazione qui: https://aws.amazon.com/it/api-gateway/.
Webhook Bot Telegram
E’ tutto pronto! A questo punto non ci resta che dire a Telegram a quale indirizzo riferirsi per interrogare il nostro bot: ovvero definire il “webhook”.
Per impostarlo procediamo tramite API con il comando visto sopra e, di default, Telegram notificherà tutti gli eventi della chat; se vogliamo evitare di caricare inutilmente il servizio ci conviene limitare le chiamate ai soli aggiornamenti pertinenti, è possibile farlo dettagliando il tipo di richieste durante l’impostazione:
https://api.telegram.org/botTOKEN/setWebhook?url=WEBHOOK_URL&allowed_updates=["message","channel_post"]
Possiamo poi richiedere informazioni riguardo il Webhook tramite:
https://api.telegram.org/botTOKEN/getWebhookInfo
Conclusioni
Abbiamo raggiunto il nostro obiettivo, il bot è pronto per ricevere domande e rispondere a tono.
E’ sicuramente migliorabile in diversi aspetti, il primo probabilmente è la gestione degli errori – praticamente assente – oltre che la sintassi molto basilare.
Come esperimento è comunque sufficiente per capire meglio come funzionano i bot, strumenti in rapida diffusione molto comodi per scopi informativi e ludici e recentemente in sperimentazione anche per scopi commerciali e promozionali da parte di diverse realtà.
Inoltre è stato un buon pretesto per provare la creazione di un servizio serverless: senz’altro una tecnologia molto interessante in quanto permette di creare applicazioni senza costi fissi e perfettamente scalabili, l’ideale per la sperimentazione di nuove idee.
Tutto il codice visto sopra è liberamente disponibile su GitHub all’indirizzo https://github.com/andreacasarin/CryptocurrencyChangeBot, il bot Telegram, inoltre, è attivo, per utilizzarlo è sufficiente aggiungerlo tramite questo link: https://t.me/CryptocurrencyChangeBot.
Non ha comandi diretti ma reagisce a particolari messaggi inviati, ovvero:
Value
è la forma più semplice e restituisce il valore dei Bitcoin in Dollari americani,
Value eth
restituisce il valore di Ethereum (ad esempio) in Dollari americani,
Value eth eur
infine restituisce il valore della valuta richiesta in Euro.
Se ti serve supporto per realizzare il tuo bot Telegram ingaggiami!