diff --git a/classes/station.py b/classes/station.py index ec8a33a..81988c9 100644 --- a/classes/station.py +++ b/classes/station.py @@ -3,9 +3,13 @@ class Station: self.name = name self.sttype = sttype self.extid = extid - self.xcoord = float(xcoord)/1000000 - self.ycoord = float(ycoord)/1000000 + self.xcoord = float(xcoord)/1000000 if xcoord else None + self.ycoord = float(ycoord)/1000000 if ycoord else None self.prodclass = prodclass + self.services = [] + + def addService(self, svc): + self.services += [svc] def useId(self): return self.extid or self.name @@ -16,7 +20,7 @@ class Station: def lon(self): return self.xcoord - def json(self, indent = 0, name = True, extid = True, sttype = False, coords = False, prodclass = False, distance = False): + def json(self, indent = 0, name = True, extid = True, sttype = False, coords = False, prodclass = False, distance = False, services = False, servicekwargs = {}): out = " " * indent + "{\n" out += (" " * indent + " \"name\": \"%s\",\n" % self.name) if name else "" @@ -24,12 +28,20 @@ class Station: out += (" " * indent + " \"distance\": %i,\n" % int(self.distance)) if distance else "" out += (" " * indent + " \"type\": \"%s\",\n" % self.sttype) if sttype else "" - if coords: + if coords and self.xcoord: out += " " * indent + " \"coords\": {\n" out += " " * indent + " \"lon\": %f,\n" % self.xcoord out += " " * indent + " \"lat\": %f\n" % self.ycoord out += " " * indent + " },\n" + if services and self.services: + out += " " * indent + " \"services\": [\n" + + for i in range(len(self.services)): + out += self.services[i].json(indent + 2, i, **servicekwargs) + (",\n" if not i == len(self.services) - 1 else "\n") + + out += " " * indent + " ]," + out += (" " * indent + " \"prodclass\": \"%s\",\n" % self.prodclass) if prodclass else "" out = "".join(out.rsplit(",", 1)) @@ -38,7 +50,7 @@ class Station: return out - def xml(self, indent = 0, name = True, extid = True, sttype = False, coords = False, prodclass = False, distance = False): + def xml(self, indent = 0, name = True, extid = True, sttype = False, coords = False, prodclass = False, distance = False, services = False, servicekwargs = {}): out = " " * indent + "\n" out += (" " * indent + " %s\n" % self.name) if name else "" @@ -46,12 +58,20 @@ class Station: out += (" " * indent + " %i\n" % int(self.distance)) if distance else "" out += (" " * indent + " %s\n" % self.sttype) if sttype else "" - if coords: + if coords and self.xcoord: out += " " * indent + " \n" out += " " * indent + " %f\n" % self.xcoord out += " " * indent + " %f\n" % self.ycoord out += " " * indent + " \n" + if services and self.services: + out += " " * indent + " \n" + + for i in range(len(self.services)): + out += self.services[i].xml(indent + 2, i, **servicekwargs) + "\n" + + out += " " * indent + " \n" + out += (" " * indent + " %s\n" % self.prodclass) if prodclass else "" out += " " * indent + "" diff --git a/main.py b/main.py index a655ed7..f6e2df3 100644 --- a/main.py +++ b/main.py @@ -6,6 +6,7 @@ import workers.conn import workers.val import workers.closest import workers.radar +import workers.deparr from classes.request import * from classes.response import * @@ -151,6 +152,49 @@ def doRadar(req): return Response(HTTP200, JSON if req.json else XML, content) +def doDepArr(req): + try: + name = req.args["name"][0] + try: + name = name.encode("latin-1").decode("utf-8") + except UnicodeDecodeError: + pass + + except Exception: + content = "

400 Bad Request

\n" + content += "A \"name\" value is required for this type of request." + return Response(HTTP400, HTML, content) + + count = req.args["count"][0] if "count" in req.args and req.args["count"] else 30 + date = req.args["date"][0] if "date" in req.args and req.args["date"] else datetime.datetime.strftime(datetime.datetime.now(pytz.timezone("Europe/Vienna")),"%d.%m.%Y") + time = req.args["time"][0] if "time" in req.args and req.args["time"] else datetime.datetime.strftime(datetime.datetime.now(pytz.timezone("Europe/Vienna")),"%H:%M") + mode = True if req.rtype[:3] == "arr" else False + details = True + + try: + count = int(count) + except: + content = "

400 Bad Request

\n" + content += "The \"count\" value must be a numeric value." + return Response(HTTP400, HTML, content) + + try: + outtime = datetime.datetime.strptime("%s %s" % (date, time), "%d.%m.%Y %H:%M") + except: + content = "

400 Bad Request

\n" + content += "The \"date\" value must be in DD.MM.YYYY format, the \"time\" value must be in HH:MM format." + return Response(HTTP400, HTML, content) + + try: + content = workers.deparr.worker(name, count, outtime, mode, details, req.json) + except Exception as e: + content = "

500 Internal Server Error

\n" + if "debug" in req.args: + content += str(e) + return Response(HTTP500, HTML, content) + + return Response(HTTP200, JSON if req.json else XML, content) + def doNot(req): content = "

400 Bad Request

