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
This commit is contained in:
2026-02-11 14:49:38 +08:00
commit b66bac7ca8
52 changed files with 15318 additions and 0 deletions

265
wxauto/ui/sessionbox.py Normal file
View File

@@ -0,0 +1,265 @@
from __future__ import annotations
from wxauto.ui.component import (
CMenuWnd
)
from wxauto.param import (
WxParam,
WxResponse,
)
from wxauto.languages import *
from wxauto.utils import (
SetClipboardText,
)
from wxauto.logger import wxlog
from wxauto.uiautomation import Control
from wxauto.utils.tools import roll_into_view
from typing import (
List,
Union
)
import time
import re
class SessionBox:
def __init__(self, control, parent):
self.control: Control = control
self.root = parent.root
self.parent = parent
self.top_control = control.GetTopLevelControl()
self.init()
def _lang(self, text: str) -> str:
return WECHAT_SESSION_BOX.get(text, {WxParam.LANGUAGE: text}).get(WxParam.LANGUAGE)
def init(self):
self.searchbox = self.control.EditControl(Name=self._lang('搜索'))
self.session_list =\
self.control.ListControl(Name=self._lang('会话'), searchDepth=7)
self.archived_session_list =\
self.control.ListControl(Name=self._lang('折叠的群聊'), searchDepth=7)
def get_session(self) -> List[SessionElement]:
if self.session_list.Exists(0):
return [
SessionElement(i, self)
for i in self.session_list.GetChildren()
if i.Name != self._lang('折叠置顶聊天')
and not re.match(self._lang('re_置顶聊天'), i.Name)
]
elif self.archived_session_list.Exists(0):
return [SessionElement(i, self) for i in self.archived_session_list.GetChildren()]
else:
return []
def roll_up(self, n: int=5):
self.control.MiddleClick()
self.control.WheelUp(wheelTimes=n)
def roll_down(self, n: int=5):
self.control.MiddleClick()
self.control.WheelDown(wheelTimes=n)
def switch_chat(
self,
keywords: str,
exact: bool = False,
force: bool = False,
force_wait: Union[float, int] = 0.5
):
wxlog.debug(f"切换聊天窗口: {keywords}, {exact}, {force}, {force_wait}")
self.root._show()
sessions = self.get_session()
for session in sessions:
if (
keywords == session.name
and session.control.BoundingRectangle.height()
):
session.switch()
return keywords
self.searchbox.RightClick()
SetClipboardText(keywords)
menu = CMenuWnd(self)
menu.select('粘贴')
search_result = self.control.ListControl(RegexName='.*?IDS_FAV_SEARCH_RESULT.*?')
if force:
time.sleep(force_wait)
self.searchbox.SendKeys('{ENTER}')
return ''
t0 = time.time()
while time.time() -t0 < WxParam.SEARCH_CHAT_TIMEOUT:
results = []
search_result_items = search_result.GetChildren()
highlight_who = re.sub(r'(\s+)', r'</em>\1<em>', keywords)
for search_result_item in search_result_items:
item_name = search_result_item.Name
if (
search_result_item.ControlTypeName == 'PaneControl'
and search_result_item.TextControl(Name='聊天记录').Exists(0)
) or item_name == f'搜索 {keywords}':
break
elif (
search_result_item.ControlTypeName == 'ListItemControl'
and search_result_item.TextControl(Name=f"微信号: <em>{keywords}</em>").Exists(0)
):
wxlog.debug(f"{keywords} 匹配到微信号:{item_name}")
search_result_item.Click()
return item_name
elif (
search_result_item.ControlTypeName == 'ListItemControl'
and search_result_item.TextControl(Name=f"昵称: <em>{highlight_who}</em>").Exists(0)
):
wxlog.debug(f"{keywords} 匹配到昵称:{item_name}")
search_result_item.Click()
return item_name
elif (
search_result_item.ControlTypeName == 'ListItemControl'
and search_result_item.TextControl(Name=f"群聊名称: <em>{highlight_who}</em>").Exists(0)
):
wxlog.debug(f"{keywords} 匹配到群聊名称:{item_name}")
search_result_item.Click()
return item_name
elif (
search_result_item.ControlTypeName == 'ListItemControl'
and keywords == item_name
):
wxlog.debug(f"{keywords} 完整匹配")
search_result_item.Click()
return keywords
elif (
search_result_item.ControlTypeName == 'ListItemControl'
and keywords in item_name
):
results.append(search_result_item)
if exact:
wxlog.debug(f"{keywords} 未精准匹配返回None")
if search_result.Exists(0):
search_result.SendKeys('{Esc}')
return None
if results:
wxlog.debug(f"{keywords} 匹配到多个结果,返回第一个")
results[0].Click()
return results[0].Name
if search_result.Exists(0):
search_result.SendKeys('{Esc}')
def open_separate_window(self, name: str):
wxlog.debug(f"打开独立窗口: {name}")
sessions = self.get_session()
for session in sessions:
if session.name == name:
wxlog.debug(f"找到会话: {name}")
while session.control.BoundingRectangle.height():
try:
session.click()
session.double_click()
except:
pass
time.sleep(0.1)
else:
return WxResponse.success(data={'nickname': name})
wxlog.debug(f"未找到会话: {name}")
return WxResponse.failure('未找到会话')
def go_top(self):
wxlog.debug("回到会话列表顶部")
if self.archived_session_list.Exists(0):
self.control.ButtonControl(Name=self._lang('返回')).Click()
time.sleep(0.3)
first_session_name = self.session_list.GetChildren()[0].Name
while True:
self.control.WheelUp(wheelTimes=3)
time.sleep(0.1)
if self.session_list.GetChildren()[0].Name == first_session_name:
break
else:
first_session_name = self.session_list.GetChildren()[0].Name
class SessionElement:
def __init__(
self,
control: Control,
parent: SessionBox
):
self.root = parent.root
self.parent = parent
self.control = control
info_controls = [i for i in self.control.GetProgenyControl(3).GetChildren() if i.ControlTypeName=='TextControl']
self.name = info_controls[0].Name
self.time = info_controls[-1].Name
self.content = (
temp_control.Name
if (temp_control := control.GetProgenyControl(4, -1, control_type='TextControl'))
else None
)
self.ismute = (
True
if control.GetProgenyControl(4, 1, control_type='PaneControl')
else False
)
self.isnew = (new_tag_control := control.GetProgenyControl(2, 2)) is not None
self.new_count = 0
if self.isnew:
if new_tag_name := (new_tag_control.Name):
try:
self.new_count = int(new_tag_name)
self.ismute = False
except ValueError:
self.new_count = 999
else:
new_text = re.findall(self._lang('re_条数'), str(self.content))
if new_text:
try:
self.new_count = int(re.findall('\d+', new_text[0])[0])
except ValueError:
self.new_count = 999
self.content = self.content[len(new_text[0])+1:]
else:
self.new_count = 1
self.info = {
'name': self.name,
'time': self.time,
'content': self.content,
'isnew': self.isnew,
'new_count': self.new_count,
'ismute': self.ismute
}
def _lang(self, text: str) -> str:
return self.parent._lang(text)
def roll_into_view(self):
self.root._show()
roll_into_view(self.control.GetParentControl(), self.control)
def _click(self, right: bool=False, double: bool=False):
self.roll_into_view()
if right:
self.control.RightClick()
elif double:
self.control.DoubleClick()
else:
self.control.Click()
def click(self):
self._click()
def right_click(self):
self._click(right=True)
def double_click(self):
self._click(double=True)
def switch(self):
self.click()