Source code for tibiapy.parsers.leaderboard

"""Models related to the leaderboard section in Tibia.com."""
from __future__ import annotations

import datetime
import re
from typing import TYPE_CHECKING, Optional

from tibiapy import errors
from tibiapy.builders.leaderboard import LeaderboardBuilder
from tibiapy.models.leaderboard import (
    Leaderboard,
    LeaderboardEntry,
    LeaderboardRotation,
)
from tibiapy.utils import (
    parse_form_data,
    parse_integer,
    parse_pagination,
    parse_tibia_datetime,
    parse_tibiacom_content,
)

if TYPE_CHECKING:
    from bs4 import Tag

__all__ = (
    "LeaderboardParser",
)

rotation_end_pattern = re.compile(r"ends on ([^)]+)")


[docs] class LeaderboardParser: """Parser for leaderboards."""
[docs] @classmethod def from_content(cls, content: str) -> Optional[Leaderboard]: """Parse the content of the leaderboards page. Parameters ---------- content: The HTML content of the leaderboards page. Returns ------- The leaderboard, if found. """ now = datetime.datetime.now(datetime.timezone.utc) try: parsed_content = parse_tibiacom_content(content) tables = parsed_content.select("table.TableContent") form = parsed_content.select_one("form") form_data = parse_form_data(form) current_world = form_data.values["world"] if current_world is None: return None current_rotation = None rotations = [] for label, value in form_data.available_options["rotation"].items(): current = False if "Current" in label: label = "".join(rotation_end_pattern.findall(label)) current = True rotation_end = parse_tibia_datetime(label) rotation = LeaderboardRotation(rotation_id=int(value), end_date=rotation_end, is_current=current) if value == form_data.values["rotation"]: current_rotation = rotation rotations.append(rotation) builder = (LeaderboardBuilder() .world(current_world) .rotation(current_rotation) .available_worlds([w for w in form_data.available_options["world"].values() if w]) .available_rotations(rotations)) if current_rotation and current_rotation.is_current: last_update_table = tables[2] if numbers := re.findall(r"(\d+)", last_update_table.text): builder.last_updated(now - datetime.timedelta(minutes=int(numbers[0]))) cls._parse_entries(builder, tables[-1]) pagination_block = parsed_content.select_one("small") pages, total, count = parse_pagination(pagination_block) if pagination_block else (0, 0, 0) builder.current_page(pages).total_pages(total).results_count(count) return builder.build() except (AttributeError, ValueError, KeyError) as e: raise errors.InvalidContentError("content does not belong to the leaderboards", e) from e
@classmethod def _parse_entries(cls, builder: LeaderboardBuilder, entries_table: Tag) -> None: entries_rows = entries_table.select("tr[style]") for row in entries_rows: columns = row.select("td") if len(columns) != 3: continue rank = parse_integer(columns[0].text.replace(".", "")) points = parse_integer(columns[2].text) name_link = columns[1].select_one("a") name = name_link.text if name_link else None builder.add_entry(LeaderboardEntry(rank=rank, drome_level=points, name=name))