Spaces:
Runtime error
Runtime error
File size: 15,042 Bytes
6bd55ed d474a1a 81e8c42 d474a1a 80a2df0 6bd55ed 5237cb4 6bd55ed 5237cb4 d474a1a 80a2df0 5237cb4 80a2df0 5487919 80a2df0 5237cb4 80a2df0 5d257c3 5487919 88b33ac 5487919 88b33ac 80a2df0 88b33ac 80a2df0 cdf1ee0 0482d0d 80a2df0 10013bf 80a2df0 5487919 80a2df0 10013bf 5487919 10013bf 80a2df0 5487919 80a2df0 5487919 80a2df0 10013bf 5487919 10013bf 80a2df0 10013bf 5487919 10013bf 5487919 10013bf 5487919 10013bf 3549ed0 0482d0d 5487919 0482d0d 5487919 0482d0d 80a2df0 5237cb4 80a2df0 5237cb4 80a2df0 5237cb4 d474a1a 5237cb4 80a2df0 5487919 80a2df0 5237cb4 80a2df0 5237cb4 80a2df0 5237cb4 80a2df0 5237cb4 80a2df0 5237cb4 80a2df0 5237cb4 80a2df0 5237cb4 80a2df0 d474a1a 80a2df0 d474a1a 80a2df0 325a4b2 80a2df0 325a4b2 80a2df0 47a1801 325a4b2 80a2df0 325a4b2 80a2df0 d474a1a 5237cb4 4d7efa6 5237cb4 4d7efa6 d474a1a 6bd55ed e6a123f 325a4b2 a288f93 325a4b2 f2b77b2 d474a1a 0482d0d d474a1a 80a2df0 5237cb4 d474a1a 80a2df0 5237cb4 80a2df0 5237cb4 80a2df0 5237cb4 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 |
from fastapi import FastAPI, Request
from pydantic import BaseModel
from config.settings import Settings
from src.embedding import Embedding
from src.search import Search
from src.firestore_db import FirestoreDB
from src.gemini import Gemini
from src.cache import Cache
from src.lottery import Lottery
from src.logger import logger
import httpx
import json
from datetime import datetime
from typing import Dict, Optional
from functools import lru_cache
logger.info("Bắt đầu import các module")
app = FastAPI()
logger.info("Khởi tạo FastAPI hoàn tất")
# Tái sử dụng AsyncClient toàn cục
http_client = httpx.AsyncClient()
logger.info("Khởi tạo httpx.AsyncClient hoàn tất")
class CommandHandler:
"""Base class for command handlers."""
async def handle(self, bot, chat_id: int, user_id: int, text: str) -> None:
raise NotImplementedError
class StartCommand(CommandHandler):
async def handle(self, bot, chat_id: int, user_id: int, text: str) -> None:
await bot.send_message(chat_id, "Chào! Gửi câu hỏi hoặc dùng /help để xem các lệnh.")
class HelpCommand(CommandHandler):
async def handle(self, bot, chat_id: int, user_id: int, text: str) -> None:
help_text = (
"Hướng dẫn sử dụng @CotienBot:\n"
"- /start: Khởi động bot.\n"
"- /help: Hiển thị hướng dẫn này.\n"
"- /auth <mật_khẩu>: Xác thực để sử dụng các lệnh nhạy cảm.\n"
"- /train: Nhập dữ liệu đào tạo (FAISS và xổ số).\n"
"- /Gemini: Chuyển sang chế độ Gemini AI.\n"
"- /exit: Thoát chế độ Gemini.\n"
"- /lottery <đài> <ngày_bắt_đầu> <ngày_kết_thúc>: Phân tích tần suất, top bộ số (YYYY-MM-DD).\n"
"- /lottery_position <đài> <ngày> <số_ngày_trước> <độ_khớp>: Phân tích lồng quay, dự đoán (ngày DD-MM-YYYY).\n"
"- /load_lottery: Tải dữ liệu xổ số từ file JSON (data/lottery_data.json).\n"
"- Gửi câu hỏi thông thường để tìm kiếm thông tin."
)
cache_key = f"{user_id}:{text}"
bot.cache.set(cache_key, help_text)
await bot.send_message(chat_id, help_text)
class AuthCommand(CommandHandler):
async def handle(self, bot, chat_id: int, user_id: int, text: str) -> None:
if await bot.is_user_authenticated(user_id):
await bot.send_message(chat_id, "Bạn đã xác thực rồi!")
return
password = text.split(" ", 1)[1] if len(text.split(" ")) > 1 else ""
if await bot.authenticate_user(user_id, password):
await bot.send_message(chat_id, "Xác thực thành công! Bạn có thể sử dụng bot.")
else:
await bot.send_message(chat_id, "Mật khẩu sai. Vui lòng thử lại với /auth <mật_khẩu>.")
class GeminiCommand(CommandHandler):
async def handle(self, bot, chat_id: int, user_id: int, text: str) -> None:
bot.gemini_mode[user_id] = True
await bot.send_message(chat_id, "Chuyển sang chế độ Gemini. Gửi câu hỏi hoặc /exit.")
class ExitCommand(CommandHandler):
async def handle(self, bot, chat_id: int, user_id: int, text: str) -> None:
bot.gemini_mode[user_id] = False
await bot.send_message(chat_id, "Đã thoát chế độ Gemini.")
class TrainCommand(CommandHandler):
async def handle(self, bot, chat_id: int, user_id: int, text: str) -> None:
if not await bot.is_user_authenticated(user_id):
await bot.send_message(chat_id, "Vui lòng xác thực trước khi sử dụng lệnh này. Gửi: /auth <mật_khẩu>")
return
parts = text.split(maxsplit=1)
train_param = parts[1] if len(parts) > 1 else None
data_type = "đối thoại"
lottery_data = None
if train_param:
try:
parsed = json.loads(train_param)
if isinstance(parsed, dict) and "ngay" in parsed and "dai" in parsed and "giai" in parsed:
lottery_data = parsed
data_type = "xổ số"
except json.JSONDecodeError:
pass
await bot.send_message(chat_id, f"Đang đào tạo {data_type}... (Dữ liệu: {train_param or 'Không có'})")
try:
from train import train_data
import asyncio
await asyncio.get_event_loop().run_in_executor(
None, train_data, bot.db, bot.search, bot.embedding, bot.lottery, train_param if not lottery_data else None, lottery_data
)
await bot.send_message(chat_id, f"Đào tạo {data_type} hoàn tất!")
except Exception as e:
error_type = "xổ số" if lottery_data else "đối thoại"
logger.error(f"Lỗi khi đào tạo {error_type}: {str(e)}")
await bot.send_message(chat_id, f"Lỗi khi đào tạo {error_type}: {str(e)}")
class LotteryPositionCommand(CommandHandler):
async def handle(self, bot, chat_id: int, user_id: int, text: str) -> None:
if not await bot.is_user_authenticated(user_id):
await bot.send_message(chat_id, "Vui lòng xác thực trước khi sử dụng lệnh này. Gửi: /auth <mật_khẩu>")
return
parts = text.split()
if len(parts) < 5:
await bot.send_message(chat_id, "Sai định dạng. Vui lòng dùng: /lottery_position <đài> <ngày> <số_ngày_trước> <độ_khớp> (ngày DD-MM-YYYY).")
return
match_threshold = parts[-1]
days_before = parts[-2]
date = parts[-3].replace("/", "-") # Chuyển DD/MM/YYYY thành DD-MM-YYYY
try:
datetime.strptime(date, "%d-%m-%Y")
except ValueError:
await bot.send_message(chat_id, "Định dạng ngày không hợp lệ. Vui lòng dùng DD-MM-YYYY (ví dụ: 16-04-2025).")
return
dai = " ".join(parts[1:-3])
if not dai:
await bot.send_message(chat_id, "Thiếu tên đài. Vui lòng dùng: /lottery_position <đài> <ngày> <số_ngày_trước> <độ_khớp>.")
return
result = bot.lottery.analyze_position(dai, date, days_before, match_threshold)
await bot.send_message(chat_id, result)
class LotteryCommand(CommandHandler):
async def handle(self, bot, chat_id: int, user_id: int, text: str) -> None:
if not await bot.is_user_authenticated(user_id):
await bot.send_message(chat_id, "Vui lòng xác thực trước khi sử dụng lệnh này. Gửi: /auth <mật_khẩu>")
return
parts = text.split()
if len(parts) < 4:
await bot.send_message(chat_id, "Sai định dạng. Vui lòng dùng: /lottery <đài> <ngày_bắt_đầu> <ngày_kết_thúc> (YYYY-MM-DD).")
return
end_date = parts[-1]
start_date = parts[-2]
try:
datetime.strptime(start_date, "%Y-%m-%d")
datetime.strptime(end_date, "%Y-%m-%d")
except ValueError:
await bot.send_message(chat_id, "Định dạng ngày không hợp lệ. Vui lòng dùng YYYY-MM-DD (ví dụ: 2025-05-11).")
return
dai = " ".join(parts[1:-2])
if not dai:
await bot.send_message(chat_id, "Thiếu tên đài. Vui lòng dùng: /lottery <đài> <ngày_bắt_đầu> <ngày_kết_thúc>.")
return
result = bot.lottery.analyze(dai, start_date, end_date)
await bot.send_message(chat_id, result)
class LoadLotteryCommand(CommandHandler):
def __init__(self):
self.is_loading = False
async def handle(self, bot, chat_id: int, user_id: int, text: str) -> None:
if not await bot.is_user_authenticated(user_id):
await bot.send_message(chat_id, "Vui lòng xác thực trước khi sử dụng lệnh này. Gửi: /auth <mật_khẩu>")
return
if self.is_loading:
await bot.send_message(chat_id, "Đang tải dữ liệu xổ số, vui lòng chờ...")
return
self.is_loading = True
try:
json_file_path = "data/lottery_data.json"
result = bot.lottery.load_from_json(json_file_path)
await bot.send_message(chat_id, result)
finally:
self.is_loading = False
class DefaultCommand(CommandHandler):
async def handle(self, bot, chat_id: int, user_id: int, text: str) -> None:
cache_key = f"{user_id}:{text}"
gemini_cache_key = f"{user_id}:{text}:GEMINI"
cache_type = "gemini" if bot.gemini_mode.get(user_id) else "default"
cache_key_to_use = gemini_cache_key if bot.gemini_mode.get(user_id) else cache_key
cached = bot.cache.get(cache_key_to_use, type=cache_type)
if cached:
await bot.send_message(chat_id, cached)
return
query_vector = bot.embedding.generate(text)
results = bot.search.search(query_vector)
context = results[0][0] if results else None
if bot.gemini_mode.get(user_id):
response = bot.gemini.query(text, context)
bot.cache.set(gemini_cache_key, response, type="gemini")
else:
response = context or "Không tìm thấy thông tin."
bot.cache.set(cache_key, response)
await bot.send_message(chat_id, response)
logger.info("Định nghĩa các CommandHandler hoàn tất")
class Bot:
def __init__(self):
logger.info("Bắt đầu khởi tạo Bot")
self.gemini_mode: Dict[int, bool] = {}
self.authenticated_users: Dict[int, bool] = {}
self._embedding = None
self._search = None
self._db = None
self._gemini = None
self._cache = None
self._lottery = None
self.command_handlers = {
"/start": StartCommand(),
"/help": HelpCommand(),
"/auth": AuthCommand(),
"/Gemini": GeminiCommand(),
"/exit": ExitCommand(),
"/train": TrainCommand(),
"/lottery": LotteryCommand(),
"/lottery_position": LotteryPositionCommand(),
"/load_lottery": LoadLotteryCommand(),
}
logger.info("Khởi tạo Bot hoàn tất")
@property
def embedding(self):
if self._embedding is None:
logger.info("Khởi tạo Embedding")
self._embedding = Embedding()
return self._embedding
@property
def search(self):
if self._search is None:
logger.info("Khởi tạo Search")
self._search = Search()
return self._search
@property
def db(self):
if self._db is None:
logger.info("Khởi tạo FirestoreDB")
self._db = FirestoreDB()
return self._db
@property
def gemini(self):
if self._gemini is None:
logger.info("Khởi tạo Gemini")
self._gemini = Gemini()
return self._gemini
@property
def cache(self):
if self._cache is None:
logger.info("Khởi tạo Cache")
self._cache = Cache()
return self._cache
@property
def lottery(self):
if self._lottery is None:
logger.info("Khởi tạo Lottery")
self._lottery = Lottery(self.db, self.cache, self.embedding)
return self._lottery
async def send_message(self, chat_id: int, text: str) -> None:
url = f"https://api.telegram.org/bot{Settings.TELEGRAM_TOKEN}/sendMessage"
payload = {"chat_id": chat_id, "text": text}
try:
await http_client.post(url, json=payload)
except Exception as e:
logger.error(f"Lỗi khi gửi tin nhắn: {str(e)}")
async def is_user_authenticated(self, user_id: int) -> bool:
if user_id in self.authenticated_users:
return True
doc = self.db.db.collection("auth_users").document(str(user_id)).get()
if doc.exists and doc.to_dict().get("authenticated", False):
self.authenticated_users[user_id] = True
return True
return False
async def authenticate_user(self, user_id: int, password: str) -> bool:
if password == Settings.BOT_PASSWORD:
self.authenticated_users[user_id] = True
self.db.db.collection("auth_users").document(str(user_id)).set({"authenticated": True})
logger.info(f"Người dùng {user_id} đã xác thực thành công")
return True
logger.warning(f"Xác thực thất bại cho người dùng {user_id}")
return False
async def handle_message(self, chat_id: int, user_id: int, text: str) -> None:
logger.info(f"Xử lý lệnh: {text} từ user_id: {user_id}")
command = text.split()[0] if text.startswith("/") else None
if command and command not in self.command_handlers:
await self.send_message(chat_id, "Lệnh không hợp lệ. Gửi /help để xem hướng dẫn.")
return
handler = self.command_handlers.get(command, DefaultCommand())
await handler.handle(self, chat_id, user_id, text)
logger.info("Định nghĩa lớp Bot hoàn tất")
bot = Bot()
logger.info("Khởi tạo bot hoàn tất")
class WebhookUpdate(BaseModel):
message: dict
@app.get("/")
async def read_root():
return {"status": "ok"}
@app.get("/health")
async def health_check():
return {"status": "healthy"}
@app.post("/webhook")
async def webhook(update: WebhookUpdate):
message = update.message
chat_id = message["chat"]["id"]
user_id = message["from"]["id"]
text = message.get("text", "")
logger.info(f"Nhận tin nhắn từ {user_id}: {text}")
await bot.handle_message(chat_id, user_id, text)
return {"status": "ok"}
@app.on_event("startup")
async def startup_event():
logger.info("Bắt đầu startup_event")
webhook_url = f"{Settings.WEBHOOK_URL}/webhook"
url = f"https://api.telegram.org/bot{Settings.TELEGRAM_TOKEN}/setWebhook"
try:
logger.info(f"Gửi yêu cầu thiết lập webhook tới {url}")
response = await http_client.post(url, json={"url": webhook_url}, timeout=10.0)
logger.info(f"Phản hồi từ Telegram API: {response.status_code}")
if response.status_code == 200:
logger.info("Webhook đã được thiết lập")
else:
logger.error(f"Thiết lập webhook thất bại: {response.text}")
except Exception as e:
logger.error(f"Lỗi khi thiết lập webhook: {str(e)}")
logger.info("Kết thúc startup_event")
@app.on_event("shutdown")
async def shutdown_event():
logger.info("Bắt đầu shutdown_event")
await http_client.aclose()
logger.info("Đã đóng HTTP client")
logger.info("Định nghĩa các route và event handlers hoàn tất") |