├── handle ├── __pycache__ │ ├── handle.cpython-37.pyc │ ├── __init__.cpython-37.pyc │ └── wechat_handle.cpython-37.pyc ├── __init__.py ├── handle.py └── wechat_handle.py ├── examples ├── __init__.py ├── login_weixin.py ├── logout_weixin.py └── wechat_windows.py ├── tools ├── __init__.py └── functions.py ├── Exceptions ├── __init__.py └── exceptions.py ├── settings.py └── README.md /handle/__pycache__/handle.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xzkzdx/WeChatPC/HEAD/handle/__pycache__/handle.cpython-37.pyc -------------------------------------------------------------------------------- /handle/__pycache__/__init__.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xzkzdx/WeChatPC/HEAD/handle/__pycache__/__init__.cpython-37.pyc -------------------------------------------------------------------------------- /examples/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | # !/usr/bin/python3 3 | # @Time : 2019/3/15 15:59 4 | # @File : __init__.py.py 5 | 6 | -------------------------------------------------------------------------------- /tools/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | # !/usr/bin/python3 3 | # @Time : 2019/3/15 13:46 4 | # @File : __init__.py.py 5 | 6 | -------------------------------------------------------------------------------- /handle/__pycache__/wechat_handle.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xzkzdx/WeChatPC/HEAD/handle/__pycache__/wechat_handle.cpython-37.pyc -------------------------------------------------------------------------------- /Exceptions/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | # !/usr/bin/python3 3 | # @Time : 2019/4/29 14:10 4 | # @File : __init__.py.py 5 | 6 | 7 | if __name__ == '__main__': 8 | pass 9 | -------------------------------------------------------------------------------- /handle/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | # !/usr/bin/python3 3 | # @Time : 2019/3/14 10:25 4 | # @File : __init__.py.py 5 | 6 | from handle.handle import Handle, InvalidHandleError 7 | -------------------------------------------------------------------------------- /Exceptions/exceptions.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | # !/usr/bin/python3 3 | # @Time : 2019/3/14 11:17 4 | # @File : exceptions.py 5 | 6 | 7 | class InvalidHandleError(Exception): 8 | """无效的句柄异常""" 9 | 10 | def __init__(self, *args): 11 | super(InvalidHandleError, self).__init__(*args) 12 | -------------------------------------------------------------------------------- /settings.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | # !/usr/bin/python3 3 | # @Time : 2019/3/15 14:21 4 | # @File : settings.py 5 | 6 | import os 7 | 8 | PROJECT_PATH = os.path.dirname(os.path.abspath(__file__)) 9 | 10 | ABS_DIR_PATH = PROJECT_PATH 11 | 12 | IMAGE_DIR_NAME = 'image' 13 | 14 | IMAGE_SAVE_PATH = os.path.join(ABS_DIR_PATH, IMAGE_DIR_NAME) 15 | 16 | HANDLE_PIXEL_RATIO = 1.5 17 | 18 | LOGIN_UPDATE_TIME = 1 19 | 20 | ERROR_IGNORE_TIME = 0.1 21 | -------------------------------------------------------------------------------- /examples/login_weixin.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | # !/usr/bin/python3 3 | # @Time : 2019/3/21 16:49 4 | # @File : login_weixin.py 5 | from handle.wechat_handle import WeChatPCLoginHandle, WeChatStartUp 6 | 7 | 8 | class WeChatAPI(object): 9 | def login_wechat(self): 10 | wx = WeChatPCLoginHandle() 11 | wx.click_login() 12 | 13 | 14 | if __name__ == '__main__': 15 | WeChatStartUp('C:\\Program Files (x86)\\Tencent\\WeChat\\WeChat.exe').startup() 16 | wechat = WeChatAPI() 17 | wechat.login_wechat() 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 功能特性 2 | 微信PC版自动化API 3 | 4 | # 安装 5 | 依赖第三方库: pywin32 PIL 6 | 7 | pip install pywin32 8 | 9 | pip install PIL 10 | 11 | 12 | # example示例 13 | 14 | login_weixin.py 15 | 16 | ``` 17 | # -*- encoding: utf-8 -*- 18 | #在没有启动微信PC的情况下自动启动WeChat,并根据是否扫码选择点击登录,显示二维码等登录操作。 19 | from handle.wechat_handle import WeChatPCLoginHandle, WeChatStartUp 20 | 21 | 22 | class WeChatAPI(object): 23 | def login_wechat(self): 24 | wx = WeChatPCLoginHandle() 25 | wx.click_login() 26 | 27 | 28 | if __name__ == '__main__': 29 | WeChatStartUp('C:\\Program Files (x86)\\Tencent\\WeChat\\WeChat.exe').startup() 30 | wechat = WeChatAPI() 31 | wechat.login_wechat() 32 | 33 | ``` 34 | -------------------------------------------------------------------------------- /examples/logout_weixin.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | # !/usr/bin/python3 3 | # @Time : 2019/3/27 14:57 4 | # @File : logout_weixin.py 5 | from handle.wechat_handle import WeChatPCLoginHandle, WeChatPCHandle, WeChatSettingWndHandle 6 | 7 | 8 | class WeChatAPI(object): 9 | def login_wechat(self): 10 | wx = WeChatPCLoginHandle() 11 | wx.click_login() 12 | 13 | def logout_wechat(self): 14 | """对已登录进行退出登录""" 15 | wx = WeChatPCHandle() 16 | wx.get_menu_setting() 17 | 18 | wx_setting = WeChatSettingWndHandle() 19 | wx_setting.click_logout() 20 | 21 | 22 | if __name__ == '__main__': 23 | wechat = WeChatAPI() 24 | wechat.logout_wechat() 25 | -------------------------------------------------------------------------------- /tools/functions.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | # !/usr/bin/python3 3 | # @Time : 2019/3/15 13:45 4 | # @File : functions.py 5 | import os 6 | import psutil 7 | import time 8 | from PIL import Image 9 | from settings import ABS_DIR_PATH, IMAGE_DIR_NAME 10 | 11 | 12 | def get_process(): 13 | proc_info = {} 14 | for proc in psutil.process_iter(): 15 | proc_info[proc.pid] = proc.name() 16 | return proc_info 17 | 18 | 19 | def close_image(before_pids: dict, after_pids: dict): 20 | for pid, name in after_pids.items(): 21 | if name not in before_pids.values(): 22 | for proc in psutil.process_iter(): 23 | if proc.pid == pid and 'hoto' in name: 24 | proc.kill() 25 | 26 | 27 | def exists_exe(exe_name): 28 | """判断可执行应用是否启动""" 29 | for proc in psutil.process_iter(): 30 | if proc.name() == exe_name: 31 | return True 32 | return False 33 | 34 | 35 | def close_process(func): 36 | def __inner(*args, **kwargs): 37 | before_process = get_process() 38 | f_result = func(*args, **kwargs) 39 | after_process = get_process() 40 | # print(len(before_process.keys()), len(after_process.keys())) 41 | close_image(before_process, after_process) 42 | return f_result 43 | 44 | return __inner 45 | 46 | 47 | def get_img_pix_color(png_name, x_position=0, y_position=0): 48 | """获取图片指定像素点的像素""" 49 | img_src = Image.open(path_join(ABS_DIR_PATH, IMAGE_DIR_NAME, png_name)) 50 | img_src = img_src.convert('RGBA') 51 | rgd_color = img_src.load()[x_position, y_position] 52 | img_src.close() 53 | return rgd_color 54 | 55 | 56 | def exists_path(*file_name): 57 | return os.path.exists(path_join(ABS_DIR_PATH, *file_name)) 58 | 59 | 60 | @close_process 61 | def show_image(png_name, key_function=None, key_params: tuple = None): 62 | img_src = Image.open(path_join(ABS_DIR_PATH, IMAGE_DIR_NAME, png_name)) 63 | img_src.show() 64 | if key_function: 65 | key_function(*key_params) 66 | 67 | 68 | def make_dir(dir_path, dir_name): 69 | """创建文件夹""" 70 | if not os.path.exists(os.path.join(dir_path, dir_name)): 71 | os.mkdir(os.path.join(dir_path, dir_name)) 72 | 73 | 74 | def path_join(*path): 75 | return os.path.join(ABS_DIR_PATH, *path) 76 | 77 | 78 | if __name__ == '__main__': 79 | color = get_img_pix_color(path_join('image', 'login.png'), 210, 420) 80 | print(color) 81 | # print(ABS_DIR_PATH) 82 | # make_dir(ABS_DIR_PATH, IMAGE_DIR_NAME) 83 | # print(path_join('image', 'imag', 'img.png')) 84 | # print() 85 | show_image('login.png', key_function=lambda x: time.sleep(x), key_params=(1,)) 86 | -------------------------------------------------------------------------------- /examples/wechat_windows.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | # !/usr/bin/python3 3 | # @Time : 2019/3/13 10:48 4 | # @File : wechat_windows.py 5 | 6 | import win32gui 7 | import win32api 8 | import win32con 9 | import time 10 | import win32clipboard 11 | 12 | 13 | def right_click_position(hwd, x_position, y_position, sleep_time): 14 | """鼠标右点击""" 15 | # 将两个16位的值连接成一个32位的地址坐标 16 | long_position = win32api.MAKELONG(x_position, y_position) 17 | # 点击左键 18 | win32api.SendMessage(hwd, win32con.WM_RBUTTONDOWN, win32con.MK_RBUTTON, long_position) 19 | win32api.SendMessage(hwd, win32con.WM_RBUTTONUP, win32con.MK_RBUTTON, long_position) 20 | time.sleep(int(sleep_time)) 21 | 22 | 23 | def left_click_position(hwd, x_position, y_position, sleep_time): 24 | """鼠标左点击""" 25 | # 将两个16位的值连接成一个32位的地址坐标 26 | long_position = win32api.MAKELONG(x_position, y_position) 27 | # 点击左键 28 | win32api.SendMessage(hwd, win32con.WM_LBUTTONDOWN, win32con.MK_LBUTTON, long_position) 29 | win32api.SendMessage(hwd, win32con.WM_LBUTTONUP, win32con.MK_LBUTTON, long_position) 30 | time.sleep(sleep_time) 31 | 32 | 33 | def get_text_from_clipboard(): 34 | """读取剪切板""" 35 | win32clipboard.OpenClipboard() 36 | d = win32clipboard.GetClipboardData(win32con.CF_TEXT) 37 | win32clipboard.CloseClipboard() 38 | return d.decode('gbk') 39 | 40 | 41 | def set_text_to_clipboard(string): 42 | """写入剪切板""" 43 | win32clipboard.OpenClipboard() 44 | win32clipboard.EmptyClipboard() 45 | win32clipboard.SetClipboardData(win32con.CF_TEXT, string.encode(encoding='gbk')) 46 | win32clipboard.CloseClipboard() 47 | 48 | 49 | def get_mouse_position(): 50 | """获取鼠标位置""" 51 | return win32api.GetCursorPos() 52 | 53 | 54 | def set_mouse_position(x_position, y_position): 55 | """设置鼠标位置""" 56 | return win32api.SetCursorPos((x_position, y_position)) 57 | 58 | 59 | def input_content(hwd, content, sleep_second): 60 | """粘贴到消息发送框""" 61 | # 存入粘贴板 62 | set_text_to_clipboard(content) 63 | # 鼠标右键调 WeChatMainWndForPC 的 CMenuWnd 粘贴板 64 | right_click_position(hwd, 400, 500, 0.1) 65 | time.sleep(sleep_second) 66 | # 获取 CMenuWnd 粘贴板句柄 67 | CMenuWnd = win32gui.FindWindow("CMenuWnd", "CMenuWnd") 68 | # 鼠标左击粘贴 69 | left_click_position(CMenuWnd, 20, 10, 0.1) 70 | # click_combination_keys(hwd, win32con.VK_CONTROL, win32con.VK_RETURN) 71 | 72 | 73 | def click_single_key(hwd, key): 74 | """模拟键盘独立按键""" 75 | win32api.SendMessage(hwd, win32con.WM_KEYDOWN, key, 0) 76 | win32api.SendMessage(hwd, win32con.WM_KEYUP, key, 0) 77 | 78 | 79 | def click_multi_keys(hwd, *key): 80 | """模拟键盘多个独立按键""" 81 | for k in key: 82 | click_single_key(hwd, k) 83 | 84 | 85 | def click_combination_keys(hwd, *args): 86 | """模拟键盘组合按键""" 87 | for arg in args: 88 | print(arg) 89 | win32api.SendMessage(hwd, win32con.WM_SYSKEYDOWN, arg, 0) 90 | for arg in args: 91 | win32api.SendMessage(hwd, win32con.WM_SYSKEYUP, arg, 0) 92 | 93 | 94 | def weixin_operation(hwd, msg): 95 | # 点击联系人 96 | left_click_position(hwd, 200, 100, 0.1) 97 | # 写入消息 98 | input_content(hwd, msg, 0.1) 99 | 100 | 101 | if __name__ == "__main__": 102 | # 查找句柄 103 | hwnd = win32gui.FindWindow("WeChatMainWndForPC", "微信") 104 | # 查找指定句柄的子句柄,后两个参数为子类的类名与标题,如果没有或不确定,可以写None 105 | # hwnd = win32gui.FindWindow(hwnd, None, None, None) 106 | if int(hwnd) <= 0: 107 | print("没有找到模拟器,退出进程................") 108 | exit(0) 109 | print("查询到模拟器句柄: %s " % hwnd) 110 | # 没有直接修改窗口大小的方式,但可以曲线救国,几个参数分别表示句柄,起始点坐标,宽高度,是否重绘界面 111 | # 如果想改变窗口大小,就必须指定起始点的坐标,没果对起始点坐标没有要求,随便写就可以; 112 | print(win32gui.GetWindowPlacement(hwnd)[-1]) 113 | hwnd_rect = win32gui.GetWindowPlacement(hwnd)[-1] 114 | # 如果还想要放在原先的位置,就需要先获取之前的边框位置,再调用该方法即可 115 | win32gui.MoveWindow(hwnd, hwnd_rect[0], hwnd_rect[1], 850, 560, ) 116 | # win32gui.MoveWindow(hwnd, hwnd_rect[0], hwnd_rect[1], hwnd_rect[2], hwnd_rect[3], True) 117 | # time.sleep(2) 118 | # 屏幕坐标到客户端坐标 119 | # print(win32gui.ScreenToClient(hwnd, (1446, 722))) 120 | # 设置为前台 121 | win32gui.SetForegroundWindow(hwnd) 122 | # 设置为后台 123 | # win32gui.SetBkMode(hwnd, win32con.TRANSPARENT) 124 | # time.sleep(2) 125 | # 下列的后三个参数分别表示: 文件路径 打招呼句子 广告语 126 | for i in range(5): 127 | weixin_operation(hwnd, '连续发送成功次数 %s' % i) 128 | -------------------------------------------------------------------------------- /handle/handle.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | # !/usr/bin/python3 3 | # @Time : 2019/3/14 10:29 4 | # @File : handle.py 5 | 6 | import win32gui 7 | import win32api 8 | import win32ui 9 | 10 | import win32con 11 | import time 12 | import win32clipboard 13 | from win32api import GetSystemMetrics 14 | from settings import HANDLE_PIXEL_RATIO 15 | from tools.functions import path_join, get_img_pix_color, show_image, exists_path 16 | from Exceptions.exceptions import InvalidHandleError 17 | 18 | 19 | class Handle(object): 20 | handle = 0 21 | class_name = '' 22 | class_title = '' 23 | screen_shot_file_name = '{}_.png'.format(class_name) 24 | default_left = 0 25 | default_top = 0 26 | default_width = 0 27 | default_height = 0 28 | left, top, width, height = 0, 0, 0, 0 29 | 30 | def initial(self, handle_class_name, handle_title, *default_rect): 31 | self.class_name = handle_class_name 32 | self.class_title = handle_title 33 | self.handle = win32gui.FindWindow(handle_class_name, handle_title) 34 | if self.handle == 0: 35 | self.load_error('无效的窗口句柄。') 36 | self.left, self.top, self.width, self.height = self.get_handle_rect() 37 | print(self.left, self.top, self.width, self.height) 38 | self.set_format_rect(**{ 39 | 'default_left': default_rect[0] if default_rect[0] is not None else self.left, 40 | 'default_top': default_rect[1] if default_rect[1] is not None else self.top, 41 | 'default_width': default_rect[2] if default_rect[2] is not None else self.width, 42 | 'default_height': default_rect[3] if default_rect[3] is not None else self.height 43 | }) 44 | self.change_position(self.default_left, self.default_top, self.default_width, self.default_height) 45 | 46 | def startup_exe(self, exe_name): 47 | win32api.ShellExecute(0, 'open', exe_name, '', '', 1) 48 | 49 | def load_error(self, msg): 50 | raise InvalidHandleError(msg) 51 | 52 | def check_handle(self, handle_class_name, handle_title): 53 | """验证句柄""" 54 | return win32gui.FindWindow(handle_class_name, handle_title) != 0 55 | 56 | def create_handle_dc(self): 57 | """根据窗口句柄获取窗口的设备上下文DC(Divice Context)""" 58 | return win32gui.GetWindowDC(self.handle) 59 | 60 | def create_handle_mfc_dc(self, dc=None): 61 | """根据窗口上下文创建dc""" 62 | return win32ui.CreateDCFromHandle(dc if dc else self.create_handle_dc()) 63 | 64 | def handle_compatible_dc(self, dc=None, mfc_dc=None): 65 | """根据mfcDC创建可兼容的DC""" 66 | dc = dc if dc else self.create_handle_dc() 67 | mfc_dc = mfc_dc if mfc_dc else self.create_handle_mfc_dc(dc) 68 | return mfc_dc.CreateCompatibleDC() 69 | 70 | def get_handle_full_screen_shot_size(self): 71 | """句柄截图大小""" 72 | full_screen_width = int(self.width * HANDLE_PIXEL_RATIO) 73 | full_screen_height = int(self.height * HANDLE_PIXEL_RATIO) 74 | return full_screen_width, full_screen_height 75 | 76 | def handle_full_screen_shot(self, dc=None, mfc_dc=None, compatible_dc=None, image_file_name=''): 77 | """句柄截图""" 78 | dc = dc if dc else self.create_handle_dc() 79 | mfc_dc = mfc_dc if mfc_dc else self.create_handle_mfc_dc(dc=dc) 80 | compatible_dc = compatible_dc if compatible_dc else self.handle_compatible_dc(dc=dc, mfc_dc=mfc_dc) 81 | save_bit_map = win32ui.CreateBitmap() # 创建bigmap准备保存图片 82 | # 句柄全大小 83 | full_screen_width, full_screen_height = self.get_handle_full_screen_shot_size() 84 | full_size = (full_screen_width, full_screen_height) 85 | # 为bitmap开辟空间 86 | save_bit_map.CreateCompatibleBitmap(mfc_dc, full_screen_width, full_screen_height) 87 | compatible_dc.SelectObject(save_bit_map) 88 | compatible_dc.BitBlt((0, 0), full_size, mfc_dc, (0, 0), win32con.SRCCOPY) 89 | self.screen_shot_file_name = image_file_name if image_file_name else self.screen_shot_file_name 90 | save_bit_map.SaveBitmapFile(compatible_dc, path_join('image', self.screen_shot_file_name)) 91 | return save_bit_map.GetBitmapBits() 92 | 93 | @property 94 | def handler(self): 95 | """获取句柄""" 96 | return self.handle 97 | 98 | def get_handle_rect(self): 99 | """获取句柄矩形""" 100 | # abs_position = win32gui.GetWindowRect(self.handle)[:2] 101 | abs_position = win32gui.GetWindowPlacement(self.handle)[-1][:2] 102 | handle_shape = win32gui.GetClientRect(self.handle)[2:] 103 | # print(abs_position, handle_shape) 104 | return abs_position + handle_shape 105 | 106 | def set_format_rect(self, **rect): 107 | """rect must has four params, 108 | example: (left, top, width, height) 109 | if any param is None or lt zero, it will not change 110 | """ 111 | if len(rect.values()) != 4: 112 | raise TypeError("reset_rect() takes exactly 4 arguments (%s given)" % len(rect)) 113 | for r_key, r_value in rect.items(): 114 | if not (isinstance(r_value, int)) ^ (r_value is None): 115 | raise ValueError("reset_rect() params must be int or None") 116 | r_value = 0 if r_value is None else r_value 117 | setattr(self, r_key, r_value) 118 | 119 | def reset_handle_rect(self, *rect, not_ensure_move=False): 120 | """rect must has four params, 121 | example: (left, top, width, height) 122 | if any param is None or lt zero, it will not change 123 | """ 124 | self.set_format_rect(**{'left': rect[0], 'top': rect[1], 'width': rect[2], 'height': rect[3]}) 125 | win32gui.MoveWindow(self.handle, self.left, self.top, self.width, self.height, not_ensure_move) 126 | 127 | def change_position(self, *args, not_ensure_move: bool = False, ensure_hidden: bool = True): 128 | self.show_handle() 129 | self.reset_handle_rect(*args, not_ensure_move=not_ensure_move) 130 | self.set_handle_min() if ensure_hidden else self.set_handle_foreground() 131 | 132 | def get_handle_by_position(self, position_x, position_y): 133 | # return win32gui.WindowFromPoint((position_x, position_y,)) 134 | self.set_mouse_position(self.left + position_x, self.top + position_y) 135 | return win32gui.WindowFromPoint(self.get_mouse_position()) 136 | 137 | def get_handle_name(self, handle): 138 | return win32gui.GetClassName(handle) if handle else self.class_name 139 | 140 | def get_handle_title(self, handle): 141 | return win32gui.GetWindowText(handle) if handle else self.class_name 142 | 143 | def search_children_handle_from_parent(self, class_name): 144 | children_handle = win32gui.FindWindowEx(self.handle, 0, class_name, None) 145 | win32gui.ChildWindowFromPoint() 146 | return children_handle 147 | 148 | def get_children_handles(self): 149 | """对微信端无效""" 150 | # self.show_handle() 151 | children_handle_list = [] 152 | win32gui.EnumChildWindows(self.handler, lambda hand, param: param.append(hand), children_handle_list) 153 | return children_handle_list 154 | 155 | def set_handle_max(self): 156 | """最大化句柄窗口""" 157 | # self.set_handle_min() 158 | win32gui.ShowWindow(self.handle, win32con.SW_MAXIMIZE) 159 | 160 | def set_handle_min(self): 161 | """最小化句柄窗口""" 162 | win32gui.ShowWindow(self.handle, win32con.SW_MINIMIZE) 163 | 164 | def show_handle(self, handle_id=None): 165 | """显示句柄窗口""" 166 | handle_id = handle_id if handle_id else self.handle 167 | win32gui.ShowWindow(handle_id, win32con.SW_SHOWDEFAULT) 168 | # self.set_handle_background() 169 | 170 | def hidden_handle(self): 171 | """隐藏句柄窗口 0 """ 172 | # win32gui.ShowWindow(self.handle, win32con.HIDE_WINDOW) 173 | win32gui.ShowWindow(self.handle, win32con.SW_HIDE) 174 | 175 | def set_handle_foreground(self): 176 | """在前台显示""" 177 | # self.show_handle() 178 | win32gui.SetForegroundWindow(self.handle) 179 | 180 | def set_handle_background(self): 181 | """在后台显示""" 182 | win32gui.SetForegroundWindow(self.handle) 183 | 184 | def get_mouse_position(self): 185 | """获取鼠标位置""" 186 | return win32api.GetCursorPos() 187 | 188 | def set_mouse_position(self, x_position, y_position): 189 | """设置鼠标位置""" 190 | return win32api.SetCursorPos((x_position, y_position)) 191 | 192 | def get_text_from_clipboard(self, decoding='gbk'): 193 | """读取剪切板""" 194 | win32clipboard.OpenClipboard() 195 | d = win32clipboard.GetClipboardData(win32con.CF_TEXT) 196 | win32clipboard.CloseClipboard() 197 | return d.decode(decoding) 198 | 199 | def set_text_to_clipboard(self, string, encoding='gbk'): 200 | """写入剪切板""" 201 | win32clipboard.OpenClipboard() 202 | win32clipboard.EmptyClipboard() 203 | win32clipboard.SetClipboardData(win32con.CF_TEXT, string.encode(encoding=encoding)) 204 | win32clipboard.CloseClipboard() 205 | 206 | def mouse_left_click_move(self, x_position, y_position, x_end_position, y_end_position, sleep_time=1): 207 | # 将两个16位的值连接成一个32位的地址坐标 208 | start_position = win32api.MAKELONG(x_position, y_position) 209 | end_position = win32api.MAKELONG(x_end_position, y_end_position) 210 | self.set_mouse_position(x_position, y_position) 211 | time.sleep(sleep_time) 212 | # 点击左键 213 | win32api.SendMessage(self.handle, win32con.WM_LBUTTONDOWN, win32con.MK_LBUTTON, start_position) 214 | self.set_mouse_position(x_end_position, y_end_position) 215 | time.sleep(sleep_time) 216 | win32api.mouse_event(win32con.MOUSEEVENTF_WHEEL, x_position, y_position, 1) 217 | win32api.mouse_event(win32con.MOUSEEVENTF_MOVE, 0, 0, -1) 218 | time.sleep(sleep_time) 219 | win32api.SendMessage(self.handle, win32con.WM_LBUTTONUP, win32con.MK_LBUTTON, end_position) 220 | time.sleep(sleep_time) 221 | 222 | def mouse_right_click_position(self, x_position, y_position, sleep_time=0.1, handle_id=None): 223 | """鼠标右点击""" 224 | # 将两个16位的值连接成一个32位的地址坐标 225 | long_position = win32api.MAKELONG(x_position, y_position) 226 | handle_id = handle_id if handle_id else self.handle 227 | # 点击左键 228 | win32api.SendMessage(handle_id, win32con.WM_RBUTTONDOWN, win32con.MK_RBUTTON, long_position) 229 | win32api.SendMessage(handle_id, win32con.WM_RBUTTONUP, win32con.MK_RBUTTON, long_position) 230 | time.sleep(sleep_time) 231 | 232 | def mouse_left_click_position(self, x_position, y_position, sleep_time=0.1, handle_id=None): 233 | """鼠标左点击""" 234 | # 将两个16位的值连接成一个32位的地址坐标 235 | long_position = win32api.MAKELONG(x_position, y_position) 236 | handle_id = handle_id if handle_id else self.handle 237 | # 点击左键 238 | time.sleep(sleep_time) 239 | win32api.SendMessage(handle_id, win32con.WM_LBUTTONDOWN, win32con.MK_LBUTTON, long_position) 240 | win32api.SendMessage(handle_id, win32con.WM_LBUTTONUP, win32con.MK_LBUTTON, long_position) 241 | 242 | def click_single_key(self, key): 243 | """模拟键盘独立按键""" 244 | win32api.SendMessage(self.handle, win32con.WM_KEYDOWN, key, 0) 245 | win32api.SendMessage(self.handle, win32con.WM_KEYUP, key, 0) 246 | 247 | def click_multi_keys(self, *keys): 248 | """模拟键盘多个独立按键""" 249 | for key in keys: 250 | self.click_single_key(key) 251 | 252 | def ctrl_v(self): 253 | self.click_multi_keys(win32con.VK_CONTROL, 86) 254 | 255 | def click_combination_keys(self, *args): 256 | """模拟键盘组合按键""" 257 | for arg in args: 258 | win32api.SendMessage(self.handle, win32con.WM_SYSKEYDOWN, arg, 0) 259 | for arg in args: 260 | win32api.SendMessage(self.handle, win32con.WM_SYSKEYUP, arg, 0) 261 | 262 | def mouse_move_up(self, x_position, y_position): 263 | self.set_mouse_position(x_position, y_position) 264 | win32api.mouse_event(win32con.MOUSEEVENTF_WHEEL, x_position, y_position, 1) 265 | 266 | def mouse_move_down(self, x_position, y_position): 267 | self.set_mouse_position(x_position, y_position) 268 | win32api.mouse_event(win32con.MOUSEEVENTF_WHEEL, 0, 0, -1) 269 | 270 | def get_screen_resolution(self): 271 | """获取屏幕分辨率""" 272 | return GetSystemMetrics(0), GetSystemMetrics(1) 273 | 274 | def show_screen_shot(self, key_function=None, key_params: tuple = None): 275 | """显示截图""" 276 | exists_path('image', self.screen_shot_file_name) 277 | show_image(self.screen_shot_file_name, key_function=key_function, key_params=key_params) 278 | 279 | def get_position_color(self, x_position, y_position, image_name=''): 280 | absolute_position = (x_position * HANDLE_PIXEL_RATIO, y_position * HANDLE_PIXEL_RATIO) 281 | self.handle_full_screen_shot(image_file_name=image_name) 282 | position_color = get_img_pix_color(self.screen_shot_file_name, *absolute_position) 283 | return position_color 284 | 285 | 286 | if __name__ == '__main__': 287 | win32api.ShellExecute(0, 'open', '', '', '', 0) 288 | -------------------------------------------------------------------------------- /handle/wechat_handle.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | # !/usr/bin/python3 3 | # @Time : 2019/3/13 17:17 4 | # @File : wechat_handle.py 5 | import time 6 | 7 | import pywintypes 8 | 9 | from handle import Handle, InvalidHandleError 10 | from settings import LOGIN_UPDATE_TIME, ERROR_IGNORE_TIME 11 | from tools.functions import exists_exe 12 | 13 | 14 | class WeChatStartUp(Handle): 15 | 16 | def __init__(self, exe_path: str = '', exe_name: str = 'WeChat.exe'): 17 | """先尝试将exe名加入初始化,若系统找不到指定文件,请尝试初始化exe的绝对路径 18 | 如:exe_path = 'WeChat.exe' 19 | 或:exe_path = 'C:\\Program Files (x86)\\Tencent\\WeChat\\WeChat.exe' """ 20 | self.exe_path = exe_path 21 | self.exe_name = exe_name 22 | 23 | def startup(self): 24 | self.startup_exe(self.exe_path) 25 | while 1: 26 | if exists_exe(self.exe_name): 27 | break 28 | 29 | 30 | class WeChatPCLoginHandle(Handle): 31 | """WeChat登录句柄""" 32 | 33 | def __init__(self): 34 | class_titles = ["登录", '微信'] 35 | for title_index, class_title in enumerate(class_titles, start=1): 36 | try: 37 | self.initial("WeChatLoginWndForPC", class_title, *(None, None, 280, 400)) 38 | break 39 | except InvalidHandleError: 40 | if title_index == len(class_titles): 41 | self.load_error("没有登陆窗口,请确认您的操作") 42 | 43 | def code_login(self, relative_x=140, relative_y=280): 44 | """二维码登陆""" 45 | self.handle_full_screen_shot(image_file_name='code_login.png') 46 | self.show_screen_shot(key_function=self.check_login, key_params=(relative_x, relative_y)) 47 | 48 | def click_login(self, relative_x=140, relative_y=280): 49 | """点击登陆按钮登录""" 50 | self.show_handle() 51 | login_color = self.get_position_color(relative_x, relative_y, image_name='click_login.png') 52 | if login_color == (26, 173, 25, 255): 53 | self.mouse_left_click_position(relative_x, relative_y) 54 | self.check_login(relative_x, relative_y) 55 | 56 | def check_login(self, relative_x, relative_y): 57 | """登录验证""" 58 | self.show_handle() 59 | do_check = True 60 | first_check = True 61 | while 1: 62 | time.sleep(LOGIN_UPDATE_TIME) 63 | if not self.check_handle(self.class_name, self.class_title): 64 | if self.check_handle("WeChatMainWndForPC", '微信'): 65 | break 66 | elif first_check: 67 | first_check = False 68 | for i in range(1, 15): 69 | """斜向求值""" 70 | try: 71 | change_color = self.get_position_color(130 + i, 340 + i) 72 | except pywintypes.error: 73 | return 74 | if change_color != (245, 245, 245, 255): 75 | """全灰度判断""" 76 | do_check = False 77 | # print(130 + i, 340 + i) 78 | break 79 | if do_check: 80 | self.code_login() 81 | do_check = False 82 | 83 | 84 | class WeChatPCHandle(Handle): 85 | """WeChat父句柄""" 86 | 87 | def __init__(self): 88 | self.initial("WeChatMainWndForPC", "微信", *(None, None, 850, 560)) 89 | 90 | def scroll_move2top(self, place='left', ): 91 | pass 92 | 93 | def scroll_move2button(self, place='left', ): 94 | pass 95 | 96 | def message_list_move2top(self): 97 | # position_x = self.left + 180 98 | # position_y = self.top + 100 99 | # self.mouse_left_click_move(position_x, position_y, position_x, position_y + 100) 100 | self.message_list_range_move(10000) 101 | 102 | def message_list_move2bottom(self): 103 | self.message_list_range_move(10000, move_up=False) 104 | # position_x = self.left + 180 105 | # position_y = self.top + 150 106 | # color = self.get_position_color(position_x, position_y) 107 | # print(color) 108 | 109 | def message_list_range_move(self, frequency: int, move_up: bool = True): 110 | self.show_handle() 111 | time.sleep(0.1) 112 | position_x = self.left + 180 113 | position_y = self.top + 150 114 | for i in range(frequency): 115 | self.mouse_move_up(position_x, position_y) if move_up else self.mouse_move_down(position_x, position_y) 116 | # self.hidden_handle() 117 | 118 | def get_menu_setting(self): 119 | self.show_handle() 120 | self.mouse_left_click_position(30, 535) 121 | menu_handle_id = self.get_handle_by_position(105, 470) 122 | self.mouse_left_click_position(60, 115, handle_id=menu_handle_id) 123 | 124 | def get_menu_feedback(self): 125 | self.mouse_left_click_position(30, 535) 126 | menu_handle_id = self.get_handle_by_position(105, 470) 127 | self.mouse_left_click_position(60, 25, handle_id=menu_handle_id) 128 | 129 | def get_menu_backup_and_recovery(self): 130 | self.mouse_left_click_position(30, 535) 131 | menu_handle_id = self.get_handle_by_position(105, 470) 132 | self.mouse_left_click_position(60, 70, handle_id=menu_handle_id) 133 | 134 | def click_friend(self, f_position): 135 | self.mouse_left_click_position(170, f_position) 136 | 137 | def input_content(self, msg_content): 138 | """粘贴到消息发送框""" 139 | # 存入粘贴板 140 | self.set_text_to_clipboard(msg_content) 141 | # 鼠标右键调 WeChatMainWndForPC 的 CMenuWnd 粘贴板 142 | self.mouse_right_click_position(400, 500) 143 | # 获取 CMenuWnd 粘贴板句柄 144 | c_menu_wnd = CMenuWnd() 145 | # 鼠标左击粘贴 146 | c_menu_wnd.click_menu_wnd() 147 | # click_combination_keys(hwd, win32con.VK_CONTROL, win32con.VK_RETURN) 148 | 149 | def send_msg2dialog_box(self, msg_content): 150 | """粘贴到消息发送框""" 151 | self.mouse_left_click_position(450, 500) 152 | # 存入粘贴板 153 | self.set_text_to_clipboard(msg_content) 154 | # 鼠标右键调 WeChatMainWndForPC 的 CMenuWnd 粘贴板 155 | self.mouse_right_click_position(400, 500, 0.1) 156 | # 获取 CMenuWnd 粘贴板句柄 157 | c_menu_wnd = CMenuWnd() 158 | # 鼠标左击粘贴 159 | c_menu_wnd.click_menu_wnd() 160 | 161 | def send_msg2friend(self, msg_content, f_position: int): 162 | self.click_friend(f_position) 163 | self.send_msg2dialog_box(msg_content) 164 | self.mouse_left_click_position(780, 540) 165 | 166 | def send_msg2top_friend(self, msg_content): 167 | self.message_list_move2top() 168 | self.click_friend(90) 169 | self.send_msg2dialog_box(msg_content) 170 | self.mouse_left_click_position(780, 540) 171 | 172 | def click_sending_msg(self, m_position): 173 | self.mouse_left_click_position(710, m_position) 174 | 175 | def close_web_view(self, wait_time: float): 176 | web_view = WeChatWebViewWnd() 177 | web_view.close_web(wait_time) 178 | 179 | 180 | class WeChatChatWnd(Handle): 181 | """微信聊天句柄""" 182 | 183 | def __init__(self, name: str): 184 | self.initial("ChatWnd", name, *(None, None, 550, 640)) 185 | 186 | def send_msg(self, msg_content): 187 | """发送消息""" 188 | self.show_handle() 189 | self.set_handle_foreground() 190 | # 存入粘贴板 191 | self.set_text_to_clipboard(msg_content) 192 | # 鼠标右键调 WeChatMainWndForPC 的 CMenuWnd 粘贴板 193 | self.mouse_right_click_position(200, 580, 0.1) 194 | # 获取 CMenuWnd 粘贴板句柄 195 | c_menu_wnd = CMenuWnd() 196 | # 鼠标左击粘贴 197 | c_menu_wnd.click_menu_wnd() 198 | # 点击发送 199 | self.mouse_left_click_position(self.width - 60, self.height - 20) 200 | 201 | def click_last_sent_msg(self, m_position): 202 | self.mouse_left_click_position(710, m_position) 203 | 204 | def close_web_view(self, wait_time: float): 205 | web_view = WeChatWebViewWnd() 206 | web_view.close_web(wait_time) 207 | 208 | def move2bottom(self): 209 | self.message_list_range_move(10000, move_up=False) 210 | 211 | def close_chat(self): 212 | self.mouse_left_click_position(self.left - 15, 15) 213 | 214 | def message_list_range_move(self, frequency: int, move_up: bool = True): 215 | self.show_handle() 216 | time.sleep(0.1) 217 | position_x = self.left + 180 218 | position_y = self.top + 150 219 | for i in range(frequency): 220 | self.mouse_move_up(position_x, position_y) if move_up else self.mouse_move_down(position_x, position_y) 221 | # self.hidden_handle() 222 | 223 | def delete_top_msg(self): 224 | """删除顶条信息""" 225 | self.show_handle() 226 | self.set_handle_foreground() 227 | self.mouse_right_click_position(self.width - 90, 120, 0.1) 228 | time.sleep(1) 229 | # 获取 CMenuWnd 粘贴板句柄 230 | c_menu_wnd = CMenuWnd(None, None, 76, 196) 231 | # 鼠标左击粘贴 232 | c_menu_wnd.click_menu_wnd(t_position=c_menu_wnd.height - 5, sleep_time=0) 233 | # time.sleep(1) 234 | # 线程解决退出登录鼠标左键无法抬起的问题 235 | while 1: 236 | try: 237 | delete_h = WeChatPCLogoutHandle() 238 | delete_h.confirm() 239 | break 240 | except InvalidHandleError: 241 | continue 242 | 243 | 244 | class CMenuWnd(Handle): 245 | """微信对话框粘贴板""" 246 | 247 | def __init__(self, *rect): 248 | rect = rect if rect else (None, None, 76, 30,) 249 | self.initial("CMenuWnd", "CMenuWnd", *rect) 250 | 251 | def click_menu_wnd(self, t_position: int = 10, sleep_time: float = 0): 252 | self.mouse_left_click_position(20, t_position, sleep_time) 253 | 254 | 255 | class WeChatPCMenuHandle(Handle): 256 | """左下角菜单子句柄""" 257 | 258 | def __init__(self, handle_id, father_handle_id): 259 | """初始化菜单句柄id""" 260 | self.handle_id = handle_id 261 | self.father_handle_id = father_handle_id 262 | 263 | def click_feedback(self, message): 264 | """点击选择意见反馈""" 265 | self.mouse_left_click_position(60, 25, handle_id=self.handle_id) 266 | 267 | def click_backup_and_recovery(self): 268 | """点击选择备份与恢复""" 269 | self.mouse_left_click_position(60, 70, handle_id=self.handle_id) 270 | 271 | def click_setting(self): 272 | """点击选择设置""" 273 | self.mouse_left_click_position(60, 115, handle_id=self.handle_id) 274 | 275 | 276 | class WeChatPCFeedbackHandle(Handle): 277 | """意见反馈句柄""" 278 | 279 | def __init__(self): 280 | self.initial("SetMenuWnd", "", *(None, None, 134, 138)) 281 | 282 | def feedback(self, message): 283 | self.set_text_to_clipboard(message) 284 | self.show_handle() 285 | self.mouse_left_click_position(100, 100) 286 | self.ctrl_v() 287 | 288 | 289 | class WeChatWebViewWnd(Handle): 290 | def __init__(self): 291 | self.initial('CefWebViewWnd', '微信', *(None, None, 640, 740)) 292 | 293 | def close_web(self, wait_time: float = 0.1): 294 | self.mouse_left_click_position(625, 15, wait_time) 295 | 296 | 297 | class WeChatSettingWndHandle(Handle): 298 | def __init__(self): 299 | self.initial('SettingWnd', '设置', *(None, None, 550, 470)) 300 | 301 | def click_logout(self): 302 | """点击退出登录""" 303 | import threading 304 | self.show_handle() 305 | # 线程解决退出登录鼠标左键无法抬起的问题 306 | mouse_left = threading.Thread(target=self.mouse_left_click_position, args=(282, 282,)) 307 | mouse_left.start() 308 | while 1: 309 | try: 310 | confirm_dialog = WeChatPCLogoutHandle() 311 | threading.Thread(target=confirm_dialog.logout).start() 312 | break 313 | except InvalidHandleError: 314 | time.sleep(ERROR_IGNORE_TIME) 315 | 316 | 317 | class WeChatPCLogoutHandle(Handle): 318 | """WeChat退出登录句柄""" 319 | 320 | def __init__(self): 321 | self.initial("ConfirmDialog", "微信", *(None, None, 360, 224)) 322 | 323 | def confirm(self): 324 | """确定""" 325 | self.set_handle_foreground() 326 | self.show_handle() 327 | self.mouse_left_click_position(225, 190) 328 | 329 | def logout(self): 330 | """退出登陆""" 331 | self.confirm() 332 | self.check_logout() 333 | 334 | def cancel(self): 335 | self.set_handle_foreground() 336 | self.show_handle() 337 | self.set_mouse_position(self.left + 305, self.top + 190) 338 | self.mouse_left_click_position(305, 190) 339 | 340 | def check_logout(self): 341 | """退出登录验证""" 342 | while 1: 343 | if not self.check_handle(self.class_name, self.class_title): 344 | if self.check_handle("WeChatLoginWndForPC", '微信'): 345 | break 346 | 347 | 348 | if __name__ == '__main__': 349 | # wx = WeChatPCLoginHandle() 350 | # wx.click_login() 351 | # wx = WeChatPCHandle() 352 | # wx.send_msg2top_friend('http://baidu.com') 353 | # wx.click_sending_msg(380) 354 | # wx.close_web_view(5) 355 | # web_v = WeChatWebViewWnd() 356 | # print(web_v.handle) 357 | friend = WeChatChatWnd('清竹') 358 | friend.send_msg('https://mp.weixin.qq.com/s/hWKlgb_dGn9EO6lbQ7esHw') 359 | # friend.delete_top_msg() 360 | --------------------------------------------------------------------------------