188 lines
7.2 KiB
Python
188 lines
7.2 KiB
Python
import json
|
|
import logging
|
|
import time
|
|
|
|
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 pendulum import DateTime
|
|
|
|
LOGGER = logging.getLogger(__name__)
|
|
|
|
|
|
class GestionSportsServices:
|
|
@staticmethod
|
|
async def book(
|
|
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
|
|
a booking.
|
|
The gestion-sports backend does not allow several bookings at the same time
|
|
so there is no need to make each request one after the other
|
|
|
|
:param club: the club in which the booking will be made
|
|
:param user: the user that wants to book the court
|
|
:param booking_filter: the booking conditions to meet
|
|
:return: the booked court, or None if no court was booked
|
|
"""
|
|
connector = GestionSportsConnector(club)
|
|
LOGGER.info(
|
|
"Booking any available court from GestionSports API at %s",
|
|
connector.booking_url,
|
|
)
|
|
|
|
async with ClientSession() as session:
|
|
# use asyncio to request a booking on every court
|
|
# the gestion-sports backend is able to book only one court for a user
|
|
await connector.land(session)
|
|
await connector.login(session, user)
|
|
|
|
booking_opening = club.booking_platform.booking_opening
|
|
GestionSportsServices.wait_until_booking_time(
|
|
booking_filter, booking_opening
|
|
)
|
|
|
|
bookings = await connector.book_any_court(session, booking_filter)
|
|
|
|
LOGGER.debug("Booking results:\n'%s'", bookings)
|
|
return connector.get_booked_court(bookings, booking_filter.sport_name)
|
|
|
|
@staticmethod
|
|
async def has_user_available_slots(user: User, club: Club) -> bool:
|
|
connector = GestionSportsConnector(club)
|
|
async with ClientSession() as session:
|
|
await connector.land(session)
|
|
await connector.login(session, user)
|
|
bookings = await connector.get_ongoing_bookings(session)
|
|
|
|
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
|
|
) -> None:
|
|
"""
|
|
Wait until the booking is open.
|
|
The booking filter contains the date and time of the booking.
|
|
The club has the information about when the booking is open for that date.
|
|
|
|
:param booking_opening:
|
|
:param booking_filter: the booking information
|
|
"""
|
|
LOGGER.info("Waiting for booking time")
|
|
booking_datetime = GestionSportsServices.build_booking_datetime(
|
|
booking_filter, booking_opening
|
|
)
|
|
now = pendulum.now()
|
|
duration_until_booking = booking_datetime - now
|
|
LOGGER.debug(f"Current time: {now}, Datetime to book: {booking_datetime}")
|
|
LOGGER.debug(
|
|
f"Time to wait before booking: {duration_until_booking.hours:0>2}"
|
|
f":{duration_until_booking.minutes:0>2}"
|
|
f":{duration_until_booking.seconds:0>2}"
|
|
)
|
|
|
|
while now < booking_datetime:
|
|
time.sleep(1)
|
|
now = pendulum.now()
|
|
LOGGER.info("It's booking time!")
|
|
|
|
@staticmethod
|
|
def build_booking_datetime(
|
|
booking_filter: BookingFilter, booking_opening: BookingOpening
|
|
) -> DateTime:
|
|
"""
|
|
Build the date and time when the booking is open for a given match date.
|
|
The booking filter contains the date and time of the booking.
|
|
The club has the information about when the booking is open for that date.
|
|
|
|
:param booking_opening:the booking opening conditions
|
|
:param booking_filter: the booking information
|
|
:return: the date and time when the booking is open
|
|
"""
|
|
date_to_book = booking_filter.date
|
|
booking_date = date_to_book.subtract(days=booking_opening.days_before)
|
|
|
|
opening_time = pendulum.parse(booking_opening.opening_time)
|
|
booking_hour = opening_time.hour
|
|
booking_minute = opening_time.minute
|
|
|
|
return booking_date.at(booking_hour, booking_minute)
|
|
|
|
@staticmethod
|
|
async def get_all_tournaments(user: User, club: Club) -> list[Tournament]:
|
|
connector = GestionSportsConnector(club)
|
|
async with ClientSession() as session:
|
|
await connector.land(session)
|
|
await connector.login(session, user)
|
|
|
|
session_html = await connector.send_tournaments_sessions_request(session)
|
|
tournaments_id = GestionSportsServices.retrieve_tournament_session(
|
|
await session_html.text()
|
|
)
|
|
|
|
tournaments = await connector.send_tournaments_request(
|
|
session, tournaments_id
|
|
)
|
|
return GestionSportsServices.retrieve_tournaments(await tournaments.text())
|
|
|
|
@staticmethod
|
|
def retrieve_tournament_session(sessions: str) -> str:
|
|
session_object = json.loads(sessions).get("Inscription tournois:school-outline")
|
|
return list(session_object.keys())[0]
|
|
|
|
@staticmethod
|
|
def retrieve_tournaments(html: str) -> list[Tournament]:
|
|
soup = BeautifulSoup(html, "html.parser")
|
|
tournaments = []
|
|
|
|
cards = soup.find_all("div", {"class": "card-body"})
|
|
for card in cards:
|
|
title = card.find("h5")
|
|
price = title.find("span").get_text().strip()
|
|
name = title.get_text().strip().removesuffix(price).strip()
|
|
elements = card.find("div", {"class": "row"}).find_all("li")
|
|
date = elements[0].get_text().strip()
|
|
start_time, end_time = (
|
|
elements[2].get_text().strip().replace("h", ":").split(" - ")
|
|
)
|
|
start_datetime = pendulum.from_format(
|
|
f"{date} {start_time}", "DD/MM/YYYY HH:mm"
|
|
)
|
|
end_datetime = pendulum.from_format(
|
|
f"{date} {end_time}", "DD/MM/YYYY HH:mm"
|
|
)
|
|
gender = elements[1].get_text().strip()
|
|
places_left = (
|
|
card.find("span", {"class": "nb_place_libre"}).get_text().strip()
|
|
)
|
|
tournament = Tournament(
|
|
name=name,
|
|
price=price,
|
|
start_date=start_datetime,
|
|
end_date=end_datetime,
|
|
gender=gender,
|
|
places_left=places_left,
|
|
)
|
|
tournaments.append(tournament)
|
|
|
|
return tournaments
|