最近在给朋友做个工作用的软件,需要和微信配套使用且需要实现一些自动操作。

  1. 通过好友请求并随机备注。
  2. 根据关键字自动回复,包括文字、图片及文件。
  3. 定时发送信息,包括单次、隔日、每周末及每日。
  4. 处理转账,根据金额及备注进行相应操作,包括接收转账后一系列操作或退回转账。
  5. 处理扫码付款,同上一条相类似。
  6. 手动批量发送信息
  7. 保存所有好友信息

上诉功能可以很容易实现,但是还存在几个不足点,例如

  1. 不能够后台操作,也就是说微信在工作期间,在一些操作过程中,电脑不能正常使用,其他时间无影响。针对这个情况,有一个解决方案,即将任务设置放在本地,将微信放在服务器,天翼云的低配虚拟主机,八十元/年。
  2. 处理转账较慢,大概最短需要3秒,最长需要5秒。这一过程暂时没有找到更合适的解决办法。

以上就是本篇文章的简要介绍,下面来具体谈一谈。

1. 工具说明

1.1 微信版本

我一直使用的是2.9.5版本。最新版本和更旧的版本我都使用过,在所有可用的版本中,这个版本是运行最合适的一个版本,旧版本有些功能不全,而最新版本过于臃肿,所以建议找到这个版本使用。

1.2 python 版本

我是用的是 python3.5,在 pycharm 中建立虚拟环境,对于库包,随用随装。

2. 总览

设置端是按照朋友需求写的,方法都是写好的,直接调用就可以。设置端可以根据自己的需求写。在此我只是介绍一下代码能够实现的功能。

2.1 好友信息列表

在这里插入图片描述

2.2 自动回复设置

自动回复设置

2.3 好友转账及二维码付款

这两个设置一模一样,所以只张贴一个

2.4 定时信息

定时信息

3. 实现过程

from pywinauto.application import Application


class WeChat:
	PID = get_pid()
	if not self.PID:
		print('未检测到微信运行')
		return 0
	else:
		# 在这里要设置应用后端,即 uia 或 win32,这里微信要设置为 uia,具体方法可参考文末链接
		self.app = Application(backend='uia')
		self.app.connect(process=self.PID)
		self.win = self.app[u'微信']
		print('已建立与微信链接')

整个包装成类,方便在写设置端的时候直接继承,方便使用

3.1 获取微信PID

from psutil import process_iter


def get_pid(self):
    PID = process_iter()
    for pid_temp in PID :
        pid_dic = pid_temp .as_dict(attrs=['pid','name'])
        if pid_dic['name'] == 'WeChat.exe':
            return pid_dic['pid']
	else:
		return 0

3.2 获取好友信息

def get_text(self):
    try:
    	# 在这里如此定位控件是最快的,逐级定位思路清晰,定位精准
        return self.win.child_window(title="消息", control_type="List").child_window(control_type="Edit", found_index=0).window_text()
    except:
        return ''

3.3 包装点击控件

from pywinauto import mouse


# 左键点击某个控件
def click_one(self, title='', control_type='', button='left', found_index=0, left=10, top=10, times=1):
    """
    :param title: 控件名称
    :param control_type: 控件类型
    :param button: left right middle 对应左、右、中三个键
    :param times: 点击次数
    :param found_index: 选择同名同类型多个控件中的第几个
    :param left: X 正方向修正
    :param top: Y 正方向修正
    :return:
    """
    # 在这里控件名称和类型,可以只选择一个,不过定位精准度不高
    if title == '' and control_type == '':
        raise ValueError('控件名和控件类型不可为空!')
    position = self.win.child_window(title=title, control_type=control_type, found_index=found_index).rectangle()
    for i in range(times):
        mouse.click(button=button, coords=(position.left + left, position.top + top))
        if times > 1:
            sleep(0.2)

3.4 获取信息列表

def get_users(self):
    user_lis = []
    try:
    	# 列表中添加的是发来新信息的好友名称
        users = self.win.child_window(title="会话", control_type="List").children()
        for user in users:
            user_lis.append(user.window_text())
    except:
    	print('获取好友列表失败')
		pass
    return user_lis

3.5 点击或搜索好友

# user 为需要找到的好友名称,如果信息列表中存在,则直接点击,不存在则搜索
def find_user(self, user=''):
    if user== '':
        raise ValueError('好友名称不可为空!')
        
    if self.win.child_window(title=user, control_type='Text').exists(timeout=0.2):
        self.click_one(title=user, control_type='Text')
    else:
        self.click_one(title='搜索', control_type='Edit', times=2)
        # 这里必须要等待一定时间,等待搜索框加载出来,时间可以自行尝试着缩短
        sleep(0.5)
        self.win.type_keys(user)
        sleep(0.5)
        self.win.type_keys('{ENTER}')
        self.click_one(title='输入', control_type='Edit')

定位到好友有两种情况,第一种是存在于聊天界面中,第二种是不在聊天列表中,所以为了方便,先判断好友是否在当前聊天框中,如果没有,再搜索,毕竟搜索有些浪费时间

3.6 删除好友聊天框

def delete_user(self, user=''):
    try:
    	self.click_one(title=user, control_type='Text', button='right')
        
        # 这里我提供了两种删除聊天的方式,速度两者相差无几
        self.app.PopupMenu['删除聊天'].click_input('left')
        # self.app['Menu'].child_window(title='删除聊天', control_type='Text').click_input('left')
    except:
        return None

