pysportbot 0.0.10__py3-none-any.whl → 0.0.11__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
pysportbot/__init__.py CHANGED
@@ -49,7 +49,7 @@ class SportBot:
49
49
  self._is_logged_in = True
50
50
  self._logger.info("Login successful!")
51
51
  except Exception:
52
- self._is_logged_in = False # Ensure state is False on failure
52
+ self._is_logged_in = False
53
53
  self._logger.exception(ErrorMessages.login_failed())
54
54
  raise
55
55
 
@@ -83,7 +83,6 @@ class SportBot:
83
83
 
84
84
  def book(self, activity: str, start_time: str) -> None:
85
85
 
86
- self._logger.debug(f"Attempting to book class '{activity}' on {start_time}")
87
86
  if not self._is_logged_in:
88
87
  self._logger.error(ErrorMessages.not_logged_in())
89
88
  raise PermissionError(ErrorMessages.not_logged_in())
@@ -92,21 +91,42 @@ class SportBot:
92
91
  self._logger.error(ErrorMessages.no_activities_loaded())
93
92
  raise ValueError(ErrorMessages.no_activities_loaded())
94
93
 
94
+ # Fetch the daily slots for the activity
95
95
  slots = self.daily_slots(activity, start_time.split(" ")[0])
96
+
97
+ # Find the slot that matches the start time
96
98
  matching_slot = slots[slots["start_timestamp"] == start_time]
99
+
100
+ # If no matching slot is found, raise an error
97
101
  if matching_slot.empty:
98
102
  error_msg = ErrorMessages.slot_not_found(activity, start_time)
99
103
  self._logger.error(error_msg)
100
104
  raise IndexError(error_msg)
101
105
 
102
- slot_id = matching_slot.iloc[0]["id_activity_calendar"]
103
-
106
+ # The targeted slot
107
+ target_slot = matching_slot.iloc[0]
108
+ # The unique slot ID
109
+ slot_id = target_slot["id_activity_calendar"]
110
+ # The total member capacity of the slot
111
+ slot_capacity = target_slot["capacity"]
112
+ # The number of members already inscribed in the slot
113
+ slot_n_inscribed = target_slot["n_inscribed"]
114
+ # Log slot capacity
115
+ self._logger.info(
116
+ f"Attempting to book class '{activity}' on {start_time} with ID {slot_id} (Slot capacity: {slot_n_inscribed}/{slot_capacity})"
117
+ )
118
+
119
+ # Check if the slot is already booked out
120
+ if slot_n_inscribed >= slot_capacity:
121
+ self._logger.error(f"Activity '{activity}' on {start_time} with ID {slot_id} is booked out...")
122
+ raise ValueError(ErrorMessages.slot_capacity_full())
123
+
124
+ # Attempt to book the slot
104
125
  try:
105
126
  self._bookings.book(slot_id)
106
127
  self._logger.info(f"Successfully booked class '{activity}' on {start_time}")
107
128
  except ValueError:
108
- self._logger.exception(f"Failed to book class '{activity}' on {start_time}")
109
- raise
129
+ self._logger.error(f"Failed to book class '{activity}' on {start_time}")
110
130
 
111
131
  def cancel(self, activity: str, start_time: str) -> None:
112
132
 
@@ -131,5 +151,4 @@ class SportBot:
131
151
  self._bookings.cancel(slot_id)
132
152
  self._logger.info(f"Successfully cancelled class '{activity}' on {start_time}")
133
153
  except ValueError:
134
- self._logger.exception(f"Failed to cancel class '{activity}' on {start_time}")
135
- raise
154
+ self._logger.error(f"Failed to cancel class '{activity}' on {start_time}")
@@ -14,31 +14,6 @@ from .scheduling import calculate_class_day, calculate_next_execution
14
14
  logger = get_logger(__name__)
15
15
 
16
16
 
