Files
weixin-holiday-message/print_wechat_contacts.py

357 lines
12 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- 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()