It looks like it works?
This commit is contained in:
107
book_crossfit.py
107
book_crossfit.py
@@ -39,6 +39,10 @@ CATEGORY_ID = "677" # Activity category ID for CrossFit
|
|||||||
TIMEZONE = "Europe/Paris" # Adjust to your timezone
|
TIMEZONE = "Europe/Paris" # Adjust to your timezone
|
||||||
TARGET_RESERVATION_TIME = "20:01" # When bookings open (8 PM)
|
TARGET_RESERVATION_TIME = "20:01" # When bookings open (8 PM)
|
||||||
DEVICE_TYPE = "3"
|
DEVICE_TYPE = "3"
|
||||||
|
|
||||||
|
# Retry configuration
|
||||||
|
RETRY_MAX = 3
|
||||||
|
RETRY_BACKOFF = 1
|
||||||
APP_VERSION = "5.09.21"
|
APP_VERSION = "5.09.21"
|
||||||
|
|
||||||
# Define your preferred sessions
|
# Define your preferred sessions
|
||||||
@@ -156,14 +160,28 @@ class CrossFitBooker:
|
|||||||
# print(f"Request Data: {request_data}")
|
# print(f"Request Data: {request_data}")
|
||||||
# print(f"Headers: {self.get_auth_headers()}")
|
# print(f"Headers: {self.get_auth_headers()}")
|
||||||
|
|
||||||
|
# Add retry logic with exponential backoff
|
||||||
|
for retry in range(RETRY_MAX):
|
||||||
try:
|
try:
|
||||||
# Make the request
|
|
||||||
response = self.session.post(
|
response = self.session.post(
|
||||||
url,
|
url,
|
||||||
headers=self.get_auth_headers(),
|
headers=self.get_auth_headers(),
|
||||||
data=urlencode(request_data),
|
data=urlencode(request_data),
|
||||||
timeout=10
|
timeout=10
|
||||||
)
|
)
|
||||||
|
break # Success, exit retry loop
|
||||||
|
except (requests.exceptions.ConnectionError,
|
||||||
|
requests.exceptions.Timeout,
|
||||||
|
requests.exceptions.ReadTimeout) as e:
|
||||||
|
if retry == RETRY_MAX - 1:
|
||||||
|
raise # Final retry failed, propagate error
|
||||||
|
wait_time = RETRY_BACKOFF * (2 ** retry)
|
||||||
|
logging.warning(f"Request failed (attempt {retry+1}/{RETRY_MAX}): {str(e)}. Retrying in {wait_time}s...")
|
||||||
|
time.sleep(wait_time)
|
||||||
|
else:
|
||||||
|
# All retries exhausted
|
||||||
|
print(f"Failed after {RETRY_MAX} attempts")
|
||||||
|
return None
|
||||||
|
|
||||||
# Debug raw response
|
# Debug raw response
|
||||||
# print(f"Response Status Code: {response.status_code}")
|
# print(f"Response Status Code: {response.status_code}")
|
||||||
@@ -186,71 +204,64 @@ class CrossFitBooker:
|
|||||||
elif response.status_code == 401:
|
elif response.status_code == 401:
|
||||||
print("401 Unauthorized - token may be expired or invalid")
|
print("401 Unauthorized - token may be expired or invalid")
|
||||||
return None
|
return None
|
||||||
|
elif 500 <= response.status_code < 600:
|
||||||
|
raise requests.exceptions.ConnectionError(f"Server error {response.status_code}")
|
||||||
else:
|
else:
|
||||||
print(f"Unexpected status code: {response.status_code}")
|
print(f"Unexpected status code: {response.status_code}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
except requests.exceptions.RequestException as e:
|
|
||||||
print(f"Request failed: {str(e)}")
|
|
||||||
return None
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Unexpected error: {str(e)}")
|
|
||||||
return None
|
|
||||||
|
|
||||||
def book_session(self, session_id: str) -> bool:
|
def book_session(self, session_id: str) -> bool:
|
||||||
"""Book a specific session with debug logging."""
|
"""Book a specific session with debug logging."""
|
||||||
|
return self._make_request(
|
||||||
logging.info(f"Attempting to book session_id: {session_id}")
|
url="https://sport.nubapp.com/api/v4/activities/bookActivityCalendar.php",
|
||||||
if not self.auth_token or not self.user_id:
|
data=self._prepare_booking_data(session_id),
|
||||||
logging.error("Not authenticated: missing auth_token or user_id")
|
success_msg=f"Successfully booked session {session_id}"
|
||||||
return False
|
|
||||||
|
|
||||||
try:
|
|
||||||
# Prepare headers
|
|
||||||
headers = self.get_auth_headers()
|
|
||||||
|
|
||||||
# print(f"[DEBUG] Request headers: {headers}")
|
|
||||||
|
|
||||||
# Prepare the exact request data from cURL
|
|
||||||
request_data = self.mandatory_params.copy()
|
|
||||||
request_data.update({
|
|
||||||
"id_activity_calendar": session_id, # Note the different parameter name
|
|
||||||
"id_user": self.user_id,
|
|
||||||
"action_by": self.user_id, # Same as id_user in this case
|
|
||||||
"n_guests": "0",
|
|
||||||
"booked_on": "3" # 3 likely means "booked via app"
|
|
||||||
})
|
|
||||||
|
|
||||||
print(f"[DEBUG] Final request data: {request_data}")
|
|
||||||
|
|
||||||
# Use the correct endpoint
|
|
||||||
response = self.session.post(
|
|
||||||
"https://sport.nubapp.com/api/v4/activities/bookActivityCalendar.php",
|
|
||||||
headers=headers,
|
|
||||||
data=urlencode(request_data)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
logging.debug(f"Response status: {response.status_code}")
|
def _prepare_booking_data(self, session_id: str) -> Dict:
|
||||||
logging.debug(f"API response: {response.text}")
|
"""Prepare request data for booking a session"""
|
||||||
|
return {
|
||||||
|
**self.mandatory_params,
|
||||||
|
"id_activity_calendar": session_id,
|
||||||
|
"id_user": self.user_id,
|
||||||
|
"action_by": self.user_id,
|
||||||
|
"n_guests": "0",
|
||||||
|
"booked_on": "3"
|
||||||
|
}
|
||||||
|
|
||||||
if response.ok:
|
def _make_request(self, url: str, data: Dict, success_msg: str) -> bool:
|
||||||
|
"""Handle API requests with retry logic and response processing"""
|
||||||
|
for retry in range(RETRY_MAX):
|
||||||
try:
|
try:
|
||||||
|
response = self.session.post(
|
||||||
|
url,
|
||||||
|
headers=self.get_auth_headers(),
|
||||||
|
data=urlencode(data),
|
||||||
|
timeout=10
|
||||||
|
)
|
||||||
|
|
||||||
|
if response.status_code == 200:
|
||||||
json_response = response.json()
|
json_response = response.json()
|
||||||
if json_response.get("success", False):
|
if json_response.get("success", False):
|
||||||
logging.info(f"Successfully booked session {session_id}")
|
logging.info(success_msg)
|
||||||
return True
|
return True
|
||||||
else:
|
|
||||||
logging.error(f"API returned success:false: {json_response}")
|
logging.error(f"API returned success:false: {json_response}")
|
||||||
return False
|
return False
|
||||||
except ValueError:
|
|
||||||
logging.error("Invalid JSON response")
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
logging.error(f"HTTP {response.status_code}: {response.text}")
|
logging.error(f"HTTP {response.status_code}: {response.text}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
except Exception as e:
|
except (requests.exceptions.ConnectionError,
|
||||||
logging.critical(f"Unexpected error: {str(e)}", exc_info=True)
|
requests.exceptions.Timeout,
|
||||||
|
requests.exceptions.ReadTimeout) as e:
|
||||||
|
if retry == RETRY_MAX - 1:
|
||||||
|
logging.error(f"All {RETRY_MAX} retry attempts failed")
|
||||||
|
raise
|
||||||
|
wait_time = RETRY_BACKOFF * (2 ** retry)
|
||||||
|
logging.warning(f"Request failed (attempt {retry+1}/{RETRY_MAX}): {str(e)}. Retrying in {wait_time}s...")
|
||||||
|
time.sleep(wait_time)
|
||||||
|
|
||||||
|
logging.error(f"Failed to complete request after {RETRY_MAX} attempts")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def is_session_bookable(self, session: Dict, current_time: datetime) -> bool:
|
def is_session_bookable(self, session: Dict, current_time: datetime) -> bool:
|
||||||
|
|||||||
Reference in New Issue
Block a user