Fetching your IP...
52 | 53 | 54 | """ 55 | 56 | def __init__(self, debug: bool = False, headless: Optional[bool] = False, useragent: Optional[str] = None, browser_type: str = "chromium"): 57 | self.debug = debug 58 | self.browser_type = browser_type 59 | self.headless = headless 60 | self.useragent = useragent 61 | self.browser_args = [] 62 | if useragent: 63 | self.browser_args.append(f"--user-agent={useragent}") 64 | 65 | async def _setup_page(self, browser, url: str, sitekey: str, action: str = None, cdata: str = None): 66 | if self.browser_type == "chrome": 67 | page = browser.pages[0] 68 | else: 69 | page = await browser.new_page() 70 | 71 | url_with_slash = url + "/" if not url.endswith("/") else url 72 | 73 | if self.debug: 74 | logger.debug(f"Navigating to URL: {url_with_slash}") 75 | 76 | turnstile_div = f'' 77 | page_data = self.HTML_TEMPLATE.replace("", turnstile_div) 78 | 79 | await page.route(url_with_slash, lambda route: route.fulfill(body=page_data, status=200)) 80 | await page.goto(url_with_slash) 81 | 82 | return page, url_with_slash 83 | 84 | async def _get_turnstile_response(self, page, max_attempts: int = 10) -> Optional[str]: 85 | for attempt in range(max_attempts): 86 | if self.debug: 87 | logger.debug(f"Attempt {attempt + 1}: No Turnstile response yet.") 88 | 89 | try: 90 | turnstile_check = await page.input_value("[name=cf-turnstile-response]") 91 | if turnstile_check == "": 92 | await page.click("//div[@class='cf-turnstile']", timeout=3000) 93 | await asyncio.sleep(3) 94 | else: 95 | return turnstile_check 96 | except Exception as e: 97 | logger.debug(f"Click error: {str(e)}") 98 | continue 99 | return None 100 | 101 | async def solve(self, proxy:json,url: str, sitekey: str, action: str = None, cdata: str = None): 102 | start_time = time.time() 103 | logger.debug(f"Attempting to solve URL: {url}") 104 | proxy_config = config.get("proxy") or {} 105 | # proxy = { 106 | # "server": proxy_config.get("server"), 107 | # "username": proxy_config.get("username"), 108 | # "password": proxy_config.get("password"), 109 | # } 110 | logger.debug(f"Proxy: {proxy},type:{type(proxy)}") 111 | async with AsyncCamoufox( 112 | headless=self.headless, 113 | geoip=True, 114 | proxy=proxy, 115 | ) as browser: 116 | try: 117 | page,url_with_slash = await self._setup_page(browser, url, sitekey, action, cdata) 118 | token = await self._get_turnstile_response(page) 119 | elapsed = round(time.time() - start_time, 2) 120 | 121 | if not token: 122 | logger.error("Failed to retrieve Turnstile value.") 123 | return TurnstileResult(None, elapsed, "failure", "No token obtained") 124 | 125 | logger.info(emoji("SUCCESS", f"Solved Turnstile in {elapsed}s -> {token[:10]}...")) 126 | return TurnstileResult(token, elapsed, "success") 127 | except Exception as e: 128 | logger.error(emoji("ERROR", f"Failed to solve Turnstile: {str(e)}")) 129 | return TurnstileResult(None, elapsed, "failure", str(e)) 130 | finally: 131 | await browser.close() 132 | # 强制垃圾回收 133 | gc.collect() 134 | # 打印内存使用情况 135 | rss_kb = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss 136 | rss_mb = rss_kb / 1024 if sys.platform != "darwin" else rss_kb / (1024 * 1024) # macOS单位不同 137 | logger.debug(f"🧠 内存占用: {rss_mb:.2f} MB") 138 | logger.debug(f"对象数量追踪: {len(gc.get_objects())}") 139 | try: 140 | open_fds = len(os.listdir(f'/proc/{os.getpid()}/fd')) 141 | logger.debug(f"📎 打开文件描述符数: {open_fds}") 142 | except Exception: 143 | pass 144 | 145 | async def get_turnstile_token(proxy:json,url: str, sitekey: str, action: str = None, cdata: str = None, debug: bool = False, headless: bool = False, useragent: str = None): 146 | solver = TurnstileSolver(debug=debug, useragent=useragent, headless=headless) 147 | logger.debug(f"solver: {solver}") 148 | result = await solver.solve(proxy=proxy,url=url, sitekey=sitekey, action=action, cdata=cdata) 149 | return result.__dict__ 150 | 151 | async def run(task_data,proxy): 152 | logger.debug(f"task_data: {task_data}") 153 | url = task_data["websiteURL"] 154 | sitekey = task_data["websiteKey"] 155 | action = task_data.get("metadata", {}).get("action") 156 | logger.debug(f"action: {sitekey}") 157 | headless_str = config.get("camoufox").get("headless", "true") 158 | headless = headless_str.lower() == "true" 159 | logger.debug(f"headless: {headless}") 160 | res = await get_turnstile_token( 161 | proxy=proxy, 162 | url=url, 163 | sitekey=sitekey, 164 | action=None, 165 | cdata=None, 166 | debug=False, 167 | headless=headless, 168 | useragent=None, 169 | ) 170 | return { 171 | "token": res["turnstile_value"], 172 | "elapsed": res["elapsed_time_seconds"], 173 | "status": "success" if res["turnstile_value"] else "failure", 174 | "type": "turnstile" 175 | } -------------------------------------------------------------------------------- /client/task_handlers/HcaptchaCracker.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import gc 3 | import json 4 | import os 5 | import resource 6 | import sys 7 | import time 8 | 9 | import yaml 10 | from camoufox.async_api import AsyncCamoufox 11 | from hcaptcha_challenger.agent import AgentV, AgentConfig 12 | from hcaptcha_challenger.models import CaptchaResponse, ChallengeSignal 13 | from hcaptcha_challenger.utils import SiteKey 14 | from common.logger import get_logger,emoji 15 | 16 | logger = get_logger("HCaptcha") 17 | 18 | with open("config/config.yaml", "r") as f: 19 | config = yaml.safe_load(f) 20 | # gemini_key = config.get("apikey").get("gemini_api_key") 21 | # models = config.get("models") 22 | headless_str = config.get("camoufox").get("headless", "true") 23 | headless = headless_str.lower() == "true" 24 | # if gemini_key: 25 | # os.environ["GEMINI_API_KEY"] = gemini_key 26 | # else: 27 | # raise RuntimeError("config.yaml 缺少 gemini_api_key") 28 | 29 | async def run(task_data, proxy): 30 | url = task_data["websiteURL"] 31 | sitekey = task_data["websiteKey"] 32 | print(task_data) 33 | gemini_key = task_data["clientKey"] 34 | action = task_data.get("metadata", {}).get("action", "") 35 | cdata = task_data.get("metadata", {}).get("cdata", "") 36 | 37 | logger.debug(f"🌐 Preparing hCaptcha page at {url}") 38 | start_time = time.time() 39 | async with AsyncCamoufox( 40 | headless=headless, 41 | proxy=proxy, 42 | geoip=True, 43 | args=["--lang=en-US", "--accept-language=en-US,en;q=0.9"] 44 | ) as browser: 45 | try: 46 | page = await browser.new_page() 47 | await page.goto(SiteKey.as_site_link(sitekey)) 48 | 49 | # 初始化 Agent 50 | agent_config = AgentConfig( 51 | GEMINI_API_KEY=gemini_key, 52 | EXECUTION_TIMEOUT = 300, 53 | RESPONSE_TIMEOUT = 30, 54 | RETRY_ON_FAILURE = True, 55 | # CHALLENGE_CLASSIFIER_MODEL=models['CHALLENGE_CLASSIFIER_MODEL'], 56 | # IMAGE_CLASSIFIER_MODEL=models['IMAGE_CLASSIFIER_MODEL'], 57 | # SPATIAL_POINT_REASONER_MODEL=models['SPATIAL_POINT_REASONER_MODEL'], 58 | # SPATIAL_PATH_REASONER_MODEL=models['SPATIAL_PATH_REASONER_MODEL'], 59 | ) 60 | agent = AgentV(page=page, agent_config=agent_config) 61 | 62 | await agent.robotic_arm.click_checkbox() 63 | 64 | # 执行挑战并等待结果 65 | await agent.wait_for_challenge() 66 | elapsed = round(time.time() - start_time, 2) 67 | if agent.cr_list: 68 | cr = agent.cr_list[-1] 69 | cr_data = cr.model_dump() 70 | logger.debug(cr_data) 71 | token = cr_data["generated_pass_UUID"] if cr_data.get("is_pass") else None 72 | logger.info(emoji("SUCCESS", f"Solved Hcaptcha in {elapsed}s -> {token[:10]}...")) 73 | return { 74 | "token": token, 75 | "elapsed": cr_data.get("expiration", 0), 76 | "status": "success" if cr_data.get("is_pass") else "failure", 77 | "type": "hcaptcha" 78 | } 79 | else: 80 | return { 81 | "token": None, 82 | "elapsed": 0, 83 | "status": "failure", 84 | "type": "hcaptcha" 85 | } 86 | 87 | except Exception as e: 88 | logger.error(emoji("ERROR", f"Failed to solve Hcaptcha: {str(e)}")) 89 | return { 90 | "token": None, 91 | "elapsed": 0, 92 | "status": "failure", 93 | "type": "hcaptcha" 94 | } 95 | finally: 96 | await page.close() 97 | # 强制垃圾回收 98 | gc.collect() 99 | # 打印内存使用情况 100 | rss_kb = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss 101 | rss_mb = rss_kb / 1024 if sys.platform != "darwin" else rss_kb / (1024 * 1024) # macOS单位不同 102 | logger.debug(f"🧠 内存占用: {rss_mb:.2f} MB") 103 | logger.debug(f"对象数量追踪: {len(gc.get_objects())}") 104 | try: 105 | open_fds = len(os.listdir(f'/proc/{os.getpid()}/fd')) 106 | logger.debug(f"📎 打开文件描述符数: {open_fds}") 107 | except Exception: 108 | pass 109 | # if __name__ == "__main__": 110 | # task_data = { 111 | # "websiteURL": "https://faucet.n1stake.com/", 112 | # "websiteKey": "d0ba98cc-0528-41a0-98fe-dc66945e5416" 113 | # } 114 | # proxy = { 115 | # "server": "http://pr-sg.ip2world.com:6001", 116 | # "username": "capsolver-zone-resi-region-hk", 117 | # "password": "123456" 118 | # } 119 | # 120 | # token = asyncio.run(run(task_data, proxy)) 121 | # print(token) -------------------------------------------------------------------------------- /client/task_handlers/test.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import json 3 | 4 | from playwright.async_api import async_playwright, Page 5 | 6 | from hcaptcha_challenger.agent import AgentV, AgentConfig 7 | from hcaptcha_challenger.models import CaptchaResponse 8 | from hcaptcha_challenger.utils import SiteKey 9 | 10 | 11 | async def challenge(page: Page) -> AgentV: 12 | """Automates the process of solving an hCaptcha challenge.""" 13 | # Initialize the agent configuration with API key (from parameters or environment) 14 | agent_config = AgentConfig() 15 | 16 | # Create an agent instance with the page and configuration 17 | # AgentV appears to be a specialized agent for visual challenges 18 | agent = AgentV(page=page, agent_config=agent_config) 19 | 20 | # Click the hCaptcha checkbox to initiate the challenge 21 | # The robotic_arm is an abstraction for performing UI interactions 22 | await agent.robotic_arm.click_checkbox() 23 | 24 | # Wait for the challenge to appear and be ready for solving 25 | # This may involve waiting for images to load or instructions to appear 26 | await agent.wait_for_challenge() 27 | 28 | # Note: The code ends here, suggesting this is part of a larger solution 29 | # that would continue with challenge solving steps after this point 30 | return agent 31 | 32 | 33 | async def main(): 34 | async with async_playwright() as p: 35 | browser = await p.chromium.launch(headless=False) 36 | context = await browser.new_context() 37 | 38 | # Create a new page in the provided browser context 39 | page = await context.new_page() 40 | 41 | # Navigate to the hCaptcha test page using a predefined site key 42 | # SiteKey.user_easy likely refers to a test/demo hCaptcha with lower difficulty 43 | # await page.goto(SiteKey.as_site_link(SiteKey.discord)) 44 | await page.goto(SiteKey.as_site_link(SiteKey.user_easy)) 45 | 46 | # --- When you encounter hCaptcha in your workflow --- 47 | agent = await challenge(page) 48 | if agent.cr_list: 49 | cr: CaptchaResponse = agent.cr_list[-1] 50 | print(json.dumps(cr.model_dump(by_alias=True), indent=2, ensure_ascii=False)) 51 | 52 | 53 | if __name__ == "__main__": 54 | asyncio.run(main()) 55 | -------------------------------------------------------------------------------- /client/test.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | from task_handlers.HcaptchaCracker import run 3 | 4 | async def main(): 5 | proxy = { 6 | "server": "http://capsolver-zone-resi-region-hk:123456@pr-sg.ip2world.com:6001" 7 | 8 | } 9 | task = { 10 | "websiteURL": "https://accounts.hcaptcha.com/demo", 11 | "websiteKey": "00000000-0000-0000-0000-000000000000", # 替换为真实 sitekey 12 | "metadata": { 13 | "label": "bus" 14 | } 15 | } 16 | result = await run(task, proxy) 17 | print(result) 18 | 19 | if __name__ == "__main__": 20 | asyncio.run(main()) -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.9" 2 | 3 | services: 4 | frontend: 5 | container_name: frontend 6 | build: 7 | context: ./frontend 8 | dockerfile: Dockerfile 9 | ports: 10 | - "8080:8080" 11 | depends_on: 12 | - backend 13 | networks: 14 | - brush-net 15 | 16 | backend: 17 | container_name: backend 18 | build: 19 | context: ./backend 20 | dockerfile: Dockerfile 21 | ports: 22 | - "8000:8000" 23 | restart: always 24 | networks: 25 | - brush-net 26 | 27 | networks: 28 | brush-net: 29 | external: true -------------------------------------------------------------------------------- /frontend/Dockerfile: -------------------------------------------------------------------------------- 1 | # 构建阶段 2 | FROM node:18 AS builder 3 | WORKDIR /app 4 | COPY . . 5 | RUN npm install && npm run build 6 | 7 | # 部署阶段:用 nginx 托管构建产物 8 | FROM nginx:alpine 9 | COPY --from=builder /app/build /usr/share/nginx/html 10 | COPY nginx.conf /etc/nginx/conf.d/default.conf 11 | COPY ssl /app/ssl 12 | EXPOSE 8080 13 | CMD ["nginx", "-g", "daemon off;"] -------------------------------------------------------------------------------- /frontend/README.md: -------------------------------------------------------------------------------- 1 | # 🧩 监控面板 2 | 3 | 监控系统内所有节点和任务状态 4 | 5 | ## 📁 目录结构 6 | 7 | - src/ 8 | - index.js 9 | - App.jsx 10 | - api.js 11 | - components/ 12 | - TaskTable.jsx # 任务管理 13 | - WorkerTable.jsx # worker管理 14 | - pages/ 15 | - Login.jsx # 登陆页面,默认账号密码都是admin 16 | 17 | 18 | ## 🚀 启动方式 19 | 20 | ```bash 21 | npm install 22 | npm run start 23 | ``` -------------------------------------------------------------------------------- /frontend/common/logger.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import os 3 | from logging.handlers import RotatingFileHandler 4 | 5 | LOG_LEVEL = os.getenv("LOG_LEVEL", "INFO").upper() 6 | LOG_DIR = os.getenv("LOG_DIR", "./logs") 7 | os.makedirs(LOG_DIR, exist_ok=True) 8 | 9 | def get_logger(name: str) -> logging.Logger: 10 | logger = logging.getLogger(name) 11 | if logger.hasHandlers(): 12 | return logger 13 | 14 | logger.setLevel(LOG_LEVEL) 15 | 16 | formatter = logging.Formatter("[%(asctime)s] [%(levelname)s] [%(name)s] %(message)s") 17 | 18 | # 控制台输出 19 | ch = logging.StreamHandler() 20 | ch.setFormatter(formatter) 21 | logger.addHandler(ch) 22 | 23 | # 文件输出(按模块名称区分) 24 | fh = RotatingFileHandler(f"{LOG_DIR}/{name}.log", maxBytes=10 * 1024 * 1024, backupCount=3) 25 | fh.setFormatter(formatter) 26 | logger.addHandler(fh) 27 | 28 | return logger 29 | # common/emoji_log.py 30 | def emoji(level: str, message: str) -> str: 31 | tags = { 32 | "DEBUG": "🐞", 33 | "INFO": "ℹ️", 34 | "SUCCESS": "✅", 35 | "WARNING": "⚠️", 36 | "ERROR": "❌", 37 | "CRITICAL": "🔥", 38 | "TASK": "📌", 39 | "STARTUP": "🚀", 40 | "SHUTDOWN": "🛑", 41 | "NETWORK": "🌐", 42 | "DB": "🗃️" 43 | } 44 | return f"{tags.get(level.upper(), '')} {message}" -------------------------------------------------------------------------------- /frontend/nginx.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 8080; 3 | server_name localhost; 4 | 5 | # 静态资源 6 | location / { 7 | root /usr/share/nginx/html; 8 | index index.html; 9 | try_files $uri $uri/ /index.html; 10 | } 11 | 12 | # API 反向代理 13 | location /api/ { 14 | proxy_pass http://backend:8000/; 15 | proxy_http_version 1.1; 16 | proxy_set_header Host $host; 17 | proxy_set_header X-Real-IP $remote_addr; 18 | } 19 | location /ws/ { 20 | proxy_pass http://backend:8000/; 21 | proxy_http_version 1.1; 22 | proxy_set_header Upgrade $http_upgrade; 23 | proxy_set_header Connection "upgrade"; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frontend-dashboard-auth", 3 | "version": "1.0.0", 4 | "private": true, 5 | "dependencies": { 6 | "antd": "^5.13.5", 7 | "axios": "^1.6.8", 8 | "md5": "^2.3.0", 9 | "react": "^18.2.0", 10 | "react-dom": "^18.2.0", 11 | "react-router-dom": "^6.23.0", 12 | "react-scripts": "5.0.1" 13 | }, 14 | "scripts": { 15 | "start": "react-scripts start", 16 | "build": "react-scripts build" 17 | }, 18 | "browserslist": { 19 | "production": [ 20 | ">0.2%", 21 | "not dead", 22 | "not op_mini all" 23 | ], 24 | "development": [ 25 | "last 1 chrome version", 26 | "last 1 firefox version", 27 | "last 1 safari version" 28 | ] 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /frontend/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |