Spaces:
Running
Running
import asyncio | |
import os | |
import bcrypt | |
import hashlib | |
import json | |
import re | |
import traceback | |
import time | |
from fuzzywuzzy import fuzz | |
from zoneinfo import ZoneInfo | |
from datetime import datetime, timedelta | |
from typing import Union, Dict, List | |
from collections import defaultdict | |
from src.logger import logger | |
from google.cloud.firestore_v1 import ArrayUnion | |
from src.firestore_db import FirestoreDB | |
from src.search import Search | |
from src.lottery_core import LotteryCore | |
from config.settings import Settings | |
from tenacity import retry, stop_after_attempt, wait_fixed | |
class CommandHandler: | |
async def handle(self, bot, chat_id: int, user_id: int, text: str, is_gradio: bool = False) -> Union[bool, str]: | |
raise NotImplementedError | |
class StartCommand(CommandHandler): | |
async def handle(self, bot, chat_id: int, user_id: int, text: str, is_gradio: bool = False) -> Union[bool, str]: | |
logger.debug(f"[StartCommand] chat_id={chat_id}, user_id={user_id}, text={text[:50]}..., is_gradio={is_gradio}") | |
response = "Chào! Gửi câu hỏi hoặc dùng /help để xem các lệnh." | |
if is_gradio: | |
return response | |
await bot.post_message(chat_id, [response], is_gradio) | |
return True | |
class HelpCommand(CommandHandler): | |
async def handle(self, bot, chat_id: int, user_id: int, text: str, is_gradio: bool = False) -> Union[bool, str]: | |
logger.debug(f"[HelpCommand] chat_id={chat_id}, user_id={user_id}, text={text[:50]}..., is_gradio={is_gradio}") | |
help_text = [ | |
"Hướng dẫn sử dụng @CotienBot:", | |
"- /start: Khởi động bot.", | |
"- /help: Hiển thị hướng dẫn này.", | |
"- /train <văn_bản>: Đào tạo văn bản thông thường.", | |
"- /train_json: Đào tạo dữ liệu xổ số từ JSON.", | |
"- /Gemini: Chuyển sang chế độ Gemini AI.", | |
"- /exit: Thoát chế độ Gemini.", | |
"- /lottery <đài> <ngày_bắt_đầu> <ngày_kết_thúc>: Phân tích xổ số.", | |
"- /load_lottery <kết_quả>: Nạp kết quả xổ số từ văn bản.", | |
"- /save_lottery_vectors: Lưu vector xổ số (đã tắt).", | |
"- /add_lottery <kết_quả>: Thêm kết quả xổ số.", | |
"- /migrate_lottery_data: Di chuyển dữ liệu xổ số.", | |
"- /check_dates: Kiểm tra dữ liệu Firestore.", | |
"- /schedule [đài]: Lấy lịch xổ số.", | |
"- /lottery_position <đài> <ngày> <số_bản_ghi>: Dự đoán xổ số (ngày: DD-MM-YYYY).", | |
"- /register <email/tên_người_dùng> <mật_khẩu>: Đăng ký tài khoản.", | |
"- /auth <mật_khẩu_bot>: Xác thực để sử dụng lệnh nâng cao.", | |
"- Gửi 'Lịch xổ số' hoặc câu hỏi thông thường để tìm kiếm." | |
] | |
cache_key = f"{user_id}:{text[:100]}" | |
await bot.cache.set(cache_key, help_text, type="default", ttl=Settings.CACHE_TTL or 86400) | |
if is_gradio: | |
return help_text | |
await bot.post_message(chat_id, [help_text], is_gradio) | |
return True | |
class GeminiCommand(CommandHandler): | |
async def handle(self, bot, chat_id: int, user_id: int, text: str, is_gradio: bool = False) -> str: | |
logger.debug(f"[GeminiCommand] chat_id={chat_id}, user_id={user_id}, text={text[:50]}..., is_gradio={is_gradio}") | |
try: | |
is_authenticated, message = await bot.check_user_auth(user_id, is_gradio) | |
if not is_authenticated: | |
logger.debug(f"[GeminiCommand] Authentication failed for user_id={user_id}: {message}") | |
return message | |
bot.gemini_mode[user_id] = True | |
doc_id = f"gradio_{user_id}" if is_gradio else str(user_id) | |
success = await bot.db.set( | |
{"gemini_mode": True}, | |
data_type="users", | |
doc_id=doc_id, | |
merge=True | |
) | |
if not success: | |
logger.error(f"[GeminiCommand] Failed to set gemini_mode=True in Firestore for doc_id={doc_id}") | |
bot.gemini_mode[user_id] = False | |
return "Lỗi: Không thể lưu trạng thái Gemini vào Firestore. Vui lòng thử lại." | |
logger.debug(f"[GeminiCommand] Set gemini_mode=True for user_id={user_id}, doc_id={doc_id}") | |
response = "Chuyển sang chế độ Gemini. Gửi câu hỏi hoặc /exit để thoát." | |
if is_gradio: | |
return response | |
await bot.post_message(chat_id, [response], is_gradio) | |
return response | |
except Exception as e: | |
logger.error(f"[GeminiCommand] Error: {str(e)}", exc_info=True) | |
bot.gemini_mode[user_id] = False | |
response = f"Lỗi khi chuyển sang chế độ Gemini: {str(e)}" | |
if is_gradio: | |
return response | |
await bot.post_message(chat_id, [response], is_gradio) | |
return response | |
class ExitCommand(CommandHandler): | |
async def handle(self, bot, chat_id: int, user_id: int, text: str, is_gradio: bool = False) -> Union[bool, str]: | |
logger.debug(f"[ExitCommand] chat_id={chat_id}, user_id={user_id}, text={text[:50]}..., is_gradio={is_gradio}") | |
try: | |
# Kiểm tra xác thực | |
is_authenticated, message = await bot.check_user_auth(user_id, is_gradio) | |
if not is_authenticated: | |
logger.debug(f"[ExitCommand] Authentication failed for user_id={user_id}: {message}") | |
if is_gradio: | |
return message | |
await bot.post_message(chat_id, [message], is_gradio) | |
return False | |
bot.gemini_mode[user_id] = False | |
doc_id = f"gradio_{user_id}" if is_gradio else str(user_id) | |
await bot.db.set( | |
{"gemini_mode": False}, | |
data_type="users", | |
doc_id=doc_id, | |
merge=True | |
) | |
logger.debug(f"[ExitCommand] Set gemini_mode=False for user_id={user_id}") | |
response = "Đã thoát khỏi chế độ Gemini." | |
if is_gradio: | |
return response | |
await bot.post_message(chat_id, [response], is_gradio) | |
return True | |
except Exception as e: | |
logger.error(f"[ExitCommand] Error: {str(e)}", exc_info=True) | |
response = f"Lỗi khi thoát chế độ Gemini: {str(e)}" | |
if is_gradio: | |
return response | |
await bot.post_message(chat_id, [response], is_gradio) | |
return False | |
class TrainCommand(CommandHandler): | |
async def handle(self, bot, chat_id: int, user_id: int, text: str, is_gradio: bool = False) -> str: | |
logger.debug(f"[TrainCommand] chat_id={chat_id}, user_id={user_id}, text={text[:50]}..., is_gradio={is_gradio}") | |
try: | |
is_authenticated, message = await bot.check_user_auth(user_id, is_gradio) | |
if not is_authenticated: | |
return message | |
parts = text.split(maxsplit=1) | |
if len(parts) < 2: | |
return "Vui lòng cung cấp văn bản hoặc JSON để huấn luyện." | |
content = parts[1].strip() | |
if not content: | |
return "Vui lòng cung cấp văn bản hoặc JSON để huấn luyện." | |
result = await train_data(bot.db, bot.search, param=content, user_id=str(user_id)) | |
if result["status"] == "error": | |
return result["message"][0] if result["message"] else "Lỗi huấn luyện." | |
cache_key = f"training_data:{user_id}" | |
user_doc = await bot.db.get(data_type="users", doc_id=f"gradio_{user_id}") | |
training_data = user_doc.get("training_data", []) if user_doc else [] | |
await bot.cache.set( | |
cache_key, | |
json.dumps(training_data), | |
type="default", | |
ttl=Settings.CACHE_TTL or 86400 | |
) | |
logger.debug(f"[TrainCommand] Refreshed cache: key={cache_key}") | |
await bot.db.save_chat_history(user_id=str(user_id), prompt=text) | |
logger.debug(f"[TrainCommand] Saved to chat history: user_id={user_id}, text={text[:50]}...") | |
logger.info(f"[TrainCommand] Trained: key={content[:50]}..., content={content[:50]}...") | |
return "Đào tạo hoàn tất." | |
except Exception as e: | |
logger.error(f"[TrainCommand] Error: {str(e)}", exc_info=True) | |
return f"Lỗi: {str(e)}" | |
class TrainJsonCommand(CommandHandler): | |
async def handle(self, bot, chat_id: int, user_id: int, text: str, is_gradio: bool = False) -> Union[bool, str]: | |
logger.debug(f"[TrainJsonCommand] chat_id={chat_id}, user_id={user_id}, text={text[:50]}..., is_gradio={is_gradio}") | |
try: | |
async with asyncio.timeout(180): | |
is_authenticated, message = await bot.check_user_auth(user_id, is_gradio) | |
if not is_authenticated: | |
response = message | |
if is_gradio: | |
return response | |
await bot.post_message(chat_id, [response], is_gradio) | |
return False | |
result = await train_json(bot.lottery_core, file_path=Settings.LOTTERY_DATA_PATH) | |
response = result["message"] if isinstance(result["message"], str) else " | ".join(result["message"]) | |
if result["status"] == "error": | |
if is_gradio: | |
return response | |
await bot.post_message(chat_id, [response], is_gradio) | |
return False | |
if is_gradio: | |
return response | |
await bot.post_message(chat_id, [response], is_gradio) | |
return True | |
except asyncio.TimeoutError: | |
logger.error("[TrainJsonCommand] Timeout, timeout=180s") | |
response = "Lỗi: Timeout khi tải dữ liệu JSON" | |
if is_gradio: | |
return response | |
await bot.post_message(chat_id, [response], is_gradio) | |
return False | |
except Exception as e: | |
logger.error(f"[TrainJsonCommand] Error: {str(e)}", exc_info=True) | |
response = f"Lỗi: {str(e)}" | |
if is_gradio: | |
return response | |
await bot.post_message(chat_id, [response], is_gradio) | |
return False | |
class LotteryCommand(CommandHandler): | |
async def handle(self, bot, chat_id: int, user_id: int, text: str, is_gradio: bool = False) -> Union[bool, str]: | |
logger.debug(f"[LotteryCommand] chat_id={chat_id}, user_id={user_id}, text={text[:50]}..., is_gradio={is_gradio}") | |
try: | |
# Kiểm tra xác thực | |
is_authenticated, message = await bot.check_user_auth(user_id, is_gradio) | |
if not is_authenticated: | |
logger.debug(f"[LotteryCommand] Authentication failed for user_id={user_id}: {message}") | |
if is_gradio: | |
return message | |
await bot.post_message(chat_id, [message], is_gradio) | |
return False | |
async with asyncio.timeout(180): | |
parts = text.split(maxsplit=3) | |
if len(parts) < 4: | |
response = "Dùng: /lottery <đài> <ngày_bắt_đầu> <ngày_kết_thúc> (DD-MM-YYYY)." | |
if is_gradio: | |
return response | |
await bot.post_message(chat_id, [response], is_gradio) | |
return False | |
end_date = parts[-1].replace("/", "-") | |
start_date = parts[-2].replace("/", "-") | |
dai = parts[1].lower() | |
if dai not in Settings.DAI_CONFIG: | |
response = f"Đài không hợp lệ: {dai}. Dùng: {', '.join(Settings.DAI_CONFIG.keys())}" | |
if is_gradio: | |
return response | |
await bot.post_message(chat_id, [response], is_gradio) | |
return False | |
try: | |
datetime.strptime(start_date, "%d-%m-%Y") | |
datetime.strptime(end_date, "%d-%m-%Y") | |
except ValueError: | |
response = "Định dạng ngày không hợp lệ: DD-MM-YYYY." | |
if is_gradio: | |
return response | |
await bot.post_message(chat_id, [response], is_gradio) | |
return False | |
logger.debug(f"[lottery] Analyzing: dai={dai}, start={start_date}, end={end_date}") | |
lottery_core = bot.lottery_core | |
result = await lottery_core.analyze(dai, start_date, end_date) | |
if not result or "error" in result: | |
response = result.get("error", "Không tìm thấy dữ liệu.") | |
if is_gradio: | |
return response | |
await bot.post_message(chat_id, [response], is_gradio) | |
return False | |
response = [f"Phân tích {dai} từ {start_date} đến {end_date}:"] | |
for hang, stats in result["frequencies"].items(): | |
if stats: | |
response.append(f"<br>Hàng {hang}:" if is_gradio else f"\nHàng {hang}:") | |
for stat in stats: | |
response.append(f"- Số {stat['number']}: {stat['count']} lần") | |
response.append("<br>Bộ ba xuất hiện nhiều nhất:" if is_gradio else "\nBộ ba xuất hiện nhiều nhất:") | |
response.extend(f"- {triplet}" for triplet in result["triplets"]) | |
response_str = "<br>".join(response) if is_gradio else "\n".join(response) | |
if len(response_str.encode("utf-8")) > 3500: | |
response_str = ("<br>" if is_gradio else "\n").join(response[:50]) + ("<br>" if is_gradio else "\n") + "[Kết quả đã rút ngắn]" | |
if is_gradio: | |
return response_str | |
await bot.post_message(chat_id, [response_str], is_gradio) | |
return True | |
except asyncio.TimeoutError: | |
logger.error("[lottery] Timeout, timeout=180s") | |
response = "Lỗi: Timeout khi phân tích" | |
if is_gradio: | |
return response | |
await bot.post_message(chat_id, [response], is_gradio) | |
return False | |
except Exception as e: | |
logger.error(f"[lottery] Error: {str(e)}", exc_info=True) | |
response = f"Lỗi: {str(e)}" | |
if is_gradio: | |
return response | |
await bot.post_message(chat_id, [response], is_gradio) | |
return False | |
class LoadLotteryCommand(CommandHandler): | |
def __init__(self): | |
self.lock = asyncio.Lock() | |
async def handle(self, bot, chat_id: int, user_id: int, text: str, is_gradio: bool = False) -> Union[bool, str]: | |
logger.debug(f"[LoadLotteryCommand] chat_id={chat_id}, user_id={user_id}, text={text[:50]}..., is_gradio={is_gradio}") | |
async with self.lock: | |
try: | |
# Kiểm tra xác thực | |
is_authenticated, message = await bot.check_user_auth(user_id, is_gradio) | |
if not is_authenticated: | |
logger.debug(f"[LoadLotteryCommand] Authentication failed for user_id={user_id}: {message}") | |
if is_gradio: | |
return message | |
await bot.post_message(chat_id, [message], is_gradio) | |
return False | |
async with asyncio.timeout(180): | |
result_text = "" | |
if is_gradio: | |
result_text = text.split(" ", 1)[1] if len(text.split(" ", 1)) > 1 else "" | |
if not result_text: | |
response = "Cung cấp văn bản kết quả xổ số sau lệnh /load_lottery. Ví dụ: Xổ số Bình Định 03-07-2025\nĐặc biệt: 162010\nGiải nhất: 63575" | |
if is_gradio: | |
return response | |
await bot.post_message(chat_id, [response], is_gradio) | |
return False | |
else: | |
if text.strip() == "/load_lottery": | |
if not bot.message or not bot.message.message.get("reply_to_message"): | |
response = "Trả lời tin nhắn chứa kết quả xổ số hoặc cung cấp văn bản. Ví dụ: Xổ số Bình Định 03-07-2025\nĐặc biệt: 162010\nGiải nhất: 63575" | |
if is_gradio: | |
return response | |
await bot.post_message(chat_id, [response], is_gradio) | |
return False | |
result_text = bot.message.message["reply_to_message"].get("text", "") | |
else: | |
result_text = text.split(" ", 1)[1] if len(text.split(" ", 1)) > 1 else "" | |
if not result_text: | |
response = "Cung cấp văn bản kết quả xổ số. Ví dụ: Xổ số Bình Định 03-07-2025\nĐặc biệt: 162010\nGiải nhất: 63575" | |
if is_gradio: | |
return response | |
await bot.post_message(chat_id, [response], is_gradio) | |
return False | |
result_text = re.sub(r'\s+', ' ', result_text.strip()) | |
lines = result_text.split('\n') | |
if not lines: | |
response = "Lỗi: Văn bản rỗng." | |
if is_gradio: | |
return response | |
await bot.post_message(chat_id, [response], is_gradio) | |
return False | |
header_match = re.match( | |
r"Xổ số\s+(.+?)(?:\s+Thứ\s+\w+|Chủ\s+Nhật)?\s*(\d{2}[-./]\d{2}[-./]\d{4})\b", | |
lines[0], | |
re.IGNORECASE | |
) | |
if not header_match: | |
response = "Lỗi: Dòng đầu phải có định dạng 'Xổ số <Đài> [Thứ X/Chủ Nhật] DD-MM-YYYY'. Ví dụ: 'Xổ số Bình Định 03-07-2025'" | |
if is_gradio: | |
return response | |
await bot.post_message(chat_id, [response], is_gradio) | |
return False | |
await bot.cache.clear(type="lottery") | |
logger.debug(f"[LoadLotteryCommand] Cleared lottery cache") | |
override = bool(re.search(r"\boverride\b", result_text.lower(), re.IGNORECASE)) | |
if override: | |
result_text = re.sub(r"\boverride\b", "", result_text, flags=re.IGNORECASE).strip() | |
response = f"Đang nạp kết quả xổ số {'(override)' if override else ''}..." | |
if not is_gradio: | |
await bot.post_message(chat_id, [response], is_gradio) | |
lottery_core = bot.lottery_core | |
result = await lottery_core.load_lottery(result_text, override=override) | |
if "Lỗi" in result: | |
response = result | |
if is_gradio: | |
return response | |
await bot.post_message(chat_id, [response], is_gradio) | |
return False | |
cache_key = f"lottery_load:{user_id}:{hashlib.sha256(result_text.encode()).hexdigest()}" | |
await bot.cache.set( | |
cache_key, | |
result, | |
type="lottery", | |
ttl=Settings.CACHE_TTL or 86400 | |
) | |
logger.debug(f"[LoadLotteryCommand] Cached result: key={cache_key}") | |
response = f"Kết quả xổ số đã được nạp thành công: {result}" | |
if is_gradio: | |
return response | |
await bot.post_message(chat_id, [response], is_gradio) | |
return True | |
except asyncio.TimeoutError: | |
logger.error("[load_lottery] Timeout, timeout=180s") | |
response = "Lỗi: Timeout khi tải kết quả" | |
if is_gradio: | |
return response | |
await bot.post_message(chat_id, [response], is_gradio) | |
return False | |
except Exception as e: | |
logger.error(f"[load_lottery] Error: {str(e)}", exc_info=True) | |
response = f"Lỗi: {str(e)}" | |
if is_gradio: | |
return response | |
await bot.post_message(chat_id, [response], is_gradio) | |
return False | |
class SaveLotteryVectorsCommand(CommandHandler): | |
async def handle(self, bot, chat_id: int, user_id: int, text: str, is_gradio: bool = False) -> Union[bool, str]: | |
logger.debug(f"[SaveLotteryVectorsCommand] chat_id={chat_id}, user_id={user_id}, text={text[:50]}..., is_gradio={is_gradio}") | |
try: | |
# Kiểm tra xác thực | |
is_authenticated, message = await bot.check_user_auth(user_id, is_gradio) | |
if not is_authenticated: | |
logger.debug(f"[SaveLotteryVectorsCommand] Authentication failed for user_id={user_id}: {message}") | |
if is_gradio: | |
return message | |
await bot.post_message(chat_id, [message], is_gradio) | |
return False | |
response = "Lỗi: Tính năng lưu vector xổ số đã bị tắt." | |
if is_gradio: | |
return response | |
await bot.post_message(chat_id, [response], is_gradio) | |
return False | |
except Exception as e: | |
logger.error(f"[SaveLotteryVectorsCommand] Error: {str(e)}", exc_info=True) | |
response = f"Lỗi: {str(e)}" | |
if is_gradio: | |
return response | |
await bot.post_message(chat_id, [response], is_gradio) | |
return False | |
class AddLotteryCommand(CommandHandler): | |
async def handle(self, bot, chat_id: int, user_id: int, text: str, is_gradio: bool = False) -> Union[bool, str]: | |
logger.debug(f"[AddLotteryCommand] chat_id={chat_id}, user_id={user_id}, text={text[:50]}..., is_gradio={is_gradio}") | |
try: | |
# Kiểm tra xác thực | |
is_authenticated, message = await bot.check_user_auth(user_id, is_gradio) | |
if not is_authenticated: | |
logger.debug(f"[AddLotteryCommand] Authentication failed for user_id={user_id}: {message}") | |
if is_gradio: | |
return message | |
await bot.post_message(chat_id, [message], is_gradio) | |
return False | |
async with asyncio.timeout(90): | |
result_text = "" | |
if is_gradio: | |
result_text = text.split(maxsplit=1)[1] if len(text.split(maxsplit=1)) > 1 else "" | |
if not result_text: | |
response = "Cung cấp văn bản kết quả xổ số sau lệnh /add_lottery." | |
if is_gradio: | |
return response | |
await bot.post_message(chat_id, [response], is_gradio) | |
return False | |
else: | |
if text.strip() == "/add_lottery": | |
if not bot.message or not bot.message.message.get("reply_to_message"): | |
response = "Trả lời tin nhắn chứa kết quả xổ số hoặc cung cấp văn bản." | |
if is_gradio: | |
return response | |
await bot.post_message(chat_id, [response], is_gradio) | |
return False | |
result_text = bot.message.message["reply_to_message"].get("text", "") | |
else: | |
result_text = text.split(maxsplit=1)[1] if len(text.split(maxsplit=1)) > 1 else "" | |
if not result_text: | |
response = "Cung cấp văn bản kết quả xổ số." | |
if is_gradio: | |
return response | |
await bot.post_message(chat_id, [response], is_gradio) | |
return False | |
response = "Đang thêm kết quả xổ số..." | |
if not is_gradio: | |
await bot.post_message(chat_id, [response], is_gradio) | |
lottery_core = bot.lottery_core | |
result = await lottery_core.add_lottery_result(result_text) | |
response = result | |
await bot.cache.clear(type="lottery") | |
if is_gradio: | |
return response | |
await bot.post_message(chat_id, [response], is_gradio) | |
return True | |
except asyncio.TimeoutError: | |
logger.error("[add_lottery] Timeout, timeout=90s") | |
response = "Lỗi: Timeout khi thêm kết quả" | |
if is_gradio: | |
return response | |
await bot.post_message(chat_id, [response], is_gradio) | |
return False | |
except Exception as e: | |
logger.error(f"[add_lottery] Error: {str(e)}", exc_info=True) | |
response = f"Lỗi: {str(e)}" | |
if is_gradio: | |
return response | |
await bot.post_message(chat_id, [response], is_gradio) | |
return False | |
class MigrateLotteryDataCommand(CommandHandler): | |
async def handle(self, bot, chat_id: int, user_id: int, text: str, is_gradio: bool = False) -> Union[bool, str]: | |
logger.debug(f"[MigrateLotteryDataCommand] chat_id={chat_id}, user_id={user_id}, text={text[:50]}..., is_gradio={is_gradio}") | |
try: | |
# Kiểm tra xác thực | |
is_authenticated, message = await bot.check_user_auth(user_id, is_gradio) | |
if not is_authenticated: | |
logger.debug(f"[MigrateLotteryDataCommand] Authentication failed for user_id={user_id}: {message}") | |
if is_gradio: | |
return message | |
await bot.post_message(chat_id, [message], is_gradio) | |
return False | |
async with asyncio.timeout(180): | |
response = "Đang chuyển dữ liệu xổ số..." | |
if not is_gradio: | |
await bot.post_message(chat_id, [response], is_gradio) | |
lottery_core = bot.lottery_core | |
result = await lottery_core.migrate_lottery_data() | |
response = result | |
await bot.cache.clear(type="lottery") | |
if is_gradio: | |
return response | |
await bot.post_message(chat_id, [response], is_gradio) | |
return True | |
except asyncio.TimeoutError: | |
logger.error("[migrate_data] Timeout, timeout=180s") | |
response = "Lỗi: Timeout khi chuyển dữ liệu" | |
if is_gradio: | |
return response | |
await bot.post_message(chat_id, [response], is_gradio) | |
return False | |
except Exception as e: | |
logger.error(f"[migrate_data] Error: {str(e)}", exc_info=True) | |
response = f"Lỗi: {str(e)}" | |
if is_gradio: | |
return response | |
await bot.post_message(chat_id, [response], is_gradio) | |
return False | |
class CheckDatesCommand(CommandHandler): | |
async def handle(self, bot, chat_id: int, user_id: int, text: str, is_gradio: bool = False) -> Union[bool, str]: | |
logger.debug(f"[CheckDatesCommand] chat_id={chat_id}, user_id={user_id}, text={text[:50]}..., is_gradio={is_gradio}") | |
try: | |
# Kiểm tra xác thực | |
is_authenticated, message = await bot.check_user_auth(user_id, is_gradio) | |
if not is_authenticated: | |
logger.debug(f"[CheckDatesCommand] Authentication failed for user_id={user_id}: {message}") | |
if is_gradio: | |
return message | |
await bot.post_message(chat_id, [message], is_gradio) | |
return False | |
timeout = Settings.QUERY_TIMEOUT if Settings.QUERY_TIMEOUT is not None else 90 | |
async with asyncio.timeout(timeout): | |
response = "Đang kiểm tra dữ liệu kết quả xổ số trong Firestore..." | |
logger.info(f"[CheckDatesCommand] Sending initial response: {response}") | |
if not is_gradio: | |
await bot.post_message(chat_id, [response], is_gradio) | |
if not hasattr(bot.firestore_lottery, 'async_db') or not bot.firestore_lottery.async_db: | |
response = "Lỗi: Firestore không được khởi tạo. Vui lòng kiểm tra cấu hình." | |
logger.error(f"[CheckDatesCommand] Firestore not initialized for chat_id={chat_id}") | |
if is_gradio: | |
return response | |
await bot.post_message(chat_id, [response], is_gradio) | |
return False | |
for dai, config in Settings.DAI_CONFIG.items(): | |
if "days" not in config: | |
response = f"Lỗi cấu hình: Thiếu 'days' cho đài {dai}" | |
logger.error(f"[CheckDatesCommand] Missing 'days' in config for dai={dai}, chat_id={chat_id}") | |
if is_gradio: | |
return response | |
await bot.post_message(chat_id, [response], is_gradio) | |
return False | |
logger.debug(f"DAI_CONFIG: {Settings.DAI_CONFIG}") | |
records_by_dai: Dict[str, List[dict]] = defaultdict(list) | |
for dai in Settings.DAI_CONFIG.keys(): | |
logger.debug(f"[CheckDatesCommand] Querying Firestore for dai={dai}, limit=200") | |
start_time = time.time() | |
results = await bot.firestore_lottery.get_lottery_by_dai(dai=dai, limit=200, select_fields=["ngay", "dai", "giai"]) | |
logger.info(f"[CheckDatesCommand] Retrieved {len(results)} records for dai={dai} in {time.time() - start_time:.2f} seconds") | |
for data in results: | |
logger.debug(f"[CheckDatesCommand] Record for {dai}: {data}") | |
ngay = data.get('ngay') | |
if ngay: | |
try: | |
parsed_date = datetime.strptime(ngay, "%d-%m-%Y") | |
ngay = parsed_date.strftime("%d-%m-%Y") | |
records_by_dai[dai].append({"ngay": ngay, "data": data}) | |
logger.debug(f"[CheckDatesCommand] Parsed: ngay={ngay}, dai={dai}") | |
except ValueError: | |
logger.warning(f"[CheckDatesCommand] Invalid date format: ngay={ngay}, dai={dai}, record={data}") | |
continue | |
else: | |
logger.warning(f"[CheckDatesCommand] Missing ngay: {data}") | |
if not any(records_by_dai.values()): | |
response = "Không tìm thấy tài liệu kết quả xổ số trong Firestore cho bất kỳ đài nào." | |
logger.error(f"[CheckDatesCommand] No records found for chat_id={chat_id}: {response}") | |
if is_gradio: | |
return response | |
await bot.post_message(chat_id, [response], is_gradio) | |
return False | |
results = [] | |
existing_dates = defaultdict(set) | |
max_records_per_dai = Settings.MAX_RECORDS_PER_DAI or 20 | |
for dai in Settings.DAI_CONFIG.keys(): | |
sorted_records = sorted( | |
records_by_dai.get(dai, []), | |
key=lambda x: datetime.strptime(x["ngay"], "%d-%m-%Y"), | |
reverse=True | |
) | |
for record in sorted_records[:max_records_per_dai]: | |
ngay = record["ngay"] | |
results.append({"ngay": ngay, "dai": dai}) | |
existing_dates[dai].add(ngay) | |
if not results: | |
response = "Không tìm thấy bản ghi hợp lệ nào sau khi lọc." | |
logger.error(f"[CheckDatesCommand] No valid records after filtering for chat_id={chat_id}") | |
if is_gradio: | |
return response | |
await bot.post_message(chat_id, [response], is_gradio) | |
return False | |
results.sort(key=lambda x: datetime.strptime(x["ngay"], "%d-%m-%Y"), reverse=True) | |
missing_dates = [] | |
today = datetime.now(ZoneInfo("Asia/Ho_Chi_Minh")).date() | |
date_window_days = Settings.DATE_WINDOW_DAYS or 30 | |
for dai, config in Settings.DAI_CONFIG.items(): | |
expected_days = config["days"] | |
for i in range(date_window_days + 1): | |
check_date = today - timedelta(days=i) | |
check_day = check_date.weekday() | |
if check_day in expected_days: | |
formatted_date = check_date.strftime("%d-%m-%Y") | |
if dai not in existing_dates or formatted_date not in existing_dates[dai]: | |
missing_dates.append(f"Ngày thiếu: {formatted_date} (đài: {dai})") | |
response_lines = [f"Danh sách kết quả xổ số (tối đa {max_records_per_dai} bản ghi gần nhất mỗi đài):"] | |
response_lines.extend(f"ngay={r['ngay']}, dai={r['dai']}" for r in results[:50]) | |
if len(results) > 50: | |
response_lines.append(f"<br>(Lưu ý: Chỉ hiển thị 50 bản ghi đầu tiên, tổng cộng {len(results)} bản ghi.)" if is_gradio else f"\n(Lưu ý: Chỉ hiển thị 50 bản ghi đầu tiên, tổng cộng {len(results)} bản ghi.)") | |
if missing_dates: | |
response_lines.append("<br>Các ngày thiếu:" if is_gradio else "\nCác ngày thiếu:") | |
response_lines.extend(missing_dates) | |
else: | |
response_lines.append(f"<br>Không có ngày thiếu trong {date_window_days} ngày gần nhất." if is_gradio else f"\nKhông có ngày thiếu trong {date_window_days} ngày gần nhất.") | |
response = "<br>".join(response_lines) if is_gradio else "\n".join(response_lines) | |
logger.info(f"[CheckDatesCommand] Final response for chat_id={chat_id}: {response[:100]}...") | |
if is_gradio: | |
return response | |
await bot.post_message(chat_id, [response], is_gradio) | |
return True | |
except asyncio.TimeoutError: | |
logger.error(f"[CheckDatesCommand] Timeout occurred for chat_id={chat_id}") | |
response = "Lỗi: Hết thời gian xử lý. Vui lòng thử lại sau." | |
if is_gradio: | |
return response | |
await bot.post_message(chat_id, [response], is_gradio) | |
return False | |
except Exception as e: | |
logger.error(f"[CheckDatesCommand] Error for chat_id={chat_id}: {str(e)}\n{traceback.format_exc()}") | |
response = "Lỗi hệ thống khi kiểm tra dữ liệu. Vui lòng thử lại sau." | |
if is_gradio: | |
return response | |
await bot.post_message(chat_id, [response], is_gradio) | |
return False | |
class ScheduleCommand(CommandHandler): | |
async def handle(self, bot, chat_id: int, user_id: int, text: str, is_gradio: bool = False) -> Union[bool, str]: | |
logger.debug(f"[ScheduleCommand] chat_id={chat_id}, user_id={user_id}, text={text[:50]}..., is_gradio={is_gradio}") | |
try: | |
# Kiểm tra xác thực | |
is_authenticated, message = await bot.check_user_auth(user_id, is_gradio) | |
if not is_authenticated: | |
logger.debug(f"[ScheduleCommand] Authentication failed for user_id={user_id}: {message}") | |
if is_gradio: | |
return message | |
await bot.post_message(chat_id, [message], is_gradio) | |
return False | |
async with asyncio.timeout(90): | |
parts = text.split(maxsplit=1) | |
dai = parts[1].lower() if len(parts) > 1 else None | |
if dai and dai not in Settings.DAI_CONFIG: | |
response = f"Đài không hợp lệ: {dai}. Dùng: {', '.join(Settings.DAI_CONFIG.keys())}" | |
if is_gradio: | |
return response | |
await bot.post_message(chat_id, [response], is_gradio) | |
return False | |
lottery_core = bot.lottery_core | |
result = await lottery_core.get_schedule(dai) | |
response = result | |
if is_gradio: | |
return response | |
await bot.post_message(chat_id, [response], is_gradio) | |
return True | |
except asyncio.TimeoutError: | |
logger.error("[schedule] Timeout, timeout=90s") | |
response = "Lỗi: Timeout khi lấy lịch" | |
if is_gradio: | |
return response | |
await bot.post_message(chat_id, [response], is_gradio) | |
return False | |
except Exception as e: | |
logger.error(f"[schedule] Error: {str(e)}", exc_info=True) | |
response = f"[schedule] {str(e)}" | |
if is_gradio: | |
return response | |
await bot.post_message(chat_id, [response], is_gradio) | |
return False | |
class LotteryPositionCommand(CommandHandler): | |
async def handle(self, bot, chat_id: int, user_id: int, text: str, is_gradio: bool = False) -> str: | |
logger.debug(f"[LotteryPositionCommand] chat_id={chat_id}, user_id={user_id}, text={text[:50]}..., is_gradio={is_gradio}") | |
try: | |
# Kiểm tra xác thực | |
is_authenticated, message = await bot.check_user_auth(user_id, is_gradio) | |
if not is_authenticated: | |
logger.debug(f"[LotteryPositionCommand] Authentication failed for user_id={user_id}: {message}") | |
return message | |
async with asyncio.timeout(60): | |
parts = text.split() | |
if len(parts) != 4: | |
response = ( | |
"Sai định dạng. Dùng: /lottery_position <đài> <ngày> <số_bản_ghi><br>" | |
"Ví dụ: /lottery_position xskh 18-05-2025 7<br>" | |
"- <đài>: xskh<br>" | |
"- <ngày>: DD-MM-YYYY<br>" | |
"- <số_bản_ghi>: Số nguyên (VD: 7)" | |
) if is_gradio else ( | |
"Sai định dạng. Dùng: /lottery_position <đài> <ngày> <số_bản_ghi>\n" | |
"Ví dụ: /lottery_position xskh 18-05-2025 7\n" | |
"- <đài>: xskh\n" | |
"- <ngày>: DD-MM-YYYY\n" | |
"- <số_bản_ghi>: Số nguyên (VD: 7)" | |
) | |
return response | |
dai = parts[1].lower() | |
date = parts[2].replace("/", "-") | |
num_records = parts[3] | |
if dai not in Settings.DAI_CONFIG: | |
response = f"Đài không hợp lệ: {dai}. Dùng: {', '.join(Settings.DAI_CONFIG.keys())}" | |
return response | |
expected_days = Settings.DAI_CONFIG[dai]["days"] | |
day_name = ["Thứ Hai", "Thứ Ba", "Thứ Tư", "Thứ Năm", "Thứ Sáu", "Thứ Bảy", "Chủ Nhật"] | |
try: | |
date_obj = datetime.strptime(date, "%d-%m-%Y") | |
if date_obj.weekday() not in expected_days: | |
response = f"Lỗi: Đài {dai} chỉ quay vào {', '.join(day_name[d] for d in expected_days)}. Ngày {date} là {day_name[date_obj.weekday()]}." | |
return response | |
except ValueError: | |
response = "Định dạng ngày không hợp lệ: DD-MM-YYYY." | |
return response | |
try: | |
num_records_int = int(num_records) | |
if num_records_int < 1: | |
response = "Số bản ghi phải lớn hơn hoặc bằng 1." | |
return response | |
except ValueError: | |
response = "Số bản ghi phải là số nguyên." | |
return response | |
logger.info(f"[LotteryPositionCommand] Gọi analyze_position: dai={dai}, date={date}, num_records={num_records}") | |
lottery_analysis = bot.lottery_analysis | |
result = await lottery_analysis.analyze_position(dai, date, num_records) | |
if not result: | |
response = "Không có kết quả dự đoán." | |
return response | |
if isinstance(result, (list, tuple)): | |
response = "<br>".join(str(item).strip() for item in result if item) if is_gradio else "\n".join(str(item).strip() for item in result if item) | |
else: | |
response = str(result).strip() | |
if not response: | |
response = "Không có nội dung dự đoán." | |
return response | |
except asyncio.TimeoutError: | |
logger.error("[LotteryPositionCommand] Timeout, timeout=60s") | |
response = "Lỗi: Timeout khi dự đoán" | |
return response | |
except Exception as e: | |
logger.error(f"[LotteryPositionCommand] Error: {str(e)}", exc_info=True) | |
response = f"Lỗi: {str(e)}" | |
return response | |
class DefaultCommand(CommandHandler): | |
async def handle(self, bot, chat_id: int, user_id: int, text: str, is_gradio: bool = False) -> str: | |
logger.debug(f"[DefaultCommand] chat_id={chat_id}, user_id={user_id}, text={text[:50]}..., is_gradio={is_gradio}") | |
try: | |
if bot.gemini_mode.get(user_id, False): | |
cache_key = f"{user_id}:{text}:Gem" | |
cache_type = "gemini" | |
cached_response = await bot.cache.get(cache_key, cache_type) | |
if cached_response: | |
logger.debug(f"[DefaultCommand] Cache hit: key={cache_key}, type={cache_type}") | |
cached_metadata = await bot.cache.get(f"{cache_key}:metadata", cache_type) or {} | |
used_context = cached_metadata.get("used_context", False) if isinstance(cached_metadata, dict) else False | |
prefix = "**Gemini context**" if used_context else "**Gemini**" | |
return f"{prefix} {cached_response}" | |
await bot.db.save_chat_history(user_id=str(user_id), prompt=text) | |
logger.debug(f"[DefaultCommand] Saved to chat history: user_id={user_id}, text={text[:50]}...") | |
response_parts, used_context = await bot.gemini.generate_response( | |
prompt=text, | |
user_id=str(user_id), | |
is_gradio=is_gradio | |
) | |
response = response_parts[0] # Chỉ lấy phần đầu tiên | |
prefix = "**Gemini context**" if used_context else "**Gemini**" | |
formatted_response = f"{prefix} {response}" | |
await bot.cache.set(cache_key, response, cache_type, ttl=Settings.CACHE_TTL or 86400) | |
await bot.cache.set(f"{cache_key}:metadata", {"used_context": used_context}, cache_type, ttl=Settings.CACHE_TTL or 86400) | |
await bot.db.save_chat_history(user_id=str(user_id), prompt=formatted_response) | |
logger.debug(f"[DefaultCommand] Sending response: {formatted_response[:100]}...") | |
return formatted_response | |
cache_key = f"{user_id}:{text}" | |
cache_type = "default" | |
cached_response = await bot.cache.get(cache_key, cache_type) | |
if cached_response: | |
logger.debug(f"[DefaultCommand] Cache hit: key={cache_key}, type={cache_type}") | |
return cached_response | |
doc_id = f"gradio_{user_id}" if is_gradio else str(user_id) | |
user_doc = await bot.db.get(data_type="users", doc_id=doc_id) | |
training_data = user_doc.get("training_data", []) if user_doc else [] | |
if not training_data: | |
logger.debug("[DefaultCommand] No training data found") | |
response = f"Không có dữ liệu huấn luyện liên quan đến '{text}'. Vui lòng dùng /train để thêm dữ liệu." | |
await bot.cache.set(cache_key, response, cache_type, ttl=Settings.CACHE_TTL or 86400) | |
await bot.db.save_chat_history(user_id=str(user_id), prompt=text) | |
await bot.db.save_chat_history(user_id=str(user_id), prompt=response) | |
return response | |
best_match = None | |
best_score = 0 | |
for item in training_data: | |
if isinstance(item, dict) and "key" in item and "content" in item and item.get("status") == "completed": | |
score = fuzz.partial_ratio(text.lower(), item["key"].lower()) | |
if score > best_score and score >= 80: | |
best_score = score | |
best_match = item["content"] | |
logger.debug(f"[DefaultCommand] Best match: score={best_score}, context={best_match[:100] if best_match else 'None'}...") | |
if best_match: | |
response = best_match | |
else: | |
response = f"Không có dữ liệu huấn luyện liên quan đến '{text}'. Vui lòng dùng /train để thêm dữ liệu." | |
await bot.cache.set(cache_key, response, cache_type, ttl=Settings.CACHE_TTL or 86400) | |
await bot.db.save_chat_history(user_id=str(user_id), prompt=text) | |
await bot.db.save_chat_history(user_id=str(user_id), prompt=response) | |
logger.debug(f"[DefaultCommand] Sending response: {response[:100]}...") | |
return response | |
except Exception as e: | |
logger.error(f"[DefaultCommand] Error: {str(e)}", exc_info=True) | |
response = "Không có thông tin để trả lời. Vui lòng thử lại." | |
await bot.db.save_chat_history(user_id=str(user_id), prompt=text) | |
await bot.db.save_chat_history(user_id=str(user_id), prompt=response) | |
return response | |
class RegisterCommand(CommandHandler): | |
async def handle(self, bot, chat_id: int, user_id: int, text: str, is_gradio: bool = True) -> str: | |
logger.debug(f"[RegisterCommand] Handling: chat_id={chat_id}, user_id={user_id}, text={text[:50]}...") | |
parts = text.split() | |
if len(parts) != 3 or parts[0].lower() != "/register": | |
logger.info(f"[RegisterCommand] Invalid syntax: {text}") | |
return "Cú pháp: /register <email/tên_người_dùng> <mật_khẩu>" | |
identifier = parts[1] | |
user_password = parts[2] | |
# Kiểm tra user_id, nếu None thì tạo mới | |
if user_id is None: | |
combined = f"{identifier}:{user_password}" | |
user_id = int(hashlib.sha256(combined.encode()).hexdigest(), 16) % 10000000 | |
logger.debug(f"[RegisterCommand] Generated user_id={user_id} for identifier={identifier}") | |
doc_id = f"gradio_{user_id}" if is_gradio else str(user_id) | |
try: | |
password_hash = bcrypt.hashpw(user_password.encode(), bcrypt.gensalt()).decode() | |
except Exception as e: | |
logger.error(f"[RegisterCommand] Bcrypt error: {str(e)}") | |
return "Lỗi: Không thể mã hóa mật khẩu. Vui lòng thử lại." | |
try: | |
# Kiểm tra trùng lặp identifier | |
existing_user = await bot.db.get_user_by_identifier(identifier) | |
if existing_user: | |
logger.info(f"[RegisterCommand] Duplicate identifier: {identifier}") | |
return f"Email hoặc tên người dùng '{identifier}' đã được sử dụng." | |
user_data = { | |
"identifier": identifier, | |
"password_hash": password_hash, | |
"created_at": datetime.now().isoformat(), | |
"authenticated": False, | |
"last_auth": None, | |
"training_data": [], | |
"gemini_mode": False | |
} | |
if is_gradio: | |
user_data["type"] = "gradio" | |
else: | |
user_data["timestamp"] = datetime.now().isoformat() | |
user_data["is_auth"] = False | |
success = await bot.db.set( | |
user_data, | |
data_type="users", | |
doc_id=doc_id | |
) | |
if success: | |
logger.info(f"[RegisterCommand] Registered user_id={user_id}, identifier={identifier}, is_gradio={is_gradio}") | |
return f"Đăng ký thành công! user_id={user_id}. Đăng nhập bằng identifier và mật khẩu, sau đó dùng /auth <mật_khẩu_bot> để xác thực lệnh." | |
else: | |
logger.error(f"[RegisterCommand] Failed to save user_id={user_id}") | |
return "Lỗi: Không thể lưu thông tin đăng ký." | |
except Exception as e: | |
logger.error(f"[RegisterCommand] Error: user_id={user_id}, error={str(e)}") | |
return f"Lỗi khi đăng ký: {str(e)}" | |
class AuthCommand(CommandHandler): | |
async def handle(self, bot, chat_id: int, user_id: int, text: str, is_gradio: bool = False) -> Union[bool, str]: | |
logger.debug(f"[AuthCommand] chat_id={chat_id}, user_id={user_id}, text={text[:50]}..., is_gradio={is_gradio}") | |
parts = text.split(maxsplit=1) | |
if len(parts) != 2 or parts[0].lower() != "/auth": | |
response = "Cú pháp: /auth <mật_khẩu_bot>" | |
if is_gradio: | |
return response | |
await bot.post_message(chat_id, [response], is_gradio) | |
return False | |
password = parts[1] | |
is_authenticated = await bot.authenticate_user(user_id, password, is_gradio=is_gradio) | |
if is_authenticated: | |
response = "Xác thực thành công! Bạn có thể sử dụng các lệnh nâng cao." | |
else: | |
response = "Xác thực thất bại: Mật khẩu không đúng." | |
if is_gradio: | |
return response | |
await bot.post_message(chat_id, [response], is_gradio) | |
return is_authenticated | |
class LogoutCommand: | |
async def handle(self, bot: "Bot", chat_id: int, user_id: int, text: str, is_gradio: bool = False) -> str: | |
logger.debug(f"[LogoutCommand] chat_id={chat_id}, user_id={user_id}, is_gradio={is_gradio}") | |
doc_id = f"gradio_{user_id}" if is_gradio else str(user_id) | |
try: | |
await bot.db.set( | |
{"authenticated": False, "bot_authenticated": False}, | |
data_type="users", | |
doc_id=doc_id, | |
merge=True | |
) | |
logger.info(f"[LogoutCommand] User logged out: doc_id={doc_id}") | |
return "Đăng xuất thành công. Vui lòng đăng nhập lại để tiếp tục." | |
except Exception as e: | |
logger.error(f"[LogoutCommand] Error: {str(e)}") | |
return "Lỗi khi đăng xuất, vui lòng thử lại." | |
async def train_data(db: FirestoreDB, search: Search, param: str, user_id: str) -> dict: | |
logger.debug(f"[train_data] user_id={user_id}, param={param[:50]}...") | |
result = {"status": "success", "message": []} | |
try: | |
if any(keyword in param.lower() for keyword in ["xổ số", "dự đoán", "bản ghi lịch sử", "chu kỳ tối ưu", "cặp số", "lottery"]): | |
result["status"] = "error" | |
result["message"].append("Lỗi: Không thể huấn luyện dữ liệu liên quan đến xổ số. Sử dụng /lottery hoặc /lottery_position.") | |
return result | |
doc_id = f"gradio_{user_id}" | |
training_data = [] | |
doc = await db.get(data_type="users", doc_id=doc_id) | |
if doc and "training_data" in doc: | |
training_data = doc["training_data"] | |
param_lower = param.lower() | |
if any(fuzz.partial_ratio(param_lower, item.get("key", "").lower()) > 95 for item in training_data): | |
logger.debug(f"[train_data] Bỏ qua dữ liệu trùng lặp: user_id={user_id}, param={param[:50]}...") | |
return result | |
key = param[:100] | |
content = param | |
training_item = { | |
"key": key, | |
"content": content, | |
"type": "text", | |
"context_type": "general" if "quán" in param_lower or "cơm" in param_lower else "other", | |
"status": "completed", | |
"timestamp": datetime.now().isoformat() | |
} | |
training_data.append(training_item) | |
await db.set( | |
{"training_data": training_data}, | |
data_type="users", | |
doc_id=doc_id, | |
merge=True | |
) | |
await search.add(text=content, data_type="default") | |
logger.info(f"[train_data] Trained: user_id={user_id}, key={key[:50]}...") | |
return result | |
except Exception as e: | |
logger.error(f"[train_data] Error: user_id={user_id}, error={str(e)}") | |
result["status"] = "error" | |
result["message"].append(f"Lỗi: {str(e)}") | |
return result | |
async def train_json(lottery_core: LotteryCore, file_path: str) -> dict: | |
try: | |
async with asyncio.timeout(300): | |
status = "success" | |
message = [] | |
logger.debug(f"[train_json] Bắt đầu: file_path={file_path}") | |
if not os.path.exists(file_path): | |
logger.error(f"[train_json] Tệp không tồn tại: {file_path}") | |
return {"status": "error", "message": f"Tệp {file_path} không tồn tại"} | |
if not os.access(file_path, os.R_OK): | |
logger.error(f"[train_json] Không có quyền đọc: {file_path}") | |
return {"status": "error", "message": f"Không có quyền đọc tệp {file_path}"} | |
if os.path.getsize(file_path) == 0: | |
logger.error(f"[train_json] Tệp rỗng: {file_path}") | |
return {"status": "error", "message": f"Tệp {file_path} rỗng"} | |
try: | |
with open(file_path, 'r', encoding='utf-8') as f: | |
data = json.load(f) | |
if not isinstance(data, (list, dict)): | |
logger.error(f"[train_json] Dữ liệu JSON không hợp lệ: {file_path}") | |
return {"status": "error", "message": f"Dữ liệu trong {file_path} không phải danh sách hoặc đối tượng JSON"} | |
except json.JSONDecodeError as e: | |
logger.error(f"[train_json] Lỗi JSON: {str(e)}") | |
return {"status": "error", "message": f"Lỗi định dạng JSON: {str(e)}"} | |
if isinstance(data, list): | |
for item in data: | |
if not (item.get("dai") and item.get("ngay") and item.get("giai")): | |
logger.error(f"[train_json] Dữ liệu xổ số thiếu trường: {item}") | |
return {"status": "error", "message": f"Dữ liệu xổ số thiếu trường 'dai', 'ngay', hoặc 'giai'"} | |
try: | |
datetime.strptime(item["ngay"], "%d-%m-%Y") | |
except ValueError: | |
logger.error(f"[train_json] Định dạng ngày không hợp lệ: {item['ngay']}") | |
return {"status": "error", "message": f"Định dạng ngày không hợp lệ: {item['ngay']}"} | |
elif isinstance(data, dict): | |
if not (data.get("dai") and data.get("ngay") and data.get("giai")): | |
logger.error(f"[train_json] Dữ liệu xổ số thiếu trường: {data}") | |
return {"status": "error", "message": f"Dữ liệu xổ số thiếu trường 'dai', 'ngay', hoặc 'giai'"} | |
try: | |
datetime.strptime(data["ngay"], "%d-%m-%Y") | |
except ValueError: | |
logger.error(f"[train_json] Định dạng ngày không hợp lệ: {data['ngay']}") | |
return {"status": "error", "message": f"Định dạng ngày không hợp lệ: {data['ngay']}"} | |
result = await lottery_core.load_from_json(file_path) | |
if not result or "error" in result: | |
message.append(result.get("error", "Lỗi khi nạp dữ liệu xổ số")) | |
status = "error" | |
else: | |
message.append(result) | |
logger.info(f"[train_json] Hoàn tất: message={message}") | |
return {"status": status, "message": " | ".join(message)} | |
except asyncio.TimeoutError: | |
logger.error("[train_json] Timeout khi đào tạo, timeout=300s") | |
return {"status": "error", "message": "Timeout khi đào tạo"} | |
except Exception as e: | |
logger.error(f"[train_json] Lỗi: error={str(e)}") | |
return {"status": "error", "message": f"Lỗi khi đào tạo: {str(e)}"} |