" content += "The request type you submitted is invalid." @@ -160,14 +204,16 @@ def application(env, re): try: req = Request(env) - if req.rtype in ["conn", "connection"]: + if req.rtype in ["conn", "connection", "connections"]: res = doConn(req) elif req.rtype in ["val", "validate"]: res = doVal(req) elif req.rtype in ["closest", "close", "near", "nearby"]: res = doNearby(req) - elif req.rtype in ["radar", "live"]: + elif req.rtype in ["radar", "live", "trains"]: res = doRadar(req) + elif req.rtype in ["dep", "arr", "departure", "arrival", "departures", "arrivals"]: + res = doDepArr(req) else: res = doNot(req) diff --git a/workers/conn.py b/workers/conn.py index d1fec89..e5d6c59 100644 --- a/workers/conn.py +++ b/workers/conn.py @@ -65,6 +65,7 @@ def getService(sid, lines, q, eq = None): except Exception as e: if eq: eq.put(sys.exc_info()) + raise def getDetails(cid, url, q, via = [], eq = None): try: @@ -127,6 +128,7 @@ def getDetails(cid, url, q, via = [], eq = None): except: if eq: eq.put(sys.exc_info()) + raise def connRequest(frm, to, count = 3, time = datetime.datetime.now(), mode = False, details = False, via = []): outdate = datetime.datetime.strftime(time, "%d.%m.%Y") diff --git a/workers/deparr.py b/workers/deparr.py new file mode 100644 index 0000000..2737d78 --- /dev/null +++ b/workers/deparr.py @@ -0,0 +1,103 @@ +from bs4 import BeautifulSoup +import datetime +import pytz +import threading +import queue +import sys + +import workers.val +from classes import * + +def getStation(name): + return list(workers.val.validateName(name))[0] + +def getService(sid, url, dtime, q = None, eq = None): + try: + zuppa = BeautifulSoup(HTTPClient().get(url).text, "html5lib") + + name = zuppa.findAll("div", { "class": "block" })[0].text.strip().replace("(Zug-Nr. ", " - ").replace(")", "") + ddate = zuppa.findAll("div", { "class": "block" })[1].text.strip() + + table = zuppa.find("table", { "class": "resultTable" }) + rows = table.findAll("tr")[1:] + + for row in rows: + if len(row.findAll("td")) > 6 and row.findAll("td")[4].text.strip() == dtime: + depst = getStation(row.findAll("td")[1].text.strip()) + currdep = row.findAll("td")[5].text.strip() + currdep = dtime if currdep == "pünktlich" else currdep or None + deppf = row.findAll("td")[-1].text.strip() or None + + dest = getStation(rows[-1].findAll("td")[1].text.strip()) + atime = rows[-1].findAll("td")[2].text.strip() + curarr = rows[-1].findAll("td")[3].text.strip() + curarr = atime if curarr == "pünktlich" else curarr or None + arrpf = rows[-1].findAll("td")[-1].text.strip() + + deptime = datetime.datetime.strptime("%s %s" % (ddate, dtime), "%d.%m.%Y %H:%M") + arrtime = datetime.datetime.strptime("%s %s" % (ddate, atime), "%d.%m.%Y %H:%M") + + if arrtime < deptime: + arrtime += datetime.timedelta(days = 1) + + if q: + q.put((sid, Service(name, depst, deptime, dest, arrtime, dest, deppf, currdep, arrpf, curarr))) + return q + + except: + if eq: + eq.put(sys.exc_info()) + raise + +def daRequest(station, count = 3, time = datetime.datetime.now(), mode = False, details = False): + outdate = datetime.datetime.strftime(time, "%d.%m.%Y") + outtime = datetime.datetime.strftime(time, "%H:%M") + + url = "http://fahrplan.oebb.at/bin/stboard.exe/dn?input=%s&boardType=%s&time=%s&productsFilter=1111111111111111&dateBegin=%s&dateEnd=&selectDate=&maxJourneys=%i&start=yes&dirInput=&sqView=2" % (station.extid if station.extid else station.name, "arr" if mode else "dep", outtime, outdate, count) + + source = HTTPClient().get(url).text + + if "traininfo.exe/dn/" not in source: + raise ValueError("No services found.") + + juha = BeautifulSoup(source, "html5lib") + + services = [] + + table = juha.find("table", {"class": "resultTable"}) + + for row in table.findAll("tr")[1:-1]: + if not len(row.findAll("td")) < 4: + services += [(row.findAll("a")[0].get("href"), row.findAll("td")[0].text.strip())] + + threads = [] + eq = queue.Queue() + q = queue.PriorityQueue() + + for i in range(len(services)): + t = threading.Thread(target=getService, args=(i, services[i][0], services[i][1], q, eq)) + t.start() + threads += [t] + + for t in threads: + t.join() + + if not eq.empty(): + exc = eq.get() + raise exc[1].with_traceback(exc[2]) + + while not q.empty(): + station.addService(q.get()[1]) + + return station + +def worker(station, count = 30, time = datetime.datetime.now(pytz.timezone("Europe/Vienna")), mode = False, details = False, json = False): + station = daRequest(getStation(station), count, time, mode, details) + + if json: + output = station.json(services = True) + else: + output = """\n""" + output += station.xml(services = True) + + return output