Files
wechat_crawler/wxauto/ui/sessionbox.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

265 lines
9.2 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 __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()