import time from functools import wraps from loguru import logger from typing import Any, Callable import os def timing_decorator(func: Callable) -> Callable: """ Decorator đo thời gian thực thi của hàm async, log thời lượng xử lý. Input: func (Callable) - hàm async cần đo. Output: Kết quả trả về của func. """ @wraps(func) async def wrapper(*args: Any, **kwargs: Any) -> Any: start_time = time.time() result = await func(*args, **kwargs) end_time = time.time() duration = end_time - start_time logger.info(f"{func.__name__} took {duration:.2f} seconds to execute") return result return wrapper def setup_logging(log_level: str = "INFO") -> None: """ Thiết lập logging với loguru, log ra file và console. Input: log_level (str) - mức log. Output: None. """ logger.remove() # Remove default handler logger.add( "logs/webot.log", rotation="500 MB", retention="10 days", level=log_level, format="{time:YYYY-MM-DD HH:mm:ss} | {level} | {message}", ) logger.add( lambda msg: print(msg), level=log_level, format="{time:YYYY-MM-DD HH:mm:ss} | {level} | {message}", ) def extract_command(text: str) -> tuple[str, str]: """ Tách lệnh (bắt đầu bằng \) và phần còn lại từ message. Input: text (str) - message từ user. Output: (command, remaining_text) - tuple (str, str). """ if not text.startswith("\\"): return "", text parts = text.split(maxsplit=1) command = parts[0][1:] # Remove the backslash remaining = parts[1] if len(parts) > 1 else "" return command, remaining def extract_keywords(text: str, keywords: list[str]) -> list[str]: """ Tìm các từ khóa xuất hiện trong message. Input: text (str), keywords (list[str]) Output: list[str] các từ khóa tìm thấy. """ return [keyword for keyword in keywords if keyword.lower() in text.lower()] def ensure_log_dir(): """ Đảm bảo thư mục logs tồn tại, tạo nếu chưa có. Input: None Output: None """ os.makedirs("logs", exist_ok=True) def validate_config(settings) -> None: """ Kiểm tra các biến môi trường/config bắt buộc, raise lỗi nếu thiếu. Input: settings (Settings) Output: None (raise RuntimeError nếu thiếu) """ missing = [] for field in [ 'facebook_verify_token', 'facebook_app_secret', 'google_sheets_credentials_file', 'google_sheets_token_file', 'conversation_sheet_id', 'supabase_url', 'supabase_key']: if not getattr(settings, field, None): missing.append(field) if missing: raise RuntimeError(f"Missing config: {', '.join(missing)}")