174 lines
6.2 KiB
Python
174 lines
6.2 KiB
Python
import os
|
|
import sys
|
|
import cv2
|
|
import numpy as np
|
|
from PyQt5.QtCore import Qt, QUrl, QTimer
|
|
from PyQt5.QtWidgets import QApplication, QMainWindow, QVBoxLayout, QWidget, QLabel, QTextEdit, QPushButton
|
|
from PyQt5.QtWebEngineWidgets import QWebEngineView, QWebEngineProfile
|
|
from PyQt5.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):
|
|
def __init__(self, account=None, delay=0.3):
|
|
super().__init__()
|
|
self.account = account or {}
|
|
self.template_dir = os.path.abspath(TEMPLATE_DIR)
|
|
self.delay = delay
|
|
|
|
# ✅ 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 cơ bản ---
|
|
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.AlignLeft)
|
|
self.status.setFixedHeight(20)
|
|
|
|
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;
|
|
""")
|
|
|
|
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)
|
|
profile = self.web.page().profile()
|
|
profile.setPersistentCookiesPolicy(QWebEngineProfile.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):
|
|
self.log_area.append(message)
|
|
print(message)
|
|
|
|
# ----------------------------------------------------
|
|
def capture_webview(self):
|
|
pixmap = self.web.grab()
|
|
if pixmap.isNull():
|
|
return None
|
|
qimg = pixmap.toImage().convertToFormat(QImage.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):
|
|
if not ok:
|
|
self.log("[ERROR] Page failed to load")
|
|
self.status.setText("Status: Page load failed")
|
|
return
|
|
|
|
self.log("[INFO] Page loaded")
|
|
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("Status: 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")
|
|
QTimer.singleShot(500, lambda: self.autofill_by_detection(regions))
|
|
|
|
# ----------------------------------------------------
|
|
def autofill_by_detection(self, regions):
|
|
email = self.account.get("email", "")
|
|
password = self.account.get("password", "")
|
|
|
|
# sắp xếp để điền 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):
|
|
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_())
|