pysportbot 0.0.19__py3-none-any.whl → 0.0.21__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/authenticator.py +224 -137
- pysportbot/endpoints.py +45 -34
- pysportbot/service/booking.py +0 -1
- pysportbot/utils/errors.py +1 -1
- {pysportbot-0.0.19.dist-info → pysportbot-0.0.21.dist-info}/METADATA +3 -4
- {pysportbot-0.0.19.dist-info → pysportbot-0.0.21.dist-info}/RECORD +8 -8
- {pysportbot-0.0.19.dist-info → pysportbot-0.0.21.dist-info}/WHEEL +1 -1
- {pysportbot-0.0.19.dist-info → pysportbot-0.0.21.dist-info}/licenses/LICENSE +1 -1
pysportbot/authenticator.py
CHANGED
|
@@ -1,7 +1,4 @@
|
|
|
1
1
|
import json
|
|
2
|
-
from urllib.parse import parse_qs, urlparse
|
|
3
|
-
|
|
4
|
-
from bs4 import BeautifulSoup
|
|
5
2
|
|
|
6
3
|
from .endpoints import Endpoints
|
|
7
4
|
from .session import Session
|
|
@@ -12,206 +9,296 @@ logger = get_logger(__name__)
|
|
|
12
9
|
|
|
13
10
|
|
|
14
11
|
class Authenticator:
|
|
15
|
-
"""
|
|
12
|
+
"""
|
|
13
|
+
Handles user authentication and Nubapp login functionality.
|
|
16
14
|
|
|
17
|
-
|
|
18
|
-
"""
|
|
19
|
-
Initialize the Authenticator.
|
|
15
|
+
Flow overview:
|
|
20
16
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
17
|
+
1. Login to Resasocial (api.resasocial.com) via /user/login
|
|
18
|
+
-> get Resasocial JWT + (id_user, id_application).
|
|
19
|
+
|
|
20
|
+
2. Store (id_user, id_application) in self.creds for use by Activities.
|
|
21
|
+
|
|
22
|
+
3. Call /secure/user/getSportUserToken with the Resasocial JWT
|
|
23
|
+
-> get Nubapp (sport.nubapp.com) JWT.
|
|
24
|
+
|
|
25
|
+
4. Store Nubapp JWT in self.headers["Authorization"].
|
|
26
|
+
|
|
27
|
+
5. Use Nubapp JWT to call /api/v4/users/getUser.php and verify the user.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
def __init__(self, session: Session, centre: str) -> None:
|
|
25
31
|
self.session = session.session
|
|
32
|
+
# Base headers; will be enriched with Nubapp JWT after login
|
|
26
33
|
self.headers = session.headers
|
|
27
|
-
|
|
34
|
+
# Centre is still passed in from the bot, but not used in the new flow
|
|
28
35
|
self.centre = centre
|
|
29
36
|
self.timeout = (5, 10)
|
|
30
37
|
|
|
31
38
|
# Authentication state
|
|
32
|
-
self.authenticated = False
|
|
39
|
+
self.authenticated: bool = False
|
|
33
40
|
self.user_id: str | None = None
|
|
34
41
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
42
|
+
# Minimal "credentials" object used by Activities
|
|
43
|
+
# (id_application, id_user) are filled after /user/login.
|
|
44
|
+
self.creds: dict[str, str] = {}
|
|
38
45
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
if response.status_code == 200:
|
|
45
|
-
response_dict = json.loads(response.content.decode("utf-8"))
|
|
46
|
-
return bool(response_dict.get("user"))
|
|
46
|
+
# Resasocial (resasports) JWT state
|
|
47
|
+
self.resasocial_jwt: str | None = None
|
|
48
|
+
self.resasocial_refresh: str | None = None
|
|
49
|
+
self.id_user: str | None = None
|
|
50
|
+
self.id_application: str | None = None
|
|
47
51
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
52
|
+
# Nubapp JWT tokens used for authenticated sport.nubapp.com requests
|
|
53
|
+
self.sport_jwt: str | None = None
|
|
54
|
+
self.sport_refresh: str | None = None
|
|
51
55
|
|
|
52
|
-
|
|
53
|
-
|
|
56
|
+
# ------------------------------------------------------------------
|
|
57
|
+
# Public API
|
|
58
|
+
# ------------------------------------------------------------------
|
|
54
59
|
|
|
55
60
|
def login(self, email: str, password: str) -> None:
|
|
56
61
|
"""
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
Args:
|
|
60
|
-
email (str): The user's email address.
|
|
61
|
-
password (str): The user's password.
|
|
62
|
+
Full login flow:
|
|
62
63
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
64
|
+
1. Resasocial JSON login (/user/login)
|
|
65
|
+
2. Fill self.creds with (id_application, id_user) for Activities
|
|
66
|
+
3. /secure/user/getSportUserToken -> Nubapp JWT
|
|
67
|
+
4. Store Nubapp JWT in headers and fetch user info to confirm identity
|
|
66
68
|
"""
|
|
67
69
|
logger.info("Starting login process...")
|
|
68
70
|
|
|
69
71
|
try:
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
#
|
|
73
|
-
self.
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
self.
|
|
79
|
-
|
|
72
|
+
self._resasocial_jwt_login(email, password)
|
|
73
|
+
|
|
74
|
+
# Expose IDs in the same structure Activities expects
|
|
75
|
+
self.creds = {
|
|
76
|
+
"id_application": str(self.id_application),
|
|
77
|
+
"id_user": str(self.id_user),
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
self._get_sport_user_token()
|
|
81
|
+
self._authenticate_with_bearer_token(self.sport_jwt)
|
|
80
82
|
self._fetch_user_information()
|
|
81
83
|
|
|
84
|
+
self.authenticated = True
|
|
82
85
|
logger.info("Login process completed successfully!")
|
|
83
86
|
|
|
84
|
-
except Exception as
|
|
87
|
+
except Exception as exc:
|
|
85
88
|
self.authenticated = False
|
|
86
89
|
self.user_id = None
|
|
87
|
-
logger.error(f"Login process failed: {
|
|
88
|
-
|
|
90
|
+
logger.error(f"Login process failed: {exc}")
|
|
91
|
+
# Normalize to a consistent login error for callers/tests
|
|
92
|
+
raise ValueError(ErrorMessages.failed_login()) from exc
|
|
89
93
|
|
|
90
|
-
def
|
|
91
|
-
"""
|
|
92
|
-
|
|
94
|
+
def is_session_valid(self) -> bool:
|
|
95
|
+
"""
|
|
96
|
+
Check whether the current Nubapp JWT is still valid by probing USER.
|
|
97
|
+
"""
|
|
98
|
+
if not self.sport_jwt:
|
|
99
|
+
return False
|
|
93
100
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
payload = {
|
|
112
|
-
"_username": email,
|
|
113
|
-
"_password": password,
|
|
114
|
-
"_csrf_token": csrf_token,
|
|
115
|
-
"_submit": "",
|
|
116
|
-
"_force": "true",
|
|
117
|
-
}
|
|
101
|
+
try:
|
|
102
|
+
# At this point self.headers already contains Nubapp Authorization
|
|
103
|
+
response = self.session.post(
|
|
104
|
+
Endpoints.USER,
|
|
105
|
+
headers=self.headers,
|
|
106
|
+
timeout=self.timeout,
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
if response.status_code != 200:
|
|
110
|
+
return False
|
|
111
|
+
|
|
112
|
+
data = response.json()
|
|
113
|
+
return bool(data.get("data", {}).get("user"))
|
|
114
|
+
|
|
115
|
+
except Exception as exc:
|
|
116
|
+
logger.debug(f"Session validation failed: {exc}")
|
|
117
|
+
return False
|
|
118
118
|
|
|
119
|
+
# ------------------------------------------------------------------
|
|
120
|
+
# Step 1: Resasocial JWT login
|
|
121
|
+
# ------------------------------------------------------------------
|
|
122
|
+
|
|
123
|
+
def _resasocial_jwt_login(self, email: str, password: str) -> None:
|
|
124
|
+
"""Perform login via the /user/login JSON endpoint on api.resasocial.com."""
|
|
125
|
+
logger.debug("Logging in via Resasocial /user/login (JWT flow)")
|
|
126
|
+
|
|
127
|
+
payload = {"username": email, "password": password}
|
|
128
|
+
# Use a copy of the base headers: no Authorization here.
|
|
119
129
|
headers = self.headers.copy()
|
|
120
|
-
headers.update({"Content-Type": "application/x-www-form-urlencoded"})
|
|
121
130
|
|
|
122
|
-
response = self.session.post(
|
|
131
|
+
response = self.session.post(
|
|
132
|
+
Endpoints.USER_LOGIN,
|
|
133
|
+
json=payload,
|
|
134
|
+
headers=headers,
|
|
135
|
+
timeout=self.timeout,
|
|
136
|
+
)
|
|
123
137
|
|
|
124
138
|
if response.status_code != 200:
|
|
125
|
-
logger.error(
|
|
139
|
+
logger.error(
|
|
140
|
+
"JWT login failed with status %s. Body (truncated): %r",
|
|
141
|
+
response.status_code,
|
|
142
|
+
response.text[:200],
|
|
143
|
+
)
|
|
126
144
|
raise ValueError(ErrorMessages.failed_login())
|
|
127
145
|
|
|
128
|
-
|
|
146
|
+
try:
|
|
147
|
+
data = response.json()
|
|
148
|
+
except Exception as exc:
|
|
149
|
+
logger.error(
|
|
150
|
+
"JWT login returned non-JSON response. Body (truncated): %r",
|
|
151
|
+
response.text[:200],
|
|
152
|
+
)
|
|
153
|
+
raise ValueError(ErrorMessages.failed_login()) from exc
|
|
154
|
+
|
|
155
|
+
logger.debug(
|
|
156
|
+
"/user/login response keys: %s; applications count=%d",
|
|
157
|
+
list(data.keys()),
|
|
158
|
+
len(data.get("applications") or []),
|
|
159
|
+
)
|
|
129
160
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
logger.debug("Retrieving Nubapp credentials")
|
|
161
|
+
self.resasocial_jwt = data.get("jwt_token")
|
|
162
|
+
self.resasocial_refresh = data.get("refresh_token")
|
|
133
163
|
|
|
134
|
-
|
|
135
|
-
|
|
164
|
+
apps = data.get("applications") or []
|
|
165
|
+
if not apps:
|
|
166
|
+
logger.error("No applications returned in /user/login response")
|
|
167
|
+
raise ValueError(ErrorMessages.failed_login())
|
|
136
168
|
|
|
137
|
-
|
|
138
|
-
|
|
169
|
+
first_app = apps[0]
|
|
170
|
+
self.id_application = first_app.get("id_application")
|
|
171
|
+
self.id_user = first_app.get("id_user")
|
|
139
172
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
173
|
+
if not self.resasocial_jwt or not self.id_user or not self.id_application:
|
|
174
|
+
logger.error(
|
|
175
|
+
"Missing required fields in /user/login response: "
|
|
176
|
+
f"jwt_token={self.resasocial_jwt}, id_user={self.id_user}, "
|
|
177
|
+
f"id_application={self.id_application}"
|
|
178
|
+
)
|
|
179
|
+
raise ValueError(ErrorMessages.failed_login())
|
|
180
|
+
|
|
181
|
+
logger.info(
|
|
182
|
+
"JWT login successful. id_user=%s, id_application=%s",
|
|
183
|
+
self.id_user,
|
|
184
|
+
self.id_application,
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
# ------------------------------------------------------------------
|
|
188
|
+
# Step 3: getSportUserToken -> Nubapp JWT
|
|
189
|
+
# ------------------------------------------------------------------
|
|
145
190
|
|
|
146
|
-
|
|
147
|
-
|
|
191
|
+
def _get_sport_user_token(self) -> None:
|
|
192
|
+
"""
|
|
193
|
+
Request the Nubapp JWT token using the Resasocial JWT.
|
|
194
|
+
This replaces the old login_from_social.php redirect method.
|
|
195
|
+
"""
|
|
196
|
+
logger.debug("Fetching Nubapp JWT via getSportUserToken")
|
|
148
197
|
|
|
149
|
-
|
|
150
|
-
|
|
198
|
+
if not self.resasocial_jwt or not self.id_user or not self.id_application:
|
|
199
|
+
logger.error("Cannot fetch sport user token without resasocial_jwt, id_user, id_application")
|
|
200
|
+
raise ValueError(ErrorMessages.failed_login())
|
|
151
201
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
202
|
+
# Local headers specific to this Resasocial-authenticated call
|
|
203
|
+
social_auth_headers = self.headers.copy()
|
|
204
|
+
social_auth_headers["Authorization"] = f"Bearer {self.resasocial_jwt}"
|
|
205
|
+
|
|
206
|
+
params = {
|
|
207
|
+
"id_user": self.id_user,
|
|
208
|
+
"id_application": self.id_application,
|
|
209
|
+
}
|
|
155
210
|
|
|
156
211
|
response = self.session.get(
|
|
157
|
-
Endpoints.
|
|
158
|
-
|
|
159
|
-
|
|
212
|
+
Endpoints.SPORT_USER_TOKEN,
|
|
213
|
+
params=params,
|
|
214
|
+
headers=social_auth_headers,
|
|
160
215
|
timeout=self.timeout,
|
|
161
|
-
allow_redirects=False,
|
|
162
216
|
)
|
|
163
217
|
|
|
164
|
-
if response.status_code !=
|
|
165
|
-
logger.error(
|
|
218
|
+
if response.status_code != 200:
|
|
219
|
+
logger.error(
|
|
220
|
+
"getSportUserToken failed with status %s. Body (truncated): %r",
|
|
221
|
+
response.status_code,
|
|
222
|
+
response.text[:200],
|
|
223
|
+
)
|
|
166
224
|
raise ValueError(ErrorMessages.failed_login())
|
|
167
225
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
226
|
+
try:
|
|
227
|
+
data = response.json()
|
|
228
|
+
except Exception as exc:
|
|
229
|
+
logger.error(
|
|
230
|
+
"getSportUserToken returned non-JSON response. Body (truncated): %r",
|
|
231
|
+
response.text[:200],
|
|
232
|
+
)
|
|
233
|
+
raise ValueError(ErrorMessages.failed_login()) from exc
|
|
234
|
+
|
|
235
|
+
logger.debug("response keys: %s", list(data.keys()))
|
|
236
|
+
|
|
237
|
+
self.sport_jwt = data.get("jwt_token")
|
|
238
|
+
self.sport_refresh = data.get("refresh_token")
|
|
239
|
+
|
|
240
|
+
if not self.sport_jwt:
|
|
241
|
+
logger.error("No jwt_token found in getSportUserToken response")
|
|
171
242
|
raise ValueError(ErrorMessages.failed_login())
|
|
172
243
|
|
|
173
|
-
|
|
174
|
-
|
|
244
|
+
logger.info("Nubapp JWT obtained successfully.")
|
|
245
|
+
|
|
246
|
+
# ------------------------------------------------------------------
|
|
247
|
+
# Step 4: Set Authorization header for Nubapp
|
|
248
|
+
# ------------------------------------------------------------------
|
|
175
249
|
|
|
250
|
+
def _authenticate_with_bearer_token(self, token: str | None) -> None:
|
|
251
|
+
"""
|
|
252
|
+
Store the Nubapp JWT in self.headers so that all subsequent
|
|
253
|
+
Nubapp API calls (including Activities) share the same auth.
|
|
254
|
+
"""
|
|
176
255
|
if not token:
|
|
177
256
|
raise ValueError(ErrorMessages.failed_login())
|
|
178
257
|
|
|
179
|
-
logger.
|
|
180
|
-
return token
|
|
181
|
-
|
|
182
|
-
def _authenticate_with_bearer_token(self, token: str) -> None:
|
|
183
|
-
"""Add bearer token to headers for authentication."""
|
|
184
|
-
logger.debug("Setting up bearer token authentication")
|
|
258
|
+
logger.debug("Setting Nubapp Authorization header")
|
|
185
259
|
self.headers["Authorization"] = f"Bearer {token}"
|
|
186
260
|
|
|
261
|
+
# ------------------------------------------------------------------
|
|
262
|
+
# Step 5: Nubapp User info
|
|
263
|
+
# ------------------------------------------------------------------
|
|
264
|
+
|
|
187
265
|
def _fetch_user_information(self) -> None:
|
|
188
|
-
"""
|
|
189
|
-
|
|
266
|
+
"""
|
|
267
|
+
Fetch and validate user information from Nubapp.
|
|
190
268
|
|
|
191
|
-
payload
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
269
|
+
No extra payload — we just rely on the Nubapp JWT already set in self.headers.
|
|
270
|
+
"""
|
|
271
|
+
logger.debug("Fetching user info from Nubapp")
|
|
272
|
+
|
|
273
|
+
if not self.sport_jwt:
|
|
274
|
+
raise ValueError(ErrorMessages.failed_login())
|
|
195
275
|
|
|
196
|
-
response = self.session.post(
|
|
276
|
+
response = self.session.post(
|
|
277
|
+
Endpoints.USER,
|
|
278
|
+
headers=self.headers,
|
|
279
|
+
timeout=self.timeout,
|
|
280
|
+
)
|
|
197
281
|
|
|
198
282
|
if response.status_code != 200:
|
|
283
|
+
logger.error(
|
|
284
|
+
"Fetching user info failed with status %s. Body (truncated): %r",
|
|
285
|
+
response.status_code,
|
|
286
|
+
response.text[:200],
|
|
287
|
+
)
|
|
199
288
|
raise ValueError(ErrorMessages.failed_login())
|
|
200
289
|
|
|
201
290
|
try:
|
|
202
|
-
|
|
203
|
-
|
|
291
|
+
data = response.json()
|
|
292
|
+
except (json.JSONDecodeError, ValueError) as exc:
|
|
293
|
+
raise ValueError(f"Failed to parse user information: {exc}") from exc
|
|
204
294
|
|
|
205
|
-
|
|
206
|
-
|
|
295
|
+
user_data = data.get("data", {}).get("user")
|
|
296
|
+
if not user_data:
|
|
297
|
+
raise ValueError("No user data found in response")
|
|
207
298
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
self.user_id = str(user_id)
|
|
213
|
-
self.authenticated = True
|
|
214
|
-
logger.info(f"Authentication successful. User ID: {self.user_id}")
|
|
299
|
+
user_id = user_data.get("id_user")
|
|
300
|
+
if not user_id:
|
|
301
|
+
raise ValueError("No user ID found in response")
|
|
215
302
|
|
|
216
|
-
|
|
217
|
-
|
|
303
|
+
self.user_id = str(user_id)
|
|
304
|
+
logger.info("Authentication successful. User ID: %s", self.user_id)
|
pysportbot/endpoints.py
CHANGED
|
@@ -1,58 +1,69 @@
|
|
|
1
|
-
from enum import
|
|
1
|
+
from enum import StrEnum
|
|
2
2
|
|
|
3
3
|
|
|
4
|
-
class Endpoints(
|
|
4
|
+
class Endpoints(StrEnum):
|
|
5
5
|
"""
|
|
6
|
-
API endpoints used
|
|
6
|
+
Centralized collection of API endpoints used by the bot.
|
|
7
7
|
|
|
8
|
-
This enum provides type-safe access to all
|
|
9
|
-
and automatic string conversion for use in HTTP requests.
|
|
8
|
+
This enum provides type-safe access to all URLs with clear structure.
|
|
10
9
|
"""
|
|
11
10
|
|
|
12
|
-
#
|
|
13
|
-
|
|
11
|
+
# ============================================================
|
|
12
|
+
# Base URLs
|
|
13
|
+
# ============================================================
|
|
14
|
+
|
|
15
|
+
# Resasocial / Resasports API (used for login, centre data, etc.)
|
|
16
|
+
BASE_SOCIAL = "https://api.resasocial.com"
|
|
17
|
+
|
|
18
|
+
# Nubapp API (used for bookings, user data, etc.)
|
|
14
19
|
BASE_NUBAPP = "https://sport.nubapp.com"
|
|
15
20
|
|
|
16
|
-
#
|
|
17
|
-
NUBAPP_RESOURCES = "web/resources"
|
|
21
|
+
# Path used for all Nubapp JSON API endpoints
|
|
18
22
|
NUBAPP_API = "api/v4"
|
|
19
23
|
|
|
20
|
-
#
|
|
24
|
+
# ============================================================
|
|
25
|
+
# Centre Management
|
|
26
|
+
# ============================================================
|
|
27
|
+
|
|
28
|
+
# Returns the list of centres with their bounds / metadata.
|
|
29
|
+
# Used by Centres.fetch_centres() to populate the centre list (slug, name, etc).
|
|
21
30
|
CENTRE = f"{BASE_SOCIAL}/ajax/applications/bounds/"
|
|
22
31
|
|
|
23
|
-
#
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
32
|
+
# ============================================================
|
|
33
|
+
# Authentication
|
|
34
|
+
# ============================================================
|
|
35
|
+
|
|
36
|
+
# Resasports login endpoint
|
|
37
|
+
USER_LOGIN = f"{BASE_SOCIAL}/user/login"
|
|
38
|
+
|
|
39
|
+
# Nubapp authentification via JWT token
|
|
40
|
+
SPORT_USER_TOKEN = f"{BASE_SOCIAL}/secure/user/getSportUserToken"
|
|
41
|
+
|
|
42
|
+
# ============================================================
|
|
43
|
+
# Nubapp User & Application
|
|
44
|
+
# ============================================================
|
|
27
45
|
|
|
28
|
-
#
|
|
46
|
+
# User information endpoint (requires Nubapp JWT)
|
|
29
47
|
USER = f"{BASE_NUBAPP}/{NUBAPP_API}/users/getUser.php"
|
|
30
48
|
|
|
31
|
-
#
|
|
49
|
+
# ============================================================
|
|
50
|
+
# Activities & Scheduling
|
|
51
|
+
# ============================================================
|
|
52
|
+
|
|
32
53
|
ACTIVITIES = f"{BASE_NUBAPP}/{NUBAPP_API}/activities/getActivities.php"
|
|
33
54
|
SLOTS = f"{BASE_NUBAPP}/{NUBAPP_API}/activities/getActivitiesCalendar.php"
|
|
34
55
|
|
|
35
|
-
#
|
|
56
|
+
# ============================================================
|
|
57
|
+
# Booking
|
|
58
|
+
# ============================================================
|
|
59
|
+
|
|
36
60
|
BOOKING = f"{BASE_NUBAPP}/{NUBAPP_API}/activities/bookActivityCalendar.php"
|
|
37
61
|
CANCELLATION = f"{BASE_NUBAPP}/{NUBAPP_API}/activities/leaveActivityCalendar.php"
|
|
38
62
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
Generate the credentials endpoint for a specific centre.
|
|
43
|
-
|
|
44
|
-
Args:
|
|
45
|
-
centre_slug (str): The unique identifier for the sports centre
|
|
46
|
-
|
|
47
|
-
Returns:
|
|
48
|
-
str: Complete URL for fetching centre credentials
|
|
49
|
-
|
|
50
|
-
Example:
|
|
51
|
-
>>> Endpoints.get_cred_endpoint("kirolklub")
|
|
52
|
-
"https://social.resasports.com/ajax/application/kirolklub/book/request"
|
|
53
|
-
"""
|
|
54
|
-
return f"{cls.BASE_SOCIAL}/ajax/application/{centre_slug}/book/request"
|
|
63
|
+
# ============================================================
|
|
64
|
+
# Utility
|
|
65
|
+
# ============================================================
|
|
55
66
|
|
|
56
67
|
def __str__(self) -> str:
|
|
57
|
-
"""Return
|
|
68
|
+
"""Return URL string for direct HTTP usage."""
|
|
58
69
|
return str(self.value)
|
pysportbot/service/booking.py
CHANGED
|
@@ -103,7 +103,6 @@ def schedule_bookings(
|
|
|
103
103
|
time_until_execution = (execution_time - now).total_seconds()
|
|
104
104
|
|
|
105
105
|
if time_until_execution > 0:
|
|
106
|
-
|
|
107
106
|
logger.info(
|
|
108
107
|
f"Waiting {time_until_execution:.2f} seconds until global execution time: "
|
|
109
108
|
f"{execution_time.strftime('%Y-%m-%d %H:%M:%S %z')}."
|
pysportbot/utils/errors.py
CHANGED
|
@@ -20,7 +20,7 @@ class ErrorMessages:
|
|
|
20
20
|
|
|
21
21
|
@staticmethod
|
|
22
22
|
def invalid_class_definition() -> str:
|
|
23
|
-
return "Each class must include 'activity', 'class_day',
|
|
23
|
+
return "Each class must include 'activity', 'class_day', 'class_time'"
|
|
24
24
|
|
|
25
25
|
@staticmethod
|
|
26
26
|
def invalid_booking_execution_format() -> str:
|
|
@@ -1,17 +1,16 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pysportbot
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.21
|
|
4
4
|
Summary: A python-based bot for automatic resasports slot booking
|
|
5
5
|
License-File: LICENSE
|
|
6
6
|
Author: Joshua Falco Beirer
|
|
7
7
|
Author-email: jbeirer@cern.ch
|
|
8
|
-
Requires-Python: >=3.
|
|
8
|
+
Requires-Python: >=3.11,<3.15
|
|
9
9
|
Classifier: Programming Language :: Python :: 3
|
|
10
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
11
10
|
Classifier: Programming Language :: Python :: 3.11
|
|
12
11
|
Classifier: Programming Language :: Python :: 3.12
|
|
13
12
|
Classifier: Programming Language :: Python :: 3.13
|
|
14
|
-
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
15
14
|
Requires-Dist: pandas (>=2.3.2,<3.0.0)
|
|
16
15
|
Requires-Dist: pytz (>=2025.2,<2026.0)
|
|
17
16
|
Requires-Dist: requests (>=2.32.5,<3.0.0)
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
pysportbot/__init__.py,sha256=PdDUmkEBSOkdw3BaKMv1sXAUgYSTl6_qUOnAEj9UDBM,6570
|
|
2
2
|
pysportbot/activities.py,sha256=fvj1Pnf3xxDuNY2FBGDOaach6tcZ_HJxCfoyGVju0oM,7598
|
|
3
|
-
pysportbot/authenticator.py,sha256=
|
|
3
|
+
pysportbot/authenticator.py,sha256=vsY_ZZ9ONfv5qNAk0stNshKbXckj8JulLN91Nt_bpK8,10891
|
|
4
4
|
pysportbot/bookings.py,sha256=yJPgCTECXBEVtQzwj3lUJ8QR3h43ycCUw80mTx-Ck60,3189
|
|
5
5
|
pysportbot/centres.py,sha256=FTK-tXUOxiJvLCHP6Bk9XEQKODQZOwwkYLlioSJPBEk,3399
|
|
6
|
-
pysportbot/endpoints.py,sha256=
|
|
6
|
+
pysportbot/endpoints.py,sha256=nHKsBIZ3dFmtOyWm7clCFqVOI8BUxn6qcbiauBQqf3U,2549
|
|
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=
|
|
9
|
+
pysportbot/service/booking.py,sha256=42KTHTgZtwtZhb7WbVc0Pk-z0V0j1cE00JOLDTlM3wY,5907
|
|
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
|
|
@@ -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=
|
|
17
|
+
pysportbot/utils/errors.py,sha256=02hNu-dXwCOumS9nvo7hjY4RfJ7o-6gKSZzzo_0QTx0,4953
|
|
18
18
|
pysportbot/utils/logger.py,sha256=ANayMEeeAIVGKvITAxOFm2EdCbzBBTpNywuytAr4Z90,5366
|
|
19
19
|
pysportbot/utils/time.py,sha256=GKfFU5WwRSaWz2xNJ6EM0w1lHOVo8of5qhqnfh9xbJM,1926
|
|
20
|
-
pysportbot-0.0.
|
|
21
|
-
pysportbot-0.0.
|
|
22
|
-
pysportbot-0.0.
|
|
23
|
-
pysportbot-0.0.
|
|
20
|
+
pysportbot-0.0.21.dist-info/METADATA,sha256=dpLakm9vBctTXlTn6293gYxzGKQxLgAKu5VgNzaLRXY,8143
|
|
21
|
+
pysportbot-0.0.21.dist-info/WHEEL,sha256=kJCRJT_g0adfAJzTx2GUMmS80rTJIVHRCfG0DQgLq3o,88
|
|
22
|
+
pysportbot-0.0.21.dist-info/licenses/LICENSE,sha256=xrsutFXNH37d__Bn3Kk2AjqHf4sTzwEa1HSkzfYKEXs,1077
|
|
23
|
+
pysportbot-0.0.21.dist-info/RECORD,,
|