Source code for trackmania.tmx

import logging
from datetime import datetime

from typing_extensions import Self

from trackmania.api import _APIClient

from ._util import _frmt_str_to_datetime, _regex_it
from .api import _APIClient
from .base import TMXObject
from .config import get_from_cache, set_in_cache
from .constants import _TMX
from .errors import InvalidTMXCode

_log = logging.getLogger(__name__)

__all__ = (
    "TMXMapTimes",
    "GbxFileMetadata",
    "TMXTags",
    "ReplayWRData",
    "TMXMetadata",
    "TMXMap",
)


async def _get_map(tmx_id: int) -> dict:
    _log.info(f"Getting map data for tmx id {tmx_id}")

    tmx_map = get_from_cache(f"tmxmap:{tmx_id}")
    if tmx_map is not None:
        return tmx_map

    api_client = _APIClient()
    map_data = await api_client.get(
        _TMX.build([_TMX.TABS.MAPS, _TMX.TABS.GET_MAP_INFO, _TMX.TABS.ID, tmx_id])
    )
    await api_client.close()

    if not isinstance(map_data, dict):
        raise InvalidTMXCode("Invalid TMX code")

    set_in_cache(f"tmxmap:{tmx_id}", map_data)

    return map_data


async def _get_random_map() -> dict:
    _log.info(f"Getting a random map from trackmania.exchange")

    api_client = _APIClient()
    map_data = await api_client.get(
        "https://trackmania.exchange/mapsearch2/search?api=on&random=1&format=json"
    )  # Not using _TMX.build here because it doesn't need _TMX.api in the url
    await api_client.close()

    return map_data["results"][0]


