pysportbot 0.0.4__py3-none-any.whl → 0.0.6__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 CHANGED
@@ -92,7 +92,7 @@ class Activities:
92
92
  logger.warning(warning_msg)
93
93
  return DataFrame()
94
94
 
95
- logger.info(f"Daily slots fetched for '{activity_name}' on {day}.")
95
+ logger.debug(f"Daily slots fetched for '{activity_name}' on {day}.")
96
96
 
97
97
  # Filter desired columns
98
98
  columns = [
@@ -10,10 +10,10 @@ from .service import run_service
10
10
  def main() -> None:
11
11
  parser = argparse.ArgumentParser(description="Run the pysportbot as a service.")
12
12
  parser.add_argument("--config", type=str, required=True, help="Path to the JSON configuration file.")
13
- parser.add_argument("--offset-seconds", type=int, default=10, help="Time offset in seconds before booking.")
13
+ parser.add_argument("--offset-seconds", type=int, default=5, help="Time offset in seconds before booking.")
14
14
  parser.add_argument("--retry-attempts", type=int, default=3, help="Number of retry attempts for weekly bookings.")
15
15
  parser.add_argument(
16
- "--retry-delay-minutes", type=int, default=2, help="Delay in minutes between retries for weekly bookings."
16
+ "--retry-delay-minutes", type=int, default=1, help="Delay in minutes between retries for weekly bookings."
17
17
  )
18
18
  parser.add_argument("--time-zone", type=str, default="Europe/Madrid", help="Timezone for the service.")
19
19
  parser.add_argument("--log-level", type=str, default="INFO", help="Logging level for the service.")
@@ -1,147 +1,156 @@
1
1
  import time
2
- from datetime import datetime, timedelta
3
- from typing import Any, Dict
2
+ from concurrent.futures import ThreadPoolExecutor, as_completed
3
+ from datetime import datetime
4
+ from typing import Any, Dict, List
4
5
 
5
6
  import pytz
6
- import schedule
7
7
 
8
8
  from pysportbot import SportBot
9
9
  from pysportbot.utils.errors import ErrorMessages
10
10
  from pysportbot.utils.logger import get_logger
11
11
 
12
- from .config_validator import DAY_MAP
13
12
  from .scheduling import calculate_class_day, calculate_next_execution
14
13
 
15
14
  logger = get_logger(__name__)
16
15
 
17
16
 
18
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:
19
22
  """
20
- Helper function to raise a ValueError for no matching slots.
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.
21
28
  """
22
- raise ValueError(ErrorMessages.no_matching_slots_for_time(activity, class_time, booking_date))
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)
23
40
 
24
41
 
25
42
  def attempt_booking(
26
43
  bot: SportBot,
27
- cls: Dict[str, Any],
44
+ activity: str,
45
+ class_day: str,
46
+ class_time: str,
28
47
  offset_seconds: int,
29
48
  retry_attempts: int = 1,
30
49
  retry_delay_minutes: int = 0,
31
50
  time_zone: str = "Europe/Madrid",
32
51
  ) -> None:
33
- activity = cls["activity"]
34
- class_day = cls["class_day"]
35
- class_time = cls["class_time"]
36
- booking_execution = cls["booking_execution"]
37
-
52
+ """
53
+ Attempt to book a slot for the given class.
54
+
55
+ Args:
56
+ bot (SportBot): The SportBot instance.
57
+ activity (str): Activity name.
58
+ class_day (str): Day of the class.
59
+ class_time (str): Time of the class.
60
+ offset_seconds (int): Delay before attempting booking.
61
+ retry_attempts (int): Number of retry attempts.
62
+ retry_delay_minutes (int): Delay between retries.
63
+ time_zone (str): Time zone for execution.
64
+ """
38
65
  for attempt_num in range(1, retry_attempts + 1):
39
66
  booking_date = calculate_class_day(class_day, time_zone).strftime("%Y-%m-%d")
40
67
 
41
68
  try:
