108 lines
2.9 KiB
Python
108 lines
2.9 KiB
Python
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass
|
|
import logging
|
|
from typing import TYPE_CHECKING
|
|
|
|
from app.commands.core import (
|
|
CommandContext,
|
|
CommandRegistry,
|
|
normalize_command_name,
|
|
)
|
|
|
|
if TYPE_CHECKING:
|
|
from app.manager import AccountManager, AccountState
|
|
from pyrogram import Client
|
|
from pyrogram.types import Message
|
|
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
|
|
@dataclass(slots=True)
|
|
class ParsedCommand:
|
|
prefix: str
|
|
name: str
|
|
args: str
|
|
alias: str
|
|
|
|
|
|
def parse_command(text: str) -> ParsedCommand | None:
|
|
stripped = text.lstrip()
|
|
if not stripped.startswith("."):
|
|
return None
|
|
|
|
prefix = ".." if stripped.startswith("..") else "."
|
|
payload = stripped[len(prefix) :]
|
|
if not payload.strip():
|
|
return None
|
|
|
|
parts = payload.strip().split(maxsplit=1)
|
|
if not parts:
|
|
return None
|
|
|
|
alias = parts[0]
|
|
name = normalize_command_name(alias)
|
|
args = parts[1] if len(parts) > 1 else ""
|
|
return ParsedCommand(prefix=prefix, name=name, args=args, alias=alias)
|
|
|
|
|
|
class CommandDispatcher:
|
|
def __init__(self, registry: CommandRegistry) -> None:
|
|
self.registry = registry
|
|
|
|
async def dispatch(
|
|
self,
|
|
client: "Client",
|
|
message: "Message",
|
|
manager: "AccountManager",
|
|
account: "AccountState",
|
|
) -> bool:
|
|
text = getattr(message, "text", None)
|
|
caption = getattr(message, "caption", None)
|
|
command_text = text if isinstance(text, str) else caption
|
|
if not isinstance(command_text, str):
|
|
return False
|
|
|
|
parsed = parse_command(command_text)
|
|
if parsed is None:
|
|
return False
|
|
|
|
definition = self.registry.resolve(parsed.name)
|
|
if definition is None:
|
|
return True
|
|
|
|
actor_id = getattr(getattr(message, "from_user", None), "id", None)
|
|
owner_id = getattr(getattr(account, "me", None), "id", None)
|
|
is_owner = owner_id is not None and actor_id == owner_id
|
|
|
|
expected_prefix = definition.expected_prefix(is_owner)
|
|
if parsed.prefix != expected_prefix:
|
|
return True
|
|
|
|
ctx = CommandContext(
|
|
client=client,
|
|
manager=manager,
|
|
account=account,
|
|
message=message,
|
|
command=definition,
|
|
prefix=parsed.prefix,
|
|
raw_text=command_text,
|
|
args=parsed.args,
|
|
alias=parsed.alias,
|
|
)
|
|
|
|
for check in definition.checks:
|
|
result = await check.run(ctx)
|
|
if not result.allowed:
|
|
if is_owner:
|
|
await message.reply_text(result.reason or "Access denied")
|
|
return True
|
|
|
|
try:
|
|
await definition.handler(ctx)
|
|
except Exception as exc: # pragma: no cover - defensive logging
|
|
log.exception("Command %s failed: %s", definition.display_name, exc)
|
|
await ctx.reply("Command failed with an internal error")
|
|
return True
|