Files

306 lines
9.9 KiB
Python
Raw Permalink 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 wechatauto import uia
from wechatauto.utils.win32 import (
FindWindow,
GetAllWindows,
SetClipboardText,
ReadClipboardData
)
from wechatauto.utils.tools import (
find_window_from_root,
find_all_windows_from_root,
is_valid_image,
get_file_dir,
now_time,
)
from .base import BaseUISubWnd
from wechatauto.param import WxParam, WxResponse
from wechatauto.logger import wxlog
from pathlib import Path
from typing import (
List,
Literal
)
import traceback
import shutil
import time
import os
def _find_popup(ui_cls_name: str, win_cls_name: str, win_name: str = None, pid: int = None, timeout: float = 2):
"""查找微信弹窗/浮层窗口。
优先按 UIA 类名匹配(不受 Qt 版本影响),失败时回退到 win32 窗口类名匹配。
"""
t0 = time.time()
while True:
if time.time() - t0 > timeout:
break
wins = find_all_windows_from_root(pid=pid, uiaclsname=ui_cls_name)
if len(wins) > 0:
return wins[0]
wins = find_all_windows_from_root(classname=win_cls_name, name=win_name, pid=pid)
if len(wins) > 0:
return wins[0]
time.sleep(0.05)
return None
class UpdateWindow(BaseUISubWnd):
"""微信更新提示窗口"""
_ui_cls_name: str = "mmui::XView"
_win_cls_name: str = "Qt51514QWindowIcon"
_win_name: str = "微信"
def __init__(self):
wins = GetAllWindows(name=self._win_name, classname=self._win_cls_name)
for win in wins:
self.control = uia.ControlFromHandle(win[0])
if (
(text := self.control.TextControl()).Exists(0)
and text.Name == '新版本'
):
break
def ignore(self):
ignore_btn = self.control.ButtonControl(
ClassName="mmui::XOutlineButton",
Name="忽略本次更新"
)
if ignore_btn.Exists(0):
ignore_btn.Click()
class Menu(BaseUISubWnd):
"""右键菜单窗口"""
_ui_cls_name: str = "mmui::XMenu"
_win_cls_name: str = "Qt51514QWindowToolSaveBits"
_win_name: str = "Weixin"
def __init__(self, parent, timeout=2):
self.parent = parent
self.root = parent.root
self.control = _find_popup(
ui_cls_name=self._ui_cls_name,
win_cls_name=self._win_cls_name,
win_name=self._win_name,
pid=self.root.pid,
timeout=timeout,
)
@property
def option_controls(self):
if not self.control:
return []
return [
i for i in
self.control.GetChildren()
if i.ControlTypeName == 'MenuItemControl'
]
@property
def option_names(self):
return [c.Name for c in self.option_controls]
def select(self, item):
if not self.control or not self.exists(0):
return WxResponse.failure('菜单窗口不存在')
if isinstance(item, int):
options = self.option_controls
if item >= len(options):
return WxResponse.failure(f'菜单选项越界:{item}')
options[item].Click()
return WxResponse.success()
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}')
class SelectContactWnd(BaseUISubWnd):
"""转发消息时的联系人选择窗口"""
_ui_cls_name: str = "mmui::SessionPickerWindow"
_ui_name: str = "微信发送给"
_win_cls_name: str = "Qt51514QWindowIcon"
_win_name: str = "微信发送给"
def __init__(self, parent, timeout=2):
self.parent = parent
self.root = parent.root
self.control = _find_popup(
ui_cls_name=self._ui_cls_name,
win_cls_name=self._win_cls_name,
win_name=self._win_name,
pid=self.root.pid,
timeout=timeout,
)
if self.control:
self.confirm_btn = self.control.ButtonControl(AutomationId="confirm_btn")
def search(self, keyword, interval=0.1):
"""搜索并选择,需完全匹配"""
search_control = self.control.EditControl(ClassName="mmui::XValidatorTextEdit")
SetClipboardText(keyword)
search_control.Click()
search_control.SendKeys('{Ctrl}a')
search_control.RightClick()
menu = Menu(self)
menu.select('粘贴')
time.sleep(interval)
all_controls = []
for c, d in uia.WalkControl(self.control):
all_controls.append(c)
for target in all_controls:
if (
target.ControlTypeName == 'CheckBoxControl'
and target.Name == keyword
):
target.Click()
return True
def confirm(self):
self.confirm_btn.Click()
def send(self, target, interval=0.1):
if isinstance(target, str):
target = [target]
for i in target:
self.search(i, interval)
self.confirm()
class SearchNewFriendWnd(BaseUISubWnd):
"""添加朋友窗口"""
_win_cls_name: str = 'Qt51514QWindowIcon'
_win_name: str = "添加朋友"
def __init__(self):
self.control = find_window_from_root(classname=self._win_cls_name, name=self._win_name)
if self.control:
self.init()
def init(self):
self.apply_btn = self.control.ButtonControl(
AutomationId="fixed_height_v_view.content_v_view.ContactProfileBottomUi.add_friend_button",
Name="添加到通讯录",
ClassName="mmui::XOutlineButton",
searchDepth=9
)
self.search_edit = self.control.EditControl(ClassName="mmui::XValidatorTextEdit", Name="搜索")
self.search_btn = self.control.ButtonControl(ClassName="mmui::XOutlineButton", Name="搜索")
def search(self, keyword):
self.search_edit.SendKeys('{Ctrl}a')
SetClipboardText(keyword)
self.search_edit.SendKeys('{Ctrl}v')
self.search_btn.Click()
def apply(self):
if self.apply_btn.Exists(0):
self.apply_btn.Click()
return WxResponse.success()
else:
return WxResponse.failure('未找到添加按钮')
class WeChatImage(BaseUISubWnd):
"""图片/视频预览窗口"""
_win_cls_name: str = 'Qt51514QWindowIcon'
_win_name: str = "预览"
def __init__(self, parent):
self.parent = parent
self.root = self.parent.root
self.control = find_window_from_root(classname=self._win_cls_name, name=self._win_name)
if self.control:
self.init()
def init(self):
toolbar_control = self.control.GroupControl(ClassName="mmui::PreviewToolbarView")
self.tools = {
btn.Name: btn for ele in toolbar_control.GetChildren()
if (btn := ele.ButtonControl()).Exists(0)
}
if self.control.WindowControl(ClassName="mmui::XPlayerControlView").Exists(0):
self.type = 'video'
else:
self.type = 'image'
def save(self, dir_path=None, timeout=10) -> Path:
"""保存图片/视频
Args:
dir_path (str): 保存文件夹路径
timeout (int, optional): 保存超时时间,默认10秒
Returns:
Path: 文件保存路径,即savepath
"""
image_sufix = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'pic']
if dir_path is None:
dir_path = WxParam.DEFAULT_SAVE_PATH
t0 = time.time()
n = 1
SetClipboardText('')
while True:
if time.time() - t0 > timeout:
if self.control.Exists(0):
self.control.SendKeys('{Esc}')
return WxResponse.failure('下载超时')
if self.control.TextControl(Name="图片过期或已被清理").Exists(0):
return WxResponse.failure('图片过期或已被清理')
try:
self.tools['更多'].Click()
menu = Menu(self.root)
menu.select('复制')
if self.type == 'video':
for _ in range(30):
clipboard_data = ReadClipboardData()
wxlog.debug(f"读取到剪贴板数据:{clipboard_data.keys()}")
if '15' not in clipboard_data:
time.sleep(0.1)
continue
path = clipboard_data['15'][0]
break
else:
clipboard_data = ReadClipboardData()
path = clipboard_data['15'][0]
if not os.path.exists(path):
return WxResponse.failure('微信BUG无法获取该图片,请重新获取')
suffix = os.path.splitext(path)[1]
if (
suffix in image_sufix
and not is_valid_image(path)
) or not os.path.getsize(path):
wxlog.debug("图片格式不正确,删除文件")
os.remove(path)
continue
wxlog.debug(f"读取到图片/视频路径[{os.path.exists(path)}, {os.path.getsize(path)}]{path}")
break
except Exception:
if n > 3:
return WxResponse.failure('微信BUG无法获取该图片,请重新获取')
n += 1
wxlog.debug(traceback.format_exc())
time.sleep(0.1)
filename = f"wechatauto_{self.type}_{now_time()}{suffix}"
filepath = get_file_dir(dir_path) / filename
wxlog.debug(f"保存到文件:{filepath}")
shutil.copyfile(path, filepath)
SetClipboardText('')
if self.control.Exists(0):
wxlog.debug("关闭图片窗口")
self.control.SendKeys('{Esc}')
return filepath