import std/[httpclient, cookies, uri, files, paths, syncio, cgi, times, strutils, tables, json] import std/math import std/times import pkg/htmlparser import std/xmltree # To use '$' for XmlNode import std/strtabs # To access XmlAttributes import std/strutils # To use cmpIgnoreCase var tokenFile = Path("/tmp/csrf-token") var csrf: JsonNode var dbregioClient = newHttpClient() var verkehrslageClient = newHttpClient() verkehrslageClient.headers["db-api-key"] = "32a84ba3-de45-46b3-a580-e3cac9591981" verkehrslageClient.headers["db-client-id"] = "ris-api" proc calcDistMeters(lat1, lon1, lat2, lon2: float): float = var R = 6371000.0; var dLat = ((lat2 - lat1) * PI) / 180 var dLon = ((lon2 - lon1) * PI) / 180 var a = sin(dLat / 2) * sin(dLat / 2) + cos((lat1 * PI) / 180) * cos((lat2 * PI) / 180) * sin(dLon / 2) * sin(dLon / 2); result = ( R * 2.0 * arctan2( sqrt(a), sqrt(1 - a) ) ) proc getCsrfToken() = if not fileExists(tokenFile): var response = dbregioClient.request("https://wifi-landingpage.dbregio.de/", HttpGet) var html = parseHtml(response.bodyStream) var cookieJar = parseCookies(response.headers["set-cookie"]) for meta in html.findAll("meta"): if (meta.attrs.hasKey "name") and (meta.attrs["name"] == "csrf-token"): csrf = %* { "token": meta.attrs["content"], "cookie": cookieJar["__Host-csrf"], } writeFile($tokenFile, $csrf) else: csrf = parseFile($tokenFile) dbregioClient.headers["Cookie"] = "__Host-csrf=" & $csrf["cookie"] dbregioClient.headers["Csrf-Token"] = $csrf["token"] proc risStationsRequest(endpoint: string, params: openArray[(string, string)]): JsonNode = var response = verkehrslageClient.request(parseUri("https://verkehrslage.bahnhof.de/api/stations") / endpoint ? params) result = parseJson(response.body) proc risBoardsRequest(endpoint: string, params: openArray[(string, string)]): JsonNode = var response = dbregioClient.request(parseUri("https://wifi-landingpage.dbregio.de/api/boards") / endpoint ? params) if response.code != Http200: removeFile(tokenFile) getCsrfToken() response = dbregioClient.request(parseUri("https://wifi-landingpage.dbregio.de/api/boards") / endpoint ? params) result = parseJson(response.body) proc risJourneysRequest(id: string, params: openArray[(string, string)]): JsonNode = var response = dbregioClient.request(parseUri("https://wifi-landingpage.dbregio.de/api/journeys") / id ? params) if response.code != Http200: removeFile(tokenFile) getCsrfToken() response = dbregioClient.request(parseUri("https://wifi-landingpage.dbregio.de/api/journeys") / id ? params) result = parseJson(response.body) proc getStations(lat, lon: float, limit: int = 3, radius: int = 500): JsonNode = var response = risStationsRequest("/stop-places/by-position", @[ ("groupBy", "SALES"), ("latitude", $lat), ("longitude", $lon), ("radius", $radius), ("limit", $limit), ]) result = newJArray() for station in items(response["stopPlaces"]): var name: string if station["names"]["DE"].hasKey("speechShort"): name = station["names"]["DE"]["speechShort"].getStr() else: name = station["names"]["DE"]["nameLong"].getStr() result.add(%* { "name": name, "id": station["evaNumber"], "dist": (int)round(calcDistMeters( lat, lon, station["position"]["latitude"].getFloat(), station["position"]["longitude"].getFloat(), )), }) proc getDepartures(evaId: string, limit: int = 10, duration: int = 60): JsonNode = var response = risBoardsRequest("/public/departures/" & evaId, @[ ("includeStationGroup", "true"), ("maxViaStops", "0"), ("sortBy", "TIME"), ("duration", $duration) ]) result = newJArray() for departure in items(response["departures"]): var direction: string if departure["transport"]["direction"]["text"].getStr() != "": direction = departure["transport"]["direction"]["text"].getStr() else: direction = departure["transport"]["direction"]["stopPlaces"][0]["name"].getStr() result.add(%* { "id": departure["transport"]["journeyID"], "type": departure["transport"]["type"], "category": departure["transport"]["category"], "line": departure["transport"]["line"], "direction": direction, "platform": departure["platform"], "time": toUnix(parse(departure["timeSchedule"].getStr(), "yyyy-MM-dd'T'HH:mm:sszzz").toTime()), "delay": inSeconds(parse(departure["time"].getStr(), "yyyy-MM-dd'T'HH:mm:sszzz") - parse(departure["timeSchedule"].getStr(), "yyyy-MM-dd'T'HH:mm:sszzz")), "canceled": departure["canceled"] }) if (result.len == limit): break proc getRoute(id: string): JsonNode = var response = risJourneysRequest(id, @[ ("separateCancelled", "true") ]) result = %* [ { "id": response["events"][0]["stopPlace"]["evaNumber"], "name": response["events"][0]["stopPlace"]["name"], "platform": response["events"][0]["platform"], "time": toUnix(parse(response["events"][0]["timeSchedule"].getStr(), "yyyy-MM-dd'T'HH:mm:sszzz").toTime()), "delay": inSeconds(parse(response["events"][0]["time"].getStr(), "yyyy-MM-dd'T'HH:mm:sszzz") - parse(response["events"][0]["timeSchedule"].getStr(), "yyyy-MM-dd'T'HH:mm:sszzz")), "canceled": false, } ] var elemNum = 0 for event in items(response["events"]): if (event["type"].getStr() == "DEPARTURE") or (response["events"].len == elemNum): result.add(%* { "id": event["stopPlace"]["evaNumber"], "name": event["stopPlace"]["name"], "platform": event["platform"], "time": toUnix(parse(event["timeSchedule"].getStr(), "yyyy-MM-dd'T'HH:mm:sszzz").toTime()), "delay": inSeconds(parse(event["time"].getStr(), "yyyy-MM-dd'T'HH:mm:sszzz") - parse(event["timeSchedule"].getStr(), "yyyy-MM-dd'T'HH:mm:sszzz")), "canceled": false, }) elemNum = elemNum+1 getCsrfToken() var params = readData() write(stdout, "Content-Type: application/json; charset=UTF-8\n") write(stdout, "Cache-Control: no-store, must-revalidate\n") write(stdout, "Pragma: no-cache\n") write(stdout, "Expires: 0\n") write(stdout, "\n") if params["get"] == "stations": write(stdout, $getStations( parseFloat(params["lat"]), parseFloat(params["lon"]), parseInt(params.getOrDefault("limit", "5")), parseInt(params.getOrDefault("radius", "750")), )) if params["get"] == "departures": write(stdout, $getDepartures( params["evaId"], parseInt(params["limit"]), parseInt(params.getOrDefault("duration", "30")), )) if params["get"] == "route": write(stdout, $getRoute( params["id"], ))