from dataclasses import dataclass from typing import List, Any, Optional from enum import Enum import re class EventType(Enum): RESONATOR_DEPLOYED = "RESONATOR_DEPLOYED" RESONATOR_DESTROYED = "RESONATOR_DESTROYED" PORTAL_CAPTURED = "PORTAL_CAPTURED" PORTAL_NEUTRALIZED = "PORTAL_NEUTRALIZED" PORTAL_UNDER_ATTACK = "PORTAL_UNDER_ATTACK" LINK_CREATED = "LINK_CREATED" LINK_DESTROYED = "LINK_DESTROYED" CONTROL_FIELD_CREATED = "CONTROL_FIELD_CREATED" UNKNOWN = "UNKNOWN" EVENT_TYPE_KEYWORDS = { EventType.RESONATOR_DEPLOYED: ["deployed a Resonator on"], EventType.RESONATOR_DESTROYED: ["destroyed a Resonator on"], EventType.LINK_DESTROYED: ["destroyed the", "Link"], EventType.PORTAL_CAPTURED: ["captured"], EventType.PORTAL_NEUTRALIZED: ["neutralized by"], EventType.PORTAL_UNDER_ATTACK: ["is under attack by"], EventType.LINK_CREATED: ["linked from"], EventType.CONTROL_FIELD_CREATED: ["created a Control Field"], } @dataclass class Markup: type: str plain: str team: str = "" name: str = "" address: str = "" latE6: int = 0 lngE6: int = 0 @dataclass class Plext: id: str timestamp: int text: str team: str plext_type: str categories: int markup: List[Markup] @classmethod def from_json(cls, data: List[Any]) -> "Plext": plext_data = data[2]["plext"] markup_data = plext_data["markup"] markup = [] for m in markup_data: markup_type = m[0] markup_details = m[1] markup.append( Markup( type=markup_type, plain=markup_details.get("plain", ""), team=markup_details.get("team", ""), name=markup_details.get("name", ""), address=markup_details.get("address", ""), latE6=markup_details.get("latE6", 0), lngE6=markup_details.get("lngE6", 0), ) ) return cls( id=data[0], timestamp=data[1], text=plext_data["text"], team=plext_data["team"], plext_type=plext_data["plextType"], categories=plext_data["categories"], markup=markup, ) def get_event_type(self) -> EventType: for event_type, keywords in EVENT_TYPE_KEYWORDS.items(): if all(keyword in self.text for keyword in keywords): # A special case for "captured", to avoid matching "destroyed" if event_type == EventType.PORTAL_CAPTURED and "destroyed" in self.text: continue return event_type return EventType.UNKNOWN def get_player_name(self) -> str: for m in self.markup: if m.type == "PLAYER": return m.plain # If player name is not in markup, try to extract from text if "agent" in self.text: match = re.search(r"agent (\w+)", self.text) if match: return match.group(1) return "" def get_portal_name(self) -> str: for m in self.markup: if m.type == "PORTAL": return m.name # If portal name is not in markup, try to extract from text match = re.search(r"(?:deployed|destroyed|captured|linked from|created a Control Field @) (.+?) \(", self.text) if not match: match = re.search(r"Your Portal (.+?) is under attack by", self.text) if not match: match = re.search(r"Your Portal (.+?) neutralized by", self.text) if match: return match.group(1).strip() return "" def get_event_coordinates(self) -> Optional[tuple[int, int]]: for m in self.markup: if m.type == "PORTAL": return m.latE6, m.lngE6 return None