Initial commit

This commit is contained in:
b267a 2025-07-15 16:36:34 +02:00
commit d50e6600fc
5 changed files with 277 additions and 0 deletions

25
.github/workflows/schedule.yml vendored Normal file
View file

@ -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

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
/.idea
*.env

176
kit.py Normal file
View file

@ -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()

73
main.py Normal file
View file

@ -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())

1
requirements.txt Normal file
View file

@ -0,0 +1 @@
requests