[docs]class TMXMapTimes(TMXObject): """ .. versionadded :: 0.3.3 Represents when the map was uploaded and/or updated on the `trackmania.exchange` website Parameters ---------- uploaded : :class:`datetime` When the map was uploaded to TMX updated : :class:`datetime` When the map was last updated on TMX """ def __init__(self, uploaded: datetime, updated: datetime): self.uploaded = uploaded self.updated = updated @classmethod def _from_dict(cls: Self, raw: dict) -> Self: _log.debug("Creating a TMXMapTimes from given dictionary") uploaded_raw, updated_raw = raw.get("UploadedAt"), raw.get("UpdatedAt") # 2022-03-15T18:18:50.007 to datetime uploaded = _frmt_str_to_datetime(uploaded_raw) updated = _frmt_str_to_datetime(updated_raw) return cls(uploaded, updated)
[docs]class GbxFileMetadata(TMXObject): """ .. versionadded :: 0.3.3 Represents data stored in the map's GBX file metadata Parameters ---------- gbx_map_name : str Unaltered map name author_login : str Unaltered author name map_type : str Nadeo map type embedded. title_pack : str Titlepack of map track_uid : str Unaltered map signature `uid` mood : str Mood of the map (daytime and map base) display_cost : int in game metric for map weight mod_name : str | None Name of texture mod, if included light_map : int Light map version exe_version : str Game application version exe_build : :class:`datetime` Build date of game version author_time : int Map author time in ms environment_name : str Name of the game environment vehicle_name : str Name of the game vehicle """ def __init__( self, gbx_map_name: str, author_login: str, map_type: str, title_pack: str, track_uid: str, mood: str, display_cost: int, mod_name: str, light_map: int, exe_version: str, exe_build: datetime, author_time: int, environment_name: str, vehicle_name: str, ): self.gbx_map_name = gbx_map_name self.author_login = author_login self.map_type = map_type self.title_pack = title_pack self.track_uid = track_uid self.mood = mood self.display_cost = display_cost self.mod_name = mod_name self.light_map = light_map self.exe_version = exe_version self.exe_build = exe_build self.author_time = author_time self.environment_name = environment_name self.vehicle_name = vehicle_name @classmethod def _from_dict(cls: Self, raw: dict) -> Self: _log.debug("Creating a GbxFileMetadata from given dictionary") args = [ raw.get("GbxMapName"), raw.get("AuthorLogin"), raw.get("MapType"), raw.get("TitlePack"), raw.get("TrackUID"), raw.get("Mood"), raw.get("DisplayCost"), raw.get("ModName"), raw.get("Lightmap"), raw.get("ExeVersion"), datetime.strptime(raw.get("ExeBuild"), "%Y-%m-%d_%H_%M"), raw.get("AuthorTime"), raw.get("EnvironmentName"), raw.get("VehicleName"), ] return cls(*args)
[docs]class TMXTags(TMXObject): """ .. versionadded :: 0.3.3 Represents TMX Map Tags Parameters ---------- map_tags : :class:`list[int]` The map tags as a list of integers """ def __init__(self, map_tags: list[int]): self.map_tags = map_tags @classmethod def _from_dict(cls: Self, raw: dict) -> Self: _log.debug("Creating a TMXTags from given dictionary") map_tags_ss = raw.get("Tags", None).split(",") map_tags = [] for tag in map_tags_ss: map_tags.append(int(tag)) return cls(map_tags) def __str__(self) -> str: return ", ".join([_TMX.MAP_TYPE_ENUMS.get(tag) for tag in self.map_tags])
[docs]class ReplayWRData(TMXObject): """ .. versionadded :: 0.3.3 Represents data relating to the wr replay Parameters --------- wr_id : int | None replay id of mx world record wr_time: int | None Time of the mx world record wr_user_id: int | None userid of the mx world record holder wr_username: str mx username of max world record holder """ def __init__( self, wr_id: int | None, wr_time: int | None, wr_user_id: int | None, wr_username: str, ): self.wr_id = wr_id self.wr_time = wr_time self.wr_user_id = wr_user_id self.wr_username = wr_username @classmethod def _from_dict(cls: Self, raw: dict) -> Self: _log.debug("Creating a ReplayWRData from given dictionary") args = [ raw.get("ReplayWRId"), raw.get("ReplayWRTime"), raw.get("ReplayWRUserID"), raw.get("ReplayWRUsername"), ] return cls(*args)
[docs]class TMXMetadata(TMXObject): """ .. versionadded :: 0.3.3 Represents a map's metadata on TMX Parameters ---------- unlisted : bool Whether the map is unlisted on TMX unreleased : bool Whether the map is unreleased on TMX downloadeable : bool Whether the map is downloadeable on TMX rating_vote_count : int The number of votes for the map rating_vote_average : float The average rating of the map has_screenshot : bool Whether the map has a screenshot has_thumbnail : bool Whether the map has a thumbnail has_ghost_blocks : bool Whether the map has ghost blocks embedded_objects_count : int The number of embedded objects on the map embedded_objects_size : int The size of the embedded objects on the map size_warning : bool Map exceeds the size limit for online servers (TM: 6MB) replay_count : int The number of replays on the map award_count : int The number of awards on the map comment_count : int The number of comments on the map image_count : int The number of custom images on the map video_count : int The number of videos on the map """ def __init__( self, unlisted: bool, unreleased: bool, downloadable: bool, rating_vote_count: int, rating_vote_average: float, has_screenshot: bool, has_thumbnail: bool, has_ghost_blocks: bool, embedded_objects_count: int, embedded_objects_size: int, size_warning: bool, replay_count: int, award_count: int, comment_count: int, image_count: int, video_count: int, ): self.unlisted = unlisted self.unreleased = unreleased self.downloadable = downloadable self.rating_vote_count = rating_vote_count self.rating_vote_average = rating_vote_average self.has_screenshot = has_screenshot self.has_thumbnail = has_thumbnail self.has_ghost_blocks = has_ghost_blocks self.embedded_objects_count = embedded_objects_count self.embedded_objects_size = embedded_objects_size self.size_warning = size_warning self.replay_count = replay_count self.award_count = award_count self.comment_count = comment_count self.image_count = image_count self.video_count = video_count @classmethod def _from_dict(cls: Self, raw: dict) -> Self: _log.debug("Creating a TMXMetaData from given dictionary") args = [ raw.get("Unlisted"), raw.get("Unreleased"), raw.get("Downloadable"), raw.get("RatingVoteCount"), raw.get("RatingVoteAverage"), raw.get("HasScreenshot"), raw.get("HasThumbnail"), raw.get("HasGhostBlocks"), raw.get("EmbeddedObjectsCount"), raw.get("EmbeddedItemsSize"), raw.get("SizeWarning"), raw.get("ReplayCount"), raw.get("AwardCount"), raw.get("CommentCount"), raw.get("ImageCount"), raw.get("VideoCount"), ] return cls(*args)
[docs]class TMXMap: """ .. versionadded :: 0.3.3 Represents a Map from TMX Parameters ---------- username : str The username of the map's creator map_name : str The name of the map. The exact same as `GbxMapName`. track_id : int | None The track id of the map map_id : int | None The map id of the map comments : str Map author's comments map_pack_id : str The map pack id of the map user_id : int The user id of the map's creator route_name : str The route name of the map length_name : str The length name of the map difficulty_name : str The difficulty name of the map laps : int The number of laps of the map times: :class:`TMXMapTimes` The uploaded and updated at times of the map. gbx_data : :class:`GbxFileMetadata` The gbx file metadata of the map tags : :class:`TMXTags` The tags of the map replay_wr_data : :class:`ReplayWRData` The replay wr data of the map metadata : :class:`TMXMetadata` The metadata of the map """ def __init__( self, username: str, map_name: str, track_id: int | None, map_id: int | None, comments: str, map_pack_id: str, user_id: int, route_name: str, length_name: str, difficulty_name: str, laps: int, times: TMXMapTimes, gbx_data: GbxFileMetadata, tags: TMXTags, replay_wr_data: ReplayWRData, metadata: TMXMetadata, ): self.username = username self.map_name = map_name self.track_id = track_id self.map_id = map_id self.comments = comments self.map_pack_id = map_pack_id self.user_id = user_id self.route_name = route_name self.length_name = length_name self.difficulty_name = difficulty_name self.laps = laps self.times = times self.gbx_data = gbx_data self.tags = tags self.replay_wr_data = replay_wr_data self.metadata = metadata @classmethod def _from_dict(cls: Self, raw: dict) -> Self: _log.debug("Creating a TMXMap from given dictionary") username = raw.get("Username") map_name = _regex_it(raw.get("GbxMapName")) track_id = raw.get("TrackID") map_id = raw.get("MapID") comments = raw.get("Comments") map_pack_id = raw.get("MappackID") user_id = raw.get("UserID") route_name = raw.get("RouteName") length_name = raw.get("LengthName") difficulty_name = raw.get("DifficultyName") laps = raw.get("Laps") times = TMXMapTimes._from_dict(raw) gbx_data = GbxFileMetadata._from_dict(raw) tags = TMXTags._from_dict(raw) replay_wr_data = ReplayWRData._from_dict(raw) metadata = TMXMetadata._from_dict(raw) args = [ username, map_name, track_id, map_id, comments, map_pack_id, user_id, route_name, length_name, difficulty_name, laps, times, gbx_data, tags, replay_wr_data, metadata, ] return cls(*args)
[docs] @classmethod async def get_map(cls: Self, tmx_id: int) -> Self: """ .. versionadded :: 0.3.3 Gets a map's data using it's tmx id Parameters ---------- tmx_id : int The tmx id Returns ------- :class:`TMXMap` The map of the given id. """ tmx_map_data = await _get_map(tmx_id) return cls._from_dict(tmx_map_data)
[docs] @classmethod async def get_random_map(cls: Self) -> Self: """ Gets a random map from trackmania.exchange Returns ------- :class:`TMXMap` The random map. """ map_data = await _get_random_map() return cls._from_dict(map_data)