anny-booking-automation/booking/client.py

144 lines
6.1 KiB
Python
Raw Permalink Normal View History

import requests
from requests.exceptions import JSONDecodeError
2026-02-06 17:42:40 +01:00
from config.constants import (
RESOURCE_URL,
BOOKING_API_BASE,
CHECKOUT_FORM_API,
ANNY_BASE_URL,
SERVICE_ID,
2026-02-08 13:09:36 +01:00
BOOKING_QUOTA_GRANT_ID,
2026-02-06 17:42:40 +01:00
)
class BookingClient:
def __init__(self, cookies):
self.session = requests.Session()
self.session.cookies = cookies
2026-02-06 17:42:40 +01:00
self.token = cookies.get("anny_shop_jwt")
2026-02-06 17:42:40 +01:00
self.session.headers.update(
{
"authorization": f"Bearer {self.token}",
"content-type": "application/vnd.api+json",
"origin": ANNY_BASE_URL,
"referer": ANNY_BASE_URL + "/",
"user-agent": "Mozilla/5.0",
}
)
def find_available_resource(self, start, end):
2026-02-06 17:42:40 +01:00
response = self.session.get(
RESOURCE_URL,
params={
"page[number]": 1,
"page[size]": 250,
"filter[available_from]": start,
"filter[available_to]": end,
"filter[availability_exact_match]": 1,
"filter[exclude_hidden]": 0,
"filter[exclude_child_resources]": 0,
"filter[availability_service_id]": int(SERVICE_ID),
"filter[include_unavailable]": 0,
"filter[pre_order_ids]": "",
"sort": "name",
},
)
if not response.ok:
print(f"❌ Failed to fetch resources: HTTP {response.status_code}")
return None
try:
2026-02-06 17:42:40 +01:00
resources = response.json().get("data", [])
except (ValueError, JSONDecodeError):
2026-02-02 13:21:04 +01:00
print(f"❌ Invalid JSON response when fetching resources: {response.text}")
# print(f"❌ Invalid JSON response when fetching resources: {response.text[:200]}")
return None
2026-02-06 17:42:40 +01:00
return resources[-1]["id"] if resources else None
def reserve(self, resource_id, start, end):
2026-02-05 01:32:36 +01:00
print(start)
print(end)
booking = self.session.post(
f"{BOOKING_API_BASE}/order/bookings",
params={
2026-02-06 17:42:40 +01:00
"stateless": "1",
"include": "customer,voucher,bookings.booking_add_ons.add_on.cover_image,bookings.sub_bookings.resource,bookings.sub_bookings.service,bookings.customer,bookings.service.custom_forms.custom_fields,bookings.service.add_ons.cover_image,bookings.service.add_ons.group,bookings.cancellation_policy,bookings.resource.cover_image,bookings.resource.parent,bookings.resource.location,bookings.resource.category,bookings.reminders,bookings.booking_series,bookings.sequenced_bookings.resource,bookings.sequenced_bookings.service,bookings.sequenced_bookings.service.add_ons.cover_image,bookings.sequenced_bookings.service.add_ons.group,bookings.booking_participants,sub_orders.bookings,sub_orders.organization.legal_documents",
},
json={
"resource_id": [resource_id],
"service_id": {SERVICE_ID: 1},
"start_date": start,
"end_date": end,
"description": "",
"customer_note": "",
"add_ons_by_service": {SERVICE_ID: [[]]},
"sub_bookings_by_service": {},
2026-02-07 13:50:59 +01:00
# INFO: changes every week: e.g. booking for a monday needs new id
2026-02-08 13:09:36 +01:00
"booking_quota_grant_id": str(BOOKING_QUOTA_GRANT_ID),
2026-02-06 17:42:40 +01:00
"strategy": "multi-resource",
},
2026-02-02 13:21:04 +01:00
# data = '{"resource_id":["15994"],"service_id":{"601":1},"start_date":"2026-02-03T22:30:00+01:00","end_date":"2026-02-03T23:30:00+01:00","description":"","customer_note":"","add_ons_by_service":{"601":[[]]},"sub_bookings_by_service":{},"booking_quota_grant_id":"24735199","strategy":"multi-resource"}'
)
2026-02-02 13:21:04 +01:00
print("resource id: %s" % resource_id)
if not booking.ok:
print(f"❌ Booking failed: HTTP {booking.status_code}")
return False
try:
data = booking.json().get("data", {})
2026-02-10 13:59:39 +01:00
# here
order_list_with_qr = booking.json().get("included", [])
qr_code=order_list_with_qr[0]["attributes"]["code"]
print(qr_code)
print(booking.text)
except (ValueError, JSONDecodeError):
2026-02-07 13:50:59 +01:00
if "Bad Request" in booking.text and "400" in booking.text:
print("❌ Bad Request 400 on Booking request.")
else:
print(f"❌ Invalid JSON response from booking request: {booking.text}")
return False
oid = data.get("id")
oat = data.get("attributes", {}).get("access_token")
if not oid or not oat:
print("❌ Missing booking ID or access token in response")
return False
2026-02-06 17:42:40 +01:00
checkout = self.session.get(
f"{CHECKOUT_FORM_API}?oid={oid}&oat={oat}&stateless=1"
)
if not checkout.ok:
print(f"❌ Checkout form failed: HTTP {checkout.status_code}")
return False
try:
customer = checkout.json().get("default", {}).get("customer", {})
except (ValueError, JSONDecodeError):
2026-02-02 13:21:04 +01:00
print(f"❌ Invalid JSON response from checkout form: {checkout.text}")
# print(f"❌ Invalid JSON response from checkout form: {checkout.text[:200]}")
return False
final = self.session.post(
f"{BOOKING_API_BASE}/order?oid={oid}&oat={oat}&stateless=1",
json={
"customer": {
"given_name": customer.get("given_name"),
"family_name": customer.get("family_name"),
2026-02-06 17:42:40 +01:00
"email": customer.get("email"),
},
"accept_terms": True,
"payment_method": "",
"success_url": f"{ANNY_BASE_URL}/checkout/success?oids={oid}&oats={oat}",
"cancel_url": f"{ANNY_BASE_URL}/checkout?step=checkout&childResource={resource_id}",
2026-02-06 17:42:40 +01:00
"meta": {"timezone": "Europe/Berlin"},
},
)
if final.ok:
print("✅ Reservation successful!")
return True
print("❌ Reservation failed.")
return False