42
- logger.info(f"Fetching available slots for {activity} on {booking_date}")
69
+ logger.info(f"Fetching available slots for '{activity}' on {booking_date}.")
43
70
  available_slots = bot.daily_slots(activity=activity, day=booking_date)
44
71
 
45
72
  matching_slots = available_slots[available_slots["start_timestamp"] == f"{booking_date} {class_time}"]
46
73
  if matching_slots.empty:
47
74
  _raise_no_matching_slots_error(activity, class_time, booking_date)
48
75
 
49
- if booking_execution != "now":
50
- logger.info(f"Waiting {offset_seconds} seconds before attempting booking.")
51
- time.sleep(offset_seconds)
76
+ logger.info(f"Waiting {offset_seconds} seconds before attempting booking.")
77
+ time.sleep(offset_seconds)
52
78
 
53
79
  slot_id = matching_slots.iloc[0]["start_timestamp"]
54
- logger.info(f"Attempting to book slot for {activity} at {slot_id} (Attempt {attempt_num}/{retry_attempts})")
80
+ logger.info(f"Attempting to book '{activity}' at {slot_id} (Attempt {attempt_num}/{retry_attempts}).")
55
81
  bot.book(activity=activity, start_time=slot_id)
56
- logger.info(f"Successfully booked {activity} at {slot_id}")
57
82
 
58
83
  except Exception as e:
59
84
  error_str = str(e)
60
- logger.warning(f"Attempt {attempt_num} failed for {activity}: {error_str}")
85
+ logger.warning(f"Attempt {attempt_num} failed: {error_str}")
61
86
 
62
87
  if ErrorMessages.slot_already_booked() in error_str:
63
- logger.warning(f"{activity} at {class_time} on {booking_date} is already booked; skipping retry.")
88
+ logger.warning("Slot already booked; skipping further retries.")
64
89
  return
65
90
 
66
91
  if attempt_num < retry_attempts:
67
92
  logger.info(f"Retrying in {retry_delay_minutes} minutes...")
68
93
  time.sleep(retry_delay_minutes * 60)
69
94
  else:
95
+ # If the booking attempt succeeds, log and exit
96
+ logger.info(f"Successfully booked '{activity}' at {slot_id}.")
70
97
  return
71
98
 
72
- logger.error(f"Failed to book {activity} after {retry_attempts} attempts.")
99
+ # If all attempts fail, log an error
100
+ logger.error(f"Failed to book '{activity}' after {retry_attempts} attempts.")
73
101
 
74
102
 
