import os import sys import cv2 import numpy as np from PyQt6.QtCore import Qt, QUrl, QTimer from PyQt6.QtWidgets import ( QApplication, QMainWindow, QVBoxLayout, QWidget, QLabel, QTextEdit, QPushButton, ) from PyQt6.QtWebEngineWidgets import QWebEngineView from PyQt6.QtWebEngineCore import QWebEngineProfile from PyQt6.QtGui import QImage from services.action_service import ActionService from services.detect_service import DetectService from services.profile_service import ProfileService from config import TEMPLATE_DIR class LoginFB(QMainWindow): """Cửa sổ tự động đăng nhập Facebook bằng nhận diện hình ảnh.""" def __init__(self, account=None, delay=0.3): super().__init__() self.account = account or {} self.delay = delay self.template_dir = os.path.abspath(TEMPLATE_DIR) # ✅ Lấy tên profile từ email hoặc username self.profile_name = ( self.account.get("email") or self.account.get("username") or "default" ) # --- Detect services --- self.detector = DetectService( template_dir=TEMPLATE_DIR, target_labels=["username", "password"] ) # --- UI setup --- self.setWindowTitle(f"FB Auto Vision Login - {self.profile_name}") self.setFixedSize(480, 680) self.web = QWebEngineView() self.status = QLabel("Status: Ready") self.status.setAlignment(Qt.AlignmentFlag.AlignLeft) self.status.setFixedHeight(20) # Log area self.log_area = QTextEdit() self.log_area.setReadOnly(True) self.log_area.setFixedHeight(120) self.log_area.setStyleSheet( """ background-color: #1e1e1e; color: #dcdcdc; font-size: 12px; font-family: Consolas, monospace; """ ) # Refresh button self.btn_refresh = QPushButton("Refresh") self.btn_refresh.setFixedHeight(30) self.btn_refresh.clicked.connect(self.refresh_page) # --- Profile --- self.profile_service = ProfileService() profile_path = self.profile_service.get_profile_path(self.profile_name) os.makedirs(profile_path, exist_ok=True) profile = self.web.page().profile() profile.setPersistentCookiesPolicy( QWebEngineProfile.PersistentCookiesPolicy.ForcePersistentCookies ) profile.setPersistentStoragePath(profile_path) self.log(f"[INFO] Profile applied at: {profile_path}") # --- Webview --- self.web.setUrl(QUrl("https://facebook.com")) self.web.setFixedSize(480, 480) # --- Layout --- layout = QVBoxLayout() layout.setContentsMargins(0, 0, 0, 0) layout.addWidget(self.web) layout.addWidget(self.status) layout.addWidget(self.log_area) layout.addWidget(self.btn_refresh) container = QWidget() container.setLayout(layout) self.setCentralWidget(container) # --- Services --- self.action = ActionService(webview=self.web, delay=self.delay) self.web.loadFinished.connect(self.on_web_loaded) # ---------------------------------------------------- def log(self, message: str): """Ghi log vào vùng log và console.""" self.log_area.append(message) print(message) # ---------------------------------------------------- def capture_webview(self): """Chụp hình ảnh nội dung webview dưới dạng numpy array (BGR).""" pixmap = self.web.grab() if pixmap.isNull(): return None qimg = pixmap.toImage().convertToFormat(QImage.Format.Format_RGBA8888) width, height = qimg.width(), qimg.height() ptr = qimg.bits() ptr.setsize(height * width * 4) arr = np.frombuffer(ptr, np.uint8).reshape((height, width, 4)) return cv2.cvtColor(arr, cv2.COLOR_RGBA2BGR) # ---------------------------------------------------- def on_web_loaded(self, ok=True): """Khi trang web load xong.""" if not ok: self.log("[ERROR] Page failed to load") self.status.setText("Status: Page load failed") return self.log("[INFO] Page loaded successfully") self.status.setText("Status: Page loaded") # ✅ Lưu profile khi load xong self.profile_service.save_profile(self.profile_name) self.log(f"[INFO] Profile saved for {self.profile_name}") # 🧠 Detect field screen = self.capture_webview() if screen is None: self.status.setText("Status: Unable to capture webview") self.log("[WARN] Unable to capture webview") return self.log("[INFO] Detecting email/password fields...") regions = self.detector.detect(screen) if not regions: self.status.setText("[WARN] No regions detected") self.log("[WARN] No regions detected") return self.status.setText(f"[INFO] Detected {len(regions)} valid regions") self.log(f"[INFO] Detected {len(regions)} valid regions") # Chờ 500ms trước khi tự điền form QTimer.singleShot(500, lambda: self.autofill_by_detection(regions)) # ---------------------------------------------------- def autofill_by_detection(self, regions): """Tự động điền email và password dựa vào vùng phát hiện.""" email = self.account.get("email", "") password = self.account.get("password", "") # sắp xếp: username trước, password sau ordered = sorted( regions, key=lambda r: ("pass" in r[0].lower(), "user" not in r[0].lower()) ) def do_action(i=0): if i >= len(ordered): return folder_name, filename, top_left, bottom_right, score = ordered[i] label = folder_name.lower() self.log(f"[ACTION] {folder_name}: {filename} ({score:.2f})") if ("user" in label or "email" in label) and email: self.log(f"[DO] Filling email: {email}") self.action.write_in_region(top_left, bottom_right, email) elif "pass" in label and password: self.log(f"[DO] Filling password: {'*' * len(password)}") self.action.write_in_region(top_left, bottom_right, password) QTimer.singleShot(int(self.delay * 1000), lambda: do_action(i + 1)) do_action() # ---------------------------------------------------- def refresh_page(self): """Tải lại trang.""" self.log("[INFO] Refreshing page...") self.web.reload() # ---------------------------------------------------- if __name__ == "__main__": app = QApplication(sys.argv) fake_account = {"email": "test@example.com", "password": "123456"} win = LoginFB(account=fake_account, delay=0.5) win.show() sys.exit(app.exec())