277 lines
8.0 KiB
Python
277 lines
8.0 KiB
Python
from __future__ import annotations
|
|
from wechatauto import uia
|
|
from wechatauto.param import (
|
|
WxParam,
|
|
WxResponse,
|
|
)
|
|
from wechatauto.languages import MENU_OPTIONS, get_lang
|
|
from wechatauto.ui.component import Menu
|
|
from wechatauto.utils.win32 import SetClipboardText
|
|
from wechatauto.logger import wxlog
|
|
import time
|
|
from typing import (
|
|
Union,
|
|
List
|
|
)
|
|
import re
|
|
|
|
|
|
class SessionBox:
|
|
"""会话列表区域"""
|
|
|
|
def __init__(self, control, parent):
|
|
self.control: uia.Control = control
|
|
self.root = parent.root
|
|
self.parent = parent
|
|
self.init()
|
|
|
|
def init(self):
|
|
self.searchbox = self.control.GroupControl(ClassName="mmui::XSearchField").EditControl()
|
|
self.session_list = self.control.GroupControl(ClassName="mmui::ChatSessionList").\
|
|
ListControl(ClassName="mmui::XTableView", Name="会话")
|
|
self.search_content = self.parent.control.WindowControl(ClassName="mmui::SearchContentPopover")
|
|
|
|
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 get_session(self) -> List['SessionElement']:
|
|
if self.session_list.Exists(0):
|
|
return [SessionElement(i, self) for i in self.session_list.GetChildren()]
|
|
else:
|
|
return []
|
|
|
|
def search(
|
|
self,
|
|
keywords: str,
|
|
force: bool = False,
|
|
force_wait: Union[float, int] = 0.5
|
|
):
|
|
self.searchbox.RightClick()
|
|
SetClipboardText(keywords)
|
|
menu = Menu(self)
|
|
menu.select('粘贴')
|
|
self.searchbox.MiddleClick()
|
|
|
|
search_result = self.search_content.ListControl()
|
|
|
|
if force:
|
|
time.sleep(force_wait)
|
|
|
|
return [SearchResultElement(i) for i in search_result.GetChildren()]
|
|
|
|
def switch_chat(
|
|
self,
|
|
keywords: str,
|
|
exact: bool = True,
|
|
force: bool = False,
|
|
force_wait: Union[float, int] = 0.5
|
|
):
|
|
wxlog.debug(f"切换聊天窗口: {keywords}, {exact}, {force}, {force_wait}")
|
|
search_box = self.search_content.ListControl()
|
|
search_result = self.search(keywords, force, force_wait)
|
|
t0 = time.time()
|
|
while time.time() - t0 < WxParam.SEARCH_CHAT_TIMEOUT:
|
|
results = []
|
|
search_result_items = search_box.GetChildren()
|
|
for search_result_item in search_result_items:
|
|
text: str = search_result_item.Name
|
|
if exact:
|
|
if text == keywords:
|
|
search_result_item.Click()
|
|
return keywords
|
|
elif (
|
|
' 微信号: ' in text
|
|
and (split := text.split(' 微信号: '))[-1].lower() == keywords.lower()
|
|
):
|
|
search_result_item.Click()
|
|
return split[0]
|
|
elif (
|
|
' 昵称: ' in text
|
|
and (split := text.split(' 昵称: '))[-1].lower() == keywords.lower()
|
|
):
|
|
search_result_item.Click()
|
|
return split[0]
|
|
else:
|
|
if keywords in text:
|
|
search_result_item.Click()
|
|
return text
|
|
|
|
if self.search_content.Exists(0):
|
|
self.control.MiddleClick()
|
|
|
|
def open_separate_window(self, name: str):
|
|
wxlog.debug(f"打开独立窗口: {name}")
|
|
realname = self.switch_chat(name)
|
|
if not realname:
|
|
return WxResponse.failure('未找到会话')
|
|
time.sleep(0.3)
|
|
while True:
|
|
session = [i for i in self.get_session() if uia.IsElementInWindow(self.session_list, i.control)][0]
|
|
if session.content.startswith(realname):
|
|
break
|
|
session.double_click()
|
|
return WxResponse.success(data={'nickname': realname})
|
|
|
|
def go_top(self):
|
|
wxlog.debug("回到会话列表顶部")
|
|
self.control.MiddleClick()
|
|
self.control.SendKeys('{Home}')
|
|
|
|
def go_bottom(self):
|
|
wxlog.debug("回到会话列表底部")
|
|
self.control.MiddleClick()
|
|
self.control.SendKeys('{End}')
|
|
|
|
|
|
class SessionElement:
|
|
"""会话列表中的单条会话"""
|
|
|
|
def __init__(
|
|
self,
|
|
control: uia.Control,
|
|
parent: SessionBox,
|
|
):
|
|
self.root = parent.root
|
|
self.parent = parent
|
|
self.control = control
|
|
self.content = control.Name
|
|
|
|
@property
|
|
def texts(self) -> List[str]:
|
|
"""拆分当前会话控件中的文本行"""
|
|
|
|
return [
|
|
line for line in str(self.content).split('\n')
|
|
if line and line.strip()
|
|
]
|
|
|
|
@property
|
|
def name(self) -> str:
|
|
"""会话名称"""
|
|
|
|
if self.texts:
|
|
return self.texts[0]
|
|
return ''
|
|
|
|
@property
|
|
def unread_count(self) -> int:
|
|
"""未读消息数量"""
|
|
|
|
unread_pattern = re.compile(r'\[(\d+)条\]')
|
|
for text in self.texts:
|
|
if match := unread_pattern.search(text):
|
|
return int(match.group(1))
|
|
return 0
|
|
|
|
def _menu_option_text(self, option_key: str) -> str:
|
|
return get_lang(MENU_OPTIONS, option_key)
|
|
|
|
def select_menu_option(self, option_key: str, wait=0.3):
|
|
"""根据配置语言选择菜单项"""
|
|
|
|
option_text = self._menu_option_text(option_key)
|
|
return self.select_option(option_text, wait)
|
|
|
|
def __repr__(self):
|
|
content = str(self.content).replace('\n', ' ')
|
|
if len(content) > 5:
|
|
content = content[:5] + '...'
|
|
return f"<wechatauto Session Element({content})>"
|
|
|
|
def roll_into_view(self):
|
|
uia.RollIntoView(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()
|
|
self._click(double=True)
|
|
|
|
def select_option(self, option: str, wait=0.3):
|
|
self.roll_into_view()
|
|
self.control.RightClick()
|
|
time.sleep(wait)
|
|
menu = Menu(self.parent)
|
|
return menu.select(option)
|
|
|
|
def pin(self):
|
|
"""置顶聊天"""
|
|
|
|
return self.select_menu_option('置顶')
|
|
|
|
def unpin(self):
|
|
"""取消置顶聊天"""
|
|
|
|
return self.select_menu_option('取消置顶')
|
|
|
|
def mark_unread(self):
|
|
"""标记为未读"""
|
|
|
|
return self.select_menu_option('标为未读')
|
|
|
|
def toggle_mute(self):
|
|
"""切换消息免打扰状态"""
|
|
|
|
return self.select_menu_option('消息免打扰')
|
|
|
|
def open_in_separate_window(self):
|
|
"""在独立窗口中打开会话"""
|
|
|
|
return self.select_menu_option('在独立窗口打开')
|
|
|
|
def hide(self):
|
|
"""不显示聊天"""
|
|
|
|
return self.select_menu_option('不显示聊天')
|
|
|
|
def delete(self):
|
|
"""删除聊天"""
|
|
|
|
return self.select_menu_option('删除聊天')
|
|
|
|
|
|
class SearchResultElement:
|
|
"""搜索结果中的单条结果"""
|
|
|
|
def __init__(self, control):
|
|
self.control = control
|
|
self.content = control.Name
|
|
self.type = control.ClassName
|
|
|
|
def __repr__(self):
|
|
content = str(self.content).replace('\n', ' ')
|
|
if len(content) > 5:
|
|
content = content[:5] + '...'
|
|
return f"<wechatauto Search Element({content})>"
|
|
|
|
def get_all_text(self):
|
|
return [
|
|
line for line in str(self.content).split('\n')
|
|
if line and line.strip()
|
|
]
|
|
|
|
def click(self):
|
|
uia.RollIntoView(self.control.GetParentControl(), self.control)
|
|
self.control.Click()
|
|
|
|
def close(self):
|
|
self.control.SendKeys('{Esc}')
|