Source code for tibiapy.parsers.event

"""Models related to the event schedule section in Tibia.com."""

import datetime
import re
import time

import bs4

from tibiapy.builders import EventScheduleBuilder
from tibiapy.models import EventEntry, EventSchedule
from tibiapy.utils import parse_popup, parse_tibiacom_content

__all__ = (
    "EventScheduleParser",
)

month_year_regex = re.compile(r"([A-z]+)\s(\d+)")


[docs] class EventScheduleParser: """Parser for the event schedule from Tibia.com."""
[docs] @classmethod def from_content(cls, content: str) -> EventSchedule: """Create an instance of the class from the html content of the event's calendar. Parameters ---------- content: The HTML content of the page. Returns ------- The event calendar contained in the page Raises ------ InvalidContent If content is not the HTML of the event's schedule page. """ parsed_content = parse_tibiacom_content(content) month, year = cls._calculate_month_year(parsed_content.select_one("div.eventscheduleheaderdateblock")) builder = EventScheduleBuilder().year(year).month(month) events_table = parsed_content.select_one("#eventscheduletable") day_cells = events_table.select("td") ongoing_events = [] ongoing_day = 1 first_day = True for day_cell in day_cells: day, today_events = cls._process_day_cell(day_cell) month, year = cls._adjust_date(ongoing_day, day, month, year) ongoing_day = day + 1 # Check which of the ongoing events did not show up today, meaning it has ended now for pending_event in ongoing_events[:]: # If it didn't show up today, it means it ended yesterday. if pending_event not in today_events: end_date = datetime.date(day=day, month=month, year=year) - datetime.timedelta(days=1) pending_event.end_date = end_date builder.add_event(pending_event) ongoing_events.remove(pending_event) for event in today_events: # Unless today is the first day of the calendar, then we don't know for sure. if event in ongoing_events: continue # Only add a start date if this is not the first day of the calendar # We do not know the actual start date of the event. if not first_day: event.start_date = datetime.date(day=day, month=month, year=year) ongoing_events.append(event) first_day = False # Add any leftover ongoing events without an end date, as we don't know when they end. for event in ongoing_events: builder.add_event(event) return builder.build()
@classmethod def _adjust_date(cls, ongoing_day: int, day: int, month: int, year: int) -> tuple[int, int]: if ongoing_day < day: # The first cells may belong to the previous month month -= 1 if day < ongoing_day: # The last cells may belong to the last month month += 1 if month > 12: # Set to january of next year month = 1 year += 1 if month < 1: # Set to december of previous year month = 12 year -= 1 return month, year @classmethod def _parse_inline_style(cls, style_content: str) -> dict[str, str]: attrs = style_content.split(";") values = {} for attr in attrs: if not attr.strip(): continue key, value = attr.split(":") values[key.strip()] = value.strip() return values @classmethod def _process_day_cell(cls, day_cell: bs4.Tag) -> tuple[int, list[EventEntry]]: day_div = day_cell.select_one("div") day = int(day_div.text) today_events = [] for popup in day_cell.select("span.HelperDivIndicator"): colored_blocks = popup.select("div:not([class])") event_colors = {} for block in colored_blocks: style_values = cls._parse_inline_style(block.get("style")) block_title = block.text.replace("*", "") event_colors[block_title] = style_values["background"] title, popup_content = parse_popup(popup["onmouseover"]) divs = popup_content.select("div") # Multiple events can be described in the same popup, they come in pairs, title and content. for title, content in zip(*[iter(d.text for d in divs)] * 2): title = title.replace(":", "") content = content.replace("• ", "") event = EventEntry(title=title, description=content, color=event_colors.get(title)) today_events.append(event) return day, today_events @classmethod def _calculate_month_year(cls, month_year_div: bs4.Tag) -> tuple[int, int]: month, year = month_year_regex.search(month_year_div.text).groups() month = time.strptime(month, "%B").tm_mon year = int(year) return month, year