赞
踩
Hello,我是新星博主:小恒不会java
使用 wxpy
或者 itchat
这种第三方库通过Python控制自己的微信号,实现很多自动化操作,用的是微信网页版接口,不过随着微信的发展(信息安全等方面愈加重要,这种不符合官方期望出现的东西,很容易就破产。也由于itchat在 python 的 request 请求中,使用到的 headers 都是非常简单的 headers。而且频繁利用到config.USER_AGENT
,被发现怀疑用脚本封号是当然的
所以我打算用wxauto,用UI控件的形式操作微信(uiautomation实现)
wxauto是某GitHub某位开源的
https://github.com/cluic/wxauto
功能很基础,毕竟是UI控件,实现的功能有
(1)群发消息包括图片,文件
(2)自动回复和添加好友(有小bug,简单修改一下就行)
(3) 监控群聊人员或者个人聊天框
(4)获取微信好友昵称等信息(有bug,我改了一下没成功)
- """
- Author: Cluic
- Update: 2024-03-10
- Version: 3.9.8.15
- """
-
- import uiautomation as uia
- from .languages import *
- from .utils import *
- from .elements import *
- from .errors import *
- from .color import *
- import datetime
- import time
- import os
- import re
- try:
- from typing import Literal
- except:
- from typing_extensions import Literal
-
- class WeChat(WeChatBase):
- def __init__(self, language='cn') -> None:
- """微信UI自动化实例
- Args:
- language (str, optional): 微信客户端语言版本, 可选: cn简体中文 cn_t繁体中文 en英文, 默认cn, 即简体中文
- """
- self.VERSION = '3.9.8.15'
- self.language = language
- self.lastmsgid = None
- self.listen = dict()
- self._checkversion()
- self.UiaAPI = uia.WindowControl(ClassName='WeChatMainWndForPC', searchDepth=1)
- self._show()
- self.SessionItemList = []
- MainControl1 = [i for i in self.UiaAPI.GetChildren() if not i.ClassName][0]
- MainControl2 = MainControl1.GetChildren()[0]
- # 三个布局,导航栏(A)、聊天列表(B)、聊天框(C)
- # _______________
- # |■|———| -□×|
- # | |———| |
- # |A| B | C | <--- 微信窗口布局简图示意
- # | |———|———————|
- # |=|———| |
- # ———————————————
- self.NavigationBox, self.SessionBox, self.ChatBox = MainControl2.GetChildren()
-
- # 初始化导航栏,以A开头 | self.NavigationBox --> A_xxx
- self.A_MyIcon = self.NavigationBox.ButtonControl()
- self.A_ChatIcon = self.NavigationBox.ButtonControl(Name=self._lang('聊天'))
- self.A_ContactsIcon = self.NavigationBox.ButtonControl(Name=self._lang('通讯录'))
- self.A_FavoritesIcon = self.NavigationBox.ButtonControl(Name=self._lang('收藏'))
- self.A_FilesIcon = self.NavigationBox.ButtonControl(Name=self._lang('聊天文件'))
- self.A_MomentsIcon = self.NavigationBox.ButtonControl(Name=self._lang('朋友圈'))
- self.A_MiniProgram = self.NavigationBox.ButtonControl(Name=self._lang('小程序面板'))
- self.A_Phone = self.NavigationBox.ButtonControl(Name=self._lang('手机'))
- self.A_Settings = self.NavigationBox.ButtonControl(Name=self._lang('设置及其他'))
-
- # 初始化聊天列表,以B开头
- self.B_Search = self.SessionBox.EditControl(Name=self._lang('搜索'))
-
- # 初始化聊天栏,以C开头
- self.C_MsgList = self.ChatBox.ListControl(Name=self._lang('消息'))
-
- self.nickname = self.A_MyIcon.Name
- print(f'初始化成功,获取到已登录窗口:{self.nickname}')
-
- def _checkversion(self):
- self.HWND = FindWindow(classname='WeChatMainWndForPC')
- wxpath = GetPathByHwnd(self.HWND)
- wxversion = GetVersionByPath(wxpath)
- if wxversion != self.VERSION:
- Warnings.lightred(self._lang('版本不一致,需要3.9.8.15版本微信,前往下载:https://github.com/tom-snow/wechat-windows-versions/releases?page=2', 'WARNING').format(wxversion, self.VERSION), stacklevel=2)
- return False
-
- def _show(self):
- self.HWND = FindWindow(classname='WeChatMainWndForPC')
- win32gui.ShowWindow(self.HWND, 1)
- win32gui.SetWindowPos(self.HWND, -1, 0, 0, 0, 0, 3)
- win32gui.SetWindowPos(self.HWND, -2, 0, 0, 0, 0, 3)
- self.UiaAPI.SwitchToThisWindow()
-
- def GetSessionAmont(self, SessionItem):
- """获取聊天对象名和新消息条数
-
- Args:
- SessionItem (uiautomation.ListItemControl): 聊天对象控件
-
- Returns:
- sessionname (str): 聊天对象名
- amount (int): 新消息条数
- """
- matchobj = re.search('\d+条新消息', SessionItem.Name)
- amount = 0
- if matchobj:
- try:
- amount = int([i for i in SessionItem.GetChildren()[0].GetChildren() if type(i) == uia.uiautomation.TextControl][0].Name)
- except:
- pass
- if amount:
- sessionname = SessionItem.Name.replace(f'{amount}条新消息','')
- else:
- sessionname = SessionItem.Name
- return sessionname, amount
-
- def CheckNewMessage(self):
- """是否有新消息"""
- self._show()
- return IsRedPixel(self.A_ChatIcon)
- #self.A_ChatIcon 图像中的红色像素点来判断是否有新消息。在很多应用中,新消息未读时图标会显示为红色,因此通过检测图标的红色像素是否存在,可以间接地确定是否有新消息。
-
- def GetNextNewMessage(self, savepic=False):
- """获取下一个新消息"""
- msgs_ = self.GetAllMessage()
- if self.lastmsgid is not None and self.lastmsgid in [i[-1] for i in msgs_[:-1]]:
- print('获取当前窗口新消息')
- idx = [i[-1] for i in msgs_].index(self.lastmsgid)
- MsgItems = self.C_MsgList.GetChildren()[idx+1:]
- msgs = self._getmsgs(MsgItems, savepic)
- return {self.CurrentChat(): msgs}
-
- elif self.CheckNewMessage():
- print('获取其他窗口新消息')
- while True:
- self.A_ChatIcon.DoubleClick(simulateMove=False)
- sessiondict = self.GetSessionList(newmessage=True)
- if sessiondict:
- break
- for session in sessiondict:
- self.ChatWith(session)
- MsgItems = self.C_MsgList.GetChildren()[-sessiondict[session]:]
- msgs = self._getmsgs(MsgItems, savepic)
- self.lastmsgid = msgs[-1][-1]
- return {session:msgs}
- else:
- # print('没有新消息')
- return None
-
- #该函数用于获取下一个新消息。首先,它会尝试获取当前窗口的新消息,如果成功,则返回当前聊天窗口的消息列表。
- #如果当前窗口没有新消息,它会检查是否有其他窗口有新消息。如果有,则会逐个点击聊天图标,打开有新消息的聊天窗口,并返回该窗口的消息列表。
- #函数返回一个字典,包含每个聊天窗口的消息列表。如果没有新消息,则返回None。
-
-
- def GetAllNewMessage(self):
- """获取所有新消息"""
- newmessages = {}
- while True:
- if self.CheckNewMessage():
- self.A_ChatIcon.DoubleClick(simulateMove=False)
- sessiondict = self.GetSessionList(newmessage=True)
- for session in sessiondict:
- self.ChatWith(session)
- newmessages[session] = self.GetAllMessage()[-sessiondict[session]:]
- else:
- break
- self.ChatWith(self._lang('文件传输助手'))
- return newmessages
-
- def GetSessionList(self, reset=False, newmessage=False):
- """获取当前聊天列表中的所有聊天对象
-
- Args:
- reset (bool): 是否重置SessionItemList
- newmessage (bool): 是否只获取有新消息的聊天对象
-
- Returns:
- SessionList (dict): 聊天对象列表,键为聊天对象名,值为新消息条数
- """
- self.SessionItem = self.SessionBox.ListItemControl()
- if reset:
- self.SessionItemList = []
- SessionList = {}
- for i in range(100):
- if self.SessionItem.BoundingRectangle.width() != 0:
- try:
- name, amount = self.GetSessionAmont(self.SessionItem)
- except:
- break
- if name not in self.SessionItemList:
- self.SessionItemList.append(name)
- if name not in SessionList:
- SessionList[name] = amount
- self.SessionItem = self.SessionItem.GetNextSiblingControl()
- if not self.SessionItem:
- break
-
- if newmessage:
- return {i:SessionList[i] for i in SessionList if SessionList[i] > 0}
- return SessionList
-
- def ChatWith(self, who, notfound: Literal['raise', 'ignore']='ignore'):
- '''打开某个聊天框
-
- Args:
- who ( str ): 要打开的聊天框好友名; * 最好完整匹配,不完全匹配只会选取搜索框第一个
- notfound ( str, optional ): 未找到时的处理方式,可选:raise-抛出异常 ignore-忽略,默认ignore
-
- Returns:
- chatname ( str ): 匹配值第一个的完整名字
- '''
- self._show()
- sessiondict = self.GetSessionList(True)
- if who in list(sessiondict.keys())[:-1]:
- if sessiondict[who] > 0:
- who1 = f"{who}{sessiondict[who]}条新消息"
- else:
- who1 = who
- self.SessionBox.ListItemControl(Name=who1).Click(simulateMove=False)
- return who
- self.UiaAPI.SendKeys('{Ctrl}f', waitTime=1)
- self.B_Search.SendKeys(who, waitTime=1.5)
- SearchResut = self.SessionBox.GetChildren()[1].GetChildren()[1]
- firstresult = [i for i in SearchResut.GetChildren()[0].GetChildren() if who in i.Name][0]
- if firstresult.Name == f'搜索 {who}':
- if len(self.SessionBox.GetChildren()[1].GetChildren()) > 1:
- self.B_Search.SendKeys('{Esc}')
- if notfound == 'raise':
- raise TargetNotFoundError(f'未查询到目标:{who}')
- elif notfound == 'ignore':
- return None
- chatname = firstresult.Name
- firstresult.Click(simulateMove=False)
- return chatname
-
- def SendMsg(self, msg, who=None, clear=True):
- """发送文本消息
- Args:
- msg (str): 要发送的文本消息
- who (str): 要发送给谁,如果为None,则发送到当前聊天页面。 *最好完整匹配,优先使用备注
- clear (bool, optional): 是否清除原本的内容,
- """
- if who in self.listen:
- chat = self.listen[who]
- chat.SendMsg(msg)
- return None
- if not msg:
- return None
- if who:
- try:
- editbox = self.ChatBox.EditControl(searchDepth=10)
- if who in self.CurrentChat() and who in editbox.Name:
- pass
- else:
- self.ChatWith(who)
- editbox = self.ChatBox.EditControl(Name=who, searchDepth=10)
- except:
- self.ChatWith(who)
- editbox = self.ChatBox.EditControl(Name=who, searchDepth=10)
- else:
- editbox = self.ChatBox.EditControl(searchDepth=10)
- if clear:
- editbox.SendKeys('{Ctrl}a', waitTime=0)
- self._show()
- if not editbox.HasKeyboardFocus:
- editbox.Click(simulateMove=False)
-
- t0 = time.time()
- while True:
- if time.time() - t0 > 10:
- raise TimeoutError(f'发送消息超时 --> {editbox.Name} - {msg}')
- SetClipboardText(msg)
- editbox.SendKeys('{Ctrl}v')
- if editbox.GetValuePattern().Value:
- break
- editbox.SendKeys('{Enter}')
-
- def SendFiles(self, filepath, who=None):
- """向当前聊天窗口发送文件
-
- Args:
- filepath (str|list): 要复制文件的绝对路径
- who (str): 要发送给谁,如果为None,则发送到当前聊天页面。 *最好完整匹配,优先使用备注
-
- Returns:
- bool: 是否成功发送文件
- """
- if who in self.listen:
- chat = self.listen[who]
- chat.SendFiles(filepath)
- return None
- filelist = []
- if isinstance(filepath, str):
- if not os.path.exists(filepath):
- Warnings.lightred(f'未找到文件:{filepath},无法成功发送', stacklevel=2)
- return False
- else:
- filelist.append(os.path.realpath(filepath))
- elif isinstance(filepath, (list, tuple, set)):
- for i in filepath:
- if os.path.exists(i):
- filelist.append(i)
- else:
- Warnings.lightred(f'未找到文件:{i}', stacklevel=2)
- else:
- Warnings.lightred(f'filepath参数格式错误:{type(filepath)},应为str、list、tuple、set格式', stacklevel=2)
- return False
-
- if filelist:
- self._show()
- if who:
- try:
- if who in self.CurrentChat() and who in self.ChatBox.EditControl(searchDepth=10).Name:
- pass
- else:
- self.ChatWith(who)
- except:
- self.ChatWith(who)
- editbox = self.ChatBox.EditControl(Name=who)
- else:
- editbox = self.ChatBox.EditControl()
- editbox.SendKeys('{Ctrl}a', waitTime=0)
- t0 = time.time()
- while True:
- if time.time() - t0 > 10:
- raise TimeoutError(f'发送文件超时 --> {filelist}')
- SetClipboardFiles(filelist)
- time.sleep(0.2)
- editbox.SendKeys('{Ctrl}v')
- if editbox.GetValuePattern().Value:
- break
- editbox.SendKeys('{Enter}')
- return True
- else:
- Warnings.lightred('所有文件都无法成功发送', stacklevel=2)
- return False
-
- def GetAllMessage(self, savepic=False, n=0):
- '''获取当前窗口中加载的所有聊天记录
-
- Args:
- savepic (bool): 是否自动保存聊天图片
-
- Returns:
- list: 聊天记录信息
- '''
- MsgItems = self.C_MsgList.GetChildren()
- msgs = self._getmsgs(MsgItems, savepic)
- return msgs
-
- def LoadMoreMessage(self):
- """加载当前聊天页面更多聊天信息
-
- Returns:
- bool: 是否成功加载更多聊天信息
- """
- loadmore = self.C_MsgList.GetChildren()[0]
- loadmore_top = loadmore.BoundingRectangle.top
- top = self.C_MsgList.BoundingRectangle.top
- while True:
- if loadmore.BoundingRectangle.top > top or loadmore.Name == '':
- isload = True
- break
- else:
- self.C_MsgList.WheelUp(wheelTimes=10, waitTime=0.1)
- if loadmore.BoundingRectangle.top == loadmore_top:
- isload = False
- break
- else:
- loadmore_top = loadmore.BoundingRectangle.top
- self.C_MsgList.WheelUp(wheelTimes=1, waitTime=0.1)
- return isload
-
- def CurrentChat(self):
- '''获取当前聊天对象名'''
- uia.SetGlobalSearchTimeout(1)
- try:
- currentname = self.ChatBox.TextControl(searchDepth=15).Name
- return currentname
- except:
- return None
- finally:
- uia.SetGlobalSearchTimeout(10)
-
- def GetNewFriends(self):
- """获取新的好友申请列表
-
- Returns:
- list: 新的好友申请列表,元素为NewFriendsElement对象,可直接调用Accept方法
- Example:
- >>> wx = WeChat()
- >>> newfriends = wx.GetNewFriends()
- >>> tags = ['标签1', '标签2']
- >>> for friend in newfriends:
- >>> remark = f'备注{friend.name}'
- >>> friend.Accept(remark=remark, tags=tags) # 接受好友请求,并设置备注和标签
- """
- self._show()
- self.SwitchToContact()
- self.SessionBox.ButtonControl(Name='ContactListItem').Click(simulateMove=False)
- NewFriendsList = [NewFriendsElement(i, self) for i in self.ChatBox.ListControl(Name='新的朋友').GetChildren()]
- AcceptableNewFriendsList = [i for i in NewFriendsList if i.acceptable]
- print(f'获取到 {len(AcceptableNewFriendsList)} 条新的好友申请')
- return AcceptableNewFriendsList
-
- def AddListenChat(self, who, savepic=False):
- """添加监听对象
-
- Args:
- who (str): 要监听的聊天对象名
- savepic (bool, optional): 是否自动保存聊天图片,只针对该聊天对象有效
- """
- exists = uia.WindowControl(searchDepth=1, ClassName='ChatWnd', Name=who).Exists(maxSearchSeconds=0.1)
- if not exists:
- self.ChatWith(who)
- self.SessionBox.ListItemControl(Name=who).DoubleClick(simulateMove=False)
- self.listen[who] = ChatWnd(who, self.language)
- self.listen[who].savepic = savepic
-
- def GetListenMessage(self):
- """获取监听对象的新消息"""
- msgs = {}
- for who in self.listen:
- chat = self.listen[who]
- chat._show()
- msg = chat.GetNewMessage(savepic=chat.savepic)
- # if [i for i in msg if i[0] != 'Self']:
- if msg:
- msgs[chat] = msg
- return msgs
-
- def SwitchToContact(self):
- """切换到通讯录页面"""
- self._show()
- self.A_ContactsIcon.Click(simulateMove=False)
-
- def SwitchToChat(self):
- """切换到聊天页面"""
- self._show()
- self.A_ChatIcon.Click(simulateMove=False)
-
- def GetGroupMembers(self):
- """获取当前聊天群成员
- Returns:
- list: 当前聊天群成员列表
- """
- ele = self.ChatBox.PaneControl(searchDepth=7, foundIndex=6).ButtonControl(Name='聊天信息')
- try:
- uia.SetGlobalSearchTimeout(1)
- rect = ele.BoundingRectangle
- Click(rect)
- except:
- return
- finally:
- uia.SetGlobalSearchTimeout(10)
- roominfoWnd = self.UiaAPI.WindowControl(ClassName='SessionChatRoomDetailWnd', searchDepth=1)
- more = roominfoWnd.ButtonControl(Name='查看更多', searchDepth=8)
- try:
- uia.SetGlobalSearchTimeout(1)
- rect = more.BoundingRectangle
- Click(rect)
- except:
- pass
- finally:
- uia.SetGlobalSearchTimeout(10)
- members = [i.Name for i in roominfoWnd.ListControl(Name='聊天成员').GetChildren()]
- while members[-1] in ['添加', '移出']:
- members = members[:-1]
- roominfoWnd.SendKeys('{Esc}')
- return members
-
- def GetAllFriendNicknames(self, keywords=None):
- """获取所有好友昵称列表
- 注:
- 1. 该方法运行时间取决于好友数量,约每秒6~8个好友的速度
- 2. 该方法未经过大量测试,可能存在未知问题,如有问题请微信群内反馈
- Args:
- keywords (str, optional): 搜索关键词,只返回包含关键词的好友昵称列表
- Returns:
- list: 所有好友昵称列表
- """
- self._show()
- self.SwitchToContact()
- self.SessionBox.ListControl(Name="联系人").ButtonControl(Name="通讯录管理").Click(simulateMove=False)
- contactwnd = ContactWnd()
- if keywords:
- contactwnd.Search(keywords)
- friends_nicknames = contactwnd.GetAllFriendNicknames()
- contactwnd.Close()
- self.SwitchToChat()
- return friends_nicknames
-
-
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
初始化:在类的初始化方法中,首先检查微信是否已经运行,如果没有运行,则尝试启动微信。然后,获取微信的窗口句柄,并设置微信窗口的大小和位置。
获取会话列表:
GetSessionList
方法用于获取当前聊天列表中的所有聊天对象。它会遍历聊天列表,并返回每个聊天对象的名称和新消息数量。切换聊天对象:
ChatWith
方法用于切换到指定的聊天对象。它会搜索指定的聊天对象,并将其设置为当前聊天对象。如果找不到指定的聊天对象,它会尝试搜索并选取搜索结果中的第一个。发送消息:
SendMsg
方法用于向当前聊天对象发送文本消息。它会将消息发送到当前聊天对象的编辑框中,并发送回车键以发送消息。发送文件:
SendFiles
方法用于向当前聊天对象发送文件。它会将文件复制到剪贴板中,并将其粘贴到当前聊天对象的编辑框中,然后发送回车键以发送文件。获取聊天记录:
GetAllMessage
方法用于获取当前聊天对象的所有聊天记录。它会遍历聊天记录列表,并返回每个聊天记录的信息。加载更多聊天记录:
LoadMoreMessage
方法用于加载当前聊天对象的更多聊天记录。它会滚动聊天记录列表,并检查是否已经加载了更多的聊天记录。获取当前聊天对象名称:
CurrentChat
方法用于获取当前聊天对象的名称。获取新的好友申请列表:
GetNewFriends
方法用于获取新的好友申请列表。它会遍历新的好友申请列表,并返回每个好友申请的信息。添加监听对象:
AddListenChat
方法用于添加监听对象。它会创建一个新的 ChatWnd 对象,并将其添加到监听对象字典中。获取监听对象的新消息:
GetListenMessage
方法用于获取监听对象的新消息。它会遍历监听对象字典,并获取每个监听对象的新消息。切换到通讯录页面:
SwitchToContact
方法用于切换到通讯录页面。切换到聊天页面:SwitchToChat
方法用于切换到聊天页面。获取当前聊天群成员:
GetGroupMembers
方法用于获取当前聊天群的成员列表。它会遍历聊天群成员列表,并返回每个成员的名称。获取所有好友昵称列表:
GetAllFriendNicknames
方法用于获取所有好友的昵称列表。它会打开通讯录窗口,并搜索指定的关键词(如果提供了关键词)。然后,它会遍历通讯录列表,并返回每个好友的昵称。最后,它会关闭通讯录窗口,并切换回聊天页面。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。