Add multi-account command ACL system
This commit is contained in:
101
app/commands/dispatcher.py
Normal file
101
app/commands/dispatcher.py
Normal file
@@ -0,0 +1,101 @@
|
||||
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)
|
||||
if not isinstance(text, str):
|
||||
return False
|
||||
|
||||
parsed = parse_command(text)
|
||||
if parsed is None:
|
||||
return False
|
||||
|
||||
definition = self.registry.resolve(parsed.name)
|
||||
if definition is None:
|
||||
await message.reply_text(f"Unknown command: {parsed.prefix}{parsed.alias}")
|
||||
return True
|
||||
|
||||
if parsed.prefix != definition.prefix:
|
||||
await message.reply_text(f"Use {definition.display_name} for this command")
|
||||
return True
|
||||
|
||||
ctx = CommandContext(
|
||||
client=client,
|
||||
manager=manager,
|
||||
account=account,
|
||||
message=message,
|
||||
command=definition,
|
||||
prefix=parsed.prefix,
|
||||
raw_text=text,
|
||||
args=parsed.args,
|
||||
alias=parsed.alias,
|
||||
)
|
||||
|
||||
for check in definition.checks:
|
||||
result = await check.run(ctx)
|
||||
if not result.allowed:
|
||||
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
|
||||
Reference in New Issue
Block a user