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 .DS_Store
Thumbs.db Thumbs.db
profiles

Binary file not shown.

View File

@ -1,43 +1,39 @@
# gui/handle/login_fb.py
import os import os
import sys import sys
import cv2 import cv2
import numpy as np import numpy as np
from PyQt5.QtCore import Qt, QUrl, QTimer from PyQt5.QtCore import Qt, QUrl, QTimer
from PyQt5.QtWidgets import QApplication, QMainWindow, QVBoxLayout, QWidget, QLabel, QTextEdit, QPushButton from PyQt5.QtWidgets import QApplication, QMainWindow, QVBoxLayout, QWidget, QLabel, QTextEdit, QPushButton
from PyQt5.QtWebEngineWidgets import QWebEngineView from PyQt5.QtWebEngineWidgets import QWebEngineView, QWebEngineProfile
from PyQt5.QtGui import QImage, QPixmap from PyQt5.QtGui import QImage
from services.action_service import ActionService from services.action_service import ActionService
from services.detect_service import DetectService from services.detect_service import DetectService
from services.profile_service import ProfileService
from config import TEMPLATE_DIR from config import TEMPLATE_DIR
class LoginFB(QMainWindow): class LoginFB(QMainWindow):
def __init__(self, account=None, delay=0.1): def __init__(self, account=None, delay=0.3):
super().__init__() super().__init__()
self.account = account or {} self.account = account or {}
self.template_dir = os.path.abspath(TEMPLATE_DIR) self.template_dir = os.path.abspath(TEMPLATE_DIR)
self.delay = delay 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 --- # --- Detect services ---
self.detector = DetectService( self.detector = DetectService(
template_dir=TEMPLATE_DIR, template_dir=TEMPLATE_DIR,
target_labels=["username", "password", "buttons/login"] target_labels=["username", "password"]
) )
# --- Detect login fail templates --- # --- UI cơ bản ---
self.fail_detector = DetectService( self.setWindowTitle(f"FB Auto Vision Login - {self.profile_name}")
template_dir=os.path.join(TEMPLATE_DIR, "login_fail")
)
# --- UI ---
self.setWindowTitle("FB Auto Vision Login")
self.setFixedSize(480, 680) self.setFixedSize(480, 680)
self.web = QWebEngineView() self.web = QWebEngineView()
self.web.setUrl(QUrl("https://facebook.com"))
self.web.setFixedSize(480, 480)
self.status = QLabel("Status: Ready") self.status = QLabel("Status: Ready")
self.status.setAlignment(Qt.AlignLeft) self.status.setAlignment(Qt.AlignLeft)
@ -57,6 +53,19 @@ class LoginFB(QMainWindow):
self.btn_refresh.setFixedHeight(30) self.btn_refresh.setFixedHeight(30)
self.btn_refresh.clicked.connect(self.refresh_page) 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 = QVBoxLayout()
layout.setContentsMargins(0, 0, 0, 0) layout.setContentsMargins(0, 0, 0, 0)
layout.addWidget(self.web) layout.addWidget(self.web)
@ -68,12 +77,8 @@ class LoginFB(QMainWindow):
container.setLayout(layout) container.setLayout(layout)
self.setCentralWidget(container) self.setCentralWidget(container)
# --- Services ---
self.action = ActionService(webview=self.web, delay=self.delay) 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) self.web.loadFinished.connect(self.on_web_loaded)
# ---------------------------------------------------- # ----------------------------------------------------
@ -95,38 +100,26 @@ class LoginFB(QMainWindow):
# ---------------------------------------------------- # ----------------------------------------------------
def on_web_loaded(self, ok=True): def on_web_loaded(self, ok=True):
"""Called every time page load finished"""
if not ok: if not ok:
self.log("[ERROR] Page failed to load") self.log("[ERROR] Page failed to load")
self.status.setText("Status: Page load failed") self.status.setText("Status: Page load failed")
return 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() screen = self.capture_webview()
if screen is None: if screen is None:
self.status.setText("Status: Unable to capture webview") self.status.setText("Status: Unable to capture webview")
self.log("Status: Unable to capture webview") self.log("Status: Unable to capture webview")
return return
# Nếu đang sau nhấn login, check login failure self.log("[INFO] Detecting email/password fields...")
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...")
regions = self.detector.detect(screen) regions = self.detector.detect(screen)
if not regions: if not regions:
@ -143,10 +136,17 @@ class LoginFB(QMainWindow):
email = self.account.get("email", "") email = self.account.get("email", "")
password = self.account.get("password", "") password = self.account.get("password", "")
for folder_name, filename, top_left, bottom_right, score in regions: # sắp xếp để điền username trước, password sau
self.log(f"[ACTION] {folder_name}: {filename} ({score:.2f})") 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() label = folder_name.lower()
self.log(f"[ACTION] {folder_name}: {filename} ({score:.2f})")
if ("user" in label or "email" in label) and email: if ("user" in label or "email" in label) and email:
self.log(f"[DO] Filling email: {email}") self.log(f"[DO] Filling email: {email}")
self.action.write_in_region(top_left, bottom_right, 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.log(f"[DO] Filling password: {'*' * len(password)}")
self.action.write_in_region(top_left, bottom_right, password) self.action.write_in_region(top_left, bottom_right, password)
elif "button" in label or "login" in label: QTimer.singleShot(int(self.delay * 1000), lambda: do_action(i + 1))
self.log("[DO] Clicking login button...")
self.login_clicked = True do_action()
self.action.click_center_of_region(top_left, bottom_right)
# ---------------------------------------------------- # ----------------------------------------------------
def refresh_page(self): def refresh_page(self):
@ -169,6 +168,6 @@ class LoginFB(QMainWindow):
if __name__ == "__main__": if __name__ == "__main__":
app = QApplication(sys.argv) app = QApplication(sys.argv)
fake_account = {"email": "test@example.com", "password": "123456"} fake_account = {"email": "test@example.com", "password": "123456"}
win = LoginFB(account=fake_account) win = LoginFB(account=fake_account, delay=0.5)
win.show() win.show()
sys.exit(app.exec_()) sys.exit(app.exec_())

View File

@ -4,6 +4,7 @@ import re
import shutil import shutil
import logging import logging
from typing import Optional, List from typing import Optional, List
from config import PROFILES_DIR # 👈 dùng từ config
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
logging.basicConfig(level=logging.INFO) logging.basicConfig(level=logging.INFO)
@ -32,21 +33,30 @@ def _sanitize_name(name: str) -> str:
class ProfileService: class ProfileService:
""" """
Service để quản thư mục profiles. 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): 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) os.makedirs(self.profiles_root, exist_ok=True)
logger.info("Profiles root: %s", self.profiles_root) logger.info("Profiles root: %s", self.profiles_root)
def get_profile_dirname(self, name: str) -> str: def get_profile_dirname(self, name: str) -> str:
"""Tên folder đã sanitize (chỉ tên folder, không có path)""" """Tên folder đã sanitize (chỉ tên folder, không có path)"""
return _sanitize_name(name) 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: def get_profile_path(self, key: str) -> str:
"""Trả về path tuyệt đối tới folder profile""" path = os.path.join(self.base_dir, key)
return os.path.join(self.profiles_root, self.get_profile_dirname(name)) os.makedirs(path, exist_ok=True)
return path
def exists(self, name: str) -> bool: def exists(self, name: str) -> bool:
"""Check folder có tồn tại không""" """Check folder có tồn tại không"""
@ -73,7 +83,6 @@ class ProfileService:
if copy_from: if copy_from:
copy_from = os.path.abspath(copy_from) copy_from = os.path.abspath(copy_from)
if os.path.isdir(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): for item in os.listdir(copy_from):
s = os.path.join(copy_from, item) s = os.path.join(copy_from, item)
d = os.path.join(path, item) d = os.path.join(path, item)
@ -136,7 +145,6 @@ class ProfileService:
try: try:
profile.setPersistentCookiesPolicy(QWebEngineProfile.ForcePersistentCookies) profile.setPersistentCookiesPolicy(QWebEngineProfile.ForcePersistentCookies)
except Exception: 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 pass
logger.info("Created QWebEngineProfile for %s -> %s", name, profile_path) logger.info("Created QWebEngineProfile for %s -> %s", name, profile_path)
return profile return profile

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB