All methods are in the right class

This commit is contained in:
Stanislas Jouffroy 2024-03-23 21:54:17 +01:00
parent 0d541e82a5
commit 7f59443b64
12 changed files with 585 additions and 1729 deletions

View file

@ -6,16 +6,24 @@ import pendulum
from aiohttp import ClientSession
from bs4 import BeautifulSoup
from gestion_sport_connector import GestionSportsConnector
from models import BookingFilter, BookingOpening, Club, Court, Tournament, User
from models import (
Booking,
BookingFilter,
BookingOpening,
Club,
Court,
Sport,
Tournament,
User,
)
from pendulum import DateTime
LOGGER = logging.getLogger(__name__)
class GestionSportsServices:
@staticmethod
async def book(
club: Club, user: User, booking_filter: BookingFilter
self, club: Club, user: User, booking_filter: BookingFilter
) -> Court | None:
"""
Perform a request for each court at the same time to increase the chances to get
@ -45,37 +53,76 @@ class GestionSportsServices:
booking_filter, booking_opening
)
bookings = await connector.book_any_court(session, booking_filter)
bookings = await connector.send_all_booking_requests(
session, booking_filter
)
LOGGER.debug("Booking results:\n'%s'", bookings)
return connector.get_booked_court(bookings, booking_filter.sport_name)
sport = club.sports.get(booking_filter.sport_name)
return self.get_booked_court(bookings, sport)
def get_booked_court(
self, bookings: list[tuple[int, dict]], sport: Sport
) -> Court | None:
"""
Parse the booking list and return the court that was booked
:param bookings: a list of bookings
:param sport: the sport of the club and all the courts it has
:return: the id of the booked court if any, None otherwise
"""
for court_id, response in bookings:
if self.is_booking_response_status_ok(response):
LOGGER.debug("Court %d is booked", court_id)
court_booked = self.find_court(court_id, sport)
LOGGER.info("Court '%s' is booked", court_booked.name)
return court_booked
LOGGER.debug("No booked court found")
return None
@staticmethod
async def has_user_available_slots(user: User, club: Club) -> bool:
def is_booking_response_status_ok(response: dict) -> bool:
"""
Check if the booking response is OK
:param response: the response as a string
:return: true if the status is ok, false otherwise
"""
return response["status"] == "ok"
@staticmethod
def find_court(court_id: int, sport: Sport) -> Court:
"""
Get all the court information based on the court id and the sport name
:param court_id: the court id
:param sport: the sport
:return: the court that has the given id and sport name
"""
for court in sport.courts:
if court.id == court_id:
return court
async def has_user_available_slots(self, user: User, club: Club) -> bool:
"""
Checks if a user has available booking slot.
If a user already has an ongoing booking, it is considered as no slot is
available
:param user: The user to check the booking availability
:param club: The club of the user
:return: True if the user has no ongoing booking, False otherwise
"""
connector = GestionSportsConnector(club)
async with ClientSession() as session:
await connector.land(session)
await connector.login(session, user)
bookings = await connector.get_ongoing_bookings(session)
bookings = await self.get_ongoing_bookings(session, connector)
return bool(bookings)
@staticmethod
async def cancel_booking(user: User, club: Club, booking_filter: BookingFilter):
connector = GestionSportsConnector(club)
async with ClientSession() as session:
await connector.land(session)
await connector.login(session, user)
await connector.cancel_booking(session, booking_filter)
@staticmethod
async def cancel_booking_id(user: User, club: Club, booking_id: int):
connector = GestionSportsConnector(club)
async with ClientSession() as session:
await connector.land(session)
await connector.login(session, user)
await connector.cancel_booking_id(session, booking_id)
@staticmethod
def wait_until_booking_time(
booking_filter: BookingFilter, booking_opening: BookingOpening
@ -128,6 +175,71 @@ class GestionSportsServices:
return booking_date.at(booking_hour, booking_minute)
async def cancel_booking(
self, user: User, club: Club, booking_filter: BookingFilter
):
connector = GestionSportsConnector(club)
async with ClientSession() as session:
await connector.land(session)
await connector.login(session, user)
bookings = await self.get_ongoing_bookings(session, connector)
for booking in bookings:
if booking.matches(booking_filter):
return await self.cancel_booking_id(session, connector, booking.id)
async def get_ongoing_bookings(
self, session: ClientSession, connector: GestionSportsConnector
) -> list[Booking]:
"""
Get the list of all ongoing bookings of a user.
The steps to perform this are to get the user's bookings page and get a hidden
property in the HTML to get a hash that will be used in the payload of the
POST request (sic) to get the user's bookings.
Gestion sports is really a mess!!
:param session: the client session shared among all connections
:param connector: the connector used to send the requests
:return: the list of all ongoing bookings of a user
"""
response = await connector.send_hash_request(session)
hash_value = self.get_hash_input(await response.text())
LOGGER.debug(f"Hash value: {hash_value}")
response = await connector.send_user_bookings_request(session, hash_value)
return [Booking(**booking) for booking in json.loads(await response.text())]
@staticmethod
def get_hash_input(html_doc: str) -> str:
"""
There is a secret hash generated by Gestion sports that is reused when trying to get
users bookings. This hash is stored in a hidden input with name "mesresas-hash"
:param html_doc: the html document when getting the page mes-resas.html
:return: the value of the hash in the page
"""
soup = BeautifulSoup(html_doc, "html.parser")
inputs = soup.find_all("input")
for input_tag in inputs:
if input_tag.get("name") == "mesresas-hash":
return input_tag.get("value").strip()
async def cancel_booking_id(
self, session: ClientSession, connector: GestionSportsConnector, booking_id: int
) -> None:
"""
Send the HTTP request to cancel the booking
:param session: the client session shared among all connections
:param connector: the connector used to send the requests
:param booking_id: the id of the booking to cancel
:return: the response from the client
"""
response = await connector.send_hash_request(session)
hash_value = self.get_hash_input(await response.text())
LOGGER.debug(f"Hash value: {hash_value}")
await connector.send_cancellation_request(session, booking_id, hash_value)
@staticmethod
async def get_all_tournaments(user: User, club: Club) -> list[Tournament]:
connector = GestionSportsConnector(club)
@ -135,7 +247,7 @@ class GestionSportsServices:
await connector.land(session)
await connector.login(session, user)
session_html = await connector.send_tournaments_sessions_request(session)
session_html = await connector.send_session_request(session)
tournaments_id = GestionSportsServices.retrieve_tournament_session(
await session_html.text()
)