from datetime import datetime, timedelta, timezone import base64 import json import logging import os import urllib.error import urllib.parse import urllib.request import re from PIL import Image, ImageDraw, ImageFont, ImageOps class SNCFAPI: CLEANR = re.compile('<.*?>') def __init__(self, config): self.baseurl = "https://www.sncf.com/api/iv" self.auth = base64.b64encode(b"admin:$2y$10$QvxWSS4f5DIFSkAmuBoV9OJG3M2bhec9d3F2.YnULBMtpzKAq2KS.").decode() self._cached_file = ".sncf-%d-%s.cache" self.cache_timeout = config.cache_timeout self.max_cache_timeout = config.max_cache_timeout def get_icon(size): height = int(size * 0.531) img = Image.open("icons/sncf.png").resize((size, height)) r,g,b,a = img.split() rgb_image = Image.merge('RGB', (r,g,b)) inverted_image = ImageOps.invert(rgb_image) r2,g2,b2 = inverted_image.split() return Image.merge('RGBA', (r2,g2,b2,a)) def get_train_status(self, numero, date): cache_file = self._cached_file % (int(numero), date.strftime("%Y-%m-%d")) # Read the mod time statinfo = None try: statinfo = os.stat(cache_file) except: pass if statinfo is None or datetime.fromtimestamp(statinfo.st_mtime, tz=timezone.utc) + timedelta(minutes=self.cache_timeout) < datetime.now(tz=timezone.utc): # Do the request and save it req = urllib.request.Request(self.baseurl + "/1.0/infoVoy/rechercherListeCirculations?numero=%d&dateCirculation=%s&codeZoneArret&typeHoraire=TEMPS_REEL" % (int(numero), date.strftime("%Y-%m-%d")), headers={ 'Authorization': "Basic " + self.auth, 'Accept': "application/json", 'Accept-Language': "fr,fr-FR;q=0.8,en-US;q=0.5,en;q=0.3", 'User-Agent': "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36", }) try: with urllib.request.urlopen(req) as f: with open(cache_file, 'wb') as fd: fd.write(f.read()) except ConnectionResetError: pass except urllib.error.URLError: pass try: statinfo = os.stat(cache_file) except: pass if statinfo is None or datetime.fromtimestamp(statinfo.st_mtime, tz=timezone.utc) + timedelta(minutes=self.max_cache_timeout) < datetime.now(tz=timezone.utc): print(self.baseurl + "/1.0/infoVoy/rechercherListeCirculations?numero=%d&dateCirculation=%s&codeZoneArret&typeHoraire=TEMPS_REEL" % (int(numero), date.strftime("%Y-%m-%d"))) logging.exception(Exception("File too old to predict SNCF issue")) return None # Retrieve cached data res = {} with open(cache_file) as f: res = json.load(f) try: return res["reponseRechercherListeCirculations"]["reponse"]["listeResultats"]["resultat"][0]["donnees"]["listeCirculations"]["circulation"][0] except: return None def get_weather(self, region): cache_file = self._cached_file % (0, region) # Read the mod time statinfo = None try: statinfo = os.stat(cache_file) except: pass if statinfo is None or datetime.fromtimestamp(statinfo.st_mtime, tz=timezone.utc) + timedelta(minutes=self.cache_timeout) < datetime.now(tz=timezone.utc): # Do the request and save it req = urllib.request.Request(self.baseurl + "/edito/bandeaux?region=%s" % (region), headers={ 'Authorization': "Basic " + self.auth, 'Accept': "application/json", 'Accept-Language': "fr,fr-FR;q=0.8,en-US;q=0.5,en;q=0.3", 'User-Agent': "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36", }) try: with urllib.request.urlopen(req, timeout=3) as f: with open(cache_file, 'wb') as fd: fd.write(f.read()) except ConnectionResetError as e: logging.exception(e) except urllib.error.URLError as e: logging.exception(e) except TimeoutError as e: logging.exception(e) try: statinfo = os.stat(cache_file) except: pass if statinfo is None or datetime.fromtimestamp(statinfo.st_mtime, tz=timezone.utc) + timedelta(minutes=self.max_cache_timeout) < datetime.now(tz=timezone.utc): raise Exception("File too old") # Retrieve cached data res = {} with open(cache_file) as f: res = json.load(f) return res class SNCFWeatherModule: def __init__(self): pass def gen_alerts(self, config, region): alerts = [] weather = SNCFAPI(config).get_weather(region) for alert in weather: if alert["type"] != "perturbation": continue yield { "description": re.sub(SNCFAPI.CLEANR, '\n', alert["content"]).replace('Restez informés sur le fil', '').replace('Twitter @train_nomad', '').strip(), "icon": SNCFAPI.get_icon, }