pysportbot 0.0.15__py3-none-any.whl → 0.0.17__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.
@@ -30,6 +30,27 @@ class Authenticator:
30
30
  self.user_id = None
31
31
  # Centre selected by the user
32
32
  self.centre = centre
33
+ # Timeout of requests
34
+ self.timeout = (5, 10)
35
+
36
+ def is_session_valid(self) -> bool:
37
+ """
38
+ Check if the current session is still valid.
39
+
40
+ Returns:
41
+ bool: True if session is valid, False otherwise.
42
+ """
43
+ try:
44
+ response = self.session.post(Endpoints.USER, headers=self.headers, timeout=self.timeout)
45
+ if response.status_code == 200:
46
+ response_dict = json.loads(response.content.decode("utf-8"))
47
+ return bool(response_dict.get("user"))
48
+ else:
49
+ logger.debug(f"Session validation failed with status code: {response.status_code}")
50
+ return False
51
+ except Exception as e:
52
+ logger.debug(f"Session validation failed with exception: {e}")
53
+ return False
33
54
 
34
55
  def login(self, email: str, password: str) -> None:
35
56
  """
@@ -46,11 +67,12 @@ class Authenticator:
46
67
 
47
68
  # Step 1: Fetch CSRF token
48
69
  logger.debug(f"GET {Endpoints.USER_LOGIN} | Headers: {json.dumps(self.headers, indent=2)}")
49
- response = self.session.get(Endpoints.USER_LOGIN, headers=self.headers)
70
+ response = self.session.get(Endpoints.USER_LOGIN, headers=self.headers, timeout=self.timeout)
50
71
  if response.status_code != 200:
51
72
  logger.error(f"Failed to fetch login popup: {response.status_code}")
52
73
  raise RuntimeError(ErrorMessages.failed_fetch("login popup"))
53
74
  logger.debug("Login popup fetched successfully.")
75
+
54
76
  soup = BeautifulSoup(response.text, "html.parser")
55
77
  csrf_tag = soup.find("input", {"name": "_csrf_token"})
56
78
  if csrf_tag is None:
@@ -70,7 +92,7 @@ class Authenticator:
70
92
  f"POST {Endpoints.LOGIN_CHECK} | Headers: {json.dumps(self.headers, indent=2)} | "
71
93
  f"Payload: {json.dumps(payload, indent=2)}"
72
94
  )
73
- response = self.session.post(Endpoints.LOGIN_CHECK, data=payload, headers=self.headers)
95
+ response = self.session.post(Endpoints.LOGIN_CHECK, data=payload, headers=self.headers, timeout=self.timeout)
74
96
  if response.status_code != 200:
75
97
  logger.error(f"Login failed: {response.status_code}, {response.text}")
76
98
  raise ValueError(ErrorMessages.failed_login())
@@ -79,7 +101,7 @@ class Authenticator:
79
101
  # Step 3: Retrieve credentials for Nubapp
80
102
  cred_endpoint = Endpoints.get_cred_endpoint(self.centre)
81
103
  logger.debug(f"GET {cred_endpoint} | Headers: {json.dumps(self.headers, indent=2)}")
82
- response = self.session.get(cred_endpoint, headers=self.headers)
104
+ response = self.session.get(cred_endpoint, headers=self.headers, timeout=self.timeout)
83
105
  if response.status_code != 200:
84
106
  logger.error(f"Failed to retrieve Nubapp credentials: {response.status_code}")
85
107
  raise RuntimeError(ErrorMessages.failed_fetch("credentials"))
@@ -94,14 +116,16 @@ class Authenticator:
94
116
  f"GET {Endpoints.NUBAP_LOGIN} | Headers: {json.dumps(self.headers, indent=2)} | "
95
117
  f"Params: {json.dumps(nubapp_creds, indent=2)}"
96
118
  )
97
- response = self.session.get(Endpoints.NUBAP_LOGIN, headers=self.headers, params=nubapp_creds)
119
+ response = self.session.get(
120
+ Endpoints.NUBAP_LOGIN, headers=self.headers, params=nubapp_creds, timeout=self.timeout
121
+ )
98
122
  if response.status_code != 200:
99
123
  logger.error(f"Login to Nubapp failed: {response.status_code}, {response.text}")
100
124
  raise ValueError(ErrorMessages.failed_login_nubapp())
101
125
  logger.info("Login to Nubapp successful!")
102
126
 
103
127
  # Step 5: Get user information
104
- response = self.session.post(Endpoints.USER, headers=self.headers, allow_redirects=True)
128
+ response = self.session.post(Endpoints.USER, headers=self.headers, allow_redirects=True, timeout=self.timeout)
105
129
 
106
130
  if response.status_code == 200:
107
131
  response_dict = json.loads(response.content.decode("utf-8"))
@@ -117,19 +117,23 @@ def schedule_bookings(
117
117
  logger.debug(f"Re-authenticating in {reauth_time:.2f} seconds.")
118
118
  time.sleep(reauth_time)
119
119
 
120
- # Re-authenticate before booking
121
- logger.info("Re-authenticating before booking.")
120
+ # Re-authenticate before booking if necessary
122
121
  try:
123
- bot.login(config["email"], config["password"], config["centre"])
124
- except Exception:
125
- logger.warning("Re-authentication failed before booking execution.")
122
+ if bot._auth and bot._auth.is_session_valid():
123
+ logger.info("Session still valid. Skipping re-authentication.")
124
+ else:
125
+ logger.info("Attempting re-authenticating before booking.")
126
+ bot.login(config["email"], config["password"], config["centre"])
127
+
128
+ except Exception as e:
129
+ logger.warning(f"Re-authentication failed before booking execution with {e}.")
126
130
 
127
131
  # Wait the remaining time until execution
128
132
  now = datetime.now(pytz.timezone(time_zone))
129
133
  remaining_time = (execution_time - now).total_seconds()
130
134
  if remaining_time > 0:
131
135
  logger.info(f"Waiting {remaining_time:.2f} seconds until booking execution.")
132
- time.sleep(remaining_time)
136
+ time.sleep(max(0, remaining_time))
133
137
 
134
138
  # Global booking delay
135
139
  if booking_delay > 0:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: pysportbot
3
- Version: 0.0.15
3
+ Version: 0.0.17
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
@@ -141,10 +141,12 @@ For example, if you have a **Yoga** class on **Monday at 18:00:00**, and you wan
141
141
  name: Weekly Booking
142
142
 
143
143
  on:
144
- # Runs at 06:00 UTC (07:00 CET) on Fridays
145
- # Ensure enough time for the job to start well before 07:30 CET
144
+ # Runs at 05:00 UTC (07:00 Madrid time during daylight saving time) on Fridays
145
+ # Note: Update to '0 6 * * 5' for winter (standard) time when Madrid shifts to UTC+1
146
+ # GitHub Actions cron expressions are always in UTC and do not support time zones
147
+ # Reference: https://github.com/orgs/community/discussions/13454
146
148
  schedule:
147
- - cron: '0 6 * * 5'
149
+ - cron: '0 5 * * 5'
148
150
 
149
151
  jobs:
150
152
  book:
@@ -1,12 +1,12 @@
1
1
  pysportbot/__init__.py,sha256=tZwOIuLO1-a3d4KKA5nNfXfN6PjJxem_hxyuff9utk4,6425
2
2
  pysportbot/activities.py,sha256=MATA0feSsDSoeFnHa4Y_dLv3gw4P6MFg-0kAEybqVIY,4991
3
- pysportbot/authenticator.py,sha256=t_5x9anmsfh0SwpN9JSemzqKv72Hwt__Q8mQBowIOIo,5183
3
+ pysportbot/authenticator.py,sha256=rs3QG9aEFxG0THMm-7STqnfMlvBG3a56KnBYMYNvSmQ,6142
4
4
  pysportbot/bookings.py,sha256=W91AYGPu0sCpGliuiVdlENsoGYzH8P6v-SyKisbSb7g,3127
5
5
  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=oNLdY3nhs3ulAD-yFoc6YhgohbSN16xRYjEL8iyHkZg,5695
9
+ pysportbot/service/booking.py,sha256=ifeo1LbtPvls-hpQOZW5TvoqeZ1MJxIipim4RHTH7nI,5908
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=trz4zweZB2W9rwWAnW9Y6YkCo-f4dqtKyhaY_yqpJ-Y,2024
@@ -17,7 +17,7 @@ pysportbot/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,
17
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.15.dist-info/LICENSE,sha256=6ov3DypdEVYpp2pn_B1MniKWO5C9iDA4O6PGcbork6c,1077
21
- pysportbot-0.0.15.dist-info/METADATA,sha256=ZOKMqVbdeuv-8YQTxRvdrQxZUgsVQphDj0k7t7MKfec,7837
22
- pysportbot-0.0.15.dist-info/WHEEL,sha256=IYZQI976HJqqOpQU6PHkJ8fb3tMNBFjg-Cn-pwAbaFM,88
23
- pysportbot-0.0.15.dist-info/RECORD,,
20
+ pysportbot-0.0.17.dist-info/LICENSE,sha256=6ov3DypdEVYpp2pn_B1MniKWO5C9iDA4O6PGcbork6c,1077
21
+ pysportbot-0.0.17.dist-info/METADATA,sha256=PW2ipkWtyRp83h_Zd1fTre_5LTuKFfGR2AT-96pnmno,8045
22
+ pysportbot-0.0.17.dist-info/WHEEL,sha256=IYZQI976HJqqOpQU6PHkJ8fb3tMNBFjg-Cn-pwAbaFM,88
23
+ pysportbot-0.0.17.dist-info/RECORD,,