3.7 通过好友申请

from pyautogui import hotkey
from pywinauto import mouse


# 添加指定好友有瑕疵,名称仅限字母、汉字、一般符号
def add_new_amigo(self, user=''):
    self.click_one(title='通讯录', control_type='Button')
    self.click_one(title='新的朋友', control_type='ListItem')
    hotkey('down')
    hotkey('up')
	
	# 这里定位有一些烦琐,我尝试了较为简便的代码,但是速度都没这样繁琐的快,这里就不张贴其他方法了
    address_list = self.win.child_window(title=user, control_type="Text").parent().parent().parent().children()[2]
    cords = address_list.rectangle()
    mouse.click(button='left', coords=(cords.left + 10, cords.top + 10))
    hotkey('esc')
    hotkey('esc')

    self.click_one(title='通讯录', control_type='Button')

3.8 修改用户名

# 修改用户姓名
def fix_user(self, old_name='', new_name=''):
    self.find_user(user=old_name)
    remark = self.win.child_window(title="备   注", control_type="Text").parent().children()[1]
    remark.type_keys(new_name)

3.9 发送文本信息

from pyperclip import copy
from pyautogui import hotkey


def send_message_to_user(self, user='', text=''):
    self.find_user(user)
    copy(text.replace('|', '\n').strip())
    hotkey('ctrl', 'v')
    hotkey('enter')

发送文本这里建议不要使用 pywinauto 自有的 type_keys() 方法,使用这一方法会无法保证缩进格式,我尝试过以 type_keys() 为前提的多种方式来保证缩进,但是效果都不太明显,所以说我采用了粘贴的方法,即代码中的 copy() 方法

3.10 发送图片

import win32clipboard as clip
from win32con import CF_DIB
from io import BytesIO
from PIL import Image
from pyautogui import hotkey


def send_image_to_user(self, user='', image_path=''):
    self.find_user(user=user)
    img = Image.open(image_path)
    output = BytesIO()
    img.convert("RGB").save(output, "BMP")
    data = output.getvalue()[14:]
    output.close()
    clip.OpenClipboard()
    clip.EmptyClipboard()
    clip.SetClipboardData(CF_DIB, data)
    clip.CloseClipboard()
    sleep(0.5)
    hotkey('ctrl', 'v')
    hotkey('enter')
    remove(image_path)

发送图片较为繁琐,如果是采用微信带的发送文件功能,那样速度就慢了很多,所以就将图片储存到剪切板中,再粘贴到聊天框中,以此加快速度。同样,发送文件也是这样的思路,不过发送文件需要先判断一下文件是否在100MB内,因为微信发送文件有大小限制。

3.11 处理转账

def payment_values(self):
    if not self.win.child_window(title="微信转账", control_type="Text", found_index=0).exists(timeout=0.1):
        if not self.win.child_window(title="微信转账", control_type="ListItem", found_index=0).exists(timeout=0.1):
            return 0

    # 获取转账备注, 见下方注释1
    remark = self.win.child_window(title="微信转账", control_type="Text", found_index=0).parent().parent().children()[0].children()[1].children()[0].window_text()

	# 获取转账的金额,只取整数
    money = self.win.child_window(title="微信转账", control_type="Text", found_index=0).parent().parent().children()[0].children()[1].children()[1].window_text()
    money = int(float(money.replace('¥', '')))
    return {'remark': remark, 'money': money}
  1. 因为要获取转账备注,所以说定位方式看着很繁琐。如果不需要对方转账填写备注内容,则定位就很简单,只需要定位到 title = ‘确认收款’ 就可以。

3.12 确认收款及退回

from pyautogui import hotkey


#收钱, 收成功 1, 失败 0
def payment_receive(self):

	# 在这里之所以要遍历寻找控件,是因为转账这一项,在不同版本微信中有不一样的控件类型,这我已经试验过数个版本
    for control_type in ['Text', 'ListItem']:
        if self.win.child_window(title="微信转账", control_type=control_type, found_index=0).exists(timeout=0.1):
            self.click_one(title='微信转账', control_type=control_type, times=2)
            self.click_one(title='确认收钱', control_type='Button')

            if self.win.child_window(title="已收钱", control_type="Text", found_index=0).exists(timeout=5):
                hotkey('esc')
                return 1
            else:
                return 0
    else:
        return 0

# 退钱, 退成功 1, 失败 0
def payment_refuse(self):
    for control_type in ['Text', 'ListItem']:
        if self.win.child_window(title="微信转账", control_type=control_type, found_index=0).exists(timeout=0.1):
            self.click_one(title='微信转账', control_type=control_type)
            self.click_one(title='立即退还', control_type='Button')
            self.click_one(title='退还', control_type='Button')
            hotkey('esc')
            return 1
    else:
        return 0

4. 总说明

上述代码块就是实现文章开头功能的相应代码,为了解释方便,我把代码拆分开来加上注释。当然,每个功能模块都有不太合适的地方,只是提供了一个较为方便的思路,对于控制微信,还有其他更为先进的方法,比如说很热门的 hook,这个就相对高深了,有兴趣的可以自己尝试了解一下。

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