75
- def schedule_bookings(
103
+ def schedule_bookings_parallel(
76
104
  bot: SportBot,
77
- config: Dict[str, Any],
78
- cls: Dict[str, Any],
105
+ classes: List[Dict[str, Any]],
106
+ booking_execution: str,
79
107
  offset_seconds: int,
80
108
  retry_attempts: int,
81
109
  retry_delay_minutes: int,
82
- time_zone: str = "Europe/Madrid",
110
+ time_zone: str,
111
+ max_threads: int,
83
112
  ) -> None:
84
- booking_execution = cls["booking_execution"]
85
- weekly = cls["weekly"]
86
- activity = cls["activity"]
87
- class_day = cls["class_day"]
88
- class_time = cls["class_time"]
89
-
90
- if weekly:
91
- # For weekly bookings, schedule recurring jobs
92
- execution_day, execution_time = booking_execution.split()
93
- logger.info(
94
- f"Class '{activity}' on {class_day} at {class_time} "
95
- f"will be booked every {execution_day} at {execution_time}."
96
- )
97
-
98
- def booking_task() -> None:
113
+ """
114
+ Execute bookings in parallel with a limit on the number of threads.
115
+
116
+ Args:
117
+ bot (SportBot): The SportBot instance.
118
+ classes (list): List of class configurations.
119
+ booking_execution (str): Global execution time for all bookings.
120
+ offset_seconds (int): Delay before each booking attempt.
121
+ retry_attempts (int): Number of retry attempts.
122
+ retry_delay_minutes (int): Delay between retries.
123
+ time_zone (str): Timezone for booking.
124
+ max_threads (int): Maximum number of threads to use.
125
+ """
126
+ # Log planned bookings
127
+ for cls in classes:
128
+ logger.info(f"Scheduled to book '{cls['activity']}' next {cls['class_day']} at {cls['class_time']}.")
129
+
130
+ # Wait globally before starting bookings
131
+ wait_for_execution(booking_execution, time_zone)
132
+
133
+ with ThreadPoolExecutor(max_workers=max_threads) as executor:
134
+ future_to_class = {
135
+ executor.submit(
136
+ attempt_booking,
137
+ bot,
138
+ cls["activity"],
139
+ cls["class_day"],
140
+ cls["class_time"],
141
+ offset_seconds,
142
+ retry_attempts,
143
+ retry_delay_minutes,
144
+ time_zone,
145
+ ): cls
146
+ for cls in classes
147
+ }
148
+
149
+ for future in as_completed(future_to_class):
150
+ cls = future_to_class[future]
151
+ activity, class_time = cls["activity"], cls["class_time"]
99
152
  try:
100
- logger.info("Re-authenticating before weekly booking...")
101
- bot.login(config["email"], config["password"], config["centre"])
102
- logger.info("Re-authentication successful.")
103
- attempt_booking(
104
- bot,
105
- cls,
106
- offset_seconds,
107
- retry_attempts,
108
- retry_delay_minutes,
109
- time_zone,
110
- )
153
+ future.result()
154
+ logger.info(f"Booking for '{activity}' at {class_time} completed successfully.")
111
155
  except Exception:
112
- logger.exception(f"Failed to execute weekly booking task for {activity}")
113
-
114
- # e.g., schedule.every().monday.at("HH:MM:SS").do(...)
115
- getattr(schedule.every(), execution_day.lower()).at(execution_time).do(booking_task)
116
-
117
- else:
118
- # For one-off (non-weekly) bookings, calculate exact date/time
119
- next_execution = calculate_next_execution(booking_execution, time_zone)
120
- tz = pytz.timezone(time_zone)
121
-
122
- day_of_week_target = DAY_MAP[class_day.lower().strip()]
123
- execution_day_of_week = next_execution.weekday()
124
-
125
- days_to_class = (day_of_week_target - execution_day_of_week + 7) % 7
126
- planned_class_date_dt = next_execution + timedelta(days=days_to_class)
127
- planned_class_date_str = planned_class_date_dt.strftime("%Y-%m-%d (%A)")
128
-
129
- next_execution_str = next_execution.strftime("%Y-%m-%d (%A) %H:%M:%S %z")
130
-
131
- logger.info(
132
- f"Class '{activity}' on {planned_class_date_str} at {class_time} "
133
- f"will be booked on {next_execution_str}."
134
- )
135
-
136
- # Wait until the next execution time
137
- time_until_execution = (next_execution - datetime.now(tz)).total_seconds()
138
- time.sleep(max(0, time_until_execution))
139
-
140
- attempt_booking(
141
- bot,
142
- cls,
143
- offset_seconds,
144
- retry_attempts=retry_attempts,
145
- retry_delay_minutes=retry_delay_minutes,
146
- time_zone=time_zone,
147
- )
156
+ logger.exception(f"Booking for '{activity}' at {class_time} failed.")
@@ -11,36 +11,54 @@ DAY_MAP = {"monday": 0, "tuesday": 1, "wednesday": 2, "thursday": 3, "friday": 4
11
11
 
12
12
 
13
13
  def validate_config(config: Dict[str, Any]) -> None:
14
- required_keys = ["email", "password", "centre", "classes"]
14
+ """
15
+ Validate the overall configuration structure and values.
16
+
17
+ Args:
18
+ config (Dict[str, Any]): Configuration dictionary.
19
+
20
+ Raises:
21
+ ValueError: If the configuration is invalid.
22
+ """
23
+
24
+ def raise_invalid_booking_format_error() -> None:
25
+ """Helper function to raise an error for invalid booking_execution format."""
26
+ raise ValueError(ErrorMessages.invalid_booking_execution_format())
27
+
28
+ required_keys = ["email", "password", "centre", "classes", "booking_execution"]
15
29
  for key in required_keys:
16
30
  if key not in config:
17
31
  raise ValueError(ErrorMessages.missing_required_key(key))
18
32
 
33
+ # Validate global booking_execution
34
+ if config["booking_execution"] != "now":
35
+ try:
36
+ day_and_time = config["booking_execution"].split()
37
+ if len(day_and_time) != 2:
38
+ raise_invalid_booking_format_error()
39
+
40
+ _, exec_time = day_and_time
41
+ datetime.strptime(exec_time, "%H:%M:%S")
42
+ except ValueError:
43
+ raise_invalid_booking_format_error()
44
+
45
+ # Validate individual class definitions
19
46
  for cls in config["classes"]:
20
- if (
21
- "activity" not in cls
22
- or "class_day" not in cls
23
- or "class_time" not in cls
24
- or "booking_execution" not in cls
25
- or "weekly" not in cls
26
- ):
47
+ if "activity" not in cls or "class_day" not in cls or "class_time" not in cls:
27
48
  raise ValueError(ErrorMessages.invalid_class_definition())
28
49
 
29
- if cls["weekly"] and cls["booking_execution"] == "now":
30
- raise ValueError(ErrorMessages.invalid_weekly_now())
31
50
 
32
- if cls["booking_execution"] != "now":
33
- day_and_time = cls["booking_execution"].split()
34
- if len(day_and_time) != 2:
35
- raise ValueError(ErrorMessages.invalid_booking_execution_format())
36
- _, exec_time = day_and_time
37
- try:
38
- datetime.strptime(exec_time, "%H:%M:%S")
39
- except ValueError as err:
40
- raise ValueError(ErrorMessages.invalid_booking_execution_format()) from err
51
+ def validate_activities(bot: SportBot, config: Dict[str, Any]) -> None:
52
+ """
53
+ Validate that all activities specified in the configuration exist.
41
54
 
55
+ Args:
56
+ bot (SportBot): The SportBot instance.
57
+ config (Dict[str, Any]): Configuration dictionary.
42
58
 
43
- def validate_activities(bot: SportBot, config: Dict[str, Any]) -> None:
59
+ Raises:
60
+ ValueError: If an activity is not found.
61
+ """
44
62
  logger.info("Fetching available activities for validation...")
45
63
  available_activities = bot.activities()
46
64
  available_activity_names = set(available_activities["name_activity"].tolist())
@@ -51,4 +69,5 @@ def validate_activities(bot: SportBot, config: Dict[str, Any]) -> None:
51
69
  activity_name = cls["activity"]
52
70
  if activity_name not in available_activity_names:
53
71
  raise ValueError(ErrorMessages.activity_not_found(activity_name, list(available_activity_names)))
72
+
54
73
  logger.info("All activities in the configuration file have been validated.")
@@ -1,14 +1,11 @@
1
- import time
1
+ import os
2
2
  from typing import Any, Dict
3
3
 
4
- import schedule
5
-
6
4
  from pysportbot import SportBot
5
+ from pysportbot.service.booking import schedule_bookings_parallel
6
+ from pysportbot.service.config_validator import validate_activities, validate_config
7
7
  from pysportbot.utils.logger import get_logger
8
8
 
9
- from .booking import schedule_bookings
10
- from .config_validator import validate_activities, validate_config
11
-
12
9
 
13
10
  def run_service(
14
11
  config: Dict[str, Any],
@@ -18,32 +15,45 @@ def run_service(
18
15
  time_zone: str = "Europe/Madrid",
19
16
  log_level: str = "INFO",
20
17
  ) -> None:
21
- # Initialize service logger
18
+ """
19
+ Run the booking service with the given configuration.
20
+
21
+ Args:
22
+ config (dict): Configuration dictionary for booking service.
23
+ offset_seconds (int): Delay before each booking attempt.
24
+ retry_attempts (int): Number of retry attempts.
25
+ retry_delay_minutes (int): Delay between retry attempts in minutes.
26
+ time_zone (str): Time zone for the booking.
27
+ log_level (str): Logging level for the service.
28
+ """
29
+ # Initialize logger
22
30
  logger = get_logger(__name__)
23
31
  logger.setLevel(log_level)
24
32
 
25
- # Validate the configuration file
33
+ # Validate configuration
26
34
  validate_config(config)
27
- # Initialize the SportBot instance
35
+
36
+ # Initialize the SportBot and authenticate
28
37
  bot = SportBot(log_level=log_level, time_zone=time_zone)
29
38
  bot.login(config["email"], config["password"], config["centre"])
30
39
 
31
- # Validate the activities in the configuration file
40
+ # Validate activities in the configuration
32
41
  validate_activities(bot, config)
33
42
 
34
- for cls in config["classes"]:
35
- schedule_bookings(
36
- bot,
37
- config,
38
- cls,
39
- offset_seconds,
40
- retry_attempts,
41
- retry_delay_minutes,
42
- time_zone,
43
- )
44
-
45
- if schedule.jobs:
46
- logger.info("Weekly bookings scheduled. Running the scheduler...")
47
- while True:
48
- schedule.run_pending()
49
- time.sleep(1)
43
+ # Determine the number of threads
44
+ max_threads = min(len(config["classes"]), os.cpu_count() or 1)
45
+ logger.info(f"Using up to {max_threads} threads for booking {len(config['classes'])} activities.")
46
+
47
+ # Schedule bookings in parallel
48
+ schedule_bookings_parallel(
49
+ bot,
50
+ config["classes"],
51
+ config["booking_execution"],
52
+ offset_seconds,
53
+ retry_attempts,
54
+ retry_delay_minutes,
55
+ time_zone,
56
+ max_threads,
57
+ )
58
+
59
+ logger.info("All bookings completed.")
@@ -1,4 +1,5 @@
1
1
  import logging
2
+ import threading
2
3
  from datetime import datetime
3
4
  from typing import ClassVar, Optional
4
5
 
@@ -8,7 +9,7 @@ from .errors import ErrorMessages
8
9
 
9
10
 
10
11
  class ColorFormatter(logging.Formatter):
11
- """Custom formatter to add color-coded log levels."""
12
+ """Custom formatter to add color-coded log levels and thread information."""
12
13
 
13
14
  COLORS: ClassVar[dict[str, str]] = {
14
15
  "DEBUG": "\033[94m", # Blue
@@ -18,17 +19,34 @@ class ColorFormatter(logging.Formatter):
18
19
  "RESET": "\033[0m", # Reset
19
20
  }
20
21
 
21
- def __init__(self, fmt: str, datefmt: str, tz: pytz.BaseTzInfo) -> None:
22
+ THREAD_COLORS: ClassVar[list[str]] = [
23
+ "\033[95m", # Magenta
24
+ "\033[96m", # Cyan
25
+ "\033[93m", # Yellow
26
+ "\033[92m", # Green
27
+ "\033[94m", # Blue
28
+ "\033[90m", # Gray
29
+ "\033[37m", # White
30
+ "\033[33m", # Orange
31
+ "\033[35m", # Purple
32
+ ]
33
+
34
+ thread_colors: dict[str, str]
35
+
36
+ def __init__(self, fmt: str, datefmt: str, tz: pytz.BaseTzInfo, include_threads: bool = False) -> None:
22
37
  """
23
- Initialize the formatter with a specific timezone.
38
+ Initialize the formatter with a specific timezone and optional thread formatting.
24
39
 
25
40
  Args:
26
41
  fmt (str): The log message format.
27
42
  datefmt (str): The date format.
28
43
  tz (pytz.BaseTzInfo): The timezone for log timestamps.
44
+ include_threads (bool): Whether to include thread information in logs.
29
45
  """
30
46
  super().__init__(fmt, datefmt)
31
47
  self.timezone = tz
48
+ self.include_threads = include_threads
49
+ self.thread_colors = {} # Initialize as an empty dictionary
32
50
 
33
51
  def formatTime(self, record: logging.LogRecord, datefmt: Optional[str] = None) -> str:
34
52
  """
@@ -46,7 +64,7 @@ class ColorFormatter(logging.Formatter):
46
64
 
47
65
  def format(self, record: logging.LogRecord) -> str:
48
66
  """
49
- Format the log record with color-coded log levels.
67
+ Format the log record with color-coded log levels and optional thread information.
50
68
 
51
69
  Args:
52
70
  record (logging.LogRecord): The log record to format.
@@ -56,6 +74,24 @@ class ColorFormatter(logging.Formatter):
56
74
  """
57
75
  color = self.COLORS.get(record.levelname, self.COLORS["RESET"])
58
76
  record.levelname = f"{color}{record.levelname}{self.COLORS['RESET']}"
77
+
78
+ if self.include_threads:
79
+ thread_name = threading.current_thread().name
80
+ if thread_name == "MainThread":
81
+ # Skip adding thread info for the main thread
82
+ record.thread_info = ""
83
+ else:
84
+ # Map thread names to simplified format (Thread 0, Thread 1, etc.)
85
+ if thread_name not in self.thread_colors:
86
+ color_index = len(self.thread_colors) % len(self.THREAD_COLORS)
87
+ self.thread_colors[thread_name] = self.THREAD_COLORS[color_index]
88
+
89
+ thread_color = self.thread_colors[thread_name]
90
+ simplified_thread_name = thread_name.split("_")[-1]
91
+ record.thread_info = f"[{thread_color}Thread {simplified_thread_name}{self.COLORS['RESET']}] "
92
+ else:
93
+ record.thread_info = ""
94
+
59
95
  return super().format(record)
60
96
 
61
97
 
@@ -74,12 +110,16 @@ def setup_logger(level: str = "INFO", timezone: str = "Europe/Madrid") -> None:
74
110
  handler = logging.StreamHandler()
75
111
  handler.setLevel(logging._nameToLevel[level.upper()])
76
112
  tz = pytz.timezone(timezone)
77
- formatter = ColorFormatter(
78
- "[%(asctime)s] [%(levelname)s] %(message)s",
113
+
114
+ # Default formatter for the main thread
115
+ thread_formatter = ColorFormatter(
116
+ "[%(asctime)s] [%(levelname)s] %(thread_info)s%(message)s",
79
117
  datefmt="%Y-%m-%d %H:%M:%S",
80
118
  tz=tz,
119
+ include_threads=True,
81
120
  )
82
- handler.setFormatter(formatter)
121
+
122
+ handler.setFormatter(thread_formatter)
83
123
  root_logger.addHandler(handler)
84
124
 
85
125
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pysportbot
3
- Version: 0.0.4
3
+ Version: 0.0.6
4
4
  Summary: A python-based bot for automatic resasports slot booking
5
5
  Home-page: https://github.com/jbeirer/resasports-bot
6
6
  Author: Joshua Falco Beirer
@@ -15,7 +15,6 @@ Requires-Dist: beautifulsoup4 (>=4.12.3,<5.0.0)
15
15
  Requires-Dist: pandas (>=2.2.3,<3.0.0)
16
16
  Requires-Dist: pytz (>=2024.2,<2025.0)
17
17
  Requires-Dist: requests (>=2.32.3,<3.0.0)
18
- Requires-Dist: schedule (>=1.2.2,<2.0.0)
19
18
  Project-URL: Documentation, https://jbeirer.github.io/resasports-bot/
20
19
  Project-URL: Repository, https://github.com/jbeirer/resasports-bot
21
20
  Description-Content-Type: text/markdown
@@ -29,9 +28,9 @@ Description-Content-Type: text/markdown
29
28
  [![Build status](https://img.shields.io/github/actions/workflow/status/jbeirer/resasports-bot/main.yml?branch=main)](https://github.com/jbeirer/resasports-bot/actions/workflows/main.yml?query=branch%3Amain)
30
29
  [![codecov](https://codecov.io/gh/jbeirer/resasports-bot/graph/badge.svg?token=ZCJV384TXF)](https://codecov.io/gh/jbeirer/resasports-bot)
31
30
  [![Commit activity](https://img.shields.io/github/commit-activity/m/jbeirer/resasports-bot)](https://github.com/jbeirer/resasports-bot/commits/main/)
32
- [![License](https://img.shields.io/github/license/jbeirer/resasports-bot)](https://github.com/jbeirer/resasports-bot?tab=MIT-1-ov-file)
31
+ [![License](https://img.shields.io/github/license/jbeirer/resasports-bot)](https://github.com/jbeirer/resasports-bot/blob/main/LICENSE)
33
32
  [![Documentation](https://img.shields.io/badge/api-docs-blue)](https://jbeirer.github.io/resasports-bot/)
34
- [![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-2.1-4baaaa.svg)](CODE_OF_CONDUCT.md)
33
+ [![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-2.1-4baaaa.svg)](https://github.com/jbeirer/resasports-bot/blob/main/CODE_OF_CONDUCT.md)
35
34
 
36
35
  Welcome to pysportbot!
37
36
 
@@ -70,7 +69,7 @@ You can easily run `pysportbot` as a service to manage your bookings automatical
70
69
  ```bash
71
70
  python -m pysportbot.service --config config.json
72
71
  ```
73
- The service requires a `json` configuration file that specifies your user data and how you would like to book your classes. Currently, three types of configuration are supported:
72
+ The service requires a `json` configuration file that specifies your user data and how you would like to book your classes. Currently, two types of configuration are supported:
74
73
 
75
74
  ##### 1. Book an upcoming class now
76
75
 
@@ -81,13 +80,13 @@ Let's say you would like to book Yoga next Monday at 18:00:00, then your `config
81
80
  "email": "your-email",
82
81
  "password": "your-password",
83
82
  "center": "your-gym-name",
83
+ "booking_execution": "now",
84
+
84
85
  "classes": [
85
86
  {
86
87
  "activity": "Yoga",
87
88
  "class_day": "Monday",
88
89
  "class_time": "18:00:00",
89
- "booking_execution": "now",
90
- "weekly": false
91
90
  }
92
91
  ]
93
92
  }
@@ -101,32 +100,13 @@ Let's say you would like to book Yoga next Monday at 18:00:00, but the execution
101
100
  "email": "your-email",
102
101
  "password": "your-password",
103
102
  "center": "your-gym-name",
104
- "classes": [
105
- {
106
- "activity": "Yoga",
107
- "class_day": "Monday",
108
- "class_time": "18:00:00",
109
- "booking_execution": "Friday 07:30:00",
110
- "weekly": false
111
- }
112
- ]
113
- }
114
- ```
115
- ##### 3. Schedule weekly booking at specific execution day and time
116
- Let's say you would like to book Yoga every Monday at 18:00:00 and the booking execution should be every Friday at 07:30:00 then your `config.json` would look like:
103
+ "booking_execution": "Friday 07:30:00",
117
104
 
118
- ```json
119
- {
120
- "email": "your-email",
121
- "password": "your-password",
122
- "center": "your-gym-name",
123
105
  "classes": [
124
106
  {
125
107
  "activity": "Yoga",
126
108
  "class_day": "Monday",
127
109
  "class_time": "18:00:00",
128
- "booking_execution": "Friday 07:30:00",
129
- "weekly": true
130
110
  }
131
111
  ]
132
112
  }
@@ -1,22 +1,22 @@
1
1
  pysportbot/__init__.py,sha256=4z5KKwrnwj_uiZYTUNgZIw_7s8ZT9C-Olz8_hzhPutw,4919
2
- pysportbot/activities.py,sha256=dyZDme6CidsBeGqB9lIHFYmlRKTmDLvTJGPoize17B0,4247
2
+ pysportbot/activities.py,sha256=y2-CCdMoPTnKZleF_icYbngbnfpmHL5Cyadv1UuiCpE,4248
3
3
  pysportbot/authenticator.py,sha256=xEU9O6W6Yp9DCueoUD0ASMkm17yvdUZqTvV4h6WlSnI,5012
4
4
  pysportbot/bookings.py,sha256=vJ0kw74qyirZlbQ7M9XqlKtRoGzuHR0_t6-zUdgldkI,3123
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
- pysportbot/service/__main__.py,sha256=egQ68-BFzvshF8d5DbVKI6ooL46pPD12v_T64CAbza0,1320
9
- pysportbot/service/booking.py,sha256=CNeMhYFAFi1UXIwAqtx8fKuO5jUsmnuf7VaSmkwIX1c,5425
8
+ pysportbot/service/__main__.py,sha256=PSaqYZ8wWlmFT0_taeOA2NLZfm9fIOy6MBhrv_IPSP4,1319
9
+ pysportbot/service/booking.py,sha256=bjPb9BLF7XOhFnr1ZArxUHNO6-10yM1z-3S5-ZFpXjQ,5713
10
10
  pysportbot/service/config_loader.py,sha256=elNNwcC7kwc0lmRqj95hAA50qSmLelWv5G2E62tDxP0,185
11
- pysportbot/service/config_validator.py,sha256=lGsSP7ubD816CQb0fZNYtdHWIXsFsZWtRLIjAMjSJRw,2167
11
+ pysportbot/service/config_validator.py,sha256=ce7p-LgPtvWnTYvpyUEE60627DHHzx5mFhEI9hDNB-4,2621
12
12
  pysportbot/service/scheduling.py,sha256=bwqbiQQ9y4ss4UXZkWuOVCgCa9XlZt1n4TT_8z9bD7M,1973
13
- pysportbot/service/service.py,sha256=JMq71x6KpaS16HRIfNTL8aLiiCT85PTmCLBIcebcK1U,1294
13
+ pysportbot/service/service.py,sha256=_LR7rJ4GAgp4saSFREiSJCm4GzzEor6ZYkIkuAq7PLM,1866
14
14
  pysportbot/session.py,sha256=pTQrz3bGzLYBtzVOgKv04l4UXDSgtA3Infn368bjg5I,1529
15
15
  pysportbot/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
16
  pysportbot/utils/errors.py,sha256=sUSs04yr_OmZeTywi1fzPsjVjkLd5iTpXlzlQqNKmgQ,5162
17
- pysportbot/utils/logger.py,sha256=_31g_qY4_aCMSXKo-63e10CwAs7ug7vSuq6TMNoRmek,3534
17
+ pysportbot/utils/logger.py,sha256=38rB0M6KPlelXPcgXPSEhXB36-_ZTERLJAQ8DRYIOTM,5189
18
18
  pysportbot/utils/time.py,sha256=VZSW8AxFIoFD5ZSmLUPcwawp6PmpkcxNjP3Db-Hl_fw,1244
19
- pysportbot-0.0.4.dist-info/LICENSE,sha256=6ov3DypdEVYpp2pn_B1MniKWO5C9iDA4O6PGcbork6c,1077
20
- pysportbot-0.0.4.dist-info/METADATA,sha256=y6MCUbZBDuw0yXJwhLFSbAfIQvmEW22y5sheiX01tPQ,5533
21
- pysportbot-0.0.4.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
22
- pysportbot-0.0.4.dist-info/RECORD,,
19
+ pysportbot-0.0.6.dist-info/LICENSE,sha256=6ov3DypdEVYpp2pn_B1MniKWO5C9iDA4O6PGcbork6c,1077
20
+ pysportbot-0.0.6.dist-info/METADATA,sha256=lECBuUu6DaV2CiVXWwHpmz1b--lL7DhxwpOsrXTITKU,4904
21
+ pysportbot-0.0.6.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
22
+ pysportbot-0.0.6.dist-info/RECORD,,