import logging
from contextlib import suppress
from datetime import datetime
from typing_extensions import Self
from ._util import _regex_it
from .api import _APIClient
from .base import ClubObject
from .config import get_from_cache, set_in_cache
from .constants import _TMIO
from .errors import TMIOException
from .player import Player
_log = logging.getLogger(__name__)
__all__ = (
"ClubMember",
"ClubActivity",
"Club",
)
[docs]class ClubMember(ClubObject):
"""
.. versionadded :: 0.5
Represents a member of a club.
Parameters
----------
name : str
The name of the player.
tag : str | None
The club tab of the player.
player_id : str
The ID of the player.
join_time : datetime
When the player joined the club.
role : str
The role of the player inside the club
vip : bool
Whether the player is a VIP.
"""
def __init__(
self,
name: str,
tag: str | None,
player_id: str,
join_time: datetime,
role: str,
vip: bool,
):
self.name = name
self.tag = tag
self.player_id = player_id
self.join_time = join_time
self.role = role
self.vip = vip
@classmethod
def _from_dict(cls: Self, raw_data: dict) -> Self:
_log.debug("Creating a ClubMember class from the given dictionary")
name = _regex_it(raw_data["player"].get("name"))
tag = _regex_it(raw_data["player"].get("tag", None))
player_id = raw_data["player"].get("id")
join_time = datetime.utcfromtimestamp(raw_data.get("joinTime"))
role = raw_data.get("role")
vip = raw_data.get("vip")
args = [
name,
tag,
player_id,
join_time,
role,
vip,
]
return cls(*args)
[docs] async def player(self) -> Player:
"""
.. versionadded :: 0.5
Gets the player who is this specific club member.
(IDK wtf to put here xD)
Returns
-------
:class:`Player`
The player who is the club member.
Raises
------
:class:`TMIOException`
If there is a problem with the TMIO API.
"""
return await Player.get_player(self.player_id)
[docs]class ClubActivity(ClubObject):
"""
.. versionadded :: 0.5
Represents an activity of the club.
Parameters
----------
name : str
The name of the activity.
type : str
What type of activity it is.
activity_id : int
The activity's ID.
target_activity_id : int
The activity's target ID.
position : int
The position of the activity within the club.
public : bool
Whether the activity is public or club-member only.
media : str
The media of the activity.
password : bool
Whether the activity is password-protected.
"""
def __init__(
self,
name: str,
type: str,
activity_id: int,
target_activity_id: int,
position: int,
public: bool,
media: str,
password: bool,
):
self.name = name
self.type = type
self.activity_id = activity_id
self.target_activity_id = target_activity_id
self.position = position
self.public = public
self.media = media
self.password = password
@classmethod
def _from_dict(cls: Self, raw_data: dict) -> Self:
_log.debug("Creating a ClubActivity class from the given dictionary")
name = _regex_it(raw_data.get("name"))
type = raw_data.get("type")
activity_id = raw_data.get("activityid")
target_activity_id = raw_data.get("targetactivityid", 0)
position = raw_data.get("position")
public = raw_data.get("public")
media = raw_data.get("media")
password = raw_data.get("password")
args = [
name,
type,
activity_id,
target_activity_id,
position,
public,
media,
password,
]
return cls(*args)
[docs]class Club(ClubObject):
"""
Represents a Club in Trackmania 2020.
Parameters
----------
background : str
The club's background url.
created_at : datetime
The club's created date.
decal : str
The club decal URL
description : str
The club's description
featured : bool
Whether the club is featured or not.
club_id : int
The club's ID.
logo : str
The club's logo URL.
member_count : int
The club's member count.
name : str
The club's name.
popularity : int
The club popularity level
state : str
The club's state.
Is either Private or Public.
tag : str
The club tag
"""
def __init__(
self,
background: str,
created_at: datetime,
decal: str,
description: str,
featured: bool,
club_id: int,
logo: str,
member_count: int,
name: str,
popularity: int,
state: str,
tag: str | None,
creator_id: str,
):
self.background = background
self.created_at = created_at
self.decal = decal
self.description = description
self.featured = featured
self.club_id = club_id
self.logo = logo
self.member_count = member_count
self.name = name
self.popularity = popularity
self.state = state
self.tag = tag
self.creator_id = creator_id
@classmethod
def _from_dict(cls: Self, raw_data: dict) -> Self:
_log.debug("Creating a Club class from the given dictionary.")
background = raw_data.get("backgroundUrl", "")
created_at = datetime.utcfromtimestamp(raw_data.get("creationTimestamp", 0))
decal = raw_data.get("decalUrl")
description = raw_data.get("description")
featured = raw_data.get("featured")
club_id = raw_data.get("id", 0)
logo = raw_data.get("logoUrl")
member_count = raw_data.get("membercount", 0)
name = _regex_it(raw_data.get("name"))
popularity = raw_data.get("popularityLevel")
state = raw_data.get("state")
tag = _regex_it(raw_data.get("tag", None))
creator_id = raw_data["creatorplayer"]["id"]
args = [
background,
created_at,
decal,
description,
featured,
club_id,
logo,
member_count,
name,
popularity,
state,
tag,
creator_id,
]
return cls(*args)
[docs] @classmethod
async def get_club(cls: Self, club_id: int) -> Self | None:
"""
.. versionadded :: 0.5
Gets a club based on its club id.
Returns None if the `club_id` is 0
Parameters
----------
club_id : int
The club's id.
Returns
-------
:class:`Club` | None
Returns a `Club` class if a club exists with the given class id. Otherwise returns None.
Raises
------
:class:`TMIOException`
If the club doesn't exist or if an unexpected error occurs on TMIO's side.
"""
if club_id == 0:
return None
club_data = get_from_cache(f"club:{club_id}")
if club_data is not None:
return cls._from_dict(club_data)
api_client = _APIClient()
club_data = await api_client.get(_TMIO.build([_TMIO.TABS.CLUB, club_id]))
await api_client.close()
with suppress(KeyError, TypeError):
raise TMIOException(club_data["error"])
return cls._from_dict(club_data)
[docs] @classmethod
async def list_clubs(cls: Self, page: int = 0) -> list[Self]:
"""
.. versionadded :: 0.5
Lists all the popular clubs.
Parameters
----------
page : int, optional
The page bumber, by default 0
Returns
-------
:class:`list[Club]`
The list of clubs on that specific page.
"""
clubs = []
club_data = get_from_cache(f"clubs:{page}")
if club_data is not None:
for club in club_data.get("clubs", []):
clubs.append(cls._from_dict(club))
return clubs
api_client = _APIClient()
club_data = await api_client.get(
_TMIO.build([_TMIO.TABS.CLUBS, page]) + "?sort=popularity"
)
await api_client.close()
set_in_cache(f"clubs:{page}", club_data, ex=43200)
for club in club_data.get("clubs", []):
clubs.append(cls._from_dict(club))
return clubs
[docs] async def get_activities(self: Self, page: int = 0) -> list[ClubActivity]:
"""
.. versionadded :: 0.5
Gets the activities of the club.
Parameters
----------
page : int
The page number, by default 0
Returns
-------
:class:`list[ClubActivity]`
The list of activities of the club.
"""
club_activities = []
all_activities = get_from_cache(f"club_activities:{self.club_id}:{page}")
if all_activities is not None:
for activity in all_activities:
club_activities.append(ClubActivity._from_dict(activity))
return club_activities
api_client = _APIClient()
all_activities = await api_client.get(
_TMIO.build([_TMIO.TABS.CLUB, self.club_id, _TMIO.TABS.ACTIVITIES, page])
)
await api_client.close()
with suppress(KeyError, TypeError):
raise TMIOException(all_activities["error"])
for activity in all_activities.get("activities", []):
club_activities.append(ClubActivity._from_dict(activity))
return club_activities
[docs] async def get_members(self: Self, page: int = 0) -> list[ClubMember]:
"""
.. versionadded :: 0.5
Gets the members of the club.
Parameters
----------
page : int, optional
The page number, by default 0
Returns
-------
:class:`list[Player]`
The list of members of the club.
"""
player_list = []
club_members = get_from_cache(f"club_members:{self.club_id}:{page}")
if club_members is not None:
for member in club_members.get("members", []):
player_list.append(ClubMember._from_dict(member))
return player_list
api_client = _APIClient()
club_members = await api_client.get(
_TMIO.build([_TMIO.TABS.CLUB, self.club_id, _TMIO.TABS.MEMBERS, page])
)
await api_client.close()
for member in club_members.get("members", []):
player_list.append(ClubMember._from_dict(member))
return player_list
[docs] async def creator(self) -> Player:
"""
.. versionadded :: 0.5
Gets the creator of the club.
Returns
-------
:class:`Player`
The creator of the club.
"""
return await Player.get_player(self.creator_id)