pysportbot 0.0.12__py3-none-any.whl → 0.0.14__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/activities.py +32 -8
- pysportbot/authenticator.py +5 -1
- pysportbot/service/scheduling.py +2 -1
- {pysportbot-0.0.12.dist-info → pysportbot-0.0.14.dist-info}/METADATA +65 -4
- {pysportbot-0.0.12.dist-info → pysportbot-0.0.14.dist-info}/RECORD +7 -7
- {pysportbot-0.0.12.dist-info → pysportbot-0.0.14.dist-info}/WHEEL +1 -1
- {pysportbot-0.0.12.dist-info → pysportbot-0.0.14.dist-info}/LICENSE +0 -0
pysportbot/activities.py
CHANGED
@@ -32,13 +32,27 @@ class Activities:
|
|
32
32
|
"""
|
33
33
|
logger.info("Fetching activities...")
|
34
34
|
response = self.session.post(Endpoints.ACTIVITIES, headers=self.headers)
|
35
|
+
|
35
36
|
if response.status_code != 200:
|
36
37
|
error_msg = ErrorMessages.failed_fetch("activities")
|
37
38
|
logger.error(error_msg)
|
38
39
|
raise RuntimeError(error_msg)
|
40
|
+
|
41
|
+
try:
|
42
|
+
activities = response.json().get("activities", {})
|
43
|
+
except json.JSONDecodeError as err:
|
44
|
+
error_msg = "Invalid JSON response while fetching activities."
|
45
|
+
logger.error(error_msg)
|
46
|
+
raise RuntimeError(error_msg) from err
|
47
|
+
|
48
|
+
if not activities:
|
49
|
+
logger.warning("No activities found in the response.")
|
50
|
+
|
51
|
+
df_activities = pd.DataFrame.from_dict(activities, orient="index")
|
52
|
+
df_activities.index = df_activities.index.astype(int) # Ensure index is integer for consistency
|
53
|
+
|
39
54
|
logger.info("Activities fetched successfully.")
|
40
|
-
|
41
|
-
return pd.DataFrame.from_dict(activities, orient="index")
|
55
|
+
return df_activities
|
42
56
|
|
43
57
|
def daily_slots(self, df_activities: DataFrame, activity_name: str, day: str) -> DataFrame:
|
44
58
|
"""
|
@@ -67,9 +81,10 @@ class Activities:
|
|
67
81
|
logger.error(error_msg)
|
68
82
|
raise ValueError(error_msg)
|
69
83
|
|
70
|
-
# Extract activity ID
|
71
|
-
activity_id
|
72
|
-
|
84
|
+
# Extract activity ID and category ID
|
85
|
+
# Ensures activity_id is an integer
|
86
|
+
activity_id = activity_match.index[0]
|
87
|
+
id_category_activity = activity_match.at[activity_id, "activityCategoryId"]
|
73
88
|
|
74
89
|
# Get Unix timestamp bounds for the day
|
75
90
|
unix_day_bounds = get_unix_day_bounds(day)
|
@@ -81,12 +96,19 @@ class Activities:
|
|
81
96
|
"end": unix_day_bounds[1],
|
82
97
|
}
|
83
98
|
response = self.session.get(Endpoints.SLOTS, headers=self.headers, params=params)
|
99
|
+
|
84
100
|
if response.status_code != 200:
|
85
101
|
error_msg = ErrorMessages.failed_fetch("slots")
|
86
102
|
logger.error(error_msg)
|
87
103
|
raise RuntimeError(error_msg)
|
88
104
|
|
89
|
-
|
105
|
+
try:
|
106
|
+
slots = response.json()
|
107
|
+
except json.JSONDecodeError as err:
|
108
|
+
error_msg = "Invalid JSON response while fetching slots."
|
109
|
+
logger.error(error_msg)
|
110
|
+
raise RuntimeError(error_msg) from err
|
111
|
+
|
90
112
|
if not slots:
|
91
113
|
warning_msg = ErrorMessages.no_slots(activity_name, day)
|
92
114
|
logger.warning(warning_msg)
|
@@ -110,10 +132,12 @@ class Activities:
|
|
110
132
|
"trainer",
|
111
133
|
]
|
112
134
|
df_slots = pd.DataFrame(slots)
|
113
|
-
|
135
|
+
|
136
|
+
# Ensure only desired columns are selected without KeyError
|
137
|
+
df_slots = df_slots.loc[:, df_slots.columns.intersection(columns)]
|
114
138
|
|
115
139
|
# Only select rows of the specified activity
|
116
|
-
df_slots = df_slots[df_slots
|
140
|
+
df_slots = df_slots[df_slots["id_activity"] == activity_id]
|
117
141
|
if df_slots.empty:
|
118
142
|
warning_msg = ErrorMessages.no_matching_slots(activity_name, day)
|
119
143
|
logger.warning(warning_msg)
|
pysportbot/authenticator.py
CHANGED
@@ -51,7 +51,11 @@ class Authenticator:
|
|
51
51
|
logger.error(f"Failed to fetch login popup: {response.status_code}")
|
52
52
|
raise RuntimeError(ErrorMessages.failed_fetch("login popup"))
|
53
53
|
logger.debug("Login popup fetched successfully.")
|
54
|
-
|
54
|
+
soup = BeautifulSoup(response.text, "html.parser")
|
55
|
+
csrf_tag = soup.find("input", {"name": "_csrf_token"})
|
56
|
+
if csrf_tag is None:
|
57
|
+
raise ValueError("CSRF token input not found on the page")
|
58
|
+
csrf_token = csrf_tag["value"] # type: ignore[index]
|
55
59
|
|
56
60
|
# Step 2: Perform login
|
57
61
|
payload = {
|
pysportbot/service/scheduling.py
CHANGED
@@ -56,6 +56,7 @@ def calculate_class_day(class_day: str, time_zone: str = "Europe/Madrid") -> dat
|
|
56
56
|
now = datetime.now(tz)
|
57
57
|
target_weekday = DAY_MAP[class_day.lower().strip()]
|
58
58
|
days_ahead = target_weekday - now.weekday()
|
59
|
-
|
59
|
+
# Ensure we always book for the upcoming week
|
60
|
+
if days_ahead <= 0:
|
60
61
|
days_ahead += 7
|
61
62
|
return now + timedelta(days=days_ahead)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.3
|
2
2
|
Name: pysportbot
|
3
|
-
Version: 0.0.
|
3
|
+
Version: 0.0.14
|
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
|
@@ -10,9 +10,9 @@ Classifier: Programming Language :: Python :: 3.10
|
|
10
10
|
Classifier: Programming Language :: Python :: 3.11
|
11
11
|
Classifier: Programming Language :: Python :: 3.12
|
12
12
|
Classifier: Programming Language :: Python :: 3.13
|
13
|
-
Requires-Dist: beautifulsoup4 (>=4.
|
13
|
+
Requires-Dist: beautifulsoup4 (>=4.13.3,<5.0.0)
|
14
14
|
Requires-Dist: pandas (>=2.2.3,<3.0.0)
|
15
|
-
Requires-Dist: pytz (>=
|
15
|
+
Requires-Dist: pytz (>=2025.1,<2026.0)
|
16
16
|
Requires-Dist: requests (>=2.32.3,<3.0.0)
|
17
17
|
Description-Content-Type: text/markdown
|
18
18
|
|
@@ -109,7 +109,11 @@ Let's say you would like to book Yoga next Monday at 18:00:00, but the execution
|
|
109
109
|
}
|
110
110
|
```
|
111
111
|
|
112
|
-
**Note:**
|
112
|
+
**Note:**
|
113
|
+
1. Bookings are always scheduled for the following week. For example, if your booking execution is set for Friday at 07:30:00 and your class is on Friday at 18:00:00, the system will schedule the booking for **next Friday** at 18:00:00, not the same day.
|
114
|
+
2. PySportBot automatically attempts to execute up to *N* bookings in parallel, where *N* corresponds to the number of available CPU cores on your machine.
|
115
|
+
3. If the number of bookings exceeds the available parallel slots, they will be processed sequentially in the exact order specified in your JSON configuration file.
|
116
|
+
|
113
117
|
|
114
118
|
The service also provides various other options that can be inspected with
|
115
119
|
|
@@ -125,6 +129,63 @@ Currently supported options include:
|
|
125
129
|
5. `--log-level`: sets the log-level of the service [default: INFO]
|
126
130
|
6. `--max-threads`: limits the number of used threads for parallel bookings [default: -1]
|
127
131
|
|
132
|
+
## Automating Weekly Bookings with GitHub Actions
|
133
|
+
|
134
|
+
While you can use PySportBot as a continuously running service, doing so often becomes cumbersome because it requires a server (or similar machine) that is always online.
|
135
|
+
|
136
|
+
Using **[GitHub Actions](https://docs.github.com/en/actions)** is one of the most reliable ways to schedule **automated weekly bookings**. With a simple workflow file, you can ensure your bookings run at a specific time each week — no manual intervention required.
|
137
|
+
|
138
|
+
For example, if you have a **Yoga** class on **Monday at 18:00:00**, and you want the **booking** to be executed on **Friday at 07:30:00**, you can automate the process by configuring a **[GitHub Actions workflow](https://docs.github.com/en/actions/writing-workflows)**. Below is a sample workflow file that triggers every **Friday at 06:00 UTC (07:00 CET)**, ensuring there is enough time before the actual booking execution at 07:30:00:
|
139
|
+
|
140
|
+
```yml
|
141
|
+
name: Weekly Booking
|
142
|
+
|
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
|
146
|
+
schedule:
|
147
|
+
- cron: '0 6 * * 5'
|
148
|
+
|
149
|
+
jobs:
|
150
|
+
book:
|
151
|
+
runs-on: ubuntu-latest
|
152
|
+
|
153
|
+
steps:
|
154
|
+
- name: Check out repository
|
155
|
+
uses: actions/checkout@v2
|
156
|
+
|
157
|
+
- name: Set up Python 3.12
|
158
|
+
uses: actions/setup-python@v2
|
159
|
+
with:
|
160
|
+
python-version: '3.12'
|
161
|
+
|
162
|
+
- name: Install pysportbot
|
163
|
+
run: |
|
164
|
+
pip install pysportbot
|
165
|
+
|
166
|
+
- name: Create config file
|
167
|
+
run: |
|
168
|
+
echo '{
|
169
|
+
"email": "your-email",
|
170
|
+
"password": "your-password",
|
171
|
+
"centre": "your-gym",
|
172
|
+
"booking_execution": "Friday 07:30:00",
|
173
|
+
"classes": [
|
174
|
+
{
|
175
|
+
"activity": "Yoga",
|
176
|
+
"class_day": "Monday",
|
177
|
+
"class_time": "18:00:00"
|
178
|
+
}
|
179
|
+
]
|
180
|
+
}' > config.json
|
181
|
+
|
182
|
+
- name: Run pysportbot
|
183
|
+
run: |
|
184
|
+
python -m pysportbot.service --config config.json --log-level INFO --booking-delay 1 --retry-attempts 3 --retry-delay 5
|
185
|
+
```
|
186
|
+
**Note:** Make sure the workflow starts early enough — before your booking execution time (e.g., 07:30 CET) — to allow for potential delays in job startup or execution.
|
187
|
+
|
188
|
+
|
128
189
|
## LICENSE
|
129
190
|
|
130
191
|
pysportbot is free of use and open-source. All versions are
|
@@ -1,6 +1,6 @@
|
|
1
1
|
pysportbot/__init__.py,sha256=tZwOIuLO1-a3d4KKA5nNfXfN6PjJxem_hxyuff9utk4,6425
|
2
|
-
pysportbot/activities.py,sha256=
|
3
|
-
pysportbot/authenticator.py,sha256=
|
2
|
+
pysportbot/activities.py,sha256=MATA0feSsDSoeFnHa4Y_dLv3gw4P6MFg-0kAEybqVIY,4991
|
3
|
+
pysportbot/authenticator.py,sha256=t_5x9anmsfh0SwpN9JSemzqKv72Hwt__Q8mQBowIOIo,5183
|
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
|
@@ -9,7 +9,7 @@ pysportbot/service/__main__.py,sha256=gsKfDMOmsVC3LHCQs0Dmp7YWJBlZeGcTsX-Mx4mk6r
|
|
9
9
|
pysportbot/service/booking.py,sha256=oNLdY3nhs3ulAD-yFoc6YhgohbSN16xRYjEL8iyHkZg,5695
|
10
10
|
pysportbot/service/config_loader.py,sha256=t086yaAyAKkCJTpxedwhyJ7QqSf5XROGDzjrFLsUxJE,179
|
11
11
|
pysportbot/service/config_validator.py,sha256=0P_pcXU7s3T3asODqFtv3mSp1HyPoVBphE4Qe1mxcps,2615
|
12
|
-
pysportbot/service/scheduling.py,sha256=
|
12
|
+
pysportbot/service/scheduling.py,sha256=trz4zweZB2W9rwWAnW9Y6YkCo-f4dqtKyhaY_yqpJ-Y,2024
|
13
13
|
pysportbot/service/service.py,sha256=eW-roFozDzkK7kTzYbSzNwZhbZpBr-22yUrAHVnrD-0,1933
|
14
14
|
pysportbot/service/threading.py,sha256=j0tHgGty8iWDjpZtTzIu-5TUnDb9S6SJErm3QBpG-nY,1947
|
15
15
|
pysportbot/session.py,sha256=pTQrz3bGzLYBtzVOgKv04l4UXDSgtA3Infn368bjg5I,1529
|
@@ -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.
|
21
|
-
pysportbot-0.0.
|
22
|
-
pysportbot-0.0.
|
23
|
-
pysportbot-0.0.
|
20
|
+
pysportbot-0.0.14.dist-info/LICENSE,sha256=6ov3DypdEVYpp2pn_B1MniKWO5C9iDA4O6PGcbork6c,1077
|
21
|
+
pysportbot-0.0.14.dist-info/METADATA,sha256=aUx264Y6n5fGY4GvpK0tClB03Gm_XyJd0cBboTWzJ1c,7837
|
22
|
+
pysportbot-0.0.14.dist-info/WHEEL,sha256=IYZQI976HJqqOpQU6PHkJ8fb3tMNBFjg-Cn-pwAbaFM,88
|
23
|
+
pysportbot-0.0.14.dist-info/RECORD,,
|
File without changes
|