diff options
| author | Ariel Costas Guerrero <ariel@costas.dev> | 2026-01-26 12:58:46 +0100 |
|---|---|---|
| committer | Ariel Costas Guerrero <ariel@costas.dev> | 2026-01-26 12:58:46 +0100 |
| commit | 83b140c93055e1c617a1c3a78d23465c5db1906e (patch) | |
| tree | 4e5298c42e86f0c66194fb7aed497acdfe9680bc | |
| parent | 986e7281d3f9903794c399f7253bca4948d9b101 (diff) | |
Add script to monitor Vitrasa feed and build when needed
| -rw-r--r-- | .gitignore | 3 | ||||
| -rw-r--r-- | build-config.json | 3 | ||||
| -rw-r--r-- | vitrasa_gtfs_ntfy/main.py | 126 |
3 files changed, 131 insertions, 1 deletions
@@ -11,3 +11,6 @@ feeds/*.zip # Python cache files *.pyc + +# Config files +vitrasa_gtfs_ntfy/config.json
\ No newline at end of file diff --git a/build-config.json b/build-config.json index 2d4e4bb..4199981 100644 --- a/build-config.json +++ b/build-config.json @@ -33,7 +33,8 @@ ], "osm": [ { - "source": "./galicia-latest.osm.pbf" + "source": "./galicia-latest.osm.pbf", + "timeZone": "Europe/Madrid" } ], "transitServiceStart": "-P3D", diff --git a/vitrasa_gtfs_ntfy/main.py b/vitrasa_gtfs_ntfy/main.py new file mode 100644 index 0000000..cd2efdb --- /dev/null +++ b/vitrasa_gtfs_ntfy/main.py @@ -0,0 +1,126 @@ +# /// script +# requires-python = ">=3.13" +# dependencies = [ +# "requests", +# ] +# /// + + +import json +import requests +import os +from datetime import datetime, timezone +from email.utils import parsedate_to_datetime + + +class Config: + gtfs_url: str = "" + ntfy_topic: str = "" + lastfeed: str = "" + file_to_monitor: str = "" + + def __init__( + self, gtfs_url: str, ntfy_topic: str, lastfeed: str, file_to_monitor: str + ) -> None: + self.gtfs_url = gtfs_url + self.ntfy_topic = ntfy_topic + self.lastfeed = lastfeed + self.file_to_monitor = file_to_monitor + + +def load_config(config_path: str) -> Config: + """Load the lastfeed data from a local source.""" + with open(config_path, "r", encoding="utf-8") as f: + data = json.load(f) + + return Config( + data["gtfs_url"], + data["ntfy_topic"], + data["lastfeed"], + data.get("file_to_monitor", ""), + ) + + +def save_config(config_path: str, config: Config) -> None: + """Save the lastfeed data to a local source.""" + with open(config_path, "w", encoding="utf-8") as f: + json.dump( + { + "gtfs_url": config.gtfs_url, + "ntfy_topic": config.ntfy_topic, + "lastfeed": config.lastfeed, + "file_to_monitor": config.file_to_monitor, + }, + f, + ensure_ascii=False, + indent=4, + ) + + +def load_gtfs_last_modified(url: str) -> datetime | None: + """Perform a HEAD request to the GTFS service to get last modification date.""" + response = requests.head(url, timeout=10) + response.raise_for_status() + last_modified = response.headers.get("Last-Modified") + if last_modified: + return parsedate_to_datetime(last_modified) + return None + + +def push_ntfy(topic: str, message: str, title: str, priority: str = "default", tags: str = "") -> None: + """Push notification using ntfy service.""" + requests.post( + f"https://ntfy.sh/{topic}", + data=message.encode("utf-8"), + headers={ + "Priority": priority, + "Tags": tags, + "Title": title + }, + timeout=10 + ) + + +if __name__ == "__main__": + # Get the directory where the script is located + script_dir = os.path.dirname(os.path.abspath(__file__)) + config_path = os.path.join(script_dir, "config.json") + + conf = load_config(config_path) + + current_last_modified = load_gtfs_last_modified(conf.gtfs_url) + if not current_last_modified: + print("Could not get last modification date.") + else: + # datetime.fromisoformat supports 'Z' in Python 3.11+ + stored_last_modified = datetime.fromisoformat(conf.lastfeed) + + # 1. Check if server has a NEWER feed than what we LAST NOTIFIED about + if current_last_modified > stored_last_modified: + print(f"New GTFS found: {current_last_modified.isoformat()}") + msg = f"Nuevo GTFS de Vitrasa con fecha {current_last_modified.isoformat()}" + push_ntfy(conf.ntfy_topic, msg, "Nuevo GTFS listo", priority="high", tags="rotating_light") + + # Update config with new date + conf.lastfeed = current_last_modified.isoformat().replace("+00:00", "Z") + save_config(config_path, conf) + else: + print("GTFS server date has not changed since last notification.") + + # 2. Check if the LOCAL file is currently outdated compared to the server + if conf.file_to_monitor: + # Resolve relative path from script location if necessary + full_path = os.path.normpath(os.path.join(script_dir, conf.file_to_monitor)) + if os.path.exists(full_path): + file_mtime = datetime.fromtimestamp(os.path.getmtime(full_path), tz=timezone.utc) + if file_mtime < current_last_modified: + print(f"Warning: Local file {conf.file_to_monitor} is outdated.") + msg = ( + f"El archivo local {os.path.basename(conf.file_to_monitor)} " + f"es anterior al feed disponible ({current_last_modified.isoformat()})" + ) + push_ntfy(conf.ntfy_topic, msg, "Archivo GTFS desactualizado", priority="default", tags="warning") + else: + print(f"Local file {conf.file_to_monitor} is up to date.") + else: + print(f"File to monitor not found: {full_path}") |
