# -*- coding: utf-8 -*- """Execute UI actions from ui_actions.json (list of ops), screenshot after each marked step. Runs in console session via schtask. ASCII output only. Ops: click(x,y,clicks,button) move(x,y) key(k) hotkey(keys[]) scroll(dy,x,y) wait(sec) shot(tag) """ import json import time import pyautogui pyautogui.FAILSAFE = True PLAN = r"C:\sanguo_bigqmt\ui_actions.json" SHOT = r"C:\sanguo_bigqmt\shot_%02d.png" plan = json.load(open(PLAN, encoding="utf-8")) # utf-8 regardless of GBK console n = 0 def shot(tag=""): global n n += 1 pyautogui.screenshot().save(SHOT % n) print("SHOT_%02d %s" % (n, tag)) for a in plan: op = a["op"] if op == "closewin": import ctypes user32 = ctypes.windll.user32 WM_CLOSE = 0x0010 if a.get("hwnd"): user32.PostMessageW(a["hwnd"], WM_CLOSE, 0, 0) print("CLOSEWIN hwnd=%s" % a["hwnd"]) else: Ws2 = ctypes.WINFUNCTYPE(ctypes.c_bool, ctypes.c_void_p, ctypes.c_void_p) hits = [] @Ws2 def cb2(h2, _): buf = ctypes.create_unicode_buffer(256) user32.GetWindowTextW(h2, buf, 256) if a["contains"] in buf.value: hits.append(h2) return True user32.EnumWindows(cb2, 0) for h2 in hits: user32.PostMessageW(h2, WM_CLOSE, 0, 0) print("CLOSEWIN %s hwnd=%s" % (a["contains"], h2)) if op == "exec": import subprocess r = subprocess.run([r"C:\Python310\python.exe", a["script"]], capture_output=True, text=True, timeout=60) for ln in (r.stdout or "").splitlines(): print("X| " + ln) for ln in (r.stderr or "").splitlines()[:10]: print("E| " + ln) if op == "activate": import ctypes user32 = ctypes.windll.user32 title = a.get("title") h = user32.FindWindowW(None, title) if title else 0 if not h and a.get("contains"): Ws = ctypes.WINFUNCTYPE(ctypes.c_bool, ctypes.c_void_p, ctypes.c_void_p) found = [] @Ws def cb(h2, _): buf = ctypes.create_unicode_buffer(256) user32.GetWindowTextW(h2, buf, 256) if a["contains"] in buf.value and user32.IsWindowVisible(h2): found.append(h2) return True user32.EnumWindows(cb, 0) h = found[0] if found else 0 if h: user32.ShowWindow(h, 9) # SW_RESTORE user32.SetForegroundWindow(h) time.sleep(0.8) print("ACTIVATE ok hwnd=%s title=%s" % (h, a.get("title") or a.get("contains"))) else: print("ACTIVATE_FAIL not_found") if op == "launch": import subprocess cwd = a.get("cwd") or None subprocess.Popen([a["exe"]], cwd=cwd) print("LAUNCH %s" % a["exe"].encode("unicode_escape").decode("ascii")) if op == "minimize": import ctypes user32 = ctypes.windll.user32 if a.get("hwnd"): user32.ShowWindow(a["hwnd"], 6) # SW_MINIMIZE print("MINIMIZE hwnd=%s" % a["hwnd"]) elif a.get("contains"): Ws3 = ctypes.WINFUNCTYPE(ctypes.c_bool, ctypes.c_void_p, ctypes.c_void_p) hits = [] @Ws3 def cb3(h2, _): buf = ctypes.create_unicode_buffer(256) user32.GetWindowTextW(h2, buf, 256) if a["contains"] in buf.value and user32.IsWindowVisible(h2): hits.append(h2) return True user32.EnumWindows(cb3, 0) for h2 in hits[:1]: user32.ShowWindow(h2, 6) print("MINIMIZE %s hwnd=%s" % (a["contains"], h2)) if op == "credtype": # decrypt DPAPI secret in-process and type it into field (x,y). # never printed, never written to disk. import base64 import ctypes class DATA_BLOB(ctypes.Structure): _fields_ = [("cbData", ctypes.c_ulong), ("pbData", ctypes.POINTER(ctypes.c_char))] enc_hex = open(r"C:\sanguo_bigqmt\creds\qmt_login.enc", encoding="ascii").read().strip() raw = bytes.fromhex(enc_hex) bin_ = DATA_BLOB(len(raw), ctypes.cast( ctypes.create_string_buffer(raw, len(raw)), ctypes.POINTER(ctypes.c_char))) bout = DATA_BLOB() ok = ctypes.windll.crypt32.CryptUnprotectData( ctypes.byref(bin_), None, None, None, None, 0, ctypes.byref(bout)) if not ok: print("CREDTYPE decrypt_failed") else: plain = ctypes.string_at(bout.pbData, bout.cbData).decode( "utf-16-le", errors="replace").rstrip("\x00") # cred json: {"user": "...", "password": "..."} import json as _j pw = _j.loads(plain)["password"] pyautogui.click(a["x"], a["y"]) time.sleep(0.4) pyautogui.typewrite(pw, interval=0.06) print("CREDTYPE typed len=%d" % len(pw)) ctypes.windll.kernel32.LocalFree(bout.pbData) if op == "text": # type arbitrary (ASCII) text at x,y — used for captcha fields pyautogui.click(a["x"], a["y"]) time.sleep(0.3) pyautogui.typewrite(a["s"], interval=0.08) print("TEXT typed len=%d" % len(a["s"])) if op == "info": im = pyautogui.screenshot() print("SIZE_cursor=%s SIZE_bitmap=%s" % (pyautogui.size(), im.size)) if op == "shot": shot(a.get("tag", "")) elif op == "region": n += 1 im = pyautogui.screenshot() im.crop((a["x"], a["y"], a["x"] + a["w"], a["y"] + a["h"])).save( SHOT % n) print("REGION_%02d %s" % (n, a.get("tag", ""))) elif op == "zoom": n += 1 s = a.get("scale", 3) im = pyautogui.screenshot().crop( (a["x"], a["y"], a["x"] + a["w"], a["y"] + a["h"])) im = im.resize((im.width * s, im.height * s)) im.save(SHOT % n) print("ZOOM_%02d %s" % (n, a.get("tag", ""))) elif op == "click": pyautogui.click(a["x"], a["y"], clicks=a.get("clicks", 1), interval=0.35, button=a.get("button", "left")) print("CLICK %s %s x%s" % (a["x"], a["y"], a.get("clicks", 1))) elif op == "move": pyautogui.moveTo(a["x"], a["y"], duration=0.25) print("MOVE %s %s" % (a["x"], a["y"])) elif op == "key": pyautogui.press(a["key"]) print("KEY %s" % a["key"]) elif op == "hotkey": pyautogui.hotkey(*a["keys"]) print("HOTKEY %s" % "+".join(a["keys"])) elif op == "scroll": pyautogui.scroll(a.get("dy", -300), x=a.get("x"), y=a.get("y")) print("SCROLL %s" % a.get("dy", -300)) elif op == "wait": time.sleep(a["sec"]) print("WAIT %s" % a["sec"]) time.sleep(a.get("after", 0.6)) shot("end") print("UI_ACT_DONE nshots=%d" % n)