tls_registration_bot.py 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712
  1. import time
  2. import json
  3. import os
  4. import re
  5. import uuid
  6. import socket
  7. import shutil
  8. import random
  9. import requests
  10. import argparse
  11. import concurrent.futures
  12. from urllib.parse import urlencode
  13. from datetime import datetime, timedelta
  14. from typing import Optional, Dict
  15. from DrissionPage.common import Keys
  16. from DrissionPage import ChromiumPage, ChromiumOptions
  17. import configure
  18. from utils.cloudflare_bypass_for_scraping import CloudflareBypasser
  19. from toolkit.vs_cloud_api import VSCloudApi
  20. from toolkit.mihomo_tunnel import MihomoTunnel
  21. from vs_types import NotFoundError, PermissionDeniedError, RateLimiteddError, SessionExpiredOrInvalidError, BizLogicError
  22. from utils.mouse import HumanMouse
  23. from utils.keyboard import HumanKeyboard
  24. from utils.scroll import HumanScroll
  25. from utils.fingerprint_utils import FingerprintGenerator
  26. from utils.fake_utils import generate_random_account_detail
  27. def load_proxies(pool_name):
  28. """从 config/proxies.json 读取对应的代理池"""
  29. config_path = os.path.join(os.path.dirname(__file__), 'config', 'proxies.json')
  30. try:
  31. with open(config_path, 'r', encoding='utf-8') as f:
  32. data = json.load(f)
  33. proxies = data.get(pool_name, [])
  34. if not proxies:
  35. raise ValueError(f"代理池 '{pool_name}' 为空或不存在!")
  36. return proxies
  37. except Exception as e:
  38. print(f"读取代理配置文件失败: {e}")
  39. exit(1)
  40. class TlsRegistrator:
  41. def __init__(self, tls_url, proxy_config: Optional[Dict]=None, capsolver_key: Optional[str]=None, account_detail: Optional[Dict]=None):
  42. self.proxy_config = proxy_config
  43. self.capsolver_key = capsolver_key
  44. self.account_detail = account_detail
  45. # 隔离的用户数据目录
  46. self.instance_id = uuid.uuid4().hex[:8]
  47. self.tls_url = tls_url
  48. # self.instance_id = '18d389e9'
  49. self.workspace = os.path.abspath(os.path.join("data/temp_browser_data", f"reg_session_{self.instance_id}"))
  50. self.page = None
  51. self.mouse = None
  52. self.keyboard = None
  53. # 持有隧道实例
  54. self.tunnel = None
  55. def _log(self, msg):
  56. now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
  57. print(f"[{now}][TLS-Reg-{self.instance_id}] {msg}")
  58. def _get_free_port(self):
  59. """获取可用端口,防止 DrissionPage 解析日志报错"""
  60. with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
  61. s.bind(('', 0))
  62. return s.getsockname()[1]
  63. def save_screenshot(self, name_prefix):
  64. try:
  65. timestamp = int(time.time())
  66. filename = f"{self.instance_id}_{name_prefix}_{timestamp}.jpg"
  67. save_path = os.path.join("data", filename)
  68. os.makedirs("data", exist_ok=True)
  69. self.page.get_screenshot(path=save_path, full_page=False)
  70. self._log(f"Screenshot saved to {save_path}")
  71. except Exception as e:
  72. self._log(f"Failed to save screenshot: {e}")
  73. def init_browser(self):
  74. """初始化独立、配置好代理的浏览器环境"""
  75. self._log("Initializing browser...")
  76. co = ChromiumOptions()
  77. # 1. 端口与路径隔离
  78. port = self._get_free_port()
  79. co.set_local_port(port)
  80. co.set_user_data_path(self.workspace)
  81. chrome_path = configure.CHROME_PATH
  82. if not chrome_path:
  83. chrome_path = os.getenv("CHROME_BIN")
  84. if chrome_path and os.path.exists(chrome_path):
  85. co.set_paths(browser_path=chrome_path)
  86. if self.proxy_config and self.proxy_config.get("ip"):
  87. p = self.proxy_config
  88. if p.get('username') and p.get('password'):
  89. self._log(f"Starting Proxy Tunnel for {p.get('ip')}...")
  90. exit_node = {
  91. "name": "ExitNode",
  92. "type": p.get('proto'),
  93. "server": p.get('ip'),
  94. "port": p.get('port'),
  95. "username": p.get('username'),
  96. "password": p.get('password')
  97. }
  98. relay_node = None
  99. if configure.MIHOMO_RELAY_NODES:
  100. relay_node = random.choice(configure.MIHOMO_RELAY_NODES)
  101. mihomo_path = configure.MIHOMO_BIN_PATH
  102. if not mihomo_path:
  103. mihomo_path = os.getenv("MIHOMO_BIN")
  104. if not mihomo_path:
  105. raise BizLogicError(message='Mihomo path is null, You need set mihomo bin path in configure or os env')
  106. self.tunnel = MihomoTunnel(mihomo_path, exit_node=exit_node, relay_node=relay_node)
  107. local_proxy = self.tunnel.start()
  108. self._log(f"Tunnel started at {local_proxy}")
  109. co.set_argument(f'--proxy-server={local_proxy}')
  110. else:
  111. proxy_str = f"{p.get('proto')}://{p.get('ip')}:{p.get('port')}"
  112. co.set_argument(f'--proxy-server={proxy_str}')
  113. else:
  114. self._log("[WARN] No proxy configured!")
  115. fingerprint_gen = FingerprintGenerator()
  116. specific_fp = fingerprint_gen.generate(self.instance_id)
  117. self._log(f'browser fingerprint={specific_fp}')
  118. # 3. 反爬及稳定性配置
  119. co.headless(False)
  120. co.set_argument('--no-sandbox')
  121. # co.set_argument('--disable-gpu')
  122. co.set_argument('--disable-dev-shm-usage')
  123. co.set_argument('--window-size=1920,1080')
  124. co.set_argument('--disable-blink-features=AutomationControlled')
  125. co.set_argument(f"--fingerprint={specific_fp.get('seed')}")
  126. co.set_argument(f"--fingerprint-platform={specific_fp.get('platform')}")
  127. co.set_argument(f"--fingerprint-brand={specific_fp.get('brand')}")
  128. self.page = ChromiumPage(co)
  129. self.page.get(self.tls_url)
  130. time.sleep(5)
  131. cf_bypasser = CloudflareBypasser(self.page, log=True)
  132. cf_bypasser.bypass(max_retry=8)
  133. time.sleep(3)
  134. cf_bypasser.handle_waiting_room()
  135. self._log("正在初始化拟人化工具...")
  136. self.mouse = HumanMouse(self.page, debug=False)
  137. self.keyboard = HumanKeyboard(self.page)
  138. self._log("随机化鼠标开始位置...")
  139. viewport_width = self.page.rect.viewport_size[0]
  140. viewport_height = self.page.rect.viewport_size[1]
  141. init_x = random.randint(10, viewport_width - 10)
  142. init_y = random.randint(10, viewport_height - 10)
  143. self.mouse.move(init_x, init_y)
  144. def solve_captcha(self, page_url: str, task_type: str, site_key: str, use_proxy = False, action: str = None, api_domain: str = None) -> str:
  145. """通用解决验证码 (同步 User-Agent 防止被盾识别为高风险)"""
  146. if not self.capsolver_key:
  147. raise ValueError("Capsolver API key missing")
  148. task = {
  149. "type": task_type,
  150. "websiteURL": page_url,
  151. "websiteKey": site_key,
  152. }
  153. if api_domain:
  154. task["apiDomain"] = api_domain
  155. if use_proxy:
  156. proxy = self.proxy_config
  157. task["proxyType"] = proxy.get('proto', 'http')
  158. task["proxyAddress"] = proxy.get('ip')
  159. task["proxyPort"] = int(proxy.get('port'))
  160. if proxy.get('username'):
  161. task["proxyLogin"] = proxy.get('username')
  162. task["proxyPassword"] = proxy.get('password')
  163. if action:
  164. task["pageAction"] = action
  165. payload = {"clientKey": self.capsolver_key, "task": task}
  166. res = requests.post("https://api.capsolver.com/createTask", json=payload, timeout=20)
  167. if res.status_code != 200 or res.json().get("errorId") != 0:
  168. raise Exception(f"Failed to create capsolver task: {res.text}")
  169. task_id = res.json().get("taskId")
  170. self._log(f"Task created: {task_id}. Waiting for solution...")
  171. for _ in range(30):
  172. r = requests.post(
  173. "https://api.capsolver.com/getTaskResult",
  174. json={"clientKey": self.capsolver_key, "taskId": task_id},
  175. timeout=20
  176. )
  177. data = r.json()
  178. if data.get("status") == "ready":
  179. self._log("Captcha solved successfully!")
  180. return data["solution"].get("gRecaptchaResponse") or data["solution"].get("token")
  181. time.sleep(3)
  182. raise Exception("Capsolver task timeout")
  183. def register(self):
  184. """执行自动注册"""
  185. email = self.account_detail.get('email')
  186. password = self.account_detail.get('pwd')
  187. self._log(f'Account Detail:{self.account_detail}')
  188. btn_selector = '#submit'
  189. if not self.page.wait.ele_displayed(btn_selector, timeout=3):
  190. register_btn = self.page.ele("tag:a@@href:registration")
  191. self.mouse.human_click_ele(register_btn)
  192. time.sleep(3)
  193. if not self.page.wait.ele_displayed(btn_selector, timeout=10):
  194. raise BizLogicError(message=f"Can't find selector={btn_selector}")
  195. time.sleep(random.uniform(0.5, 1))
  196. self._log("正在填写邮箱和密码...")
  197. email_input_e = self.page.ele('#email')
  198. self.mouse.human_click_ele(email_input_e)
  199. self.keyboard.type_text(email, humanize=True)
  200. time.sleep(random.uniform(0.2, 0.5))
  201. password_e = self.page.ele('#password')
  202. self.mouse.human_click_ele(password_e)
  203. self.keyboard.type_text(password, humanize=True)
  204. time.sleep(random.uniform(0.2, 0.5))
  205. confirm_password_e = self.page.ele('#confirm-password')
  206. self.mouse.human_click_ele(confirm_password_e)
  207. self.keyboard.type_text(password, humanize=True)
  208. time.sleep(random.uniform(0.2, 0.5))
  209. self._log("正在勾选必选条款...")
  210. for checkbox_id in ['#terms-and-conditions', '#biometric-data', '#privacy-notice']:
  211. check_box_e = self.page.ele(checkbox_id).next()
  212. self.mouse.human_click_ele(check_box_e)
  213. time.sleep(random.uniform(0.3, 0.6))
  214. self._log("提交注册...")
  215. btn_e = self.page.ele(btn_selector)
  216. time.sleep(random.uniform(0.3, 0.6))
  217. self.mouse.human_click_ele(btn_e)
  218. self._log("正在等待验证结果 (最多10秒)...")
  219. success_dialog = self.page.wait.ele_displayed('tag:h1@text():Check your email inbox', timeout=10)
  220. if not success_dialog:
  221. self.page.get_screenshot("failed_submit.png")
  222. raise BizLogicError(message='Failed to submit account registration')
  223. self._log("✅ 操作成功!已弹出提示:Check your email inbox")
  224. return True
  225. def activate(self, sent_at=None):
  226. email = self.account_detail.get('email')
  227. email_box = 'visafly666@gmail.com'
  228. sender = 'TLSContact'
  229. recipient = email
  230. subject_keywords = 'TLSContact'
  231. body_keywords = ''
  232. if not sent_at:
  233. now_utc = datetime.utcnow()
  234. sent_at = now_utc.strftime("%Y-%m-%d %H:%M:%S")
  235. content_out = VSCloudApi.Instance().fetch_mail_content(
  236. email=email_box,
  237. sender=sender,
  238. recipient=recipient,
  239. subject_keywords=subject_keywords,
  240. body_keywords=body_keywords,
  241. sent_date=sent_at,
  242. expiry=600
  243. )
  244. self._log(f'activate email content={content_out}')
  245. match = re.search(r'https://\S+', content_out)
  246. activate_link = match.group(0) if match else None
  247. self.page.get(activate_link)
  248. btn_selector = "#activation-pending-button"
  249. if not self.page.wait.ele_displayed(btn_selector, timeout=10):
  250. raise BizLogicError(message=f"Wait ele={btn_selector} timeout")
  251. self.page.ele(btn_selector).click()
  252. time.sleep(3)
  253. def make_account_useful(self):
  254. def fill_date_field(page, selector, date_str):
  255. if not date_str:
  256. return
  257. ele = page.ele(selector)
  258. ele.scroll.to_see(center=True)
  259. js_detect_format = """
  260. const parts = new Intl.DateTimeFormat().formatToParts(new Date(2023, 11, 31));
  261. let format = [];
  262. for (let part of parts) {
  263. if (part.type === 'year') format.push('Y');
  264. if (part.type === 'month') format.push('M');
  265. if (part.type === 'day') format.push('D');
  266. }
  267. return format;
  268. """
  269. date_format = page.run_js(js_detect_format)
  270. year, month, day = date_str.split('-')
  271. date_dict = {
  272. 'Y': year,
  273. 'M': month.zfill(2),
  274. 'D': day.zfill(2)
  275. }
  276. ele.click()
  277. time.sleep(0.1)
  278. page.actions.type(Keys.LEFT * 3)
  279. time.sleep(0.1)
  280. for i, char in enumerate(date_format):
  281. val = date_dict[char]
  282. page.actions.type(val)
  283. time.sleep(0.1)
  284. if char == 'Y':
  285. if i < 2:
  286. page.actions.type(Keys.RIGHT)
  287. time.sleep(0.1)
  288. else:
  289. pass
  290. email = self.account_detail.get('email')
  291. password = self.account_detail.get('pwd')
  292. location = self.account_detail.get('location')
  293. btn_selector = 'tag:button@@text():Login'
  294. if not self.page.wait.ele_displayed(btn_selector, timeout=3):
  295. login_btn = self.page.ele("tag:a@@href:login")
  296. self.mouse.human_click_ele(login_btn)
  297. time.sleep(3)
  298. if not self.page.wait.ele_displayed(btn_selector, timeout=10):
  299. raise BizLogicError(message=f"Can't find selector={btn_selector}")
  300. recpatchav2_token = ""
  301. if self.page.ele('.g-recaptcha') or self.page.ele('xpath://iframe[contains(@src, "recaptcha")]'):
  302. self._log("Solving ReCaptcha...")
  303. recpatchav2_token = self.solve_captcha(self.page.url, "ReCaptchaV2TaskProxyLess", "6LcDpXcfAAAAAM7wOEsF_38DNsL20tTvPTKxpyn0")
  304. input_ele = self.page.ele('tag:label@@text():Email').next()
  305. self.mouse.human_click_ele(input_ele)
  306. time.sleep(random.uniform(0.2, 0.6))
  307. self.keyboard.type_text(email, humanize=True)
  308. time.sleep(random.uniform(0.5, 1.2))
  309. input_ele = self.page.ele('tag:label@@text():Password').next()
  310. self.mouse.human_click_ele(input_ele)
  311. time.sleep(random.uniform(0.2, 0.6))
  312. self.keyboard.type_text(password, humanize=True)
  313. if recpatchav2_token:
  314. inject_recpatchav2_token_js = f"""
  315. var g = document.getElementById('g-recaptcha-response');
  316. if(g) {{ g.value = "{recpatchav2_token}"; }}
  317. """
  318. self._log("Inject ReCaptchaV2 Token via JS...")
  319. self.page.run_js(inject_recpatchav2_token_js)
  320. time.sleep(random.uniform(0.5, 1.0))
  321. self._log("Submitting Login...")
  322. time.sleep(random.uniform(0.3, 0.8))
  323. login_btn = self.page.ele('tag:button@@text():Login')
  324. self.mouse.human_click_ele(login_btn)
  325. self._log("Waiting for dashboard redirect...")
  326. self.page.wait.url_change('login-actions', exclude=True, timeout=45)
  327. time.sleep(4)
  328. if "login-actions" in self.page.url or "auth" in self.page.url:
  329. raise BizLogicError(message="Login Failed! Invalid credentials or Captcha rejected.")
  330. self._log("Waiting for dashboard...")
  331. self.page.wait.load_start()
  332. time.sleep(5)
  333. # 解析 Dashboard 提取 Group ID
  334. self._log("Parsing Dashboard for Travel Group...")
  335. html = self.page.html
  336. js_pattern = r'\\"travelGroups\\":\s*(\[.*?\]),\\"availableCountriesToCreateGroups'
  337. js_match = re.search(js_pattern, html, re.DOTALL)
  338. groups = []
  339. if js_match:
  340. json_str = js_match.group(1).replace(r'\"', '"')
  341. groups = json.loads(json_str)
  342. travel_group = None
  343. for g in groups:
  344. if g.get('vacName', '').lower() == location.lower():
  345. travel_group = g
  346. break
  347. if not travel_group:
  348. raise BizLogicError(message=f"Travel Group not found for {location}")
  349. formgroup_id = travel_group.get('formGroupId')
  350. self._log(f"Waiting for group button to render: {formgroup_id}")
  351. btn_selector = f'tag:a@@data-testid=btn-select-group'
  352. self._log(f"Select group_id={formgroup_id}...")
  353. self.mouse.human_click_ele(self.page.ele(btn_selector))
  354. self._log("Waiting for url redirect...")
  355. self.page.wait.url_change('travel-groups', exclude=True, timeout=45)
  356. time.sleep(2)
  357. if "travel-groups" in self.page.url or "auth" in self.page.url:
  358. raise BizLogicError(message="Redirect to service-level Failed!")
  359. btn_selector_add = 'tag:button@@data-testid=btn-add-applicant'
  360. btn_selector_max = 'tag:button@@data-testid=btn-max-number-of-applicants'
  361. error_selector = 'tag:h2@text():Something went wrong'
  362. for attempt in range(2):
  363. try:
  364. btn_selector = btn_selector_add
  365. if not self.page.wait.ele_displayed(btn_selector, timeout=10):
  366. btn_selector = btn_selector_max
  367. if not self.page.ele(btn_selector):
  368. raise BizLogicError(message=f"Can't find selector={btn_selector}")
  369. add_btn = self.page.ele(btn_selector)
  370. add_btn.scroll.to_see(center=True)
  371. time.sleep(random.uniform(0.6, 0.8))
  372. add_btn.click()
  373. if self.page.wait.ele_displayed(error_selector, timeout=5):
  374. raise BizLogicError("Page shows 'Something went wrong'")
  375. break
  376. except Exception as e:
  377. if attempt == 0:
  378. self.page.refresh()
  379. time.sleep(5)
  380. else:
  381. raise BizLogicError(message=f"Click add applicant failed after retry: {e}")
  382. visa_type = self.account_detail.get("visa_type")
  383. if visa_type:
  384. btn = self.page.ele('tag:button@@data-testid=input-visa-type')
  385. btn.scroll.to_see(center=True)
  386. btn.click()
  387. time.sleep(0.5)
  388. self.page.ele(f'tag:span@@text():{visa_type}').click(by_js=True)
  389. time.sleep(0.5)
  390. tavel_purpose = self.account_detail.get("travel_purpose")
  391. if tavel_purpose:
  392. btn = self.page.ele('tag:button@@data-testid=input-travel-purpose')
  393. btn.scroll.to_see(center=True)
  394. btn.click()
  395. time.sleep(0.5)
  396. self.page.ele(f'tag:span@@text():{tavel_purpose}').click(by_js=True)
  397. time.sleep(0.5)
  398. application_form_id = self.account_detail.get('application_form_id')
  399. if application_form_id:
  400. ele = self.page.ele('tag:input@@data-testid=f_cai')
  401. ele.scroll.to_see(center=True)
  402. ele.input(application_form_id)
  403. last_name = self.account_detail.get('last_name')
  404. if last_name:
  405. ele = self.page.ele('tag:input@@data-testid=f_pers_surnames')
  406. ele.scroll.to_see(center=True)
  407. ele.input(last_name.upper())
  408. first_name = self.account_detail.get('first_name')
  409. if first_name:
  410. ele = self.page.ele('tag:input@@data-testid=f_pers_givennames')
  411. ele.scroll.to_see(center=True)
  412. ele.input(first_name.upper())
  413. gender = self.account_detail.get('gender')
  414. if gender:
  415. try:
  416. gender = gender.capitalize()
  417. ele = self.page.ele(f'tag:label@@text():{gender}')
  418. ele.scroll.to_see(center=True)
  419. ele.click()
  420. except Exception as e:
  421. self._log(e)
  422. fill_date_field(self.page, 'tag:input@@data-testid=f_pers_birth_date', self.account_detail.get('birthday'))
  423. nationality = self.account_detail.get('nationality')
  424. if nationality:
  425. nationality = nationality.title()
  426. btn = self.page.ele('tag:label@@for=f_pers_nationality').next()
  427. btn.scroll.to_see(center=True)
  428. btn.click()
  429. time.sleep(0.5)
  430. self.page.ele(f'tag:li@@role=option@@text():{nationality}').click(by_js=True)
  431. time.sleep(0.5)
  432. province_residence = self.account_detail.get('province_residence')
  433. if province_residence:
  434. try:
  435. province_residence = province_residence.title()
  436. btn = self.page.ele('tag:label@@for=f_pers_province').next()
  437. btn.scroll.to_see(center=True)
  438. btn.click()
  439. time.sleep(0.5)
  440. self.page.ele(f'tag:li@@role=option@@text():{province_residence}').click(by_js=True)
  441. time.sleep(0.5)
  442. except Exception as e:
  443. self._log(e)
  444. passport_type = self.account_detail.get('passport_type')
  445. if passport_type:
  446. btn = self.page.ele('tag:label@@for=f_identity_type').next()
  447. btn.scroll.to_see(center=True)
  448. btn.click()
  449. time.sleep(0.5)
  450. self.page.ele(f'tag:li@@role=option@@text():{passport_type}').click(by_js=True)
  451. time.sleep(0.5)
  452. passport_no = self.account_detail.get('passport_no')
  453. if passport_no:
  454. ele = self.page.ele('tag:input@@data-testid=f_pass_num')
  455. ele.scroll.to_see(center=True)
  456. ele.input(passport_no.upper())
  457. passport_issue_date = self.account_detail.get('passport_issue_date')
  458. if passport_issue_date:
  459. try:
  460. fill_date_field(self.page, 'tag:input@@data-testid=fi_passport_issue_date', passport_issue_date)
  461. time.sleep(0.5)
  462. except Exception as e:
  463. self._log(e)
  464. passport_expiry_date = self.account_detail.get('passport_expiry_date')
  465. if passport_expiry_date:
  466. try:
  467. fill_date_field(self.page, 'tag:input@@data-testid=fi_passport_expiry_date', passport_expiry_date)
  468. time.sleep(0.5)
  469. except Exception as e:
  470. self._log(e)
  471. phone_country_code = self.account_detail.get('phone_country_code')
  472. phone_number = self.account_detail.get('phone_number')
  473. if phone_country_code:
  474. div = self.page.ele('tag:label@@for=f_pers_mobile_phone').next()
  475. btn = div.ele('tag:button')
  476. btn.scroll.to_see(center=True)
  477. btn.click()
  478. time.sleep(0.5)
  479. self.page.ele(f'tag:li@@role=option@@text():+{phone_country_code}').click(by_js=True)
  480. time.sleep(0.5)
  481. div.ele('tag:input@@type:tel').input(phone_number)
  482. # 使用封装好的日期输入函数
  483. fill_date_field(self.page, 'tag:input@@data-testid=fi_trav_origin_departure_date', self.account_detail.get('departure_origin_date'))
  484. fill_date_field(self.page, 'tag:input@@data-testid=f_trav_departure_date', self.account_detail.get('arrival_schengen_area_date'))
  485. fill_date_field(self.page, 'tag:input@@data-testid=f_trav_arrival_date', self.account_detail.get('departure_schengen_area_date'))
  486. submit_btn = self.page.ele('tag:button@@data-testid=btn-submit')
  487. submit_btn.scroll.to_see(center=True)
  488. time.sleep(1)
  489. submit_btn.click()
  490. time.sleep(6)
  491. submit_btn = self.page.ele('tag:button@@text():Confirm')
  492. submit_btn.scroll.to_see(center=True)
  493. submit_btn.click()
  494. time.sleep(6)
  495. def upload_account_to_server(self):
  496. """
  497. 将注册成功的账号上报到中心服务器
  498. """
  499. api_url = 'https://api.text.skin/api/account/add'
  500. api_token = 'tok_e946329a60ff45ba807f3f41b0e8b7fc' # 你的 Bearer Token
  501. # 构造请求头
  502. headers = {
  503. 'accept': 'application/json',
  504. 'Authorization': f'Bearer {api_token}',
  505. 'Content-Type': 'application/json'
  506. }
  507. # 构造主 Payload
  508. payload = {
  509. "pool_name": self.account_detail.get("pool_name", "default_pool"),
  510. "username": self.account_detail.get("email"),
  511. "password": self.account_detail.get("pwd"),
  512. "extra_data": self.account_detail
  513. }
  514. try:
  515. (f"Uploading account {self.account_detail['email']} to server...")
  516. resp = requests.post(api_url, json=payload, headers=headers, timeout=10)
  517. if resp.status_code == 200:
  518. self._log(f"✅ [API Upload Success] Server responded: {resp.text}")
  519. return True
  520. else:
  521. self._log(f"❌ [API Upload Failed] Status: {resp.status_code}, Body: {resp.text}")
  522. return False
  523. except Exception as e:
  524. self._log(f"❌ [API Upload Error]: {e}")
  525. return False
  526. def cleanup(self):
  527. """清理浏览器进程和缓存文件夹"""
  528. self._log("Cleaning up resources...")
  529. if self.page:
  530. try: self.page.quit()
  531. except: pass
  532. if os.path.exists(self.workspace):
  533. time.sleep(1) # 等待文件锁释放
  534. shutil.rmtree(self.workspace, ignore_errors=True)
  535. def register_worker(proxy_config, tls_url, capsolver_key):
  536. """单个注册任务的工作线程函数"""
  537. account_detail = generate_random_account_detail('CN')
  538. bot = None
  539. try:
  540. bot = TlsRegistrator(
  541. tls_url,
  542. proxy_config=proxy_config,
  543. capsolver_key=capsolver_key,
  544. account_detail=account_detail
  545. )
  546. bot.init_browser() # ⚠️ 记得在 Docker 中必须是 headless 无头模式
  547. now_utc = datetime.utcnow()
  548. sent_at = now_utc.strftime("%Y-%m-%d %H:%M:%S")
  549. bot.register()
  550. bot.activate(sent_at=sent_at)
  551. bot.make_account_useful()
  552. bot.upload_account_to_server()
  553. bot.save_screenshot(f'success_{account_detail.get("email")}')
  554. print(f"[SUCCESS] 账号 {account_detail.get('email')} 注册成功! 使用代理: {proxy_config.get('ip')}")
  555. return True
  556. except Exception as e:
  557. print(f"[ERROR] 注册失败 | 代理 IP: {proxy_config.get('ip')} | 异常信息: {e}")
  558. bot.save_screenshot('tls_registration_failed')
  559. return False
  560. finally:
  561. if bot:
  562. try:
  563. bot.cleanup()
  564. except:
  565. pass
  566. def main():
  567. # ================= 命令行参数解析 =================
  568. parser = argparse.ArgumentParser(description="TLS 批量注册机")
  569. parser.add_argument("-n", "--concurrency", type=int, default=1, help="最大并发数 (N)")
  570. parser.add_argument("-m", "--target", type=int, default=1, help="最大成功注册数 (M)")
  571. parser.add_argument("-p", "--pool", type=str, default="local", help="代理池名称")
  572. parser.add_argument("-u", "--url", type=str, default="https://visas-fr.tlscontact.com/en-us/country/gb/vac/gbLON2fr", help="TLS 目标网址")
  573. args = parser.parse_args()
  574. # ================= 环境变量读取 =================
  575. capsolver_key = os.getenv("CAPSOLVER_KEY")
  576. if not capsolver_key:
  577. capsolver_key = "CAP-5441DD341DD3CC2FAEF0BE6FE493EE9A"
  578. print(f"[*] 启动注册任务 | 目标数: {args.target} | 并发数: {args.concurrency} | 代理池: {args.pool} | URL: {args.url}")
  579. proxies = load_proxies(args.pool)
  580. print(f"[*] 成功加载代理数量: {len(proxies)} 个")
  581. success_count = 0
  582. active_tasks = 0
  583. # 使用线程池维持并发
  584. with concurrent.futures.ThreadPoolExecutor(max_workers=args.concurrency) as executor:
  585. futures = {}
  586. # 1. 初始填充任务队列
  587. while active_tasks < args.concurrency and (success_count + active_tasks) < args.target:
  588. proxy = random.choice(proxies)
  589. fut = executor.submit(register_worker, proxy, args.url, capsolver_key)
  590. futures[fut] = proxy
  591. active_tasks += 1
  592. # 2. 调度循环
  593. while futures:
  594. done, _ = concurrent.futures.wait(futures, return_when=concurrent.futures.FIRST_COMPLETED)
  595. for fut in done:
  596. proxy = futures.pop(fut)
  597. active_tasks -= 1
  598. try:
  599. if fut.result():
  600. success_count += 1
  601. print(f"[*] 进度更新: {success_count} / {args.target}")
  602. except Exception as e:
  603. print(f"[FATAL] 线程未捕获异常: {e}")
  604. # 如果还没达到目标,补入新任务
  605. if (success_count + active_tasks) < args.target:
  606. new_proxy = random.choice(proxies)
  607. new_fut = executor.submit(register_worker, new_proxy, args.url, capsolver_key)
  608. futures[new_fut] = new_proxy
  609. active_tasks += 1
  610. print(f"[*] 任务结束!共成功注册 {success_count} 个账号。")
  611. if __name__ == "__main__":
  612. main()