Skip to content

Instantly share code, notes, and snippets.

@d3d9 d3d9/example.py
Last active Mar 1, 2019

Embed
What would you like to do?
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
You can’t perform that action at this time.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.