commit d50e6600fc68aa12a8f161f98e3482fcfe37f55f Author: b267a Date: Tue Jul 15 16:36:34 2025 +0200 Initial commit diff --git a/.github/workflows/schedule.yml b/.github/workflows/schedule.yml new file mode 100644 index 0000000..d0b8afa --- /dev/null +++ b/.github/workflows/schedule.yml @@ -0,0 +1,25 @@ +name: Run Python Script + +on: + schedule: + - cron: '0 0 * * *' + +jobs: + run-script: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v2 + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: '3.x' + - name: Install dependencies + run: | + pip install -r requirements.txt + - name: Run script + env: + USERNAME: ${{ secrets.USERNAME }} + PASSWORD: ${{ secrets.PASSWORD }} + run: | + python kit.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cff7ac8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/.idea +*.env \ No newline at end of file diff --git a/kit.py b/kit.py new file mode 100644 index 0000000..2138186 --- /dev/null +++ b/kit.py @@ -0,0 +1,176 @@ +import requests +import urllib.parse +import re +import html +import time +import datetime +import pytz +import os + +from dotenv import load_dotenv + +def get_day(): + dt = datetime.datetime.now(pytz.timezone('Europe/Berlin')) + datetime.timedelta(days=3) + date = dt.strftime('%Y-%m-%d') + return date + +def login(username, password): + + ses = requests.Session() + ses.headers = { + 'accept': 'text/html, application/xhtml+xml', + 'accept-encoding': 'plain', + 'referer': 'https://auth.anny.eu/', + 'origin': 'https://auth.anny.eu', + 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:140.0) Gecko/20100101 Firefox/140.0', + 'x-requested-with': 'XMLHttpRequest', + 'x-inertia': 'true', + 'x-inertia-version': '66b32acea13402d3aef4488ccd239c93', + } + + r = ses.get( + 'https://auth.anny.eu/login/sso' + ) + + ses.headers['X-XSRF-TOKEN'] = urllib.parse.unquote(r.cookies['XSRF-TOKEN']) + + r2 = ses.post( + 'https://auth.anny.eu/login/sso', + json={ + 'domain': 'kit.edu' + }, headers={ + 'referer': 'https://auth.anny.eu/login/sso', + } + ) + + redirect_url = r2.headers['x-inertia-location'] + + r3 = ses.get( + redirect_url, + allow_redirects=True + ) + + ses.headers.pop('x-requested-with') + ses.headers.pop('x-inertia') + ses.headers.pop('x-inertia-version') + + pattern = r'name="csrf_token" value="([^"]+)"' + csrf_token = re.search(pattern, r3.text).group(1) + + r4 = ses.post( + 'https://idp.scc.kit.edu/idp/profile/SAML2/Redirect/SSO?execution=e1s1', + data={ + 'csrf_token': csrf_token, + 'j_username': username, + 'j_password': password, + '_eventId_proceed': '', + 'fudis_web_authn_assertion_input': '', + } + ) + + response = html.unescape(r4.text) + + if '/consume' in response: + print("KIT-Login successful!") + else: + print("Failed to login, probably wrong credentials!") + return False + + pattern = r'form action="([^"]+)"' + consume_url = re.search(pattern, response).group(1) + pattern = r'name="RelayState" value="([^"]+)"' + relayState = re.search(pattern, response).group(1) + pattern = r'name="SAMLResponse" value="([^"]+)"' + samlResponse = re.search(pattern, response).group(1) + + r5 = ses.post( + consume_url, + data={ + 'RelayState': relayState, + 'SAMLResponse': samlResponse + } + ) + + r6 = ses.get( + 'https://anny.eu/en-us/login?target=/en-us/home?withoutIntent=true', + allow_redirects=True + ) + + + return ses.cookies + +def test_reservation(): + load_dotenv('credentials.env', override=True) + + username = os.getenv('USERNAME') + password = os.getenv('PASSWORD') + + cookies = login(username, password) + + + TOKEN = cookies['anny_shop_jwt'] + + ses = requests.Session() + ses.cookies = cookies + ses.headers = { + 'accept': 'application/vnd.api+json', + 'accept-encoding': 'plain', + 'authorization': 'Bearer ' + TOKEN, + 'content-type': 'application/vnd.api+json', + 'origin': 'https://anny.eu', + 'referer': 'https://anny.eu/', + 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:140.0) Gecko/20100101 Firefox/140.0' + } + + r = ses.post( + 'https://b.anny.eu/api/v1/order/bookings?include=customer,voucher,bookings.booking_add_ons.add_on.cover_image,bookings.sub_bookings.resource,bookings.sub_bookings.service,bookings.series_bookings,bookings.customer,bookings.service.custom_forms.custom_fields,bookings.cancellation_policy,bookings.resource.cover_image,bookings.resource.parent,bookings.resource.category,bookings.reminders,bookings.booking_series,sub_orders.bookings,sub_orders.organization.legal_documents&stateless=1', + json={ + "resource_id": [ + "3305" + ], "service_id": { + "449": 1 + }, "start_date": get_day() + "T13:00:00+02:00", + "end_date": get_day() + "T18:00:00+02:00", + "description":"", "customer_note": "", + "add_ons_by_service": { + "449": [ + [] + ] + }, "sub_bookings_by_service": {}, + "strategy": "multi-resource" + }, cookies=cookies + ) + + print(r.status_code, r.text) + + oid = r.json()['data']['id'] + oat = r.json()['data']['attributes']['access_token'] + + r2 = ses.get( + 'https://b.anny.eu/api/ui/checkout-form?oid=' + oid + '&oat=' + oat + '&stateless=1' + ) + + customer = r2.json()['default']['customer'] + + r3 = ses.post( + 'https://b.anny.eu/api/v1/order?include=customer,voucher,bookings.booking_add_ons.add_on,bookings.sub_bookings.resource,bookings.sub_bookings.service,bookings.service.custom_forms.custom_fields,bookings.cancellation_policy,bookings.resource.cover_image,bookings.resource.parent,bookings.reminders,bookings.customer,bookings.attendees,sub_orders.bookings,sub_orders.organization.legal_documents,last_payment.method&oid=' + oid + '&oat=' + oat + '&stateless=1', + json={ + "customer": { + "given_name": customer['given_name'], + "family_name": customer['family_name'], + "email": customer['email'] + }, "accept_terms": True, + "payment_method": "", + "success_url": "https://anny.eu/checkout/success?oids=" + oid + "&oats=" + oat, + "cancel_url": "https://anny.eu/checkout?step=checkout&childResource=3302", + "meta": { + "timezone":"Europe/Berlin" + } + } + ) + + print(r3.status_code, r3.text) + if r3.ok: + print("Reservation successful!") + +test_reservation() diff --git a/main.py b/main.py new file mode 100644 index 0000000..23fb6ba --- /dev/null +++ b/main.py @@ -0,0 +1,73 @@ +import requests +import urllib.parse +import os +from dotenv import load_dotenv + +import assets.constants + +class Bibliothek: + def __init__(self): + self.session = requests.Session() + + self.session.headers = { + 'accept': 'application/vnd.api+json', + 'accept-encoding': 'gzip, deflate, br, zstd', + 'accept-language': 'de', + 'content-type': 'text/html; charset=utf-8', + 'origin': 'https://anny.eu', + 'referer': 'https://anny.eu/', + 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:140.0) Gecko/20100101 Firefox/140.0' + } + + def login(self): + load_dotenv() + username = os.getenv('USERNAME') + password = os.getenv('PASSWORD') + login = KIT(username, password) + + self.session.headers['authorization'] = ("Bearer " + TOKEN).encode('utf-8') + + def get_intervals(self, ressource_id, date): + + r = self.session.get( + 'https://b.anny.eu/api/v1/intervals/start', + params={ + 'date': date, + 'service_id[449]': 1, + 'ressource_id': ressource_id, + 'timezone': assets.constants.TIMEZONE + } + ) + return r.json() + + def calculate_all_available_slots(self, ressource_id, date): + + slot_list = self.get_intervals(ressource_id, date) + + for slot in slot_list: + message = slot['start_date'] + " - " + str(slot['number_available']) + print(message) + + def get_children(self, ressource = '1-lehrbuchsammlung-eg-und-1-og'): + + r = self.session.get( + 'https://b.anny.eu/api/v1/resources/' + ressource + '/children', + params={ + 'page[number]': 1, + 'page[size]': 1000, + 'filter[available_from]': '2025-07-10T00:00:00+02:00', + # 'filter[availability_exact_match]': 0, + 'filter[exclude_hidden]': 0, + 'filter[exclude_child_resources]': 0, + 'filter[availability_service_id]': 449, + 'filter[include_unavailable]': 1, + 'sort': 'name', + } + ) + + return r.json() + + +if __name__ == '__main__': + bibliothek = Bibliothek() + print(bibliothek.get_children()) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..663bd1f --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +requests \ No newline at end of file