import win32con import win32gui import pyautogui import time import threading import tkinter as tk import clipboard import re from pynput import keyboard as pk_keyboard pyautogui.FAILSAFE False # # 配 置 区 # LOGIN_WINDOW_TITLE Form2 # 登录窗口标题 PASSWORD_FILE password_dict.txt # 密码字典文件 WAIT_AFTER_LOGIN 0.6 # 每次点登录后等待秒数 # 密码框序号按 (y, x) 从上到下、从左到右排序后取第几个输入框 # 你的窗口有 2 个文本框 # [左] hwnd33621482 center(146,189) ← 第1个 # [右] hwnd12387780 center(306,189) ← 第2个密码框默认 PASSWORD_ENTRY_INDEX 2 # 登录按钮序号按 x 从左到右排序后取第几个 # 你的窗口只有 1 个按钮 运算填 1 LOGIN_BUTTON_INDEX 1 # ---------- 控件类名关键词兼容多种 GUI 框架---------- ENTRY_CLASS_KEYWORDS (TkChild, Entry, Edit, ThunderRT6TextBox, TextBox) BUTTON_CLASS_KEYWORDS (Button, ThunderRT6CommandButton, CommandButton) # ---------- 窗口标题忽略列表 ---------- IGNORE_TITLES { , 操作程序, Default IME, MSCTFIME UI, Program Manager, Microsoft Text Input Application, Windows Input Experience, } IGNORE_KEYWORDS ( main.py, PyCharm, Visual Studio, Code, Edge, Chrome, Firefox, DeepSeek, 密码解密原代码, 密码登录自动化脚本源代码, ) # ---------- 弹窗内容判定关键词 ---------- # 弹窗标题都是文本框密码登录程序只能靠弹窗内部文字区分 SUCCESS_TEXT_KEYWORDS (输入正确, 密码是, 登录成功, 欢迎) FAIL_TEXT_KEYWORDS (输入错误, 请重新输入, 密码错误, 不正确, 失败) # 用于弹窗按钮定位 POPUP_BUTTON_KEYWORDS (确定, OK, 是, Yes) # root tk.Tk() root.title(操作程序) root.geometry(720x620) stop_flag threading.Event() # # 日 志 输 出 # log_text tk.Text(root, height28, width88, font(Consolas, 9)) log_text.pack(pady5) def log(msg): print(msg) try: log_text.insert(tk.END, str(msg) \n) log_text.see(tk.END) root.update() except Exception: pass def log_sep(title): log( * 64) if title: log(f {title}) log( * 64) # # 空 格 键 停 止 # def trigger_stop(source): if not stop_flag.is_set(): stop_flag.set() log(f 收到停止信号 (来源: {source}), 将尽快退出 ) else: log(f 再次收到停止信号 (来源: {source}) ) def on_global_key_press(key): try: if key pk_keyboard.Key.space: trigger_stop(pynput全局) except Exception as e: print(全局监听异常:, e) def start_global_listener(): try: listener pk_keyboard.Listener(on_presson_global_key_press) listener.daemon True listener.start() log([监听] pynput 全局空格监听已启动) except Exception as e: log(f[监听] pynput 启动失败: {e}) def on_tk_space(eventNone): trigger_stop(Tk窗口) root.bind_all(space, on_tk_space) root.bind_all(KeyPress-space, on_tk_space) # # 窗 口 查 找 与 激 活 # def find_login_window(title): log(f[查找窗口] 目标标题: {title}) hwnd win32gui.FindWindow(None, title) if hwnd: log(f[查找窗口] 精确匹配成功, hwnd{hwnd}) return hwnd log([查找窗口] 精确匹配失败, 改为模糊匹配...) found [] def cb(h, _): t win32gui.GetWindowText(h) if t and title in t: found.append((h, t)) win32gui.EnumWindows(cb, None) if found: for h, t in found: log(f[查找窗口] 模糊匹配到: hwnd{h} 标题{t}) return found[0][0] log([查找窗口] 未找到任何匹配窗口) return 0 def activate_window(hwnd): if not hwnd: return False try: if win32gui.IsIconic(hwnd): log([激活窗口] 窗口最小化, 先还原) win32gui.ShowWindow(hwnd, win32con.SW_RESTORE) win32gui.SetForegroundWindow(hwnd) time.sleep(0.2) cur win32gui.GetForegroundWindow() ok (cur hwnd) log(f[激活窗口] hwnd{hwnd} 当前前台{cur} 成功{ok}) return ok except Exception as e: log(f[激活窗口] 异常: {e}) return False # # 枚 举 子 控 件 # def enum_child_controls(hwnd): controls [] def cb(h, _): if not win32gui.IsWindowVisible(h): return controls.append(( h, win32gui.GetClassName(h), win32gui.GetWindowText(h), win32gui.GetWindowRect(h) )) win32gui.EnumChildWindows(hwnd, cb, None) return controls def dump_all_controls(hwnd): log_sep(f枚举窗口 hwnd{hwnd} 的子控件) controls enum_child_controls(hwnd) if not controls: log([枚举] 未枚举到任何可见子控件) return controls for i, (h, cls, txt, rect) in enumerate(controls): cx (rect[0] rect[2]) // 2 cy (rect[1] rect[3]) // 2 log(f [{i:02d}] hwnd{h:8} class{cls:22} text{txt} frect{rect} center({cx},{cy})) log(f[枚举] 共 {len(controls)} 个控件) return controls # # 定 位 输 入 框通用兼容 Tk / VB6 / Win32 # def _is_entry_class(cls): return any(k.lower() in cls.lower() for k in ENTRY_CLASS_KEYWORDS) def _is_button_class(cls): return any(k.lower() in cls.lower() for k in BUTTON_CLASS_KEYWORDS) def find_entries(hwnd, inner_max_width250): 找出所有输入框控件先按 y 从上到下再按 x 从左到右 controls enum_child_controls(hwnd) button_rows [(r[1], r[3]) for (h, c, t, r) in controls if _is_button_class(c)] log(f[输入框] Button 行 y 范围: {button_rows}) def is_button_row(rect): top, bottom rect[1], rect[3] for bt, bb in button_rows: if not (bottom bt or top bb): return True return False candidates [] for h, cls, txt, rect in controls: if not _is_entry_class(cls): continue if is_button_row(rect): continue width rect[2] - rect[0] height rect[3] - rect[1] if width inner_max_width: log(f[输入框] 跳过大容器 hwnd{h} class{cls} width{width} rect{rect}) continue if height 0 or width 0: continue candidates.append((h, cls, txt, rect)) candidates.sort(keylambda c: (c[3][1], c[3][0])) log(f[输入框] 候选 {len(candidates)} 个已按 从上到下、从左到右 排序:) for i, (h, cls, txt, rect) in enumerate(candidates): cx (rect[0] rect[2]) // 2 cy (rect[1] rect[3]) // 2 log(f 候选[{i}] hwnd{h} class{cls} rect{rect} center({cx},{cy})) return candidates def find_nth_entry(hwnd, n): cands find_entries(hwnd) if len(cands) n: log(f[输入框] 数量不足 ({len(cands)} {n})) return None rect cands[n - 1][3] xy ((rect[0] rect[2]) // 2, (rect[1] rect[3]) // 2) log(f[输入框] 选中第 {n} 个, 点击坐标{xy}) return xy # # 定 位 按 钮通用兼容 Tk / VB6 / Win32 # def find_nth_button(hwnd, n, keywords(登录, 登陆, 确定, Login, 运算, OK)): buttons [c for c in enum_child_controls(hwnd) if _is_button_class(c[1])] buttons.sort(keylambda c: c[3][0]) log(f[定位按钮] 共 {len(buttons)} 个 Button (已按从左到右排序)) for i, (h, cls, txt, rect) in enumerate(buttons): cx (rect[0] rect[2]) // 2 cy (rect[1] rect[3]) // 2 log(f Button[{i}] hwnd{h} class{cls} text{txt} center({cx},{cy})) if not buttons: return None matched [b for b in buttons if any(k in b[2] for k in keywords)] target matched if matched else buttons if matched: log(f[定位按钮] 关键字匹配到 {len(matched)} 个) else: log([定位按钮] 关键字未匹配, 按从左到右顺序取) if len(target) n: log(f[定位按钮] 数量不足 ({len(target)} {n})) return None rect target[n - 1][3] xy ((rect[0] rect[2]) // 2, (rect[1] rect[3]) // 2) log(f[定位按钮] 选中第 {n} 个 (从左数), 点击坐标{xy}) return xy # # 顶 层 窗 口 快 照 # def snapshot_top_windows(exclude_hwndNone): 返回当前所有可见顶层窗口的 {hwnd: title} snap {} def cb(h, _): if h exclude_hwnd: return if not win32gui.IsWindowVisible(h): return t win32gui.GetWindowText(h) if not t: return if t in IGNORE_TITLES: return if any(k in t for k in IGNORE_KEYWORDS): return snap[h] t try: win32gui.EnumWindows(cb, None) except Exception as e: log(f[快照] 枚举异常: {e}) return snap # # 弹 窗 内 容 读 取VB6 MessageBox # def read_popup_text(hwnd): 读取弹窗内所有子控件的文字 自身标题拼接返回 texts [] def cb(h, _): try: if not win32gui.IsWindowVisible(h): return t win32gui.GetWindowText(h) if t: cls win32gui.GetClassName(h) texts.append(t) except Exception: pass try: win32gui.EnumChildWindows(hwnd, cb, None) except Exception as e: log(f[弹窗读取] 枚举子控件异常: {e}) try: texts.insert(0, win32gui.GetWindowText(hwnd)) except Exception: pass return | .join(texts) def find_popup_button(hwnd, keywordsPOPUP_BUTTON_KEYWORDS): 在弹窗里找确定按钮返回其中心坐标 (x, y) 或 None buttons [] def cb(h, _): try: if not win32gui.IsWindowVisible(h): return cls win32gui.GetClassName(h) if Button in cls: rect win32gui.GetWindowRect(h) t win32gui.GetWindowText(h) buttons.append((h, cls, t, rect)) except Exception: pass try: win32gui.EnumChildWindows(hwnd, cb, None) except Exception: pass if not buttons: return None log(f[弹窗按钮] 共 {len(buttons)} 个:) for h, cls, t, rect in buttons: cx (rect[0] rect[2]) // 2 cy (rect[1] rect[3]) // 2 log(f hwnd{h} class{cls} text{t} center({cx},{cy})) matched [b for b in buttons if any(k in b[2] for k in keywords)] target matched if matched else buttons rect target[0][3] return ((rect[0] rect[2]) // 2, (rect[1] rect[3]) // 2) def close_popup(hwnd, title): 关闭弹窗 1) 激活 2) 点确定按钮 3) 回车兜底 4) WM_CLOSE 兜底 if not win32gui.IsWindow(hwnd): return True log(f[弹窗] 关闭 {title} (hwnd{hwnd})) try: if win32gui.IsIconic(hwnd): win32gui.ShowWindow(hwnd, win32con.SW_RESTORE) win32gui.SetForegroundWindow(hwnd) time.sleep(0.15) except Exception as e: log(f[弹窗] 激活异常: {e}) # 1) 点确定 xy find_popup_button(hwnd) if xy: log(f[弹窗] 点击确定按钮 ({xy[0]}, {xy[1]})) pyautogui.click(xy[0], xy[1]) time.sleep(0.25) else: log([弹窗] 未找到确定按钮, 改按回车) pyautogui.press(enter) time.sleep(0.25) if not win32gui.IsWindow(hwnd): log([弹窗] 已关闭 ✓) return True # 2) 回车兜底 try: log([弹窗] 仍未关闭, 再按一次回车) pyautogui.press(enter) time.sleep(0.25) except Exception: pass if not win32gui.IsWindow(hwnd): log([弹窗] 已关闭 ✓) return True # 3) WM_CLOSE 兜底 try: log([弹窗] 仍未关闭, 改用 WM_CLOSE) win32gui.PostMessage(hwnd, win32con.WM_CLOSE, 0, 0) time.sleep(0.25) except Exception as e: log(f[弹窗] WM_CLOSE 异常: {e}) if not win32gui.IsWindow(hwnd): log([弹窗] 已关闭 ✓) return True log([弹窗] !!! 所有方式都未能关闭) return False # # 登 录 结 果 判 定 # def check_login_result(hwnd, original_title, baseline_hwnds): 返回: (success, 密码或None) → 登录成功 (fail, None) → 密码错误已关闭弹窗 (unknown, None) → 无法判断 baseline_hwnds: 本轮点击运算之前所有顶层窗口的 hwnd 集合 # ---- 1. 原窗口是否还在 ---- if not win32gui.IsWindow(hwnd): log([检测] 原登录窗口已关闭 → 登录成功) return (success, None) # ---- 2. 原窗口标题是否变化 ---- try: title win32gui.GetWindowText(hwnd) except Exception: log([检测] 读取原窗口标题异常 → 视为成功) return (success, None) if title ! original_title: log(f[检测] 原窗口标题变为 {title} → 登录成功) return (success, None) # ---- 3. 找出新增弹窗 ---- current snapshot_top_windows(exclude_hwndhwnd) new_hwnds [h for h in current.keys() if h not in baseline_hwnds] if not new_hwnds: log([检测] 未发现新弹窗 → 结果未知) return (unknown, None) log(f[检测] 发现 {len(new_hwnds)} 个新弹窗) verdict unknown found_pwd None for h in new_hwnds: t current.get(h, ) popup_text read_popup_text(h) log(f[检测] 弹窗标题{t} 内容{popup_text}) # 判断成功/失败 hit_success any(k in popup_text for k in SUCCESS_TEXT_KEYWORDS) hit_fail any(k in popup_text for k in FAIL_TEXT_KEYWORDS) if hit_success: log(f[检测] 弹窗内容含成功关键字 → 密码正确) verdict success # 解析密码是:XX m re.search(r密码是[:]\s*(\S), popup_text) if m: found_pwd m.group(1) log(f[检测] 从弹窗解析出密码: {found_pwd}) elif hit_fail: log(f[检测] 弹窗内容含失败关键字 → 密码错误) if verdict ! success: verdict fail else: # 标题/内容都不认识 → 保守按失败处理避免死循环 log(f[检测] 弹窗内容无法判定, 按密码错误处理) if verdict ! success: verdict fail # 关闭弹窗点确定 close_popup(h, t) return (verdict, found_pwd) # # 辅 助 操 作 # def interruptible_sleep(seconds, step0.05): end time.time() seconds while time.time() end: if stop_flag.is_set(): return False time.sleep(step) return True def click_at(xy, label): if not xy: log(f[点击] {label} 坐标为空, 跳过) return False log(f[点击] {label} ({xy[0]}, {xy[1]})) pyautogui.click(xy[0], xy[1]) time.sleep(0.1) return True def clear_edit(): log([清空] CtrlA - Delete) pyautogui.press(end) for _ in range(10): pyautogui.press(backspace) def cleanup_lingering_popups(exclude_hwnd, baseline_hwnds): 退出循环前把残留的弹窗都关掉 try: cur snapshot_top_windows(exclude_hwndexclude_hwnd) for h in cur: if h not in baseline_hwnds: close_popup(h, cur[h]) except Exception as e: log(f[清理] 异常: {e}) # # 主 流 程 # def try_passwords(): stop_flag.clear() log_sep(开始执行 (按空格键可随时停止)) # ---------- 第一步登录窗口置前 ---------- log( 第一步: 把登录窗口置于最前) hwnd find_login_window(LOGIN_WINDOW_TITLE) if not hwnd: log(f!!! 未找到标题包含 {LOGIN_WINDOW_TITLE} 的窗口) return log(f窗口句柄: {hwnd}) if not activate_window(hwnd): log(!!! 激活登录窗口失败) return original_title win32gui.GetWindowText(hwnd) log(f窗口已激活, 原标题{original_title}) dump_all_controls(hwnd) if stop_flag.is_set(): log(!!! 收到停止信号, 中止) return # ---------- 第二步读取 txt 文件 ---------- log( 第二步: 读取 txt 文件) try: with open(PASSWORD_FILE, r, encodingutf-8) as f: passwords [ln.strip() for ln in f if ln.strip()] except FileNotFoundError: log(f!!! 未找到文件: {PASSWORD_FILE}) return except Exception as e: log(f!!! 读取文件异常: {e}) return log(f共读取 {len(passwords)} 条密码) for i, p in enumerate(passwords[:10], 1): log(f 第{i}条: {p}) if len(passwords) 10: log(f ... (剩余 {len(passwords) - 10} 条省略)) if not passwords: log(!!! 密码字典为空) return if stop_flag.is_set(): log(!!! 收到停止信号, 中止) return # ---------- 第三步定位密码输入框 ---------- log( 第三步: 定位密码输入框) edit_xy find_nth_entry(hwnd, nPASSWORD_ENTRY_INDEX) if not edit_xy: log(!!! 未找到密码输入框) return log(f密码输入框坐标: {edit_xy}) # ---------- 第四步定位登录按钮 ---------- log( 第四步: 定位登录按钮) btn_xy find_nth_button(hwnd, nLOGIN_BUTTON_INDEX) if not btn_xy: log(!!! 未找到登录按钮) return log(f登录按钮坐标: {btn_xy}) if stop_flag.is_set(): log(!!! 收到停止信号, 中止) return # ---------- 第五步循环输入、登录 ---------- log( 第五步: 循环输入密码并登录) success_password None total len(passwords) last_baseline set() for idx, pwd in enumerate(passwords, start1): if stop_flag.is_set(): log(f!!! 第 {idx} 轮开始前检测到停止信号, 退出循环) break log_sep(f尝试 {idx}/{total} 密码{pwd}) log([循环] 重新激活窗口) if not activate_window(hwnd): log([循环] !!! 激活失败, 跳过本次) continue if stop_flag.is_set(): log([循环] !!! 停止信号, 退出) break log([循环] 点击密码输入框) click_at(edit_xy, 密码输入框) log([循环] 清空输入框) clear_edit() log(f[循环] 复制密码到剪贴板: {pwd}) clipboard.copy(pwd) time.sleep(0.03) log([循环] CtrlV 粘贴) pyautogui.hotkey(ctrl, v) time.sleep(0.15) # 点击前记录基线 last_baseline set(snapshot_top_windows(exclude_hwndhwnd).keys()) log(f[循环] 基线窗口数{len(last_baseline)}) log([循环] 点击登录按钮) click_at(btn_xy, 登录按钮) log(f[循环] 等待 {WAIT_AFTER_LOGIN}s (按空格可打断)) if not interruptible_sleep(WAIT_AFTER_LOGIN): log([循环] !!! 等待期间被空格打断, 退出循环) cleanup_lingering_popups(hwnd, last_baseline) break # 检测登录结果 result, popup_pwd check_login_result(hwnd, original_title, last_baseline) if result success: final_pwd popup_pwd or pwd log_sep(f!!! 登录成功! 密码 {final_pwd}) success_password final_pwd break elif result fail: log(f[循环] 密码 {pwd} 错误, 继续下一个) else: log([循环] 结果未知, 继续下一个) # ---------- 收尾 ---------- if success_password: log_sep(f结果: 登录成功, 密码{success_password}) elif stop_flag.is_set(): log_sep(已由用户按空格键停止) else: log_sep(全部密码已尝试完毕, 未检测到成功) # # 界 面 # btn_run tk.Button(root, text执行操作, commandtry_passwords, height2, width20) btn_run.pack(pady5) btn_clear tk.Button(root, text清空日志, commandlambda: log_text.delete(1.0, tk.END)) btn_clear.pack(pady2) start_global_listener() log([提示] 按空格键可停止。) log(f[配置] 密码框序号{PASSWORD_ENTRY_INDEX} (从上到下/从左到右), f登录按钮序号{LOGIN_BUTTON_INDEX} (从左往右)) log(f[配置] 每次登录等待 {WAIT_AFTER_LOGIN}s) root.mainloop()