cdp_auto_login.py 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262
  1. """Full auto-login via CDP including Geetest v4 slider solve.
  2. Flow:
  3. 1. (capture must be running in background)
  4. 2. Fill #username / #password
  5. 3. Click login button -> Geetest slider appears
  6. 4. Solve the slider: find the drag handle, dispatch realistic mouse drag
  7. (Input.dispatchMouseEvent with human-like trajectory over ~1.5s)
  8. 5. Monitor: login success (page navigates) -> find game -> click 启动
  9. 6. cq2.exe spawns -> TCP login traffic hits our capture
  10. """
  11. import asyncio
  12. import base64
  13. import json
  14. import subprocess
  15. import sys
  16. import httpx
  17. import websockets
  18. USERNAME = "longwu988"
  19. PASSWORD = "Longwu1988"
  20. CDP = "http://127.0.0.1:9222"
  21. async def get_targets():
  22. async with httpx.AsyncClient(timeout=5) as c:
  23. r = await c.get(f"{CDP}/json/list")
  24. return r.json()
  25. class Page:
  26. def __init__(self, ws):
  27. self.ws = ws
  28. self.mid = 0
  29. async def call(self, method, params=None):
  30. self.mid += 1
  31. await self.ws.send(json.dumps({"id": self.mid, "method": method, "params": params or {}}))
  32. while True:
  33. msg = json.loads(await self.ws.recv())
  34. if msg.get("id") == self.mid:
  35. return msg.get("result", {})
  36. async def eval(self, expr):
  37. r = await self.call("Runtime.evaluate", {"expression": expr, "returnByValue": True})
  38. return r.get("result", {}).get("value")
  39. async def mouse(self, typ, x, y, button="left", buttons=1):
  40. params = {"type": typ, "x": x, "y": y, "button": button}
  41. if typ in ("mousePressed",):
  42. params["clickCount"] = 1
  43. params["buttons"] = buttons
  44. if typ in ("mouseReleased",):
  45. params["clickCount"] = 1
  46. params["buttons"] = 0
  47. await self.call("Input.dispatchMouseEvent", params)
  48. async def screenshot(self, path):
  49. r = await self.call("Page.captureScreenshot", {"format": "png"})
  50. if r.get("data"):
  51. with open(path, "wb") as f:
  52. f.write(base64.b64decode(r["data"]))
  53. def cq2_pid():
  54. try:
  55. out = subprocess.run(
  56. ["powershell", "-NoProfile", "-Command",
  57. "Get-Process cq2 -ErrorAction SilentlyContinue | Select-Object -First 1 -ExpandProperty Id"],
  58. capture_output=True, text=True, timeout=5)
  59. return out.stdout.strip() or None
  60. except Exception:
  61. return None
  62. def gateway_conns():
  63. try:
  64. out = subprocess.run(
  65. ["powershell", "-NoProfile", "-Command",
  66. "(netstat -ano | Select-String '47.118.|:12000' | Measure-Object).Count"],
  67. capture_output=True, text=True, timeout=5)
  68. return int(out.stdout.strip() or 0)
  69. except Exception:
  70. return 0
  71. async def solve_geetest(page: Page) -> bool:
  72. """Drag the geetest slider with a human-like trajectory."""
  73. # Find the slider button position (geetest v4 renders .geetest_slider_button or similar)
  74. info = await page.eval("""
  75. (function() {
  76. // geetest v4 widget
  77. const candidates = [
  78. '.geetest_slider_button',
  79. '.geetest_btn_drag',
  80. '[class*=slider_button]',
  81. '[class*=btn_drag]',
  82. '.geetest_box .geetest_btn',
  83. ];
  84. for (const sel of candidates) {
  85. const el = document.querySelector(sel);
  86. if (el && el.offsetParent !== null) {
  87. const r = el.getBoundingClientRect();
  88. return JSON.stringify({sel, x: r.x + r.width/2, y: r.y + r.height/2, w: r.width});
  89. }
  90. }
  91. // fallback: any visible geetest element
  92. const g = document.querySelector('[class*=geetest]');
  93. if (g) {
  94. const r = g.getBoundingClientRect();
  95. return JSON.stringify({sel: 'generic-geetest', x: r.x + 50, y: r.y + r.height/2, w: 40});
  96. }
  97. return null;
  98. })()
  99. """)
  100. if not info:
  101. print(" no geetest slider found in DOM")
  102. return False
  103. d = json.loads(info)
  104. x, y = d["x"], d["y"]
  105. print(f" slider @{d['sel']} ({x:.0f},{y:.0f})")
  106. # Track distance: typical geetest v4 slide is ~260-300px
  107. dist = 280
  108. # Human-like drag: accelerate then decelerate, slight y wobble
  109. await page.mouse("mouseMoved", x, y)
  110. await asyncio.sleep(0.15)
  111. await page.mouse("mousePressed", x, y)
  112. await asyncio.sleep(0.1)
  113. import math
  114. steps = 40
  115. for i in range(1, steps + 1):
  116. # ease-out curve (fast start, slow end) like a real finger
  117. progress = i / steps
  118. eased = 1 - (1 - progress) ** 2.2
  119. cx = x + dist * eased
  120. cy = y + math.sin(i * 0.7) * 2.5 # wobble
  121. await page.mouse("mouseMoved", cx, cy)
  122. await asyncio.sleep(0.028) # ~1.1s total drag
  123. await asyncio.sleep(0.12)
  124. await page.mouse("mouseReleased", x + dist, y)
  125. print(" drag complete")
  126. await asyncio.sleep(2.5)
  127. # Check result: did the captcha pass?
  128. passed = await page.eval("""
  129. (function() {
  130. // success indicator: geetest result icon or the widget disappearing
  131. const ok = document.querySelector('.geetest_success, [class*=success], .geetest_result_success');
  132. const widget = document.querySelector('[class*=geetest_panel], .geetest_box');
  133. const retry = document.querySelector('[class*=retry], .geetest_reset');
  134. return JSON.stringify({
  135. successIcon: !!ok,
  136. widgetGone: !widget || widget.offsetParent === null,
  137. retryVisible: !!retry
  138. });
  139. })()
  140. """)
  141. print(f" result: {passed}")
  142. try:
  143. r = json.loads(passed or "{}")
  144. return r.get("successIcon") or r.get("widgetGone")
  145. except Exception:
  146. return False
  147. async def main() -> int:
  148. targets = await get_targets()
  149. login = next((t for t in targets if "login" in t.get("url", "") and t["type"] == "page"), None)
  150. if not login:
  151. print("no login page — maybe already logged in? targets:")
  152. for t in targets:
  153. print(f" [{t['type']}] {t['url'][:70]}")
  154. return 1
  155. print(f"login page: {login['url'][:60]}")
  156. async with websockets.connect(login["webSocketDebuggerUrl"], max_size=10**7) as ws:
  157. page = Page(ws)
  158. # fill credentials
  159. print("filling credentials...")
  160. await page.eval(f"""
  161. (function() {{
  162. const u = document.querySelector('#username');
  163. const p = document.querySelector('#password');
  164. if (!u || !p) return 'missing fields';
  165. const set = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value').set;
  166. set.call(u, '{USERNAME}');
  167. u.dispatchEvent(new Event('input', {{bubbles:true}}));
  168. set.call(p, '{PASSWORD}');
  169. p.dispatchEvent(new Event('input', {{bubbles:true}}));
  170. return 'filled';
  171. }})()
  172. """)
  173. await asyncio.sleep(0.5)
  174. # click login
  175. clicked = await page.eval("""
  176. (function() {
  177. const btn = document.querySelector('button.ant-btn.login-btn, button.login-btn, button.ant-btn');
  178. if (!btn) return 'no button';
  179. btn.click();
  180. return 'clicked';
  181. })()
  182. """)
  183. print(f"login click: {clicked}")
  184. await asyncio.sleep(2.5)
  185. # geetest appears? try solving up to 3 times
  186. for attempt in range(3):
  187. has_gt = await page.eval("""
  188. !!document.querySelector('[class*=geetest], iframe[src*=geetest]')
  189. """)
  190. if not has_gt:
  191. print(f"attempt {attempt+1}: no geetest visible")
  192. break
  193. print(f"attempt {attempt+1}: solving geetest...")
  194. ok = await solve_geetest(page)
  195. if ok:
  196. print("geetest PASSED")
  197. break
  198. print("geetest retry...")
  199. await asyncio.sleep(1.5)
  200. # monitor for login success + game launch
  201. print()
  202. print("=== monitoring 120s: login -> game -> traffic ===")
  203. for i in range(40):
  204. await asyncio.sleep(3)
  205. state = await page.eval("""
  206. JSON.stringify({url: location.href.slice(0,70)})
  207. """)
  208. try:
  209. st = json.loads(state or "{}")
  210. except Exception:
  211. st = {}
  212. pid = cq2_pid()
  213. conns = gateway_conns()
  214. t = (i + 1) * 3
  215. flags = []
  216. if "login" not in st.get("url", "login"):
  217. flags.append("NAVIGATED!")
  218. if pid:
  219. flags.append(f"CQ2={pid}")
  220. if conns:
  221. flags.append(f"conns={conns}")
  222. print(f" t+{t:>3}s {st.get('url','?')[:60]:<60} {' '.join(flags)}")
  223. if pid and conns >= 1:
  224. print()
  225. print("*** REAL GAME TRAFFIC LIVE — letting capture run 30 more seconds ***")
  226. await asyncio.sleep(30)
  227. return 0
  228. return 0
  229. sys.exit(asyncio.run(main()))