95 lines
2.7 KiB
Python
95 lines
2.7 KiB
Python
|
|
from __future__ import annotations
|
|||
|
|
|
|||
|
|
import re
|
|||
|
|
import unicodedata
|
|||
|
|
from typing import Any
|
|||
|
|
from typing import Iterable
|
|||
|
|
from typing import Tuple
|
|||
|
|
|
|||
|
|
_SLASH_CHARS = "//⁄∕╱⧸"
|
|||
|
|
_COMMAND_RE = re.compile(
|
|||
|
|
rf"^[\s\u00A0]*[{re.escape(_SLASH_CHARS)}]\s*([A-Za-z0-9_]+)(?:@([A-Za-z0-9_]+))?",
|
|||
|
|
flags=re.ASCII,
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
|
|||
|
|
def _clean_text(text: str | None) -> str:
|
|||
|
|
raw = str(text or "")
|
|||
|
|
if not raw:
|
|||
|
|
return ""
|
|||
|
|
cleaned: list[str] = []
|
|||
|
|
for ch in raw:
|
|||
|
|
code = ord(ch)
|
|||
|
|
if 0xFE00 <= code <= 0xFE0F:
|
|||
|
|
continue
|
|||
|
|
if 0xE0100 <= code <= 0xE01EF:
|
|||
|
|
continue
|
|||
|
|
if unicodedata.category(ch) == "Cf":
|
|||
|
|
continue
|
|||
|
|
cleaned.append(ch)
|
|||
|
|
normalized = "".join(cleaned).strip()
|
|||
|
|
if not normalized:
|
|||
|
|
return ""
|
|||
|
|
for slash in ("/", "⁄", "∕", "╱", "⧸"):
|
|||
|
|
normalized = normalized.replace(slash, "/")
|
|||
|
|
return normalized
|
|||
|
|
|
|||
|
|
|
|||
|
|
def _parse_command_token(text: str | None) -> Tuple[str, str]:
|
|||
|
|
normalized = _clean_text(text)
|
|||
|
|
if not normalized:
|
|||
|
|
return ("", "")
|
|||
|
|
match = _COMMAND_RE.match(normalized)
|
|||
|
|
if not match:
|
|||
|
|
return ("", "")
|
|||
|
|
command = str(match.group(1) or "").strip().lower()
|
|||
|
|
username = str(match.group(2) or "").strip().lower()
|
|||
|
|
return (command, username)
|
|||
|
|
|
|||
|
|
|
|||
|
|
def extract_command_token(
|
|||
|
|
text: str | None,
|
|||
|
|
entities: Iterable[Any] | None = None,
|
|||
|
|
) -> Tuple[str, str]:
|
|||
|
|
raw = str(text or "")
|
|||
|
|
if entities:
|
|||
|
|
for entity in entities:
|
|||
|
|
if str(getattr(entity, "type", "") or "").strip() != "bot_command":
|
|||
|
|
continue
|
|||
|
|
try:
|
|||
|
|
offset = int(getattr(entity, "offset", 0) or 0)
|
|||
|
|
length = int(getattr(entity, "length", 0) or 0)
|
|||
|
|
except Exception:
|
|||
|
|
continue
|
|||
|
|
if length <= 0:
|
|||
|
|
continue
|
|||
|
|
fragment = raw[offset : offset + length]
|
|||
|
|
command, username = _parse_command_token(fragment)
|
|||
|
|
if command:
|
|||
|
|
return (command, username)
|
|||
|
|
return _parse_command_token(raw)
|
|||
|
|
|
|||
|
|
|
|||
|
|
def extract_command_name(text: str | None, entities: Iterable[Any] | None = None) -> str:
|
|||
|
|
return extract_command_token(text, entities)[0]
|
|||
|
|
|
|||
|
|
|
|||
|
|
def looks_like_slash_command(text: str | None) -> bool:
|
|||
|
|
normalized = _clean_text(text)
|
|||
|
|
if not normalized:
|
|||
|
|
return False
|
|||
|
|
return normalized[:1] == "/"
|
|||
|
|
|
|||
|
|
|
|||
|
|
def split_command_and_args(text: str | None) -> Tuple[str, str]:
|
|||
|
|
normalized = _clean_text(text)
|
|||
|
|
if not normalized:
|
|||
|
|
return ("", "")
|
|||
|
|
match = _COMMAND_RE.match(normalized)
|
|||
|
|
if not match:
|
|||
|
|
return ("", "")
|
|||
|
|
command = str(match.group(1) or "").strip().lower()
|
|||
|
|
rest = normalized[match.end() :].lstrip()
|
|||
|
|
return (command, rest)
|
|||
|
|
|