From 2a9f96fb1874e6903113ec95cc93c4bbea8c4951 Mon Sep 17 00:00:00 2001 From: RRRosmontis Date: Thu, 27 Aug 2026 21:43:38 +0800 Subject: [PATCH] =?UTF-8?q?SakuraFrp=20=E8=87=AA=E5=8A=A8=E7=AD=BE?= =?UTF-8?q?=E5=88=B0=EF=BC=88main=20+=20sakura=5Ffrp=5Fauto=5Fcheck=20+=20?= =?UTF-8?q?SakuraFrpQiandao=EF=BC=89=EF=BC=8C=E5=B7=B2=E6=8E=92=E9=99=A4?= =?UTF-8?q?=20.env=20=E4=B8=8E=E6=97=A5=E5=BF=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- SakuraFrpQiandao/.gitignore | 13 + SakuraFrpQiandao/Dockerfile | 42 ++++ SakuraFrpQiandao/LICENSE | 21 ++ SakuraFrpQiandao/README.md | 171 +++++++++++++ SakuraFrpQiandao/automation.py | 256 ++++++++++++++++++++ SakuraFrpQiandao/captcha_handler.py | 298 +++++++++++++++++++++++ SakuraFrpQiandao/config.py | 61 +++++ SakuraFrpQiandao/human_simulator.py | 18 ++ SakuraFrpQiandao/main.py | 26 ++ SakuraFrpQiandao/requirements.txt | 14 ++ SakuraFrpQiandao/send_email.py | 96 ++++++++ bg.png | Bin 0 -> 54063 bytes fullbg.png | Bin 0 -> 52546 bytes main.py | 358 ++++++++++++++++++++++++++++ sakura_frp_auto_check/.gitignore | 10 + sakura_frp_auto_check/CLAUDE.md | 77 ++++++ sakura_frp_auto_check/CLAUDE_CN.md | 85 +++++++ sakura_frp_auto_check/LICENSE | 201 ++++++++++++++++ sakura_frp_auto_check/README.md | 116 +++++++++ sakura_frp_auto_check/checkin.png | Bin 0 -> 4161 bytes sakura_frp_auto_check/main.py | 346 +++++++++++++++++++++++++++ 21 files changed, 2209 insertions(+) create mode 100644 SakuraFrpQiandao/.gitignore create mode 100644 SakuraFrpQiandao/Dockerfile create mode 100644 SakuraFrpQiandao/LICENSE create mode 100644 SakuraFrpQiandao/README.md create mode 100644 SakuraFrpQiandao/automation.py create mode 100644 SakuraFrpQiandao/captcha_handler.py create mode 100644 SakuraFrpQiandao/config.py create mode 100644 SakuraFrpQiandao/human_simulator.py create mode 100644 SakuraFrpQiandao/main.py create mode 100644 SakuraFrpQiandao/requirements.txt create mode 100644 SakuraFrpQiandao/send_email.py create mode 100644 bg.png create mode 100644 fullbg.png create mode 100644 main.py create mode 100644 sakura_frp_auto_check/.gitignore create mode 100644 sakura_frp_auto_check/CLAUDE.md create mode 100644 sakura_frp_auto_check/CLAUDE_CN.md create mode 100644 sakura_frp_auto_check/LICENSE create mode 100644 sakura_frp_auto_check/README.md create mode 100644 sakura_frp_auto_check/checkin.png create mode 100644 sakura_frp_auto_check/main.py diff --git a/SakuraFrpQiandao/.gitignore b/SakuraFrpQiandao/.gitignore new file mode 100644 index 0000000..8bd7485 --- /dev/null +++ b/SakuraFrpQiandao/.gitignore @@ -0,0 +1,13 @@ +checkin.log +chromedriver.exe +captured_captcha.jpg +error_screenshot.png +error_page_source.html +.env +__pycache__/ +*.pyc +.vscode/ +.idea/ +node_modules/ +.venv +test/ diff --git a/SakuraFrpQiandao/Dockerfile b/SakuraFrpQiandao/Dockerfile new file mode 100644 index 0000000..25252c0 --- /dev/null +++ b/SakuraFrpQiandao/Dockerfile @@ -0,0 +1,42 @@ +FROM python:3.11-slim + +ARG TARGETARCH +WORKDIR /app + +# Playwright/Chrome runtime deps. +RUN apt-get update \ + && apt-get install -y --no-install-recommends \ + ca-certificates curl fonts-liberation gnupg wget \ + libasound2 libatk-bridge2.0-0 libatk1.0-0 libcups2 libdbus-1-3 \ + libdrm2 libgbm1 libgtk-3-0 libnspr4 libnss3 libx11-xcb1 \ + libxcomposite1 libxdamage1 libxfixes3 libxkbcommon0 libxrandr2 \ + && rm -rf /var/lib/apt/lists/* + +# Install browser: Google Chrome on amd64, Chromium on arm64/others. +RUN set -eux; \ + if [ "${TARGETARCH}" = "amd64" ]; then \ + wget -q -O - https://dl.google.com/linux/linux_signing_key.pub | gpg --dearmor -o /usr/share/keyrings/google-linux.gpg; \ + echo "deb [arch=amd64 signed-by=/usr/share/keyrings/google-linux.gpg] http://dl.google.com/linux/chrome/deb/ stable main" > /etc/apt/sources.list.d/google-chrome.list; \ + apt-get update; \ + apt-get install -y --no-install-recommends google-chrome-stable; \ + rm -rf /var/lib/apt/lists/*; \ + ln -sf /usr/bin/google-chrome /usr/bin/container-browser; \ + else \ + apt-get update; \ + apt-get install -y --no-install-recommends chromium || apt-get install -y --no-install-recommends chromium-browser; \ + rm -rf /var/lib/apt/lists/*; \ + if command -v chromium >/dev/null 2>&1; then ln -sf "$(command -v chromium)" /usr/bin/container-browser; else ln -sf "$(command -v chromium-browser)" /usr/bin/container-browser; fi; \ + fi + +COPY requirements.txt ./ +RUN pip install --no-cache-dir --upgrade pip \ + && pip install --no-cache-dir -r requirements.txt \ + && python -m playwright install-deps chromium + +COPY . . + +ENV CI=true \ + HEADLESS=true \ + CHROME_BINARY_PATH=/usr/bin/container-browser + +CMD ["python", "main.py"] diff --git a/SakuraFrpQiandao/LICENSE b/SakuraFrpQiandao/LICENSE new file mode 100644 index 0000000..1121860 --- /dev/null +++ b/SakuraFrpQiandao/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Yumu_Tudou + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/SakuraFrpQiandao/README.md b/SakuraFrpQiandao/README.md new file mode 100644 index 0000000..6c399e3 --- /dev/null +++ b/SakuraFrpQiandao/README.md @@ -0,0 +1,171 @@ +# SakuraFrp 自动签到脚本 + +使用 Playwright 驱动浏览器,并通过 AI 视觉识别完成 SakuraFrp 九宫格验证码签到。 + +## 功能特性 + +- 自动登录 SakuraFrp 账户 +- AI 视觉识别九宫格验证码 +- 自动点击匹配的验证码格子 +- 失败自动重试,默认最多 10 次 +- 支持 GitHub Actions 定时执行 +- 支持邮件通知和日志归档 + +## 本地运行 + +### 1. 环境准备 + +```bash +python -m venv .venv +.venv\Scripts\activate +pip install -r requirements.txt +``` + +本项目默认优先使用本机已安装的 Chrome,不需要下载 ChromeDriver,也不强制执行 `playwright install chromium`。 + +如果本机没有 Chrome,或希望使用 Playwright 托管浏览器,可以执行: + +```bash +python -m playwright install chromium +``` + +### 2. 配置环境变量 + +创建 `.env` 文件: + +```env +SAKURAFRP_USER=your_username +SAKURAFRP_PASS=your_password + +BASE_URL=https://api.example.com/v1 +API_KEY=your_api_key +MODEL=your_model_name + +MAX_RETRIES=10 +HEADLESS=false +CHROME_BINARY_PATH= + +# 是否将验证码图片转换为 base64(可选) +# 可接受:true/false, 1/0, yes/no, on/off +# 通过第三方适配器接入的服务可能不支持直接使用图片 URL,此时设为 true。 +IMAGE_AS_BASE64=false +``` + +`CHROME_BINARY_PATH` 可选。留空时会自动查找系统 Chrome;如果你的 Chrome 不在默认位置,可以显式指定: + +```env +CHROME_BINARY_PATH=C:\Program Files\Google\Chrome\Application\chrome.exe +``` + +Playwright 只复用 Chrome 可执行文件,不会读取你日常 Chrome 的用户目录、Cookie、历史记录或扩展。 + +### 3. 运行脚本 + +```bash +python main.py +``` + +## GitHub Actions 部署 + +工作流文件位于 `.github/workflows/sakurafrp_sign.yml`,默认每天北京时间 9:00 执行,也支持手动触发。 + +需要配置以下 Secrets: + +| 名称 | 说明 | +| --- | --- | +| `SAKURAFRP_USER` | SakuraFrp 用户名 | +| `SAKURAFRP_PASS` | SakuraFrp 密码 | +| `BASE_URL` | OpenAI 兼容 API 地址 | +| `API_KEY` | API 密钥 | +| `MODEL` | 多模态模型名称 | + +邮件通知为可选配置: + +| 名称 | 说明 | +| --- | --- | +| `EMAIL_USERNAME` | 发件邮箱 | +| `EMAIL_PASSWORD` | 邮箱授权码或应用密码 | +| `RECEIVER_EMAIL` | 收件邮箱,默认发给自己 | +| `SMTP_SERVER` | SMTP 服务器,默认 `smtp.gmail.com` | +| `SMTP_PORT` | SMTP 端口,默认 `587` | + +Actions 使用 GitHub runner 自带 Chrome,并执行 `python -m playwright install-deps chromium` 安装 Linux 运行依赖,避免每次下载 Playwright Chromium 大包。 + +## 项目结构 + +```text +SakuraFrp-Qiandao/ +├── .github/workflows/sakurafrp_sign.yml +├── automation.py # Playwright 签到主流程 +├── captcha_handler.py # GeeTest 验证码识别和点击 +├── config.py # 环境变量和日志配置 +├── human_simulator.py # 简单人类操作模拟 +├── main.py # 程序入口 +├── send_email.py # 邮件通知 +├── requirements.txt # Python 依赖 +└── README.md +``` + +## 配置说明 + +### 定时任务时间 + +修改 `.github/workflows/sakurafrp_sign.yml` 中的 cron 表达式: + +```yaml +schedule: + - cron: '0 1 * * *' # UTC 1:00 = 北京时间 9:00 +``` + +常用时间对照: +- `0 1 * * *` - 每天 9:00(北京时间) +- `0 13 * * *` - 每天 21:00(北京时间) +- `0 1,13 * * *` - 每天 9:00 和 21:00 + +### AI 模型推荐 + +支持任何兼容 OpenAI API 的多模态模型: + +- OpenAI: `gpt-4o` +- 阿里通义: `qwen-vl-plus`, `qwen-vl-max` +- 智谱 AI: `glm-4v` +- GitHub Copilot: 可通过第三方 OpenAI 兼容适配层接入 +- ModelScope: 各种开源视觉模型 + +## 故障排查 + +### 验证码识别失败 + +尝试更换更强的视觉模型,或增加 `MAX_RETRIES`。模型需要支持图片输入,并兼容 OpenAI Chat Completions API。 + +### 模型返回非标准响应 + +脚本会记录完整响应结构。如果响应中包含 `error` 或 `errors`,会直接终止,因为刷新验证码无法修复模型 API 错误。 + +### Playwright 下载 Chromium 失败 + +如果出现 `timed out`、`ECONNRESET` 或 `Failed to download Chrome for Testing`,通常是网络到 Playwright/CDN 下载源不稳定。推荐直接使用系统 Chrome,并在 `.env` 中按需设置 `CHROME_BINARY_PATH`。 + +### 登录失败 + +检查 `SAKURAFRP_USER` 和 `SAKURAFRP_PASS` 是否正确,必要时关闭无头模式查看页面行为: + +```env +HEADLESS=false +``` + +### 未收到邮件 + +确认邮箱开启 SMTP,并使用授权码或应用专用密码,不要直接使用普通登录密码。 + +## 注意事项 + +请勿频繁触发签到,避免被限流。妥善保管账号密码、API Key 和邮箱授权码。 + +## 相关链接 + +- [SakuraFrp 官网](https://www.natfrp.com/) +- [Playwright 文档](https://playwright.dev/python/) +- [GitHub Actions 文档](https://docs.github.com/en/actions) +- [copilot-api](https://github.com/caozhiyuan/copilot-api) +- [LiteLLM](https://github.com/BerriAI/litellm) diff --git a/SakuraFrpQiandao/automation.py b/SakuraFrpQiandao/automation.py new file mode 100644 index 0000000..e7c4205 --- /dev/null +++ b/SakuraFrpQiandao/automation.py @@ -0,0 +1,256 @@ +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 diff --git a/SakuraFrpQiandao/captcha_handler.py b/SakuraFrpQiandao/captcha_handler.py new file mode 100644 index 0000000..ede2250 --- /dev/null +++ b/SakuraFrpQiandao/captcha_handler.py @@ -0,0 +1,298 @@ +import logging +import time +import random +import json +import re +from typing import Optional, Dict + +from openai import OpenAI +from playwright.sync_api import Page +from playwright.sync_api import TimeoutError as PlaywrightTimeoutError +from config import Config + +logger = logging.getLogger(__name__) + + +class ModelApiError(Exception): + """模型 API 错误,属于不可重试错误。""" + + +class CaptchaHandler: + """验证码处理器""" + + def __init__(self, config: Config): + self.config = config + self.client = OpenAI( + base_url=config.base_url, + api_key=config.api_key + ) + + def get_img(self, page: Page): + try: + # 获取验证码图片 + captcha_img_element = page.locator(".geetest_tip_img") + captcha_img_element.wait_for(state="visible", timeout=10000) + + # 获取 CSS 属性 + bg_style = captcha_img_element.evaluate( + "el => window.getComputedStyle(el).backgroundImage" + ) + + # 正则匹配 + match = re.search(r'url\(["\']?(.*?)["\']?\)', bg_style) + if match: + img_url = match.group(1) + logger.info(f"成功获取验证码图片 URL: {img_url}") + return img_url + else: + logger.error("无法提取验证码图片 URL") + return "" + except PlaywrightTimeoutError: + logger.info("未检测到 GeeTest 验证码窗口") + return False + + def handle_geetest_captcha(self, page: Page) -> bool: + """处理 GeeTest 九宫格验证码(带重试机制)""" + logger.info("开始处理 GeeTest 验证码...") + + try: + # 获取验证码图片 + img_url = self.get_img(page) + if not img_url: + logger.error("图片获取失败,刷新网页重试...") + time.sleep(2) + return False + + # 调用视觉模型识别,imagebase64 由配置控制 + recognition_result = self._recognize_captcha(img_url, imagebase64=self.config.image_as_base64) + if not recognition_result: + logger.warning("识别失败,刷新网页重试...") + time.sleep(2) + return False + + logger.info(f"验证码识别结果: {recognition_result}") + + # 根据识别结果点击相应的九宫格 + if not self._click_captcha_items(page, recognition_result): + logger.warning("点击失败,刷新网页重试...") + time.sleep(2) + return False + + logger.warning("验证码流程完成,刷新网页验证是否成功...") + time.sleep(2) + return True + except ModelApiError: + raise + except Exception as e: + logger.error(f"处理验证码时发生错误: {e}", exc_info=True) + return False + + def _recognize_captcha(self, img_url: str, imagebase64: bool = False) -> Optional[Dict]: + """使用视觉模型识别验证码,支持 imagebase64 方式""" + try: + prompt = ( + '这是一个九宫格验证码,请按从左到右、从上到下的顺序识别每个格子里的物品名称,' + '最后识别左下角的参考图。输出格式为JSON:{"1":"名称", "2":"名称", ..., "10":"参考图名称"}。' + '名称要简洁,参考图名称必须是九宫格里已有的名称。若有类似物品(如气球与热气球),请统一名称。' + ) + image_url_value = img_url + if imagebase64: + import requests, base64 + try: + resp = requests.get(img_url) + resp.raise_for_status() + img_bytes = resp.content + img_b64 = base64.b64encode(img_bytes).decode('utf-8') + image_url_value = f"data:image/jpeg;base64,{img_b64}" + except Exception as e: + logger.error(f"图片转base64失败: {e}") + raise ModelApiError("图片转base64失败") from e + response = self.client.chat.completions.create( + model=self.config.model, + messages=[{ + 'role': 'user', + 'content': [ + {'type': 'text', 'text': prompt}, + {'type': 'image_url', 'image_url': {'url': image_url_value}} + ] + }], + stream=False + ) + + response_data = response.model_dump() if hasattr(response, "model_dump") else {} + logger.debug(f"模型完整响应: {response_data}") + + for error_key in ("error", "errors"): + if response_data.get(error_key): + logger.error(f"模型 API 返回错误: {response_data[error_key]}") + raise ModelApiError("模型 API 返回错误") + + choices = response_data.get("choices") or [] + if not choices: + logger.error(f"模型 API 返回非标准成功响应,缺少 choices: {response_data}") + raise ModelApiError("模型 API 返回非标准成功响应") + + result_content = choices[0].get("message", {}).get("content") + if not result_content: + logger.error(f"模型 API 响应缺少 message.content: {response_data}") + raise ModelApiError("模型 API 响应缺少 message.content") + + logger.info(f"模型原始输出: {result_content}") + json_text = self._extract_json(result_content) + if not json_text: + logger.error("无法从模型输出中提取有效 JSON") + raise ModelApiError("模型输出不包含有效 JSON") + + cleaned_str = json_text.replace("'", '"').strip() + + result_dict = json.loads(cleaned_str) + if isinstance(result_dict, dict) and result_dict.get("error"): + logger.error(f"模型 API 返回错误: {result_dict['error']}") + raise ModelApiError("模型 API 返回错误") + + return result_dict + + except json.JSONDecodeError as e: + logger.error(f"JSON 解析失败: {e}") + raise ModelApiError("模型输出 JSON 解析失败") from e + except Exception as e: + if isinstance(e, ModelApiError): + raise + status_code = getattr(e, "status_code", None) + if status_code: + logger.error(f"模型 API 调用失败,HTTP {status_code}: {e}") + else: + logger.error(f"模型 API 调用失败: {e}", exc_info=True) + raise ModelApiError("模型 API 调用失败") from e + + @staticmethod + def _extract_json(text: str) -> Optional[str]: + """从模型输出中提取 JSON,兼容代码块、Qwen box 标记和普通说明文本。""" + if not isinstance(text, str): + text = str(text) + + box_match = re.search(r'<\|begin_of_box\|>(.*?)<\|end_of_box\|>', text, flags=re.DOTALL) + if box_match: + return box_match.group(1).strip() + + code_block_match = re.search(r'```(?:json)?\s*(\{.*?\})\s*```', text, flags=re.DOTALL) + if code_block_match: + return code_block_match.group(1).strip() + + start = text.find('{') + if start == -1: + return None + + stack = 0 + for i in range(start, len(text)): + if text[i] == '{': + stack += 1 + elif text[i] == '}': + stack -= 1 + if stack == 0: + return text[start:i + 1].strip() + + return None + + def _click_captcha_items(self, page: Page, recognition_result: Dict) -> bool: + """ + 根据识别结果点击九宫格中匹配的格子 + + 九宫格布局(索引从1开始): + 1 2 3 + 4 5 6 + 7 8 9 + + 第10个是参考图(左下角) + """ + try: + # 获取参考图名称(第10个元素) + target_name = recognition_result.get("10", "").strip() + if not target_name: + logger.error("未能从识别结果中获取参考图名称") + return False + + logger.info(f"目标物品: {target_name}") + + # 获取所有九宫格元素(前9个) + grid_items = page.locator(".geetest_item") + item_count = grid_items.count() + + # 排除最后一个(参考图),只处理前9个 + if item_count < 9: + logger.error(f"九宫格元素数量不足,只找到 {item_count} 个") + return False + + # 遍历前9个格子,找到匹配的物品并点击 + clicked_count = 0 + for i in range(9): + position = i + 1 # 位置索引从1开始 + item_name = recognition_result.get(str(position), "").strip() + + logger.info(f"位置 {position}: {item_name}") + + # 如果当前格子的物品名称匹配参考图 + if item_name and item_name == target_name: + logger.info(f"找到匹配项!位置 {position} - {item_name}") + + # 点击该格子 + try: + grid_items.nth(i).click() + clicked_count += 1 + logger.info(f"已点击位置 {position}") + + # 点击后短暂等待,模拟人类操作 + time.sleep(random.uniform(0.3, 0.6)) + + except Exception as e: + logger.error(f"点击位置 {position} 时出错: {e}") + + if clicked_count == 0: + logger.warning(f"未找到匹配 '{target_name}' 的格子") + return False + + logger.info(f"共点击了 {clicked_count} 个匹配的格子") + + # 点击完成后,查找并点击确认按钮 + try: + # 等待确认按钮变为可用状态(移除 geetest_disable 类) + confirm_button = page.locator(".geetest_commit") + confirm_button.wait_for(state="attached", timeout=5000) + + # 检查按钮是否可用(没有 geetest_disable 类) + button_classes = confirm_button.get_attribute("class") or "" + logger.info(f"确认按钮状态: {button_classes}") + + # 等待按钮变为可点击状态(最多等待3秒) + max_wait = 3 + start = time.time() + while "geetest_disable" in (confirm_button.get_attribute("class") or ""): + if time.time() - start > max_wait: + logger.warning("确认按钮未激活,但仍尝试点击") + break + time.sleep(0.2) + + logger.info("找到确认按钮,准备点击...") + confirm_button.click(force=True) + logger.info("已点击确认按钮") + time.sleep(1) + except PlaywrightTimeoutError: + logger.info("未找到确认按钮,可能自动提交") + + return True + + except Exception as e: + logger.error(f"点击验证码格子时发生错误: {e}", exc_info=True) + return False + + def _refresh_captcha(self, page: Page) -> bool: + """刷新验证码""" + try: + logger.info("正在刷新验证码...") + page.locator(".geetest_refresh").click() + logger.info("已点击刷新按钮") + time.sleep(1.5) # 等待新验证码加载 + return True + except Exception as e: + logger.error(f"刷新验证码失败: {e}") + return False diff --git a/SakuraFrpQiandao/config.py b/SakuraFrpQiandao/config.py new file mode 100644 index 0000000..c4132f7 --- /dev/null +++ b/SakuraFrpQiandao/config.py @@ -0,0 +1,61 @@ +import logging +import os +from typing import Optional +from dataclasses import dataclass + +# 尝试加载 .env 文件 +try: + from dotenv import load_dotenv + load_dotenv(override=True) +except ImportError: + pass + +# 配置日志 +logging.basicConfig( + level=logging.INFO, + format='%(asctime)s - %(levelname)s - %(message)s', + handlers=[ + logging.StreamHandler(), + logging.FileHandler('checkin.log', encoding='utf-8') + ] +) +logger = logging.getLogger(__name__) + + +@dataclass +class Config: + """配置数据类""" + sakurafrp_user: str + sakurafrp_pass: str + base_url: str + api_key: str + model: str + chrome_binary_path: Optional[str] = None + max_retries: int = 10 + image_as_base64: bool = False + + @classmethod + def from_env(cls) -> 'Config': + """从环境变量加载配置""" + def get_env(key: str, required: bool = True) -> str: + value = os.environ.get(key, "").split('\n')[0].strip() + if required and not value: + raise ValueError(f"环境变量 {key} 未设置或为空") + return value + + def get_bool_env(key: str, default: bool = False) -> bool: + raw_value = os.environ.get(key, "").strip().lower() + if not raw_value: + return default + return raw_value in {"1", "true", "yes", "on"} + + return cls( + sakurafrp_user=get_env("SAKURAFRP_USER"), + sakurafrp_pass=get_env("SAKURAFRP_PASS"), + base_url=get_env("BASE_URL"), + api_key=get_env("API_KEY"), + model=get_env("MODEL"), + chrome_binary_path=get_env("CHROME_BINARY_PATH", required=False), + max_retries=int(get_env("MAX_RETRIES", required=False) or 10), + image_as_base64=get_bool_env("IMAGE_AS_BASE64", default=False) + ) diff --git a/SakuraFrpQiandao/human_simulator.py b/SakuraFrpQiandao/human_simulator.py new file mode 100644 index 0000000..6c6b6d5 --- /dev/null +++ b/SakuraFrpQiandao/human_simulator.py @@ -0,0 +1,18 @@ +import time +import random + + +class HumanSimulator: + """模拟人类行为""" + + @staticmethod + def type_text(element, text: str, min_delay: float = 0.05, max_delay: float = 0.2): + """模拟人类打字""" + for char in text: + element.type(char, delay=0) + time.sleep(random.uniform(min_delay, max_delay)) + + @staticmethod + def random_sleep(min_sec: float = 1.0, max_sec: float = 3.0): + """随机等待""" + time.sleep(random.uniform(min_sec, max_sec)) diff --git a/SakuraFrpQiandao/main.py b/SakuraFrpQiandao/main.py new file mode 100644 index 0000000..f650616 --- /dev/null +++ b/SakuraFrpQiandao/main.py @@ -0,0 +1,26 @@ +from config import Config +from automation import CheckInAutomation +import logging + +logger = logging.getLogger(__name__) + + +def main(): + """主函数""" + try: + # 加载配置 + config = Config.from_env() + logger.info(f"使用账户: {config.sakurafrp_user}") + + # 执行自动签到 + automation = CheckInAutomation(config) + automation.run() + + except ValueError as e: + logger.error(f"配置错误: {e}") + except Exception as e: + logger.error(f"程序执行失败: {e}", exc_info=True) + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/SakuraFrpQiandao/requirements.txt b/SakuraFrpQiandao/requirements.txt new file mode 100644 index 0000000..4928919 --- /dev/null +++ b/SakuraFrpQiandao/requirements.txt @@ -0,0 +1,14 @@ +# Browser automation +playwright>=1.46.0 + +# API client +openai>=1.0.0 + +# Environment variables +python-dotenv==1.0.0 + +# Packaging +setuptools>=65.0.0 + +# Utilities +requests>=2.31.0 diff --git a/SakuraFrpQiandao/send_email.py b/SakuraFrpQiandao/send_email.py new file mode 100644 index 0000000..492981c --- /dev/null +++ b/SakuraFrpQiandao/send_email.py @@ -0,0 +1,96 @@ +import smtplib +import os +from email.mime.text import MIMEText +from email.mime.multipart import MIMEMultipart +from email.mime.base import MIMEBase +from email import encoders +from datetime import datetime + + +def send_log_email(log_file='checkin.log'): + """ + 发送签到日志邮件 + + Args: + log_file: 日志文件路径 + """ + # 从环境变量读取配置 + smtp_server = os.getenv('SMTP_SERVER', 'smtp.gmail.com') + smtp_port = int(os.getenv('SMTP_PORT', '587')) + sender_email = os.getenv('EMAIL_USERNAME', '') + sender_password = os.getenv('EMAIL_PASSWORD', '') + receiver_email = os.getenv('RECEIVER_EMAIL', sender_email) + + # 检查配置 + if not sender_email or not sender_password: + print("❌ 邮件配置未设置,跳过发送") + return False + + try: + # 读取日志内容 + log_content = "" + if os.path.exists(log_file): + with open(log_file, 'r', encoding='utf-8') as f: + log_content = f.read() + else: + log_content = "日志文件不存在" + + # 判断签到是否成功(根据日志内容) + is_success = "签到流程完成" in log_content or "验证码验证成功" in log_content + status_emoji = "✅" if is_success else "❌" + status_text = "成功" if is_success else "失败" + + # 创建邮件 + msg = MIMEMultipart() + msg['From'] = sender_email + msg['To'] = receiver_email + msg['Subject'] = f"{status_emoji} SakuraFrp 签到{status_text} - {datetime.now().strftime('%Y-%m-%d')}" + + # 邮件正文 + body = f""" +SakuraFrp 自动签到报告 + +状态: {status_emoji} {status_text} +时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')} + +{'='*50} +日志内容: +{'='*50} + +{log_content[-2000:] if len(log_content) > 2000 else log_content} + +{'='*50} +此邮件由自动签到系统发送 + """ + + msg.attach(MIMEText(body, 'plain', 'utf-8')) + + # 添加日志附件 + if os.path.exists(log_file): + with open(log_file, 'rb') as f: + part = MIMEBase('application', 'octet-stream') + part.set_payload(f.read()) + encoders.encode_base64(part) + part.add_header('Content-Disposition', f'attachment; filename= {os.path.basename(log_file)}') + msg.attach(part) + + if smtp_port == 465: + server = smtplib.SMTP_SSL(smtp_server, smtp_port) + else: + server = smtplib.SMTP(smtp_server, smtp_port) + server.starttls() + + with server: + server.login(sender_email, sender_password) + server.send_message(msg) + + print(f"✅ 邮件发送成功: {receiver_email}") + return True + + except Exception as e: + print(f"❌ 邮件发送失败: {e}") + return False + + +if __name__ == "__main__": + send_log_email() diff --git a/bg.png b/bg.png new file mode 100644 index 0000000000000000000000000000000000000000..8fa835a077e27c752869427836bd76170b537768 GIT binary patch literal 54063 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%0AeWw-#$gHo~BlqH%VM6W*o9GDG* z{s5$Ge=tC&sN)dBvv_N8s26e}7o0ZGo+4s}*3m&Gf)VX@dv11SZFOaKcD~o?4&?PB zHAv}PDttbU=n^ZFIC9|MlC^KN)dDW+fMAx92l~$2Z~yoIyZ`Od;sT)mTuw9{k^XNn zN1#qe@B=9o3iGpbphIL$qsaf`B5ig6y5jWNv*0d(U~?f4ps_EC7=!TQ#q*%J`op1c zC<}n3Vm*qNxguOx2MDr)ni!6U7*J8$uj;IXizzYndU{*b&oW<#AkA`Vsb{HHGlqt>4neYl{JU z{ci2-+`hB5wzdioALa4k(a|%PFM+WO8UUpGqeoAOsAgJz`K1@$e&_us8ylT=MYc+P z68I6sxoMS;pZc1#qAwYK=~wVn5ll?7SNUT;!gHrTxG{G_U;J> zI!qx7Vq&tkq5f1W`Q#TOa-tQ2{Y9isLb8a(8U`o!2{rOlOoGC*8l0!JBsH8v^h5Lu z;44d{CE_T45Jscb=My3L$u-b`6Tk?rlotX2NO~1f&$X46&F$^mckjcO!OFxYP2fj@ z_3v~$;9>&C0F5CDTqL5XignBc$O@Y@nkN6AM1X1_#+M8R?mzo)&tJch{P~|hSl?WS z_{6z$=X=coHoYcx16}$0?QehQ-FM%C;0AbviKS%W62NbRVgHj)M6GGLT=?OSe}Wxu zl#fvEM=qZSRRHcRPz8;dnR2CU#<;Ms07e3^0jRlFtA%~tfOCM#K~q62WMN^kpi5N{ z;7YZOz7B%*%yP0W%{C#Y7QTP;%{Spb7cQKa`HpsSiM3Yz)!IBCY2!mYseafZMjOZ8Q z0QLZX$ld!7wzhX(d_MW=Yp?F@?+ah5<0=kNT83+llN!kuP*!U(800$Le3%oN%(aUD zyT7}%xyd%RyfbIcT)zA)SQ=ZKTV@h?mt7&7d%ykLzg4Z4KK|&ld-v~|wKhxR`yRM= ze&aX3?)xtISTU(xeJVSG0S`w})DWKGdaYhJ{$@=21zLyD9+2|f+&oNneSQ7#-~hDh z)vGV=OHBeoeM&;;g_hL7X_@)0zSG7q}Foxb%2^ywW^t=Ajfes^zo5A1j_uTA0*@Eh1gu%m#ZdOhV_1~dhZF9Yz>2HgY}Jr`w!@o^~%GCj|1OZTU!C6_Ah_%<0y);zs;$&)%p23Kt~^Z z^bvsae!mAnv-OQlncCpV(Nl23$_fkm-UlDb^iF}T0f4>x-ut>Mks2kquPBX!AgYwc z5V8P^{fp~|V7Guu0`Uyd05|KdhEVaFZ~o%u&0ARd2ot+==MG48S?#J`z_j1^#<#)3 zdF!pWebFz-$#}g*VU!1^Fh4wi=!d9E2m+v8u%zeb7vQ0wRYZ#s8IT5lG3XCaBiFCr z03UI0(1&O3>~4cN@WF>BL(X4Zzb;jdbN%`ko_rC}vVQ>nSzi{QO;H1^KqAcx(5MAb zlj*76*I!CQB&DJ39c_q@o^`#U6m!Xr-RJmArArt%!B5aKoTpI^@|4#UkD)AhnsT7h zJM2vHJlBr2%nZ5Wcl}bR&&p0n)scS3Zg`V!zKymOK(jT2H54Y2D@18*E({r-JRs+P z?#vnRtNMXIH`4$TymkAoD@dFU`a>WBFO(^!$a8WKfv@;ah$uYF000mGNkloRNgg5SntE)oT!k%-PF(LfdeRjLh4ZQ$AElLf@ZcDpM)VxG#LKw8Y;F&h-goM#LX(h30Y2W#8Zh( zt8{f;dLd13D?^zqtq7yq^*VJUJGn#a0Za)a>QoNZMxRf88yz;_uAv@3DQm+JpLvhc zFUv>bX|D=fd8Q<8JsAHOFzK4pH0^T?)j9~@HKx*706ld;X@52)<_ejpXB^b}OA^qk zAQ2H|DE#5$Cm($H@##}5jb266rFq_aAAEHC&iz0pZ>nPw4N_B1JMGodP!+4nux?p6 z5j=@xlr6vg!VPZjvS_RcFNtR>ke01?_S%R!3WSGyD%9Rg4{`lQ@ z-iJT|cwjZ!B-C0nA`?_)W-)ps#YRG8?au8xON-0pa!-=I`}yZzfcH#hY>T9K;-uK! zc9oonB~+wY%OZ4r5|!vko$Q%6%9t`cMyNj2Ss`1hO-=1&%uYrDYtbIYai|GBP~~!@ zdIeHXzw8PqIm?mf7YZW6k6 z;mA~!LJ4l2UzCy>)rF2z%7-YAMVfEzU;{j=(2is=t)K`YN)~h3xe25|0)GNG$%kSU z#!)(na!Lc!VJQ&Vd3qqK%HxpOfB`9aB$*7Fo*OOAEzQi-y5neLr_~;e zMPDj$wv%mAW2+7aaRrw9h%Obfn~5|mrIr)#yMLdhFni!+ENnSPixVtBn0|}NzB`6S z9aY2W$S^3eD%{Xtlt#F1u9MLW%?z9)@FEJKnnmX{s~o6WXT$N#s{7DiQ~&C%BL-Ge zRf@tbcHk;V&4XZ^%MAGBPKdNq=l9Q~DtMSR{zbggk>j z9U~*P$N#uQ*9mo;YFuTDH)59Q*uH*=@UyN%(@8`&UwGk#ix)1G%H_?iP31~b%d9L4 zy_`lo(KHnT%>Y%%VOOp=HX1}?;F2CB;X2axVv<)lDBoebJ(~1FK5F;&y`{opX=ctchinhD)j)5fu;h{>4e6N$B5}syjx<7o@6bcbl1sORGUK? zsx|(h6A-DIuq)3yq!t+C#M&vzuv#U^<_2b9);e2dpCtRzoWON!4UT1XLZ)E}uCz3q z%<+^wRgQXNRb7_671eT7F@oSi@t5yU!T|on`8@fb|LpMaFgH6Rdfj>Qf7w3qxPZ)V zH%VQ1#1bb&>0D)JbNPG$!e(I z{`R+Fb~}5!VA#Z}C{z!IScm`Wlk1U`3DJ3N$0!U12c3!}IEeD)kW|@|Fi||=YhgQE z8{^jQ*@bG3#-rx`*4@eN+jr(yFV580jv(sddI_4J5(p3-;X*i1WSapN4S5oKBm`#) z7d@}wzCOb}KPDQ|^-ry2ssn=QVy06Q?V%8VWj4!ft70S9G04Yp$gwAjL{C*NovjF^ zP6_}$N)-k`&6_SFQIwA_O3h)W1l0yftpPJhqRc&YgA_&~3Sn5xX$)(v9>9zQdrTA{ zd;XIrNxdG3BPs&nvyv#u(K9Ds@_L$J#t{hR;Hs0%Wm^E*_a(zu8Dp29MDCUh&a#LuAmLu8C4rop(=;DH)!M8-xe0Ycao3$*R4wlC`GyjJ;Mw zLcvk5wf^v4kxura_Jy?>G8uIbCv-H}d3dijaoE@oiy;u2iYnm5sZ974CO4R>$z<4b z**E~M7>mZ-6QHP|;P@V+I6$RkrzfJ^B4SS+tQO>i(=Xd#d#MRc<&?RfB2{D-O6(jh zYOGq>C6$w`$4-Cl7??zfh55BS)Ius%6gGB3T4u@w;6%HJ^hw~U1WjpQBW8IW(@#L4 z3!Iq~R*CE?C|&UASv_er)c`XudMxm1D_n&^d5TlhPp5B84LDKKOIvR|d)ueceTMou zpB^x#6a(o*5ybBxaG@leLygJqoGeq*UzGF77INY%8vE(@tQIH4yf2#{fiQJ;w>COQ zdp>1>%Zt8fPzBEH`9|GMCNvrWH@E@<#g4(*vgH{9Jy;P4{x|oxqd}(}P>&5Ky;i?< zG#PYx5;Gdx3`_BYr*2CL z7W3KeP;QJ%uDq54L{NtCOB?KtHkPSkD50k~iYAg;6UyLIzCkQ+W2x$q;}kh4-b)T&a~RJ;k!-GUzP&EHDr>#=^VXi+CjlHoKGxULP-v;_+8aTb4reJ!W1-0 zpO}t;DbnOJtd(f|C~Q7So+x{D$1|Pbo%#ul2}4wNzFK6G#QssA}nw zOrp-=!Jyr&(1Ocy(PYRbqtPH4$E+L_!=Rv((Q&OXS62f_^o^RcXsIf3AX!$>Vk=!( znsHbo-Qwn`{45y_bhOWCT4xF?hbMBG)wpoUQM5>dD8<=SeT8JCuBy$~9lE*drlH=+ z2xo@I0MP=F>ka`@S7ezjm^HfDMVYzA>RnZVvAuxn9PTKQDAsj6?2|As8*E!tZ+22|ZXfo;vZo^$1G_c9v$rN>JaWs?IP}k%?sL?;EMhgf$Iwh7H(QZgkK^lrDG_F>#A**epOa1Y7$4~ zpqq+fTME^YLN1TpR4+qc<~6-oSsiKK7hyf6lC%>>`rRl$xpKgMp*z8T9UsZm@Qw{h z{;U=z6%h(aV#~YiLru9SO{>NTNxe+Sno~&=sZ3QbhidIDrH(k{?%~eXdJ>K2W*e{+ z{eHjEn3T}{VTVwb9QFdCQ>GOf>#BV%r@up;#=1~zPFA8|`5omwSgi7M-H*yog{ zNyug+bpVem22hfa=m`jSnh1blTWV~!DGN&!%X6-DPdW>#9Z0IDhe-#KjK_Fd&8Phe zde>zoMLIxgZhX?~O9(MtI|Ws#4Yw5IvNYe5TmFj7J=JuqU+AwHhIxeUyT-ADEy+HM zPq`FH)+tGq|3(Xk6wak1;`S4@7}oqCV-i}03T8~!A3qt7h6&3rEiJDuHY(N9D2l-s z1H$e=0V4|K8<{B%j2r)Kp!4^hT=h(n45=kKrcA=H3hoa<`Zez9bsPDCoQv0KV zDJ&%g#S>DU>8~2iVKfXLEuq)Q;ZJwxXI9yM-%+U|T-714DFLh=2pcBS8WLQ3%_c^F z8ZoRm*M1N^o_~?vT)MN24v@3519>F%!l^xwxqS$W6^TeFC*c!0RxPBlhn7YPL*w;0 zeu`-pl1Y3IJVpahk@Vq!;#{&)lgN}*gju@5~CzDAck5Qvknwa9nP{OI!Y)0I0 zS@h>*ro&aXpI%H=NoW>UO;yWBvy&*=ShmdtRc4Ob2qQ{VltaBQvsb*SJ(GVYoJGI_ z5Ru0d;Uxh%T0_Wdq?Zyi3l#)Z^zG<}OS+K*wo=!bC54%$`1D`GP1c=(c8(2QY(Zkb zY#y*_(Uvh>`5Wnpl3V>CHI-^z#m#q9?WrRsMh{K_q4Y zJT()krDdsMw<#TB&6j3bgJ`<~voZuTfnQ=c?C);wwh#B_8ujN+&0bhr>>h3hxx#Rg z931QbQG~@x)E;&RV>foA(J1dZ-J`uiLs$#VqvqDm{^3EBfq4)Ht{09*ac?jJ(3Qx> zP*ijYDH7iog-VG$AO}G*)*gs}7e_)s$BT5rP=ovkq6pLWi>4trYd=4a+yDR&07*na zRErtOVkG^8uJX|b6xz#T_Jw3$MsYZ*>{CAy|K%hWXSj$~liZK0-Re3L2gB@GW_LJ(pcua4DD#YySOCb;S3oWIY@rZXnh zLiS_pN94^DkrGm&=3ew{JC-rSGkzrXTl$fTU=p1-!JQLyh!Tg=N=`MBkpX5%CoF*4 zbc%+fatBNQnaq#iU>P4C>|gujg@T! zDDDhKjasGdhKIcoK=H$){q6OwqxK*k#}ES&$8C)#F6BpUKtyg7F&|KZPh3BQm=1_i zpdN@EIfUBv>w?NTdcz!^NXM7!Fy%HdqCtvxixV=l)O@Z3ZiJ+gMJY&oQzc|EE2K(} zQGlzh(~#2BYbYLz%|tmHCu|gpglZx?IH=k&Y8-Y{=F2n3_;U#+dlQokmMSJ{#TT(R zjLgwvS*3t%fgu=2%}1&i1>mGtkn8LnqRUXouX|ZiHU;R8JS@jV9+6^9eJf_h8=6am zcr(HrYyZs`86la2VQekIvSMDB3RstDpSSY4s3)KjR*nVRhA}wSJdKd7)U$ur9|~nH z#{ecViznAU`RLa5FRI0`URf*TfQIRC(3knS;LOg~8udn_KJ53yFsK)+32pUzy>UD& zg`R-SqyDJZg9vlz`?*{&>b9decKy;Q9wjm?HyKTc8xfZR$_zw1cO}BruFHeZ|5#VgzD2HoYduGG%{u)n_jbeRO&bybd`y#g*AdM9qp?n#!SF; zK;6w(I|_CGXPmjTdz80+iVTiXq#CC^Yl)o{vvrW4>wkwPRmjO3yeGZjg9raovlhSx3)AFxU60+)begV3@VjMwOlwl+>c`hKGRCA((Cr3QNK{g z$I-Y_%OA7`5l`|ytrl|5Fy@XgP9ez`!Pt-_?PKG6afBQlfu+qI;Du2yJF1llw;~msr!8gRKi^jey;0>_R=R$Q<}>9N`e{00O{wNheBt6VR1{Xz1 zg;*&E5U*M|@Rv|!#C$Yp}JFc|>7@3r%zSFcRGL8)`?9^$9lTv0pA`Q(3 zi9JE>HNxyAE$Jk00Rj^A6-T4Vu-hN(?d{#XdEKL|(I~GiHYl5%S)Q*31VRL&mc#Lq zm>o5DH#Q$$ICr*OD(~#<(!hhrS{`^J7fgB+V1~unIzMQ~R2)8C0*{G~{y^?Y z;(4JkBn6@-(PRQPy2$(|L>_e}N*4#cF1!man7k)&HE64#P%5#+5hrB0ICL`yLMgW- zE8&^#SL>q}foJ?h($|tQ18PY1ZR8aU@*wJlhb=pwY5FV$3+i z4b2f-YY~lU(xFkqy82D+6{C(xRH3@PDne;cTLHG}2trW8zR?Wwe81EJww^%9loH_6 zbU$?scEr$lLmS9=7_D>~h>{|v`jJrSyh?C$G~{zMpt0HEIOFkY;3U4DD9xZ8DG5^^ zHw!eWStvQi%{!^glTUNeFK9F)&Iqd+7*=XY(E=I|Je}?4(bmo`pdbjVv2hQAY0Gov zQ%emX1`$B(y(>shgXrmwUtU zP@L061J@UabAU35qEayruo-L*(I<{eKYPN){XQ@OEcti>N@6_d_eaBFG#ZRva#DC$JMZzN4B7I^ z6|4}5qGzX&pDZnQlVl8BA&%5#3;$ktR2Z!C^ptG@e2w~>Xch0UZoxgZ) z@6n@;d-v*nK2P|tqoJ4U=ZhmiN&NE&MYeMNmwb^|_19VJr z(`N#RvB2Ns@mSbS1U+-a>hY$bty*zqfIE^cWg}aq;7ozFQCR43fQf@te*fLKAK(1^ z>)-eWNhaOaA%K&Bjn6DLu3R_^LVeh6wT=!!uu~Rad;i_v`uf*qf?%+<^Wc-uKmPRk z?9$TJ-*`Qr%grn-fl>g6YqK}pKkUxcO1Ur`^oOn+g2fzw!5|KF1ye7SF%a+zT-% zJf4l(G)Sp7WYk$wWQyJ`+d<8TSUAACUb?SDs|#U~1#T|Ka=E-bsoTTERhspkO-bbw z-?Yh{tNEXFfo!<^(n~MR&(9YMC6NCfgjcMZ zjF6-t>X_72F%S%qTg-zHO(w2juaqDF-$sBb+}KdnA~`}#TwQ51W`wlD+1ZzgV)B!) z7LkwH#4T6KW(reS$$eF5OM0XXqzH`l|ChjUgc@e<_lP%M66m>n{={;!bCk1 zoBgTWSVc1C0*c47>Q=qJCpbZ#l50YD%iTuLYK z3?E3-jpf-ECe2$s(KQB%OQ*0FA!(wAvH6r|PiB$mm7&srHakd^7emD~uuxgmEEHK=TU({8mOfN)fl}h7DNi_G5l2&inA3+QP z5Ax#T0N@D|`L07Jmvcdj_yF*H?i2u-`CK@}nCpU9!v_a$BK+#u*G5?INxJTYV^CPe za%H-WvBebI7zBZaHM2=WEhO6gb-ab zI}NTh4JtZT8JcPUj*I_!4kacGZ4+(^^;AC+v|Tg7{3$j#4v;dY1(BICTt?tzD8P^x z$?7D<#klxc7Be9;w1%=iK5p)SK^m0x)-K3uM481YqZ?ctBucQfSeXiDBwW=OqJu>_ z!?;?R)L(}H8bn!d(4TKq<7C2E1R({%10;fALi=cMezvi&xKt?CqCsbn50}>#N`Bzc zWYFr2Y4Tv}$shdjAC?yv`@?aw)hriFfDgbH2C|Ri34j*})zz!{UY~>UfE{B2@h>mT z^n1fjw+mvun2`OwVWk3*h~gv~mkZuZEjKKKk(}=i#!t3(4-a~yNhD)gV#F}seBhSz z!cv6!vq_H)Aue79UoE90ab&WWkHj2(F7ldP1OwV9E)5CQH3m;0CYT;zN~EMqlE_0b zbPENBtA}7kiCSzThMlCS+CeL!Oq_8^n`kXZ8Bfut zIixvt326vNP2!}@d)jhR#s|@XHrcPhWijUISRxDa?Da&@bIKg9a&ArU2x=sUWD+X8 znh6l`Xgu!qyK|*-y-)(jk;5mHd12r|Q2F5CaBg7Xvoc?stJf>l zV5Znuncq7)tj^7~hix$8BJK5ngXVBJ;qt^+5c7pR1jS1j5pVT7 zLuWi+^UGntAYvibM%do)u$XvUom$L@Nx|SL6milNFP6%DjaKERV4bHk$N&Hk07*na zRGum7UTRAlt3x1Fhg{g=&E){h9mNbP-7v!psGIOjbbvS(GpPQHR93ZuG{M-@sP8yT&aQ&5*z^INgh-M_yFVa7-pP6fSE#gyDVZ1(WKvL)k*;i zqFg9QJ#^j0Mj<4#d7n2S#KEwNM1)ClzBoO8#3z$}H}PVKQxIW9kkHS`HZH&e-~&$* z!VFH4G(#CP`KKtgrNT9?IE@~uju=s5Xx0*jlzSKyMc`!;&5e!g=q?!IO_`5p(+ACp zeC%0^rG1W$CZv~Tj-Q~J0#?me7R4yXE8{9ZM=lsozwlrG;+eoLSA?q}qWw}Q4@u`M z7VqhArC(3)Y0U%`g3>(M@X!?dX`h~=c$om=VuXUmkV4y?(n;$kl7*5CU^O z;D9bz)o|-dqcY~aP$`1$2CU_|;*j_G*_n;4@o3b|7s5{Ws8*XHGzkMIA5?+Cz+tqp zwVBHm+MQN0mmBh-@Ueo$%*Fsqz&v)?2x3=YeN+k#nJWT642BSJa3BByF$orrCz0qd z5RZpG4f%M~9rj^frCLLP6jwGB1&f0Crah!w7CyDl3bi^4?otsRO3W&q@KRf&+VK*i z;0G~Y86*`K^9Bedw%6O*uk0u?8?XF>nP0Ue)|sbBl3K&7+&eu6>9KUwv<<3pWCptm zOVjC?8qHc7*(s+wfj=ddmqw@Mzt~gbgli2`ku(l!-_x?HwSLpq0w)O z(bGhD{(Lutn8P^A=YxDM1Vdr(ut{8RG8&AAqjD)9h{kge)FaWTLwG2{ZHZF>b;3gU zf6yO8G(u#&vI*=WD1&;rP%Gv^9Y72l1`l37uN1KWLfnp@3lqY1M_sg>a8-<>lNu6I ztYUkjrO0NO5o}CQjW|3bnu%fnVyAo%YCyKNNhc7SP420zRk>*RzJlc3d_fThRU6JN z7Xa6PE97lG2|bN=If%ZWZ0Y1WCyviH9!~sJTZsajL=AwAWG zO`l9-Bo;Yo2oCy8)0G#DtvcCI9U!GzjS!=CxHi6p)adnEH@~>n?;M?8TgnA=&^}1m zcy}uT!?X~3EE++KxCp*$U$k>B6-BInZngown)!wK{rz2tF8N^q;2bRO{&?&I?ds$d zM;-+MEHN4nXJ+eEaL5EA>JTIy4ThfVu`bSdjDa6Q*!IBpqLCUHDW8-gHod zXbrBF`n|iijt+P8J^_zu2_l%Kuq=+Nh(nJQb8feNIQHCyh1tM&8mE?nd_IasVDf?i zP0)SkIrZwycrq#0D$I2NF!^DgFkv?XD9RV|<59oeX|C2*0|<^1G6a9nWHcOr3Bg^F zOyNu><1h>+agxvHXcD;uB4?#S0k{ubL*gN_4HoQl0{R4)O_ctW^GTc>9JXPi5C;U( zDV9oL>lBlux4Y9@e{%5XQK`|$iwIPa3(yrgqEEPIJpSysvzw}{A41Yzeae>5W#E*k zN`JY4QuN45H${J1k7yLFy&`BPFixD~0K;M=st$=#Ry>nA%I-1H!Y!khW?ip5mAlT# z^3z<6$^k8kjM<_e@f3cT8i``yr&uB2lc6Y56yK&F$!?zM)UbG356+A;WAPJo9uXs! zh^bEG1S@If8mr4q!%yjhX4sz&4M*HpDt}W?fQVSDefap%gK{w!5HB9}2N5fU0Mq$Q zxm+puwNkMtWOf2pED?#T)qJTuo{am0&Lob8r;y+WZl=<#l^-Rt+NejvQJF+i9R5cgm*@*Ic|0ll+am?IH{F2%>~Q8NcI)IenU z0D=-Nr1@kx7y)DX`Op;}RS(P>kS)!lHrVkc5c~OjtyZq*!twU@(c_2BM~_&uDLQ!! z`yRyWSPbTmDwpHzY1X4bC|EUTy@|~*L6$i(OBKy9qZdDOb>zxI{~6rlbJ4PSR2moR7_7B69|hFp$pBspYqSLqKJ*y7&~l zEEMAyR3EZHto0C{3{mO9)vwaW!c)f|)%B6s#mlUR=%LL)zbE%^-5m7V@Cbp_2i<7Y z^Bj@;6gWvS@0H78sT71ch(C;}1F_*gI6VOnfxX&nx1u;|EH2~<1=u`sx?Hyxhy+wn z3uExB0_7*kh>%IK64V-ic}NnE0cge1M5I*+GUB6ge-e+PWDGtOFqDgVw^%NTYH|kA zXoyCHA}Zj9oCMK?`d*RJ(Dn0D5{4d zx77>!gJ3dtqR3%Jz>9FCqZ)vV4FZOdmZ)4MCd-Z_7N^_~KHcB!Oo$86mPP*xMcnX&&vyOShhKj}xqr$NV%#!1x zFp1m!9lpdAQ3I3E(G_bdRfy~Iw3Uc?x=_)MX_RfOK=3Fi7{C=YLNfx3csKxY4rXyY zn%ui}b291@aC(mV;cOW~z{7x5N~M0Uo%6xpPsU>^yLCD~i`(r3_!)2x;O|L1VdC`d z2so!$EQVmAMw4hX1W#hM3_2{~(PSdavp`x;g1{Boh7{fa zRUr<=14=KIYn5tg(C>k;7rHHu95+W?<^@5*eKrOt;j=_l`W7I9O@s|y^qeBt?Cn-> zW1X~GtF;EKTnAWf_t32zcz&7W8crUOfs_!ESQX3zJDZDBUo{~}IVS^j5<@$Tqo-RQ zgX?%2F(o?rT6f1d1}LVn1G5suIh)>lJv0!1;eA)!eNMll!1o+fs=1o+40tmGJ&A<2h@NYu;&1{2jN?RE#0PoYQ_=I6Gz;z<;NW^jDjH^YTE zw?7tXOA%8iIymtphJXwpG@rzI&jWSU+}=Le-Je@rI(_EKgz@$Dt%nDP2kYC_T(#O* zav(n(9HttX*DZ2c@g zVo#Tv1bvsp&S+w$rjC#kTEggGG5(ho$(AX?4MCAq<7GRDJe5n$&8QJDM#*rL2~;ka znIKa|)!ZIoI%JcY$T-Q$2-~}m1x{Q&8SECU>sP#^WCs>)Gg>eSe&2jb;Jw8b9u1W6B>&~JZ=PP1;QPUa9az*LmV;|0zwNQBuL!BQETJg{mF2A z@zOK1m!E;4c@$Q!@4fTz;reoU#yd6kb!%XP(I<)RajCf}jnaZvnp^l0)0u3NO5r_i z#hI6GNuM=X^|so51{LBE1=6u$kfXW^xSGL_Gnn!h*}}uopj?%nriL{vDa-k!H{S*_g{TEFH*n`4&jUSMjap!FjBjHbpFDHO066ef}5Z1b^F~j z)#{`_>UZ1Y@d$QuZf>qrtpI8gBq*{dIk15U&q*5*ZUMK9lL)j8hn=-P^wOYlq1gtx1NdN#407*na zRCRa|g5^-|ZEkPvHk<9?2>wiDMS-IhS;gIrD!`@_I&Dj)MXDSkMT$l)bCRJze0k{( zrT`<2o06x$48<66Xv=cL52@9g^`8)%my{%=rYy#s;Yf5E3H%JGR5g;Z-1L;BHVi&m zH)2wKA}IPt2`5=`YfXe$15pu=%#{`Z(}tNO(JVF7^cwqy<{#5b4HMz9uNNnoZ>83S z{cwD&LoHY;SIArh2%d_wu{{D%-t*&xJlfbDje*Dr_%7Rn=&03!SZ@cg4F?+jVs5rF zR}Wks&(F`KI1oB;lD=I85hbBIWQ)e3ij*+5`H zG#m05AA#*oCNYRn(JjFh9b&x7%$&Ejnrv?!9_)`Nu~V$BZy$~aeYr|56$ett6KzE* zo$OF1F{)Wiu8&=PQI}DBqcAV4&ibvw+%J!N-9|p-(u3B#>LRSCPJ~DjASP~}gmOeo#WR%EdefTcX$RwmRMJWE?r8xZ7^? zkh1OV&XdQ7o)?bB?VW9~D1wFgnNcr(e1BswOrF26+HQBjQ33we;b>5;Rl&P78b@G0 zw+|2E@vzfOa$pP;hcJE!uEhx}mGbZmQI`NFfx{=!xK=9-dhqb5kPq5DQQ-rjZ%}8k zP!7Ty1t2g^z#$t7;FR;{&z$9jT$7TWyZ30TS@wdE@`CS%o?EY$Ax`3pfVTqb1T&ni zh>amdZaGCvHUKr0sYI<@d|jpMUU~Lev9&1^AmQT9rOZH&c@otw>O@xqN2oqYN$)GQ zG(ncTv@S|-u8=*tau69)qMO61*7-;!n74{;$caicL=oy}M-10=E>op-w_26@e*(qo zWI>#e0sT~$r6(9gOqoF6CRL=U%FHxr8YPe&RPY=IqwdTotwG4&^;t@4FqAMgb+pvf zdrandi#EokQpNZDgPr}+7^2Lgh!#AO0D(e3Jbh|yac=(f+8UU!fMgDj25YAlUwiqL zwdK`Xwc!x&!TpWfclIWeKFqV*=^q?4T=LfsGiLa~y|17vC+0dt0n3(GO}wvUc@E_dPi=aD^QCCfyN5xSF*L&Y9u_)p=VxC&MGA9F`l>P>)cab@a}z_5LPPQmaZ#CS zJ39N~$^n|{3T>hyajP%M**Rvp^l_BxhD@0SDD2hRYhu??JO?+A8ewK7S#j%c9E1rw zXpzUMu8o(3G;d6!52g5# zljyV&Y5NnA5z@wVI)jxMB&M8*T&puHN5lM^?YpX!m7;m{x+ztNZ9NHR#%p;D(kLz^ z8f(e-9MeohxkoMEmky1kp$Pu#!YrmNj;l=I)TYuCCsd`G*c#PBqxJ$|;+U|exaZ}b zdH#iJrS|UIZ|(1I`=X%SpNt~#&zw263T~Qa>j)Sz^hmW>0PV57ygWDCczE|gyEmGd zE4=>ouV1-zc6)21(`x_ZCw~`B;zD5tkkaPHZa(LHc5QuOu5$P8=KR8JzEGTBY`H-Q z^f?@kceZyN+OG|_P;K|-@yW6i+i?bKc)@JI|b0RJ29c*kx z5Gu#6Lv7cEUU>0NdSbQ?b|90Fs?WgEQwBxeV?i*ZHIBI z=O#5H&#ce%V%pQEdcf*F+L%!bnED>@twbl%Q=T=Q729Y-i^&#fzz>7b=+^aX`#ak~ zo^`vuqr-zr`Kl~0jw=PXl=C4H5PDwo@bLb_uJ?h#TEJ~_>V&@ zji7$y_K=LK(eyzHp>~~&s)eW6#|^rj zUd(o$f9dMt;xgD0?PhCtcQ2658by%W< z4Ph3!z{{6rs?nf0>@(_3B%+VR`Z#K7s7egZuF453%&B9b6FOsP#)YW-@9vdno|UI& zXLc|{Tj`tjrv_Hh6P1PBMobocW)0M>xG8jqY}R7=&Q zOANo@fzR1n%WO;OZa2D8+~4)RTCFxSI}5%U81doJ!OrgPo!fVM-41wK03sCfx$$W9 z^EZCpY_|Hn@xs#lAN}c{Tzuy0JHLA8Z~o?|5IZ|{=G^OF|4nc@tUr3tZZ%g{mS1}5 z#hW*7u5WBCFRy(4>)%{hT)F$;L8S~Kic+iHzkO%p&fO=#Y<`e$wYoQN-UhVOsMi-} zXGfw+dITE4cfG^rA&fZ;!yNn<_>Rb{6~L!h$WKNi+3z|L`6Z5MRSEF`z(o^LIZ8;7 zbBmS8%|(8Jdu8qh9J3j;IMg(mkTl4Up)Oq{hOi;cU#96^QypjEoH$tl@8~uf@l6&_I_)Mz6#nTy{%`gVTEkKA?CCQwu6(W#9~^)< zh$my+#+O4lB%cq9g#b*5cB_*wmbep$Qs{(s$D_$u_=(H2b969CTz|qzB89J$Qk2?O zC0dTzw_38G7C~ZV$M+5?%Dr;s8C31qUqGj8I3(k0JhlfZElnpQQ!W39&P2j}M-o-1 z$f#}~TG5-6;IghEf@XuJ8Uu24fg5^=6*VEjQVXmbL*Be$oUj?1M*37-qN@h=>Po1t zL^~sy

