2b2ae3c4d8
- qmt_sentinel.py: sanguo-qmt-sentinel 每30min(进程+桥RPC ping判红,账户类仅记录); 红->schtasks /run sanguo-qmt-relogin->轮询桥恢复<=10min->复检留痕;无退避(拍板#4) - qmt_probe_0915.py: sanguo-qmt-probe0915 周一至五09:15; 510050.SH买100@0.01 FIX_PRICE必不成交单(09-09 09:40实证参数形状)+即撤; status57废单=会话陈旧 ->触发relogin赶开盘; 窗口硬卡09:15:00-09:19:30(09:20-09:25不可撤) - qmt_gate_common.py: redis/ping/tasklist/触发/轮询/gate留痕共享库 - 留痕: C:\sanguo_bigqmt\gate\qmt_gate_YYYYMMDD.log + qmt_gate_latest.json - ui_act.py 修复: activate仅contains时KeyError; credtype LocalFree类型错; 新增text op (typewrite连打丢数字/shift字符,验证码须key/hotkey逐字符打) - 实测: 首夜14跳全绿cash零漂移; 下单RPC管线ok=True; SKIP/退出码/桥死分支全验 - schtask XML x2(SYSTEM+HighestAvailable,照sanguo-xt-daily模式)
189 lines
7.0 KiB
Python
189 lines
7.0 KiB
Python
# -*- 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)
|