2022-08-19 11:59:43 +00:00
|
|
|
from datetime import datetime, timedelta, timezone
|
|
|
|
import base64
|
|
|
|
import json
|
2022-12-30 19:25:26 +00:00
|
|
|
import logging
|
2022-08-19 11:59:43 +00:00
|
|
|
import os
|
2022-08-19 14:58:30 +00:00
|
|
|
import urllib.error
|
2022-08-19 11:59:43 +00:00
|
|
|
import urllib.parse
|
|
|
|
import urllib.request
|
|
|
|
import re
|
|
|
|
|
|
|
|
from PIL import Image, ImageDraw, ImageFont, ImageOps
|
|
|
|
|
|
|
|
class SNCFAPI:
|
|
|
|
|
|
|
|
CLEANR = re.compile('<.*?>')
|
|
|
|
|
2022-12-30 18:54:21 +00:00
|
|
|
def __init__(self, config):
|
2022-08-19 11:59:43 +00:00
|
|
|
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"
|
2022-12-30 18:54:21 +00:00
|
|
|
self.cache_timeout = config.cache_timeout
|
|
|
|
self.max_cache_timeout = config.max_cache_timeout
|
2022-08-19 11:59:43 +00:00
|
|
|
|
|
|
|
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):
|
2022-08-21 00:12:53 +00:00
|
|
|
cache_file = self._cached_file % (int(numero), date.strftime("%Y-%m-%d"))
|
2022-08-19 11:59:43 +00:00
|
|
|
# Read the mod time
|
|
|
|
statinfo = None
|
|
|
|
try:
|
|
|
|
statinfo = os.stat(cache_file)
|
|
|
|
except:
|
|
|
|
pass
|
|
|
|
|
2022-12-30 18:54:21 +00:00
|
|
|
if statinfo is None or datetime.fromtimestamp(statinfo.st_mtime, tz=timezone.utc) + timedelta(minutes=self.cache_timeout) < datetime.now(tz=timezone.utc):
|
2022-08-19 11:59:43 +00:00
|
|
|
# Do the request and save it
|
2022-12-30 19:25:26 +00:00
|
|
|
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",
|
|
|
|
})
|
2022-08-19 11:59:43 +00:00
|
|
|
try:
|
|
|
|
with urllib.request.urlopen(req) as f:
|
|
|
|
with open(cache_file, 'wb') as fd:
|
|
|
|
fd.write(f.read())
|
|
|
|
except ConnectionResetError:
|
|
|
|
pass
|
2022-08-19 14:58:30 +00:00
|
|
|
except urllib.error.URLError:
|
|
|
|
pass
|
2022-08-19 11:59:43 +00:00
|
|
|
|
2022-12-30 18:54:21 +00:00
|
|
|
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):
|
2023-01-16 12:48:41 +00:00
|
|
|
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
|
2022-12-30 18:54:21 +00:00
|
|
|
|
2022-08-19 11:59:43 +00:00
|
|
|
# Retrieve cached data
|
|
|
|
res = {}
|
|
|
|
with open(cache_file) as f:
|
|
|
|
res = json.load(f)
|
|
|
|
|
2022-08-21 00:12:53 +00:00
|
|
|
try:
|
|
|
|
return res["reponseRechercherListeCirculations"]["reponse"]["listeResultats"]["resultat"][0]["donnees"]["listeCirculations"]["circulation"][0]
|
|
|
|
except:
|
|
|
|
return None
|
2022-08-19 11:59:43 +00:00
|
|
|
|
|
|
|
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
|
|
|
|
|
2022-12-30 18:54:21 +00:00
|
|
|
if statinfo is None or datetime.fromtimestamp(statinfo.st_mtime, tz=timezone.utc) + timedelta(minutes=self.cache_timeout) < datetime.now(tz=timezone.utc):
|
2022-08-19 11:59:43 +00:00
|
|
|
# Do the request and save it
|
2022-12-30 19:25:26 +00:00
|
|
|
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",
|
|
|
|
})
|
2022-08-19 11:59:43 +00:00
|
|
|
try:
|
2022-12-30 19:25:26 +00:00
|
|
|
with urllib.request.urlopen(req, timeout=3) as f:
|
2022-08-19 11:59:43 +00:00
|
|
|
with open(cache_file, 'wb') as fd:
|
|
|
|
fd.write(f.read())
|
2022-12-30 19:25:26 +00:00
|
|
|
except ConnectionResetError as e:
|
|
|
|
logging.exception(e)
|
|
|
|
except urllib.error.URLError as e:
|
|
|
|
logging.exception(e)
|
|
|
|
except TimeoutError as e:
|
|
|
|
logging.exception(e)
|
2022-08-19 11:59:43 +00:00
|
|
|
|
2022-12-30 18:54:21 +00:00
|
|
|
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")
|
|
|
|
|
2022-08-19 11:59:43 +00:00
|
|
|
# Retrieve cached data
|
|
|
|
res = {}
|
|
|
|
with open(cache_file) as f:
|
|
|
|
res = json.load(f)
|
|
|
|
|
|
|
|
return res
|
|
|
|
|
|
|
|
|
|
|
|
class SNCFWeatherModule:
|
|
|
|
|
|
|
|
def __init__(self):
|
|
|
|
pass
|
|
|
|
|
2022-12-30 18:54:21 +00:00
|
|
|
def gen_alerts(self, config, region):
|
2022-08-19 11:59:43 +00:00
|
|
|
alerts = []
|
|
|
|
|
2022-12-30 18:54:21 +00:00
|
|
|
weather = SNCFAPI(config).get_weather(region)
|
2022-08-19 11:59:43 +00:00
|
|
|
for alert in weather:
|
|
|
|
if alert["type"] != "perturbation":
|
|
|
|
continue
|
|
|
|
|
|
|
|
yield {
|
2022-08-19 14:59:10 +00:00
|
|
|
"description": re.sub(SNCFAPI.CLEANR, '\n', alert["content"]).replace('Restez informés sur le fil', '').replace('Twitter @train_nomad', '').strip(),
|
2022-08-19 11:59:43 +00:00
|
|
|
"icon": SNCFAPI.get_icon,
|
|
|
|
}
|