Compare commits
2 Commits
f4c8185ed0
...
1aba2bf683
| Author | SHA1 | Date |
|---|---|---|
|
|
1aba2bf683 | |
|
|
9a2c793380 |
|
|
@ -47,3 +47,5 @@ htmlcov/
|
||||||
.DS_Store
|
.DS_Store
|
||||||
Thumbs.db
|
Thumbs.db
|
||||||
|
|
||||||
|
profiles
|
||||||
|
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -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_())
|
||||||
|
|
|
||||||
|
|
@ -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 lý thư mục profiles.
|
Service để quản lý 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 |
Loading…
Reference in New Issue