e0bae394ee
Backend security: - Reject Gitea webhooks when webhook_secret is empty (was silently skipping) - Add slowapi rate limiting on login (5/min) and setup (3/min) endpoints - Add CORS middleware with configurable origins - Mask telegram_webhook_secret in settings API response - Protect system-owned command template configs from regular user modification - Increase minimum password length to 8 characters Backend performance: - Batch queries in _resolve_command_context (3 queries instead of 3N) - Concurrent album fetching with asyncio.gather in immich commands - Singleton Jinja2 SandboxedEnvironment (reuse instead of per-render creation) - TTLCache for rate limits (bounded memory, auto-eviction) - Optional aiohttp session reuse in send_reply/send_media_group Backend code quality: - Extract dispatch_helpers.py (shared link_data loading + event filtering) - Extract database/seeds.py from main.py (490 lines → dedicated module) - Split immich_handler.py (415 lines) into commands/immich/ subpackage - Replace bare except blocks with logged warnings - Add per-provider config validation (Pydantic models) - Truncate command input to 512 chars - Expose usage_* and desc_* slots in capabilities and variables API Frontend security: - CSS.escape() for user-controlled querySelector in highlight.ts - Client-side password min 8 chars validation on setup and password change Frontend code quality: - Replace any types with proper interfaces across top files - Decompose targets/+page.svelte into TargetForm + ReceiverSection - Fix $derived.by usage, $state mutation patterns - Add console.warn to empty catch blocks Frontend UX: - Auth redirect via goto() with "Redirecting..." state - Platform-aware Ctrl/Cmd K keyboard hint - Remove stat-card hover transform Frontend accessibility: - Modal: role=dialog, aria-modal, focus trap, restore focus - EntitySelect/IconGridSelect: listbox/option roles, aria-selected/disabled
41 lines
1.2 KiB
Python
41 lines
1.2 KiB
Python
"""Command text parsing — extracts command name, arguments, and optional count."""
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
def parse_command(text: str) -> tuple[str, str, int | None]:
|
|
"""Parse a command message into (command, args, count).
|
|
|
|
Examples:
|
|
"/search sunset" -> ("search", "sunset", None)
|
|
"/latest Family 5" -> ("latest", "Family", 5)
|
|
"/events 10" -> ("events", "", 10)
|
|
"/help@mybot" -> ("help", "", None)
|
|
"""
|
|
text = text[:512].strip()
|
|
if not text.startswith("/"):
|
|
return ("", text, None)
|
|
|
|
# Strip @botname suffix: /command@botname args
|
|
parts = text[1:].split(None, 1)
|
|
cmd = parts[0].split("@")[0].lower()
|
|
rest = parts[1] if len(parts) > 1 else ""
|
|
|
|
# Try to extract trailing count
|
|
count = None
|
|
rest_parts = rest.rsplit(None, 1)
|
|
if len(rest_parts) == 2:
|
|
try:
|
|
count = int(rest_parts[1])
|
|
rest = rest_parts[0]
|
|
except ValueError:
|
|
pass
|
|
elif rest_parts and rest_parts[0]:
|
|
try:
|
|
count = int(rest_parts[0])
|
|
rest = ""
|
|
except ValueError:
|
|
pass
|
|
|
|
return (cmd, rest.strip(), count)
|