cdp_search_launch.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311
  1. """Search for 玖扬复古176 via CDP, open its page, click 启动, monitor traffic.
  2. Flow:
  3. 1. On home page (pc.hzwlan.com/home/homePage), find the search input
  4. 2. Type 玖扬复古176 (native setter + keyboard events to trigger suggestions)
  5. 3. Click the matching game suggestion/card
  6. 4. On the game detail page, click the 启动 button
  7. 5. Monitor: cq.exe/cq2.exe spawn? gateway :12000 connections? capture lines?
  8. """
  9. import asyncio
  10. import base64
  11. import json
  12. import subprocess
  13. import sys
  14. import time
  15. import httpx
  16. import websockets
  17. CDP = "http://127.0.0.1:9222"
  18. GAME_NAME = "玖扬复古176"
  19. async def get_targets():
  20. async with httpx.AsyncClient(timeout=5) as c:
  21. r = await c.get(f"{CDP}/json/list")
  22. return r.json()
  23. class Page:
  24. def __init__(self, ws):
  25. self.ws = ws
  26. self.mid = 0
  27. async def call(self, method, params=None):
  28. self.mid += 1
  29. await self.ws.send(json.dumps({"id": self.mid, "method": method, "params": params or {}}))
  30. while True:
  31. msg = json.loads(await self.ws.recv())
  32. if msg.get("id") == self.mid:
  33. return msg.get("result", {})
  34. async def eval(self, expr):
  35. r = await self.call("Runtime.evaluate", {"expression": expr, "returnByValue": True})
  36. return r.get("result", {}).get("value")
  37. async def click(self, x, y):
  38. await self.call("Input.dispatchMouseEvent", {"type": "mousePressed", "x": x, "y": y, "button": "left", "clickCount": 1, "buttons": 1})
  39. await asyncio.sleep(0.06)
  40. await self.call("Input.dispatchMouseEvent", {"type": "mouseReleased", "x": x, "y": y, "button": "left", "clickCount": 1, "buttons": 0})
  41. async def key(self, char, raw_key=None, code=None, key_code=None):
  42. params = {"type": "keyDown", "text": char, "key": raw_key or char}
  43. if code:
  44. params["code"] = code
  45. if key_code:
  46. params["windowsVirtualKeyCode"] = key_code
  47. await self.call("Input.dispatchKeyEvent", params)
  48. await asyncio.sleep(0.04)
  49. params["type"] = "keyUp"
  50. await self.call("Input.dispatchKeyEvent", params)
  51. async def screenshot(self, path):
  52. r = await self.call("Page.captureScreenshot", {"format": "png"})
  53. if r.get("data"):
  54. with open(path, "wb") as f:
  55. f.write(base64.b64decode(r["data"]))
  56. def cq_procs():
  57. try:
  58. out = subprocess.run(
  59. ["powershell", "-NoProfile", "-Command",
  60. "Get-Process | Where-Object { $_.Name -match '^cq' } | ForEach-Object { \"$($_.Name):$($_.Id)\" }"],
  61. capture_output=True, text=True, timeout=5)
  62. return [l.strip() for l in out.stdout.splitlines() if l.strip()] or []
  63. except Exception:
  64. return []
  65. def gateway_conns():
  66. try:
  67. out = subprocess.run(
  68. ["powershell", "-NoProfile", "-Command",
  69. "(netstat -ano | Select-String ':12000' | Measure-Object).Count"],
  70. capture_output=True, text=True, timeout=5)
  71. return int(out.stdout.strip() or 0)
  72. except Exception:
  73. return 0
  74. def capture_lines():
  75. try:
  76. out = subprocess.run(
  77. ["powershell", "-NoProfile", "-Command",
  78. "(Get-Content 'D:\\传奇\\cq2_sdk\\real_login.jsonl' -ErrorAction SilentlyContinue | Measure-Object).Count"],
  79. capture_output=True, text=True, timeout=5)
  80. return int(out.stdout.strip() or 0)
  81. except Exception:
  82. return 0
  83. async def main():
  84. targets = await get_targets()
  85. home = next((t for t in targets if "homePage" in t.get("url", "") and t["type"] == "page"), None)
  86. if not home:
  87. # fallback: any hzwlan page that's not login
  88. home = next((t for t in targets if "hzwlan" in t.get("url", "") and "login" not in t["url"] and t["type"] == "page"), None)
  89. if not home:
  90. print("no home page target:")
  91. for t in targets:
  92. print(f" [{t['type']}] {t['url'][:70]}")
  93. return 1
  94. print(f"page: {home['url'][:60]}")
  95. async with websockets.connect(home["webSocketDebuggerUrl"], max_size=10**7) as ws:
  96. page = Page(ws)
  97. # 1. locate search input
  98. sr = await page.eval("""
  99. (function() {
  100. const inputs = document.querySelectorAll('input');
  101. for (const inp of inputs) {
  102. const r = inp.getBoundingClientRect();
  103. if (r.width > 100 && r.y < 150 && inp.offsetParent) {
  104. return JSON.stringify({x: r.x + r.width/2, y: r.y + r.height/2, w: r.width, ph: inp.placeholder || ''});
  105. }
  106. }
  107. return null;
  108. })()
  109. """)
  110. if not sr:
  111. print("search input not found")
  112. await page.screenshot("no_search.png")
  113. return 1
  114. s = json.loads(sr)
  115. print(f"search input @ ({s['x']:.0f},{s['y']:.0f}) w={s['w']:.0f} ph='{s['ph'][:20]}'")
  116. # 2. click it and type the game name
  117. await page.click(s["x"], s["y"])
  118. await asyncio.sleep(0.4)
  119. # clear existing
  120. await page.key("a", raw_key="a", code="KeyA", key_code=65)
  121. # type via input events (Chinese chars don't map to keys — use direct value set + suggestions trigger)
  122. typed = await page.eval(f"""
  123. (function() {{
  124. const inp = document.activeElement;
  125. if (!inp || inp.tagName !== 'INPUT') {{
  126. // find again
  127. const inputs = document.querySelectorAll('input');
  128. for (const i of inputs) {{
  129. const r = i.getBoundingClientRect();
  130. if (r.width > 100 && r.y < 150 && i.offsetParent) {{ inp = i; break; }}
  131. }}
  132. }}
  133. if (!inp) return 'no input';
  134. const set = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value').set;
  135. set.call(inp, '{GAME_NAME}');
  136. inp.dispatchEvent(new Event('input', {{bubbles: true}}));
  137. inp.dispatchEvent(new Event('change', {{bubbles: true}}));
  138. inp.dispatchEvent(new KeyboardEvent('keyup', {{key: 'a', bubbles: true}}));
  139. return 'typed: ' + inp.value;
  140. }})()
  141. """)
  142. print(f"typed: {typed}")
  143. await asyncio.sleep(2.5)
  144. # 3. look for suggestions / search results with 玖扬
  145. sugg = await page.eval(f"""
  146. (function() {{
  147. const results = [];
  148. const els = document.querySelectorAll('*');
  149. for (const el of els) {{
  150. if (el.children.length <= 3) {{
  151. const t = (el.innerText || '').trim();
  152. if (t.includes('玖扬') && t.length < 80 && !results.some(r => r.text === t)) {{
  153. const r = el.getBoundingClientRect();
  154. if (r.width > 30 && r.height > 10 && r.y > 0) {{
  155. results.push({{text: t.slice(0,50), x: r.x + r.width/2, y: r.y + r.height/2}});
  156. }}
  157. }}
  158. }}
  159. if (results.length >= 6) break;
  160. }}
  161. return JSON.stringify(results);
  162. }})()
  163. """)
  164. print(f"\n=== suggestions mentioning 玖扬 ===")
  165. try:
  166. sugs = json.loads(sugg or "[]")
  167. for sg in sugs:
  168. print(f" '{sg['text'][:40]}' @ ({sg['x']:.0f},{sg['y']:.0f})")
  169. except Exception:
  170. print(sugg[:300])
  171. sugs = []
  172. await page.screenshot("search_results.png")
  173. if not sugs:
  174. # press Enter to submit search
  175. await page.key("\r", raw_key="Enter", code="Enter", key_code=13)
  176. print("no suggestions — pressed Enter to search")
  177. await asyncio.sleep(3.5)
  178. sugg2 = await page.eval(f"""
  179. (function() {{
  180. const results = [];
  181. const els = document.querySelectorAll('*');
  182. for (const el of els) {{
  183. if (el.children.length <= 3) {{
  184. const t = (el.innerText || '').trim();
  185. if (t.includes('玖扬') && t.length < 80 && !results.some(r => r.text === t)) {{
  186. const r = el.getBoundingClientRect();
  187. if (r.width > 30 && r.height > 10 && r.y > 0) {{
  188. results.push({{text: t.slice(0,50), x: r.x + r.width/2, y: r.y + r.height/2}});
  189. }}
  190. }}
  191. }}
  192. if (results.length >= 6) break;
  193. }}
  194. return JSON.stringify(results);
  195. }})()
  196. """)
  197. try:
  198. sugs = json.loads(sugg2 or "[]")
  199. print(f"after Enter: {len(sugs)} matches")
  200. for sg in sugs:
  201. print(f" '{sg['text'][:40]}' @ ({sg['x']:.0f},{sg['y']:.0f})")
  202. except Exception:
  203. sugs = []
  204. await page.screenshot("search_results2.png")
  205. if not sugs:
  206. print("game not found via search — aborting")
  207. return 1
  208. # 4. click the first 玖扬 match
  209. g0 = sugs[0]
  210. print(f"\nclicking '{g0['text'][:30]}'...")
  211. await page.click(g0["x"], g0["y"])
  212. await asyncio.sleep(4)
  213. # 5. on the game page, find 启动
  214. for round in range(4):
  215. launch = await page.eval("""
  216. (function() {
  217. const btns = document.querySelectorAll('button, [class*=btn], [class*=start], [class*=launch], a');
  218. const cands = [];
  219. for (const b of btns) {
  220. const t = (b.innerText || '').trim();
  221. if (/启动|开始游戏|进入游戏|继续游戏|开始/.test(t) && t.length < 12 && b.offsetParent) {
  222. const r = b.getBoundingClientRect();
  223. if (r.width > 40) {
  224. cands.push({text: t, x: r.x + r.width/2, y: r.y + r.height/2, w: r.width});
  225. }
  226. }
  227. }
  228. return JSON.stringify(cands.slice(0, 5));
  229. })()
  230. """)
  231. try:
  232. lc = json.loads(launch or "[]")
  233. except Exception:
  234. lc = []
  235. url_now = await page.eval("location.href.slice(0,80)")
  236. print(f"round {round+1}: url={url_now} launch_btns={len(lc)}")
  237. if lc:
  238. b0 = lc[0]
  239. print(f" LAUNCH: '{b0['text']}' @ ({b0['x']:.0f},{b0['y']:.0f})")
  240. await page.screenshot("before_launch.png")
  241. await page.click(b0["x"], b0["y"])
  242. print("*** 启动 CLICKED ***")
  243. break
  244. await asyncio.sleep(3)
  245. else:
  246. print("no launch button found")
  247. await page.screenshot("no_launch_btn.png")
  248. return 1
  249. # 6. monitor
  250. print("\n=== monitoring 240s: procs / gw conns / capture ===")
  251. t0 = time.time()
  252. saw_traffic = False
  253. while time.time() - t0 < 240:
  254. await asyncio.sleep(5)
  255. el = int(time.time() - t0)
  256. procs = cq_procs()
  257. conns = gateway_conns()
  258. lines = capture_lines()
  259. flags = []
  260. if procs:
  261. flags.append("procs=" + ",".join(p.split(":")[0] for p in procs))
  262. if conns:
  263. flags.append(f"gw={conns}")
  264. if lines:
  265. flags.append(f"cap={lines}")
  266. saw_traffic = True
  267. print(f" t+{el:>3}s {' '.join(flags) or '...'}")
  268. if lines >= 2:
  269. print("\n*** REAL TCP TRAFFIC CAPTURED ***")
  270. await asyncio.sleep(15) # let more login frames accumulate
  271. return 0
  272. if procs and conns and el > 60:
  273. print("\n*** game connected to gateway ***")
  274. await asyncio.sleep(30)
  275. return 0
  276. print(f"\ndone. captured={saw_traffic}")
  277. return 0 if saw_traffic else 1
  278. sys.exit(asyncio.run(main()))