6|M-6pIimhx|C_KE0N-@$OQ60>gUSz7wF5@&*+o9)a#E>?dDDs;*Ab=|( z0K+t_)f?q%EeLaCaRB=u5e>jue=zKHdmx2GrjY0d3}5kYesSvuKlW#WQnItS#t6(jIbcjSsYXpuj z&@hb0gI*WndY2w>P7_2@RAdhs2e0^wWP&_9gZB?Mcsxl zsab0XR(~dSghMR0@u)>NRK`%$?G=@zyG}F<>SGX4#dE`;`e7+?DEi2NY^kFhler{b zR;^4*rAR2I&`jwEG_{G6x~`V91hX%wjy|hP)Bp%(MhQM-o<99|`gtTR)Kmr+=R~<~ zr_YF(`ff`199x8_wChZoH?}jEjh2}{a@agPy_G{w9Ggzy=frYNQCu#{Q~&@F07*na zRD!u;R1H`50#p!Drt&AklJHCQ*>fczslwfxpB?ON0TKI@V z5mh;vjtT5DBaX5QC<#ZO3Wj|r5+hR#UCMSegxKhcr(ny@(4PW1h6s>K)j=CsOW^oR z-JeW5AG6;w+U*!Y$X_JrR!!rrTtLicqEy8aR8=d{EP;iY;v<)wsO(g88=4u0W%qO-+Z#_h&4X?{1WzK1MP@Mo`rZ8lA`T~$DN`=Jb??#Vx9(^&y*49qJ9lD)vqdxP@ zl}oN)IBNBB`CO|Tb#AY>n)^Gu&A_Kuu3Q9iUw`uC!nt#xC`y$=4q_4GsM&7L)MtQV zN6|3MyZOLpVbE@i-Zijy{bB6n0tjx1c5Aq<*b;>h-PN4PGAJf9j8agoPf@c4BQd+c zRTxyg?_R$0ETJiAacF9LW)H{77f98r&p8qZ`?Oh1=sjKNh?D?$w5k)CX6KwE<(_EMPXlPs^9LyKLRjv+^m z5O#XL<{R1;BvV^xezsu}#R!Z>U#ORGL(m6{iXlQ`)QN24f~vk=Y8 zRF?)^TS_7F1;Yi!(jAQVnypSh0;U1tZgoZRZo``ingTf2AeJOsV9xw*N$4Uvi=*vhp^wcG6m zGUC?h^bQY?s+BUB)?B7yiw-fKHy%f0V7o~yYP!Mm>E%H+5ibPil58X?{G=Kvpzuf% zxJ*XhaHaG*q7iEXxaxhRdrbhuy>$5*PIXPODOgL@jlr+BnMmy)%=sN!y^tx;W)lt7 zLd|hflA_c86ZcE2ZKI$JsB{uz1ZDe!3DoTVC<-jM3Py`r} ziTreG`h-m5{;6M)$AB3KN;Rs?7r6>l^_R!Z)LBW?N(|EvLzXtbh*497NUE+x4^2X0 zdAS;fL(u=`xSm%i7Jzn(m9pmt<0u)8z?5KEpdoW3#D$_UJa_PPJK@t2X`x`b^5H1n z+1+ooI%1LiAZFy}Z@%~7!Go(Wyf`y6`_V_&KL7j%*e6#mU+MP;U~Cji`NgGW;HbNI z@8`n8;^N}^lg&quH+q91Ac@1ncBeP=z1;r65d=dZ*bGA*k0<2{cztvIJ~+N$ai}=t zBo=9=K;_)=iJPZ>j=>MQ)LDTYY~@HWKq~gYv4~O@2v{yg@l|YzXVWc}%pe0=**(?; zL0U9U?Jj?cQ^j$tiV8AKG>Q}n8Uu1ar$PiI^$hTg!^(JXQ?@3?KcMx6dbEmBDV9+; zaqzLsu`ONA8YU%=XJ=$pSs*j0#9Pyzl8 zOH7(gE}pw5%4bAlId?FbZ13zpe!K;UAFhA0zWK(_KWKN_ODikIQuW>UJ{*ik=gyxG z0{`ai+x_0)(uIrQBii2D0moFMF}J<7|M2k!IIdQKsD>-Uc7XL z^OOh>N$3X|X>;@Vi;yeu>BE)y`xHx?}D%B>4!GZ*tnWw7N zwyf+ZhVe3?az&U#49{TyW(&nBRY6s$5=;VUG?yvo#g^zuxlKef zdRZmx;Qb{aQO%(pafA(g!I8KGP|)M`?H=$)H0kt5ckVyFbN2z@`-S<1?d|RHaD4In z`7j6|$XzUyo`3$T=nd2C`a!PKAKbWg7nlkn6(YO5H;_lE<(o%`U{Y7gU~Cs5R01}* z*f+-q%RUCF;^!Sdhi({RXchINa2$9ZqQ>mjS7oQ9vDJoouJGOw!cW}RNI&=CNxjsqE!sq4C+m;mlc;wbDiEIm5D;M z=)m3JD!bm%wh^INB&1zrjto_D#V4o>5JM`Zq>M^oLWmQ?N@c(#r7)KhE$sV)@nj5- z@npEuk(tmMhoEl6QZi&p$rD<22&Cmf{F@)OyLf^7Vtx})jd4i6CIgc;(D%Vxgq#e zcpN4Pfj`90d!QT~S5Z@{8RX_D1f}1@g;feuy{nR1388i{mP%$YZ&EucgBy^VTGPsu zw57F1B-3=Ndss|)pH$lpB^jw1qfW3n8GWVHd5m63$~J=kT}LyW;tOLTX?~V`kJlsq zAqE!WXyqSm;BX5lF|6sK8nYPnV}w1?A{zfjZj_Q$`Svh3PboAV5D?#EsS>a)KioZZ z1YnJ{8nY>rQUc2%G$U3Vtu7c}FuaX0tgQ4CfaX)=r%TGp5t)x04Sc&{`~VxwyXrwRI=Fd0_%Dbd}KxCw{pZBV>~h8sp7<-?fnSbQuWMsWmg zvlPULkLB=FKdJF4S9wZEq#2I`a}ts7M6Lo-~8snj}iUD%Xb8Y%!m&5B!bbWBFG zRCIP0w$R~*`+4e0!Yk!LR7QweP2NP&CGmYf7lwf^S|a6qS`5V5lEQ|LTxmpLny1LY z)P6P|ix%ACQxs1EPs*vvMvT&sHBXnAdmQsS_aCACS}b91`E`az$MIBIlM`H`Q@dqu%dPEK7(`h%49^+is_0n_ znd8l!DU_8lD+$S9KCWnr$wSArj!^8%Y^p)`zu`mTxC*$WxeN)@-c~gc%BS2!IEo~R z0otay==Nubxs{M6*n%u2V)-_)lVP()zqau4C*|KG*_SL+VLC;annhvKZ)Q6fF)XE* z8fjQogUCLN;sgVh_%1C3PBHKKqVG!_adm!*1Vxm~bN+xB<3t@p>dM6h027NERHFJB zQ56lQCJjjR3MQjSh4aUq5d_9&h?6T6hoj-%&Sv{)Hy^l#obM6PNp3VLCgY(e6Z~7v zw(EPPQZ1LSM3D=m&f+mFx-^%Cx=WHopIxMzvtZGowUjy)K+%aCFt!}Vpywkpd2rlWNVnTST|N=q=Sq97%RG?$a8t^(O1 z+2fR5$pqgjR}0thTv9qGy4Q}Bi9(zpVnE1ls64fusHQ|+B(B9h(F0{FD@2OlR4$lN zap}zCt~}qEDP@gC+&jG@8WMk7r zHht&P+n+c}=ITl&oR8QQxy#96XYhDqXM1~R&^~O`b4zoLeCTz$-i#N(gWHD(y-rJ1 z;Us*}8|>}wI^drw)E&1Z`q?KFxy!0CYP#sE5KBca6K3pN>2ko;chLP{c877SH|B}Q zHMTlt>DoI)Y@KT?2aJewl_qx_9noVuP4u2oyG5mD5|!p`Ya32dPSAI>c}&hm9m}AW zL)KDi`lK4WNV1o;(8_{U;Mx#5$#&eM3Mf%kVt|y#w4^qf*eWjo(T>(G)T}C1`E{Zr zqdbL)IYtF(wPP$I{VlU@OjEzW9hQ^YEU7Za^|5+r&6rYH#fs@-GUG)DsRr^rkvV=e z9`#54b}|_RF3kmAAiE62ga@8@CYTSNJiS?Rk}xwwdLJWxOmX*t?meMHsA!YTM1~Z1 zc0LXakd!o^WG`l^w@XI(W(@ z!7~V+k*q=>JcA6>`|4N5R3BZ9I*fETJ&1^XH+36o4Fu(wquY09(AP9aX{eS;M_J$P zny(;+Re4pN@touL0nt*ncy|=J8{gV*pDQYLJ;H@pmDc3D7d6i6%yI=+=4g-!YkDq z41gy%Q740~t!?0kaWokWhw#N}W4>Hp5T`g$OwMDel2kBK<9LY<O|}>hWo8@D|B;9bO)F~72^gnNukbUNQ7quGE%XF*$GH{Dq)3cdNmAz zW{KiX1KW^(>2#V2PuVz|S7!$+l=4)LCSn|;JVlm-DDN3l-3oIav}BivvXf_i$gR1why_f*vYO5#gK>jb7Z? z(V9V|hHy`$Gto&D$)n9hbY1r5mYyDt6&+$#M1))FTPDdc=8mX-W=S9%TM!QPqhV4m zxz_5Wk)!0>F4O1e|)d2;9lht`*Xtwfn z!-?=Fss8nPn^QTcpP~0$!4XyI8CNd5Yr^3 zNzSsVO-3^|O+%A_r8x^I41}(BNhZjx2#0CfQgo?`I+S$5|CBovBTMGgF9rEb`Vv=U zZIFHHM^^p@Za@n@m8warr`gUQ4RV-K=vYsP{DuBR^rp(Qk>oKkjOaj~frn^UJIMCR z>`2C=PV-=Q^YQNXMyuT-lVKtB{Avj}p;*aH90txyVIwE8FFMb~E|b=85`*LhAuB9K zjg*PZTTJAaK zp;l6XQ*&~U7FeiRjV+F9jjL`OYL&73v%E>7Ag``aONqc1f;KZKw~zh-w_Ck$ncykO z5;WFTQ#^&E%)Jn1>jP%+Wv|(V%QCX9Eg-l?iCQys9I{>Q$kq6Wlt0PS`@MZ`t!QBLvsPId1 zbo)_>yMTcJ4DB5>M{zv2FjvUs;QC6r0A_oCFxfw79W>kVDC+fwtyV8L6ZsXQrHlkN z(lFN6y$J0We>kTG4K|1^5#@nO&7xC0z$t}Y` zdb?zKLuyjH)>cdE;g*Jzb3(?+RGxC9XOPUa?zboYowXr@rc zrxF816_03H|G#HH`NEmu!S5&IUJ|#*$x&zAnKWYoP*H^nEg^-ZNa&E&B|>%j zE*_XeeBhEK6=PUp5_qJL4~ilLPMghcr`H=NY-Mq-=ZU-tPCfq999d4yMs<}XY&x!CkDVfBLj0Kn{Jq#@4z~Lzpv&>vY zuACo7@do%hs0SI!qx4hz+LTa~;MH43tnyT9Lu97L%!xT1(F`x;5}M-vFX0?M4dOGG zX`Zq8ouCvDW;)YNDunYh>t3_(PclRZ3N`Iu=TJlaWd!}^;-Kb8|U0)K;xyQnKLW%)uLMnNKna-2fd@C3D~OR zZsf=SPhreUgp@p|094x^x<~D5Znj>j3>|tjNhs^GZr}G4h+vECCILx=|6evHA_MTf zPQ+mmam=GcxYBT8!P-`$TBtRW4@g;@79@H?=8L6fyZPy-*PEx7E}vb@OVk1sRVWn~ z-JsSW?O_s@D_&S2ZjdkxI?Lmhxb(A9ZabY*`;B7MMIHpBtE|wchb>R2f;+^+y`nV5 z!j}9amb)?Q0nCCQo7}CGBuy^NK8ubT{dJ6P(x0{P!#IEn+~Nn4Av3g?F$2&+D;LF> z)cU!ZM_BGJt|#c2DJTnBE{lvYqhvQlY11l&kc>atdJ|0xBB7R)n~sw@Byglg*BaB@ z3@mlIrt7BIUcI9VKDp>yDw0VhETb686vmR{ie=>Br|k9HhiugKNd%6gqEF7PEWG^u zrE&KN-pPTJAL3%2J|D%oShy!c$C-0@-SJAE>jMY^c(i-iXRI(^BVoWNG4Bn-N#b)_ z_Pj=;5|jcj2f+XbGyquFBR3olhlhu4kmb$R;HWcf^~a7EoLxIRj+48$@4=#!^JHPR zSS^Mirc34G($Z3Q(7XTO@nk%ht>kLud@cwf(p;*B)!Dok6k9_!f{@Kw(dR)`6LfcGZVUFBYy;HUNm27Po_2Y%?!y&Un_4#+4^Ct*jw8>=b<;&qFDBFK z#G!3bzr!bkyvzIjUg+}G^QSJHTAnF~B&fDpN5k%@*XjTq0Z#B8nj>z`^%k6{?gTk` zOXm2KaWX#a0$%A6emIGH!^lmd&|&_lKa*=LpPDPw!8z*Xi+PtheJ>BT_))XlAA&K^ z-fs_fo4s76@!$ND{}x!{-~CVj=K3d}pIxb+TBfL>=I6?}$Z1bJ7KYl1BIl;|4tJAffQ_}CC`x#`&Kx1QqH0MQo`mcO;nKyP zl5zaFlN5b7NUw}d``VA;cw)v*j2&n`olY@GcRQ;+l$SmI90elU?#|TMh|y8>#L;he5=_ z@XdjohH$Uv*BCE~ZbDQ-8{r3K69_*K`_TjxgHKrA1(Sfx)eFyFSY9}{6jXEI7%dbE zbi$e>gy=)LRO<96VWE7!RIAP{JGt`Lf9pHNYGbxq{Ez?c|5FS|z33+6{;=Ebbq;d{ zx85kf_Sy?;D+_B&vz0!ujQ*&9!iEq=f_W^PdCD- zcA=sjk;|mMO+9lf_NA&{i)4f!(w(G_M>vr_$Vt>=RD^|Ih-%cur(uQ#5)|DlCnHk7 zbRz@y(;CADPAaaZ7)idJp3Z0^zb{jCNSzoj7p+#QMk`t4pC9`oA(@lHw8oGTQ*PB! zNn6Ntu%t3Tj*eI3|0Xh8LL|CnrlStgN8eH`w9&3w>G@H$EHYaGgVu00IDmR29(y8( zC=Og_rd~dOZY|`Kdwc67CPmMSI$howmbsTBPRVu3LC*KR5=6fy;xJg@w331}k*!W) z(F?9f`m7ebh2_TOmo8j*?p!caq&XKN!(c%8d3T)n@YK`}2zz{<0)9LNNE{awIbG5`F(*9HDLwho!F7&PCuGFy}C5#c- za#uw$2v^jc`bIUYWN;L>>A_%Pe1j9?u0aWm`)Vl=ETnZi|GG@leBva!7f)wHXCZufY}^Q)Z0Rz)H7RXR@Rl#FgUGY{O0X zG$nLvOftofnQkWx%M>KlBR23j8i$T)yMfleOrZeZW;F6>Jm`1#ch@0+yEs25lHx|A z#hKc$+3p>+XlGbSNWchyqKfa8MOH=-2&X9cQWJRuyvTTA(FlUhzE5&RuR349aBk`R zm9w*}bNNQeDTj&Y48}3_3a(d}bff)l=k%4+VPlb$=I`En2%&=VeB;TTJH6Ik-Uq)` zD;al#La9F2@D>}jdVO_mwNxrGuwz{skGiAr7-k$r;8AT)B33HTGJs56om;>$VNBE{ zFh`%v#I;CA_(UxSJrb;0P-!|dtc@mG%zKz$B)b?BLQK%W(UBd4A~ZFN=!Ts~t$2`I zz8d9LGEY9Hax7$Gy`%DWZ2t_+ATCYTj^%T7Odlr|ZLZhfC?RGZo{)5O=}w*Fx9t6hmu}K4aahVC#X(AI*1{Y+9eURIp zXk5)P@{Gy`S3~$~M0Fj6%*LU^!7Dmy$9ucmOY^fc^*U@%e=rD$v$?hR_{JU9>{Y;8 zNa9@Ll&AxEp8|Yv69@3T2jPS$ap6XyD@CAi{*@+bXD-g6Qc zJVsZafA-9&wee_paBwhex9+_EUT1r+e{dME#HTEDNuJUoTsj%Xag_KY5H?=HuM~@6 zts;^feYY`JpI=^{J+)kD6mofQ3|>>ui#Z8$mCpXr=eHhRBIU(r=jN+nWBV?-KmGs! z5CBO;K~%8W9WQ{U^1YdvncdCxVe??n+HdV{M1$T;t<-=&^-8r;0gIvC?qGfRkuMGOKpj*xiZI05tZ30$FZ12?!JIY z#*0n$PHMqQvQiD5vVxynwcggvO(=>GV)0h?Cgdb^p1G3PU*hJ@Q$~eJm^03V45-pF zm`n!{OgiWEi=` zTJxxUCr9lOmw-t{1-a4|s%TsM=ZP+jT{iCKE0s}q(rdR~dG(dAy!P__+qbrN_O>5C z{_L}xFI>EMZfUgyPwMomfcKdbw+375+r80*hGgZ!TI1AWFjvV}OWbn@5x{=8Qg7sD zs^G*0L!>tz@9!MAVeZA(F4ev2gx}mb=s;Y|FIB=~`RJ%6(k(m)@%3(gajmnz&ZEJ6 zy%Nlp3PsVT9w1N{co1|0=G@%c0*v=$eFNlJsTg{0E@spZa`}AT^FvT_qJf4N${4OX z?U72ys9I0)kI7Y0US_VynJa%Q%>ZwM3vd^`v>I1`KzaHrv5W^p&gSg$Buc!op1wQ6=3UBsnK?NUPt6DbCCh zxt0B+cDvPXROf-j+lPmfZW}^rpMCu9;m(sFX|Ju!gL%;HwJPO8z7UQEy>72la%$u8 z_`$=6pyOMeUM{T2+G$xQBhnJw@g$y%N0B`B1YAlgS3%iL4EuvfTtKnrTb;A4q=QJP z$!HIv?Owg7_@;aQ+&O}WT+5Gij~(SUH-Ah!2l?&vXS|VPDV?l~Nq&{4kDOu*^P-kt zU;cWMb17?PSfpk&lB%_0TEiWGNn21m22u@32(eMVQkPgZDn&07TrUGT$A(0R)~*(Z zXsbpiID$(@wf@%&V+#U3Uq1D-ya{ut%C*qDr!~2VsfnG7MXLypOI~(l9t0T8J_S?} zRlEeE3Ws}J#nAuS*S_NW?x&x8a`*njYuD}^wc3Sz9w^80>B8DvalR7H*JvdG>!P?& zEiTWTfAvyvZ6?V(5L%n?1UyB4J_nB+C2YSt{NnBd?)aClzFb>@I2t)@b{3b{4t5W2 z-@bR|+_}cmQqHBF!#((O?~6~5b~jh%>!()cRu<>-f&2K$L!j45G;X(A2M34UUTlHQ z5V@bS#igTLl#`QI;3V^k=u~bM{iK@zkVVC$9FLn0F`W4#gK`3n)SSa>U=2ir-R()o+|O8Nf1yR~v*cDB*l**e&K z+->f$ac^m^o)2LSCNqr+n8pttJs6F~-EQ~r;Ba$e8|Dy2ve-jJHQdQK?sU7bE%{=x zQmFtPLx69>J-<+Q!b;-hP+v=;V|W_0E<$IbL?>(LQ{Gh-Ug!Ko3z(49t7d64uCN>)M3hc6Gkub3o0PY8mn0kvhm!OS6v%v5lW}f@UP<&I+k=6+d7q6iAfFQ!X!=b4DQW1mUT1 z$>;Tw2cC|H_cn{={L8Prc;UjiXwn~z!$KjPUubOa4U(8l;s~5UgK@O8cet>-U&w*~ z-;X>87+^dakujr#vFip;HuqnCZKb?&!9N(S?_QhPZ=K=(CdE;OnK zJ5L7v=6o&RCBxp)o=>C3T-o#7{lh&<%JpjT5swe{_dVa8otf!$dR%szpPikBpvEK? zUO?aT!T?~M>w#+vtY+U6ou9y?<@=0+AK1h0Oe(idE`Y)`8un0KzQwf!kz~uJC6_YB z^!mzo+%sp+N?)xVqD{rK^<}QHl1vgXVx3}J0QsXD9aldq=}!%jTO?zrXBwk|U>yK9 z!_jzL{L$TcRlOOyg9)+&(dtOAiRocu8$F!!DH{TRGkut8HsLhwvc%QeN`_NiJP1t@ zB+2HM=+(+($jZ^g#IhNfvLy&1C%YChLp7u4rVs+PJ9DuspKLD&mw8}1$HTl6FVqVj znSek#+}~a~wQ~8HOEZls=mXDpAewu4bhNj(2XqyDtkdbEY=1Q zpPgF-WO)6?z59tHA9cN{j3`Ym9n z*?Kt)z+{L@BI77HIN0lTJK_Yl(TK@YJqv|WuP2xRJ}wqYfRR9N0N}~z3y$Y|j+@W< zg?tFX2ho;_f>rNwFYkt6Fyz3)WR@|7{nE99qLEuxoO07+w=k;Q4HRowHD#)TMu2FP zQr$2@kwR9~ov_DviY`?pSLhv4$>qoJJ-3((315%UdY5M%M1+vz7KlwNl@cX4s) z`Dd>@ezbmY*l}fN2KcHMhNGBlA9Tty^DF1h_eRmpYxmdAUTK`Y^4=%684cT@1V$6! zl~bqA2yrEhi{Zh}MyGuQ;W!T7DwjeqBaX)m_{z{12qTesDP z6;6MH1iPMeCC;F0F6w>lda92Fqb=X`b?|Y@;!kIUU~J^m6a6;u~jRz zW~WyuH(ve5ZvxF+}%B>)fx-)3w{_xqd}+L0kYoSdi>zd4Zu}A z8TC3x;KQw!fQG#&8V`q~agyxpYz@WHKqEjzp5wP#o#AK%-l2YP2>9NK-0tCDmpV}8ZFq2!KW$*Fmnz$OjV*P_n(HvR$ndMEQ^}L zFXaX0XpT^ed5EfT*KBJAT)CbKCnu)~Dh!ilW=d1@!vaN8u{kN(+IM5=qloMmz;rKa zTu$q`k`igA*?9Vc{j2Ob$5ry5y1|L@b@V-?qKB)^qx#Vt?|{+>mlmw-XgC0C;q>yU zQqJFRkMG>OzYC$|T%p#OYs@TcZ0z2<2X^gDE?-+*m_2psbi31g{q=8r?d!h*$mN$m z`O*D5H#_}_6Be9eqgo)VXD-~i|M=Xq&tH1+<@@&^95tHF?%zg|9!*xF=wNTPrWV-Z4uO8FR9@!m3 z*prD2Rx{(4Kw3m;F7sa<+;T_8lo`<_Eci-fjjEC8)6{033RiMf_`*+c`(QP_sx-y6 zHuwgUi2@Q8$bCIK53Bwzv zf8^BE{pH1}3CxYXfVX0tSWdp&I}q*l!W{9uXgq%Dl~>^Ao10s%>jH^GwC(Qg8;@^Y2lwciwZ${1mU4kJ>Vpfk z2~MPy<;A7Nh2dy$)I8kY+1}gR2T+vD<$?Zs-9BKUPNxkrzECU!AoGL5BqIC!-GhT} zvjrh+;dF*jnryO`;EqhBh2cPuE%Zg6vb0kX*q}n>sl$g8orsXAhWd=-0atp1@NXHC zWU70t2pPG$`zq$MQ7;ugie8k|mx7s?gTx8RM3j`fUH(pWk&4A9B>Mw3Phn{b2gAWB zF>p)&5H*_ zP)~?zq;E{wQT8<{k6MnbX{&<>62ZJor5>}IgvGAogY&*t@J}r-I;;=Z0U#GNzUu%0 z5CBO;K~%|?%7uwDH$Pvi)POp#-?;Vo$wn+sSrlgoC44*_74o7ojf|plrBo{A7Z>M! zk(?}o)(i9X*5X3zVE>B`-fbT4c^-=={m`F1cV^WO!ghbWwYgp|6)NRoxl{%K)*KFZ zclXveHeqy)#!TWATCH~Ic@R(oh*hc9Tn_?jev~-9L3Gsa_j*!!J@6tFLT)Y6Wvr(;_Z|`q3kDB}YN71m~X&*NC z69`$_)s^w8%&#xysz8gmb;na_c;lzAS4Y zpy`HnWQ0sZa3&ou5=_(c=P%C9&7y-+dtkKF1CL~t_ubvyxqtsYAqMp-7YWf8r5q2^ zsswvlo@47O%f8h#t-eCIsoF=P)g8Z-wS+5RWtgWYP)*UjhU4-|$9TNV@dngZq2}iKdy-eln-lMae};r_%~tr;;xm?C#Y{ zem)n1+sAhSJ&neL&S=z~O!`SQ0yWTU9}YT4gJg7OZTZ64)x+J#I}eNC&L0iiiwpJJ zw{JyJZ|BL~t(|=U2c1qcpARlwK9|pjTicsJs{MZV(WA%x;TYn?j0Ppq6GUb(Q>gUIvIo3 znFOQpq)?nVa3jYB4;UGPtuWxuC@jtjtwQ}+dLRVVD8QnYmFP4As!|(qgSF>K^&Hpr zok=V+w@p!v*)Y`=Yx-@jC9ZCG!R>A1hG(RA<;53YDi;c7V5vTlek3@O$ja8;J2PJ|$86)Zlm@S)lfrTd_2M-@!y7KJj zH}93p)!p6wdVL0-dunYJB6Seh260}<7s{1Nl<-2n6o!RSXK?@F{c)5;326gAw0hma z1X#h9&aqCvFE}BZv^`OrSFM*T^@i_yaU8>}#|igyUbPtJyy66yK^&p&pwt5637elg zSoy8eh~nciSREDxd~8?`THF9~V3 zn()r*%8EV;V@m&vN~I4z{P4z&>m;Rb)9l?mkt^&&)zG=IEDfsA$d9Vf8yW_x=^Rg8 zvE0Na$uSvh>>qQkDKQ*Q6ck15k@fE2s6=iO(Zexo#k8R!wF8^NT1w%Ftx~R4I}-pu zv`w-ptcAasQ>(HrU{NS4&{r}}CS%Y7jaqT0UIQ5!MHBFWRIAmwxw(3!FzmMny$)cl znOe0VP}7(I{f+wlUI!#P#Kl~Pi+*{obLz}#@OXao;fGhRzT|pgzuSIr|2`Pd`@1_Z z5eO0N?Ck9B?X9kz+COO3s4 zn8|xL5=0cIB!oI?)eW(afYmgsZ~@oKm+L+P?&{hKlU+Boo7~0>M6(37=*B=8f<^%Z z*vqBj{M_s`XqI_6*Kgc-@BR0H(UIn4f;HoY`Vr@#1y1$Nv-F@y#Nskev<0FI;_z33 z9F9RooI^&|W_~b?p_xJJM{9w(GVd9cf3W^unPuD{Ckq{E*VeHd4ONcC2|2;CIOeUL zdYLX72KA}iN)btt@#gvyaLS)qTUuJ2Yc&tS6H|t`Zm9tA-BJ$xCL%|pQ7_l4W$;>- zO9h%F?bcDFQD0h`FPBOXWH>lDxN_wRkZ7mX`MaO|v|6nL1p@>3dxK7=^W^a(V3ks_ z7>ItT0Yr`APZ;I~{oda0ZWPCbLVh$FH(M=mf`UEJ1A#wG_ILNX-R@DdE&AGf;`AL^ z-48EM5)Or*ip6{&9AC+3Gy&`ckp{Vk7 zC<6JCF&gbbNST@{;~vY4GsOd`8NjEaD7X%KHHhXw=~|%HDiV@GN=oD~?cGY}A+Z*d zHT&tslxl@^T&Jcl=}jUc+kj&tGNq;-UC2onf&ea`OyV(!*9aW<{odkSH4JFG(=8Qg zZJ|bK1ZIDUOHa66pF!9L94#Ua)Ia$3oHcF09XY9IMHVyj^W<9e6d(87fV%$j^xWTVP(ND z)`?$;c_2%X<-}10(KNQ;%9fr~84C;@m3l)Bbho6TW)rTXw{q8ryRx<-edv0Fwd+xK z6-U{b>9Q0P=Q9t+5$G7 z7xV|HjB7^J*LC`d>Jx0Y)=~zDk-Uevz6;#sZ#x8E7JnXc4s$84r$Qqa@>W zI(`S5C2xv}vXoMqBEQV4YOjXPlSIMO1kDSj8Mt|bh=~Ng(`+6d9PBU5*1**>1n?8O zzzJZ7Mw5X@#y&)EJ>Km!Av6rS0rqw>hW%=R00(@swJjnEz@Z}u8;e6``O?CY53zu9 zd8SeV?+8S!0o23h0>R$Abvw)zAzIh&4Z$oP4aQ>-=uC8_f3m&-hCoaBOh=Q^ghD(4 z0FWz82%%5>00jbssLO?182X{eNR=JF6Vdzv_@Q2}HyVvnrJgUAff)+*Ilnx^{Q{$5 z!gaE$%rD^D2g5OmhKla@RJv>u)hkEQii6o}f&y5Ie#PHT3V$Tez$`o@&_oyG8`4zP zqzO%%!vPe}2?6!ZZ+!j2g^O5C0uQ=$`C_eBGbD1@Y(c=0XeoR5-rcv~dYh+AXA^;e z+T7TV`sEtG3g^?x!Hm`(x$?A;v_AzU23n9Q4S=sSCYOSc^rcf_q{c|BZl~g<+oe%H z1%Oz_nkHOpU}LV(sk9fl)v}q4ny_Q6BiU0-^ckim_Q%!fSn|rn<+Ue|9$owV=7m$s z-J`v;XO=+#gLxVFLh^zmHHjvHNBl4ce={});kC3h3*QDK80?5RnLsq6-EM=hE|<$- zzlv6*jw)LZMVHS6n}EgIrK5 zd$|$?JAwFsI^}T{s1_93m$FzoLRDl_oEwDag{k!s1#w)_g0VgI#t93WT3aK37QI%P zK3oi`!6qzjJd&2dVeC`btv~*w?=8&Dog`RAk;GSDe+{d)clY-1|KIKr!jEGCqcF@H z*-zONq*3H)jWuHrP}zuAl^jUnt<4MdqQ<+bzDIR-ll+jPPI6L{$=LFl)zr_mO0?6p zQ)4xLRpnQb?V?3Jifj<*x*5HsZfsDKj%}oighW8o4k!{a0YNedA=ex8ckgfJAslwJ z*Y37XugS)_9&Qa$ zuzacbXnkY<@aWd9yTie#KbR1AEZXTsZmS=6dc$U?-){A!;V$GRC?7yLLPrDO2l4N_ zVIDMs4RF)pHdmEPc$s#|@YE=n4iSh*5tT$d?n6VJC&D?7rnZtJ$PKSM4h~rntD4J}JgR{*osU zcy3Us&px{U==Q@677Y&$S*ty)7C_?i`AX1eR6?IZ0IitM_d8tx?0)FAyKQhfb=sYL zK3}g@dOS89!eX0Lg4e{=f)EZ|b1TxgV!ntR>I>ut354i0<6F?d~k zn&bmdgt1*8BBQ{2Ak)D?1k6Ao+Tl?EY@j3Y8hNLlO

}1e0w+&?mLhjAaXn1e!JQ6JP$xQM5C+KO0IIHFG>Ia z5CBO;K~#{d)@q9j3*g29EL1L4lb8X~Z*L!fY~R@4f4s4`zS|r}d>DKC?Lo5(@e5~T z?`VCueedDk-cc94oW2)=u@QoK0b~xS$`$E8m2v?94>+=10e1-aioi4&4hO?w9~2Il z@9-;3NqCMDl(DW%a-^bxU#VEF*K)-Q4J+IUm=h-AtSncCRXJ{`6y~TVxGjB<8<1i$ zbfVW(8`F4k>K0TiCDEz7w7jCFo6P`fn@lWLyJh6Q7@Pq76~&JpK3-i}7OCKTssvPM z63`z1>VNzno6V+bk4;h>f6~j)nHtD(@rl2RWRim!=2pXXQCNOW!4h|$&8p{oIGtU%w)xL9fM@G6P(DlYqGhM0Z0+I z?&IX8oeDvKXcFZB898)zwgFQA#pf;pOb1UZxce3t7ZVn*t*wBUGmgdKYn@IXyhcFu z_aCh@H+TQx)|)j1e>81_JSphon(F zKn=|3s9q_}&NKiQiBuiRgTPI=DBOWr*K0KhP6KL!4Fa2C2qr_6i0}i1&s+zfT4T0R zu2iX?V{U08&~A_rFHwhfV12wG8{Ioff>g#-8KM@n|3AX6tVgcm3Rl&=x7X>O@i1Ou z8%2tQ6kgzGMal08iIj&#C;^c?@R}$;0$z~7D1rcSjPW)Z_j;GAoVAwjMl`~1_wBA* zx9Y6lIdx7ra>nG?U<`M1k$&A{-a9h7Om+YM!*HTINff*$fgTzvOEQnFw)JZL`0;mu zQeR!Yk?kV>=9j7B-9E`XPA#cDHe;ISAP9j?JEz)Z&<>Uksrv+%u z4ibkBkLMF-Pke1nUV zLwv7;*+E1ZLI!uPE-x<5!N>%3sHQ%*Tgr)=Os9ar0uu(xiQesdh)Z+`1MeM9b$)e) zSQ5DSU8uK1gUv)Cs(_6Yg=R#TQWriiaetyA!HON3 z;qK2Kf^Y4EDhfhBJlu4LK+~p>h zwbo?kCwT^$D&Y4;%3ww@ho9uLJaEdRqkQoGCtoZ$^oQ6rR1pPp;yj?FO(g4#7l`Sc zd}vdE)K+-FrhA>97>mRpM;7&D7s*0m2I({hcIoA-9HJ{x`X}w;Ae-J3l|Wy1%@7I6c1uh|o~Y zdegjK@4=CHaedSF9hGV_1g8$%?9lrjD_KlNx0cI`^99&rfH1*qN0=@#?AjJEEii$= zEr-W|ctQ(Y&vdbyZ0ZcZOVp)XIIV$8~DrrVx<&NOJ!ZsC;3S6_<|Qr(C@gu;wxS zi9ATEjFOkCG}H2%hOyMF!mQji@&(zKq0H@5p_T1cwjA*RkLupYs6-}SnE?$kAVlB? z&t!8h`E`bLg zOqRFpXl9#vN3ETM9kqS9xp@tK^!4>CpkjWsfq`SVBRB_?R)nF01}vihc**P47OsLX z%?CxsV+<)_zZ(AiPtE2mZf=ZLa+Ps8EVhyWFx#1xEcZwNIV!4oRt7ILH zvb7Ihxull5264qQd$QC~$GjJxf#*Yg>&duJ;?0`qM8yI51clY5uqP&#;3oPT%Vrq~ zEm87WT`$g;i{%pZ06>*sbFX*Z^49HL=bPCQK%P|_>e;O@T1?LGfcPITZjTpNvkTyX zImQq${|8T6dhF|n9k3u&{ObDE%aGhSKjSHO6D{Lt9P4)I$6})U_yySl8oZ{`oR*WELXZGwD72E72_#ZWPgwL@r;!rVC5uGKZJTRzdf5@Ufx}8Z-)JTc{XczKxWMnNdNNTbh2nLQwTeNpzJqN zii}H40GU!nUCVplb`XF@dCqOdC4s?VV{=&0&Ayv=H9)3yGlrjZ-h(`!&CdWSZTA~^ zrYbaGPz|*QlL3OwFw$r=24<*h>~Y&PNJ4EJQ}$K2hSf?Yf# z$2Oq}dU9b1kxUqAmt-?X^RKvikTpWxf@W4iwSi0bBSlhZQ2YlMmtN~fatT|(LrgN= zboWEjLM*wz|Ne(R{qcSH02~Is7ZG>?`&jA%>A4JWb~D@#2${J`7~X`e@RyB>P4h!F z>4IK)VJ|)c!L1!NOkpA+7G?9qb2ZT|i3FWK!KafG(Lw*j&z;&qiDLPEvcz;;;{1X8 zQ_6XBeBQx3)v-9HcP6r8%+`*JrpGDx8&QpDq7$7ew~{VEWyP@6Vl$qePp8Ny&Q2Cc z5Mbg@aV*!tw+2f3<%BCzdo;2kwAOhP{(<~m@WYWC*#72)cSNi`_{ZI_1-GC>2@fnV zb^`{{>Z%1Ky>BZDm*M&5iUY&A1uzGoet1(?vey7@firTm>P&M^M?f1&=NzbsHP+Nu zw}FkuOlVLthEc#dH_FFwG83hanU9g{0sl^G>y- zQTW-4hk>anvMMgh@5G$8NZN}u9d)afEEvJ{Setsidt_AAT=FZYH5sfR1jIvJjW61P z{9Pv@QeK~o33*DM`eq8F+4R&|k}OAKEeDb$*v_I23u<&|&`>jCVg#5GrSM<|CQup( z;~s>4=$N_TKeYdAJZMm%YzD#K4;U&&X6Vsd2qIUEC~=rd*JIECyKWjxtAKDD1kXUk z?}ol{J%-?n(S$CXIq;05lpc;2gSnP-Nav!_y7PZmZn zrx_zZ5z6wO5{z+cD=!6KybB3CQk;p*k8slWDBz@+!|s$z*nrUOH*_w~8E$A36}&Do zfScWJk8Z#oF-`~qz@&OY1wk+1af1=ou>)gfWVXRJoAqw>dIg+gB9}5(afN5FwuqFs zyWMuzOloQ{isu&`2A?P3OH_owR&ZaBC~RL>HL_yYHUYaYpiRt)i$^~6+5M~h2){n6*QmK-FGloQ{t72Wu~uM+k)}ynG_Dw>tfE>VEn=w-ZHVBftmojhHg5JITg^!OQqclGuZ^CO4@sOVT9hHS z5Lz)IGK6aK=yQid5~Z3Pk-SoNU9=*Uyz{H&5T)?WDJH)6VppJyN{gn!>8K*aj!q>aHC&s}&b} zrgmY2+SzoFfHRr_hpn5@bQZ$S)eWG!^Rw{?6PAG5(M|$-Ky8CXeJd=Mh3#OcEG)sm60Ju5Q7H zpK3Y)00Kx!L_t)pGN64%&3Fd(l&`RyQSU9p;<_;4F(1o;T6j)O2nulHq zZZ^4`PM6dcvIqL@fE*e1Fh+wOoL|8rAoCDTFr_J+V;x+o(tFYw2&Uw+jD!dUM5VJM zCPCl}3n&2}UNhM;U~Jio|df)5^+L3x%m8pCKkG@xdIElqo_S)*MeKw z0fQFoB4iK1f3(+xNot2r*pVF3JPrSkF{e`UU}ueCK7gxY zXqobhdEW=BT>&vTpi)f#o{X`+cGYw^nE_DRfR(}^6g?t-a6!}KL*bk0vKaN|se`RN zyvclJE5sc1K!J#e!gffz^?v{W0RR6kyPWs{000I_L_t&o0QK$~4Qe{}Hvj+t07*qo IM6N<$g0J-KCjbBd literal 0 HcmV?d00001 diff --git a/fullbg.png b/fullbg.png new file mode 100644 index 0000000000000000000000000000000000000000..efaa2b4240b8f83bd2a22773634ee939ebdec595 GIT binary patch literal 52546 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 literal 0 HcmV?d00001 diff --git a/main.py b/main.py new file mode 100644 index 0000000..8d514f1 --- /dev/null +++ b/main.py @@ -0,0 +1,358 @@ +import logging +import os +import sys +import time +import random +import shutil + +import cv2 +import numpy as np + +from playwright.sync_api import sync_playwright, Page +from playwright.sync_api import TimeoutError as PlaywrightTimeoutError +from playwright.sync_api import Error as PlaywrightError + +# 将 SakuraFrpQiandao 加入搜索路径,复用其模块 +sys.path.insert(0, os.path.join(os.path.dirname(__file__), "SakuraFrpQiandao")) + +from config import Config +from captcha_handler import CaptchaHandler, ModelApiError +from human_simulator import HumanSimulator + +logging.basicConfig( + level=logging.INFO, + format='%(asctime)s - %(levelname)s - %(message)s', + handlers=[ + logging.StreamHandler(), + logging.FileHandler('checkin.log', encoding='utf-8'), + ], +) +logger = logging.getLogger(__name__) + + +def get_gap_offset(bg_path: str, fullbg_path: str) -> int: + """OpenCV 滑块缺口检测""" + bg = cv2.imread(bg_path, 0) + fullbg = cv2.imread(fullbg_path, 0) + + diff = cv2.absdiff(fullbg, bg) + _, thresh = cv2.threshold(diff, 25, 255, cv2.THRESH_BINARY) + + contours, _ = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) + if len(contours) < 2: + raise ValueError("轮廓数量不足,滑块识别失败") + + sorted_contours = sorted(contours, key=cv2.contourArea, reverse=True)[:2] + rects = [cv2.boundingRect(c) for c in sorted_contours] + target = max(rects, key=lambda r: r[0]) + x, y, w, h = target + logger.info(f"滑块缺口坐标: x={x}, y={y}, w={w}, h={h}") + return x + + +def drag_slider(page: Page, slider, canvas_bg, gap_x: int): + """模拟人类拖动滑块,根据 canvas 缩放比例校正距离""" + # 计算 canvas 显示宽度与实际截图宽度的比例 + canvas_box = canvas_bg.bounding_box() + bg_img = cv2.imread("bg.png", 0) + img_w = bg_img.shape[1] # 截图宽度(像素) + display_w = canvas_box['width'] # CSS 显示宽度 + scale = display_w / img_w if img_w > 0 else 1.0 + distance = int(gap_x * scale) + logger.info(f"Canvas 缩放比例: {scale:.3f} (img={img_w}px, display={display_w:.1f}px), 校正距离: {distance}px") + + slider_box = slider.bounding_box() + start_x = slider_box['x'] + slider_box['width'] / 2 + start_y = slider_box['y'] + slider_box['height'] / 2 + + logger.info(f"滑块起点: ({start_x:.0f}, {start_y:.0f})") + + page.mouse.move(start_x, start_y) + page.mouse.down() + page.wait_for_timeout(random.randint(200, 400)) + + # 分段拖动到目标位置过一点,再回退微调(模拟人类) + page.mouse.move(start_x + distance + random.randint(2, 8), start_y + random.uniform(-1, 1)) + page.wait_for_timeout(random.randint(300, 600)) + page.mouse.move(start_x + distance - random.randint(2, 5), start_y) + page.wait_for_timeout(random.randint(200, 400)) + page.mouse.move(start_x + distance - random.randint(5, 8), start_y) + page.mouse.up() + + logger.info("滑块拖动完成") + + +def solve_slider_captcha(page: Page) -> bool: + """处理滑块验证码(OpenCV 方案)""" + logger.info("检测到滑块验证码,使用 OpenCV 方案处理...") + try: + # 截图含缺口的背景 + bg = page.query_selector('.geetest_canvas_bg') + if not bg: + logger.error("未找到滑块背景 canvas") + return False + bg.screenshot(path="bg.png") + + # 显示完整背景并截图 + page.evaluate( + "document.querySelector('.geetest_canvas_fullbg').style.display = 'block';" + ) + fullbg = page.query_selector('.geetest_canvas_fullbg') + fullbg.screenshot(path="fullbg.png") + page.evaluate( + "document.querySelector('.geetest_canvas_fullbg').style.display = 'none';" + ) + + gap_x = get_gap_offset("bg.png", "fullbg.png") + + slider = page.query_selector('.geetest_slider_button') + if not slider: + logger.error("未找到滑块按钮") + return False + + drag_slider(page, slider, bg, gap_x) + time.sleep(2) + + # 检查成功文案或 slider 消失 + try: + page.wait_for_selector('.geetest_slider_button', state='detached', timeout=3000) + logger.info("滑块已消失,验证通过") + return True + except PlaywrightTimeoutError: + logger.warning("滑块未消失,可能未通过") + return False + + except Exception as e: + logger.error(f"滑块验证处理出错: {e}", exc_info=True) + return False + + +def detect_captcha_type(page: Page) -> str: + """检测验证码类型: 'slider' | 'grid' | 'none'""" + try: + slider = page.query_selector('.geetest_slider_button') + if slider and slider.is_visible(): + return 'slider' + except Exception: + pass + + try: + grid = page.query_selector('.geetest_tip_img') + if grid and grid.is_visible(): + return 'grid' + except Exception: + pass + + return 'none' + + +class CheckInBot: + def __init__(self, config: Config): + self.config = config + self.captcha_handler = CaptchaHandler(config) + self.simulator = HumanSimulator() + self.max_retries = config.max_retries + + def run(self): + headless = os.getenv('CI') == 'true' or os.getenv('HEADLESS', 'false').lower() == 'true' + + with sync_playwright() as playwright: + browser = None + try: + launch_opts = { + "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() + if chrome_path: + launch_opts["executable_path"] = chrome_path + + browser = playwright.chromium.launch(**launch_opts) + 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) + + self._login(page) + self._navigate_dashboard(page) + self._do_checkin(page) + logger.info("签到流程完成") + + except ModelApiError: + logger.error("模型 API 调用失败,终止") + except Exception as e: + logger.error(f"执行出错: {e}", exc_info=True) + finally: + if browser and headless: + browser.close() + + def _resolve_chrome(self): + if self.config.chrome_binary_path and os.path.exists(self.config.chrome_binary_path): + return self.config.chrome_binary_path + for exe in ("google-chrome", "google-chrome-stable", "chrome", "chromium"): + found = shutil.which(exe) + if found: + return found + return None + + def _login(self, page: Page): + url = "https://www.natfrp.com/user/" + logger.info(f"导航到登录页: {url}") + page.goto(url, wait_until="domcontentloaded") + + user_input = page.locator("#username") + pass_input = page.locator("#password") + user_input.wait_for(state="visible") + pass_input.wait_for(state="visible") + + logger.info("输入登录凭据...") + user_input.fill("") + self.simulator.type_text(user_input, self.config.sakurafrp_user) + pass_input.fill("") + self.simulator.type_text(pass_input, self.config.sakurafrp_pass) + + login_btn = page.locator("#login") + login_btn.wait_for(state="visible") + login_btn.click() + logger.info("登录成功") + + def _navigate_dashboard(self, page: Page): + self.simulator.random_sleep(3, 5) + self._dismiss_age_check(page) + + def _dismiss_age_check(self, page: Page) -> bool: + """尝试多种方式解除年龄确认弹窗""" + # 方式1: 检测 adult-check 弹窗,点击"是"按钮 + try: + adult_check = page.locator(".adult-check") + if adult_check.count() > 0 and adult_check.first.is_visible(): + logger.info("检测到年龄确认弹窗...") + yes_btn = page.locator(".adult-check button").first + yes_btn.wait_for(state="visible", timeout=3000) + yes_btn.click(force=True) + logger.info("已点击年龄确认按钮(force click)") + time.sleep(1) + return True + except Exception: + pass + + # 方式2: 匹配文本"是,我已满18岁" + try: + age = page.locator("div.yes a", has_text="是,我已满18岁") + age.wait_for(state="visible", timeout=3000) + age.click() + logger.info("已处理年龄确认(文本匹配)") + time.sleep(1) + return True + except PlaywrightTimeoutError: + pass + + logger.info("无需年龄确认") + return False + + def _already_signed(self, page: Page) -> bool: + try: + page.wait_for_selector("text=今天已经签到过啦", timeout=3000) + return True + except PlaywrightTimeoutError: + return False + + def _do_checkin(self, page: Page): + for attempt in range(1, self.max_retries + 1): + logger.info(f"签到尝试 {attempt}/{self.max_retries}") + + # 先检查是否已签到 + if self._already_signed(page): + logger.info("今日已签到") + return + + btn = None + try: + btn = page.locator("button", has_text="点击这里签到") + btn.wait_for(state="visible", timeout=5000) + except PlaywrightTimeoutError: + # 等久一点再检查一次 + time.sleep(2) + if self._already_signed(page): + logger.info("今日已签到(延迟检测)") + return + logger.error("未找到签到按钮,也未检测到已签到标识") + return + + logger.info("点击签到按钮...") + self._dismiss_age_check(page) # 二次确认,防止弹窗阻挡点击 + btn.click(force=True) + self.simulator.random_sleep(2, 4) + + captcha_type = detect_captcha_type(page) + logger.info(f"验证码类型: {captcha_type}") + + if captcha_type == 'slider': + if solve_slider_captcha(page): + page.reload(wait_until="domcontentloaded") + time.sleep(3) + if self._already_signed(page): + logger.info("滑块验证 + 签到成功") + return + else: + page.reload(wait_until="domcontentloaded") + time.sleep(3) + continue + + elif captcha_type == 'grid': + try: + self.captcha_handler.handle_geetest_captcha(page) + page.reload(wait_until="domcontentloaded") + time.sleep(3) + if self._already_signed(page): + logger.info("九宫格验证 + 签到成功") + return + except ModelApiError: + raise + except Exception as e: + logger.error(f"九宫格处理出错: {e}") + + elif captcha_type == 'none': + time.sleep(3) + if self._already_signed(page): + logger.info("签到成功(无验证码)") + return + + page.reload(wait_until="domcontentloaded") + time.sleep(3) + + logger.warning("已达最大重试次数") + + +def main(): + try: + config = Config.from_env() + logger.info(f"使用账户: {config.sakurafrp_user}") + bot = CheckInBot(config) + bot.run() + except ValueError as e: + logger.error(f"配置错误: {e}") + except Exception as e: + logger.error(f"程序执行失败: {e}", exc_info=True) + + +if __name__ == "__main__": + main() diff --git a/sakura_frp_auto_check/.gitignore b/sakura_frp_auto_check/.gitignore new file mode 100644 index 0000000..a354c7f --- /dev/null +++ b/sakura_frp_auto_check/.gitignore @@ -0,0 +1,10 @@ +/bg.png +/cookies.txt +/debug_01_bg_gray.png +/debug_02_fullbg_gray.png +/debug_03_diff.png +/debug_04_thresh.png +/debug_05_with_box.png +/fullbg.png +/state.json +/account.txt \ No newline at end of file diff --git a/sakura_frp_auto_check/CLAUDE.md b/sakura_frp_auto_check/CLAUDE.md new file mode 100644 index 0000000..f739e32 --- /dev/null +++ b/sakura_frp_auto_check/CLAUDE.md @@ -0,0 +1,77 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +This is an automated FRP (NATFRP) check-in bot built with Python and Playwright. The application automates daily sign-in on www.natfrp.com with CAPTCHA solving capabilities. + +## Architecture + +### Core Components + +- **main.py**: Single-file application containing the entire automation logic +- **Computer Vision Module** (`get_gap_offset`): Uses OpenCV to solve sliding CAPTCHA puzzles by detecting gaps in background images +- **Browser Automation**: Uses Playwright to interact with the NATFRP website +- **State Management**: Preserves login sessions using `state.json` to avoid repeated logins +- **Retry Logic**: Built-in retry mechanism with configurable maximum attempts + +### Key Functions + +- `get_gap_offset()`: CAPTCHA solver using image difference detection in main.py:7-51 +- `main()`: Main automation workflow in main.py:251-324 +- `solve_geetest_puzzle()`: Handles GeeTest CAPTCHA solving in main.py:220-235 +- `wait_signed_text_and_shoot()`: Success detection and screenshot capture in main.py:238-248 + +## Running the Application + +### Basic Execution +```bash +python main.py +``` + +### Dependencies +The project requires: +- playwright +- opencv-python (cv2) +- pathlib (built-in) + +Install Playwright browsers after installing the package: +```bash +playwright install chromium +``` + +## Configuration Files + +- **account.txt**: Contains username (line 1) and password (line 2) for NATFRP login +- **state.json**: Browser session state for maintaining login sessions +- **.gitignore**: Excludes sensitive files and debug images + +## Generated Files + +- **bg.png, fullbg.png**: CAPTCHA background images extracted during solving +- **debug_*.png**: Debug images for CAPTCHA solving analysis (when debug=True) +- **checkin.png**: Screenshot saved upon successful sign-in + +## Configuration Variables + +Key settings in main.py:53-64: +- `MAX_RETRY`: Maximum retry attempts (0 for infinite) +- `ALREADY_SIGNED_TEXT`: Success detection text ("今天已经签到过啦") +- `SIGNED_ANCESTOR_LEVELS`: Screenshot capture depth (3 parent levels) +- `SIGNED_ANCESTOR_SCREENSHOT`: Success screenshot filename + +## CAPTCHA Solving Process + +1. Extract background image with gap (`bg.png`) +2. Extract complete background image (`fullbg.png`) +3. Calculate absolute difference between images +4. Apply binary threshold and find contours +5. Select gap location based on largest contours +6. Simulate human-like mouse dragging to solve + +## Browser Configuration + +- Runs in non-headless mode by default (`headless=False`) +- Uses `slow_mo=200` for human-like interaction timing +- Maintains session state between runs via `state.json` \ No newline at end of file diff --git a/sakura_frp_auto_check/CLAUDE_CN.md b/sakura_frp_auto_check/CLAUDE_CN.md new file mode 100644 index 0000000..fefc77a --- /dev/null +++ b/sakura_frp_auto_check/CLAUDE_CN.md @@ -0,0 +1,85 @@ +# CLAUDE_CN.md + +此文件为 Claude Code (claude.ai/code) 在此代码仓库中工作时提供中文指导。 + +## 项目概述 + +这是一个使用 Python 和 Playwright 构建的自动化 FRP (NATFRP) 签到机器人。该应用程序可以自动在 www.natfrp.com 上进行每日签到,具备验证码破解能力。 + +## 架构设计 + +### 核心组件 + +- **main.py**: 包含所有自动化逻辑的单文件应用程序 +- **计算机视觉模块** (`get_gap_offset`): 使用 OpenCV 通过检测背景图像缺口来破解滑动验证码 +- **浏览器自动化**: 使用 Playwright 与 NATFRP 网站交互 +- **状态管理**: 使用 `state.json` 保存登录会话以避免重复登录 +- **重试机制**: 内置重试逻辑,支持配置最大重试次数 + +### 关键函数 + +- `get_gap_offset()`: 验证码破解器,使用图像差异检测 (main.py:7-51) +- `main()`: 主要自动化工作流程 (main.py:251-324) +- `solve_geetest_puzzle()`: 处理极验验证码破解 (main.py:220-235) +- `wait_signed_text_and_shoot()`: 成功检测和截图保存 (main.py:238-248) + +## 运行应用程序 + +### 基本执行 +```bash +python main.py +``` + +### 依赖项 +项目需要以下依赖: +- playwright +- opencv-python (cv2) +- pathlib (内置模块) + +安装 Playwright 浏览器: +```bash +playwright install chromium +``` + +## 配置文件 + +- **account.txt**: 包含 NATFRP 登录用户名(第1行)和密码(第2行) +- **state.json**: 浏览器会话状态,用于保持登录状态 +- **.gitignore**: 排除敏感文件和调试图像 + +## 生成文件 + +- **bg.png, fullbg.png**: 验证码破解过程中提取的背景图像 +- **debug_*.png**: 验证码破解分析的调试图像(当 debug=True 时) +- **checkin.png**: 签到成功时保存的截图 + +## 配置变量 + +main.py:53-64 中的关键设置: +- `MAX_RETRY`: 最大重试次数(0表示无限重试) +- `ALREADY_SIGNED_TEXT`: 成功检测文本("今天已经签到过啦") +- `SIGNED_ANCESTOR_LEVELS`: 截图捕获深度(3层父级元素) +- `SIGNED_ANCESTOR_SCREENSHOT`: 成功截图文件名 + +## 验证码破解流程 + +1. 提取带缺口的背景图像(`bg.png`) +2. 提取完整背景图像(`fullbg.png`) +3. 计算两张图像的绝对差异 +4. 应用二值化阈值并查找轮廓 +5. 根据最大轮廓选择缺口位置 +6. 模拟人类般的鼠标拖拽来破解验证码 + +## 浏览器配置 + +- 默认以非无头模式运行(`headless=False`) +- 使用 `slow_mo=200` 实现类人交互时序 +- 通过 `state.json` 在运行间保持会话状态 + +## 使用说明 + +1. 确保 `account.txt` 文件包含正确的用户名和密码 +2. 首次运行会创建 `state.json` 保存登录状态 +3. 程序会自动检测是否已签到,避免重复操作 +4. 遇到验证码时会自动进行图像识别和破解 +5. 签到成功后会保存截图作为凭证 \ No newline at end of file diff --git a/sakura_frp_auto_check/LICENSE b/sakura_frp_auto_check/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/sakura_frp_auto_check/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/sakura_frp_auto_check/README.md b/sakura_frp_auto_check/README.md new file mode 100644 index 0000000..79d1be9 --- /dev/null +++ b/sakura_frp_auto_check/README.md @@ -0,0 +1,116 @@ +# SAKURA FRP 自动签到脚本 + +> 自动访问 `https://www.natfrp.com/user/` 完成每日签到;支持在本地直接运行,也支持在 **QingLong(青龙)** 定时任务中运行。 +> 成功后会自动保存签到结果截图(默认:`checkin.png`),并在必要时自动处理滑块验证。 + +## 一、目录结构 + +``` +. +├─ main.py # 主程序(无需改动即可用) +├─ account.txt # 账号文件(必填:第1行用户名;第2行密码) +├─ state.json # 登录状态缓存文件(自动生成与更新) +└─ checkin.png # 成功时保存的签到区域截图(自动生成) +``` + +> 说明:程序会优先尝试复用 `state.json` 中的登录状态;状态失效时会自动回退为账号密码登录。 + +## 二、准备工作 + +1. **创建账号文件 `account.txt`** + - 第 1 行:用户名 + - 第 2 行:密码 + - 文件编码建议使用 `UTF-8`,不要多余空行或空格。 + 示例: + ``` + your_username + your_password + ``` + +2. **(可选)检查/调整配置** — 打开 `main.py` 顶部“配置”区域,可按需修改: + - `ACCOUNT_FILE`:账号文件路径(默认 `account.txt`) + - `STATE_FILE`:状态缓存文件(默认 `state.json`) + - `MAX_RETRY`:失败重试次数(`0` 表示无限重试) + - `SIGNED_ANCESTOR_SCREENSHOT`:成功截图文件名(默认 `checkin.png`) + +> **无需修改网站域名与地址**,默认已指向 `https://www.natfrp.com/user/`。 + +## 三、环境要求 + +需要安装Playwright及其依赖,推荐使用 Python 3.7 及以上版本。 + +## 四、在 QingLong(青龙)中使用 + +> 适用于青龙面板 v2.XX 及以上版本(路径请以你面板的实际目录为准)。 + +### 1)放置脚本与账号文件 + +方式 A:在面板「文件管理」中将 `main.py` 和 `account.txt` 上传到脚本目录,例如: +``` +/ql/data/scripts/natfrp_checkin/ +``` + +方式 B:通过仓库/订阅拉取(如你有自建仓库),确保最终能在脚本目录看到 `main.py` 与 `account.txt`。 + +> **注意**:`account.txt` 一定要跟 `main.py` 放在同一目录(或同步修改 `ACCOUNT_FILE` 为你的自定义路径)。 + +### 2)创建定时任务 + +在青龙面板 ->「定时任务」->「新建任务」: + +- **名称**:NATFRP 自动签到 +- **命令**(按你的实际路径替换): + ```bash + python3 /ql/data/scripts/natfrp_checkin/main.py + ``` +- **定时规则(CRON)**:建议每日固定时间运行一次,例如每天 8:10: + ``` + 10 8 * * * + ``` + +保存后即可生效。你也可以在任务列表中手动点击「运行」测试是否正常。 + +### 3)查看运行日志与产物 + +- 在任务详情页可查看实时日志; +- 成功后会生成 `checkin.png`(截图)与 `state.json`(状态); +- 若失败,会打印原因并根据 `MAX_RETRY` 自动重试。 + +## 五、常见问题 + +1. **提示账号文件不存在或格式错误** + - 确认 `account.txt` 与 `main.py` 在同一目录; + - 确认文件编码为 `UTF-8`,且只有两行:第一行用户名,第二行密码; + - 不要包含多余空行或空格。 + +2. **一直提示登录状态过期** + - 删除同目录下旧的 `state.json` 后重跑; + - 确认账号密码正确; + - 网络/地区问题可能导致访问异常,可稍后再试。 + +3. **出现滑块但始终无法通过** + - 目标站点的风控策略可能调整; + - 可适当增加重试次数(`MAX_RETRY`),或更换执行时间段; + - 若长时间失败,建议手动登录一次,让站点信任度恢复。 + +4. **成功了但没有截图** + - 程序以出现“今天已经签到过啦”的提示为成功判据; + - 如果站点文案发生变化,可修改 `ALREADY_SIGNED_TEXT`; + - 截图保存路径由 `SIGNED_ANCESTOR_SCREENSHOT` 控制(默认 `checkin.png`)。 + +5. **青龙里命令执行不动或报路径错误** + - 确认命令中的绝对路径与实际文件路径一致; + - 如果你把文件放在其它目录,务必同步修改命令与 `ACCOUNT_FILE` 路径。 + +## 六、日志与产物 + +- **日志输出**:执行时会输出每一步进度与判定结果,便于排查。 +- **截图与状态**: + - `checkin.png`:成功时的页面关键区域截图; + - `state.json`:登录状态缓存(自动生成/刷新); + - 若启用内部调试流程,可能在同目录产生若干 `debug_*.png` 用于图像比对,这些文件可忽略或自行清理。 + +--- + +**至此即可完成配置与使用。** +如需嵌入到你现有的青龙仓库,只要保证 `main.py` 与 `account.txt` 的相对位置一致,并按上文的命令与定时规则召唤即可。祝签到愉快 🎯 \ No newline at end of file diff --git a/sakura_frp_auto_check/checkin.png b/sakura_frp_auto_check/checkin.png new file mode 100644 index 0000000000000000000000000000000000000000..158a2830781cc4307317648ab2fa9b984fcb4444 GIT binary patch literal 4161 zcmchbX*3j$*T)BAi>PEvn4)NswXtu>R`#+lLm5nnPllMH zmMmlJGRD3P2J`g%U;mz0&vTv^pL_1P=iYPgoBO@z+&FVngR^HYodEyai0KmO}Pxp>xa6X0T1Q+sX?b+%HgibsUn31CB_1#DmtKl3#X?J!j z|M(Gye)E<8hYL^Dr8$?hIpFFqRu-~RPUlx_XE9R8uz2C>O0LMa>(}LeC^C||U0n5; zRK8%Oh#fxYC(yMWb%i?hcLue`x|IYn?TYv){IBm3|wI#@PWefj2Z-=+OqezKQT>Q(^qcA72M;^Fb8#Oddf06i5=SYWS+~MUg|A7pnxS5~QW+ z7p%xgUz=|DdedH(j2)O3!53z_gg zhf(Gh({EPCcLc2;v)2Z1ab!8Yd|HXM$K(9%Zwu3&oy?~F&P;Dr5IqEs7v_s(l*N+c zj4Qt3({?i%ib$K3KAcMOF>O@Pl@OmObyw4C;~e0=bs8`v19~xu-%%;*Yf^HNth5Pg z*Y`#2+p&KJ2u&dm0|k}aUVx2K7DUb7aFWWcu3*f!mHk60rOBBFuYnBc;wtvJi;Q+6 zcF%@`{N{g@)DP#j6@EvJI6{_2k|gBST1}6%_P(F!R-@nUM7j=P{65=%p$AZsiN@z! zPdBVE;wRbwNh>=ZaoSd48{^fU!w@;u*=o&r=``=C0&97GDGC0G`k;_tr;%-y7y5#> zV3h8xuvy6!5*CsgS1z`n=RSR(E=)JaG{pxvok1#%){4loNUy`sFq6q)j=WWSPKq3 zqjq$L)*VW8b__Q$50>!gSoicSzX{^DuF63&{a)t)CfWP;<%>Lx^-nTSw|{l^ZapPB zZ%AnDd@rezSuTJYN8| z)sjA7`QlwLt$7syKPrnfu$$JYv3J;$)&D@r_A@Xo zzfETDAx-AR64&o5J8sGlCGR3P?ZhTA?HC=9%*Dj%LGGZS+@y8G?efR%ak{k7DYM?F zU6Y{e?qO(kwoU5_m)H2*sProkvX1-C&xVdR>@xY-#_#;$sK|)^fj+o-E78eMceLiw zC?!6jbqL$i1Qt!Ca1q2fkW`KP>^IMw^_9%07TmY~Xsa(UJ8KyHCwK3aY)gFk5A6rP zkr%W4uC3N}>jtQ&|KT!4X0Lij3Xrpn-RF%L?cpB{`x%?{z?q zdyar+dq1@{fVQY3QQqxiC<##R8{=nsU3%2y4~lEE0C>a+53KpieGeo>=n` zV)T8mp8K&wa?sMA+SDa6@&+E?a;>(Cj2K@!K86OFD?%1vk4*P;#cA_Sh1br#D0{H( zX|n9OJj_K1A+Qcg8+X@r8lPUL3HQF2STnda+fhgqwKyhfBqj|fgc)VQ<*LfQwrO~? z&7f=sfEEo$1szHc1F9d*pQjha9(K(Rqr~yXYvsIdr`&EMd z+;U(RXY`)zgzdb!#L?(mU;Bh!(}X%Nd_bUhbXW`5=PdizTG;C4U)^$L)h~H$iqrBO zoZKQ(1XT*Md1IkU91a^%$|;%^R1XpRN{b7s-S(W3ytrvrl?5q;Pl-rYgR(~Ane3fOXw)-RHn}UYp=iP*FF#yrKZm4ecd>j)*mcagbkYi5(Wju-D4{Bk zf?{rA&k1#K%BuQ?pNi@}qBc%A4~xoXmDA<(a#W{%&diK`us*(!f3OKZTbUAS#Z|?< zVZGyi;wE_YG3L0N70i#Wof7kUv#(aC|1457O#Luwr{3`mUo9%{L*)$ESarNj@;6B0 zM*l099b^;SSHaP&qPs`ipKxck`BKiB7S=-P zIjLo5yTd@w>_ccpYxk6J2}9V=rM9W`p-Bmrx+gCrFXrB#wvl-stzRo(A0MQ`O2sK3 z27WJ3*^jGO9VQoMd}H4G1bRo|^YxUbTiesHWIo$WnL3BtH(xe7^`}b=lF6P_3UVgt ziU>#gY67Ush~*P9%}HsT+#T33$-1vOjxlZVG31GjQ zh32C1IlBn#K?Rz7K_e`XJ}*iSKiVBv+px6oVNCWvMacPYUyV(#(A;)v3%AqbQ3A!9 zf&mfYZ2!(;vnXtGJ@3^AmDLGZFU26|`ZN2vkPaJ^Nln8A;$h7#|8uM4Cn>I3!IFTQ z#&iglvTNzOkjFQcmG#WInb16u^#?K~#NDITF*n(q)YUJMetMLOFJDZ0+UN8tc>R^u zW6}F8{o4Mn?_bSi&?L9peRc&H|M6OLd~F*ST=!SSEZKjmv@V3YBkNVvrcWZ2YGPND zHj}b>4e`|6ShPC1N+r9NmHy@1Vym93PK=Qw;P*uGHLUkzM`h->oNp~{PxHR`Vi4iB-!6?S;l=vEsDoeNo1t=5H}xD7Q1hnClufWddwMp%1C*|wU9(kA_9`!2IXHC@a+ z80TX=Kj7F+pAa9K=^cT_qXk$YY@0EGl6R`Y2z?WXf6qyU>D zD?Vg-JnLD}+dMaw^P7|Cj(Pz*W~q;yVL!SAOcu>-tI85-kNjm*zuLe!ElPUfJFrsZA}avDW1)6=U}N2ftVGl zfByWLSpSLBVtYP?KGlYB<~^+(M$l<-$9eP}hLNSM`A^|W{4X^x0RNR%|LGB^gG2rI z@8HUm@G%Gk@}I>^ji5~*ji*4SvFu9Hsp zODnF|9UYu((C>~q|E+XekHP}e5WtsYZ3*6gZTLS8@~`n65TpibHRh|xY%bk^2^?t+ zKO}c0*f0X_*Z0 表示最大重试次数 + +# --- 新增/修改 --- +ALREADY_SIGNED_TEXT = "今天已经签到过啦" # 用于判定成功的文案(包含即可) +SIGNED_ANCESTOR_LEVELS = 3 # 向上取第 3 层父级 +SIGNED_ANCESTOR_SCREENSHOT = "checkin.png" # 成功时保存截图的文件名 +# ======================== + +# ---------------- 读取账号密码 ---------------- +def load_username_password(path: Path): + if not path.exists(): + raise FileNotFoundError(f"{path} 文件不存在") + lines = [l.strip() for l in path.read_text(encoding="utf-8").strip().splitlines() if l.strip()] + if len(lines) < 2: + raise ValueError("account.txt 必须至少有两行:用户名、密码") + return lines[0], lines[1] + +# ---------------- 检测函数 ---------------- +def is_cookie_expired(page, timeout=6000): + try: + # 页面出现“Nyatwork 登录”说明需要重新登录 + page.wait_for_selector("text=Nyatwork 登录", timeout=timeout) + return True + except: + return False + +def confirm_age_if_needed(page, timeout=6000): + try: + page.wait_for_selector('text=是,我已满18岁', timeout=timeout) + page.click('text=是,我已满18岁') + print("[INFO] 已点击满18岁确认按钮") + except: + print("[INFO] 无需进行年龄确认") + +def is_logged_in(page, timeout=6000): + try: + page.wait_for_selector("text=账号信息", timeout=timeout) + print("[INFO] 检测到已登录状态") + return True + except: + return False + +def is_sign_button_visible(page, timeout=6000): + try: + page.wait_for_selector("text=点击这里签到", timeout=timeout) + return True + except: + return False + +def is_captcha_visible(page, timeout=6000): + try: + page.wait_for_selector(".geetest_slider_button", timeout=timeout) + return True + except: + return False + +def wait_captcha_disappear(page, timeout=10000): + try: + page.wait_for_selector("text=签到成功", timeout=timeout) + return True + except: + return False + +# --- 新增/修改:查找“今天已经签到过啦”的元素(包含匹配),并取其第 N 层父级 --- +def find_signed_text_locator(page, timeout=6000): + """ + 返回包含“今天已经签到过啦”的元素 locator(取第一个可见)。 + 使用 normalize-space 规避前后空格影响,并做“包含”匹配。 + """ + selector = "text=%s" % ALREADY_SIGNED_TEXT + try: + page.wait_for_selector(selector, timeout=timeout) + return page.locator(selector).first + except Exception as e: + return None + +def screenshot_signed_text_ancestor(page, levels: int = SIGNED_ANCESTOR_LEVELS, + path: str = SIGNED_ANCESTOR_SCREENSHOT, + timeout: int = 6000) -> bool: + """ + 截图“包含『今天已经签到过啦』的元素”的第 levels 层父级。 + 若未找到该元素或父级,兜底整页截图。返回 True 表示截到了祖先元素,False 表示做了兜底。 + """ + base = find_signed_text_locator(page, timeout=timeout) + if base is None: + # 没找到文本元素,整页兜底 + try: + page.screenshot(path=path, full_page=True) + print(f"[WARN] 未找到包含『{ALREADY_SIGNED_TEXT}』的元素,已兜底整页截图:{path}") + except Exception as e: + print(f"[ERROR] 兜底整页截图失败:{e}") + return False + return False + + try: + # 取第 N 层父级 + ancestor = base.locator(f"xpath=ancestor::*[{levels}]").first + # 有些页面结构不够深,尝试逐级回退 + for lvl in range(levels, 0, -1): + candidate = base.locator(f"xpath=ancestor::*[{lvl}]").first + try: + candidate.wait_for(state="visible", timeout=1200) + try: + candidate.scroll_into_view_if_needed() + except: + pass + candidate.screenshot(path=path) + print(f"[INFO] 已保存签到截图:{path}") + return True + except: + continue + + # 如果所有层级都没成功,整页兜底 + page.screenshot(path=path, full_page=True) + print(f"[WARN] 祖先节点截图未成功,已兜底整页截图:{path}") + return False + except Exception as e: + print(f"[ERROR] 祖先截图异常:{e}") + try: + page.screenshot(path=path, full_page=True) + print(f"[WARN] 异常时已兜底整页截图:{path}") + except: + return False + return False + +# ---------------- 操作函数 ---------------- +def login_with_user_pass(page, username, password): + page.fill("#username", username) + page.fill("#password", password) + page.click("button[id=login]") + print("[INFO] 已提交登录表单") + +def click_sign_in_button(page): + page.click("text=点击这里签到") + print("[INFO] 已点击签到按钮") + +def drag_slider_fixed_steps(page, slider_element, distance, button_type="middle", debug_pause=False): + box = slider_element.bounding_box() + start_x = int(box['x']) + start_y = int(box['y'] + box['height'] / 2) + + print(f"[INFO] 滑块初始坐标: ({start_x}, {start_y})") + print(f"[INFO] 目标滑动距离: {distance} 像素") + + page.mouse.move(start_x, start_y) + page.mouse.down(button=button_type) + page.wait_for_timeout(300) + + page.mouse.move(start_x + distance + random.randint(2, 8), start_y) + page.wait_for_timeout(500) + + page.mouse.move(start_x + distance - 2, start_y) + page.mouse.move(start_x + distance - 6, start_y) + page.wait_for_timeout(400) + page.mouse.move(start_x + distance - 8, start_y) + + page.mouse.up(button=button_type) + print("[INFO] 滑动完成") + + if debug_pause: + page.pause() + +def solve_geetest_puzzle(page): + bg_path = "bg.png" + fullbg_path = "fullbg.png" + page.query_selector('.geetest_canvas_bg').screenshot(path=bg_path) + page.evaluate("""document.querySelector('.geetest_canvas_fullbg').style.display = 'block';""") + page.query_selector('.geetest_canvas_fullbg').screenshot(path=fullbg_path) + page.evaluate("""document.querySelector('.geetest_canvas_fullbg').style.display = 'none';""") + + gap_x = get_gap_offset(bg_path, fullbg_path, debug=True) + print(f"[INFO] 缺口坐标: {gap_x}") + + slider = page.query_selector('.geetest_slider_button') + distance = gap_x + + drag_slider_fixed_steps(page, slider, distance, button_type="left", debug_pause=False) + print("[INFO] 验证码滑动完成") + +# --- 新增/修改:以“今天已经签到过啦”判定成功 --- +def wait_signed_text_and_shoot(page, timeout=8000) -> bool: + """ + 等待出现包含『今天已经签到过啦』的元素;出现则截图其第 3 层父级并返回 True。 + 若未出现则返回 False。 + """ + loc = find_signed_text_locator(page, timeout=timeout) + if loc is None: + return False + # 找到了就截图祖先 + screenshot_signed_text_ancestor(page, SIGNED_ANCESTOR_LEVELS, SIGNED_ANCESTOR_SCREENSHOT) + return True + +# ---------------- 主逻辑 ---------------- +def main() -> bool: + """ + 主流程 + :return: True 表示成功,False 表示失败需要重试 + """ + username, password = load_username_password(ACCOUNT_FILE) + + with sync_playwright() as p: + browser = p.chromium.launch(headless=True, slow_mo=200) + + # 先尝试从 state.json 加载状态(cookie/session) + try: + context = browser.new_context(storage_state=STATE_FILE) + print("[INFO] 已加载保存的状态文件") + except: + print("[WARN] 未找到状态文件,将新建上下文") + context = browser.new_context() + + page = context.new_page() + page.goto(target_url, timeout=8000) + + # 登录后再检查 18 岁确认(若页面已处于登录态也会直接检测) + if is_logged_in(page): + confirm_age_if_needed(page) + else: + # 检查状态文件里的 cookie 是否失效 + if is_cookie_expired(page): + print("[WARN] Cookie 已过期,使用账号密码登录") + login_with_user_pass(page, username, password) + if is_logged_in(page): + print("[INFO] 登录成功,更新状态文件") + context.storage_state(path=STATE_FILE) + # 登录成功后再检查一次 18 岁确认 + confirm_age_if_needed(page) + else: + print("[ERROR] 登录失败") + browser.close() + return False + + # --- 新增/修改:页面若已出现“今天已经签到过啦”,直接判定成功并截图祖先 --- + if wait_signed_text_and_shoot(page, timeout=2000): + print("[INFO] 已经签到过啦(页面已有提示)") + browser.close() + return True + + # 正常签到流程 + if is_sign_button_visible(page): + click_sign_in_button(page) + if is_captcha_visible(page): + solve_geetest_puzzle(page) + # 成功判定:等待出现“今天已经签到过啦” + if wait_signed_text_and_shoot(page, timeout=10000): + print("[INFO] 签到成功(出现『今天已经签到过啦』)") + browser.close() + return True + else: + print("[ERROR] 签到失败:未出现『今天已经签到过啦』提示") + browser.close() + return False + else: + # 无验证码路径:同样等待出现成功提示 + if wait_signed_text_and_shoot(page, timeout=8000): + print("[INFO] 签到成功(无需验证码)") + browser.close() + return True + else: + print("[ERROR] 签到失败:未出现『今天已经签到过啦』提示(无需验证码路径)") + browser.close() + return False + else: + # 不再以“找不到按钮”作为成功判据;此分支仅作为提示与失败返回 + print("[ERROR] 未发现签到按钮,且未检测到『今天已经签到过啦』提示") + browser.close() + return False + +# ---------------- 带重试启动 ---------------- +if __name__ == "__main__": + attempt = 0 + while True: + attempt += 1 + print(f"[INFO] 第 {attempt} 次执行签到") + try: + success = main() + except Exception as e: + print(f"[ERROR] 程序异常: {e}") + success = False + + if success: + print("[INFO] 本次执行成功,退出程序") + break + else: + if MAX_RETRY > 0 and attempt >= MAX_RETRY: + print("[ERROR] 超过最大重试次数,退出程序") + break + print("[WARN] 本次失败,10秒后重试") + time.sleep(10)