pip install uiautomation
pip install dataset
import logging
import random
import re
import sys
import time
import dataset
import uiautomation as auto
from uiautomation import WindowControl
# 这里要构建一个全局锁,因为同一时刻内,桌面只能被一个线程锁操纵。防止第一个操作未完成就开始了第二个动作,造成混乱。
LOCK = 0
class WxChat:
"""
微信控制类
"""
def __init__(self):
"""
初始化窗口
"""
logging.basicConfig(level=logging.DEBUG)
super().__init__()
auto.uiautomation.DEBUG_SEARCH_TIME = True
auto.uiautomation.SetGlobalSearchTimeout(2) # 设置全局搜索超时时间
# 微信窗口句柄
self.wxWindow = auto.WindowControl(searchDepth=1, ClassName='WeChatMainWndForPC',
Name='微信', desc='微信窗口')
if not self.wxWindow.Exists(0, 0):
self.wxWindow = auto.WindowControl(
searchDepth=1, ClassName='WeChatMainWndForPC',
Name='微信', desc='微信窗口')
if not self.wxWindow.Exists(0, 0):
logging.error("微信窗口未激活!,请去登录微信,并将窗口打开")
sys.exit()
self.wxWindow.SetActive()
logging.info("激活窗口")
self.wxWindow.SetTopmost(True)
logging.info("设置为顶层")
self.SessionList = self.wxWindow.ListControl(Name='会话')
self.EditMsg = self.wxWindow.EditControl(Name='输入')
self.SearchBox = self.wxWindow.EditControl(Name='搜索')
self.msg_list = self.wxWindow.ListControl(Name='消息')
def send_message(self, full_name, message, chat_type='联系人') -> int:
"""
搜索好友或群聊->打开聊天窗口->发送消息
:param full_name: 好友全名(原微信昵称或者备注昵称都可以,但是最好要唯一,保证目标在搜索结果的第一位)
:param message: 消息
:param chat_type: 联系人、群聊
:return: 1发送成功 2发送失败 3被删除好友 4已被拉黑
"""
# 点击搜索框
self.wxWindow.EditControl(Name="搜索").Click(waitTime=0.5)
# 清空输入框
self.wxWindow.ButtonControl(Name="清空", desc='点击清空输入').Click(waitTime=0.5)
# 清空后会失去输入焦点,这里再次点击搜索框
self.wxWindow.EditControl(Name="搜索").Click(waitTime=0.5) # 点击搜索框
self.wxWindow.SendKeys(full_name) # 输入内容
# 定位到联系人标签
text_control = self.wxWindow.TextControl(Name=chat_type)
rect = text_control.BoundingRectangle
# 计算出稍微下面一点的坐标
x = rect.left + (rect.right - rect.left) / 2
y = rect.bottom + 10 # 20是一个例子,你可以根据实际情况调整
# 使用 Click() 方法点击新的坐标位置,即:搜索到的第一个好友的位置
auto.Click(x=int(x), y=y, waitTime=1)
# 发送消息
self.wxWindow.SendKeys(message, waitTime=1) # 输入内容
# self.wxWindow.ButtonControl(Name="发送(S)").Click(waitTime=get_random_second()) # 点击发送
auto.PressKey(auto.Keys.VK_ENTER, waitTime=0.4)
# 这里要监听一下,看看是不是已经被拉黑或者删除 TODO
# 界面恢复到原始状态
for i in range(5):
self.wxWindow.ButtonControl(Name="聊天").Click(waitTime=WxChat.get_random_second())
return 1
def add_friend_from_group_chat(self, group_full_name: str, serial_numbers: list[int],
greeting: str = "我通过了你的朋友验证请求,现在我们可以开始聊天了"):
"""
群聊添加好友
:param group_full_name: 群聊名称
:param serial_numbers: 要添加的好友序号
:param greeting: 问候语
:return:
"""
self.wxWindow.EditControl(Name="搜索").Click(waitTime=WxChat.get_random_second()) # 点击搜索框
# 清空输入框
clear_button = self.wxWindow.ButtonControl(Name="清空", desc='点击清空输入')
if clear_button.Exists(0, 0):
clear_button.Click(waitTime=WxChat.get_random_second())
else:
self.wxWindow.ButtonControl(Name="清空", desc='点击清空输入').Click(waitTime=WxChat.get_random_second())
# 清空后会失去输入焦点,这里再次点击搜索框
self.wxWindow.EditControl(Name="搜索").Click(waitTime=WxChat.get_random_second()) # 点击搜索框
self.wxWindow.SendKeys(group_full_name) # 输入内容
time.sleep(0.5)
# 定位到联系人标签
text_control = self.wxWindow.TextControl(Name="群聊")
rect = text_control.BoundingRectangle
# 计算出稍微下面一点的坐标
x = rect.left + (rect.right - rect.left) / 2
y = rect.bottom + 10 # 20是一个例子,你可以根据实际情况调整
# 使用 Click() 方法点击新的坐标位置,即:搜索到的第一个好友的位置
auto.Click(x=int(x), y=y)
# 打开群聊详情
self.wxWindow.ButtonControl(Name="聊天信息", desc='点击...打开群聊详情').Click(waitTime=0.5)
# self.wxWindow.ButtonControl(Name="查看更多", desc='展开群成员列表').Click(waitTime=0.5)
# 定位到搜索框
edit_control = self.wxWindow.EditControl(Name="搜索群成员", desc='定位到成员搜索框')
rect = edit_control.BoundingRectangle
# 计算出群主的坐标位置,成员格子中每个格子大小是34*34,每个格子横向间隔54,竖向间隔69
x_leader = rect.left - 20 + 17
y_leader = rect.bottom + 49
# auto.Click(x=int(x_leader), y=y_leader)
# 获取群成员数量
control = self.wxWindow.TextControl(RegexName=f'^{group_full_name} \\(.*\\)', desc='点击清空输入')
# control.Click(waitTime=0.01)
findall = re.findall(r'[(](.*?)[)]', control.Name)
group_number = int(findall[0])
logging.info(f"群成员数量({group_full_name}):{group_number}")
# 边上滑动条的位置点位
side_position_x, side_position_y = x_leader + 3 * 54 + 40, y_leader
# 初始化位置类
self.groupGrid = GroupGrid(group_number=group_number, x_leader=x_leader, y_leader=y_leader)
# 添加好友
for serial_number in serial_numbers:
# 点击几次输入框确保收缩详情
for i in range(5):
self.wxWindow.EditControl(Name=group_full_name, LocalizedControlType="编辑", desc='点击输入框').Click(
waitTime=0.01)
self.wxWindow.ButtonControl(Name="聊天信息", desc='点击...打开群聊详情').Click(waitTime=0.5)
self.wxWindow.ButtonControl(Name="查看更多", desc='展开群成员列表').Click(waitTime=0.5)
time.sleep(2)
b, p = self.groupGrid.get_position(serial_number) # 计算出下滑次数和点击的位置坐标
print(f"下滑次数{b}")
# 下滑b次
for i in range(b):
time.sleep(1)
auto.DragDrop(side_position_x, side_position_y, side_position_x, side_position_y + 45, moveSpeed=1)
side_position_y += 45
print(f"下滑{i + 1}次")
# 点击位置
auto.Click(x=p[0], y=p[1])
# 添加好友
button_control = self.wxWindow.ButtonControl(Name="添加到通讯录", desc='点击添加到通讯录')
# 如果有这个按钮
if not button_control.Exists(0, 0):
print("未发现按钮:添加到通讯录")
continue
button_control.Click(waitTime=1)
dialog: WindowControl = auto.WindowControl(Name='添加朋友请求', desc='添加朋友请求')
if not dialog.Exists(0, 0):
print("未检测到对话框")
continue
# 写入好友申请语:第一个编辑框
edit_control = dialog.EditControl()
# 点击聚焦
edit_control.Click(waitTime=0.1)
# Ctrl+A 全选
edit_control.SendKeys('{Ctrl}a', waitTime=WxChat.get_random_second())
# 输入问候语
edit_control.SendKeys(greeting)
# 确定
dialog.ButtonControl(Name="确定").Click(waitTime=WxChat.get_random_second())
# 界面恢复到原始状态
for i in range(5):
self.wxWindow.ButtonControl(Name="聊天").Click(waitTime=WxChat.get_random_second())
def message_listening(self, chat_type, full_name):
"""
监听指定聊天:原理是定时打开聊天框进行判断最后一条消息是不是对方发的。
:param chat_type: 联系人、群聊
:param full_name: 好友全名(原微信昵称或者备注昵称都可以,但是最好要唯一,保证目标在搜索结果的第一位)
:return:
"""
pass
def friend_likes(self, limit: int):
"""
朋友圈点赞
:param limit: 点赞最新多少条。
:return:
"""
pass
def send_material_to_chat(self, names: list[str], material_path_pic: str, material_content: str):
"""
发送素材到指定人、群聊
:param names: (好友/群聊)名称(全名且不可重复)
:param material_path_pic: 图片地址
:param material_content: 文字内容
:return:
"""
pass
def add_friend_by_account(self, account):
"""
根据账号添加好友,
:param account: 账号
:return:
"""
self.wxWindow.ButtonControl(Name="通讯录", desc='点击通讯录').Click(waitTime=0.5)
self.wxWindow.ButtonControl(Name="添加朋友", desc='点击添加朋友').Click(waitTime=0.5)
edit_control = self.wxWindow.EditControl(Name="微信号/手机号", desc='点击输入框')
edit_control.Click(waitTime=0.5)
edit_control.SendKeys(account)
self.wxWindow.TextControl(Name=f"搜索:{account}", desc='搜索好友').Click(waitTime=0.5)
# 回到原始界面
# self.wxWindow.ButtonControl(Name="聊天", desc='点击通讯录').Click(waitTime=0.5)
def restore_face(self):
"""
此方法已弃用
:return:
"""
# 界面恢复到聊天界面(可能不成功!!!)
for i in range(5):
self.wxWindow.ButtonControl(Name="聊天").Click(waitTime=WxChat.get_random_second())
@staticmethod
def get_random_second():
return random.randrange(10, 100, 1) / 100
@staticmethod
def restore_minimize():
logging.basicConfig(level=logging.INFO)
"""
将界面恢复到最小化状态
:return:
"""
for i in range(5):
logging.info(f"最小化第{i + 1}次")
auto.PressKey(auto.Keys.VK_ESCAPE, waitTime=0.1)
@staticmethod
def outbound_chat():
logging.basicConfig(level=logging.INFO)
"""
将微信从最小化呼出
:return:
"""
wxWindow = None
for i in range(10):
logging.info(f"尝试呼出第{i + 1}次")
show_hide_icon = auto.ButtonControl(Name="显示隐藏的图标", desc='显示隐藏的图标')
if show_hide_icon.Exists(0, 0):
show_hide_icon.Click(waitTime=0.5)
wx_icon = auto.ButtonControl(Name="微信", desc='将微信从最小化呼出')
if wx_icon.Exists(0, 0):
wx_icon.Click(waitTime=0.5)
wxWindow = auto.WindowControl(searchDepth=1, ClassName='WeChatMainWndForPC',
Name='微信', desc='微信窗口')
if wxWindow.Exists(0, 0):
logging.info("呼出成功")
break
if wxWindow is None or not wxWindow.Exists(0, 0):
logging.info("呼出失败")
class GroupGrid:
"""
群好友的网格排列
"""
def __init__(self, group_number, x_leader, y_leader):
"""
初始化
:param group_number: 好友数量
:param x_leader: 群主位置X
:param y_leader: 群主位置Y
"""
self.group_number = group_number # 群成员数量
self.group_row_number, self.group_row_number_remainder = divmod(group_number, 4) # 群行数和余数
self.positions = [
(x_leader, y_leader),
(x_leader + 1 * 54, y_leader),
(x_leader + 2 * 54, y_leader),
(x_leader + 3 * 54, y_leader)
]
def get_position(self, number):
"""
获取第number个群成员的位置和下滑次数
:param number:
:return:
"""
b, y = divmod(number, 4)
if y == 0:
y = 4
b = b - 1
# 返回下滑的次数和位置坐标
return b, self.positions[y - 1]
if __name__ == '__main__':
WxChat.outbound_chat()
# 向好友发送消息
wx_chat = WxChat()
# 发送消息
wx_chat.send_message(full_name="Mongose", message="您好")
# 向群聊添加好友
wx_chat.add_friend_from_group_chat(group_full_name="浙江活动小分队", serial_numbers=[19])
# 搜索微信号或手机号并添加好友
wx_chat.add_friend_by_account('sunziwen3366')