Compare commits

...

2 Commits

Author SHA1 Message Date
Admin 1aba2bf683 login 2025-10-15 16:49:57 +07:00
Admin 9a2c793380 login 2025-10-15 16:48:43 +07:00
6 changed files with 63 additions and 54 deletions

2
.gitignore vendored
View File

@ -47,3 +47,5 @@ htmlcov/
.DS_Store
Thumbs.db
profiles

Binary file not shown.

View File

@ -1,43 +1,39 @@
# gui/handle/login_fb.py
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
from PyQt5.QtGui import QImage, QPixmap
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.1):
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", "buttons/login"]
target_labels=["username", "password"]
)
# --- Detect login fail templates ---
self.fail_detector = DetectService(
template_dir=os.path.join(TEMPLATE_DIR, "login_fail")
)
# --- UI ---
self.setWindowTitle("FB Auto Vision Login")
# --- UI cơ bản ---
self.setWindowTitle(f"FB Auto Vision Login - {self.profile_name}")
self.setFixedSize(480, 680)
self.web = QWebEngineView()
self.web.setUrl(QUrl("https://facebook.com"))
self.web.setFixedSize(480, 480)
self.status = QLabel("Status: Ready")
self.status.setAlignment(Qt.AlignLeft)
@ -57,6 +53,19 @@ class LoginFB(QMainWindow):
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)
@ -68,12 +77,8 @@ class LoginFB(QMainWindow):
container.setLayout(layout)
self.setCentralWidget(container)
# --- Services ---
self.action = ActionService(webview=self.web, delay=self.delay)
self.login_clicked = False
# Giữ reference popup (nếu cần) hiện tại không dùng
# self.login_fail_popup = None
self.web.loadFinished.connect(self.on_web_loaded)
# ----------------------------------------------------
@ -95,38 +100,26 @@ class LoginFB(QMainWindow):
# ----------------------------------------------------
def on_web_loaded(self, ok=True):
"""Called every time page load finished"""
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
# Nếu đang sau nhấn login, check login failure
if self.login_clicked:
self.log("[INFO] Page loaded after login click. Detecting login failure...")
fail_regions = self.fail_detector.detect(screen)
if fail_regions:
self.log(f"[FAIL] Login failed detected via template ({len(fail_regions)} regions):")
for folder_name, filename, top_left, bottom_right, score in fail_regions:
self.log(f" - {filename} @ {top_left}-{bottom_right} (score: {score:.2f})")
self.status.setText("Status: Login failed")
else:
self.log("[SUCCESS] Login seems successful!")
self.status.setText("Status: Login successful")
self.login_clicked = False
return
# Nếu là initial page load
self.log("[INFO] Page loaded. Starting template detection...")
self.log("[INFO] Detecting email/password fields...")
regions = self.detector.detect(screen)
if not regions:
@ -143,10 +136,17 @@ class LoginFB(QMainWindow):
email = self.account.get("email", "")
password = self.account.get("password", "")
for folder_name, filename, top_left, bottom_right, score in regions:
self.log(f"[ACTION] {folder_name}: {filename} ({score:.2f})")
# 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)
@ -155,10 +155,9 @@ class LoginFB(QMainWindow):
self.log(f"[DO] Filling password: {'*' * len(password)}")
self.action.write_in_region(top_left, bottom_right, password)
elif "button" in label or "login" in label:
self.log("[DO] Clicking login button...")
self.login_clicked = True
self.action.click_center_of_region(top_left, bottom_right)
QTimer.singleShot(int(self.delay * 1000), lambda: do_action(i + 1))
do_action()
# ----------------------------------------------------
def refresh_page(self):
@ -169,6 +168,6 @@ class LoginFB(QMainWindow):
if __name__ == "__main__":
app = QApplication(sys.argv)
fake_account = {"email": "test@example.com", "password": "123456"}
win = LoginFB(account=fake_account)
win = LoginFB(account=fake_account, delay=0.5)
win.show()
sys.exit(app.exec_())

View File

@ -4,6 +4,7 @@ import re
import shutil
import logging
from typing import Optional, List
from config import PROFILES_DIR # 👈 dùng từ config
logger = logging.getLogger(__name__)
logging.basicConfig(level=logging.INFO)
@ -32,21 +33,30 @@ def _sanitize_name(name: str) -> str:
class ProfileService:
"""
Service để quản thư mục profiles.
Mặc định root folder là ./profiles (tương đối với working dir).
Mặc định root folder lấy từ config.PROFILES_DIR.
"""
base_dir = PROFILES_DIR
def __init__(self, profiles_root: Optional[str] = None):
self.profiles_root = os.path.abspath(profiles_root or "profiles")
# 👇 Dùng PROFILES_DIR nếu không truyền thủ công
self.profiles_root = os.path.abspath(profiles_root or PROFILES_DIR)
os.makedirs(self.profiles_root, exist_ok=True)
logger.info("Profiles root: %s", self.profiles_root)
def get_profile_dirname(self, name: str) -> str:
"""Tên folder đã sanitize (chỉ tên folder, không có path)"""
return _sanitize_name(name)
def save_profile(self, key: str):
# ở đây có thể không cần làm gì nhiều vì Qt tự lưu cookie
# nhưng bạn có thể log hoặc thêm custom logic
print(f"[ProfileService] Saved profile for {key}")
def get_profile_path(self, name: str) -> str:
"""Trả về path tuyệt đối tới folder profile"""
return os.path.join(self.profiles_root, self.get_profile_dirname(name))
def get_profile_path(self, key: str) -> str:
path = os.path.join(self.base_dir, key)
os.makedirs(path, exist_ok=True)
return path
def exists(self, name: str) -> bool:
"""Check folder có tồn tại không"""
@ -73,7 +83,6 @@ class ProfileService:
if copy_from:
copy_from = os.path.abspath(copy_from)
if os.path.isdir(copy_from):
# copy nội dung bên trong copy_from vào path
for item in os.listdir(copy_from):
s = os.path.join(copy_from, item)
d = os.path.join(path, item)
@ -136,7 +145,6 @@ class ProfileService:
try:
profile.setPersistentCookiesPolicy(QWebEngineProfile.ForcePersistentCookies)
except Exception:
# Một vài phiên bản PyQt có thể khác tên hằng, bọc try/except để an toàn
pass
logger.info("Created QWebEngineProfile for %s -> %s", name, profile_path)
return profile

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB