Added a service that can get all current tournaments list
This commit is contained in:
parent
e6023e0687
commit
3d0bd47079
26 changed files with 4305 additions and 204 deletions
|
@ -3,7 +3,7 @@ import logging
|
|||
|
||||
import config
|
||||
from gestion_sports_services import GestionSportsServices
|
||||
from models import Action, BookingFilter, Club, Court, User
|
||||
from models import Action, BookingFilter, Club, Court, Tournament, User
|
||||
|
||||
LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
@ -51,7 +51,18 @@ async def cancel_booking_id(club: Club, user: User, booking_id: int) -> None:
|
|||
await service.cancel_booking_id(user, club, booking_id)
|
||||
|
||||
|
||||
def main() -> tuple[Court, User] | None:
|
||||
async def get_tournaments(club: Club, user: User) -> list[Tournament]:
|
||||
"""
|
||||
Cancel a booking that matches the booking id
|
||||
|
||||
:param club: the club in which the booking was made
|
||||
:param user: the user who made the booking
|
||||
"""
|
||||
service = GestionSportsServices()
|
||||
return await service.get_all_tournaments(user, club)
|
||||
|
||||
|
||||
def main() -> tuple[Court, User] | list[Tournament] | None:
|
||||
"""
|
||||
Main function used to book a court
|
||||
|
||||
|
@ -80,3 +91,8 @@ def main() -> tuple[Court, User] | None:
|
|||
club = config.get_club()
|
||||
booking_filter = config.get_booking_filter()
|
||||
asyncio.run(cancel_booking(club, user, booking_filter))
|
||||
|
||||
elif action == Action.TOURNAMENTS:
|
||||
user = config.get_user()
|
||||
club = config.get_club()
|
||||
return asyncio.run(get_tournaments(club, user))
|
||||
|
|
|
@ -1,42 +0,0 @@
|
|||
import logging
|
||||
|
||||
from aiohttp import ClientSession
|
||||
from connectors import Connector
|
||||
from models import BookingFilter, Club, User
|
||||
|
||||
LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class BookingService:
|
||||
def __init__(self, club: Club, connector: Connector):
|
||||
LOGGER.info("Initializing booking service at for club", club.name)
|
||||
self.club = club
|
||||
self.connector = connector
|
||||
self.session: ClientSession | None = None
|
||||
|
||||
async def __aenter__(self):
|
||||
self.session = ClientSession()
|
||||
return self
|
||||
|
||||
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
||||
await self.session.close()
|
||||
|
||||
async def book(self, user: User, booking_filter: BookingFilter) -> int | None:
|
||||
"""
|
||||
Book a court matching the booking filters for a user.
|
||||
The steps to perform a booking are to go to the landing page, to log in, wait
|
||||
and for the time when booking is open and then actually book the court
|
||||
|
||||
:param user: the user that wants to book a court
|
||||
:param booking_filter: the booking criteria
|
||||
:return: the court number if the booking is successful, None otherwise
|
||||
"""
|
||||
if self.connector is None:
|
||||
LOGGER.error("No connection to Gestion Sports is available")
|
||||
return None
|
||||
|
||||
if user is None or booking_filter is None:
|
||||
LOGGER.error("Not enough information available to book a court")
|
||||
return None
|
||||
|
||||
self.connector.book(user, booking_filter)
|
|
@ -143,4 +143,4 @@ def get_action() -> Action:
|
|||
Get the action to perform from an environment variable
|
||||
:return: the action to perform
|
||||
"""
|
||||
return Action(os.environ.get("ACTION"))
|
||||
return Action(os.environ.get("ACTION").lower())
|
||||
|
|
|
@ -49,6 +49,11 @@ class GestionSportsConnector:
|
|||
self.club.booking_platform.urls.get(name).path,
|
||||
)
|
||||
|
||||
def _get_url_parameter(self, name: str) -> str:
|
||||
self._check_url_path_exists(name)
|
||||
|
||||
return self.club.booking_platform.urls.get(name).parameter
|
||||
|
||||
def _get_payload_template(self, name: str) -> Path:
|
||||
"""
|
||||
Get the path to the template file for the service with the given name
|
||||
|
@ -178,6 +183,18 @@ class GestionSportsConnector:
|
|||
"""
|
||||
return self._get_payload_template("cancellation")
|
||||
|
||||
@property
|
||||
def tournaments_sessions_url(self) -> str:
|
||||
return self._get_url_path("tournament-sessions")
|
||||
|
||||
@property
|
||||
def tournaments_sessions_template(self) -> Path:
|
||||
return self._get_payload_template("tournament-sessions")
|
||||
|
||||
@property
|
||||
def tournaments_list_url(self) -> str:
|
||||
return self._get_url_path("tournaments-list")
|
||||
|
||||
@property
|
||||
def available_sports(self) -> dict[str, Sport]:
|
||||
"""
|
||||
|
@ -209,7 +226,7 @@ class GestionSportsConnector:
|
|||
payload = PayloadBuilder.build(self.login_template, user=user, club=self.club)
|
||||
|
||||
async with session.post(
|
||||
self.login_url, data=payload, headers=POST_HEADERS
|
||||
self.login_url, data=payload, headers=POST_HEADERS, allow_redirects=False
|
||||
) as response:
|
||||
resp_text = await response.text()
|
||||
LOGGER.debug("Connexion request response:\n%s", resp_text)
|
||||
|
@ -421,3 +438,23 @@ class GestionSportsConnector:
|
|||
for booking in bookings:
|
||||
if booking.matches(booking_filter):
|
||||
return await self.cancel_booking_id(session, booking.id)
|
||||
|
||||
async def send_tournaments_sessions_request(
|
||||
self, session: ClientSession
|
||||
) -> ClientResponse:
|
||||
payload = self.tournaments_sessions_template.read_text()
|
||||
|
||||
async with session.post(
|
||||
self.tournaments_sessions_url, data=payload, headers=POST_HEADERS
|
||||
) as response:
|
||||
LOGGER.debug("tournament sessions: \n%s", await response.text())
|
||||
return response
|
||||
|
||||
async def send_tournaments_request(
|
||||
self, session: ClientSession, tournement_session_id: str
|
||||
) -> ClientResponse:
|
||||
final_url = self.tournaments_list_url + tournement_session_id
|
||||
LOGGER.debug("Getting tournaments list at %s", final_url)
|
||||
async with session.get(final_url) as response:
|
||||
LOGGER.debug("tournaments: %s\n", await response.text())
|
||||
return response
|
|
@ -1,10 +1,12 @@
|
|||
import json
|
||||
import logging
|
||||
import time
|
||||
|
||||
import pendulum
|
||||
from aiohttp import ClientSession
|
||||
from connectors import GestionSportsConnector
|
||||
from models import BookingFilter, BookingOpening, Club, Court, User
|
||||
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__)
|
||||
|
@ -125,3 +127,62 @@ class GestionSportsServices:
|
|||
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
|
||||
|
|
|
@ -70,6 +70,7 @@ class Sport(BaseModel):
|
|||
class Url(BaseModel):
|
||||
name: str
|
||||
path: str
|
||||
parameter: Optional[str] = Field(default=None)
|
||||
payload_template: Optional[str] = Field(default=None, alias="payloadTemplate")
|
||||
|
||||
|
||||
|
@ -210,3 +211,13 @@ class Booking(BaseModel):
|
|||
class Action(Enum):
|
||||
BOOK = "book"
|
||||
CANCEL = "cancel"
|
||||
TOURNAMENTS = "tournaments"
|
||||
|
||||
|
||||
class Tournament(BaseModel):
|
||||
name: str
|
||||
price: str
|
||||
start_date: DateTime
|
||||
end_date: DateTime
|
||||
gender: str
|
||||
places_left: str | int
|
||||
|
|
|
@ -1,12 +1,16 @@
|
|||
{
|
||||
"Connection": "keep-alive",
|
||||
"Accept": "application/json, text/javascript, */*; q=0.01",
|
||||
"Accept-Language": "en-US,en;q=0.5",
|
||||
"Accept-Encoding": "gzip, deflate, br",
|
||||
"DNT": "1",
|
||||
"Cache-Control": "no-cache",
|
||||
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
|
||||
"Accept": "application/json, text/javascript, */*; q=0.01",
|
||||
"X-Requested-With": "XMLHttpRequest",
|
||||
"Connection": "keep-alive",
|
||||
"DNT": "1",
|
||||
"Origin": "https://toulousepadelclub.gestion-sports.com",
|
||||
"Pragma": "no-cache",
|
||||
"Sec-Fetch-Dest": "empty",
|
||||
"Sec-Fetch-Mode": "cors",
|
||||
"Sec-Fetch-Site": "same-origin"
|
||||
"Sec-Fetch-Site": "same-origin",
|
||||
"User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:123.0) Gecko/20100101 Firefox/123.0",
|
||||
"X-Requested-With": "XMLHttpRequest"
|
||||
}
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
ajax=loadSessionForSpecDay&date=all
|
|
@ -17,3 +17,8 @@ platforms:
|
|||
- name: cancellation
|
||||
path: /membre/mesresas.html
|
||||
payloadTemplate: gestion-sports/booking-cancellation-payload.txt
|
||||
- name: tournament-sessions
|
||||
path: /membre/index.php
|
||||
payloadTemplate: gestion-sports/tournament-sessions-payload.txt
|
||||
- name: tournaments-list
|
||||
path: /membre/events/event.html?event=
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue