Files

257 lines
9.6 KiB
Python

import logging
import os
import shutil
import time
from playwright.sync_api import Error as PlaywrightError
from playwright.sync_api import Page
from playwright.sync_api import TimeoutError as PlaywrightTimeoutError
from playwright.sync_api import sync_playwright
from config import Config
from captcha_handler import ModelApiError
logger = logging.getLogger(__name__)
class CheckInAutomation:
"""签到自动化主类"""
def __init__(self, config: Config):
self.config = config
try:
from captcha_handler import CaptchaHandler
except ImportError:
from captcha_handler import CaptchaHandler # 如果模块名不同
self.captcha_handler = CaptchaHandler(config)
try:
from human_simulator import HumanSimulator
except ImportError:
from human_simulator import HumanSimulator # 如果模块名不同
self.simulator = HumanSimulator()
self.max_retries = config.max_retries
def run(self):
"""执行签到流程"""
# GitHub Actions 环境自动使用 headless 模式
headless = os.getenv('CI') == 'true' or os.getenv('HEADLESS', 'false').lower() == 'true'
with sync_playwright() as playwright:
browser = None
try:
logger.info("正在初始化 Playwright Chromium...")
launch_options = {
"headless": headless,
"args": [
"--window-size=1280,800",
"--disable-blink-features=AutomationControlled",
"--no-proxy-server",
"--lang=zh-CN",
"--disable-gpu",
"--no-sandbox",
"--disable-dev-shm-usage",
],
}
chrome_path = self._resolve_chrome_path()
if chrome_path:
logger.info(f"使用 Chrome 路径: {chrome_path}")
launch_options["executable_path"] = chrome_path
browser = playwright.chromium.launch(**launch_options)
context = browser.new_context(
viewport={"width": 1280, "height": 800},
locale="zh-CN",
user_agent=(
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/120.0.0.0 Safari/537.36"
),
)
context.add_init_script(
"Object.defineProperty(navigator, 'webdriver', { get: () => undefined });"
)
page = context.new_page()
page.set_default_timeout(20000)
# 步骤1: 登录
if not self._login(page):
logger.error("登录失败")
return
# 步骤2: 跳转到 处理年龄
if not self._navigate_to_sakurafrp(page):
logger.error("跳转到 SakuraFrp 失败")
return
# 步骤3: 执行签到
if not self._perform_checkin(page):
logger.error("签到失败")
page.screenshot(path="error_screenshot.png", full_page=True)
with open("error_page_source.html", "w", encoding="utf-8") as f:
f.write(page.content())
return
logger.info("✓ 签到流程完成")
except ModelApiError:
logger.error("模型 API 调用失败,终止签到流程")
return
except Exception as e:
logger.error(f"执行过程中发生错误: {e}", exc_info=True)
finally:
if browser and headless:
browser.close()
logger.info("脚本执行完毕")
def _resolve_chrome_path(self):
"""优先使用环境变量指定的 Chrome,其次查找系统 Chrome。"""
if self.config.chrome_binary_path and os.path.exists(self.config.chrome_binary_path):
return self.config.chrome_binary_path
candidate_paths = [
os.path.join(
os.environ.get("PROGRAMFILES", ""),
"Google",
"Chrome",
"Application",
"chrome.exe",
),
os.path.join(
os.environ.get("PROGRAMFILES(X86)", ""),
"Google",
"Chrome",
"Application",
"chrome.exe",
),
os.path.join(
os.environ.get("LOCALAPPDATA", ""),
"Google",
"Chrome",
"Application",
"chrome.exe",
),
]
for executable in ("google-chrome", "google-chrome-stable", "chrome", "chromium"):
found = shutil.which(executable)
if found:
return found
for path in candidate_paths:
if path and os.path.exists(path):
return path
return None
def _login(self, page: Page) -> bool:
"""执行登录"""
login_url = "https://www.natfrp.com/user/"
logger.info(f"导航到登录页面: {login_url}")
page.goto(login_url, wait_until="domcontentloaded")
try:
# 输入用户名和密码
username_input = page.locator("#username")
password_input = page.locator("#password")
username_input.wait_for(state="visible")
password_input.wait_for(state="visible")
logger.info("输入登录凭据...")
username_input.fill("")
self.simulator.type_text(username_input, self.config.sakurafrp_user)
password_input.fill("")
self.simulator.type_text(password_input, self.config.sakurafrp_pass)
# 点击登录按钮
login_button = page.locator("#login")
login_button.wait_for(state="visible")
logger.info("点击登录按钮...")
login_button.click()
self.simulator.random_sleep(3, 5)
logger.info("登录成功")
return True
except PlaywrightTimeoutError:
logger.error("登录页面元素加载超时")
return False
except Exception as e:
logger.error(f"登录过程出错: {e}", exc_info=True)
return False
def _navigate_to_sakurafrp(self, page: Page) -> bool:
"""跳转到 SakuraFrp 仪表板"""
try:
# 点击 SakuraFrp 链接
# sakura_link = page.locator("div.action-list a", has_text="Sakura Frp")
# logger.info("点击 SakuraFrp 跳转链接...")
# sakura_link.click()
# self.simulator.random_sleep(2, 4)
# 处理年龄确认弹窗(如果存在)
try:
age_confirm = page.locator("div.yes a", has_text="是,我已满18岁")
age_confirm.wait_for(state="visible", timeout=5000)
logger.info("处理年龄确认弹窗...")
age_confirm.click()
self.simulator.random_sleep(2, 3)
except PlaywrightTimeoutError:
logger.info("未检测到年龄确认弹窗")
logger.info("成功跳转到 SakuraFrp 仪表板")
return True
except PlaywrightTimeoutError:
logger.warning("SakuraFrp 跳转链接未找到,可能已在目标页面")
return True
except Exception as e:
logger.error(f"跳转过程出错: {e}", exc_info=True)
return False
def _perform_checkin(self, page: Page) -> bool:
"""执行签到操作"""
for attempt in range(1, self.max_retries+1):
logger.info(f"验证码尝试 {attempt}/{self.max_retries}")
try:
# 查找签到按钮
check_in_button = None
try:
check_in_button = page.locator("button", has_text="点击这里签到")
check_in_button.wait_for(state="visible")
logger.info("找到签到按钮")
except PlaywrightTimeoutError:
# 检查是否已签到
try:
page.locator("p", has_text="今天已经签到过啦").wait_for(
state="visible",
timeout=2000,
)
logger.info("今日已签到")
return True
except PlaywrightTimeoutError:
logger.error("未找到签到按钮或已签到标识")
return False
# 点击签到按钮
if check_in_button:
logger.info("点击签到按钮...")
check_in_button.click()
self.simulator.random_sleep(2, 4)
# 处理验证码
self.captcha_handler.handle_geetest_captcha(page)
page.reload(wait_until="domcontentloaded")
time.sleep(5)
continue
return False
except ModelApiError:
raise
except PlaywrightError as e:
logger.error(f"签到过程出错: {e}", exc_info=True)
return False
except Exception as e:
logger.error(f"签到过程出错: {e}", exc_info=True)
return False
logger.info("已达到最大重试次数")
return False