Files
weixin-holiday-message/print_wechat_contacts.py

357 lines
12 KiB
Python
Raw Permalink Normal View History

# -*- coding: utf-8 -*-
"""
打印PC微信通讯录的所有子控件
需要以管理员权限运行Python
"""
import os
import uiautomation as auto
import time
import ctypes
from ctypes import wintypes, POINTER, byref, c_int, c_ulong, c_void_p
import comtypes.client
def print_control_tree(control, depth=0, max_depth=20, file=None):
"""递归打印控件树"""
if depth > max_depth:
return
# 获取控件信息
control_type = control.ControlTypeName
class_name = control.ClassName or ""
name = control.Name or ""
automation_id = control.AutomationId or ""
rect = control.BoundingRectangle
# 缩进
indent = " " * depth
# 打印控件信息
line = f"{indent}ControlType: {control_type} ClassName: {class_name} Name: {name} AutomationId: {automation_id} Rect: {rect} Depth: {depth}"
print(line)
if file:
file.write(line + "\n")
# 递归打印子控件
try:
children = control.GetChildren()
for child in children:
print_control_tree(child, depth + 1, max_depth, file)
except Exception as e:
error_line = f"{indent} [Error getting children: {e}]"
print(error_line)
if file:
file.write(error_line + "\n")
def print_control_tree_raw_comtypes(uiAutomation, element, depth=0, max_depth=20, file=None):
"""使用comtypes直接访问IUIAutomation接口"""
if depth > max_depth:
return
try:
# 获取控件属性
name = element.CurrentName or ""
class_name = element.CurrentClassName or ""
automation_id = element.CurrentAutomationId or ""
control_type_id = element.CurrentControlType
localized_control_type = element.CurrentLocalizedControlType or ""
# 获取BoundingRectangle
try:
rect = element.CurrentBoundingRectangle
rect_str = f"({rect.left},{rect.top},{rect.right},{rect.bottom})"
except:
rect_str = "N/A"
indent = " " * depth
line = f"{indent}ControlType: {localized_control_type} ClassName: {class_name} Name: {name} AutomationId: {automation_id} Rect: {rect_str} Depth: {depth}"
print(line)
if file:
file.write(line + "\n")
# 使用RawViewWalker遍历子元素
tree_walker = uiAutomation.RawViewWalker
child = tree_walker.GetFirstChildElement(element)
while child:
print_control_tree_raw_comtypes(uiAutomation, child, depth + 1, max_depth, file)
# 获取下一个兄弟元素
child = tree_walker.GetNextSiblingElement(child)
except Exception as e:
indent = " " * depth
error_line = f"{indent}[Error: {e}]"
print(error_line)
if file:
file.write(error_line + "\n")
def enum_windows_callback(hwnd, results):
"""枚举窗口回调函数"""
import win32gui
import win32process
if win32gui.IsWindowVisible(hwnd):
class_name = win32gui.GetClassName(hwnd)
title = win32gui.GetWindowText(hwnd)
_, pid = win32process.GetWindowThreadProcessId(hwnd)
# 检查是否是微信相关窗口
try:
import subprocess
result = subprocess.run(['tasklist', '/FI', f'PID eq {pid}', '/NH'],
capture_output=True, text=True, creationflags=subprocess.CREATE_NO_WINDOW)
if 'WeChat' in result.stdout or 'wechat' in result.stdout.lower():
results.append((hwnd, class_name, title, pid))
except:
pass
return True
def print_win32_children(hwnd, depth=0, max_depth=20, file=None):
"""使用Win32 API枚举子窗口"""
import win32gui
if depth > max_depth:
return
class_name = win32gui.GetClassName(hwnd)
title = win32gui.GetWindowText(hwnd)
rect = win32gui.GetWindowRect(hwnd)
indent = " " * depth
line = f"{indent}HWND: 0x{hwnd:X} ClassName: {class_name} Title: {title} Rect: {rect} Depth: {depth}"
print(line)
if file:
file.write(line + "\n")
# 枚举子窗口
def child_callback(child_hwnd, _):
print_win32_children(child_hwnd, depth + 1, max_depth, file)
return True
try:
win32gui.EnumChildWindows(hwnd, child_callback, None)
except:
pass
def print_accessible_tree(hwnd, depth=0, max_depth=20, file=None):
"""使用IAccessible接口枚举控件"""
import ctypes
from ctypes import POINTER, byref
from ctypes import windll, oledll
if depth > max_depth:
return
try:
import comtypes.client
from comtypes import IUnknown
# 获取IAccessible接口
accessible = oledll.oleacc.AccessibleObjectFromWindow(
hwnd,
0xFFFFFFFC, # OBJID_CLIENT
comtypes.IUnknown._iid_,
byref(ctypes.POINTER(comtypes.IUnknown)())
)
# 尝试获取更多信息
indent = " " * depth
line = f"{indent}HWND: 0x{hwnd:X} [Accessible Object Available]"
print(line)
if file:
file.write(line + "\n")
except Exception as e:
indent = " " * depth
line = f"{indent}HWND: 0x{hwnd:X} [No IAccessible: {e}]"
print(line)
if file:
file.write(line + "\n")
def main():
print("=" * 80)
print("打印PC微信通讯录的所有子控件")
print("=" * 80)
print()
# 设置全局搜索超时时间
auto.SetGlobalSearchTimeout(10)
# 获取桌面根控件
root = auto.GetRootControl()
print("桌面根控件:", root)
print()
# 查找微信主窗口 - 尝试多种方式
print("正在查找微信窗口...")
wechat_window = None
# 方式1: 通过ClassName查找 (旧版微信)
wechat_window = auto.WindowControl(searchDepth=1, ClassName='WeChatMainWndForPC')
if wechat_window.Exists(2, 1):
print(f"方式1找到微信窗口: {wechat_window.Name}, ClassName: {wechat_window.ClassName}")
else:
# 方式2: 通过Name查找
wechat_window = auto.WindowControl(searchDepth=1, Name='微信')
if wechat_window.Exists(2, 1):
print(f"方式2找到微信窗口: {wechat_window.Name}, ClassName: {wechat_window.ClassName}")
else:
# 方式3: 通过进程名查找 (新版微信 WeChatAppEx.exe)
print("尝试通过进程查找微信窗口...")
for window in root.GetChildren():
try:
process_id = window.ProcessId
import subprocess
result = subprocess.run(['tasklist', '/FI', f'PID eq {process_id}', '/NH'],
capture_output=True, text=True, creationflags=subprocess.CREATE_NO_WINDOW)
if 'WeChat' in result.stdout or 'wechat' in result.stdout.lower():
wechat_window = window
print(f"方式3找到微信窗口: {window.Name}, ClassName: {window.ClassName}, PID: {process_id}")
break
except Exception as e:
continue
if wechat_window is None or not wechat_window.Exists(1, 1):
print("\n未找到微信窗口!请确保:")
print("1. 微信已打开并登录")
print("2. 以管理员权限运行此脚本")
print("\n尝试打印所有顶级窗口以帮助调试...")
print("=" * 80)
for window in root.GetChildren():
try:
print(f"Window: Name='{window.Name}' ClassName='{window.ClassName}' PID={window.ProcessId}")
except:
pass
return
print()
# 打印微信窗口的基本信息
print("=" * 80)
print("微信主窗口信息:")
print("=" * 80)
print(f"Name: {wechat_window.Name}")
print(f"ClassName: {wechat_window.ClassName}")
print(f"AutomationId: {wechat_window.AutomationId}")
print(f"ControlType: {wechat_window.ControlTypeName}")
print(f"ProcessId: {wechat_window.ProcessId}")
print(f"Handle: {wechat_window.NativeWindowHandle}")
print()
# 输出文件
output_file = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'wechat_controls_output.txt')
# 首先尝试标准方法
print("=" * 80)
print("方式1: 使用标准UIAutomation遍历控件...")
print("=" * 80)
with open(output_file, 'w', encoding='utf-8') as f:
f.write("=" * 80 + "\n")
f.write("微信窗口控件树 - 标准UIAutomation\n")
f.write("=" * 80 + "\n\n")
f.write(f"微信窗口: Name={wechat_window.Name}, ClassName={wechat_window.ClassName}\n\n")
print_control_tree(wechat_window, depth=0, max_depth=20, file=f)
print()
print(f"标准方法输出已保存到: {output_file}")
print()
# 尝试使用RawViewWalker (可以获取更多控件)
print("=" * 80)
print("方式2: 使用RawViewWalker遍历控件 (包括原始控件)...")
print("=" * 80)
output_file_raw = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'wechat_controls_raw.txt')
try:
# 加载UIAutomation类型库
comtypes.client.GetModule('UIAutomationCore.dll')
from comtypes.gen.UIAutomationClient import IUIAutomation, CUIAutomation
# 创建IUIAutomation实例
uiAutomation = comtypes.CoCreateInstance(CUIAutomation._reg_clsid_, interface=IUIAutomation)
# 获取RootElement
root_element = uiAutomation.GetRootElement()
# 找到微信窗口元素
condition = uiAutomation.CreatePropertyCondition(30005, "微信") # UIA_NamePropertyId = 30005
# 查找微信窗口 (TreeScope_Children = 2)
wechat_element = root_element.FindFirst(2, condition)
if wechat_element:
with open(output_file_raw, 'w', encoding='utf-8') as f:
f.write("=" * 80 + "\n")
f.write("微信窗口控件树 - RawViewWalker\n")
f.write("=" * 80 + "\n\n")
print_control_tree_raw_comtypes(uiAutomation, wechat_element, depth=0, max_depth=20, file=f)
print(f"\nRawViewWalker输出已保存到: {output_file_raw}")
else:
print("使用RawViewWalker未找到微信窗口元素")
except Exception as e:
print(f"RawViewWalker方法出错: {e}")
import traceback
traceback.print_exc()
print()
# 尝试使用Win32 API枚举窗口
print("=" * 80)
print("方式3: 使用Win32 API枚举所有子窗口...")
print("=" * 80)
try:
import win32gui
import win32process
hwnd = wechat_window.NativeWindowHandle
output_file_win32 = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'wechat_controls_win32.txt')
with open(output_file_win32, 'w', encoding='utf-8') as f:
f.write("=" * 80 + "\n")
f.write("微信窗口控件树 - Win32 API\n")
f.write("=" * 80 + "\n\n")
print_win32_children(hwnd, depth=0, max_depth=20, file=f)
print(f"\nWin32 API输出已保存到: {output_file_win32}")
except ImportError:
print("未安装pywin32跳过Win32 API方式")
print("可以通过 'pip install pywin32' 安装")
except Exception as e:
print(f"Win32 API方法出错: {e}")
import traceback
traceback.print_exc()
print()
print("=" * 80)
print("完成!")
print("=" * 80)
print()
print("说明:")
print("新版微信(WeChatAppEx.exe)使用CEF/Chromium渲染UI")
print("其内部控件不通过Windows原生控件实现")
print("因此UIAutomation和Win32 API都无法直接访问其内部控件。")
print()
print("如需获取微信控件信息,可以尝试:")
print("1. 使用Microsoft Accessibility Insights工具")
print("2. 使用Chrome DevTools Protocol (如果微信支持)")
print("3. 使用OCR或图像识别技术")
if __name__ == '__main__':
main()