Created
February 20, 2025 17:41
-
-
Save Romern/1522dfb970010355ded2d492cf1d3101 to your computer and use it in GitHub Desktop.
adapted from https://github.com/Lasslos/bonn-termine-bot/blob/master/appointments.py. Nutzt "Biometrisches Foto" für die minimalsten 5 minuten slots.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import requests | |
import logging | |
import re | |
import json | |
import http.client as http_client | |
from html import unescape | |
from datetime import datetime | |
class Appointment: | |
def __init__(self, date_time, unit, duration, link): | |
self.date_time: datetime = date_time | |
self.unit = unit | |
self.duration = duration | |
self.link = link | |
def __str__(self): | |
return f"Date: {self.date_time}, Unit: {self.unit}, Duration: {self.duration} minutes" | |
def __repr__(self): | |
return f"Date: {self.date_time}, Unit: {self.unit}, Duration: {self.duration} minutes" | |
# Parse from json | |
@staticmethod | |
def from_json(json_data): | |
return Appointment(datetime.fromisoformat(json_data["datetime_iso86001"]), json_data["unit"], | |
json_data["duration"], "https://stadt-aachen.saas.smartcjm.com" + json_data["link"]) | |
def enable_debug(): | |
http_client.HTTPConnection.debuglevel = 1 | |
logging.basicConfig() | |
logging.getLogger().setLevel(logging.DEBUG) | |
requests_log = logging.getLogger("requests.packages.urllib3") | |
requests_log.setLevel(logging.DEBUG) | |
requests_log.propagate = True | |
def get_appointments() -> list[Appointment]: | |
domain = "https://stadt-aachen.saas.smartcjm.com" | |
response = requests.get(domain + "/m/buergerservice/extern/calendar/?uid=15940648-b483-46d9-819e-285707f1fc34", | |
allow_redirects=False) | |
# Base url should return 302 with 'wsid' as a parameter in the url | |
if response.status_code != 302: | |
print("Couldn't get wsid token") | |
exit() | |
# Get parameter from the url | |
base_url = domain + response.headers["Location"] | |
# Only send cookies "__RequestVerificationToken" and "ASP.NET_SessionId" | |
cookies = { | |
"__RequestVerificationToken": response.cookies.get_dict()["__RequestVerificationToken"], | |
"ASP.NET_SessionId": response.cookies.get_dict()["ASP.NET_SessionId"], | |
} | |
response2 = requests.get(base_url, cookies=cookies, allow_redirects=False) | |
# Load request token | |
pattern = (r"^(?:.*)<input type='hidden' id='RequestVerificationToken' name='__RequestVerificationToken' value='(" | |
r".*?)' />") | |
match = re.search(pattern, response2.text, flags=re.MULTILINE) | |
if match: | |
form_token = match.group(1) | |
else: | |
print("Couldn't get form token") | |
exit() | |
# Load form data file | |
form_data = '&action_type=&steps=serviceslocationssearch_resultsbookingfinish&step_current=services&step_current_index=0&step_goto=%2B1&services=&services=116df7ba-ea4e-4cf0-b6b9-0723d37ef78d&service_116df7ba-ea4e-4cf0-b6b9-0723d37ef78d_amount=1&services=e2da5155-fe39-4a21-bc07-46cd16a824b9&service_e2da5155-fe39-4a21-bc07-46cd16a824b9_amount=0&services=167f484a-f65d-49cf-8925-dca7be061b36&service_167f484a-f65d-49cf-8925-dca7be061b36_amount=0&services=c4e3989a-e5c0-418f-9198-80493c707ec2&service_c4e3989a-e5c0-418f-9198-80493c707ec2_amount=0&services=fae35b04-9e00-4f73-b1f2-e1ad0a17a87e&service_fae35b04-9e00-4f73-b1f2-e1ad0a17a87e_amount=0&services=10b657a5-0e90-44bc-b72a-2cfec873f6d2&service_10b657a5-0e90-44bc-b72a-2cfec873f6d2_amount=0&services=00dc099a-f550-4481-b1da-48321aea5224&service_00dc099a-f550-4481-b1da-48321aea5224_amount=0&services=c4893630-52bf-4f77-b728-f09407c728e3&service_c4893630-52bf-4f77-b728-f09407c728e3_amount=0&services=0886bc62-fa3e-405a-af63-8054959e85b3&service_0886bc62-fa3e-405a-af63-8054959e85b3_amount=0&services=716e4507-b635-4dca-8b86-00c10da3d3f9&service_716e4507-b635-4dca-8b86-00c10da3d3f9_amount=0&services=74575ac4-9906-4ad5-a8af-aa556e526704&service_74575ac4-9906-4ad5-a8af-aa556e526704_amount=0&services=0f3887e6-be79-46fc-a5b1-af02110d91be&service_0f3887e6-be79-46fc-a5b1-af02110d91be_amount=0&services=d9c6e7d3-d24d-4ccf-8fd4-62ba9c05caf7&service_d9c6e7d3-d24d-4ccf-8fd4-62ba9c05caf7_amount=0&services=51d53890-e2e0-4f76-a05d-537341860a51&service_51d53890-e2e0-4f76-a05d-537341860a51_amount=0&services=a38ae120-a5a8-4fe7-a5b1-7a7a938a07db&service_a38ae120-a5a8-4fe7-a5b1-7a7a938a07db_amount=0&services=d60dc34b-8586-48bd-9feb-23bc6fa91794&service_d60dc34b-8586-48bd-9feb-23bc6fa91794_amount=0&services=07103639-8025-4df0-9772-220e84eb0af2&service_07103639-8025-4df0-9772-220e84eb0af2_amount=0&services=5e0d7cd5-8784-43f0-a3ac-b10da3d2af96&service_5e0d7cd5-8784-43f0-a3ac-b10da3d2af96_amount=0&services=7d4c84e7-fb13-45c1-97a6-185af8235b6e&service_7d4c84e7-fb13-45c1-97a6-185af8235b6e_amount=0&services=2fc984c8-70b3-439c-9dca-b77db3ad76d8&service_2fc984c8-70b3-439c-9dca-b77db3ad76d8_amount=0&services=05bdef1d-1b12-45ba-bd29-fae0d1a58ed7&service_05bdef1d-1b12-45ba-bd29-fae0d1a58ed7_amount=0&services=7caa3811-0ba7-4e31-841e-aa69831d7782&service_7caa3811-0ba7-4e31-841e-aa69831d7782_amount=0&services=7028a6df-7a61-40f8-a920-3ec5c9d1f969&service_7028a6df-7a61-40f8-a920-3ec5c9d1f969_amount=0&services=146c80d6-bb02-42b8-aafa-bd3e855414c9&service_146c80d6-bb02-42b8-aafa-bd3e855414c9_amount=0&services=64a30e77-8224-41e8-bee9-624464dcd860&service_64a30e77-8224-41e8-bee9-624464dcd860_amount=0&services=cc8b1d05-8cd9-4555-a666-39ed185292ba&service_cc8b1d05-8cd9-4555-a666-39ed185292ba_amount=0&services=38c66af1-44e8-4781-804d-1693e703a346&service_38c66af1-44e8-4781-804d-1693e703a346_amount=0&services=b0a3bbe1-4747-44cb-a8d3-b56a3be9fe15&service_b0a3bbe1-4747-44cb-a8d3-b56a3be9fe15_amount=0&services=b755ee1f-9df1-4a66-acfd-76b9e46b1211&service_b755ee1f-9df1-4a66-acfd-76b9e46b1211_amount=0&services=216bbe43-d673-472b-be08-fe122ee1bc2b&service_216bbe43-d673-472b-be08-fe122ee1bc2b_amount=0&services=b829df33-9df6-4bd9-b740-301df3efb643&service_b829df33-9df6-4bd9-b740-301df3efb643_amount=0&services=2db0b2e2-8f00-4aa6-a08c-44dff95cc043&service_2db0b2e2-8f00-4aa6-a08c-44dff95cc043_amount=0&services=7bee4872-ba56-4070-9f6d-f45afdf491cb&service_7bee4872-ba56-4070-9f6d-f45afdf491cb_amount=0&services=d1a00eaf-f73b-4e98-ac2f-568b2d9c16c1&service_d1a00eaf-f73b-4e98-ac2f-568b2d9c16c1_amount=0&services=4a561c29-1721-4e27-b470-1a3265bf93ab&service_4a561c29-1721-4e27-b470-1a3265bf93ab_amount=0&services=1d2f6113-811d-4643-87d0-ebd3a7780959&service_1d2f6113-811d-4643-87d0-ebd3a7780959_amount=0&services=4c62d7e6-1c14-44a2-a8ea-3dac4b680023&service_4c62d7e6-1c14-44a2-a8ea-3dac4b680023_amount=0&services=98025ae5-88a5-4e8e-ac53-6ddd11231543&service_98025ae5-88a5-4e8e-ac53-6ddd11231543_amount=0&services=b98b99d3-aa3b-4af8-99c6-ed9580de78aa&service_b98b99d3-aa3b-4af8-99c6-ed9580de78aa_amount=0&services=bab4aab0-4572-4ae6-9b81-5db9f7ea75df&service_bab4aab0-4572-4ae6-9b81-5db9f7ea75df_amount=0&services=3f6be2ff-0acf-477f-afd7-1bc3400dfa0e&service_3f6be2ff-0acf-477f-afd7-1bc3400dfa0e_amount=0&services=f8a495fc-90bf-4e96-a776-802483d2b542&service_f8a495fc-90bf-4e96-a776-802483d2b542_amount=0&services=602452fb-fdcf-49ad-a3f6-48dba4af97db&service_602452fb-fdcf-49ad-a3f6-48dba4af97db_amount=0&services=9968f6be-36d5-494e-846d-cdcb0388b221&service_9968f6be-36d5-494e-846d-cdcb0388b221_amount=0' | |
form_data = "__RequestVerificationToken=" + form_token + form_data | |
# Send post to base_url with form_data | |
headers = {"Content-Type": "application/x-www-form-urlencoded"} | |
requests.post(base_url, data=form_data, cookies=cookies, headers=headers, allow_redirects=False) | |
# Finally, get the appointments | |
url = base_url.split("?")[0] + "search_result?search_mode=all&" + base_url.split("?")[1] | |
response4 = requests.get(url, cookies=cookies, allow_redirects=False) | |
pattern = r"(?<=<div id=\"json_appointment_list\">).*?(?=</div>)" | |
match = re.search(pattern, response4.text, flags=re.DOTALL) | |
if match: | |
appointments_json = json.loads(unescape(match.group(0))) | |
appointments = [] | |
for appointment in appointments_json["appointments"]: | |
appointments.append(Appointment.from_json(appointment)) | |
return appointments | |
else: | |
print("JSON data not found") | |
exit() | |
if __name__ == "__main__": | |
for a in sorted(get_appointments(), key=(lambda x : x.date_time)): | |
print(a) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment