from __future__ import annotations import re from .fields import LINE SPEECH_LIMIT = 1000 SPOKEN_TEXT = 500 SPOKEN_NAME = 60 SPOKEN_GROUP = 120 ALARM_WORDS = {"pan-pan": "PAN-PAN", "security": "SECURITE"} CALLS = re.compile(r"^(?:(?:MAYDAY|PAN[ -]?PAN|SECURIT[EÉY])\b[\s:,.;!-]*)+", re.IGNORECASE) HUB_MARK = re.compile(r"\[kvb-hub msg [0-9a-f]{16}\]") HUB_FILE = re.compile(r"\s+file: /\S*$") SPOKEN_LINE = re.compile(LINE.format(SPEECH_LIMIT)) def clip(text: str, limit: int) -> str: words = " ".join(text.split()) if len(words) <= limit: return words return words[:limit].rsplit(" ", 1)[0] or words[:limit] def spoken_text(cls: str, text: str) -> str: body = HUB_MARK.sub("", text) if body != text: body = HUB_FILE.sub("", body) if cls in ("mayday", *ALARM_WORDS): body = CALLS.sub("", body.strip()) body = clip(body, SPOKEN_TEXT) return "" if body == "-" else body def phrase(cls: str, sender: str, called: list[str], text: str) -> str: """Args: cls: the radio class of the row: broadcast, direct, security, pan-pan or mayday. sender: the sender's name valid at the row's time. called: the names of the called stations valid at the row's time. text: the radio text. Returns: The row in maritime radio phraseology (SMCP style), one line of at most SPEECH_LIMIT characters. """ station = clip(sender, SPOKEN_NAME) or "unknown station" if cls == "mayday": head, end = f"MAYDAY MAYDAY MAYDAY, this is {station} {station} {station}", "over" elif cls in ALARM_WORDS: word = ALARM_WORDS[cls] head, end = f"{word} {word} {word}, all stations, this is {station}", "over" elif cls == "direct" and called: group = clip(" and ".join(clip(name, SPOKEN_NAME) for name in called), SPOKEN_GROUP) head, end = f"{group}, {group}, this is {station}", "over" else: head, end = f"all stations, this is {station}", "out" return ", ".join(part for part in (head, spoken_text(cls, text), end) if part)