17
- def _raise_no_matching_slots_error(activity: str, class_time: str, booking_date: str) -> None:
18
- raise ValueError(ErrorMessages.no_matching_slots_for_time(activity, class_time, booking_date))
19
-
20
-
21
- def wait_for_execution(booking_execution: str, time_zone: str) -> None:
22
- """
23
- Wait until the specified global execution time.
24
-
25
- Args:
26
- booking_execution (str): Global execution time in "Day HH:MM:SS" format.
27
- time_zone (str): Timezone for calculation.
28
- """
29
- tz = pytz.timezone(time_zone)
30
- execution_time = calculate_next_execution(booking_execution, time_zone)
31
- now = datetime.now(tz)
32
- time_until_execution = (execution_time - now).total_seconds()
33
-
34
- if time_until_execution > 0:
35
- logger.info(
36
- f"Waiting {time_until_execution:.2f} seconds until global execution time: "
37
- f"{execution_time.strftime('%Y-%m-%d %H:%M:%S %z')}."
38
- )
39
- time.sleep(time_until_execution)
40
-
41
-
42
17
  def attempt_booking(
43
18
  bot: SportBot,
44
19
  activity: str,
@@ -64,23 +39,19 @@ def attempt_booking(
64
39
  booking_date = calculate_class_day(class_day, time_zone).strftime("%Y-%m-%d")
65
40
 
66
41
  try:
67
- available_slots = bot.daily_slots(activity=activity, day=booking_date)
68
-
69
- matching_slots = available_slots[available_slots["start_timestamp"] == f"{booking_date} {class_time}"]
70
- if matching_slots.empty:
71
- _raise_no_matching_slots_error(activity, class_time, booking_date)
72
-
73
- slot_id = matching_slots.iloc[0]["start_timestamp"]
74
- logger.info(f"Attempting to book '{activity}' at {slot_id} (Attempt {attempt_num}/{retry_attempts}).")
75
- bot.book(activity=activity, start_time=slot_id)
42
+ bot.book(activity=activity, start_time=f"{booking_date} {class_time}")
76
43
 
77
44
  except Exception as e:
78
45
  error_str = str(e)
79
46
  logger.warning(f"Attempt {attempt_num} failed: {error_str}")
80
47
 
48
+ # Decide whether to retry based on the error message
81
49
  if ErrorMessages.slot_already_booked() in error_str:
82
50
  logger.warning("Slot already booked; skipping further retries.")
83
51
  return
52
+ if ErrorMessages.slot_capacity_full() in error_str:
53
+ logger.warning("Slot capacity full; skipping further retries.")
54
+ return
84
55
 
85
56
  if attempt_num < retry_attempts:
86
57
  logger.info(f"Retrying in {retry_delay} seconds...")
@@ -119,21 +90,51 @@ def schedule_bookings(
119
90
  for cls in config["classes"]:
120
91
  logger.info(f"Scheduled to book '{cls['activity']}' next {cls['class_day']} at {cls['class_time']}.")
121
92
 
122
- # Wait globally before starting bookings
123
- wait_for_execution(config["booking_execution"], time_zone)
93
+ # Booking execution day and time
94
+ booking_execution = config["booking_execution"]
95
+
96
+ # Exact time when booking will be executed (modulo global booking delay)
97
+ execution_time = calculate_next_execution(booking_execution, time_zone)
98
+
99
+ # Get the time now
100
+ now = datetime.now(pytz.timezone(time_zone))
101
+
102
+ # Calculate the seconds until execution
103
+ time_until_execution = (execution_time - now).total_seconds()
104
+
105
+ if time_until_execution > 0:
106
+
107
+ logger.info(
108
+ f"Waiting {time_until_execution:.2f} seconds until global execution time: "
109
+ f"{execution_time.strftime('%Y-%m-%d %H:%M:%S %z')}."
110
+ )
111
+ # Re-authenticate 60 seconds before booking execution
112
+ reauth_time = time_until_execution - 60
113
+
114
+ if reauth_time <= 0:
115
+ logger.debug("Less than 60 seconds remain until execution; re-authenticating now.")
116
+ else:
117
+ logger.debug(f"Re-authenticating in {reauth_time:.2f} seconds.")
118
+ time.sleep(reauth_time)
119
+
120
+ # Re-authenticate before booking
121
+ logger.info("Re-authenticating before booking.")
122
+ try:
123
+ bot.login(config["email"], config["password"], config["centre"])
124
+ except Exception:
125
+ logger.warning("Re-authentication failed before booking execution.")
126
+
127
+ # Wait the remaining time until execution
128
+ now = datetime.now(pytz.timezone(time_zone))
129
+ remaining_time = (execution_time - now).total_seconds()
130
+ if remaining_time > 0:
131
+ logger.info(f"Waiting {remaining_time:.2f} seconds until booking execution.")
132
+ time.sleep(remaining_time)
124
133
 
125
134
  # Global booking delay
126
135
  logger.info(f"Waiting {booking_delay} seconds before attempting booking.")
127
136
  time.sleep(booking_delay)
128
137
 
129
- # Re-authenticate before booking
130
- logger.debug("Re-authenticating before booking.")
131
- try:
132
- bot.login(config["email"], config["password"], config["centre"])
133
- except Exception:
134
- logger.exception("Re-authentication failed before booking execution.")
135
- raise
136
-
137
138
  # Submit bookings in parallel
138
139
  with ThreadPoolExecutor(max_workers=max_threads) as executor:
139
140
  future_to_class = {
@@ -156,4 +157,4 @@ def schedule_bookings(
156
157
  try:
157
158
  future.result()
158
159
  except Exception:
159
- logger.exception(f"Booking for '{activity}' at {class_time} failed.")
160
+ logger.error(f"Booking for '{activity}' at {class_time} failed.")
@@ -115,6 +115,11 @@ class ErrorMessages:
115
115
  """Return an error message for a slot that is unavailable."""
116
116
  return "The slot is not available."
117
117
 
118
+ @staticmethod
119
+ def slot_capacity_full() -> str:
120
+ """Return an error message for a slot that is full."""
121
+ return "The slot is full. Cannot book."
122
+
118
123
  @staticmethod
119
124
  def cancellation_failed() -> str:
120
125
  """Return an error message for a failed cancellation."""
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: pysportbot
3
- Version: 0.0.10
3
+ Version: 0.0.11
4
4
  Summary: A python-based bot for automatic resasports slot booking
5
5
  Author: Joshua Falco Beirer
6
6
  Author-email: jbeirer@cern.ch
@@ -1,4 +1,4 @@
1
- pysportbot/__init__.py,sha256=xrsPD3OrQGbT9ttuMp1vi-i69i4A-ePNc9IoutcJHnk,5620
1
+ pysportbot/__init__.py,sha256=tZwOIuLO1-a3d4KKA5nNfXfN6PjJxem_hxyuff9utk4,6425
2
2
  pysportbot/activities.py,sha256=Aq8l570sxqEAKplsEMVkuDoPJJjoaZlIwPh6i64_SJ8,4252
3
3
  pysportbot/authenticator.py,sha256=xEU9O6W6Yp9DCueoUD0ASMkm17yvdUZqTvV4h6WlSnI,5012
4
4
  pysportbot/bookings.py,sha256=W91AYGPu0sCpGliuiVdlENsoGYzH8P6v-SyKisbSb7g,3127
@@ -6,7 +6,7 @@ pysportbot/centres.py,sha256=FTK-tXUOxiJvLCHP6Bk9XEQKODQZOwwkYLlioSJPBEk,3399
6
6
  pysportbot/endpoints.py,sha256=ANh5JAbdzyZQ-i4ODrhYlskPpU1gkBrw9UhMC7kRSvU,1353
7
7
  pysportbot/service/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
8
  pysportbot/service/__main__.py,sha256=gsKfDMOmsVC3LHCQs0Dmp7YWJBlZeGcTsX-Mx4mk6ro,1496
9
- pysportbot/service/booking.py,sha256=ZrfIX0ghp1X_DUtknpQbY-ABi2nWFLZYAXR06cOb4lk,5616
9
+ pysportbot/service/booking.py,sha256=cOUzqbpX6UTlDB5-bQZdiBnc7tdbvBQtJLQNXsM9ZHE,5661
10
10
  pysportbot/service/config_loader.py,sha256=t086yaAyAKkCJTpxedwhyJ7QqSf5XROGDzjrFLsUxJE,179
11
11
  pysportbot/service/config_validator.py,sha256=0P_pcXU7s3T3asODqFtv3mSp1HyPoVBphE4Qe1mxcps,2615
12
12
  pysportbot/service/scheduling.py,sha256=bwqbiQQ9y4ss4UXZkWuOVCgCa9XlZt1n4TT_8z9bD7M,1973
@@ -14,10 +14,10 @@ pysportbot/service/service.py,sha256=eW-roFozDzkK7kTzYbSzNwZhbZpBr-22yUrAHVnrD-0
14
14
  pysportbot/service/threading.py,sha256=j0tHgGty8iWDjpZtTzIu-5TUnDb9S6SJErm3QBpG-nY,1947
15
15
  pysportbot/session.py,sha256=pTQrz3bGzLYBtzVOgKv04l4UXDSgtA3Infn368bjg5I,1529
16
16
  pysportbot/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
17
- pysportbot/utils/errors.py,sha256=mpTeKjOUIxJbOqL6A6apdMb7Cpto86DkrKHWfROXjMc,4979
17
+ pysportbot/utils/errors.py,sha256=IRarfGtk1tETxUGAhPAIbeits_u2tJvVCGTtkHIdYFQ,5146
18
18
  pysportbot/utils/logger.py,sha256=ANayMEeeAIVGKvITAxOFm2EdCbzBBTpNywuytAr4Z90,5366
19
19
  pysportbot/utils/time.py,sha256=VZSW8AxFIoFD5ZSmLUPcwawp6PmpkcxNjP3Db-Hl_fw,1244
20
- pysportbot-0.0.10.dist-info/LICENSE,sha256=6ov3DypdEVYpp2pn_B1MniKWO5C9iDA4O6PGcbork6c,1077
21
- pysportbot-0.0.10.dist-info/METADATA,sha256=tWybhZ0aefyjtkRBAQZzQgALjd7tAOaMXvUPdD-Byvk,5161
22
- pysportbot-0.0.10.dist-info/WHEEL,sha256=RaoafKOydTQ7I_I3JTrPCg6kUmTgtm4BornzOqyEfJ8,88
23
- pysportbot-0.0.10.dist-info/RECORD,,
20
+ pysportbot-0.0.11.dist-info/LICENSE,sha256=6ov3DypdEVYpp2pn_B1MniKWO5C9iDA4O6PGcbork6c,1077
21
+ pysportbot-0.0.11.dist-info/METADATA,sha256=uPyuHrJiU-pC1zOVdlKpwgZgowrTLsI_PGZ7IzxbZME,5161
22
+ pysportbot-0.0.11.dist-info/WHEEL,sha256=RaoafKOydTQ7I_I3JTrPCg6kUmTgtm4BornzOqyEfJ8,88
23
+ pysportbot-0.0.11.dist-info/RECORD,,