97 lines
2.5 KiB
Python
97 lines
2.5 KiB
Python
|
#!/usr/bin/python3
|
||
|
|
||
|
import os
|
||
|
from dataclasses import dataclass
|
||
|
from datetime import UTC, datetime
|
||
|
from decimal import Decimal
|
||
|
|
||
|
import flask
|
||
|
import lxml.html
|
||
|
import pytz
|
||
|
|
||
|
app = flask.Flask(__name__)
|
||
|
app.debug = True
|
||
|
|
||
|
data_loc = os.path.expanduser("~/lib/data/eurotunnel")
|
||
|
|
||
|
|
||
|
def get_filename(direction: str) -> tuple[datetime, str]:
|
||
|
"""Most recent list of outbound prices."""
|
||
|
end = f"_{direction}.html"
|
||
|
most_recent = max([f for f in os.listdir(data_loc) if f.endswith(end)])
|
||
|
filename = os.path.join(data_loc, most_recent)
|
||
|
timestamp = most_recent.removesuffix(end)
|
||
|
dt_utc = datetime.strptime(timestamp, "%Y-%m-%d_%H%M%S").replace(tzinfo=UTC)
|
||
|
dt = dt_utc.astimezone(pytz.timezone("Europe/London"))
|
||
|
return (dt, filename)
|
||
|
|
||
|
|
||
|
@dataclass
|
||
|
class Train:
|
||
|
"""Eurostar train."""
|
||
|
|
||
|
dep: str
|
||
|
arr: str
|
||
|
price: Decimal | None = None
|
||
|
|
||
|
|
||
|
def get_tickets(filename: str) -> list[Train]:
|
||
|
"""Get trains and prices."""
|
||
|
tree = lxml.html.parse(filename)
|
||
|
root = tree.getroot()
|
||
|
|
||
|
trains = []
|
||
|
by_time = {}
|
||
|
|
||
|
for mission in root.findall(".//div[@data-mission]"):
|
||
|
dep_text = mission.findtext(".//b")
|
||
|
assert dep_text
|
||
|
dep = dep_text.replace(":", "")
|
||
|
arr_text = mission.findtext(".//small")
|
||
|
assert arr_text
|
||
|
arr = arr_text.removeprefix("Arrive ").replace(":", "")
|
||
|
item = Train(dep=dep, arr=arr)
|
||
|
trains.append(item)
|
||
|
by_time[dep] = item
|
||
|
|
||
|
for ticket in root.xpath("//div[contains(@class, 'ticket')]"):
|
||
|
classes = ticket.get("class").split(" ")
|
||
|
if "ticket" not in classes:
|
||
|
continue
|
||
|
ticket_class = classes[-1]
|
||
|
if ticket_class != "standard":
|
||
|
continue
|
||
|
|
||
|
mission_time = ticket.getparent().getparent().get("data-mission-time")
|
||
|
|
||
|
onclick = ticket.get("onclick").split(",")
|
||
|
price = Decimal(onclick[2][1:-1])
|
||
|
|
||
|
by_time[mission_time].price = price
|
||
|
|
||
|
return trains
|
||
|
|
||
|
|
||
|
@app.route("/")
|
||
|
def index() -> str:
|
||
|
"""Index."""
|
||
|
out_ts, out_filename = get_filename("outbound")
|
||
|
out = get_tickets(out_filename)
|
||
|
out = [t for t in out if t.dep > "0800" and "0700" < t.arr < "2200"]
|
||
|
|
||
|
back_ts, back_filename = get_filename("return")
|
||
|
back = get_tickets(back_filename)
|
||
|
back = [t for t in back if t.dep > "1100" and "0700" < t.arr < "2200"]
|
||
|
|
||
|
return flask.render_template(
|
||
|
"index.html",
|
||
|
out_ts=out_ts,
|
||
|
out=out,
|
||
|
back_ts=back_ts,
|
||
|
back=back,
|
||
|
)
|
||
|
|
||
|
|
||
|
if __name__ == "__main__":
|
||
|
app.run(host="0.0.0.0")
|