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:
75
wxauto/msgs/__init__.py
Normal file
75
wxauto/msgs/__init__.py
Normal file
@@ -0,0 +1,75 @@
|
||||
from .msg import parse_msg
|
||||
from .base import (
|
||||
BaseMessage,
|
||||
HumanMessage
|
||||
)
|
||||
from .attr import (
|
||||
SystemMessage,
|
||||
TickleMessage,
|
||||
TimeMessage,
|
||||
FriendMessage,
|
||||
SelfMessage
|
||||
)
|
||||
from .type import (
|
||||
TextMessage,
|
||||
ImageMessage,
|
||||
VoiceMessage,
|
||||
VideoMessage,
|
||||
FileMessage,
|
||||
LinkMessage,
|
||||
OtherMessage
|
||||
)
|
||||
from .self import (
|
||||
SelfMessage,
|
||||
SelfTextMessage,
|
||||
SelfVoiceMessage,
|
||||
SelfImageMessage,
|
||||
SelfVideoMessage,
|
||||
SelfFileMessage,
|
||||
SelfLinkMessage,
|
||||
SelfOtherMessage,
|
||||
)
|
||||
from .friend import (
|
||||
FriendMessage,
|
||||
FriendTextMessage,
|
||||
FriendVoiceMessage,
|
||||
FriendImageMessage,
|
||||
FriendVideoMessage,
|
||||
FriendFileMessage,
|
||||
FriendLinkMessage,
|
||||
FriendOtherMessage,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
'parse_msg',
|
||||
'BaseMessage',
|
||||
'HumanMessage',
|
||||
'SystemMessage',
|
||||
'TickleMessage',
|
||||
'TimeMessage',
|
||||
'FriendMessage',
|
||||
'SelfMessage',
|
||||
'TextMessage',
|
||||
'ImageMessage',
|
||||
'VoiceMessage',
|
||||
'VideoMessage',
|
||||
'FileMessage',
|
||||
'LinkMessage',
|
||||
'OtherMessage',
|
||||
'SelfMessage',
|
||||
'SelfTextMessage',
|
||||
'SelfVoiceMessage',
|
||||
'SelfImageMessage',
|
||||
'SelfVideoMessage',
|
||||
'SelfFileMessage',
|
||||
'SelfLinkMessage',
|
||||
'SelfOtherMessage',
|
||||
'FriendMessage',
|
||||
'FriendTextMessage',
|
||||
'FriendVoiceMessage',
|
||||
'FriendImageMessage',
|
||||
'FriendVideoMessage',
|
||||
'FriendFileMessage',
|
||||
'FriendLinkMessage',
|
||||
'FriendOtherMessage',
|
||||
]
|
||||
85
wxauto/msgs/attr.py
Normal file
85
wxauto/msgs/attr.py
Normal file
@@ -0,0 +1,85 @@
|
||||
from .base import *
|
||||
from wxauto.utils.tools import (
|
||||
parse_wechat_time
|
||||
)
|
||||
|
||||
class SystemMessage(BaseMessage):
|
||||
attr = 'system'
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
control: uia.Control,
|
||||
parent: "ChatBox"
|
||||
):
|
||||
super().__init__(control, parent)
|
||||
self.sender = 'system'
|
||||
self.sender_remark = 'system'
|
||||
|
||||
class TickleMessage(SystemMessage):
|
||||
attr = 'tickle'
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
control: uia.Control,
|
||||
parent: "ChatBox"
|
||||
):
|
||||
super().__init__(control, parent)
|
||||
self.tickle_list = [
|
||||
i.Name for i in
|
||||
control.ListItemControl().GetParentControl().GetChildren()
|
||||
]
|
||||
self.content = f"[{len(self.tickle_list)}条]{self.tickle_list[0]}"
|
||||
|
||||
|
||||
class TimeMessage(SystemMessage):
|
||||
attr = 'time'
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
control: uia.Control,
|
||||
parent: "ChatBox"
|
||||
):
|
||||
super().__init__(control, parent)
|
||||
self.time = parse_wechat_time(self.content)
|
||||
|
||||
class FriendMessage(HumanMessage):
|
||||
attr = 'friend'
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
control: uia.Control,
|
||||
parent: "ChatBox"
|
||||
):
|
||||
super().__init__(control, parent)
|
||||
self.head_control = self.control.ButtonControl(RegexName='.*?')
|
||||
self.sender = self.head_control.Name
|
||||
if (
|
||||
(remark_control := self.control.TextControl()).Exists(0)
|
||||
and remark_control.BoundingRectangle.top < self.head_control.BoundingRectangle.top
|
||||
):
|
||||
self.sender_remark = remark_control.Name
|
||||
else:
|
||||
self.sender_remark = self.sender
|
||||
|
||||
@property
|
||||
def _xbias(self):
|
||||
if WxParam.FORCE_MESSAGE_XBIAS:
|
||||
return int(self.head_control.BoundingRectangle.width()*1.5)
|
||||
return WxParam.DEFAULT_MESSAGE_XBIAS
|
||||
|
||||
|
||||
class SelfMessage(HumanMessage):
|
||||
attr = 'self'
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
control: uia.Control,
|
||||
parent: "ChatBox"
|
||||
):
|
||||
super().__init__(control, parent)
|
||||
|
||||
@property
|
||||
def _xbias(self):
|
||||
if WxParam.FORCE_MESSAGE_XBIAS:
|
||||
return -int(self.head_control.BoundingRectangle.width()*1.5)
|
||||
return -WxParam.DEFAULT_MESSAGE_XBIAS
|
||||
185
wxauto/msgs/base.py
Normal file
185
wxauto/msgs/base.py
Normal file
@@ -0,0 +1,185 @@
|
||||
from wxauto import uiautomation as uia
|
||||
from wxauto.logger import wxlog
|
||||
from wxauto.param import (
|
||||
WxResponse,
|
||||
WxParam,
|
||||
PROJECT_NAME
|
||||
)
|
||||
from wxauto.ui.component import (
|
||||
CMenuWnd,
|
||||
SelectContactWnd
|
||||
)
|
||||
from wxauto.utils.tools import roll_into_view
|
||||
from wxauto.languages import *
|
||||
from typing import (
|
||||
Dict,
|
||||
List,
|
||||
Union,
|
||||
TYPE_CHECKING
|
||||
)
|
||||
from hashlib import md5
|
||||
import time
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from wxauto.ui.chatbox import ChatBox
|
||||
|
||||
def truncate_string(s: str, n: int=8) -> str:
|
||||
s = s.replace('\n', '').strip()
|
||||
return s if len(s) <= n else s[:n] + '...'
|
||||
|
||||
class Message:...
|
||||
class BaseMessage(Message):
|
||||
type: str = 'base'
|
||||
attr: str = 'base'
|
||||
control: uia.Control
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
control: uia.Control,
|
||||
parent: "ChatBox",
|
||||
):
|
||||
self.control = control
|
||||
self.parent = parent
|
||||
self.root = parent.root
|
||||
self.content = self.control.Name
|
||||
self.id = self.control.runtimeid
|
||||
self.sender = self.attr
|
||||
self.sender_remark = self.attr
|
||||
|
||||
def __repr__(self):
|
||||
cls_name = self.__class__.__name__
|
||||
content = truncate_string(self.content)
|
||||
return f"<{PROJECT_NAME} - {cls_name}({content}) at {hex(id(self))}>"
|
||||
|
||||
@property
|
||||
def message_type_name(self) -> str:
|
||||
return self.__class__.__name__
|
||||
|
||||
def chat_info(self) -> Dict:
|
||||
if self.control.Exists(0):
|
||||
return self.parent.get_info()
|
||||
|
||||
def _lang(self, text: str) -> str:
|
||||
return MESSAGES.get(text, {WxParam.LANGUAGE: text}).get(WxParam.LANGUAGE)
|
||||
|
||||
def roll_into_view(self) -> WxResponse:
|
||||
if roll_into_view(self.control.GetParentControl(), self.control, equal=True) == 'not exist':
|
||||
wxlog.warning('消息目标控件不存在,无法滚动至显示窗口')
|
||||
return WxResponse.failure('消息目标控件不存在,无法滚动至显示窗口')
|
||||
return WxResponse.success('成功')
|
||||
|
||||
@property
|
||||
def info(self) -> Dict:
|
||||
_info = self.parent.get_info().copy()
|
||||
_info['class'] = self.message_type_name
|
||||
_info['id'] = self.id
|
||||
_info['type'] = self.type
|
||||
_info['attr'] = self.attr
|
||||
_info['content'] = self.content
|
||||
return _info
|
||||
|
||||
|
||||
class HumanMessage(BaseMessage):
|
||||
attr = 'human'
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
control: uia.Control,
|
||||
parent: "ChatBox",
|
||||
):
|
||||
super().__init__(control, parent)
|
||||
self.head_control = self.control.ButtonControl(searchDepth=2)
|
||||
|
||||
|
||||
def roll_into_view(self) -> WxResponse:
|
||||
if roll_into_view(self.control.GetParentControl(), self.head_control, equal=True) == 'not exist':
|
||||
return WxResponse.failure('消息目标控件不存在,无法滚动至显示窗口')
|
||||
return WxResponse.success('成功')
|
||||
|
||||
def click(self):
|
||||
self.roll_into_view()
|
||||
self.head_control.Click(x=self._xbias)
|
||||
|
||||
def right_click(self):
|
||||
self.roll_into_view()
|
||||
self.head_control.Click(x=-self._xbias)
|
||||
self.head_control.RightClick(x=self._xbias)
|
||||
|
||||
def select_option(self, option: str, timeout=None) -> WxResponse:
|
||||
self.root._show()
|
||||
def _select_option(self, option):
|
||||
if not (roll_result := self.roll_into_view()):
|
||||
return roll_result
|
||||
self.right_click()
|
||||
menu = CMenuWnd(self.root)
|
||||
return menu.select(item=option)
|
||||
|
||||
if timeout:
|
||||
t0 = time.time()
|
||||
while True:
|
||||
if (time.time() - t0) > timeout:
|
||||
return WxResponse(False, '引用消息超时')
|
||||
if quote_result := _select_option(self, option):
|
||||
return quote_result
|
||||
|
||||
else:
|
||||
return _select_option(self, option)
|
||||
|
||||
def quote(
|
||||
self, text: str,
|
||||
at: Union[List[str], str] = None,
|
||||
timeout: int = 3
|
||||
) -> WxResponse:
|
||||
"""引用消息
|
||||
|
||||
Args:
|
||||
text (str): 引用内容
|
||||
at (List[str], optional): @用户列表
|
||||
timeout (int, optional): 超时时间,单位为秒,若为None则不启用超时设置
|
||||
|
||||
Returns:
|
||||
WxResponse: 调用结果
|
||||
"""
|
||||
if not self.select_option('引用', timeout=timeout):
|
||||
wxlog.debug(f"当前消息无法引用:{self.content}")
|
||||
return WxResponse(False, '当前消息无法引用')
|
||||
|
||||
if at:
|
||||
self.parent.input_at(at)
|
||||
|
||||
return self.parent.send_text(text)
|
||||
|
||||
def reply(
|
||||
self, text: str,
|
||||
at: Union[List[str], str] = None
|
||||
) -> WxResponse:
|
||||
"""引用消息
|
||||
|
||||
Args:
|
||||
text (str): 回复内容
|
||||
at (List[str], optional): @用户列表
|
||||
timeout (int, optional): 超时时间,单位为秒,若为None则不启用超时设置
|
||||
|
||||
Returns:
|
||||
WxResponse: 调用结果
|
||||
"""
|
||||
if at:
|
||||
self.parent.input_at(at)
|
||||
|
||||
return self.parent.send_text(text)
|
||||
|
||||
def forward(self, targets: Union[List[str], str], timeout: int = 3) -> WxResponse:
|
||||
"""转发消息
|
||||
|
||||
Args:
|
||||
targets (Union[List[str], str]): 目标用户列表
|
||||
timeout (int, optional): 超时时间,单位为秒,若为None则不启用超时设置
|
||||
|
||||
Returns:
|
||||
WxResponse: 调用结果
|
||||
"""
|
||||
if not self.select_option('转发', timeout=timeout):
|
||||
return WxResponse(False, '当前消息无法转发')
|
||||
|
||||
select_wnd = SelectContactWnd(self)
|
||||
return select_wnd.send(targets)
|
||||
73
wxauto/msgs/friend.py
Normal file
73
wxauto/msgs/friend.py
Normal file
@@ -0,0 +1,73 @@
|
||||
from .type import *
|
||||
from .attr import FriendMessage
|
||||
import sys
|
||||
|
||||
class FriendTextMessage(FriendMessage, TextMessage):
|
||||
def __init__(
|
||||
self,
|
||||
control: uia.Control,
|
||||
parent: "ChatBox"
|
||||
):
|
||||
super().__init__(control, parent)
|
||||
|
||||
class FriendQuoteMessage(FriendMessage, QuoteMessage):
|
||||
def __init__(
|
||||
self,
|
||||
control: uia.Control,
|
||||
parent: "ChatBox",
|
||||
):
|
||||
super().__init__(control, parent)
|
||||
|
||||
class FriendImageMessage(FriendMessage, ImageMessage):
|
||||
def __init__(
|
||||
self,
|
||||
control: uia.Control,
|
||||
parent: "ChatBox",
|
||||
|
||||
):
|
||||
super().__init__(control, parent)
|
||||
|
||||
class FriendFileMessage(FriendMessage, FileMessage):
|
||||
def __init__(
|
||||
self,
|
||||
control: uia.Control,
|
||||
parent: "ChatBox",
|
||||
|
||||
):
|
||||
super().__init__(control, parent)
|
||||
|
||||
class FriendLinkMessage(FriendMessage, LinkMessage):
|
||||
def __init__(
|
||||
self,
|
||||
control: uia.Control,
|
||||
parent: "ChatBox",
|
||||
|
||||
):
|
||||
super().__init__(control, parent)
|
||||
|
||||
class FriendVideoMessage(FriendMessage, VideoMessage):
|
||||
def __init__(
|
||||
self,
|
||||
control: uia.Control,
|
||||
parent: "ChatBox",
|
||||
|
||||
):
|
||||
super().__init__(control, parent)
|
||||
|
||||
class FriendVoiceMessage(FriendMessage, VoiceMessage):
|
||||
def __init__(
|
||||
self,
|
||||
control: uia.Control,
|
||||
parent: "ChatBox",
|
||||
|
||||
):
|
||||
super().__init__(control, parent)
|
||||
|
||||
class FriendOtherMessage(FriendMessage, OtherMessage):
|
||||
def __init__(
|
||||
self,
|
||||
control: uia.Control,
|
||||
parent: "ChatBox",
|
||||
|
||||
):
|
||||
super().__init__(control, parent)
|
||||
133
wxauto/msgs/msg.py
Normal file
133
wxauto/msgs/msg.py
Normal file
@@ -0,0 +1,133 @@
|
||||
from .attr import *
|
||||
from .type import OtherMessage
|
||||
from . import self as selfmsg
|
||||
from . import friend as friendmsg
|
||||
from wxauto.languages import *
|
||||
from wxauto.param import WxParam
|
||||
from wxauto import uiautomation as uia
|
||||
from typing import Literal
|
||||
import re
|
||||
|
||||
class MESSAGE_ATTRS:
|
||||
SYS_TEXT_HEIGHT = 33
|
||||
TIME_TEXT_HEIGHT = 34
|
||||
CHAT_TEXT_HEIGHT = 52
|
||||
FILE_MSG_HEIGHT = 115
|
||||
LINK_MSG_HEIGHT = 115
|
||||
VOICE_MSG_HEIGHT = 55
|
||||
|
||||
TEXT_MSG_CONTROL_NUM = (8, 9, 10, 11)
|
||||
TIME_MSG_CONTROL_NUM = (1,)
|
||||
SYS_MSG_CONTROL_NUM = (4,5,6)
|
||||
IMG_MSG_CONTROL_NUM = (9, 10, 11, 12)
|
||||
FILE_MSG_CONTROL_NUM = tuple(i for i in range(15, 30))
|
||||
LINK_MSG_CONTROL_NUM = tuple(i for i in range(15, 30))
|
||||
VOICE_MSG_CONTROL_NUM = tuple(i for i in range(10, 30))
|
||||
VIDEO_MSG_CONTROL_NUM = (13, 14, 15, 16)
|
||||
QUOTE_MSG_CONTROL_NUM = tuple(i for i in range(16, 30))
|
||||
LINK_MSG_CONTROL_NUM = tuple(i for i in range(15, 30))
|
||||
|
||||
def _lang(text: str) -> str:
|
||||
return MESSAGES.get(text, {WxParam.LANGUAGE: text}).get(WxParam.LANGUAGE)
|
||||
|
||||
SEPICIAL_MSGS = [
|
||||
_lang(i)
|
||||
for i in [
|
||||
'[图片]', # ImageMessage
|
||||
'[视频]', # VideoMessage
|
||||
'[语音]', # VoiceMessage
|
||||
'[文件]', # FileMessage
|
||||
'[链接]', # LinkMessage
|
||||
]
|
||||
]
|
||||
|
||||
def parse_msg_attr(
|
||||
control: uia.Control,
|
||||
parent,
|
||||
):
|
||||
msg_rect = control.BoundingRectangle
|
||||
height = msg_rect.height()
|
||||
mid = (msg_rect.left + msg_rect.right) / 2
|
||||
for length, _ in enumerate(uia.WalkControl(control)):length += 1
|
||||
|
||||
# TimeMessage
|
||||
if (
|
||||
length in MESSAGE_ATTRS.TIME_MSG_CONTROL_NUM
|
||||
):
|
||||
return TimeMessage(control, parent)
|
||||
|
||||
# FriendMessage or SelfMessage
|
||||
if (head_control := control.ButtonControl(searchDepth=2)).Exists(0):
|
||||
head_rect = head_control.BoundingRectangle
|
||||
if head_rect.left < mid:
|
||||
return parse_msg_type(control, parent, 'Friend')
|
||||
else:
|
||||
return parse_msg_type(control, parent, 'Self')
|
||||
|
||||
# SystemMessage or TickleMessage
|
||||
else:
|
||||
if length in MESSAGE_ATTRS.SYS_MSG_CONTROL_NUM:
|
||||
return SystemMessage(control, parent)
|
||||
elif control.ListItemControl(RegexName=_lang('re_拍一拍')).Exists(0):
|
||||
return TickleMessage(control, parent)
|
||||
else:
|
||||
return OtherMessage(control, parent)
|
||||
|
||||
def parse_msg_type(
|
||||
control: uia.Control,
|
||||
parent,
|
||||
attr: Literal['Self', 'Friend']
|
||||
):
|
||||
for length, _ in enumerate(uia.WalkControl(control)):length += 1
|
||||
content = control.Name
|
||||
wxlog.debug(f'content: {content}, length: {length}')
|
||||
|
||||
if attr == 'Friend':
|
||||
msgtype = friendmsg
|
||||
else:
|
||||
msgtype = selfmsg
|
||||
|
||||
# Special Message Type
|
||||
if content in SEPICIAL_MSGS:
|
||||
# ImageMessage
|
||||
if content == _lang('[图片]') and length in MESSAGE_ATTRS.IMG_MSG_CONTROL_NUM:
|
||||
return getattr(msgtype, f'{attr}ImageMessage')(control, parent)
|
||||
|
||||
# VideoMessage
|
||||
elif content == _lang('[视频]') and length in MESSAGE_ATTRS.VIDEO_MSG_CONTROL_NUM:
|
||||
return getattr(msgtype, f'{attr}VideoMessage')(control, parent)
|
||||
|
||||
# FileMessage
|
||||
elif content == _lang('[文件]') and length in MESSAGE_ATTRS.FILE_MSG_CONTROL_NUM:
|
||||
return getattr(msgtype, f'{attr}FileMessage')(control, parent)
|
||||
|
||||
# LinkMessage
|
||||
elif content == _lang('[链接]') and length in MESSAGE_ATTRS.LINK_MSG_CONTROL_NUM:
|
||||
return getattr(msgtype, f'{attr}LinkMessage')(control, parent)
|
||||
|
||||
# TextMessage
|
||||
if length in MESSAGE_ATTRS.TEXT_MSG_CONTROL_NUM:
|
||||
return getattr(msgtype, f'{attr}TextMessage')(control, parent)
|
||||
|
||||
# QuoteMessage
|
||||
elif (
|
||||
rematch := re.compile(_lang('re_引用消息'), re.DOTALL).match(content)
|
||||
and length in MESSAGE_ATTRS.QUOTE_MSG_CONTROL_NUM
|
||||
):
|
||||
return getattr(msgtype, f'{attr}QuoteMessage')(control, parent)
|
||||
|
||||
# VoiceMessage
|
||||
elif (
|
||||
rematch := re.compile(_lang('re_语音')).match(content)
|
||||
and length in MESSAGE_ATTRS.VOICE_MSG_CONTROL_NUM
|
||||
):
|
||||
return getattr(msgtype, f'{attr}VoiceMessage')(control, parent)
|
||||
|
||||
return getattr(msgtype, f'{attr}OtherMessage')(control, parent)
|
||||
|
||||
def parse_msg(
|
||||
control: uia.Control,
|
||||
parent
|
||||
):
|
||||
result = parse_msg_attr(control, parent)
|
||||
return result
|
||||
67
wxauto/msgs/self.py
Normal file
67
wxauto/msgs/self.py
Normal file
@@ -0,0 +1,67 @@
|
||||
from .type import *
|
||||
from .attr import SelfMessage
|
||||
import sys
|
||||
|
||||
class SelfTextMessage(SelfMessage, TextMessage):
|
||||
def __init__(
|
||||
self,
|
||||
control: uia.Control,
|
||||
parent: "ChatBox"
|
||||
):
|
||||
super().__init__(control, parent)
|
||||
|
||||
class SelfQuoteMessage(SelfMessage, QuoteMessage):
|
||||
def __init__(
|
||||
self,
|
||||
control: uia.Control,
|
||||
parent: "ChatBox",
|
||||
):
|
||||
super().__init__(control, parent)
|
||||
|
||||
class SelfImageMessage(SelfMessage, ImageMessage):
|
||||
def __init__(
|
||||
self,
|
||||
control: uia.Control,
|
||||
parent: "ChatBox"
|
||||
):
|
||||
super().__init__(control, parent)
|
||||
|
||||
class SelfFileMessage(SelfMessage, FileMessage):
|
||||
def __init__(
|
||||
self,
|
||||
control: uia.Control,
|
||||
parent: "ChatBox"
|
||||
):
|
||||
super().__init__(control, parent)
|
||||
|
||||
class SelfLinkMessage(SelfMessage, LinkMessage):
|
||||
def __init__(
|
||||
self,
|
||||
control: uia.Control,
|
||||
parent: "ChatBox"
|
||||
):
|
||||
super().__init__(control, parent)
|
||||
|
||||
class SelfVideoMessage(SelfMessage, VideoMessage):
|
||||
def __init__(
|
||||
self,
|
||||
control: uia.Control,
|
||||
parent: "ChatBox"
|
||||
):
|
||||
super().__init__(control, parent)
|
||||
|
||||
class SelfVoiceMessage(SelfMessage, VoiceMessage):
|
||||
def __init__(
|
||||
self,
|
||||
control: uia.Control,
|
||||
parent: "ChatBox"
|
||||
):
|
||||
super().__init__(control, parent)
|
||||
|
||||
class SelfOtherMessage(SelfMessage, OtherMessage):
|
||||
def __init__(
|
||||
self,
|
||||
control: uia.Control,
|
||||
parent: "ChatBox"
|
||||
):
|
||||
super().__init__(control, parent)
|
||||
222
wxauto/msgs/type.py
Normal file
222
wxauto/msgs/type.py
Normal file
@@ -0,0 +1,222 @@
|
||||
from wxauto.utils.tools import (
|
||||
get_file_dir,
|
||||
)
|
||||
from wxauto.ui.component import (
|
||||
CMenuWnd,
|
||||
WeChatImage,
|
||||
WeChatBrowser,
|
||||
)
|
||||
from wxauto.utils.win32 import (
|
||||
ReadClipboardData,
|
||||
SetClipboardText,
|
||||
)
|
||||
from .base import *
|
||||
from typing import (
|
||||
Union,
|
||||
)
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
import re
|
||||
|
||||
class TextMessage(HumanMessage):
|
||||
type = 'text'
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
control: uia.Control,
|
||||
parent: "ChatBox"
|
||||
):
|
||||
super().__init__(control, parent)
|
||||
|
||||
class QuoteMessage(HumanMessage):
|
||||
type = 'quote'
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
control: uia.Control,
|
||||
parent: "ChatBox",
|
||||
):
|
||||
super().__init__(control, parent)
|
||||
self.content, self.quote_content = \
|
||||
re.findall(self._lang('re_引用消息'), self.content, re.DOTALL)[0]
|
||||
|
||||
class MediaMessage:
|
||||
|
||||
def download(
|
||||
self,
|
||||
dir_path: Union[str, Path] = None,
|
||||
timeout: int = 10
|
||||
) -> Path:
|
||||
if dir_path is None:
|
||||
dir_path = WxParam.DEFAULT_SAVE_PATH
|
||||
if self.type == 'image':
|
||||
filename = f"wxauto_{self.type}_{time.strftime('%Y%m%d%H%M%S')}.png"
|
||||
elif self.type == 'video':
|
||||
filename = f"wxauto_{self.type}_{time.strftime('%Y%m%d%H%M%S')}.mp4"
|
||||
filepath = get_file_dir(dir_path) / filename
|
||||
|
||||
self.click()
|
||||
|
||||
t0 = time.time()
|
||||
while True:
|
||||
self.right_click()
|
||||
menu = CMenuWnd(self)
|
||||
if menu and menu.select('复制'):
|
||||
try:
|
||||
clipboard_data = ReadClipboardData()
|
||||
cpath = clipboard_data['15'][0]
|
||||
break
|
||||
except:
|
||||
pass
|
||||
else:
|
||||
menu.close()
|
||||
if time.time() - t0 > timeout:
|
||||
return WxResponse.failure(f'下载超时: {self.type}')
|
||||
time.sleep(0.1)
|
||||
|
||||
shutil.copyfile(cpath, filepath)
|
||||
SetClipboardText('')
|
||||
if imagewnd := WeChatImage():
|
||||
imagewnd.close()
|
||||
return filepath
|
||||
|
||||
class ImageMessage(HumanMessage, MediaMessage):
|
||||
type = 'image'
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
control: uia.Control,
|
||||
parent: "ChatBox"
|
||||
):
|
||||
super().__init__(control, parent)
|
||||
|
||||
class VideoMessage(HumanMessage, MediaMessage):
|
||||
type = 'video'
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
control: uia.Control,
|
||||
parent: "ChatBox"
|
||||
):
|
||||
super().__init__(control, parent)
|
||||
|
||||
class VoiceMessage(HumanMessage):
|
||||
type = 'voice'
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
control: uia.Control,
|
||||
parent: "ChatBox"
|
||||
):
|
||||
super().__init__(control, parent)
|
||||
|
||||
def to_text(self):
|
||||
"""语音转文字"""
|
||||
if self.control.GetProgenyControl(8, 4):
|
||||
return self.control.GetProgenyControl(8, 4).Name
|
||||
voicecontrol = self.control.ButtonControl(Name='')
|
||||
if not voicecontrol.Exists(0.5):
|
||||
return WxResponse.failure('语音转文字失败')
|
||||
self.right_click()
|
||||
menu = CMenuWnd(self.parent)
|
||||
menu.select('语音转文字')
|
||||
|
||||
text = ''
|
||||
while True:
|
||||
if not self.control.Exists(0):
|
||||
return WxResponse.failure('消息已撤回')
|
||||
text_control = self.control.GetProgenyControl(8, 4)
|
||||
if text_control is not None:
|
||||
if text_control.Name == text:
|
||||
return text
|
||||
text = text_control.Name
|
||||
time.sleep(0.1)
|
||||
|
||||
class LinkMessage(HumanMessage):
|
||||
type = 'link'
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
control: uia.Control,
|
||||
parent: "ChatBox"
|
||||
):
|
||||
super().__init__(control, parent)
|
||||
|
||||
def get_url(self) -> str:
|
||||
self.click()
|
||||
if webbrower := WeChatBrowser():
|
||||
url = webbrower.get_url()
|
||||
webbrower.close()
|
||||
return url
|
||||
else:
|
||||
wxlog.debug(f'找不到浏览器窗口')
|
||||
|
||||
return None
|
||||
|
||||
class FileMessage(HumanMessage):
|
||||
type = 'file'
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
control: uia.Control,
|
||||
parent: "ChatBox"
|
||||
):
|
||||
super().__init__(control, parent)
|
||||
#self.filename = control.TextControl().Name
|
||||
self.filename = control.GetProgenyControl(9, control_type='TextControl').Name
|
||||
self.filesize = control.GetProgenyControl(10, control_type='TextControl').Name
|
||||
|
||||
def download(
|
||||
self,
|
||||
dir_path: Union[str, Path] = None,
|
||||
force_click: bool = False,
|
||||
timeout: int = 10
|
||||
) -> Path:
|
||||
"""下载文件"""
|
||||
if dir_path is None:
|
||||
dir_path = WxParam.DEFAULT_SAVE_PATH
|
||||
filepath = get_file_dir(dir_path) / self.filename
|
||||
t0 = time.time()
|
||||
def open_file_menu():
|
||||
while not (menu := CMenuWnd(self.parent)):
|
||||
self.roll_into_view()
|
||||
self.right_click()
|
||||
return menu
|
||||
if force_click:
|
||||
self.click()
|
||||
while True:
|
||||
if time.time() - t0 > timeout:
|
||||
return WxResponse.failure("文件下载超时")
|
||||
try:
|
||||
if self.control.TextControl(Name=self._lang('接收中')).Exists(0):
|
||||
time.sleep(0.1)
|
||||
continue
|
||||
menu = open_file_menu()
|
||||
if (option := self._lang('复制')) in menu.option_names:
|
||||
menu.select(option)
|
||||
temp_filepath = Path(ReadClipboardData().get('15')[0])
|
||||
break
|
||||
except:
|
||||
time.sleep(0.1)
|
||||
|
||||
t0 = time.time()
|
||||
while True:
|
||||
if time.time() - t0 > 2:
|
||||
return WxResponse.failure("文件下载超时")
|
||||
try:
|
||||
shutil.copyfile(temp_filepath, filepath)
|
||||
SetClipboardText('')
|
||||
return filepath
|
||||
except:
|
||||
time.sleep(0.01)
|
||||
|
||||
class OtherMessage(BaseMessage):
|
||||
type = 'other'
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
control: uia.Control,
|
||||
parent: "ChatBox",
|
||||
):
|
||||
super().__init__(control, parent)
|
||||
self.url = control.TextControl().Name
|
||||
Reference in New Issue
Block a user