|
@@ -5,21 +5,33 @@ import re
|
|
|
import uuid
|
|
import uuid
|
|
|
import socket
|
|
import socket
|
|
|
import shutil
|
|
import shutil
|
|
|
|
|
+import random
|
|
|
import requests
|
|
import requests
|
|
|
|
|
+from urllib.parse import urlencode
|
|
|
from datetime import datetime, timedelta
|
|
from datetime import datetime, timedelta
|
|
|
|
|
+from typing import Optional, Dict
|
|
|
|
|
+from DrissionPage.common import Keys
|
|
|
from DrissionPage import ChromiumPage, ChromiumOptions
|
|
from DrissionPage import ChromiumPage, ChromiumOptions
|
|
|
from utils.cloudflare_bypass_for_scraping import CloudflareBypasser
|
|
from utils.cloudflare_bypass_for_scraping import CloudflareBypasser
|
|
|
from toolkit.vs_cloud_api import VSCloudApi
|
|
from toolkit.vs_cloud_api import VSCloudApi
|
|
|
|
|
+from vs_types import NotFoundError, PermissionDeniedError, RateLimiteddError, SessionExpiredOrInvalidError, BizLogicError
|
|
|
|
|
+from utils.mouse import HumanMouse
|
|
|
|
|
+from utils.keyboard import HumanKeyboard
|
|
|
|
|
+from utils.scroll import HumanScroll
|
|
|
|
|
|
|
|
class TlsRegistrator:
|
|
class TlsRegistrator:
|
|
|
- def __init__(self, proxy_config: dict, capsolver_key: str):
|
|
|
|
|
|
|
+ def __init__(self, tls_url, proxy_config: Optional[Dict]=None, capsolver_key: Optional[str]=None, account_detail: Optional[Dict]=None):
|
|
|
self.proxy_config = proxy_config
|
|
self.proxy_config = proxy_config
|
|
|
self.capsolver_key = capsolver_key
|
|
self.capsolver_key = capsolver_key
|
|
|
-
|
|
|
|
|
|
|
+ self.account_detail = account_detail
|
|
|
# 隔离的用户数据目录
|
|
# 隔离的用户数据目录
|
|
|
- self.instance_id = uuid.uuid4().hex[:8]
|
|
|
|
|
|
|
+ #self.instance_id = uuid.uuid4().hex[:8]
|
|
|
|
|
+ self.tls_url = tls_url
|
|
|
|
|
+ self.instance_id = '18d389e9'
|
|
|
self.workspace = os.path.abspath(os.path.join("data", f"reg_session_{self.instance_id}"))
|
|
self.workspace = os.path.abspath(os.path.join("data", f"reg_session_{self.instance_id}"))
|
|
|
self.page = None
|
|
self.page = None
|
|
|
|
|
+ self.mouse = None
|
|
|
|
|
+ self.keyboard = None
|
|
|
|
|
|
|
|
def _log(self, msg):
|
|
def _log(self, msg):
|
|
|
print(f"[TLS-Reg-{self.instance_id}] {msg}")
|
|
print(f"[TLS-Reg-{self.instance_id}] {msg}")
|
|
@@ -53,145 +65,146 @@ class TlsRegistrator:
|
|
|
|
|
|
|
|
# 3. 反爬及稳定性配置
|
|
# 3. 反爬及稳定性配置
|
|
|
co.headless(False)
|
|
co.headless(False)
|
|
|
|
|
+ co.set_argument('--lang=en-US')
|
|
|
co.set_argument('--no-sandbox')
|
|
co.set_argument('--no-sandbox')
|
|
|
co.set_argument('--disable-gpu')
|
|
co.set_argument('--disable-gpu')
|
|
|
co.set_argument('--disable-dev-shm-usage')
|
|
co.set_argument('--disable-dev-shm-usage')
|
|
|
co.set_argument('--window-size=1920,1080')
|
|
co.set_argument('--window-size=1920,1080')
|
|
|
co.set_argument('--disable-blink-features=AutomationControlled')
|
|
co.set_argument('--disable-blink-features=AutomationControlled')
|
|
|
-
|
|
|
|
|
self.page = ChromiumPage(co)
|
|
self.page = ChromiumPage(co)
|
|
|
-
|
|
|
|
|
- def solve_captcha(self, page_url: str, site_key: str) -> str:
|
|
|
|
|
- """调用 Capsolver 解决验证码 (无依赖版本)"""
|
|
|
|
|
- if not self.capsolver_key:
|
|
|
|
|
- raise ValueError("Capsolver API key is missing!")
|
|
|
|
|
-
|
|
|
|
|
- self._log("Submitting captcha task to Capsolver...")
|
|
|
|
|
|
|
+ self.page.run_cdp('Emulation.setLocaleOverride', locale='en-US')
|
|
|
|
|
+ self.page.get(self.tls_url)
|
|
|
|
|
+ cf_bypasser = CloudflareBypasser(self.page, log=True)
|
|
|
|
|
+ cf_bypasser.bypass()
|
|
|
|
|
+ time.sleep(3)
|
|
|
|
|
+ cf_bypasser.handle_waiting_room()
|
|
|
|
|
|
|
|
|
|
+ self._log("正在初始化拟人化工具...")
|
|
|
|
|
+ self.mouse = HumanMouse(self.page, debug=True)
|
|
|
|
|
+ self.keyboard = HumanKeyboard(self.page)
|
|
|
|
|
+ self._log("随机化鼠标开始位置...")
|
|
|
|
|
+ viewport_width = self.page.rect.viewport_size[0]
|
|
|
|
|
+ viewport_height = self.page.rect.viewport_size[1]
|
|
|
|
|
+ init_x = random.randint(10, viewport_width - 10)
|
|
|
|
|
+ init_y = random.randint(10, viewport_height - 10)
|
|
|
|
|
+ self.mouse.move(init_x, init_y)
|
|
|
|
|
+
|
|
|
|
|
+ def solve_captcha(self, page_url: str, task_type: str, site_key: str, use_proxy = False, action: str = None, api_domain: str = None) -> str:
|
|
|
|
|
+ """通用解决验证码 (同步 User-Agent 防止被盾识别为高风险)"""
|
|
|
|
|
+ if not self.capsolver_key:
|
|
|
|
|
+ raise ValueError("Capsolver API key missing")
|
|
|
|
|
+
|
|
|
task = {
|
|
task = {
|
|
|
- "type": "ReCaptchaV3TaskProxyLess",
|
|
|
|
|
|
|
+ "type": task_type,
|
|
|
"websiteURL": page_url,
|
|
"websiteURL": page_url,
|
|
|
"websiteKey": site_key,
|
|
"websiteKey": site_key,
|
|
|
- "pageAction": "register"
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- payload = {
|
|
|
|
|
- "clientKey": self.capsolver_key,
|
|
|
|
|
- "task": task
|
|
|
|
|
- }
|
|
|
|
|
- print(f'createTask payload={payload}')
|
|
|
|
|
- create_res = requests.post("https://api.capsolver.com/createTask", json=payload, timeout=20)
|
|
|
|
|
- if create_res.status_code != 200 or create_res.json().get("errorId") != 0:
|
|
|
|
|
- raise Exception(f"Failed to create capsolver task: {create_res.text}")
|
|
|
|
|
|
|
+ if api_domain:
|
|
|
|
|
+ task["apiDomain"] = api_domain
|
|
|
|
|
|
|
|
- task_id = create_res.json().get("taskId")
|
|
|
|
|
- self._log(f"Captcha Task created: {task_id}. Waiting for solution...")
|
|
|
|
|
-
|
|
|
|
|
|
|
+ if use_proxy:
|
|
|
|
|
+ proxy = self.proxy_config
|
|
|
|
|
+ task["proxyType"] = proxy.get('scheme', 'http')
|
|
|
|
|
+ task["proxyAddress"] = proxy.get('ip')
|
|
|
|
|
+ task["proxyPort"] = int(proxy.get('port'))
|
|
|
|
|
+ if proxy.get('username'):
|
|
|
|
|
+ task["proxyLogin"] = proxy.get('username')
|
|
|
|
|
+ task["proxyPassword"] = proxy.get('password')
|
|
|
|
|
+
|
|
|
|
|
+ if action:
|
|
|
|
|
+ task["pageAction"] = action
|
|
|
|
|
+
|
|
|
|
|
+ payload = {"clientKey": self.capsolver_key, "task": task}
|
|
|
|
|
+ res = requests.post("https://api.capsolver.com/createTask", json=payload, timeout=20)
|
|
|
|
|
+ if res.status_code != 200 or res.json().get("errorId") != 0:
|
|
|
|
|
+ raise Exception(f"Failed to create capsolver task: {res.text}")
|
|
|
|
|
+
|
|
|
|
|
+ task_id = res.json().get("taskId")
|
|
|
|
|
+ self._log(f"Task created: {task_id}. Waiting for solution...")
|
|
|
|
|
+
|
|
|
for _ in range(30):
|
|
for _ in range(30):
|
|
|
- res = requests.post(
|
|
|
|
|
|
|
+ r = requests.post(
|
|
|
"https://api.capsolver.com/getTaskResult",
|
|
"https://api.capsolver.com/getTaskResult",
|
|
|
json={"clientKey": self.capsolver_key, "taskId": task_id},
|
|
json={"clientKey": self.capsolver_key, "taskId": task_id},
|
|
|
timeout=20
|
|
timeout=20
|
|
|
)
|
|
)
|
|
|
- data = res.json()
|
|
|
|
|
- self._log(f"data={data}")
|
|
|
|
|
|
|
+ data = r.json()
|
|
|
if data.get("status") == "ready":
|
|
if data.get("status") == "ready":
|
|
|
self._log("Captcha solved successfully!")
|
|
self._log("Captcha solved successfully!")
|
|
|
- return data.get("solution", {}).get("gRecaptchaResponse") or data.get("solution", {}).get("token")
|
|
|
|
|
|
|
+ return data["solution"].get("gRecaptchaResponse") or data["solution"].get("token")
|
|
|
time.sleep(3)
|
|
time.sleep(3)
|
|
|
-
|
|
|
|
|
raise Exception("Capsolver task timeout")
|
|
raise Exception("Capsolver task timeout")
|
|
|
|
|
|
|
|
- def register(self, email, password, issuer_id="cnCNG2fr"):
|
|
|
|
|
|
|
+ def register(self):
|
|
|
"""执行自动注册"""
|
|
"""执行自动注册"""
|
|
|
- try:
|
|
|
|
|
- self.init_browser()
|
|
|
|
|
-
|
|
|
|
|
- # 1. 访问注册页面并过盾
|
|
|
|
|
- reg_url = f"https://visas-fr.tlscontact.com/en-us/registration?issuerId={issuer_id}"
|
|
|
|
|
- self._log(f"Navigating to {reg_url}")
|
|
|
|
|
- self.page.get(reg_url)
|
|
|
|
|
- cf_bypasser = CloudflareBypasser(self.page, log=True)
|
|
|
|
|
- cf_bypasser.bypass()
|
|
|
|
|
- time.sleep(3)
|
|
|
|
|
-
|
|
|
|
|
- cf_bypasser.handle_waiting_room()
|
|
|
|
|
|
|
+ email = self.account_detail.get('email')
|
|
|
|
|
+ password = self.account_detail.get('pwd')
|
|
|
|
|
+ btn_selector = '#submit'
|
|
|
|
|
+ if not self.page.wait.ele_displayed(btn_selector, timeout=3):
|
|
|
|
|
+ register_btn = self.page.ele("tag:a@@href:registration")
|
|
|
|
|
+ self.mouse.move_to(register_btn)
|
|
|
|
|
+ self.mouse.click(register_btn.rect.midpoint[0], register_btn.rect.midpoint[1], humanize=True)
|
|
|
|
|
+ time.sleep(3)
|
|
|
|
|
+ if not self.page.wait.ele_displayed(btn_selector, timeout=10):
|
|
|
|
|
+ raise BizLogicError(message=f"Can't find selector={btn_selector}")
|
|
|
|
|
+ time.sleep(random.uniform(0.5, 1))
|
|
|
|
|
+ self._log("正在填写邮箱和密码...")
|
|
|
|
|
+ email_input_e = self.page.ele('#email')
|
|
|
|
|
+ self.mouse.click(int(email_input_e.rect.midpoint[0]), int(email_input_e.rect.midpoint[1]), humanize=True)
|
|
|
|
|
+ self.keyboard.type_text(email, humanize=True)
|
|
|
|
|
+ time.sleep(random.uniform(0.2, 0.5))
|
|
|
|
|
+
|
|
|
|
|
+ password_e = self.page.ele('#password')
|
|
|
|
|
+ self.mouse.click(int(password_e.rect.midpoint[0]), int(password_e.rect.midpoint[1]), humanize=True)
|
|
|
|
|
+ self.keyboard.type_text(password, humanize=True)
|
|
|
|
|
+ time.sleep(random.uniform(0.2, 0.5))
|
|
|
|
|
+
|
|
|
|
|
+ confirm_password_e = self.page.ele('#confirm-password')
|
|
|
|
|
+ self.mouse.click(int(confirm_password_e.rect.midpoint[0]), int(confirm_password_e.rect.midpoint[1]), humanize=True)
|
|
|
|
|
+ self.keyboard.type_text(password, humanize=True)
|
|
|
|
|
+ time.sleep(random.uniform(0.2, 0.5))
|
|
|
|
|
|
|
|
- site_key = "6LcTpXcfAAAAAM3VojNhyV-F1z92ADJIvcSZ39Y9"
|
|
|
|
|
- captcha_token = self.solve_captcha(reg_url, site_key)
|
|
|
|
|
|
|
+ self._log("正在勾选必选条款...")
|
|
|
|
|
+ for checkbox_id in ['#terms-and-conditions', '#biometric-data', '#privacy-notice']:
|
|
|
|
|
+ check_box_e = self.page.ele(checkbox_id).next()
|
|
|
|
|
+ mx, my = int(check_box_e.rect.midpoint[0]), int(check_box_e.rect.midpoint[1])
|
|
|
|
|
+ self.mouse.click(mx, my, humanize=True)
|
|
|
|
|
+ time.sleep(random.uniform(0.3, 0.6))
|
|
|
|
|
+
|
|
|
|
|
+ self._log("提交注册...")
|
|
|
|
|
+ btn_e = self.page.ele(btn_selector)
|
|
|
|
|
+
|
|
|
|
|
+ btn_e.scroll.to_see(center=True)
|
|
|
|
|
+ time.sleep(1)
|
|
|
|
|
+
|
|
|
|
|
+ btn_mx, btn_my = int(btn_e.rect.midpoint[0]), int(btn_e.rect.midpoint[1])
|
|
|
|
|
+ self.mouse.click(btn_mx, btn_my, humanize=True)
|
|
|
|
|
+
|
|
|
|
|
+ time.sleep(0.5)
|
|
|
|
|
+ btn_e.click(by_js=True)
|
|
|
|
|
|
|
|
- self._log("Constructing Next.js fetch request...")
|
|
|
|
|
- action_id = "608511a5a6e58da30dc13b591255b358caa2193367"
|
|
|
|
|
- router_state = '%5B%22%22%2C%7B%22children%22%3A%5B%5B%22lang%22%2C%22en-us%22%2C%22d%22%5D%2C%7B%22children%22%3A%5B%22(auth)%22%2C%7B%22children%22%3A%5B%22registration%22%2C%7B%22children%22%3A%5B%22__PAGE__%22%2C%7B%7D%2Cnull%2Cnull%5D%7D%2Cnull%2Cnull%5D%7D%2Cnull%2Cnull%5D%7D%2Cnull%2Cnull%2Ctrue%5D%7D%2Cnull%2Cnull%5D'
|
|
|
|
|
-
|
|
|
|
|
- payload =[
|
|
|
|
|
- {
|
|
|
|
|
- "email": email,
|
|
|
|
|
- "password": password,
|
|
|
|
|
- "locale": "en",
|
|
|
|
|
- "issuerId": issuer_id,
|
|
|
|
|
- "consentList": ["SURVEY"]
|
|
|
|
|
- },
|
|
|
|
|
- captcha_token
|
|
|
|
|
- ]
|
|
|
|
|
-
|
|
|
|
|
- body_str = json.dumps(payload).replace("'", "\\'")
|
|
|
|
|
-
|
|
|
|
|
- js_script = f"""
|
|
|
|
|
- const url = "{reg_url}";
|
|
|
|
|
- const headers = {{
|
|
|
|
|
- 'next-action': '{action_id}',
|
|
|
|
|
- 'next-router-state-tree': '{router_state}',
|
|
|
|
|
- 'accept': 'text/x-component',
|
|
|
|
|
- 'content-type': 'text/plain;charset=UTF-8'
|
|
|
|
|
- }};
|
|
|
|
|
- const bodyData = '{body_str}';
|
|
|
|
|
-
|
|
|
|
|
- return fetch(url, {{ method: 'POST', headers: headers, body: bodyData }})
|
|
|
|
|
- .then(async response => {{
|
|
|
|
|
- const text = await response.text();
|
|
|
|
|
- const headers = {{}};
|
|
|
|
|
- response.headers.forEach((value, key) => headers[key] = value);
|
|
|
|
|
- return {{ status: response.status, body: text, headers: headers, url: response.url }};
|
|
|
|
|
- }}).catch(err => {{
|
|
|
|
|
- return {{ status: 0, body: err.toString(), headers: {{}} }};
|
|
|
|
|
- }});
|
|
|
|
|
- """
|
|
|
|
|
-
|
|
|
|
|
- self._log("Submitting registration...")
|
|
|
|
|
- res_dict = self.page.run_js(js_script)
|
|
|
|
|
-
|
|
|
|
|
- # 4. 解析结果 (处理 Next.js Server Action 响应)
|
|
|
|
|
- status = res_dict.get('status')
|
|
|
|
|
- body_text = res_dict.get('body', '')
|
|
|
|
|
- resp_headers = {str(k).lower(): v for k, v in res_dict.get('headers', {}).items()}
|
|
|
|
|
- action_redirect = resp_headers.get('x-action-redirect', '')
|
|
|
|
|
-
|
|
|
|
|
- # 【核心修改点】:适配 Next.js 响应中的 "status":"OK"
|
|
|
|
|
- is_success = (
|
|
|
|
|
- status == 303 or
|
|
|
|
|
- (status == 200 and ("login" in action_redirect or '"status":"OK"' in body_text))
|
|
|
|
|
- )
|
|
|
|
|
|
|
+ self._log("正在等待验证结果 (最多10秒)...")
|
|
|
|
|
+ success_dialog = self.page.wait.ele_displayed('tag:h1@text():Check your email inbox', timeout=10)
|
|
|
|
|
|
|
|
- if is_success:
|
|
|
|
|
- self._log("✅ Registration SUCCESS!")
|
|
|
|
|
- self._log(f"Response snippet: {body_text[:100]}...")
|
|
|
|
|
- return True
|
|
|
|
|
- else:
|
|
|
|
|
- self._log(f"❌ Registration FAILED! Status: {status}")
|
|
|
|
|
- self._log(f"Response Body: {body_text[:500]}")
|
|
|
|
|
- return False
|
|
|
|
|
- except Exception as e:
|
|
|
|
|
- self._log(f"Error during registration: {e}")
|
|
|
|
|
- return False
|
|
|
|
|
|
|
+ if not success_dialog:
|
|
|
|
|
+ self.page.get_screenshot("failed_submit.png")
|
|
|
|
|
+ raise BizLogicError(message='Failed to submit account registration')
|
|
|
|
|
+ self._log("✅ 操作成功!已弹出提示:Check your email inbox")
|
|
|
|
|
+ return True
|
|
|
|
|
|
|
|
- def activate(self, email, sent_at=None):
|
|
|
|
|
|
|
+ def activate(self, sent_at=None):
|
|
|
|
|
+ email = self.account_detail.get('email')
|
|
|
email_box = 'visafly666@gmail.com'
|
|
email_box = 'visafly666@gmail.com'
|
|
|
sender = 'TLSContact'
|
|
sender = 'TLSContact'
|
|
|
recipient = email
|
|
recipient = email
|
|
|
- subject_keywords = 'Activate'
|
|
|
|
|
|
|
+ subject_keywords = 'TLSContact'
|
|
|
body_keywords = ''
|
|
body_keywords = ''
|
|
|
|
|
|
|
|
|
|
+ if not sent_at:
|
|
|
|
|
+ now_utc = datetime.utcnow()
|
|
|
|
|
+ sent_at = now_utc.strftime("%Y-%m-%d %H:%M:%S")
|
|
|
|
|
+
|
|
|
content_out = VSCloudApi.Instance().fetch_mail_content(
|
|
content_out = VSCloudApi.Instance().fetch_mail_content(
|
|
|
email=email_box,
|
|
email=email_box,
|
|
|
sender=sender,
|
|
sender=sender,
|
|
@@ -201,15 +214,227 @@ class TlsRegistrator:
|
|
|
sent_date=sent_at,
|
|
sent_date=sent_at,
|
|
|
expiry=600
|
|
expiry=600
|
|
|
)
|
|
)
|
|
|
- print(f'activate email content={content_out}')
|
|
|
|
|
|
|
+ self._log(f'activate email content={content_out}')
|
|
|
match = re.search(r'https://\S+', content_out)
|
|
match = re.search(r'https://\S+', content_out)
|
|
|
activate_link = match.group(0) if match else None
|
|
activate_link = match.group(0) if match else None
|
|
|
- tab = self.page.new_tab(activate_link)
|
|
|
|
|
- btn_selector = "Activate"
|
|
|
|
|
- if not tab.wait.ele_displayed(btn_selector, timeout=10):
|
|
|
|
|
- return False
|
|
|
|
|
- tab.ele(btn_selector).click()
|
|
|
|
|
|
|
+ self.page.get(activate_link)
|
|
|
|
|
+ btn_selector = "#activation-pending-button"
|
|
|
|
|
+ if not self.page.wait.ele_displayed(btn_selector, timeout=10):
|
|
|
|
|
+ raise BizLogicError(message=f"Wait ele={btn_selector} timeout")
|
|
|
|
|
+ self.page.ele(btn_selector).click()
|
|
|
|
|
+ btn_selector = '#btn-login'
|
|
|
|
|
+ if not self.page.wait.ele_displayed(btn_selector, timeout=10):
|
|
|
|
|
+ raise BizLogicError(message=f"Wait ele={btn_selector} timeout")
|
|
|
|
|
+
|
|
|
|
|
+ def make_account_useful(self):
|
|
|
|
|
+
|
|
|
|
|
+ def fill_date_field(page, selector, date_str):
|
|
|
|
|
+ if not date_str:
|
|
|
|
|
+ return
|
|
|
|
|
+ ele = page.ele(selector)
|
|
|
|
|
+ ele.scroll.to_see(center=True) # 滚动到屏幕中间
|
|
|
|
|
+ ele.click()
|
|
|
|
|
+ time.sleep(0.2)
|
|
|
|
|
+ year, month, day = date_str.split('-')
|
|
|
|
|
+ page.actions.type(year)
|
|
|
|
|
+ page.actions.type(Keys.TAB)
|
|
|
|
|
+ time.sleep(0.1)
|
|
|
|
|
+ page.actions.type(month)
|
|
|
|
|
+ time.sleep(0.1)
|
|
|
|
|
+ page.actions.type(day)
|
|
|
|
|
+
|
|
|
|
|
+ email = self.account_detail.get('email')
|
|
|
|
|
+ password = self.account_detail.get('pwd')
|
|
|
|
|
+ location = self.account_detail.get('location')
|
|
|
|
|
+ btn_selector = '#btn-login'
|
|
|
|
|
+ if not self.page.wait.ele_displayed(btn_selector, timeout=3):
|
|
|
|
|
+ login_btn = self.page.ele("tag:a@@href:login")
|
|
|
|
|
+ self.mouse.move_to(login_btn)
|
|
|
|
|
+ self.mouse.click(login_btn.rect.midpoint[0], login_btn.rect.midpoint[1], humanize=True)
|
|
|
|
|
+ time.sleep(3)
|
|
|
|
|
+ if not self.page.wait.ele_displayed(btn_selector, timeout=10):
|
|
|
|
|
+ raise BizLogicError(message=f"Can't find selector={btn_selector}")
|
|
|
|
|
+ g_token = ""
|
|
|
|
|
+ if self.page.ele('.g-recaptcha') or self.page.ele('xpath://iframe[contains(@src, "recaptcha")]'):
|
|
|
|
|
+ self._log("Solving ReCaptcha...")
|
|
|
|
|
+ g_token = self.solve_captcha(self.page.url, "ReCaptchaV2TaskProxyLess", "6LcDpXcfAAAAAM7wOEsF_38DNsL20tTvPTKxpyn0")
|
|
|
|
|
+
|
|
|
|
|
+ js_login = f"""
|
|
|
|
|
+ var u = document.getElementById('email-input-field');
|
|
|
|
|
+ if(u) {{ u.value = "{email}"; u.dispatchEvent(new Event('input', {{bubbles:true}})); }}
|
|
|
|
|
+
|
|
|
|
|
+ var p = document.getElementById('password-input-field');
|
|
|
|
|
+ if(p) {{ p.value = "{password}"; p.dispatchEvent(new Event('input', {{bubbles:true}})); }}
|
|
|
|
|
+
|
|
|
|
|
+ var g = document.getElementById('g-recaptcha-response');
|
|
|
|
|
+ if(g) {{ g.value = "{g_token}"; }}
|
|
|
|
|
+
|
|
|
|
|
+ var btn = document.getElementById('btn-login');
|
|
|
|
|
+ if(btn) {{ btn.click(); return true; }} else {{ return false; }}
|
|
|
|
|
+ """
|
|
|
|
|
+
|
|
|
|
|
+ self._log("Submitting Login via JS...")
|
|
|
|
|
+ if not self.page.run_js(js_login):
|
|
|
|
|
+ raise BizLogicError(message="Login button missing")
|
|
|
|
|
+
|
|
|
|
|
+ self._log("Waiting for dashboard redirect...")
|
|
|
|
|
+ self.page.wait.url_change('login-actions', exclude=True, timeout=45)
|
|
|
|
|
+ time.sleep(4)
|
|
|
|
|
+
|
|
|
|
|
+ if "login-actions" in self.page.url or "auth" in self.page.url:
|
|
|
|
|
+ raise BizLogicError(message="Login Failed! Invalid credentials or Captcha rejected.")
|
|
|
|
|
+
|
|
|
|
|
+ self._log("Waiting for dashboard...")
|
|
|
|
|
+ self.page.wait.load_start()
|
|
|
|
|
+ time.sleep(5)
|
|
|
|
|
+
|
|
|
|
|
+ # 解析 Dashboard 提取 Group ID
|
|
|
|
|
+ self._log("Parsing Dashboard for Travel Group...")
|
|
|
|
|
+ html = self.page.html
|
|
|
|
|
+ js_pattern = r'\\"travelGroups\\":\s*(\[.*?\]),\\"availableCountriesToCreateGroups'
|
|
|
|
|
+ js_match = re.search(js_pattern, html, re.DOTALL)
|
|
|
|
|
+
|
|
|
|
|
+ groups = []
|
|
|
|
|
+ if js_match:
|
|
|
|
|
+ json_str = js_match.group(1).replace(r'\"', '"')
|
|
|
|
|
+ groups = json.loads(json_str)
|
|
|
|
|
+
|
|
|
|
|
+ travel_group = None
|
|
|
|
|
+ for g in groups:
|
|
|
|
|
+ if g.get('vacName', '').lower() == location.lower():
|
|
|
|
|
+ travel_group = g
|
|
|
|
|
+ break
|
|
|
|
|
+
|
|
|
|
|
+ if not travel_group:
|
|
|
|
|
+ raise BizLogicError(message=f"Travel Group not found for {location}")
|
|
|
|
|
+
|
|
|
|
|
+ formgroup_id = travel_group.get('formGroupId')
|
|
|
|
|
+ self._log(f"Waiting for group button to render: {formgroup_id}")
|
|
|
|
|
+ btn_selector = f'tag:a@@data-testid=btn-select-group'
|
|
|
|
|
+ self._log(f"Select group_id={formgroup_id} via JS...")
|
|
|
|
|
+ self.page.ele(btn_selector).click(by_js=True)
|
|
|
|
|
+
|
|
|
|
|
+ self._log("Waiting for url redirect...")
|
|
|
|
|
+ self.page.wait.url_change('travel-groups', exclude=True, timeout=45)
|
|
|
|
|
+ time.sleep(2)
|
|
|
|
|
+
|
|
|
|
|
+ if "travel-groups" in self.page.url or "auth" in self.page.url:
|
|
|
|
|
+ raise BizLogicError(message="Redirect to service-level Failed!")
|
|
|
|
|
+
|
|
|
|
|
+ btn_selector = 'tag:button@@data-testid=btn-add-applicant'
|
|
|
|
|
+ if not self.page.wait.ele_displayed(btn_selector, timeout=10):
|
|
|
|
|
+ btn_selector = 'tag:button@@data-testid=btn-max-number-of-applicants'
|
|
|
|
|
+ if not self.page.ele(btn_selector):
|
|
|
|
|
+ raise BizLogicError(message=f"Can't find selector={btn_selector}")
|
|
|
|
|
+ self.page.ele(btn_selector).click()
|
|
|
|
|
+ time.sleep(6)
|
|
|
|
|
+
|
|
|
|
|
+ visa_type = self.account_detail.get("visa_type")
|
|
|
|
|
+ if visa_type:
|
|
|
|
|
+ btn = self.page.ele('tag:button@@data-testid=input-visa-type')
|
|
|
|
|
+ btn.scroll.to_see(center=True)
|
|
|
|
|
+ btn.click()
|
|
|
|
|
+ time.sleep(0.5)
|
|
|
|
|
+ self.page.ele(f'tag:span@@text():{visa_type}').click(by_js=True)
|
|
|
|
|
+ time.sleep(0.5)
|
|
|
|
|
|
|
|
|
|
+ tavel_purpose = self.account_detail.get("travel_purpose")
|
|
|
|
|
+ if tavel_purpose:
|
|
|
|
|
+ btn = self.page.ele('tag:button@@data-testid=input-travel-purpose')
|
|
|
|
|
+ btn.scroll.to_see(center=True)
|
|
|
|
|
+ btn.click()
|
|
|
|
|
+ time.sleep(0.5)
|
|
|
|
|
+ self.page.ele(f'tag:span@@text():{tavel_purpose}').click(by_js=True)
|
|
|
|
|
+ time.sleep(0.5)
|
|
|
|
|
+
|
|
|
|
|
+ application_form_id = self.account_detail.get('application_form_id')
|
|
|
|
|
+ if application_form_id:
|
|
|
|
|
+ ele = self.page.ele('tag:input@@data-testid=f_cai')
|
|
|
|
|
+ ele.scroll.to_see(center=True)
|
|
|
|
|
+ ele.input(application_form_id)
|
|
|
|
|
+
|
|
|
|
|
+ last_name = self.account_detail.get('last_name')
|
|
|
|
|
+ if last_name:
|
|
|
|
|
+ ele = self.page.ele('tag:input@@data-testid=f_pers_surnames')
|
|
|
|
|
+ ele.scroll.to_see(center=True)
|
|
|
|
|
+ ele.input(last_name.upper())
|
|
|
|
|
+
|
|
|
|
|
+ first_name = self.account_detail.get('first_name')
|
|
|
|
|
+ if first_name:
|
|
|
|
|
+ ele = self.page.ele('tag:input@@data-testid=f_pers_givennames')
|
|
|
|
|
+ ele.scroll.to_see(center=True)
|
|
|
|
|
+ ele.input(first_name.upper())
|
|
|
|
|
+
|
|
|
|
|
+ gender = self.account_detail.get('gender')
|
|
|
|
|
+ if gender:
|
|
|
|
|
+ gender = gender.capitalize()
|
|
|
|
|
+ ele = self.page.ele(f'tag:label@@text():{gender}')
|
|
|
|
|
+ ele.scroll.to_see(center=True)
|
|
|
|
|
+ ele.click()
|
|
|
|
|
+
|
|
|
|
|
+ fill_date_field(self.page, 'tag:input@@data-testid=f_pers_birth_date', self.account_detail.get('birthday'))
|
|
|
|
|
+
|
|
|
|
|
+ nationality = self.account_detail.get('nationality')
|
|
|
|
|
+ if nationality:
|
|
|
|
|
+ nationality = nationality.title()
|
|
|
|
|
+ btn = self.page.ele('tag:label@@for=f_pers_nationality').next()
|
|
|
|
|
+ btn.scroll.to_see(center=True)
|
|
|
|
|
+ btn.click()
|
|
|
|
|
+ time.sleep(0.5)
|
|
|
|
|
+ self.page.ele(f'tag:li@@role=option@@text():{nationality}').click(by_js=True)
|
|
|
|
|
+ time.sleep(0.5)
|
|
|
|
|
+
|
|
|
|
|
+ province_residence = self.account_detail.get('province_residence')
|
|
|
|
|
+ if province_residence:
|
|
|
|
|
+ province_residence = province_residence.title()
|
|
|
|
|
+ btn = self.page.ele('tag:label@@for=f_pers_province').next()
|
|
|
|
|
+ btn.scroll.to_see(center=True)
|
|
|
|
|
+ btn.click()
|
|
|
|
|
+ time.sleep(0.5)
|
|
|
|
|
+ self.page.ele(f'tag:li@@role=option@@text():{province_residence}').click(by_js=True)
|
|
|
|
|
+ time.sleep(0.5)
|
|
|
|
|
+
|
|
|
|
|
+ passport_type = self.account_detail.get('passport_type')
|
|
|
|
|
+ if passport_type:
|
|
|
|
|
+ btn = self.page.ele('tag:label@@for=f_identity_type').next()
|
|
|
|
|
+ btn.scroll.to_see(center=True)
|
|
|
|
|
+ btn.click()
|
|
|
|
|
+ time.sleep(0.5)
|
|
|
|
|
+ self.page.ele(f'tag:li@@role=option@@text():{passport_type}').click(by_js=True)
|
|
|
|
|
+ time.sleep(0.5)
|
|
|
|
|
+
|
|
|
|
|
+ passport_no = self.account_detail.get('passport_no')
|
|
|
|
|
+ if passport_no:
|
|
|
|
|
+ ele = self.page.ele('tag:input@@data-testid=f_pass_num')
|
|
|
|
|
+ ele.scroll.to_see(center=True)
|
|
|
|
|
+ ele.input(passport_no.upper())
|
|
|
|
|
+
|
|
|
|
|
+ phone_country_code = self.account_detail.get('phone_country_code')
|
|
|
|
|
+ phone_number = self.account_detail.get('phone_number')
|
|
|
|
|
+ if phone_country_code:
|
|
|
|
|
+ div = self.page.ele('tag:label@@for=f_pers_mobile_phone').next()
|
|
|
|
|
+ btn = div.ele('tag:button')
|
|
|
|
|
+ btn.scroll.to_see(center=True)
|
|
|
|
|
+ btn.click()
|
|
|
|
|
+ time.sleep(0.5)
|
|
|
|
|
+ self.page.ele(f'tag:li@@role=option@@text():+{phone_country_code}').click(by_js=True)
|
|
|
|
|
+ time.sleep(0.5)
|
|
|
|
|
+ div.ele('tag:input@@type:tel').input(phone_number)
|
|
|
|
|
+
|
|
|
|
|
+ # 使用封装好的日期输入函数
|
|
|
|
|
+ fill_date_field(self.page, 'tag:input@@data-testid=fi_trav_origin_departure_date', self.account_detail.get('departure_origin_date'))
|
|
|
|
|
+ fill_date_field(self.page, 'tag:input@@data-testid=f_trav_departure_date', self.account_detail.get('arrival_schengen_area_date'))
|
|
|
|
|
+ fill_date_field(self.page, 'tag:input@@data-testid=f_trav_arrival_date', self.account_detail.get('departure_schengen_area_date'))
|
|
|
|
|
+
|
|
|
|
|
+ submit_btn = self.page.ele('tag:button@@data-testid=btn-submit')
|
|
|
|
|
+ submit_btn.scroll.to_see(center=True)
|
|
|
|
|
+ time.sleep(1)
|
|
|
|
|
+ submit_btn.click()
|
|
|
|
|
+ time.sleep(6)
|
|
|
|
|
+ submit_btn = self.page.ele('tag:button@@text():Confirm')
|
|
|
|
|
+ submit_btn.scroll.to_see(center=True)
|
|
|
|
|
+ submit_btn.click()
|
|
|
|
|
+
|
|
|
def cleanup(self):
|
|
def cleanup(self):
|
|
|
"""清理浏览器进程和缓存文件夹"""
|
|
"""清理浏览器进程和缓存文件夹"""
|
|
|
self._log("Cleaning up resources...")
|
|
self._log("Cleaning up resources...")
|
|
@@ -233,18 +458,37 @@ if __name__ == "__main__":
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
CAPSOLVER_KEY = "CAP-5441DD341DD3CC2FAEF0BE6FE493EE9A"
|
|
CAPSOLVER_KEY = "CAP-5441DD341DD3CC2FAEF0BE6FE493EE9A"
|
|
|
-
|
|
|
|
|
- TARGET_EMAIL = "lisi39@gmail-app.com"
|
|
|
|
|
- TARGET_PWD = "Visafly@111"
|
|
|
|
|
- TARGET_ISSUER = "cnCNG2fr"
|
|
|
|
|
-
|
|
|
|
|
|
|
+ TLS_URL = "https://visas-fr.tlscontact.com/visa/cn/cnCNG2fr/home"
|
|
|
|
|
+ ACCOUNT_DETAIL = {
|
|
|
|
|
+ "email": "lisi103@gmail-app.com",
|
|
|
|
|
+ "pwd": "Visafly@111",
|
|
|
|
|
+ "location": "Chengdu",
|
|
|
|
|
+ "visa_type": "Short stay (<90 days) - Tourism",
|
|
|
|
|
+ "travel_purpose": "Tourism / Private visit",
|
|
|
|
|
+ "application_form_id": "FRA1CA20260420996",
|
|
|
|
|
+ "last_name": "Song",
|
|
|
|
|
+ "first_name": "Xiao",
|
|
|
|
|
+ "gender": "Male",
|
|
|
|
|
+ "birthday": "1998-12-18",
|
|
|
|
|
+ "nationality": "China",
|
|
|
|
|
+ "province_residence": "Sichuan",
|
|
|
|
|
+ "passport_type": "Ordinary passport",
|
|
|
|
|
+ "passport_no": "EJ8628293",
|
|
|
|
|
+ "phone_country_code": "86",
|
|
|
|
|
+ "phone_number": "17386068917",
|
|
|
|
|
+ "departure_origin_date": "2026-05-26",
|
|
|
|
|
+ "arrival_schengen_area_date": "2026-05-26",
|
|
|
|
|
+ "departure_schengen_area_date": "2026-05-28"
|
|
|
|
|
+ }
|
|
|
# ============================================
|
|
# ============================================
|
|
|
-
|
|
|
|
|
- bot = TlsRegistrator(proxy_config=PROXY_CONFIG, capsolver_key=CAPSOLVER_KEY)
|
|
|
|
|
- now_utc = datetime.utcnow()
|
|
|
|
|
- formatted_utc_time = (now_utc - timedelta(minutes=4)).strftime("%Y-%m-%d %H:%M:%S")
|
|
|
|
|
- # success = bot.register(email=TARGET_EMAIL, password=TARGET_PWD, issuer_id=TARGET_ISSUER)
|
|
|
|
|
- # if not success:
|
|
|
|
|
- # print("\n--> 流程完成:账号注册失败,请检查日志。")
|
|
|
|
|
-
|
|
|
|
|
- bot.activate(email=TARGET_EMAIL, sent_at=formatted_utc_time)
|
|
|
|
|
|
|
+ try:
|
|
|
|
|
+ bot = TlsRegistrator(TLS_URL, proxy_config=PROXY_CONFIG, capsolver_key=CAPSOLVER_KEY, account_detail=ACCOUNT_DETAIL)
|
|
|
|
|
+ bot.init_browser()
|
|
|
|
|
+ now_utc = datetime.utcnow()
|
|
|
|
|
+ sent_at = now_utc.strftime("%Y-%m-%d %H:%M:%S")
|
|
|
|
|
+ bot.register()
|
|
|
|
|
+ bot.activate(sent_at=sent_at)
|
|
|
|
|
+ bot.make_account_useful()
|
|
|
|
|
+ except Exception as e:
|
|
|
|
|
+ print(f'Exception Info={e}')
|
|
|
|
|
+ time.sleep(3600)
|