diff --git a/app/erfiume/apis.py b/app/erfiume/apis.py
index ade8eb8..1e93774 100644
--- a/app/erfiume/apis.py
+++ b/app/erfiume/apis.py
@@ -17,6 +17,272 @@
UNKNOWN_VALUE = -9999.0
+KNOWN_STATIONS = [
+ "S. Zeno",
+ "Spessa Po",
+ "Parma S. Siro",
+ "Mercato Saraceno",
+ "Fiorenzuola d'Arda",
+ "Fiscaglia Monte",
+ "Navicello",
+ "Camposanto",
+ "Fidenza SIAP",
+ "Codigoro",
+ "Casoni",
+ "Ponte Ronca",
+ "Gallo",
+ "Castenaso",
+ "Correcchio Sillaro",
+ "Beccara Nuova Reno",
+ "Salsominore",
+ "Vigoleno",
+ "Lonza",
+ "Morciano di Romagna",
+ "Pievepelago idro",
+ "Casse Espansione Enza monte",
+ "S. Secondo",
+ "Cassa Crostolo SIAP",
+ "Tornolo",
+ "Parma Ovest",
+ "Rasponi",
+ "Castel San Pietro",
+ "Ponte dell'Olio",
+ "Arcoveggio",
+ "S. Sofia",
+ "Lugo SIAP",
+ "Pieve Cesato",
+ "Cardinala Idice",
+ "Ciriano",
+ "Fossalta",
+ "Fiorano",
+ "Puianello",
+ "Borgo Visignolo",
+ "Cusercoli Idro",
+ "Colorno AIPO",
+ "Ficarolo",
+ "Fusignano",
+ "Foscaglia Panaro",
+ "Teodorano",
+ "Ponte Sant'Ambrogio",
+ "Saletto",
+ "Ponte Val di Sasso",
+ "Case Bonini",
+ "Capoponte",
+ "Ponte Valenza Po",
+ "Secondo Salto",
+ "Ponte Vico",
+ "Sermide",
+ "Ponte Verucchio",
+ "Battiferro Bypass",
+ "Calcara",
+ "Casalmaggiore",
+ "Diga di Ridracoli",
+ "Pontelagoscuro",
+ "Fiscaglia Valle",
+ "Molato Diga Monte",
+ "S. Antonio",
+ "Ponte Lamberti",
+ "Linaro",
+ "Montanaro",
+ "Lugo",
+ "Cremona",
+ "Forcelli",
+ "S. Agata",
+ "Modena Naviglio",
+ "Casalecchio canale",
+ "Ponte Samone",
+ "Bagnetto Reno",
+ "Ponte Alto",
+ "Ponte Messa",
+ "Dosso",
+ "Loiano Ponte Savena",
+ "S. Carlo",
+ "Ponte Braldo",
+ "Vergato",
+ "Mordano",
+ "Castiglione",
+ "Pracchia",
+ "Ponte Becca Po",
+ "Ongina",
+ "Rivergaro",
+ "Vignola SIAP",
+ "S. Zaccaria",
+ "Alseno",
+ "Ramiola",
+ "Savignano",
+ "Strada Casale",
+ "Rocca San Casciano",
+ "S. Marco",
+ "Bobbio",
+ "Casola Valsenio",
+ "Fornovo SIAP",
+ "Pioppa",
+ "Chiavicone Idice",
+ "Ponte Veggia",
+ "La Dozza",
+ "Fanano",
+ "Cadelbosco",
+ "Sostegno Reno",
+ "S. Bartolo",
+ "Correcchio canale",
+ "Canonica Valle",
+ "Mezzano",
+ "Saliceto",
+ "Ponte Nibbiano",
+ "Gandazzolo Reno",
+ "S. Ruffillo Savena",
+ "Farini",
+ "Ostia Parmense",
+ "Bova",
+ "Palesio",
+ "Modigliana",
+ "Paltrone Samoggia",
+ "Ponte Cavola",
+ "Rimini Ausa",
+ "Ponte Bacchello",
+ "Sesto Imolese",
+ "Pontenure",
+ "Chiavica Bastia Sillaro",
+ "Silla",
+ "Ongina Po",
+ "Sorbolo",
+ "Isola S.Antonio PO",
+ "Chiavicone Reno",
+ "Parma Ponte Nuovo",
+ "Rossenna",
+ "Castellina di Soragna",
+ "Pontelagoscuro idrometro Boicelli",
+ "S. Vittoria",
+ "Sarna",
+ "Casale Monferrato Po",
+ "Imola",
+ "Mignano Diga",
+ "Polesella SIAP",
+ "Vetto",
+ "Borello",
+ "Ponte Calanca",
+ "Rivalta RE",
+ "Opera Reno Panfilia",
+ "Tebano",
+ "Parma cassa invaso CAE",
+ "Bazzano",
+ "Alfonsine",
+ "Forli'",
+ "Casalecchio tiro a volo",
+ "Matellica",
+ "Pianoro",
+ "Porretta Terme",
+ "Selvanizza",
+ "Compiano",
+ "Corniglio",
+ "Lavino di Sotto",
+ "Calisese",
+ "Castell'Arquato Canale",
+ "Bentivoglio",
+ "Ponte Felisio",
+ "S. Bernardino",
+ "Ponte Dolo",
+ "Borgoforte",
+ "Luretta",
+ "Marzocchina",
+ "Trebbia Valsigiara",
+ "S. Donnino",
+ "Casse Espansione Enza SIAP",
+ "Bondeno Panaro",
+ "Carignano Po",
+ "Borgo Tossignano",
+ "Accursi Idice",
+ "Isola Pescaroli SIAP",
+ "Ravone Via del Chiu",
+ "Anzola Ghironda",
+ "Ponte Locatello",
+ "Villanova",
+ "Coccolia",
+ "Sasso Marconi",
+ "Santarcangelo di Romagna",
+ "Ponte degli Alpini",
+ "Centonara",
+ "Bevano Adriatica",
+ "Castrocaro",
+ "Codrignano",
+ "S. Ilario d'Enza",
+ "Salsomaggiore sul Ghiara",
+ "Berceto Baganza",
+ "Veggiola",
+ "Vigolo Marchese",
+ "Cesena",
+ "Castelmaggiore",
+ "Casei Gerola Po",
+ "Suviana",
+ "Invaso",
+ "Brocchetti",
+ "Bonconvento",
+ "Cento",
+ "Burana",
+ "Savio",
+ "Fornovo",
+ "Ponte Uso",
+ "S. Cesario SIAP",
+ "Piacenza",
+ "Rubiera casse monte",
+ "Pianello Val Tidone idro",
+ "Conca Diga",
+ "Cavanella SIAP",
+ "Ponte Bastia",
+ "Spilamberto",
+ "Ariano",
+ "S. Maria Nova",
+ "Gatta",
+ "Boretto",
+ "Marsaglia",
+ "Gorzano",
+ "Rimini SS16",
+ "Lavino di Sopra",
+ "Castell'Arquato",
+ "Cotignola",
+ "Parma Ponte Verdi",
+ "Ca' de Caroli",
+ "Fiumalbo",
+ "Rivalta RA",
+ "Cedogno",
+ "Ravone",
+ "Castelbolognese",
+ "Ponte Nibbiano Tidoncello",
+ "Meldola",
+ "Pizzocalvo",
+ "Ponte Motta",
+ "Quarto",
+ "Ponteceno",
+ "Noceto",
+ "Gandazzolo Savena",
+ "Crescentino Po",
+ "Rubiera casse valle",
+ "Monte Cerignone",
+ "Impianto Forcelli Lavino",
+ "Bondanello",
+ "Firenzuola idro",
+ "Ronco",
+ "Rottofreno",
+ "Ferriere Idro",
+ "Bomporto",
+ "Pradella",
+ "Toccalmatto",
+ "Langhirano idro",
+ "Ponte Dattaro",
+ "Marzolara",
+ "Rubiera Tresinaro",
+ "Massarolo",
+ "Opera Po",
+ "Concordia sulla Secchia",
+ "Rubiera SS9",
+ "Marradi",
+ "Casalecchio chiusa",
+ "Reda",
+ "Cabanne",
+ "Faenza",
+ "Portonovo",
+]
+
@dataclass
class Stazione:
diff --git a/app/erfiume/tgbot.py b/app/erfiume/tgbot.py
index 65698ab..11fc8b6 100644
--- a/app/erfiume/tgbot.py
+++ b/app/erfiume/tgbot.py
@@ -17,16 +17,19 @@
MessageHandler,
filters,
)
+from thefuzz import process # type: ignore[import-untyped]
if TYPE_CHECKING:
from aws_lambda_powertools.utilities.typing import LambdaContext
from aws_lambda_powertools.utilities import parameters
+from .apis import KNOWN_STATIONS
from .logging import logger
from .storage import AsyncDynamoDB
RANDOM_SEND_LINK = 10
+FUZZ_SCORE_CUTOFF = 80
# UTILS
@@ -110,7 +113,7 @@ async def start(update: Update, _: ContextTypes.DEFAULT_TYPE | None) -> None:
and update.message
):
user = update.effective_user
- message = rf"Ciao {user.mention_html()}! Scrivi il nome di una stazione da monitorare per iniziare (e.g. Cesena o /S. Carlo)"
+ message = rf"Ciao {user.mention_html()}! Scrivi il nome di una stazione da monitorare per iniziare (e.g. Cesena o /S. Carlo) o cercane una con /stazioni" # noqa: E501
await update.message.reply_html(message)
elif (
is_from_user(update)
@@ -119,7 +122,7 @@ async def start(update: Update, _: ContextTypes.DEFAULT_TYPE | None) -> None:
and update.message
):
chat = update.effective_chat
- message = rf"Ciao {chat.title}! Per iniziare scrivete il nome di una stazione da monitorare (e.g. /Cesena o /S. Carlo)"
+ message = rf"Ciao {chat.title}! Per iniziare scrivete il nome di una stazione da monitorare (e.g. /Cesena o /S. Carlo) o cercane una con /stazioni" # noqa: E501
await update.message.reply_html(message)
@@ -136,6 +139,28 @@ async def cesena(update: Update, _: ContextTypes.DEFAULT_TYPE) -> None:
)
+async def list_stations(update: Update, _: ContextTypes.DEFAULT_TYPE) -> None:
+ """Send a message when the command /cesena is issued."""
+ if update.message:
+ await update.message.reply_html("\n".join(KNOWN_STATIONS))
+
+
+async def info(update: Update, _: ContextTypes.DEFAULT_TYPE) -> None:
+ """Send a message when the command /cesena is issued."""
+ message = cleandoc(
+ """
+ Bot Telegram che permette di leggere i livelli idrometrici dei fiumi dell'Emilia Romagna.
+ I dati idrometrici sono ottenuti dalle API messe a disposizione da allertameteo.regione.emilia-romagna.it.
+ Il progetto è completamente open-source (https://github.com/notdodo/erfiume_bot).
+ Per donazioni per mantenere il servizio attivo: buymeacoffee.com/d0d0
+
+ Inizia con /start o /stazioni
+ """
+ )
+ if update.message:
+ await update.message.reply_html(message, disable_web_page_preview=True)
+
+
async def handle_private_message(
update: Update, context: ContextTypes.DEFAULT_TYPE
) -> None:
@@ -146,16 +171,22 @@ async def handle_private_message(
message = cleandoc(
"""Stazione non trovata!
Inserisci esattamente il nome che vedi dalla pagina https://allertameteo.regione.emilia-romagna.it/livello-idrometrico
- Ad esempio 'Cesena', 'Lavino di Sopra' o 'S. Carlo'"""
+ Ad esempio 'Cesena', 'Lavino di Sopra' o 'S. Carlo'.
+ Se non sai quale cercare prova con /stazioni"""
)
if update.message and update.effective_chat and update.message.text:
- logger.info("Received private message: %s", update.message.text)
async with AsyncDynamoDB(table_name="Stazioni") as dynamo:
- stazione = await dynamo.get_matching_station(
- update.message.text.replace("/", "").strip()
+ query = update.message.text.replace("/", "").strip()
+ fuzzy_query = process.extractOne(
+ query, KNOWN_STATIONS, score_cutoff=FUZZ_SCORE_CUTOFF
)
- if stazione and update.message:
- message = stazione.create_station_message()
+ logger.info(query)
+ if fuzzy_query:
+ stazione = await dynamo.get_matching_station(fuzzy_query[0])
+ if stazione and update.message:
+ message = stazione.create_station_message()
+ if query != fuzzy_query[0]:
+ message += "\nSe non è la stazione corretta prova ad affinare la ricerca."
await context.bot.send_message(
chat_id=update.effective_chat.id,
text=message,
@@ -173,16 +204,24 @@ async def handle_group_message(
message = cleandoc(
"""Stazione non trovata!
Inserisci esattamente il nome che vedi dalla pagina https://allertameteo.regione.emilia-romagna.it/livello-idrometrico
- Ad esempio '/Cesena', '/Lavino di Sopra' o '/S. Carlo'"""
+ Ad esempio '/Cesena', '/Lavino di Sopra' o '/S. Carlo'.
+ Se non sai quale cercare prova con /stazioni"""
)
if update.message and update.effective_chat and update.message.text:
- logger.info("Received group message: %s", update.message.text)
async with AsyncDynamoDB(table_name="Stazioni") as dynamo:
- stazione = await dynamo.get_matching_station(
+ query = (
update.message.text.replace("/", "").replace("erfiume_bot", "").strip()
)
- if stazione and update.message:
- message = stazione.create_station_message()
+ fuzzy_query = process.extractOne(
+ query, KNOWN_STATIONS, score_cutoff=FUZZ_SCORE_CUTOFF
+ )
+ logger.info(query)
+ if fuzzy_query:
+ stazione = await dynamo.get_matching_station(fuzzy_query[0])
+ if stazione and update.message:
+ message = stazione.create_station_message()
+ if query != fuzzy_query[0]:
+ message += "\nSe non é la stazione corretta prova ad affinare la ricerca."
await context.bot.send_message(
chat_id=update.effective_chat.id,
text=message,
@@ -199,6 +238,8 @@ async def bot(event: dict[str, Any], _context: LambdaContext) -> None:
application.add_handler(CommandHandler("start", start))
application.add_handler(CommandHandler("cesena", cesena))
+ application.add_handler(CommandHandler("stazioni", list_stations))
+ application.add_handler(CommandHandler("info", info))
application.add_handler(
MessageHandler(
filters.ChatType.PRIVATE & (filters.TEXT | filters.COMMAND),
diff --git a/app/erfiume_bot.py b/app/erfiume_bot.py
index 100edb2..8f9d4d6 100644
--- a/app/erfiume_bot.py
+++ b/app/erfiume_bot.py
@@ -25,5 +25,4 @@ def handler(event: dict[str, Any], context: LambdaContext) -> dict[str, Any]:
logger.exception(traceback.format_exc())
return {"statusCode": 501}
- logger.info("Successfully processed event")
return {"statusCode": 200}
diff --git a/app/poetry.lock b/app/poetry.lock
index f84d17a..6b430a4 100644
--- a/app/poetry.lock
+++ b/app/poetry.lock
@@ -2333,6 +2333,20 @@ files = [
{file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"},
]
+[[package]]
+name = "thefuzz"
+version = "0.22.1"
+description = "Fuzzy string matching in python"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "thefuzz-0.22.1-py3-none-any.whl", hash = "sha256:59729b33556850b90e1093c4cf9e618af6f2e4c985df193fdf3c5b5cf02ca481"},
+ {file = "thefuzz-0.22.1.tar.gz", hash = "sha256:7138039a7ecf540da323792d8592ef9902b1d79eb78c147d4f20664de79f3680"},
+]
+
+[package.dependencies]
+rapidfuzz = ">=3.0.0,<4.0.0"
+
[[package]]
name = "tomlkit"
version = "0.13.2"
@@ -3520,4 +3534,4 @@ multidict = ">=4.0"
[metadata]
lock-version = "2.0"
python-versions = "^3.12"
-content-hash = "bafd51aacf0a900831407dee4da7a10e7469a7ddfb30c41fa7beaafc9420fac9"
+content-hash = "9bb664df4cccdb34bb5af6322b7302ec83748dc5b62911e61894efce2f60e550"
diff --git a/app/pyproject.toml b/app/pyproject.toml
index 8c30c01..57b2b17 100644
--- a/app/pyproject.toml
+++ b/app/pyproject.toml
@@ -13,6 +13,7 @@ aioboto3 = "^13.1.1"
poetry-dotenv-plugin = "^0.2.0"
python = "^3.12"
python-telegram-bot = "^21.5"
+thefuzz = "^0.22.1"
[tool.poetry.group.dev.dependencies]
awscli-local = "^0.22.0"