Skip to content

Instantly share code, notes, and snippets.

@d3d9
Last active March 1, 2019 02:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save d3d9/fc3ab7a8c6af6212a25f9456c866cb0b to your computer and use it in GitHub Desktop.
Save d3d9/fc3ab7a8c6af6212a25f9456c866cb0b to your computer and use it in GitHub Desktop.
radbox.py: lädt Daten von https://www.bikeandridebox.de/ bzw. https://www.dein-radschloss.de/ und weiteren (Kienzler/SinusQuadrat)
#!/usr/bin/env python3.7
# -*- coding: utf-8 -*-
from radbox import *
if __name__ == "__main__":
baseurls = [URL("https://www.dein-radschloss.de/"), URL("https://www.bikeandridebox.de/")]
print("\n".join(f"[{bi}] {bu}" for bi, bu in enumerate(baseurls)))
try:
baseurl = baseurls[int(input("Basis-URL aussuchen: "))]
except:
print("Eingabe unklar, erste URL wird verwendet")
baseurl = baseurls[0]
loadprices = input("Sollen auch Boxinformationen (aktuell nur Preise) geladen werden (*/n)? ") != "n"
buchenurl = URL(baseurl+"boxbuchen/")
print("Stationsliste wird geladen")
stations, references = getstations(buchenurl)
while True:
try:
print("\nStationen:")
print("\n".join(station.id+" "+station.name for station in sorted(stations.values(), key=lambda _: _.id)))
if references:
print("\nVerweise (Basis-URL wird gewechselt)")
print("\n".join(f"{id} {name} ({url})" for id, (name, url) in sorted(references.items(), key=lambda _: _[0])))
in_id = input("\nStations-"+("/Verweis-" if references else "")+"ID eingeben: ")
if in_id in stations:
station = stations[in_id]
if station.getinfo(baseurl=baseurl, loadprices=loadprices):
print("\n"+str(station).replace("Box(", "\nBox(").replace("Gap(", "\nGap("))
else:
print("loading current data for selected station was unsuccessful")
elif in_id in references:
baseurl = references[in_id][1]
buchenurl = URL(baseurl+"boxbuchen/")
print("Stationsliste wird geladen")
stations, references = getstations(buchenurl)
else:
print("ID", in_id, "unbekannt")
except KeyboardInterrupt:
break
#!/usr/bin/env python3.7
# -*- coding: utf-8 -*-
"""Helps with loading bicycle box data from services like https://www.bikeandridebox.de/"""
from __future__ import annotations
from bs4 import BeautifulSoup, element
from requests import get, post
from typing import NewType, List, Dict, Union
from dataclasses import dataclass
from datetime import datetime, timedelta
URL = NewType("URL", str)
HTML = NewType("HTML", str)
@dataclass
class Station:
"""Bicycle "Box" station"""
id: str
name: str
buchenurl: str
currency: str
description: HTML = None
design: HTML = None
accepttext: str = None
boxword: str = None
boxnrlength: int = None
boxcount: int = None
boxrows: int = None
images: List[Dict[str, URL]] = None
boxes: List[Union[Box, Gap]] = None
def getinfo(self: Station, baseurl: URL, loadprices: bool = False) -> bool:
"""Adds current data to this Station object, returns success"""
stationdata = post(baseurl,
params={'eID': "bookboxUpdateData"},
data={'action': "getData",
'controller': "General",
'L': "0",
'unitId': self.id})
j = stationdata.json()
if j['success']:
self.description = HTML(j['description'])
self.design = HTML(j['design'])
self.accepttext = j['accepttext']
self.boxword = j['boxword']
self.boxnrlength = j['boxnrlength']
self.boxcount = j['availableBox']
self.images = j['images']
for image in self.images:
for imgtype, path in image.items():
image[imgtype] = URL(baseurl+path)
self.boxes = []
if j['design'].find('svg') < 0:
uls = BeautifulSoup(j['design'], "lxml").find_all("ul", {'class': "clearfix"})
self.boxrows = len(uls)
for row, designelements in enumerate(uls):
for e in filter(lambda _: type(_) == element.Tag, designelements):
if ("gap" in (e.attrs.get('class') or [])) or not (e.text.strip() or e.attrs.get('data-value')):
self.boxes.append(Gap(stationid=self.id,
row=row,
style=e.attrs.get('style')))
else:
self.boxes.append(Box(stationid=self.id,
id=None,
number=e.attrs['data-value'],
displayas=e.text,
state=None,
usedinreminder=None,
row=row,
style=e.attrs.get('style'),
prices=None,
currency=self.currency,
boxword=self.boxword,
boxtypes=e.attrs.get('class') or []))
else:
for box in (j.get('boxes') or []):
self.boxes.append(Box(stationid=self.id,
id=None,
number=box['number'],
displayas=None,
state=None,
usedinreminder=None,
row=None,
style=None,
prices=None,
currency=self.currency,
boxword=self.boxword,
boxtypes=None))
for box in self.boxes:
if type(box) == Box:
if 'boxes' in j:
jsonbox = list(filter(lambda jb: jb['number'] == box.number, j['boxes']))
if len(jsonbox) == 1:
box.id = jsonbox[0]['id']
box.state = jsonbox[0]['state']
box.usedinreminder = jsonbox[0].get('usedinreminder')
# ^ mittlerweile scheinbar anders, todo
if loadprices:
box.getinfo(baseurl)
elif not jsonbox:
box.inactive = True
else:
print("error: unexpected amount of boxes found in json:", len(jsonbox), "for box", box.number)
break
else:
box.inactive = True
actualboxcount = len(list(filter(lambda _: not _.inactive, filter(lambda __: type(__) == Box, self.boxes))))
if self.boxcount != actualboxcount:
print("warning: unexpected amount of boxes (", actualboxcount, " instead of ", self.boxcount, ")", sep="")
else:
print("error: (station info) success is not true. ?", self.id, self.name)
return j['success']
@dataclass
class Box:
"""Box or other type of space that can contain a vehicle"""
stationid: str
id: int
number: str
displayas: str
state: str
usedinreminder: bool
row: int
style: str
prices: List[BoxPrice]
currency: str
boxword: str
boxtypes: List[str]
inactive: bool = False
def getinfo(self: Box, baseurl: URL) -> bool:
"""Loads available information (currently only pricing?) for this "Box", returns success"""
boxdata = post(baseurl,
params={'eID': "bookboxUpdateData"},
data={'action': "getData",
'controller': "General",
'L': "0",
'boxId': self.id})
j = boxdata.json()
self.prices = []
if j['success']:
for id, priceinfo in j['priceinfo'].items():
bstr = priceinfo['bookTo']
self.prices.append(BoxPrice(boxid=self.id,
id=id,
duration=priceinfo['duration'],
price=priceinfo['price'],
displayas=priceinfo['formated_price'],
currency=self.currency,
bookto=datetime.strptime(bstr.replace("24:", "23:"), "%d.%m.%Y %H:%M")+timedelta(hours="24:" in bstr),
booktostr=bstr))
else:
print("error: (box info) success is not true. ?", self.id)
return j['success']
@dataclass
class BoxPrice:
"""An available "Box" price"""
boxid: int
id: str
duration: str
price: int # ?
displayas: str
currency: str
bookto: datetime
booktostr: str
@dataclass
class Gap:
"""Structural gap between "Box"es"""
stationid: str
row: int
style: str
def textfromto(html: HTML, findstr: str, beginafter: bool, until: str) -> str:
""""""
pos = html.index(findstr) + len(findstr)*beginafter
return html[pos:pos+html[pos:].index(until)]
def getstations(buchenurl: URL) -> Tuple[Dict[str, Station], Dict[str, Tuple[str, URL]]]:
"""Returns dict containing selectable bike box stations"""
buchenpage = get(buchenurl)
currency = textfromto(HTML(buchenpage.text), "var paymentCurrency = \"", True, "\"")
references = {}
if "redirectUnits[" in buchenpage.text:
rline = textfromto(HTML(buchenpage.text), "redirectUnits[", False, "\n")
for ruline in filter(lambda _: _.strip(), rline.split(';')):
url = URL(textfromto(ruline, "] = \"", True, "\""))
url = URL((url+("/" if not url.endswith("/") else ""))[:(-10 if "boxbuchen/" in url else None)])
references[textfromto(ruline, "redirectUnits[", True, "]")] = url
stationlist = BeautifulSoup(buchenpage.text, "lxml").find("select", {'name': "location"}).find_all("option")
stations = {}
for station in stationlist:
id = station['value']
name = station.text
if(id == '0'):
continue
if(id in stations):
print('warning: duplicate station selectable:', id, name)
if(id in references):
# print(f'warning: station {id} references other baseurl {references[id]}, not adding to stations dict')
references[id] = name, references[id]
continue
stations[id] = Station(id=id, name=name, buchenurl=buchenurl, currency=currency)
return stations, references
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment