Files
wechat_crawler/wxauto/ui/component.py
李顺东 b66bac7ca8 feat: Initialize wxauto WeChat automation project with job extraction tools
- Add wxauto package with WeChat UI automation and message handling capabilities
- Implement job_extractor.py for automated job posting extraction from WeChat groups
- Add job_extractor_gui.py providing graphical interface for job extraction tool
- Create comprehensive documentation in Chinese covering GUI usage, multi-group support, and quick start guides
- Add build configuration files (build_exe.py, build_exe.spec) for packaging as standalone executable
- Include utility scripts for WeChat interaction (auto_send_msg.py, get_history.py, receive_file_transfer.py)
- Add project configuration files (pyproject.toml, setup.cfg, requirements.txt)
- Include test files (test_api.py, test_com_fix.py) for API and compatibility validation
- Add Apache 2.0 LICENSE and comprehensive README documentation
- Configure .gitignore to exclude build artifacts, logs, and temporary files
2026-02-11 14:49:38 +08:00

408 lines
15 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.

from wxauto.utils import (
FindWindow,
SetClipboardText,
ReadClipboardData,
GetAllWindows,
GetWindowRect,
capture
)
from wxauto.param import (
WxParam,
WxResponse,
)
from wxauto.languages import *
from .base import (
BaseUISubWnd
)
from wxauto.logger import wxlog
from wxauto.uiautomation import (
ControlFromHandle,
)
from wxauto.utils.tools import (
get_file_dir,
roll_into_view,
)
from PIL import Image
from wxauto import uiautomation as uia
import traceback
import shutil
import time
class EditBox:
...
class SelectContactWnd(BaseUISubWnd):
"""选择联系人窗口"""
_ui_cls_name = 'SelectContactWnd'
def __init__(self, parent):
self.parent = parent
self.root = parent.root
hwnd = FindWindow(self._ui_cls_name, timeout=1)
if hwnd:
self.control = ControlFromHandle(hwnd)
else:
self.control = parent.root.control.PaneControl(ClassName=self._ui_cls_name, searchDepth=1)
self.editbox = self.control.EditControl()
def send(self, target):
if isinstance(target, str):
SetClipboardText(target)
while not self.editbox.HasKeyboardFocus:
self.editbox.Click()
time.sleep(0.1)
self.editbox.SendKeys('{Ctrl}a')
self.editbox.SendKeys('{Ctrl}v')
checkbox = self.control.ListControl().CheckBoxControl()
if checkbox.Exists(1):
checkbox.Click()
self.control.ButtonControl(Name='发送').Click()
return WxResponse.success()
else:
self.control.SendKeys('{Esc}')
wxlog.debug(f'未找到好友:{target}')
return WxResponse.failure(f'未找到好友:{target}')
elif isinstance(target, list):
n = 0
fail = []
multiselect = self.control.ButtonControl(Name='多选')
if multiselect.Exists(0):
multiselect.Click()
for i in target:
SetClipboardText(i)
while not self.editbox.HasKeyboardFocus:
self.editbox.Click()
time.sleep(0.1)
self.editbox.SendKeys('{Ctrl}a')
self.editbox.SendKeys('{Ctrl}v')
checkbox = self.control.ListControl().CheckBoxControl()
if checkbox.Exists(1):
checkbox.Click()
n += 1
else:
fail.append(i)
wxlog.debug(f"未找到转发对象:{i}")
if n > 0:
self.control.ButtonControl(RegexName='分别发送(\d+').Click()
if n == len(target):
return WxResponse.success()
else:
return WxResponse.success('存在未转发成功名单', data=fail)
else:
self.control.SendKeys('{Esc}')
wxlog.debug(f'所有好友均未未找到:{target}')
return WxResponse.failure(f'所有好友均未未找到:{target}')
class CMenuWnd(BaseUISubWnd):
_ui_cls_name = 'CMenuWnd'
def __init__(self, parent):
self.parent = parent
self.root = parent.root
if menulist := [i for i in GetAllWindows() if 'CMenuWnd' in i]:
self.control = uia.ControlFromHandle(menulist[0][0])
else:
self.control = self.root.control.MenuControl(ClassName=self._ui_cls_name)
def _lang(self, text: str) -> str:
return MENU_OPTIONS.get(text, {WxParam.LANGUAGE: text}).get(WxParam.LANGUAGE)
@property
def option_controls(self):
return self.control.ListControl().GetChildren()
@property
def option_names(self):
return [c.Name for c in self.option_controls]
def select(self, item):
if not self.exists(0):
return WxResponse.failure('菜单窗口不存在')
if isinstance(item, int):
self.option_controls[item].Click()
return WxResponse.success()
item = self._lang(item)
for c in self.option_controls:
if c.Name == item:
c.Click()
return WxResponse.success()
if self.exists(0):
self.close()
return WxResponse.failure(f'未找到选项:{item}')
def close(self):
try:
self.control.SendKeys('{ESC}')
except Exception as e:
pass
class NetErrInfoTipsBarWnd(BaseUISubWnd):
_ui_cls_name = 'NetErrInfoTipsBarWnd'
def __init__(self, parent):
self.control = parent.root.control.PaneControl(ClassName=self._ui_cls_name)
def __bool__(self):
return self.exists(0)
class WeChatImage(BaseUISubWnd):
_ui_cls_name = 'ImagePreviewWnd'
def __init__(self) -> None:
self.hwnd = FindWindow(classname=self._ui_cls_name)
if self.hwnd:
self.control = ControlFromHandle(self.hwnd)
self.type = 'image'
if self.control.PaneControl(ClassName='ImagePreviewLayerWnd').Exists(0):
self.type = 'video'
MainControl1 = [i for i in self.control.GetChildren() if not i.ClassName][0]
self.ToolsBox, self.PhotoBox = MainControl1.GetChildren()
# tools按钮
self.t_previous = self.ToolsBox.ButtonControl(Name=self._lang('上一张'))
self.t_next = self.ToolsBox.ButtonControl(Name=self._lang('下一张'))
self.t_zoom = self.ToolsBox.ButtonControl(Name=self._lang('放大'))
self.t_translate = self.ToolsBox.ButtonControl(Name=self._lang('翻译'))
self.t_ocr = self.ToolsBox.ButtonControl(Name=self._lang('提取文字'))
self.t_save = self.control.ButtonControl(Name=self._lang('另存为...'))
self.t_qrcode = self.ToolsBox.ButtonControl(Name=self._lang('识别图中二维码'))
def _lang(self, text: str) -> str:
return IMAGE_WINDOW.get(text, {WxParam.LANGUAGE: text}).get(WxParam.LANGUAGE)
def ocr(self, wait=10):
result = ''
ctrls = self.PhotoBox.GetChildren()
if len(ctrls) == 2:
self.t_ocr.Click()
t0 = time.time()
while time.time() - t0 < wait:
ctrls = self.PhotoBox.GetChildren()
if len(ctrls) == 3:
TranslateControl = ctrls[-1]
result = TranslateControl.TextControl().Name
if result:
return result
else:
self.t_ocr.Click()
time.sleep(0.1)
return result
def save(self, dir_path=None, timeout=10):
"""保存图片/视频
Args:
dir_path (str): 绝对路径,包括文件名和后缀,例如:"D:/Images/微信图片_xxxxxx.png"
timeout (int, optional): 保存超时时间默认10秒
Returns:
str: 文件保存路径即savepath
"""
if dir_path is None:
dir_path = WxParam.DEFAULT_SAVE_PATH
suffix = 'png' if self.type == 'image' else 'mp4'
filename = f"wxauto_{self.type}_{time.strftime('%Y%m%d%H%M%S')}.{suffix}"
filepath = get_file_dir(dir_path) / filename
t0 = time.time()
SetClipboardText('')
while True:
if time.time() - t0 > timeout:
if self.control.Exists(0):
self.control.SendKeys('{Esc}')
raise TimeoutError('下载超时')
try:
self.control.ButtonControl(Name=self._lang('更多')).Click()
menu = self.control.MenuControl(ClassName='CMenuWnd')
menu.MenuItemControl(Name=self._lang('复制')).Click()
clipboard_data = ReadClipboardData()
path = clipboard_data['15'][0]
wxlog.debug(f"读取到图片/视频路径:{path}")
break
except:
wxlog.debug(traceback.format_exc())
time.sleep(0.1)
shutil.copyfile(path, filepath)
SetClipboardText('')
if self.control.Exists(0):
wxlog.debug("关闭图片窗口")
self.control.SendKeys('{Esc}')
return filepath
class NewFriendElement:
def __init__(self, control, parent):
self.parent = parent
self.root = parent.root
self.control = control
self.name = self.control.Name
self.msg = self.control.GetFirstChildControl().PaneControl(SearchDepth=1).GetChildren()[-1].TextControl().Name
self.NewFriendsBox = self.root.chatbox.control.ListControl(Name='新的朋友').GetParentControl()
self.status = self.control.GetFirstChildControl().GetChildren()[-1]
self.acceptable = isinstance(self.status, uia.ButtonControl)
def __repr__(self) -> str:
return f"<wxauto New Friends Element at {hex(id(self))} ({self.name}: {self.msg})>"
def delete(self):
wxlog.info(f'删除好友请求: {self.name}')
roll_into_view(self.NewFriendsBox, self.control)
self.control.RightClick()
menu = CMenuWnd(self.root)
menu.select('删除')
def reply(self, text):
wxlog.debug(f'回复好友请求: {self.name}')
roll_into_view(self.NewFriendsBox, self.control)
self.control.Click()
self.root.ChatBox.ButtonControl(Name='回复').Click()
edit = self.root.ChatBox.EditControl()
edit.Click()
edit.SendKeys('{Ctrl}a')
SetClipboardText(text)
edit.SendKeys('{Ctrl}v')
time.sleep(0.1)
self.root.ChatBox.ButtonControl(Name='发送').Click()
dialog = self.root.UiaAPI.PaneControl(ClassName='WeUIDialog')
while edit.Exists(0):
if dialog.Exists(0):
systext = dialog.TextControl().Name
wxlog.debug(f'系统提示: {systext}')
dialog.SendKeys('{Esc}')
self.root.ChatBox.ButtonControl(Name='').Click()
return WxResponse.failure(msg=systext)
time.sleep(0.1)
self.root.ChatBox.ButtonControl(Name='').Click()
return WxResponse.success()
def accept(self, remark=None, tags=None, permission='朋友圈'):
"""接受好友请求
Args:
remark (str, optional): 备注名
tags (list, optional): 标签列表
permission (str, optional): 朋友圈权限, 可选值:'朋友圈', '仅聊天'
"""
if not self.acceptable:
wxlog.debug(f"当前好友状态无法接受好友请求:{self.name}")
return
wxlog.debug(f"接受好友请求:{self.name} 备注:{remark} 标签:{tags}")
self.root._show()
roll_into_view(self.NewFriendsBox, self.status)
self.status.Click()
NewFriendsWnd = self.root.control.WindowControl(ClassName='WeUIDialog')
tipscontrol = NewFriendsWnd.TextControl(Name="你的联系人较多,添加新的朋友时需选择权限")
permission_sns = NewFriendsWnd.CheckBoxControl(Name='聊天、朋友圈、微信运动等')
permission_chat = NewFriendsWnd.CheckBoxControl(Name='仅聊天')
if tipscontrol.Exists(0.5):
permission_sns = tipscontrol.GetParentControl().GetParentControl().TextControl(Name='朋友圈')
permission_chat = tipscontrol.GetParentControl().GetParentControl().TextControl(Name='仅聊天')
if remark:
remarkedit = NewFriendsWnd.TextControl(Name='备注名').GetParentControl().EditControl()
remarkedit.Click()
remarkedit.SendKeys('{Ctrl}a')
remarkedit.SendKeys(remark)
if tags:
tagedit = NewFriendsWnd.TextControl(Name='标签').GetParentControl().EditControl()
for tag in tags:
tagedit.Click()
tagedit.SendKeys(tag)
NewFriendsWnd.PaneControl(ClassName='DropdownWindow').TextControl().Click()
if permission == '朋友圈':
permission_sns.Click()
elif permission == '仅聊天':
permission_chat.Click()
NewFriendsWnd.ButtonControl(Name='确定').Click()
class WeChatLoginWnd(BaseUISubWnd):
_ui_cls_name = 'WeChatLoginWndForPC'
def __init__(self):
self.hwnd = FindWindow(classname=self._ui_cls_name)
if self.hwnd:
self.control = ControlFromHandle(self.hwnd)
def _lang(self, text: str) -> str:
return WECHAT_LOGINWND.get(text, {WxParam.LANGUAGE: text}).get(WxParam.LANGUAGE)
def clear_hint(self) -> bool:
self._show()
hint_dialog = self.control.PaneControl(Name=self._lang('提示'))
if hint_dialog.Exists(0):
dialog_button = self.control.ButtonControl(Name=self._lang('确定'))
dialog_button.Click()
return True
else:
return False
def login(self) -> bool:
self._show()
enter = self.control.ButtonControl(Name=self._lang('进入微信'))
if enter.Exists(0):
enter.Click()
return True
else:
return False
def get_qrcode(self) -> Image.Image:
self._show()
qrcode = self.control.ButtonControl(Name=self._lang('二维码'))
if qrcode.Exists(0):
window_rect = GetWindowRect(self.hwnd)
win_left, win_top, win_right, win_bottom = window_rect
bbox = win_left + 62, win_top + 88, win_left + 218, win_top + 245
return capture(self.hwnd, bbox)
else:
return None
class WeChatBrowser(BaseUISubWnd):
_ui_cls_name = 'Chrome_WidgetWin_0'
_ui_name = '微信'
def __init__(self):
self.hwnd = FindWindow(classname=self._ui_cls_name, name=self._ui_name)
if self.hwnd:
self.control = ControlFromHandle(self.hwnd)
def _lang(self, text: str) -> str:
return WECHAT_BROWSER.get(text, {WxParam.LANGUAGE: text}).get(WxParam.LANGUAGE)
def get_url(self) -> str:
self._show()
tab = self.control.TabItemControl()
if tab.Exists():
tab.RightClick()
copy_link_item = uia.MenuItemControl(Name=self._lang('复制链接'))
if copy_link_item.Exists():
copy_link_item.Click()
clipboard_data = ReadClipboardData()
url = (clipboard_data.get('13') or
clipboard_data.get('1') or
None)
SetClipboardText('')
return url
else:
wxlog.debug(f'找不到复制链接菜单项')
else:
wxlog.debug(f'找不到标签页')
def close(self):
close_button = self.control.ButtonControl(Name=self._lang('关闭'), foundIndex=1)
if close_button.Exists():
close_button.Click()
close_button = self.control.ButtonControl(Name=self._lang('关闭'), foundIndex=2)
if close_button.Exists():
close_button.Click()
close_button = self.control.ButtonControl(Name=self._lang('关闭'), foundIndex=3)
if close_button.Exists():
close_button.Click()