├── README.md ├── adb_devices_control └── adb_devices_control.py ├── qq ├── QQ号.xlsx ├── default_verify_msg.json ├── main.py ├── oappium.py ├── oauth.py ├── oxls.py ├── pytransform.py ├── qqaf_auto_tool.py ├── qqaf_auto_tool_multi.py ├── qqaf_auto_tool_ui.py ├── qqaf_auto_tool_ui.ui ├── qt_table_view.py └── settings.py └── weixin_raise_accounts ├── main.py ├── oappium.py ├── oauth.py ├── settings.py ├── wra_auto_tool.py ├── wra_auto_tool_multi.py ├── wra_auto_tool_ui.py └── wra_auto_tool_ui.ui /README.md: -------------------------------------------------------------------------------- 1 | # AppiumProjects 2 | 基于Appium框架开发的自动化程序 3 | 4 | ## adb_devices_control 5 | 利用adb命令对设备进行控制 6 | 7 | ## qq 8 | QQ自动加好友 9 | 10 | ## weixin_raise_accounts 11 | 微信养号,包括关注公众号、阅读文章、朋友圈点赞、发送消息等功能 12 | -------------------------------------------------------------------------------- /adb_devices_control/adb_devices_control.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import re 4 | import time 5 | # import usb.core 6 | 7 | 8 | 9 | def get_devices(): 10 | devices = [] 11 | ret = os.popen('adb devices -l') 12 | devices_info = [i for i in ret.readlines() if 'model' in i] 13 | ret.close() 14 | for info in devices_info: 15 | serial = str(re.search(r'(.*?)device', info).group(1).strip()) 16 | devices.append(serial) 17 | 18 | return devices 19 | 20 | def get_window_size(device): 21 | ret = os.popen(f'adb -s {device} shell wm size') 22 | result = ret.read() 23 | ret.close() 24 | 25 | if result: 26 | clear_btn_loc = re.search(r'Physical size: (\d+)x(\d+)', result) 27 | x = int(clear_btn_loc.group(1)) 28 | y = int(clear_btn_loc.group(2)) 29 | 30 | return x,y 31 | 32 | def device_check(): 33 | ret = os.popen('adb devices -l') 34 | devices_info = [i for i in ret.readlines() if 'model' in i] 35 | ret.close() 36 | print(f'设备数量:{len(devices_info)}\n') 37 | print('详细信息:') 38 | device_serials = [] 39 | for i, info in enumerate(devices_info): 40 | serial = str(re.search(r'(.*?)device', info).group(1).strip()) 41 | deviceName = re.search(r'model:(.*?) device', info).group(1).strip() 42 | device_serials.append(serial) 43 | print(f'序列号:{serial} 机型:{deviceName}') 44 | 45 | print('') 46 | return device_serials 47 | 48 | def clear_cache(): 49 | devices = get_devices() 50 | for device in devices: 51 | ret = os.popen(f'adb -s {device} shell input keyevent 3') 52 | ret.close() 53 | 54 | ret = os.popen(f'adb -s {device} shell input keyevent 82') 55 | ret.close() 56 | 57 | x,y = get_window_size(device) 58 | x *= 0.5 59 | y *= 0.9 60 | 61 | ret = os.popen(f'adb -s {device} shell input tap {x} {y}') 62 | ret.close() 63 | print(f'设备 {device} 操作成功') 64 | 65 | 66 | # 唤醒并解锁屏幕 67 | def awake_and_unlock_screen(): 68 | devices = get_devices() 69 | for device in devices: 70 | ret1 = os.popen(f'adb -s {device} shell "dumpsys window policy|grep isStatusBarKeyguard"') 71 | result1 = ret1.read() 72 | ret1.close() 73 | 74 | ret2 = os.popen(f'adb -s {device} shell "dumpsys window policy|grep mShowingLockscreen"') 75 | result2 = ret2.read() 76 | ret2.close() 77 | 78 | ret3 = os.popen(f'adb -s {device} shell "dumpsys window policy|grep mScreenOnEarly"') 79 | result3 = ret3.read() 80 | ret3.close() 81 | 82 | if 'isStatusBarKeyguard=true' in result1 or 'mShowingLockscreen=true' in result2: 83 | x, y = get_window_size(device) 84 | x1, y1, x2, y2 = 1 / 2 * x, 9 / 10 * y, 1 / 2 * x, 1 / 10 * y 85 | 86 | if 'mScreenOnEarly=false' in result3: 87 | ret = os.popen(f'adb -s {device} shell input keyevent 26') 88 | ret.close() 89 | 90 | ret = os.popen(f'adb -s {device} shell input swipe {x1} {y1} {x2} {y2}') 91 | ret.close() 92 | 93 | print(f'设备 {device} 操作成功') 94 | 95 | def click_by_keycode(keycode): 96 | devices = get_devices() 97 | for device in devices: 98 | ret = os.popen(f'adb -s {device} shell input keyevent {keycode}') 99 | ret.close() 100 | 101 | print(f'设备 {device} 操作成功') 102 | 103 | def install_app(device,filename): 104 | ret = os.popen(f'adb -s {device} install -r {filename}') 105 | 106 | x,y = get_window_size(device) 107 | x*=0.25 108 | y*=0.91 109 | 110 | time.sleep(10) 111 | 112 | tap_install = os.popen(f'adb -s {device} shell input tap {x} {y}') 113 | tap_install.close() 114 | 115 | result = ret.read() 116 | ret.close() 117 | if result.strip() == 'Success': 118 | print(f'设备 {device} 安装成功') 119 | 120 | def update_version(app_filename,package_name,update_version): 121 | devices = get_devices() 122 | 123 | # filename = (os.path.dirname(__file__) + '/' + app_filename).replace('\\', '/') 124 | filename = (os.path.dirname(os.path.realpath(sys.argv[0])) + '/' + app_filename).replace('\\', '/') 125 | print(filename) 126 | if not os.path.exists(filename): 127 | print('apk文件不存在,请将apk文件与该程序放在同一目录下\n') 128 | return 129 | 130 | for device in devices: 131 | ret = os.popen(f'adb -s {device} shell pm dump {package_name} | findstr "versionName"') 132 | result = ret.read() 133 | ret.close() 134 | 135 | version = re.search(r'versionName=(.*)',result) 136 | if version: 137 | version = version.group(1).strip() 138 | if version != update_version: 139 | print(f'设备 {device} 的版本为{version},需更新为 {update_version},正在卸载旧版本...') 140 | ret = os.popen(f'adb -s {device} shell pm uninstall {package_name}') 141 | result = ret.read() 142 | ret.close() 143 | 144 | if result.strip() == 'Success': 145 | print(f'设备 {device} 卸载旧版本成功,正在安装...') 146 | install_app(device,filename) 147 | 148 | else: 149 | print(f'设备 {device} 的app版本正确,无需更新') 150 | 151 | else: 152 | print(f'设备 {device} 未安装该app,正在安装...') 153 | install_app(device, filename) 154 | 155 | def reset_keyboard(): 156 | devices = get_devices() 157 | for device in devices: 158 | ret = os.popen(f'adb -s {device} shell ime set com.sohu.inputmethod.sogou.xiaomi/.SogouIME') 159 | ret.close() 160 | print(f'设备 {device} 操作成功') 161 | 162 | # def reconnect_device(): 163 | # cnt = 0 164 | # try: 165 | # devs = list(usb.core.find(find_all=1)) 166 | # for dev in devs: 167 | # print(f'正在重连设备 {dev.serial_number} ') 168 | # dev.reset() 169 | # cnt += 1 170 | # 171 | # 172 | # print(f'重连设备数:{cnt}') 173 | # 174 | # except Exception as e: 175 | # print(f'重连失败:{e}') 176 | 177 | print('命令:\n0:设备检测\n1:点击菜单\n2:点击HOME\n3:清除缓存\n4:唤醒并解锁\n5:重置输入法\n6:按下电源键\n7:按下拨号键\n' 178 | '11:更新抖音版本\n12:更新小红书版本\n13:更新微信版本\n14:更新QQ版本\n98:设备重连\n99:退出\n') 179 | while True: 180 | command = input('\n请输入命令进行相应操作:') 181 | 182 | try: 183 | command = int(command) 184 | if command == 0: 185 | device_check() 186 | elif command == 1: 187 | click_by_keycode(82) 188 | elif command == 2: 189 | click_by_keycode(3) 190 | elif command == 3: 191 | clear_cache() 192 | elif command == 4: 193 | awake_and_unlock_screen() 194 | elif command == 5: 195 | reset_keyboard() 196 | elif command == 6: 197 | click_by_keycode(26) 198 | elif command == 7: 199 | click_by_keycode(5) 200 | elif command == 11: 201 | update_version('com.ss.android.ugc.aweme_380.apk', 'com.ss.android.ugc.aweme', '3.8.0') 202 | elif command == 12: 203 | update_version('com.xingin.xhs_5.35.1_5351001.apk', 'com.xingin.xhs', '5.35.1') 204 | elif command == 13: 205 | update_version('com.tencent.mm_7.0.0_1380.apk', 'com.tencent.mm', '7.0.0') 206 | elif command == 14: 207 | update_version('com.tencent.mobileqq_7.9.8_999.apk', 'com.tencent.mobileqq', '7.9.8') 208 | 209 | # elif command == 98: 210 | # reconnect_device() 211 | 212 | elif command == 99: 213 | break 214 | else: 215 | print('未知命令,请重新输入\n') 216 | 217 | except Exception as e: 218 | print('未知命令,请重新输入\n') 219 | print('错误:',e) 220 | -------------------------------------------------------------------------------- /qq/QQ号.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingZXY/AppiumProjects/a5cc66cd4de9957aedf3d80632408b3d23b38bf6/qq/QQ号.xlsx -------------------------------------------------------------------------------- /qq/default_verify_msg.json: -------------------------------------------------------------------------------- 1 | ["\u4f60\u597d", "\u5728\u5417\uff1f", "\u53ef\u4ee5\u52a0\u4e2a\u597d\u53cb\u5417\uff1f"] -------------------------------------------------------------------------------- /qq/main.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from pytransform import pyarmor_runtime 4 | from qqaf_auto_tool_ui import Ui_MainWindow 5 | import oauth 6 | from settings import * 7 | from qqaf_auto_tool_multi import QQAFAutoToolMulti 8 | from qt_table_view import DevicesTableView 9 | from PyQt5 import QtWidgets 10 | from PyQt5.QtWidgets import * 11 | from PyQt5.QtCore import * 12 | from PyQt5.QtGui import * 13 | import sys 14 | import json 15 | import os 16 | import re 17 | import logging 18 | import time 19 | import subprocess 20 | import pandas as pd 21 | from oxls import export_from_mongoDB 22 | 23 | class QQAFQt(QtWidgets.QMainWindow, Ui_MainWindow): 24 | # 表格数据信号,用于接收信号来改变表格内容 25 | TableDataSignal = pyqtSignal(str, str, str) 26 | 27 | def __init__(self): 28 | ''' 29 | Qt窗口对象初始化 30 | ''' 31 | self.add_qq_list = [] 32 | self.start_flag = 0 33 | self.default_verify_msg_filename = 'default_verify_msg.json' 34 | 35 | QtWidgets.QMainWindow.__init__(self) 36 | Ui_MainWindow.__init__(self) 37 | self.setupUi(self) 38 | self.initUi() 39 | 40 | def initUi(self): 41 | ''' 42 | 界面初始化 43 | :return: 44 | ''' 45 | # 事件绑定 46 | self.pb_import_data_source.clicked.connect(self.click_pb_import_data_source) 47 | self.pb_import_verify_msg.clicked.connect(self.click_pb_import_verify_msg) 48 | self.pb_export_verify_msg.clicked.connect(self.click_pb_export_verify_msg) 49 | self.pb_start.clicked.connect(self.click_pb_start) 50 | 51 | # 设置默认值 52 | self.le_add_interval_1.setText('10') 53 | self.le_add_interval_2.setText('20') 54 | 55 | # 初始化默认验证消息 56 | if os.path.exists(self.default_verify_msg_filename): 57 | try: 58 | verify_msg_list = json.load(open(self.default_verify_msg_filename, 'r')) 59 | self.te_verify_msg.setText('\n'.join(verify_msg_list)) 60 | except: 61 | logging.info('Verify Msg Json File Not Correct.') 62 | 63 | # 初始化设备列表 64 | self.device_table_view = DevicesTableView(self.tableView, self.TableDataSignal) 65 | self.device_table_view.list_bind(init=True) 66 | 67 | # 启动设备检测定时器 68 | self.timer = QTimer(self) 69 | self.timer.timeout.connect(self.device_table_view.list_bind) 70 | self.timer.start(10000) 71 | 72 | def click_pb_import_data_source(self): 73 | ''' 74 | 点击导入数据来源 75 | :return: 76 | ''' 77 | if self.check_if_already_start(): 78 | return 79 | 80 | fileName_choose, _ = QFileDialog.getOpenFileName(self, "选取文件", "", "All Files (*);;Excel Files (*.xlsx);;Excel Files (*.xls);;Csv Files (*.csv)") 81 | if fileName_choose == "": 82 | return 83 | 84 | self.parse_data_source(fileName_choose) 85 | 86 | def parse_data_source(self,filename): 87 | ''' 88 | 解析数据来源 89 | :param filename: 文件名 90 | :return: 91 | ''' 92 | try: 93 | if '.xls' in filename or '.xlsx' in filename: 94 | df = pd.read_excel(filename) 95 | elif '.csv' in filename: 96 | df = pd.read_csv(filename) 97 | else: 98 | self.showMessageBox('文件格式不正确,请检查文件后缀是否为.xls/.xlsx/.csv') 99 | return 100 | 101 | data = df.values.tolist() 102 | self.add_qq_list = [{'name':i[0].strip(),'qq':str(i[1]).strip()} for i in data] 103 | self.lb_data_source_info.setText(f'QQ号总数:{len(self.add_qq_list)}') 104 | 105 | except Exception as e: 106 | self.showMessageBox(f'导入失败:{e}') 107 | 108 | def click_pb_import_verify_msg(self): 109 | if self.check_if_already_start(): 110 | return 111 | 112 | fileName_choose, _ = QFileDialog.getOpenFileName(self, "选取文件", "", "Json Files (*.json)") 113 | if fileName_choose == "": 114 | return 115 | 116 | try: 117 | verify_msg_list = json.load(open(fileName_choose, 'r')) 118 | self.te_verify_msg.setText('\n'.join(verify_msg_list)) 119 | 120 | except Exception as e: 121 | self.showMessageBox(f'导入失败:{e}') 122 | 123 | def click_pb_export_verify_msg(self): 124 | fileName_choose, _ = QFileDialog.getSaveFileName(self, "文件保存", "", "Json Files (*.json)") 125 | if fileName_choose == "": 126 | return 127 | 128 | verify_msg_list = self.te_verify_msg.toPlainText().split('\n') 129 | json.dump(verify_msg_list, open(fileName_choose, 'w')) 130 | 131 | def click_pb_start(self): 132 | if self.check_data() == False: 133 | return 134 | 135 | self.device_table_view.init_table_info() 136 | self.save_default_verify_msg() 137 | 138 | self.start_flag = 1 139 | 140 | verify_msg_list = self.te_verify_msg.toPlainText().split('\n') 141 | add_friends_interval = (int(self.le_add_interval_1.text()), int(self.le_add_interval_2.text())) 142 | 143 | backend = Backend(self.add_qq_list, verify_msg_list, add_friends_interval, self.TableDataSignal, self) 144 | backend.finish_signal.connect(self.update_flag) 145 | backend.start() 146 | 147 | self.pb_start.setDisabled(True) 148 | 149 | def save_default_verify_msg(self): 150 | ''' 151 | 保存此次验证消息,下次初始化时使用 152 | :return: 153 | ''' 154 | verify_msg_list = self.te_verify_msg.toPlainText().split('\n') 155 | json.dump(verify_msg_list, open(self.default_verify_msg_filename, 'w')) 156 | 157 | def update_flag(self,text): 158 | self.start_flag = 0 159 | self.pb_start.setDisabled(False) 160 | 161 | def check_data(self): 162 | if_ok = True 163 | if len(self.add_qq_list) <= 0: 164 | self.showMessageBox('QQ号个数必须大于0') 165 | if_ok = False 166 | 167 | if self.te_verify_msg.toPlainText() == "": 168 | self.showMessageBox('验证消息不能为空') 169 | if_ok = False 170 | 171 | interval1,interval2 = self.le_add_interval_1.text(),self.le_add_interval_2.text() 172 | if interval1.isdigit() and interval2.isdigit(): 173 | interval1, interval2 = int(interval1), int(interval2) 174 | if interval1 > interval2: 175 | self.showMessageBox('最小间隔必须小于等于最大间隔') 176 | if_ok = False 177 | else: 178 | self.showMessageBox('间隔必须为整数') 179 | if_ok = False 180 | 181 | return if_ok 182 | 183 | def check_if_already_start(self): 184 | if self.start_flag != 0: 185 | self.showMessageBox('正在添加好友,无法进行该操作') 186 | return True 187 | else: 188 | return False 189 | 190 | def click_pb_export_result(self): 191 | fileName_choose, _ = QFileDialog.getSaveFileName(self, "文件保存", "", "Excel Files (*.xlsx)") 192 | if fileName_choose == "": 193 | return 194 | 195 | field_mapping = {} 196 | 197 | 198 | def showMessageBox(self, msg, title='提示', type='i'): 199 | if type == 'i': 200 | QMessageBox.information(self, title, msg) 201 | elif type == 'q': 202 | reply = QMessageBox.question(self, title, msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.Yes) 203 | return reply 204 | 205 | 206 | class Backend(QThread): 207 | finish_signal = pyqtSignal(str) 208 | 209 | def __init__(self,add_qq_list,verify_msg_list,add_friends_interval,table_data_signal,parent=None): 210 | super(Backend, self).__init__(parent) 211 | self.add_qq_list = add_qq_list 212 | self.verify_msg_list = verify_msg_list 213 | self.add_friends_interval = add_friends_interval 214 | self.table_data_signal = table_data_signal 215 | 216 | def run(self): 217 | auto_obj = QQAFAutoToolMulti(self.add_qq_list, self.verify_msg_list, self.add_friends_interval, qt_signal=self.table_data_signal) 218 | auto_obj.init_settings() 219 | auto_obj.run() 220 | 221 | self.finish_signal.emit('Done') 222 | 223 | 224 | if __name__ == '__main__': 225 | # auth = oauth.if_auth() 226 | # if auth: 227 | try: 228 | app = QApplication(sys.argv) 229 | window = QQAFQt() 230 | window.show() 231 | sys.exit(app.exec()) 232 | except Exception as e: 233 | logging.error(e) -------------------------------------------------------------------------------- /qq/oappium.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import os 4 | import ctypes 5 | import re 6 | import logging 7 | import socket 8 | import threading 9 | from appium import webdriver 10 | from selenium.webdriver.support.ui import WebDriverWait 11 | from selenium.webdriver.support import expected_conditions as EC 12 | from selenium.webdriver.common.by import By 13 | import time 14 | import subprocess 15 | from copy import deepcopy 16 | 17 | 18 | def execute_cmd(cmd, type=0): 19 | ''' 20 | 使用subprocess执行系统命令 21 | :param cmd: 命令 22 | :param type: 类型 0:不需要返回值 1:返回执行结果 2:返回子进程 23 | :return: 24 | ''' 25 | p = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 26 | 27 | if type == 0: 28 | p.wait() 29 | p.terminate() 30 | 31 | elif type == 1: 32 | infos = [str(i, encoding='utf-8') for i in p.stdout.readlines()] 33 | p.wait() 34 | p.terminate() 35 | 36 | return infos 37 | 38 | elif type == 2: 39 | return p 40 | 41 | 42 | class AppiumAutoTool(): 43 | def __init__(self,deviceName,serial,port,driver,desired_caps): 44 | self.deviceName = deviceName 45 | self.serial = serial 46 | self.port = port 47 | self.driver = driver 48 | self.desired_caps = desired_caps 49 | self.x = self.driver.get_window_size()['width'] 50 | self.y = self.driver.get_window_size()['height'] 51 | 52 | def awake_and_unlock_screen(self): 53 | for i in range(3): 54 | screen_state = self.get_screen_lock_state() 55 | 56 | if screen_state == 0: 57 | self.driver.press_keycode(26) 58 | self.driver.swipe(1 / 2, 9 / 10, 1 / 2, 1 / 10, 1000) 59 | 60 | elif screen_state == 1: 61 | self.driver.swipe(1 / 2, 9 / 10, 1 / 2, 1 / 10, 1000) 62 | 63 | elif screen_state == 2: 64 | return 65 | 66 | time.sleep(2) 67 | 68 | logging.warning(f'Screen Unlock Failed:{self.serial}') 69 | 70 | # 获取屏幕锁定状态 0:暗屏未解锁 1:亮屏未解锁 2:亮屏已解锁 71 | def get_screen_lock_state(self): 72 | result1 = ''.join(execute_cmd(f'adb -s {self.serial} shell "dumpsys window policy|grep isStatusBarKeyguard"',type=1)) 73 | 74 | result2 = ''.join(execute_cmd(f'adb -s {self.serial} shell "dumpsys window policy|grep mShowingLockscreen"',type=1)) 75 | 76 | result3 = ''.join(execute_cmd(f'adb -s {self.serial} shell "dumpsys window policy|grep mScreenOnEarly"', type=1)) 77 | 78 | if 'isStatusBarKeyguard=true' in result1 or 'mShowingLockscreen=true' in result2: 79 | if 'mScreenOnEarly=false' in result3: 80 | state = 0 81 | else: 82 | state = 1 83 | 84 | else: 85 | state = 2 86 | 87 | return state 88 | 89 | # 按下返回键 90 | def press_back(self,sleep=2): 91 | self.driver.press_keycode(4) 92 | time.sleep(sleep) 93 | 94 | # 按下返回键(使用adb命令) 95 | def press_back_adb(self,sleep=2): 96 | execute_cmd(f'adb -s {self.serial} shell input keyevent 4') 97 | time.sleep(sleep) 98 | 99 | # 判断元素是否存在 100 | def is_el_exist(self, type, selector, timeout=3): 101 | wait_for_el = WebDriverWait(self.driver, timeout) 102 | if type == 'id': 103 | try: 104 | el = wait_for_el.until(EC.presence_of_element_located((By.ID, selector))) 105 | return el 106 | except: 107 | return False 108 | elif type == 'xpath': 109 | try: 110 | el = wait_for_el.until(EC.presence_of_element_located((By.XPATH, selector))) 111 | # print('find:',selector) 112 | return el 113 | except: 114 | # print('not find:', selector) 115 | return False 116 | 117 | # 判断元素是否可点击 118 | def is_el_clickable(self, type, selector, timeout=3): 119 | wait_for_el = WebDriverWait(self.driver, timeout) 120 | if type == 'id': 121 | try: 122 | el = wait_for_el.until(EC.element_to_be_clickable((By.ID, selector))) 123 | return el 124 | except: 125 | return False 126 | elif type == 'xpath': 127 | try: 128 | el = wait_for_el.until(EC.element_to_be_clickable((By.XPATH, selector))) 129 | return el 130 | except: 131 | return False 132 | 133 | # 判断元素是否出现在当前页面 134 | def is_el_displayed(self, type, selector, y_ratio, timeout=3): 135 | wait_for_el = WebDriverWait(self.driver, timeout) 136 | if type == 'id': 137 | try: 138 | el = wait_for_el.until(EC.visibility_of_element_located((By.ID, selector))) 139 | loc = el.location_once_scrolled_into_view 140 | if loc and loc['y'] < y_ratio * self.y: 141 | return el 142 | else: 143 | return False 144 | except: 145 | return False 146 | elif type == 'xpath': 147 | try: 148 | el = wait_for_el.until(EC.visibility_of_element_located((By.XPATH, selector))) 149 | loc = el.location_once_scrolled_into_view 150 | if loc and loc['y'] < y_ratio * self.y: 151 | return el 152 | else: 153 | return False 154 | except: 155 | return False 156 | 157 | # 点击不稳定元素(点击一次可能无效,一直点击到下一个元素出现) 158 | def click_unstable_el(self, el, next_el_type, next_el_selector, timeout=3): 159 | for i in range(10): 160 | el.click() 161 | 162 | next_el = self.is_el_exist(next_el_type, next_el_selector, timeout) 163 | if next_el: 164 | return next_el 165 | 166 | raise Exception('Click Unstable Element Error',el) 167 | 168 | # 通过xpath点击不稳定元素(点击一次可能无效,一直点击到下一个元素出现) 169 | def click_unstable_el_by_xpath(self, current_el_type,current_el_selector, next_el_type, next_el_selector, timeout=3): 170 | wait = WebDriverWait(self.driver, timeout) 171 | by = By.XPATH if current_el_type=='xpath' else By.ID 172 | 173 | for i in range(10): 174 | el = wait.until(EC.element_to_be_clickable((by,current_el_selector))) 175 | el.click() 176 | time.sleep(5) 177 | 178 | next_el = self.is_el_exist(next_el_type, next_el_selector, timeout) 179 | if next_el: 180 | return next_el 181 | 182 | raise Exception('Click Unstable Element Error', current_el_selector) 183 | 184 | # 根据屏幕比例进行滑动 185 | def swipe(self, ratio_x1, ratio_y1, ratio_x2, ratio_y2, duration): 186 | self.driver.swipe(ratio_x1 * self.x, ratio_y1 * self.y, ratio_x2 * self.x, ratio_y2 * self.y, duration) 187 | 188 | # 切换为搜狗输入法并退出driver 189 | def quit(self): 190 | execute_cmd(f'adb -s {self.serial} shell ime set com.sohu.inputmethod.sogou.xiaomi/.SogouIME') 191 | 192 | self.driver.quit() 193 | 194 | 195 | class MultiAppium(): 196 | TODAY = time.strftime('%Y-%m-%d') 197 | logging.basicConfig(filename=f'{TODAY}.log', level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') 198 | 199 | def __init__(self): 200 | self.devices = [] 201 | self.appium_port = 30000 202 | self.bp_port = 40000 203 | self.socket_obj = socket.socket() 204 | self.socket_hostname = socket.getfqdn(socket.gethostname()) 205 | self.socket_addr = socket.gethostbyname(self.socket_hostname) 206 | self.server_threads = [] 207 | self.task_threads = [] 208 | self.target = None 209 | self.desired_caps = None 210 | self.server_processes = [] 211 | 212 | def showMessagebox(self,title, text, style=0): 213 | return ctypes.windll.user32.MessageBoxW(0, text, title, style) 214 | 215 | def check_environment(self): 216 | try: 217 | result = execute_cmd('adb devices -l',type=1) 218 | devices = [i for i in result if 'model' in i] 219 | 220 | if devices: 221 | return True 222 | else: 223 | self.showMessagebox('提示', '当前无设备连接,请检查设备连接情况!') 224 | 225 | except: 226 | self.showMessagebox('提示','运行失败,端口被占用!请关闭360手机助手的后台进程!') 227 | 228 | def get_devices(self): 229 | device_serial = [] 230 | result = execute_cmd('adb devices -l', type=1) 231 | devices_info = [i for i in result if 'model' in i] 232 | 233 | logging.info('Devices Info:') 234 | for i, info in enumerate(devices_info): 235 | serial = str(re.search(r'(.*?)device', info).group(1).strip()) 236 | deviceName = re.search(r'model:(.*?) device', info).group(1).strip() 237 | port = str(self.get_available_port_by_socket()) 238 | 239 | self.devices.append( 240 | { 241 | 'deviceName': deviceName, 242 | 'serial': serial, 243 | 'port': port, 244 | } 245 | ) 246 | 247 | device_serial.append(serial) 248 | 249 | 250 | logging.info(serial+' '+deviceName+' '+port+' ') 251 | 252 | return device_serial 253 | 254 | # 获取屏幕大小 255 | def get_window_size(self,device): 256 | result = ''.join(execute_cmd(f'adb -s {device} shell wm size',type=1)) 257 | 258 | if result: 259 | clear_btn_loc = re.search(r'Physical size: (\d+)x(\d+)', result) 260 | x = int(clear_btn_loc.group(1)) 261 | y = int(clear_btn_loc.group(2)) 262 | 263 | return x, y 264 | 265 | # 唤醒并解锁屏幕 266 | def awake_and_unlock_screen(self,devices): 267 | for device in devices: 268 | result1 = ''.join(execute_cmd(f'adb -s {device} shell "dumpsys window policy|grep isStatusBarKeyguard"', type=1)) 269 | 270 | result2 = ''.join(execute_cmd(f'adb -s {device} shell "dumpsys window policy|grep mShowingLockscreen"', type=1)) 271 | 272 | result3 = ''.join(execute_cmd(f'adb -s {device} shell "dumpsys window policy|grep mScreenOnEarly"', type=1)) 273 | 274 | if 'isStatusBarKeyguard=true' in result1 or 'mShowingLockscreen=true' in result2: 275 | x, y = self.get_window_size(device) 276 | x1, y1, x2, y2 = 1 / 2 * x, 9 / 10 * y, 1 / 2 * x, 1 / 10 * y 277 | 278 | if 'mScreenOnEarly=false' in result3: 279 | execute_cmd(f'adb -s {device} shell input keyevent 26') 280 | 281 | execute_cmd(f'adb -s {device} shell input swipe {x1} {y1} {x2} {y2}') 282 | 283 | def get_available_port_by_socket(self): 284 | while True: 285 | try: 286 | self.socket_obj.connect((self.socket_addr, self.appium_port)) 287 | self.socket_obj.close() 288 | self.appium_port += 1 289 | except: 290 | port = self.appium_port 291 | self.appium_port += 1 292 | return port 293 | 294 | def get_available_bp_port_by_socket(self): 295 | while True: 296 | try: 297 | self.socket_obj.connect((self.socket_addr, self.bp_port)) 298 | self.socket_obj.close() 299 | self.bp_port += 1 300 | except: 301 | port = self.bp_port 302 | self.bp_port += 1 303 | return port 304 | 305 | def kill_all_appium(self): 306 | execute_cmd('taskkill /f /t /im node.exe') 307 | 308 | def start_server(self,serial, port): 309 | logging.info(f'Thread Start-{serial}') 310 | bp_port = self.get_available_bp_port_by_socket() 311 | logging.info(f'Start Server:{serial} {port} {bp_port}') 312 | 313 | # cmd = r'node C:\Users\ethan\AppData\Local\Programs\Appium\resources\app\node_modules\appium\build\lib\main.js -p {} -bp {} -U {}'.format(port, bp_port, serial) 314 | cmd = r'appium -p {} -bp {} -U {}'.format(port, bp_port, serial) 315 | process = subprocess.Popen(cmd,shell=True) 316 | self.server_processes.append(process) 317 | 318 | logging.info(f'Server Start Succeed:{process.pid} {serial} {port} {bp_port}') 319 | 320 | def get_server_threads(self): 321 | for device in self.devices: 322 | serial = device['serial'] 323 | port = device['port'] 324 | 325 | t = threading.Thread(target=self.start_server,args=(serial,port)) 326 | self.server_threads.append(t) 327 | 328 | def get_task_threads(self): 329 | get_driver_threads = [] 330 | for device in self.devices: 331 | deviceName = device['deviceName'] 332 | serial = device['serial'] 333 | port = device['port'] 334 | 335 | caps = deepcopy(self.desired_caps) 336 | 337 | caps['deviceName'] = deviceName 338 | caps['udid'] = serial 339 | 340 | t = threading.Thread(target=self.get_driver,args=(serial,deviceName,port,self.target,caps)) 341 | t.start() 342 | get_driver_threads.append(t) 343 | 344 | for t in get_driver_threads: 345 | t.join() 346 | 347 | def get_driver(self,serial,deviceName,port,target,desired_caps,try_time=3): 348 | for i in range(try_time): 349 | try: 350 | driver = webdriver.Remote(f'http://localhost:{port}/wd/hub', desired_caps) 351 | logging.info(f'Get Driver Succeed:{deviceName} {serial}') 352 | t = threading.Thread(target=target, args=(deviceName, serial, port, driver, desired_caps)) 353 | self.task_threads.append(t) 354 | return 355 | except Exception as e: 356 | logging.error(f'Driver Start Failed:{e} Retring:{i+1}') 357 | 358 | logging.warning(f'Get Driver Failed:{deviceName} {serial}') 359 | 360 | def run(self): 361 | if_env_ok = self.check_environment() 362 | if if_env_ok: 363 | task_list = [] 364 | # 获取设备 365 | devices = self.get_devices() 366 | 367 | # 唤醒并解锁 368 | self.awake_and_unlock_screen(devices) 369 | 370 | # 终止所有appium 371 | self.kill_all_appium() 372 | 373 | # 启动server线程 374 | self.get_server_threads() 375 | for t1 in self.server_threads: 376 | t1.setDaemon(True) 377 | t1.start() 378 | 379 | time.sleep(len(self.server_threads)) 380 | 381 | # 启动driver线程 382 | self.get_task_threads() 383 | for t2 in self.task_threads: 384 | task_list.append(t2) 385 | t2.setDaemon(True) 386 | t2.start() 387 | 388 | # 等待所有任务执行完成 389 | for task in task_list: 390 | task.join() 391 | 392 | # 关闭所有appium服务器进程 393 | for process in self.server_processes: 394 | process.terminate() 395 | logging.info(f'Server End Succeed:{process.pid}') 396 | 397 | # 终止所有appium 398 | self.kill_all_appium() 399 | 400 | -------------------------------------------------------------------------------- /qq/oauth.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import uuid 3 | from hashlib import sha224 4 | import pickle 5 | import ctypes 6 | import subprocess 7 | import re 8 | 9 | AUTH_FILE = 'auth.config' 10 | 11 | class AuthError(Exception): 12 | pass 13 | 14 | def get_encrypted_mac(mac): 15 | mac = mac.replace('-','').lower() 16 | encrypt_mac = sha224(mac.encode()) 17 | 18 | return encrypt_mac.hexdigest() 19 | 20 | def create_allowed_macs(allow_macs): 21 | allow_macs = list(map(get_encrypted_mac,allow_macs)) 22 | with open(AUTH_FILE,'wb') as f: 23 | pickle.dump(allow_macs,f) 24 | 25 | def get_current_encrypted_mac(): 26 | node = uuid.getnode() 27 | mac = uuid.UUID(int=node).hex[-12:] 28 | encrypt_mac = sha224(mac.encode()) 29 | 30 | return encrypt_mac.hexdigest() 31 | 32 | def get_current_encrypted_mac_yt(): 33 | try: 34 | p = subprocess.Popen("ipconfig /all", stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, 35 | shell=True) 36 | ipconfig_info = p.stdout.read().decode('gbk') 37 | 38 | patterns = [ 39 | '以太网适配器 以太网:.*?物理地址.*?: (.{17})', 40 | '以太网适配器 本地连接:.*?物理地址.*?: (.{17})' 41 | ] 42 | 43 | m = None 44 | for pattern in patterns: 45 | m = re.search(pattern,ipconfig_info,re.S) 46 | if m != None: 47 | break 48 | 49 | mac = m.group(1).replace('-','').lower() 50 | # print(ipconfig_info) 51 | # print(mac) 52 | 53 | encrypt_mac = sha224(mac.encode()) 54 | return encrypt_mac.hexdigest() 55 | 56 | except Exception as e: 57 | raise AuthError('Get Mac Failed: %s ' % e) 58 | 59 | def get_allowed_macs(): 60 | with open(AUTH_FILE,'rb') as f: 61 | allow_macs = pickle.load(f) 62 | return allow_macs 63 | 64 | def if_auth(): 65 | try: 66 | allowed = get_allowed_macs() 67 | current = get_current_encrypted_mac_yt() 68 | if current in allowed: 69 | return True 70 | else: 71 | ctypes.windll.user32.MessageBoxW(0, '您的计算机无权限对该程序进行操作!', '提示', 0) 72 | return False 73 | 74 | except Exception as e: 75 | ctypes.windll.user32.MessageBoxW(0, f'{e}', '错误', 0) 76 | return False 77 | 78 | 79 | 80 | 81 | if __name__ == '__main__': 82 | allow_macs = ['B0-FC-36-78-C7-52','94-DE-80-3C-7B-94','00-E0-4C-06-6C-BC', 83 | '1C-6F-65-BB-BA-4C','30-9C-23-C6-5F-38','08-00-27-20-4D-C0'] 84 | create_allowed_macs(allow_macs) 85 | 86 | # get_current_encrypted_mac_yt() 87 | 88 | -------------------------------------------------------------------------------- /qq/oxls.py: -------------------------------------------------------------------------------- 1 | from openpyxl import Workbook 2 | import pymongo 3 | 4 | """ 5 | Excel文件操作 6 | """ 7 | 8 | # 从内存导出Excel文件 9 | def export_from_memory(header,data,filename,sheetname='Sheet1'): 10 | output_workbook = Workbook() 11 | output_worksheet = output_workbook.create_sheet(sheetname, index=0) 12 | data.insert(0, header) 13 | for row in data: 14 | output_worksheet.append(row) 15 | 16 | output_workbook.save(filename) 17 | 18 | 19 | # 从MongoDB导出Excel文件 20 | def export_from_mongoDB(client, db, collection, field_mapping, query=None, sort_by=None, sheetname='Sheet1', filename='result.xlsx',auth=None): 21 | ''' 22 | 从MongoDB导出Excel文件 23 | :param client: MongoDB连接串 24 | :param db: 库名 25 | :param collection:表名 26 | :param field_mapping:字段映射 传入一个有序字典 27 | :param query: 查询条件 28 | :param sort_by: 排序条件 29 | :param sheetname: sheet名 30 | :param filename: 文件名 31 | :param auth: 数据库账号密码 32 | ''' 33 | client = pymongo.MongoClient(client) 34 | if auth: 35 | client.admin.authenticate(*auth) 36 | 37 | db = client[db] 38 | collection = db[collection] 39 | 40 | output_workbook = Workbook() 41 | output_worksheet = output_workbook.create_sheet(sheetname, index=0) 42 | 43 | header = list(field_mapping.values()) 44 | output_worksheet.append(header) 45 | 46 | query = {} if not query else query 47 | results = collection.find(query) 48 | 49 | if sort_by:results.sort(sort_by) 50 | 51 | for result in results: 52 | row = [result[i] for i in field_mapping.keys()] 53 | output_worksheet.append(row) 54 | 55 | output_workbook.save(filename) 56 | 57 | 58 | -------------------------------------------------------------------------------- /qq/pytransform.py: -------------------------------------------------------------------------------- 1 | # Because ctypes is new from Python 2.5, so pytransform doesn't work 2 | # before Python 2.5 3 | # 4 | from ctypes import cdll, c_char, c_char_p, c_int, c_void_p, \ 5 | pythonapi, py_object, PYFUNCTYPE 6 | 7 | import os 8 | import sys 9 | import platform 10 | import struct 11 | 12 | # 13 | # Hardware type 14 | # 15 | HT_HARDDISK, HT_IFMAC, HT_IPV4, HT_IPV6, HT_DOMAIN = range(5) 16 | 17 | # 18 | # Global 19 | # 20 | _pytransform = None 21 | _get_error_msg = None 22 | _debug_mode = sys.flags.debug 23 | 24 | class PytransformError(Exception): 25 | pass 26 | 27 | def dllmethod(func): 28 | def wrap(*args, **kwargs): 29 | # args = [(s.encode() if isinstance(s, str) else s) for s in args] 30 | result = func(*args, **kwargs) 31 | if isinstance(result, int) and result != 0: 32 | errmsg = _get_error_msg() 33 | raise PytransformError(errmsg) 34 | return result 35 | return wrap 36 | 37 | @dllmethod 38 | def init_pytransform(): 39 | major, minor = sys.version_info[0:2] 40 | # Python2.5 no sys.maxsize but sys.maxint 41 | # bitness = 64 if sys.maxsize > 2**32 else 32 42 | prototype = PYFUNCTYPE(c_int, c_int, c_int, c_void_p) 43 | init_module = prototype(('init_module', _pytransform)) 44 | return init_module(major, minor, pythonapi._handle) 45 | 46 | @dllmethod 47 | def init_runtime(): 48 | prototype = PYFUNCTYPE(c_int, c_int, c_int, c_int, c_int) 49 | _init_runtime = prototype(('init_runtime', _pytransform)) 50 | return _init_runtime(0, 0, 0, 0) 51 | 52 | @dllmethod 53 | def encrypt_code_object(pubkey, co, flags): 54 | prototype = PYFUNCTYPE(py_object, py_object, py_object, c_int) 55 | dlfunc = prototype(('encrypt_code_object', _pytransform)) 56 | return dlfunc(pubkey, co, flags) 57 | 58 | def generate_capsule(licfile): 59 | prikey, pubkey, prolic = _generate_project_capsule() 60 | capkey, newkey = _generate_pytransform_key(licfile, pubkey) 61 | return prikey, pubkey, capkey, newkey, prolic 62 | 63 | @dllmethod 64 | def _generate_project_capsule(): 65 | prototype = PYFUNCTYPE(py_object) 66 | dlfunc = prototype(('generate_project_capsule', _pytransform)) 67 | return dlfunc() 68 | 69 | @dllmethod 70 | def _generate_pytransform_key(licfile, pubkey): 71 | prototype = PYFUNCTYPE(py_object, c_char_p, py_object) 72 | dlfunc = prototype(('generate_pytransform_key', _pytransform)) 73 | return dlfunc(licfile.encode(), pubkey) 74 | 75 | @dllmethod 76 | def generate_license_file(filename, priname, rcode, start=-1, count=1): 77 | prototype = PYFUNCTYPE(c_int, c_char_p, c_char_p, c_char_p, c_int, c_int) 78 | dlfunc = prototype(('generate_project_license_files', _pytransform)) 79 | return dlfunc(filename.encode(), priname.encode(), rcode.encode(), 80 | start, count) 81 | 82 | @dllmethod 83 | def get_registration_code(): 84 | prototype = PYFUNCTYPE(py_object) 85 | dlfunc = prototype(('get_registration_code', _pytransform)) 86 | return dlfunc() 87 | 88 | def get_expired_days(): 89 | prototype = PYFUNCTYPE(py_object) 90 | dlfunc = prototype(('get_expired_days', _pytransform)) 91 | return dlfunc() 92 | 93 | def get_hd_info(hdtype, size=256): 94 | t_buf = c_char * size 95 | buf = t_buf() 96 | if (_pytransform.get_hd_info(hdtype, buf, size) == -1): 97 | raise PytransformError(_get_error_msg()) 98 | return buf.value.decode() 99 | 100 | def show_hd_info(): 101 | return _pytransform.show_hd_info() 102 | 103 | def get_license_info(): 104 | info = { 105 | 'expired': 'Never', 106 | 'restrict_mode': 'Enabled', 107 | 'HARDDISK': 'Any', 108 | 'IFMAC': 'Any', 109 | 'IFIPV4': 'Any', 110 | 'DOMAIN': 'Any', 111 | 'CODE': '', 112 | } 113 | rcode = get_registration_code().decode() 114 | if rcode is None: 115 | raise PytransformError(_get_error_msg()) 116 | index = 0 117 | if rcode.startswith('*TIME:'): 118 | from time import ctime 119 | index = rcode.find('\n') 120 | info['expired'] = ctime(float(rcode[6:index])) 121 | index += 1 122 | 123 | if rcode[index:].startswith('*FLAGS:'): 124 | info['restrict_mode'] = 'Disabled' 125 | index += len('*FLAGS:') + 1 126 | 127 | prev = None 128 | start = index 129 | for k in ['HARDDISK', 'IFMAC', 'IFIPV4', 'DOMAIN', 'FIXKEY', 'CODE']: 130 | index = rcode.find('*%s:' % k) 131 | if index > -1: 132 | if prev is not None: 133 | info[prev] = rcode[start:index] 134 | prev = k 135 | start = index + len(k) + 2 136 | info['CODE'] = rcode[start:] 137 | 138 | return info 139 | 140 | def format_platname(): 141 | plat = platform.system().lower() 142 | bitness = struct.calcsize('P'.encode()) * 8 143 | mach = platform.machine().lower() 144 | return os.path.join('%s%s' % (plat, bitness), 'arm') \ 145 | if plat == 'linux' and (mach[:3] == 'arm' or mach[:5] == 'aarch') \ 146 | else '%s%s' % (plat, bitness) 147 | 148 | # Load _pytransform library 149 | def _load_library(path=None, is_runtime=0): 150 | path = os.path.dirname(__file__) if path is None \ 151 | else os.path.normpath(path) 152 | 153 | plat = platform.system().lower() 154 | if plat == 'linux': 155 | filename = os.path.abspath(os.path.join(path, '_pytransform.so')) 156 | elif plat == 'darwin': 157 | filename = os.path.join(path, '_pytransform.dylib') 158 | elif plat == 'windows': 159 | filename = os.path.join(path, '_pytransform.dll') 160 | elif plat == 'freebsd': 161 | filename = os.path.join(path, '_pytransform.so') 162 | else: 163 | raise PytransformError('Platform %s not supported' % plat) 164 | 165 | if not os.path.exists(filename): 166 | if is_runtime: 167 | raise PytransformError('Could not find "%s"' % filename) 168 | bitness = struct.calcsize('P'.encode()) * 8 169 | libpath = os.path.join(path, 'platforms', format_platname()) 170 | filename = os.path.join(libpath, os.path.basename(filename)) 171 | if not os.path.exists(filename): 172 | raise PytransformError('Could not find "%s"' % filename) 173 | try: 174 | m = cdll.LoadLibrary(filename) 175 | except Exception as e: 176 | raise PytransformError('Load %s failed:\n%s' % (filename, e)) 177 | 178 | # Removed from v4.6.1 179 | # if plat == 'linux': 180 | # m.set_option(-1, find_library('c').encode()) 181 | 182 | if not os.path.abspath('.') == os.path.abspath(path): 183 | m.set_option(1, path.encode()) 184 | 185 | # Required from Python3.6 186 | m.set_option(2, sys.byteorder.encode()) 187 | 188 | m.set_option(3, c_char_p(_debug_mode)) 189 | m.set_option(4, c_char_p(not is_runtime)) 190 | 191 | return m 192 | 193 | def pyarmor_init(path=None, is_runtime=0): 194 | global _pytransform 195 | global _get_error_msg 196 | if _pytransform is None: 197 | _pytransform = _load_library(path, is_runtime) 198 | _get_error_msg = _pytransform.get_error_msg 199 | _get_error_msg.restype = c_char_p 200 | return init_pytransform() 201 | 202 | def pyarmor_runtime(path=None): 203 | try: 204 | if _pytransform is not None: 205 | raise PytransformError('_pytransform can not be loaded twice') 206 | if pyarmor_init(path, is_runtime=1) == 0: 207 | init_runtime() 208 | except PytransformError as e: 209 | print(e) 210 | sys.exit(1) 211 | 212 | # 213 | # Deprecated functions from v5.1 214 | # 215 | @dllmethod 216 | def encrypt_project_files(proname, filelist, mode=0): 217 | prototype = PYFUNCTYPE(c_int, c_char_p, py_object, c_int) 218 | dlfunc = prototype(('encrypt_project_files', _pytransform)) 219 | return dlfunc(proname.encode(), filelist, mode) 220 | 221 | def generate_project_capsule(licfile): 222 | prikey, pubkey, prolic = _generate_project_capsule() 223 | capkey = _encode_capsule_key_file(licfile) 224 | return prikey, pubkey, capkey, prolic 225 | 226 | @dllmethod 227 | def _encode_capsule_key_file(licfile): 228 | prototype = PYFUNCTYPE(py_object, c_char_p, c_char_p) 229 | dlfunc = prototype(('encode_capsule_key_file', _pytransform)) 230 | return dlfunc(licfile.encode(), None) 231 | 232 | @dllmethod 233 | def encrypt_files(key, filelist, mode=0): 234 | t_key = c_char * 32 235 | prototype = PYFUNCTYPE(c_int, t_key, py_object, c_int) 236 | dlfunc = prototype(('encrypt_files', _pytransform)) 237 | return dlfunc(t_key(*key), filelist, mode) 238 | 239 | @dllmethod 240 | def generate_module_key(pubname, key): 241 | t_key = c_char * 32 242 | prototype = PYFUNCTYPE(py_object, c_char_p, t_key, c_char_p) 243 | dlfunc = prototype(('generate_module_key', _pytransform)) 244 | return dlfunc(pubname.encode(), t_key(*key), None) 245 | 246 | # 247 | # Compatible for PyArmor v3.0 248 | # 249 | @dllmethod 250 | def old_init_runtime(systrace=0, sysprofile=1, threadtrace=0, threadprofile=1): 251 | '''Only for old version, before PyArmor 3''' 252 | pyarmor_init(is_runtime=1) 253 | prototype = PYFUNCTYPE(c_int, c_int, c_int, c_int, c_int) 254 | _init_runtime = prototype(('init_runtime', _pytransform)) 255 | return _init_runtime(systrace, sysprofile, threadtrace, threadprofile) 256 | 257 | @dllmethod 258 | def import_module(modname, filename): 259 | '''Only for old version, before PyArmor 3''' 260 | prototype = PYFUNCTYPE(py_object, c_char_p, c_char_p) 261 | _import_module = prototype(('import_module', _pytransform)) 262 | return _import_module(modname.encode(), filename.encode()) 263 | 264 | @dllmethod 265 | def exec_file(filename): 266 | '''Only for old version, before PyArmor 3''' 267 | prototype = PYFUNCTYPE(c_int, c_char_p) 268 | _exec_file = prototype(('exec_file', _pytransform)) 269 | return _exec_file(filename.encode()) 270 | -------------------------------------------------------------------------------- /qq/qqaf_auto_tool.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from appium import webdriver 3 | from selenium.webdriver.support.ui import WebDriverWait 4 | from selenium.webdriver.support import expected_conditions as EC 5 | from selenium.webdriver.common.by import By 6 | import logging 7 | import time 8 | import oappium 9 | import pymongo 10 | from settings import * 11 | import random 12 | import func_timeout 13 | from PyQt5.QtWidgets import QApplication 14 | 15 | # 超时时间 16 | TIMEOUT = 60 17 | 18 | # 待添加的QQ号列表 19 | ADD_QQ_LIST = [ 20 | # {'name':'测试','qq':'195627250'}, 21 | # {'name':'煤油','qq':'123456'}, # 4 22 | # {'name':'巨田','qq':'654321'}, # 5 23 | 24 | {'name':'张三','qq':'615215416'}, # 4 25 | # {'name':'李四','qq':'498546235'}, # 0 26 | # {'name':'王五','qq':'845125164'}, # 0 27 | {'name':'小红','qq':'698565421'}, # 4 28 | {'name':'小蓝','qq':'265215421'}, # 4 29 | # {'name':'小绿','qq':'123525461'}, # 0 30 | {'name':'小橙','qq':'135246952'}, # 4 31 | {'name':'小黄','qq':'265425187'}, # 4 32 | # {'name':'小青','qq':'236965451'}, # 0 33 | # {'name':'小紫','qq':'285421542'}, # 0 34 | 35 | ] 36 | 37 | # 验证消息列表 38 | VERIFY_MSG_LIST = ['你好','可以加个好友吗?'] 39 | 40 | # 加好友间隔 41 | ADD_FRIENDS_INTERVAL = (10,20) 42 | 43 | # Qt信号 44 | QT_SIGNAL = None 45 | 46 | 47 | class QQAFAutoTool(oappium.AppiumAutoTool): 48 | def __init__(self,deviceName,serial,port,driver,desired_caps,shuffle_list=None): 49 | super().__init__(deviceName,serial,port,driver,desired_caps) 50 | self.wait = WebDriverWait(self.driver, TIMEOUT) 51 | self.current_qq = '' 52 | self.current_qq_name = '' 53 | self.client = pymongo.MongoClient(MONGO_CLIENT) 54 | self.client.admin.authenticate('root', 'csdjgs9B15BS') 55 | self.db = self.client[MONGO_DB] 56 | self.collection = self.db[MONGO_COLLECTION] 57 | self.shuffle_list = shuffle_list 58 | self.init_shuffle_list() 59 | 60 | 61 | def init_shuffle_list(self): 62 | ''' 63 | 初始化待添加QQ的乱序列表 64 | :return: 65 | ''' 66 | if self.shuffle_list == None: 67 | self.shuffle_list = ADD_QQ_LIST.copy() 68 | random.shuffle(self.shuffle_list) 69 | 70 | def filter_shuffle_list(self): 71 | ''' 72 | 过滤乱序列表,从列表中移除数据库存在的数据 73 | :return: 74 | ''' 75 | db_results = self.collection.find({'device_qq':self.current_qq}) 76 | results_qq = [result['add_qq'] for result in db_results] 77 | for add_qq in self.shuffle_list: 78 | if add_qq['qq'] in results_qq: 79 | self.shuffle_list.remove(add_qq) 80 | 81 | def get_current_account_info(self): 82 | ''' 83 | 获取当前账号信息 84 | :return: 85 | ''' 86 | el_head = self.wait.until(EC.element_to_be_clickable((By.ID,'com.tencent.mobileqq:id/conversation_head'))) 87 | el_head.click() 88 | 89 | time.sleep(5) 90 | 91 | el_account_manage = self.click_unstable_el_by_xpath('xpath','//android.widget.Button[@content-desc="设置"]/android.widget.TextView','id','com.tencent.mobileqq:id/account_switch') 92 | el_account_manage.click() 93 | 94 | el_name = self.wait.until(EC.presence_of_element_located((By.XPATH,'//android.widget.ImageView[@resource-id="com.tencent.mobileqq:id/icon"]/following-sibling::android.widget.RelativeLayout/android.widget.TextView[@resource-id="com.tencent.mobileqq:id/name"]'))) 95 | el_qq = self.wait.until(EC.presence_of_element_located((By.XPATH,'//android.widget.ImageView[@resource-id="com.tencent.mobileqq:id/icon"]/following-sibling::android.widget.RelativeLayout/android.widget.TextView[@resource-id="com.tencent.mobileqq:id/account"]'))) 96 | 97 | self.current_qq_name = el_name.text 98 | self.current_qq = el_qq.text 99 | 100 | self.emit_to_qt('当前账号',self.current_qq) 101 | 102 | self.press_back(3) 103 | self.press_back(3) 104 | self.press_back(3) 105 | 106 | def if_qq_in_db(self,qq): 107 | ''' 108 | 检测QQ是否在数据库中存在 109 | :param qq: 待添加的QQ 110 | :return: 111 | ''' 112 | result = self.collection.find_one({'$and':[{'device_qq':self.current_qq},{'add_qq':qq}]}) 113 | return True if result else False 114 | 115 | def if_qq_refuse_to_add(self): 116 | ''' 117 | 检测QQ是否拒绝任何人添加 118 | :return: 119 | ''' 120 | if not self.is_el_exist('xpath','//android.widget.LinearLayout/android.widget.EditText[@resource-id="com.tencent.mobileqq:id/name"]',5): 121 | return True 122 | 123 | @func_timeout.func_set_timeout(10) 124 | def check_if_qq_not_found(self): 125 | ''' 126 | 为此方法添加超时机制,因为Appium的Bug导致该页面有时无法加载完成 127 | :return: 128 | ''' 129 | if not self.is_el_exist('xpath','//android.widget.FrameLayout[@content-desc="查看大头像"]/android.widget.ImageView[2]',5): 130 | return True 131 | 132 | def if_qq_not_found(self): 133 | ''' 134 | 检测QQ是否不存在 135 | :return: 136 | ''' 137 | try: 138 | if_not_found = self.check_if_qq_not_found() 139 | return if_not_found 140 | except func_timeout.exceptions.FunctionTimedOut: 141 | # logging.info('timeout') 142 | return True 143 | 144 | def if_qq_already_friend(self): 145 | ''' 146 | 检测QQ是否已添加为好友 147 | :return: 148 | ''' 149 | if self.is_el_exist('xpath','//android.widget.Button[@content-desc="发消息"]'): 150 | return True 151 | 152 | def if_need_answer_question(self): 153 | ''' 154 | 检测QQ是否需要回答问题 155 | :return: 156 | ''' 157 | if self.is_el_exist('xpath','//android.widget.EditText[@resource-id="com.tencent.mobileqq:id/name" and @text="输入答案"]'): 158 | return True 159 | 160 | def save_to_mongo(self,item): 161 | ''' 162 | 保存至数据库 163 | :param item: 数据项 164 | :return: 165 | ''' 166 | item['device_serial'] = self.serial 167 | item['device_qq'] = self.current_qq 168 | item['device_qq_name'] = self.current_qq_name 169 | item['add_time'] = time.time() 170 | 171 | self.collection.update_one({'device_qq':item['device_qq'],'add_qq':item['add_qq'],'add_qq_type':item['add_qq_type']},{'$set':item},True) 172 | 173 | def add_friends(self): 174 | ''' 175 | 添加好友主流程 176 | :return: 177 | ''' 178 | el_plus = self.wait.until(EC.element_to_be_clickable((By.XPATH,'//android.widget.ImageView[@content-desc="快捷入口"]'))) 179 | el_plus.click() 180 | 181 | el_add_friends = self.wait.until(EC.element_to_be_clickable((By.XPATH, '//android.widget.LinearLayout[@content-desc="加好友/群 按钮"]'))) 182 | el_add_friends.click() 183 | 184 | el_search_bar1 = self.wait.until(EC.element_to_be_clickable((By.XPATH, '//android.widget.EditText[@content-desc="搜索栏、QQ号、手机号、群、公众号"]'))) 185 | el_search_bar1.click() 186 | 187 | self.filter_shuffle_list() 188 | wait_to_add = len(self.shuffle_list) 189 | self.emit_to_qt('待添加',str(wait_to_add)) 190 | 191 | time.sleep(3) 192 | 193 | for friend in self.shuffle_list: 194 | wait_to_add -= 1 195 | self.emit_to_qt('待添加', str(wait_to_add)) 196 | 197 | name,qq = friend['name'],friend['qq'] 198 | if self.if_qq_in_db(qq): 199 | continue 200 | 201 | item = {'add_qq':qq,'add_qq_name':name,'verify_msg':''} 202 | 203 | el_search_bar2 = self.wait.until(EC.presence_of_element_located((By.ID, 'com.tencent.mobileqq:id/et_search_keyword'))) 204 | el_search_bar2.send_keys(qq) 205 | 206 | el_find_person = self.wait.until(EC.element_to_be_clickable((By.XPATH, f'//android.widget.LinearLayout[@content-desc="找人:{qq}"]'))) 207 | el_find_person.click() 208 | 209 | time.sleep(2) 210 | 211 | if self.if_qq_not_found(): 212 | # 添加类型4:QQ号不存在 213 | item['add_qq_type'] = 4 214 | 215 | self.save_to_mongo(item) 216 | self.press_back_adb() 217 | 218 | el_search_bar1 = self.wait.until(EC.element_to_be_clickable((By.XPATH, '//android.widget.EditText[@content-desc="搜索栏、QQ号、手机号、群、公众号"]'))) 219 | el_search_bar1.click() 220 | continue 221 | 222 | if self.if_qq_already_friend(): 223 | # 添加类型3:已添加为好友 224 | item['add_qq_type'] = 3 225 | self.save_to_mongo(item) 226 | self.press_back() 227 | continue 228 | 229 | el_add = self.wait.until(EC.element_to_be_clickable((By.ID, 'com.tencent.mobileqq:id/txt'))) 230 | el_add.click() 231 | 232 | if self.if_qq_refuse_to_add(): 233 | # 添加类型5:拒绝任何人添加 234 | item['add_qq_type'] = 5 235 | self.save_to_mongo(item) 236 | self.press_back() 237 | continue 238 | 239 | if self.if_need_answer_question(): 240 | # 添加类型1:问题限制 241 | item['add_qq_type'] = 1 242 | self.save_to_mongo(item) 243 | self.press_back() 244 | continue 245 | 246 | el_verify_msg = self.is_el_exist('xpath','//android.widget.RelativeLayout/android.widget.EditText[@resource-id="com.tencent.mobileqq:id/name"]') 247 | if el_verify_msg: 248 | msg = random.choice(VERIFY_MSG_LIST) 249 | el_verify_msg.clear() 250 | el_verify_msg.send_keys(msg) 251 | # 添加类型0:正常验证 252 | item['add_qq_type'] = 0 253 | item['verify_msg'] = msg 254 | else: 255 | # 添加类型2:允许任何人添加 256 | item['add_qq_type'] = 2 257 | 258 | el_remark = self.wait.until(EC.presence_of_element_located((By.XPATH, '//android.widget.LinearLayout/android.widget.EditText[@resource-id="com.tencent.mobileqq:id/name"]'))) 259 | el_remark.clear() 260 | el_remark.send_keys(name) 261 | 262 | el_send = self.wait.until(EC.element_to_be_clickable((By.ID, 'com.tencent.mobileqq:id/ivTitleBtnRightText'))) 263 | el_send.click() 264 | 265 | self.save_to_mongo(item) 266 | 267 | self.wait.until(EC.element_to_be_clickable((By.XPATH, '//android.widget.TextView[@resource-id="com.tencent.mobileqq:id/ivTitleBtnLeft" and @text="返回"]'))) 268 | self.press_back(3) 269 | 270 | time.sleep(random.randint(*ADD_FRIENDS_INTERVAL)) 271 | 272 | def run(self): 273 | self.get_current_account_info() 274 | 275 | self.add_friends() 276 | 277 | self.quit() 278 | 279 | def restart(self): 280 | try: 281 | self.driver.quit() 282 | except: 283 | pass 284 | 285 | for i in range(3): 286 | try: 287 | self.driver = webdriver.Remote(f'http://localhost:{self.port}/wd/hub', self.desired_caps) 288 | self.__init__(self.deviceName,self.serial,self.port,self.driver,self.desired_caps,self.shuffle_list) 289 | break 290 | except Exception as e: 291 | time.sleep(5) 292 | logging.warning(f'Restart to Get Driver Failed:{e} {self.serial} Retrying:{i+1}') 293 | 294 | def start(self): 295 | # 更新设备状态为'运行中' 296 | self.emit_to_qt('状态', DEVICE_STATE_DICT[4]) 297 | 298 | max_retry = 100 299 | sleep_time = 60 300 | 301 | for i in range(max_retry): 302 | try: 303 | self.run() 304 | logging.info(f'Finished.{self.serial}') 305 | # 更新设备状态为'运行完成' 306 | self.emit_to_qt('状态', DEVICE_STATE_DICT[5]) 307 | return 308 | except Exception as e: 309 | time.sleep(sleep_time) 310 | logging.error(f'Running Error:{e} {self.serial} Retrying:{i+1}') 311 | self.restart() 312 | 313 | # 更新设备状态为'运行异常' 314 | self.emit_to_qt('状态', DEVICE_STATE_DICT[6]) 315 | 316 | def emit_to_qt(self,field_name,data): 317 | ''' 318 | 发送信号至Qt 319 | :param field_name: 要更新的字段名 320 | :param data: 更新的数据内容 321 | :return: 322 | ''' 323 | if QT_SIGNAL: 324 | QT_SIGNAL.emit(self.serial,field_name,data) 325 | QApplication.processEvents() 326 | 327 | 328 | def run_driver(deviceName, serial, port, driver, desired_caps): 329 | auto_tool = QQAFAutoTool(deviceName, serial, port, driver, desired_caps) 330 | auto_tool.start() 331 | 332 | 333 | 334 | 335 | 336 | def test(): 337 | desired_caps = { 338 | "platformName": "Android", 339 | "deviceName": 'Redmi_Note_4X', 340 | "appPackage": "com.tencent.mobileqq", 341 | "appActivity": ".activity.SplashActivity", 342 | "noReset": True, 343 | 'unicodeKeyboard': True, 344 | 'newCommandTimeout': 86400, 345 | } 346 | 347 | driver = webdriver.Remote(f'http://localhost:4723/wd/hub', desired_caps) 348 | auto_tool = QQAFAutoTool('Redmi_Note_4X','2dd9d6319804',4723,driver,desired_caps) 349 | auto_tool.get_current_account_info() 350 | auto_tool.add_friends() 351 | auto_tool.quit() 352 | 353 | if __name__ == '__main__': 354 | test() 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | -------------------------------------------------------------------------------- /qq/qqaf_auto_tool_multi.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import oappium 4 | import qqaf_auto_tool 5 | import logging 6 | import threading 7 | from settings import * 8 | from appium import webdriver 9 | from copy import deepcopy 10 | 11 | 12 | # 待添加的QQ号列表 13 | ADD_QQ_LIST = [ 14 | # {'name':'测试','qq':'195627250'}, 15 | # {'name':'煤油','qq':'123456'}, # 4 16 | # {'name':'巨田','qq':'654321'}, # 5 17 | 18 | {'name':'张三','qq':'615215416'}, # 4 19 | # {'name':'李四','qq':'498546235'}, # 0 20 | # {'name':'王五','qq':'845125164'}, # 0 21 | {'name':'小红','qq':'698565421'}, # 4 22 | {'name':'小蓝','qq':'265215421'}, # 4 23 | # {'name':'小绿','qq':'123525461'}, # 0 24 | {'name':'小橙','qq':'135246952'}, # 4 25 | {'name':'小黄','qq':'265425187'}, # 4 26 | # {'name':'小青','qq':'236965451'}, # 0 27 | # {'name':'小紫','qq':'285421542'}, # 0 28 | ] 29 | 30 | # 验证消息列表 31 | VERIFY_MSG_LIST = ['你好','可以加个好友吗?'] 32 | 33 | # 加好友间隔 34 | ADD_FRIENDS_INTERVAL = (10,20) 35 | 36 | class QQAFAutoToolMulti(oappium.MultiAppium): 37 | def __init__(self,add_qq_list,verify_msg_list,add_friends_interval,qt_signal=None): 38 | super().__init__() 39 | self.target = qqaf_auto_tool.run_driver 40 | self.desired_caps = { 41 | "platformName": "Android", 42 | "deviceName": '', 43 | "appPackage": "com.tencent.mobileqq", 44 | "appActivity": ".activity.SplashActivity", 45 | "noReset": True, 46 | 'unicodeKeyboard': True, 47 | 'newCommandTimeout': 86400, 48 | "udid": '', 49 | } 50 | self.add_qq_list = add_qq_list 51 | self.verify_msg_list = verify_msg_list 52 | self.add_friends_interval = add_friends_interval 53 | self.qt_signal = qt_signal 54 | 55 | def init_settings(self): 56 | qqaf_auto_tool.ADD_QQ_LIST = self.add_qq_list 57 | qqaf_auto_tool.VERIFY_MSG_LIST = self.verify_msg_list 58 | qqaf_auto_tool.ADD_FRIENDS_INTERVAL = self.add_friends_interval 59 | qqaf_auto_tool.QT_SIGNAL = self.qt_signal 60 | 61 | def get_task_threads(self): 62 | get_driver_threads = [] 63 | for device in self.devices: 64 | deviceName = device['deviceName'] 65 | serial = device['serial'] 66 | port = device['port'] 67 | caps = deepcopy(self.desired_caps) 68 | 69 | caps['deviceName'] = deviceName 70 | caps['udid'] = serial 71 | 72 | self.qt_signal.emit(serial, '状态', DEVICE_STATE_DICT[1]) 73 | 74 | t = threading.Thread(target=self.get_driver,args=(serial,deviceName,port,self.target,caps)) 75 | t.start() 76 | get_driver_threads.append(t) 77 | 78 | for t in get_driver_threads: 79 | t.join() 80 | 81 | def get_driver(self,serial,deviceName,port,target,desired_caps,try_time=3): 82 | for i in range(try_time): 83 | try: 84 | driver = webdriver.Remote(f'http://localhost:{port}/wd/hub', desired_caps) 85 | 86 | logging.info(f'Get Driver Succeed:{deviceName} {serial}') 87 | self.qt_signal.emit(serial, '状态', DEVICE_STATE_DICT[2]) 88 | 89 | t = threading.Thread(target=target, args=(deviceName, serial, port, driver, desired_caps)) 90 | self.task_threads.append(t) 91 | 92 | return 93 | except Exception as e: 94 | logging.error(f'Driver Start Failed:{e} Retring:{i+1}') 95 | 96 | logging.warning(f'Get Driver Failed:{deviceName} {serial}') 97 | self.qt_signal.emit(serial, '状态', DEVICE_STATE_DICT[3]) 98 | 99 | if __name__ == '__main__': 100 | auto_obj = QQAFAutoToolMulti(ADD_QQ_LIST,VERIFY_MSG_LIST,ADD_FRIENDS_INTERVAL) 101 | auto_obj.init_settings() 102 | auto_obj.run() -------------------------------------------------------------------------------- /qq/qqaf_auto_tool_ui.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Form implementation generated from reading ui file 'qqaf_auto_tool_ui.ui' 4 | # 5 | # Created by: PyQt5 UI code generator 5.12 6 | # 7 | # WARNING! All changes made in this file will be lost! 8 | 9 | from PyQt5 import QtCore, QtGui, QtWidgets 10 | 11 | 12 | class Ui_MainWindow(object): 13 | def setupUi(self, MainWindow): 14 | MainWindow.setObjectName("MainWindow") 15 | MainWindow.resize(642, 572) 16 | self.centralwidget = QtWidgets.QWidget(MainWindow) 17 | self.centralwidget.setObjectName("centralwidget") 18 | self.groupBox = QtWidgets.QGroupBox(self.centralwidget) 19 | self.groupBox.setGeometry(QtCore.QRect(20, 20, 601, 221)) 20 | self.groupBox.setStyleSheet("#groupBox{border:1px solid}") 21 | self.groupBox.setObjectName("groupBox") 22 | self.pb_import_data_source = QtWidgets.QPushButton(self.groupBox) 23 | self.pb_import_data_source.setGeometry(QtCore.QRect(99, 27, 75, 23)) 24 | self.pb_import_data_source.setObjectName("pb_import_data_source") 25 | self.label = QtWidgets.QLabel(self.groupBox) 26 | self.label.setGeometry(QtCore.QRect(23, 33, 48, 16)) 27 | self.label.setObjectName("label") 28 | self.lb_data_source_info = QtWidgets.QLabel(self.groupBox) 29 | self.lb_data_source_info.setGeometry(QtCore.QRect(180, 33, 401, 16)) 30 | self.lb_data_source_info.setText("") 31 | self.lb_data_source_info.setObjectName("lb_data_source_info") 32 | self.label_3 = QtWidgets.QLabel(self.groupBox) 33 | self.label_3.setGeometry(QtCore.QRect(21, 70, 54, 12)) 34 | self.label_3.setObjectName("label_3") 35 | self.te_verify_msg = QtWidgets.QTextEdit(self.groupBox) 36 | self.te_verify_msg.setGeometry(QtCore.QRect(100, 70, 411, 71)) 37 | self.te_verify_msg.setObjectName("te_verify_msg") 38 | self.label_4 = QtWidgets.QLabel(self.groupBox) 39 | self.label_4.setGeometry(QtCore.QRect(20, 160, 54, 12)) 40 | self.label_4.setObjectName("label_4") 41 | self.le_add_interval_2 = QtWidgets.QLineEdit(self.groupBox) 42 | self.le_add_interval_2.setGeometry(QtCore.QRect(168, 157, 61, 20)) 43 | self.le_add_interval_2.setObjectName("le_add_interval_2") 44 | self.le_add_interval_1 = QtWidgets.QLineEdit(self.groupBox) 45 | self.le_add_interval_1.setGeometry(QtCore.QRect(98, 157, 61, 20)) 46 | self.le_add_interval_1.setObjectName("le_add_interval_1") 47 | self.label_13 = QtWidgets.QLabel(self.groupBox) 48 | self.label_13.setGeometry(QtCore.QRect(160, 160, 21, 16)) 49 | self.label_13.setObjectName("label_13") 50 | self.pb_start = QtWidgets.QPushButton(self.groupBox) 51 | self.pb_start.setGeometry(QtCore.QRect(520, 190, 75, 23)) 52 | self.pb_start.setObjectName("pb_start") 53 | self.pb_import_verify_msg = QtWidgets.QPushButton(self.groupBox) 54 | self.pb_import_verify_msg.setGeometry(QtCore.QRect(520, 70, 41, 23)) 55 | self.pb_import_verify_msg.setObjectName("pb_import_verify_msg") 56 | self.pb_export_verify_msg = QtWidgets.QPushButton(self.groupBox) 57 | self.pb_export_verify_msg.setGeometry(QtCore.QRect(520, 118, 41, 23)) 58 | self.pb_export_verify_msg.setObjectName("pb_export_verify_msg") 59 | self.groupBox_2 = QtWidgets.QGroupBox(self.centralwidget) 60 | self.groupBox_2.setGeometry(QtCore.QRect(20, 260, 601, 281)) 61 | self.groupBox_2.setStyleSheet("#groupBox_2{border:1px solid}") 62 | self.groupBox_2.setObjectName("groupBox_2") 63 | self.tableView = QtWidgets.QTableView(self.groupBox_2) 64 | self.tableView.setGeometry(QtCore.QRect(10, 20, 581, 251)) 65 | self.tableView.setObjectName("tableView") 66 | self.line = QtWidgets.QFrame(self.centralwidget) 67 | self.line.setGeometry(QtCore.QRect(20, 240, 601, 20)) 68 | self.line.setFrameShape(QtWidgets.QFrame.HLine) 69 | self.line.setFrameShadow(QtWidgets.QFrame.Sunken) 70 | self.line.setObjectName("line") 71 | MainWindow.setCentralWidget(self.centralwidget) 72 | self.statusbar = QtWidgets.QStatusBar(MainWindow) 73 | self.statusbar.setObjectName("statusbar") 74 | MainWindow.setStatusBar(self.statusbar) 75 | 76 | self.retranslateUi(MainWindow) 77 | QtCore.QMetaObject.connectSlotsByName(MainWindow) 78 | 79 | def retranslateUi(self, MainWindow): 80 | _translate = QtCore.QCoreApplication.translate 81 | MainWindow.setWindowTitle(_translate("MainWindow", "QQ加好友")) 82 | self.groupBox.setTitle(_translate("MainWindow", "配置信息")) 83 | self.pb_import_data_source.setText(_translate("MainWindow", "导入")) 84 | self.label.setText(_translate("MainWindow", "数据来源")) 85 | self.label_3.setText(_translate("MainWindow", "验证消息")) 86 | self.te_verify_msg.setPlaceholderText(_translate("MainWindow", "每行一句,随机抽取")) 87 | self.label_4.setText(_translate("MainWindow", "间隔(秒)")) 88 | self.label_13.setText(_translate("MainWindow", "-")) 89 | self.pb_start.setText(_translate("MainWindow", "开始")) 90 | self.pb_import_verify_msg.setText(_translate("MainWindow", "导入")) 91 | self.pb_export_verify_msg.setText(_translate("MainWindow", "导出")) 92 | self.groupBox_2.setTitle(_translate("MainWindow", "设备信息")) 93 | 94 | 95 | -------------------------------------------------------------------------------- /qq/qqaf_auto_tool_ui.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | MainWindow 4 | 5 | 6 | 7 | 0 8 | 0 9 | 642 10 | 572 11 | 12 | 13 | 14 | QQ加好友 15 | 16 | 17 | 18 | 19 | 20 | 20 21 | 20 22 | 601 23 | 221 24 | 25 | 26 | 27 | #groupBox{border:1px solid} 28 | 29 | 30 | 配置信息 31 | 32 | 33 | 34 | 35 | 99 36 | 27 37 | 75 38 | 23 39 | 40 | 41 | 42 | 导入 43 | 44 | 45 | 46 | 47 | 48 | 23 49 | 33 50 | 48 51 | 16 52 | 53 | 54 | 55 | 数据来源 56 | 57 | 58 | 59 | 60 | 61 | 180 62 | 33 63 | 401 64 | 16 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 22 75 | 70 76 | 54 77 | 12 78 | 79 | 80 | 81 | 验证消息 82 | 83 | 84 | 85 | 86 | 87 | 100 88 | 70 89 | 411 90 | 71 91 | 92 | 93 | 94 | 每行一句,随机抽取 95 | 96 | 97 | 98 | 99 | 100 | 20 101 | 160 102 | 54 103 | 12 104 | 105 | 106 | 107 | 间隔(秒) 108 | 109 | 110 | 111 | 112 | 113 | 169 114 | 157 115 | 61 116 | 20 117 | 118 | 119 | 120 | 121 | 122 | 123 | 99 124 | 157 125 | 61 126 | 20 127 | 128 | 129 | 130 | 131 | 132 | 133 | 161 134 | 160 135 | 21 136 | 16 137 | 138 | 139 | 140 | - 141 | 142 | 143 | 144 | 145 | 146 | 520 147 | 190 148 | 75 149 | 23 150 | 151 | 152 | 153 | 开始 154 | 155 | 156 | 157 | 158 | 159 | 520 160 | 70 161 | 41 162 | 23 163 | 164 | 165 | 166 | 导入 167 | 168 | 169 | 170 | 171 | 172 | 520 173 | 118 174 | 41 175 | 23 176 | 177 | 178 | 179 | 导出 180 | 181 | 182 | 183 | 184 | 185 | 186 | 20 187 | 260 188 | 601 189 | 281 190 | 191 | 192 | 193 | #groupBox_2{border:1px solid} 194 | 195 | 196 | 设备信息 197 | 198 | 199 | 200 | 201 | 10 202 | 20 203 | 581 204 | 251 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 20 213 | 240 214 | 601 215 | 20 216 | 217 | 218 | 219 | Qt::Horizontal 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | -------------------------------------------------------------------------------- /qq/qt_table_view.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from settings import * 4 | from PyQt5.QtWidgets import * 5 | from PyQt5.QtCore import * 6 | from PyQt5.QtGui import * 7 | import re 8 | import subprocess 9 | 10 | 11 | class DevicesTableView(): 12 | def __init__(self,tableView,TableDataSignal): 13 | self.devices = [] 14 | self.tableView = tableView 15 | self.TableDataSignal = TableDataSignal 16 | self.TableDataSignal.connect(self.update_device) 17 | 18 | # 列表初始化 19 | self.model = QStandardItemModel() 20 | self.model.setHorizontalHeaderLabels(DEVICE_HEADERS) 21 | self.tableView.setModel(self.model) 22 | 23 | # 水平方向标签拓展剩下的窗口部分,填满表格 24 | self.tableView.horizontalHeader().setStretchLastSection(True) 25 | # 水平方向,表格大小拓展到适当的尺寸 26 | self.tableView.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) 27 | 28 | # 禁止单元格编辑 29 | self.tableView.setEditTriggers(QAbstractItemView.NoEditTriggers) 30 | 31 | def list_bind(self, init=False): 32 | ''' 33 | 绑定列表 34 | :param init: 是否初始化,默认为False;传入init为True时用于列表初始化,不执行update_devices方法 35 | :return: 36 | ''' 37 | current_devices = self.get_devices() 38 | 39 | # 添加原无现有的设备至设备列表 40 | self.append_devices(current_devices) 41 | 42 | # 将原有现无,且原状态不为连接中断的设备,更新为连接中断;将原有现有,且原状态为连接中断的设备,更新为重连成功 43 | if not init: 44 | self.update_devices(current_devices) 45 | 46 | def get_devices(self): 47 | ''' 48 | 获取当前连接的设备信息 49 | :return: 当前连接的设备序列号列表 50 | ''' 51 | devices = [] 52 | p = subprocess.Popen("adb devices -l", stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE,shell=True) 53 | for i in p.stdout.readlines(): 54 | info = str(i,encoding='utf-8') 55 | if 'model' in info: 56 | serial = str(re.search(r'(.*?)device', info).group(1).strip()) 57 | devices.append(serial) 58 | 59 | p.wait() 60 | p.terminate() 61 | 62 | return devices 63 | 64 | def append_devices(self,current_devices): 65 | ''' 66 | 添加设备至设备列表 67 | :param current_devices: 当前连接设备 68 | :return: 69 | ''' 70 | for device in current_devices: 71 | if device not in self.devices: 72 | device_model = DeviceInfo() 73 | device_model.device_serial = device 74 | 75 | state_item = MyQStandardItem(DEVICE_STATE_DICT[device_model.device_state]) 76 | state_item.setForeground(QBrush(QColor(*CORLOR_GREEN))) 77 | 78 | self.model.appendRow([ 79 | MyQStandardItem(device_model.device_serial,align=Qt.AlignLeft|Qt.AlignVCenter), 80 | state_item, 81 | ]) 82 | 83 | self.devices.append(device) 84 | 85 | def update_devices(self,current_devices): 86 | ''' 87 | 将当前连接设备与原连接设备对比,更新设备列表 88 | :param current_devices: 89 | :return: 90 | ''' 91 | for device in self.devices: 92 | item = self.model.findItems(device, Qt.MatchExactly, column=HEADERS_INDEX_DICT['设备']) 93 | row = item[0].row() 94 | column = HEADERS_INDEX_DICT['状态'] 95 | state = DEVICE_STATE_NAME_DICT[str(self.model.data(self.model.index(row, column)))] 96 | 97 | ''' 98 | 检测原设备是否存在当前连接的设备中, 99 | 不存在代表该设备失去连接,需要将状态改为连接中断; 100 | 存在但设备状态为连接中断的,代表设备曾经断过,需要将状态改为重连成功 101 | ''' 102 | if device not in current_devices: 103 | self.TableDataSignal.emit(device, '状态', DEVICE_STATE_DICT[7]) 104 | QApplication.processEvents() 105 | else: 106 | if state == 7: 107 | self.TableDataSignal.emit(device, '状态', DEVICE_STATE_DICT[9]) 108 | 109 | def update_device(self, serial, field_name, data): 110 | ''' 111 | 更新设备信息 112 | :param serial: 要更新的设备的序列号 113 | :param field_name: 要更新的字段名 114 | :param data: 更新的数据内容 115 | :return: 116 | ''' 117 | item = self.model.findItems(serial, Qt.MatchExactly, column=HEADERS_INDEX_DICT['设备']) 118 | row = item[0].row() 119 | column = HEADERS_INDEX_DICT[field_name] 120 | 121 | self.update_item(row, column, data) 122 | 123 | def update_item(self, row, column, data): 124 | ''' 125 | 根据行列坐标更新列表数据,若更新的字段为状态,则根据状态更新为自定义的颜色 126 | :param row: 行号 127 | :param column: 列号 128 | :param data: 更新的数据内容 129 | :return: 130 | ''' 131 | new_item = MyQStandardItem(data) 132 | if column == HEADERS_INDEX_DICT['状态']: 133 | if data in GREEN_STATE: 134 | new_item.setForeground(QBrush(QColor(*CORLOR_GREEN))) 135 | 136 | elif data in BLUE_STATE: 137 | new_item.setForeground(QBrush(QColor(*CORLOR_BLUE))) 138 | 139 | elif data in ORANGE_STATE: 140 | new_item.setForeground(QBrush(QColor(*CORLOR_ORANGE))) 141 | 142 | elif data in RED_STATE: 143 | new_item.setForeground(QBrush(QColor(*CORLOR_RED))) 144 | 145 | self.model.setItem(row, column, new_item) 146 | 147 | def init_table_info(self): 148 | ''' 149 | 初始化列表信息 150 | :return: 151 | ''' 152 | for row in range(0,self.model.rowCount()): 153 | for column in range(2,self.model.columnCount()): 154 | self.update_item(row, column, '') 155 | 156 | 157 | 158 | class MyQStandardItem(QStandardItem): 159 | def __init__(self,data,align=Qt.AlignCenter): 160 | super().__init__(data) 161 | self.setTextAlignment(align) 162 | -------------------------------------------------------------------------------- /qq/settings.py: -------------------------------------------------------------------------------- 1 | 2 | # 数据库信息 3 | MONGO_CLIENT = 'localhost' 4 | MONGO_DB = 'QQ' 5 | MONGO_COLLECTION = 'AddRecord' 6 | 7 | # 设备信息列表字段 8 | DEVICE_HEADERS = ['设备','状态','当前账号','待添加'] 9 | 10 | # 设备信息类 11 | class DeviceInfo(): 12 | device_serial = '' 13 | device_state = 0 14 | current_account = '' 15 | wait_to_add = '' 16 | 17 | # 设备状态字典 18 | DEVICE_STATE_DICT = { 19 | 0:'连接成功', 20 | 1:'正在启动', 21 | 2:'启动成功', 22 | 3:'启动失败', 23 | 4:'运行中', 24 | 5:'运行完成', 25 | 6:'运行异常', 26 | 7:'连接中断', 27 | 8:'断线重连', 28 | 9:'重连成功', 29 | } 30 | 31 | # 设备状态名称字典 32 | DEVICE_STATE_NAME_DICT = {v:k for k,v in DEVICE_STATE_DICT.items()} 33 | 34 | 35 | # 列头索引字典 36 | HEADERS_INDEX_DICT = { 37 | '设备':0, 38 | '状态':1, 39 | '当前账号':2, 40 | '待添加':3, 41 | } 42 | 43 | 44 | # 设备状态颜色归类 45 | GREEN_STATE = ['连接成功','重连成功','运行完成'] 46 | BLUE_STATE = ['正在启动','启动成功','运行中'] 47 | RED_STATE = ['启动失败','运行异常','连接中断'] 48 | ORANGE_STATE = ['断线重连'] 49 | 50 | # 颜色RGB设置 51 | CORLOR_GREEN = (50,205,50) 52 | CORLOR_BLUE = (0,0,255) 53 | CORLOR_RED = (255,0,0) 54 | CORLOR_ORANGE = (255,165,0) -------------------------------------------------------------------------------- /weixin_raise_accounts/main.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from pytransform import pyarmor_runtime 4 | import wra_auto_tool_ui 5 | import oauth 6 | from settings import * 7 | from wra_auto_tool_multi import WRAAutoToolMulti 8 | from PyQt5 import QtWidgets 9 | from PyQt5.QtWidgets import * 10 | from PyQt5.QtCore import * 11 | from PyQt5.QtGui import * 12 | import sys 13 | import json 14 | import os 15 | import re 16 | # import usb.core 17 | import logging 18 | import time 19 | import subprocess 20 | 21 | 22 | 23 | class WRAQt(QtWidgets.QMainWindow, wra_auto_tool_ui.Ui_MainWindow): 24 | TableDataSignal = pyqtSignal(str, str, str) 25 | 26 | def __init__(self): 27 | self.tactics = [] 28 | self.start_flag = 0 29 | self.default_tactics_filename = 'default.json' 30 | self.devices = [] 31 | self.auto_reconnect = False 32 | 33 | QtWidgets.QMainWindow.__init__(self) 34 | wra_auto_tool_ui.Ui_MainWindow.__init__(self) 35 | self.setupUi(self) 36 | self.initUi() 37 | 38 | def initUi(self): 39 | 40 | # region 事件绑定 41 | 42 | # tab-关注公众号 43 | self.pb_add_official_accounts.clicked.connect(self.click_pb_add_official_accounts) 44 | self.pb_clear_official_accounts.clicked.connect(self.click_pb_clear_official_accounts) 45 | self.pb_add_tactic0.clicked.connect(self.click_pb_add_tactic0) 46 | 47 | # tab-文章阅读转发 48 | self.pb_add_tactic1.clicked.connect(self.click_pb_add_tactic1) 49 | 50 | # tab-朋友圈点赞 51 | self.pb_add_tactic2.clicked.connect(self.click_pb_add_tactic2) 52 | 53 | # tab-聊天消息 54 | self.pb_add_chat_objects.clicked.connect(self.click_pb_add_chat_objects) 55 | self.pb_clear_chat_objects.clicked.connect(self.click_pb_clear_chat_objects) 56 | 57 | self.pb_add_msg_contents.clicked.connect(self.click_pb_add_msg_contents) 58 | self.pb_clear_msg_contents.clicked.connect(self.click_pb_clear_msg_contents) 59 | 60 | self.pb_add_tactic3.clicked.connect(self.click_pb_add_tactic3) 61 | 62 | # 策略信息 63 | self.pb_import_tactics.clicked.connect(self.click_pb_import_tactics) 64 | self.pb_export_tactics.clicked.connect(self.click_pb_export_tactics) 65 | self.pb_clear_tactics.clicked.connect(self.click_pb_clear_tactics) 66 | self.pb_start.clicked.connect(self.click_pb_start) 67 | 68 | self.TableDataSignal.connect(self.update_device) 69 | 70 | # endregion 71 | 72 | # region 设置默认值 73 | 74 | # tab-关注公众号 75 | self.le_concern_num.setText('5') 76 | self.le_concern_interval_1.setText('5') 77 | self.le_concern_interval_2.setText('10') 78 | self.le_tactic0_interval.setText('5') 79 | 80 | # tab-文章阅读转发 81 | self.le_article_read_num.setText('3') 82 | self.cb_if_share.setChecked(False) 83 | self.le_read_share_interval_1.setText('5') 84 | self.le_read_share_interval_2.setText('10') 85 | self.le_tactic1_interval.setText('5') 86 | 87 | # tab-朋友圈点赞 88 | self.le_moments_swipe_num.setText('10') 89 | self.le_moments_thumbup_ratio.setText('70') 90 | self.le_thumbup_interval_1.setText('0') 91 | self.le_thumbup_interval_2.setText('2') 92 | self.le_tactic2_interval.setText('5') 93 | 94 | # tab-聊天消息 95 | self.le_send_msg_interval_1.setText('5') 96 | self.le_send_msg_interval_2.setText('10') 97 | self.le_tactic3_interval.setText('5') 98 | 99 | # 熄屏 100 | self.cb_screen_off.setChecked(False) 101 | self.cb_screen_off.setVisible(False) 102 | 103 | # endregion 104 | 105 | # region 公众号初始化 106 | if os.path.exists('oa.json'): 107 | try: 108 | oa_list = json.load(open('oa.json', 'r')) 109 | for oa in oa_list: 110 | self.te_official_accounts.append(oa) 111 | except: 112 | logging.info('OA Json File Not Correct.') 113 | 114 | # endregion 115 | 116 | # region 策略初始化 117 | if os.path.exists(self.default_tactics_filename): 118 | try: 119 | self.tactics = json.load(open(self.default_tactics_filename, 'r')) 120 | self.bind_te_current_tactics() 121 | except: 122 | logging.info('Default Tactic Json File Not Correct.') 123 | 124 | # endregion 125 | 126 | # region 列表初始化 127 | self.model = QStandardItemModel(0,7) 128 | self.model.setHorizontalHeaderLabels(DEVICE_HEADERS) 129 | self.tableView.setModel(self.model) 130 | 131 | # 水平方向标签拓展剩下的窗口部分,填满表格 132 | self.tableView.horizontalHeader().setStretchLastSection(True) 133 | # 水平方向,表格大小拓展到适当的尺寸 134 | self.tableView.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) 135 | 136 | # 禁止单元格编辑 137 | self.tableView.setEditTriggers(QAbstractItemView.NoEditTriggers) 138 | 139 | 140 | # 绑定列表 141 | self.list_bind(type=1) 142 | 143 | # endregion 144 | 145 | # region 启动设备检测定时器 146 | self.timer = QTimer(self) 147 | self.timer.timeout.connect(self.list_bind) 148 | self.timer.start(10000) 149 | 150 | # endregion 151 | 152 | # 绑定列表 153 | def list_bind(self,type=0): 154 | current_devices = self.get_devices() 155 | 156 | # 添加原无现有的设备至设备列表 157 | self.append_devices(current_devices) 158 | 159 | # 将原有现无,且原状态不为连接中断的设备,更新为连接中断;将原有现有,且原状态为连接中断的设备,更新为连接成功 160 | if type == 0: 161 | self.update_devices(current_devices) 162 | 163 | def append_devices(self,current_devices): 164 | for device in current_devices: 165 | if device not in self.devices: 166 | device_model = DeviceInfo() 167 | device_model.device_serial = device 168 | 169 | state_item = MyQStandardItem(DEVICE_STATE_DICT[device_model.device_state]) 170 | state_item.setForeground(QBrush(QColor(*CORLOR_GREEN))) 171 | 172 | self.model.appendRow([ 173 | MyQStandardItem(device_model.device_serial,align=Qt.AlignLeft|Qt.AlignVCenter), 174 | state_item, 175 | MyQStandardItem(str(device_model.current_tactic)), 176 | MyQStandardItem(str(device_model.concern_num)), 177 | MyQStandardItem(str(device_model.read_num)), 178 | MyQStandardItem(str(device_model.moments_swipe_num)), 179 | MyQStandardItem(str(device_model.send_num)), 180 | ]) 181 | 182 | self.devices.append(device) 183 | 184 | def update_devices(self,current_devices): 185 | for device in self.devices: 186 | item = self.model.findItems(device, Qt.MatchExactly, column=HEADERS_INDEX_DICT['设备']) 187 | row = item[0].row() 188 | column = HEADERS_INDEX_DICT['状态'] 189 | state = DEVICE_STATE_NAME_DICT[str(self.model.data(self.model.index(row, column)))] 190 | 191 | ''' 192 | 检测原设备是否存在当前连接的设备中, 193 | 不存在代表该设备失去连接,需要重连; 194 | 存在但设备状态为连接中断的,代表设备曾经断过,需要将状态改为重连成功 195 | ''' 196 | if device not in current_devices: 197 | if self.auto_reconnect: 198 | self.TableDataSignal.emit(device, '状态', DEVICE_STATE_DICT[8]) 199 | if self.reconnect_device(device): 200 | self.TableDataSignal.emit(device, '状态', DEVICE_STATE_DICT[9]) 201 | else: 202 | self.TableDataSignal.emit(device, '状态', DEVICE_STATE_DICT[7]) 203 | else: 204 | self.TableDataSignal.emit(device, '状态', DEVICE_STATE_DICT[7]) 205 | else: 206 | if state == 7: 207 | self.TableDataSignal.emit(device, '状态', DEVICE_STATE_DICT[9]) 208 | 209 | def update_device(self,serial,type,data): 210 | item = self.model.findItems(serial, Qt.MatchExactly, column=HEADERS_INDEX_DICT['设备']) 211 | row = item[0].row() 212 | column = HEADERS_INDEX_DICT[type] 213 | 214 | self.update_item(row, column, data) 215 | 216 | def get_devices(self): 217 | devices = [] 218 | p = subprocess.Popen("adb devices -l", stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE,shell=True) 219 | for i in p.stdout.readlines(): 220 | info = str(i,encoding='utf-8') 221 | if 'model' in info: 222 | serial = str(re.search(r'(.*?)device', info).group(1).strip()) 223 | devices.append(serial) 224 | 225 | p.wait() 226 | p.terminate() 227 | 228 | return devices 229 | 230 | # region 界面交互 231 | def click_pb_add_official_accounts(self): 232 | official_account = self.le_official_account.text().strip() 233 | if official_account == '': 234 | self.showMessageBox('请输入公众号') 235 | return 236 | 237 | current_official_accounts = list(filter(None, self.te_official_accounts.toPlainText().split('\n'))) 238 | if official_account in current_official_accounts: 239 | self.showMessageBox('该公众号已存在') 240 | return 241 | 242 | self.te_official_accounts.append(official_account) 243 | 244 | def click_pb_clear_official_accounts(self): 245 | self.te_official_accounts.setText('') 246 | 247 | def click_pb_add_chat_objects(self): 248 | chat_object = self.le_chat_object.text().strip() 249 | chat_object_type = self.cb_chat_object_type.currentIndex() 250 | chat_object_type_name = self.cb_chat_object_type.currentText() 251 | 252 | if chat_object == '': 253 | self.showMessageBox('请输入聊天对象') 254 | return 255 | 256 | if chat_object_type == 0: 257 | self.showMessageBox('请选择对象类型') 258 | return 259 | 260 | chat_object = f'({chat_object_type_name}){chat_object}' 261 | current_chat_objects = list(filter(None, self.te_chat_objects.toPlainText().split('\n'))) 262 | if chat_object in current_chat_objects: 263 | self.showMessageBox('该聊天对象已存在') 264 | return 265 | 266 | self.te_chat_objects.append(chat_object) 267 | 268 | def click_pb_clear_chat_objects(self): 269 | self.te_chat_objects.setText('') 270 | 271 | def click_pb_add_msg_contents(self): 272 | msg_content = self.le_msg_content.text().strip() 273 | if msg_content == '': 274 | self.showMessageBox('请输入消息内容') 275 | return 276 | 277 | current_msg_contents = list(filter(None, self.te_msg_contents.toPlainText().split('\n'))) 278 | if msg_content in current_msg_contents: 279 | self.showMessageBox('该消息内容已存在') 280 | return 281 | 282 | self.te_msg_contents.append(msg_content) 283 | 284 | def click_pb_clear_msg_contents(self): 285 | self.te_msg_contents.setText('') 286 | 287 | def click_pb_add_tactic0(self): 288 | if self.check_if_already_start(): 289 | return 290 | 291 | if self.le_concern_num.text().strip().isdigit(): 292 | concern_num = int(self.le_concern_num.text().strip()) 293 | if concern_num <= 0: 294 | self.showMessageBox('关注数必须为大于1的整数') 295 | return 296 | else: 297 | self.showMessageBox('关注数必须为整数') 298 | return 299 | 300 | current_official_accounts = list(filter(None, self.te_official_accounts.toPlainText().split('\n'))) 301 | if not current_official_accounts: 302 | self.showMessageBox('请至少添加一个公众号') 303 | return 304 | 305 | if self.check_interval(0,self.le_concern_interval_1.text(),self.le_concern_interval_2.text()): 306 | concern_interval_1 = int(self.le_concern_interval_1.text()) 307 | concern_interval_2 = int(self.le_concern_interval_2.text()) 308 | else: 309 | return 310 | 311 | if self.check_interval(1,self.le_tactic0_interval.text()): 312 | tactic0_interval = int(self.le_tactic0_interval.text()) 313 | else: 314 | return 315 | 316 | self.tactics.append( 317 | { 318 | 'type': 0, 319 | 'name': '关注公众号', 320 | 'concern_num': concern_num, 321 | 'official_accounts': current_official_accounts, 322 | 'concern_interval': (concern_interval_1,concern_interval_2), 323 | 'tactic_interval': tactic0_interval 324 | } 325 | ) 326 | 327 | self.bind_te_current_tactics() 328 | 329 | def click_pb_add_tactic1(self): 330 | if self.check_if_already_start(): 331 | return 332 | 333 | if self.le_article_read_num.text().strip().isdigit(): 334 | article_read_num = int(self.le_article_read_num.text().strip()) 335 | if article_read_num <= 0: 336 | self.showMessageBox('阅读文章数必须为大于1的整数') 337 | return 338 | else: 339 | self.showMessageBox('阅读文章数必须为整数') 340 | return 341 | 342 | if self.check_interval(0, self.le_read_share_interval_1.text(), self.le_read_share_interval_2.text()): 343 | read_share_interval_1 = int(self.le_read_share_interval_1.text()) 344 | read_share_interval_2 = int(self.le_read_share_interval_2.text()) 345 | else: 346 | return 347 | 348 | if self.check_interval(1, self.le_tactic1_interval.text()): 349 | tactic1_interval = int(self.le_tactic1_interval.text()) 350 | else: 351 | return 352 | 353 | if_share = self.cb_if_share.isChecked() 354 | 355 | self.tactics.append( 356 | { 357 | 'type': 1, 358 | 'name': '文章阅读转发', 359 | 'article_read_num': article_read_num, 360 | 'if_share': if_share, 361 | 'read_share_interval': (read_share_interval_1,read_share_interval_2), 362 | 'tactic_interval':tactic1_interval 363 | } 364 | ) 365 | 366 | self.bind_te_current_tactics() 367 | 368 | def click_pb_add_tactic2(self): 369 | if self.check_if_already_start(): 370 | return 371 | 372 | if self.le_moments_swipe_num.text().strip().isdigit(): 373 | moments_swipe_num = int(self.le_moments_swipe_num.text().strip()) 374 | if moments_swipe_num <= 0: 375 | self.showMessageBox('滑动次数必须为大于1的整数') 376 | return 377 | else: 378 | self.showMessageBox('滑动次数必须为整数') 379 | return 380 | 381 | if self.le_moments_thumbup_ratio.text().strip().isdigit(): 382 | moments_thumbup_ratio = int(self.le_moments_thumbup_ratio.text().strip()) 383 | if moments_thumbup_ratio < 0 or moments_thumbup_ratio > 100: 384 | self.showMessageBox('点赞概率必须为0-100的整数') 385 | return 386 | else: 387 | self.showMessageBox('点赞概率必须为整数') 388 | return 389 | 390 | if self.check_interval(0, self.le_thumbup_interval_1.text(), self.le_thumbup_interval_2.text()): 391 | thumbup_interval_1 = int(self.le_thumbup_interval_1.text()) 392 | thumbup_interval_2 = int(self.le_thumbup_interval_2.text()) 393 | else: 394 | return 395 | 396 | if self.check_interval(1, self.le_tactic2_interval.text()): 397 | tactic2_interval = int(self.le_tactic2_interval.text()) 398 | else: 399 | return 400 | 401 | self.tactics.append( 402 | { 403 | 'type': 2, 404 | 'name': '朋友圈点赞', 405 | 'moments_swipe_num': moments_swipe_num, 406 | 'moments_thumbup_ratio':moments_thumbup_ratio, 407 | 'thumbup_interval': (thumbup_interval_1,thumbup_interval_2), 408 | 'tactic_interval':tactic2_interval 409 | } 410 | ) 411 | 412 | self.bind_te_current_tactics() 413 | 414 | def click_pb_add_tactic3(self): 415 | if self.check_if_already_start(): 416 | return 417 | 418 | current_chat_objects = self.get_current_chat_objects() 419 | if not current_chat_objects: 420 | self.showMessageBox('请至少添加一个聊天对象') 421 | return 422 | 423 | current_msg_contents = list(filter(None, self.te_msg_contents.toPlainText().split('\n'))) 424 | if not current_msg_contents: 425 | self.showMessageBox('请至少添加一条消息内容') 426 | return 427 | 428 | if self.check_interval(0, self.le_send_msg_interval_1.text(), self.le_send_msg_interval_2.text()): 429 | send_msg_interval_1 = int(self.le_send_msg_interval_1.text()) 430 | send_msg_interval_2 = int(self.le_send_msg_interval_2.text()) 431 | else: 432 | return 433 | 434 | if self.check_interval(1, self.le_tactic3_interval.text()): 435 | tactic3_interval = int(self.le_tactic3_interval.text()) 436 | else: 437 | return 438 | 439 | self.tactics.append( 440 | { 441 | 'type': 3, 442 | 'name': '聊天消息', 443 | 'chat_objects': current_chat_objects, 444 | 'msg_contents': current_msg_contents, 445 | 'send_msg_interval': (send_msg_interval_1,send_msg_interval_2), 446 | 'tactic_interval':tactic3_interval 447 | } 448 | ) 449 | 450 | self.bind_te_current_tactics() 451 | 452 | def click_pb_import_tactics(self): 453 | if self.check_if_already_start(): 454 | return 455 | 456 | reply = self.showMessageBox('导入策略会清空当前已维护策略,是否继续?',type='question') 457 | if reply != QMessageBox.Yes: 458 | return 459 | try: 460 | filename, _ = QFileDialog.getOpenFileName(self, "选取文件", "", "Json Files (*.json)") 461 | if filename: 462 | self.tactics = json.load(open(filename,'r')) 463 | self.bind_te_current_tactics() 464 | except: 465 | self.showMessageBox('导入失败,请检查文件内容是否正确') 466 | 467 | def click_pb_export_tactics(self): 468 | if not self.tactics: 469 | self.showMessageBox('当前无策略,不需要导出') 470 | return 471 | 472 | filename, _ = QFileDialog.getSaveFileName(self, "文件保存", "", "Json Files (*.json)") 473 | if filename: 474 | json.dump(self.tactics,open(filename,'w')) 475 | 476 | def click_pb_clear_tactics(self): 477 | if self.check_if_already_start(): 478 | return 479 | 480 | self.tactics = [] 481 | self.bind_te_current_tactics() 482 | 483 | def click_pb_start(self): 484 | if self.check_if_already_start(): 485 | return 486 | 487 | if not self.tactics: 488 | self.showMessageBox('请至少添加一个策略') 489 | return 490 | 491 | self.init_table_info() 492 | self.save_default_tactics() 493 | 494 | self.start_flag = 1 495 | 496 | screen_off = self.cb_screen_off.isChecked() 497 | switch_accounts = self.cb_switch_accounts.isChecked() 498 | 499 | backend = Backend(self.tactics,self.TableDataSignal,screen_off,switch_accounts,self) 500 | backend.finish_signal.connect(self.update_flag) 501 | backend.start() 502 | 503 | self.pb_start.setDisabled(True) 504 | 505 | # endregion 506 | 507 | def init_table_info(self): 508 | ''' 509 | 初始化列表信息 510 | :return: 511 | ''' 512 | for row in range(1,self.model.rowCount()): 513 | for column in range(2,self.model.columnCount()): 514 | if column == 2: 515 | self.update_item(row,column,'未开始') 516 | elif column == 7: 517 | self.update_item(row, column, '') 518 | else: 519 | self.update_item(row,column,'0') 520 | 521 | def save_default_tactics(self): 522 | ''' 523 | 保存此次策略,下次程序初始化时使用 524 | :return: 525 | ''' 526 | json.dump(self.tactics,open(self.default_tactics_filename,'w')) 527 | 528 | def update_flag(self,text): 529 | self.start_flag = 0 530 | self.pb_start.setDisabled(False) 531 | 532 | def update_item(self, row, column, data): 533 | new_item = MyQStandardItem(data) 534 | if column == HEADERS_INDEX_DICT['状态']: 535 | if data in GREEN_STATE: 536 | new_item.setForeground(QBrush(QColor(*CORLOR_GREEN))) 537 | 538 | elif data in BLUE_STATE: 539 | new_item.setForeground(QBrush(QColor(*CORLOR_BLUE))) 540 | 541 | elif data in ORANGE_STATE: 542 | new_item.setForeground(QBrush(QColor(*CORLOR_ORANGE))) 543 | 544 | elif data in RED_STATE: 545 | new_item.setForeground(QBrush(QColor(*CORLOR_RED))) 546 | 547 | 548 | self.model.setItem(row, column, new_item) 549 | 550 | # region 辅助方法 551 | 552 | def reconnect_device(self,device): 553 | try: 554 | # device_in_libusb = usb.core.find(serial_number=device) 555 | device_in_libusb = None 556 | if device_in_libusb is not None: 557 | device_in_libusb.reset() 558 | time.sleep(3) 559 | 560 | devices = self.get_devices() 561 | if device in devices: 562 | logging.info(f'Reconnect Succeed:{device}') 563 | return True 564 | 565 | else: 566 | logging.warning(f'Reconnect Failed:device({device}) lost connection') 567 | 568 | else: 569 | logging.warning(f'Reconnect Failed:device({device}) not exist in libusb') 570 | 571 | except Exception as e: 572 | logging.warning(e) 573 | 574 | def check_interval(self,type,*args): 575 | if type == 0: 576 | interval1,interval2 = args 577 | if interval1.isdigit() and interval2.isdigit(): 578 | interval1,interval2 = int(interval1),int(interval2) 579 | if interval1 <= interval2: 580 | return True 581 | 582 | else: 583 | self.showMessageBox('最小间隔必须小于等于最大间隔') 584 | else: 585 | self.showMessageBox('间隔必须为整数') 586 | 587 | elif type == 1: 588 | interval = args[0] 589 | if interval.isdigit(): 590 | return True 591 | else: 592 | self.showMessageBox('策略间隔必须为整数') 593 | 594 | def get_current_chat_objects(self): 595 | current_chat_objects = [] 596 | objects_in_te = list(filter(None, self.te_chat_objects.toPlainText().split('\n'))) 597 | for obj in objects_in_te: 598 | if obj.startswith('(好友)'): 599 | current_chat_objects.append({'name':obj[4:],'type':1}) 600 | 601 | elif obj.startswith('(群聊)'): 602 | current_chat_objects.append({'name': obj[4:], 'type': 2}) 603 | 604 | return current_chat_objects 605 | 606 | def check_if_already_start(self): 607 | if self.start_flag != 0: 608 | self.showMessageBox('当前有策略正在运行') 609 | return True 610 | else: 611 | return False 612 | 613 | def bind_te_current_tactics(self): 614 | self.te_current_tactics.setText('') 615 | 616 | for i, tactic in enumerate(self.tactics): 617 | type = tactic['type'] 618 | name = tactic['name'] 619 | 620 | info = '' 621 | if type == 0: 622 | concern_num = tactic['concern_num'] 623 | official_accounts = tactic['official_accounts'] 624 | concern_interval = tactic['concern_interval'] 625 | tactic0_interval = tactic['tactic_interval'] 626 | info = f'策略号:{i+1} \n 策略类型:{name} \n 关注数:{concern_num} \n 公众号:{official_accounts} \n 间隔:{concern_interval}秒 \n 策略间隔:{tactic0_interval}分钟 \n ' 627 | 628 | elif type == 1: 629 | article_read_num = tactic['article_read_num'] 630 | if_share = '是' if tactic['if_share'] else '否' 631 | read_share_interval = tactic['read_share_interval'] 632 | tactic1_interval = tactic['tactic_interval'] 633 | info = f'策略号:{i+1} \n 策略类型:{name} \n 阅读文章数:{article_read_num} \n 是否转发:{if_share} \n 间隔:{read_share_interval}秒 \n 策略间隔:{tactic1_interval}分钟 \n ' 634 | 635 | elif type == 2: 636 | moments_swipe_num = tactic['moments_swipe_num'] 637 | moments_thumbup_ratio = tactic['moments_thumbup_ratio'] 638 | thumbup_interval = tactic['thumbup_interval'] 639 | tactic2_interval = tactic['tactic_interval'] 640 | info = f'策略号:{i+1} \n 策略类型:{name} \n 滑动次数:{moments_swipe_num} \n 点赞概率:{moments_thumbup_ratio} \n 间隔:{thumbup_interval}秒 \n 策略间隔:{tactic2_interval}分钟 \n ' 641 | 642 | elif type == 3: 643 | chat_objects = tactic['chat_objects'] 644 | msg_contents = tactic['msg_contents'] 645 | send_msg_interval = tactic['send_msg_interval'] 646 | tactic3_interval = tactic['tactic_interval'] 647 | info = f'策略号:{i+1} \n 策略类型:{name} \n 聊天对象:{chat_objects} \n 消息内容:{msg_contents} \n 间隔:{send_msg_interval}秒 \n 策略间隔:{tactic3_interval}分钟 \n ' 648 | 649 | self.te_current_tactics.append(info) 650 | 651 | def showMessageBox(self, msg, title='提示', type='information'): 652 | if type == 'information': 653 | QMessageBox.information(self, title, msg) 654 | elif type == 'question': 655 | reply = QMessageBox.question(self, title, msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.Yes) 656 | return reply 657 | 658 | # endregion 659 | 660 | 661 | class Backend(QThread): 662 | finish_signal = pyqtSignal(str) 663 | 664 | def __init__(self,tactics,table_data_signal,screen_off,switch_accounts,parent=None): 665 | super(Backend, self).__init__(parent) 666 | self.tactics = tactics 667 | self.table_data_signal = table_data_signal 668 | self.screen_off = screen_off 669 | self.switch_accounts = switch_accounts 670 | 671 | def run(self): 672 | auto_obj = WRAAutoToolMulti(self.tactics,qt_signal=self.table_data_signal,screen_off=self.screen_off,switch_accounts=self.switch_accounts) 673 | auto_obj.init_settings() 674 | auto_obj.run() 675 | 676 | self.finish_signal.emit('Done') 677 | 678 | 679 | class MyQStandardItem(QStandardItem): 680 | def __init__(self,data,align=Qt.AlignCenter): 681 | super().__init__(data) 682 | self.setTextAlignment(align) 683 | 684 | 685 | if __name__ == '__main__': 686 | auth = oauth.if_auth() 687 | if auth: 688 | try: 689 | app = QApplication(sys.argv) 690 | window = WRAQt() 691 | window.show() 692 | sys.exit(app.exec()) 693 | except Exception as e: 694 | logging.error(e) 695 | 696 | 697 | 698 | -------------------------------------------------------------------------------- /weixin_raise_accounts/oappium.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import os 4 | import ctypes 5 | import re 6 | import logging 7 | import socket 8 | import threading 9 | from appium import webdriver 10 | from selenium.webdriver.support.ui import WebDriverWait 11 | from selenium.webdriver.support import expected_conditions as EC 12 | from selenium.webdriver.common.by import By 13 | import time 14 | import subprocess 15 | from copy import deepcopy 16 | 17 | 18 | def execute_cmd(cmd, type=0): 19 | ''' 20 | 使用subprocess执行系统命令 21 | :param cmd: 命令 22 | :param type: 类型 0:不需要返回值 1:返回执行结果 2:返回子进程 23 | :return: 24 | ''' 25 | p = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 26 | 27 | if type == 0: 28 | p.wait() 29 | p.terminate() 30 | 31 | elif type == 1: 32 | infos = [str(i, encoding='utf-8') for i in p.stdout.readlines()] 33 | p.wait() 34 | p.terminate() 35 | 36 | return infos 37 | 38 | elif type == 2: 39 | return p 40 | 41 | 42 | class AppiumAutoTool(): 43 | def __init__(self,deviceName,serial,port,driver,desired_caps): 44 | self.deviceName = deviceName 45 | self.serial = serial 46 | self.port = port 47 | self.driver = driver 48 | self.desired_caps = desired_caps 49 | self.x = self.driver.get_window_size()['width'] 50 | self.y = self.driver.get_window_size()['height'] 51 | 52 | def awake_and_unlock_screen(self): 53 | for i in range(3): 54 | screen_state = self.get_screen_lock_state() 55 | 56 | if screen_state == 0: 57 | self.driver.press_keycode(26) 58 | self.driver.swipe(1 / 2, 9 / 10, 1 / 2, 1 / 10, 1000) 59 | 60 | elif screen_state == 1: 61 | self.driver.swipe(1 / 2, 9 / 10, 1 / 2, 1 / 10, 1000) 62 | 63 | elif screen_state == 2: 64 | return 65 | 66 | time.sleep(2) 67 | 68 | logging.warning(f'Screen Unlock Failed:{self.serial}') 69 | 70 | 71 | # 获取屏幕锁定状态 0:暗屏未解锁 1:亮屏未解锁 2:亮屏已解锁 72 | def get_screen_lock_state(self): 73 | result1 = ''.join(execute_cmd(f'adb -s {self.serial} shell "dumpsys window policy|grep isStatusBarKeyguard"',type=1)) 74 | 75 | result2 = ''.join(execute_cmd(f'adb -s {self.serial} shell "dumpsys window policy|grep mShowingLockscreen"',type=1)) 76 | 77 | result3 = ''.join(execute_cmd(f'adb -s {self.serial} shell "dumpsys window policy|grep mScreenOnEarly"', type=1)) 78 | 79 | if 'isStatusBarKeyguard=true' in result1 or 'mShowingLockscreen=true' in result2: 80 | if 'mScreenOnEarly=false' in result3: 81 | state = 0 82 | else: 83 | state = 1 84 | 85 | else: 86 | state = 2 87 | 88 | return state 89 | 90 | 91 | 92 | # 按下返回键 93 | def press_back(self,sleep=2): 94 | self.driver.press_keycode(4) 95 | time.sleep(sleep) 96 | 97 | # 判断元素是否存在 98 | def is_el_exist(self, type, selector, timeout=3): 99 | wait_for_el = WebDriverWait(self.driver, timeout) 100 | if type == 'id': 101 | try: 102 | el = wait_for_el.until(EC.presence_of_element_located((By.ID, selector))) 103 | return el 104 | except: 105 | return False 106 | elif type == 'xpath': 107 | try: 108 | el = wait_for_el.until(EC.presence_of_element_located((By.XPATH, selector))) 109 | return el 110 | except: 111 | return False 112 | 113 | # 判断元素是否可点击 114 | def is_el_clickable(self, type, selector, timeout=3): 115 | wait_for_el = WebDriverWait(self.driver, timeout) 116 | if type == 'id': 117 | try: 118 | el = wait_for_el.until(EC.element_to_be_clickable((By.ID, selector))) 119 | return el 120 | except: 121 | return False 122 | elif type == 'xpath': 123 | try: 124 | el = wait_for_el.until(EC.element_to_be_clickable((By.XPATH, selector))) 125 | return el 126 | except: 127 | return False 128 | 129 | # 判断元素是否出现在当前页面 130 | def is_el_displayed(self, type, selector, y_ratio, timeout=3): 131 | wait_for_el = WebDriverWait(self.driver, timeout) 132 | if type == 'id': 133 | try: 134 | el = wait_for_el.until(EC.visibility_of_element_located((By.ID, selector))) 135 | loc = el.location_once_scrolled_into_view 136 | if loc and loc['y'] < y_ratio * self.y: 137 | return el 138 | else: 139 | return False 140 | except: 141 | return False 142 | elif type == 'xpath': 143 | try: 144 | el = wait_for_el.until(EC.visibility_of_element_located((By.XPATH, selector))) 145 | loc = el.location_once_scrolled_into_view 146 | if loc and loc['y'] < y_ratio * self.y: 147 | return el 148 | else: 149 | return False 150 | except: 151 | return False 152 | 153 | # 点击不稳定元素(点击一次可能无效,一直点击到下一个元素出现) 154 | def click_unstable_el(self, el, next_el_type, next_el_selector, timeout=3): 155 | for i in range(10): 156 | el.click() 157 | 158 | next_el = self.is_el_exist(next_el_type, next_el_selector, timeout) 159 | if next_el: 160 | return next_el 161 | 162 | raise Exception('Click Unstable Element Error',el) 163 | 164 | # 通过xpath点击不稳定元素(点击一次可能无效,一直点击到下一个元素出现) 165 | def click_unstable_el_by_xpath(self, current_el_type,current_el_selector, next_el_type, next_el_selector, timeout=3): 166 | wait = WebDriverWait(self.driver, timeout) 167 | by = By.XPATH if current_el_type=='xpath' else By.ID 168 | 169 | for i in range(10): 170 | el = wait.until(EC.element_to_be_clickable((by,current_el_selector))) 171 | el.click() 172 | time.sleep(5) 173 | 174 | next_el = self.is_el_exist(next_el_type, next_el_selector, timeout) 175 | if next_el: 176 | return next_el 177 | 178 | raise Exception('Click Unstable Element Error', current_el_selector) 179 | 180 | # 根据屏幕比例进行滑动 181 | def swipe(self, ratio_x1, ratio_y1, ratio_x2, ratio_y2, duration): 182 | self.driver.swipe(ratio_x1 * self.x, ratio_y1 * self.y, ratio_x2 * self.x, ratio_y2 * self.y, duration) 183 | 184 | # 切换为搜狗输入法并退出driver 185 | def quit(self): 186 | execute_cmd(f'adb -s {self.serial} shell ime set com.sohu.inputmethod.sogou.xiaomi/.SogouIME') 187 | 188 | self.driver.quit() 189 | 190 | 191 | class MultiAppium(): 192 | TODAY = time.strftime('%Y-%m-%d') 193 | logging.basicConfig(filename=f'{TODAY}.log', level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') 194 | 195 | def __init__(self): 196 | self.devices = [] 197 | self.appium_port = 30000 198 | self.bp_port = 40000 199 | self.socket_obj = socket.socket() 200 | self.socket_hostname = socket.getfqdn(socket.gethostname()) 201 | self.socket_addr = socket.gethostbyname(self.socket_hostname) 202 | self.server_threads = [] 203 | self.task_threads = [] 204 | self.target = None 205 | self.desired_caps = None 206 | self.server_processes = [] 207 | 208 | def showMessagebox(self,title, text, style=0): 209 | return ctypes.windll.user32.MessageBoxW(0, text, title, style) 210 | 211 | def check_environment(self): 212 | try: 213 | result = execute_cmd('adb devices -l',type=1) 214 | devices = [i for i in result if 'model' in i] 215 | 216 | if devices: 217 | return True 218 | else: 219 | self.showMessagebox('提示', '当前无设备连接,请检查设备连接情况!') 220 | 221 | except: 222 | self.showMessagebox('提示','运行失败,端口被占用!请关闭360手机助手的后台进程!') 223 | 224 | def get_devices(self): 225 | device_serial = [] 226 | result = execute_cmd('adb devices -l', type=1) 227 | devices_info = [i for i in result if 'model' in i] 228 | 229 | logging.info('Devices Info:') 230 | for i, info in enumerate(devices_info): 231 | serial = str(re.search(r'(.*?)device', info).group(1).strip()) 232 | deviceName = re.search(r'model:(.*?) device', info).group(1).strip() 233 | port = str(self.get_available_port_by_socket()) 234 | 235 | self.devices.append( 236 | { 237 | 'deviceName': deviceName, 238 | 'serial': serial, 239 | 'port': port, 240 | } 241 | ) 242 | 243 | device_serial.append(serial) 244 | 245 | 246 | logging.info(serial+' '+deviceName+' '+port+' ') 247 | 248 | return device_serial 249 | 250 | # 获取屏幕大小 251 | def get_window_size(self,device): 252 | result = ''.join(execute_cmd(f'adb -s {device} shell wm size',type=1)) 253 | 254 | if result: 255 | clear_btn_loc = re.search(r'Physical size: (\d+)x(\d+)', result) 256 | x = int(clear_btn_loc.group(1)) 257 | y = int(clear_btn_loc.group(2)) 258 | 259 | return x, y 260 | 261 | # 唤醒并解锁屏幕 262 | def awake_and_unlock_screen(self,devices): 263 | for device in devices: 264 | result1 = ''.join(execute_cmd(f'adb -s {device} shell "dumpsys window policy|grep isStatusBarKeyguard"', type=1)) 265 | 266 | result2 = ''.join(execute_cmd(f'adb -s {device} shell "dumpsys window policy|grep mShowingLockscreen"', type=1)) 267 | 268 | result3 = ''.join(execute_cmd(f'adb -s {device} shell "dumpsys window policy|grep mScreenOnEarly"', type=1)) 269 | 270 | if 'isStatusBarKeyguard=true' in result1 or 'mShowingLockscreen=true' in result2: 271 | x, y = self.get_window_size(device) 272 | x1, y1, x2, y2 = 1 / 2 * x, 9 / 10 * y, 1 / 2 * x, 1 / 10 * y 273 | 274 | if 'mScreenOnEarly=false' in result3: 275 | execute_cmd(f'adb -s {device} shell input keyevent 26') 276 | 277 | execute_cmd(f'adb -s {device} shell input swipe {x1} {y1} {x2} {y2}') 278 | 279 | def get_available_port_by_socket(self): 280 | while True: 281 | try: 282 | self.socket_obj.connect((self.socket_addr, self.appium_port)) 283 | self.socket_obj.close() 284 | self.appium_port += 1 285 | except: 286 | port = self.appium_port 287 | self.appium_port += 1 288 | return port 289 | 290 | def get_available_bp_port_by_socket(self): 291 | while True: 292 | try: 293 | self.socket_obj.connect((self.socket_addr, self.bp_port)) 294 | self.socket_obj.close() 295 | self.bp_port += 1 296 | except: 297 | port = self.bp_port 298 | self.bp_port += 1 299 | return port 300 | 301 | def kill_all_appium(self): 302 | execute_cmd('taskkill /f /t /im node.exe') 303 | 304 | def start_server(self,serial, port): 305 | logging.info(f'Thread Start-{serial}') 306 | bp_port = self.get_available_bp_port_by_socket() 307 | logging.info(f'Start Server:{serial} {port} {bp_port}') 308 | 309 | # cmd = r'node C:\Users\ethan\AppData\Local\Programs\Appium\resources\app\node_modules\appium\build\lib\main.js -p {} -bp {} -U {}'.format(port, bp_port, serial) 310 | cmd = r'appium -p {} -bp {} -U {}'.format(port, bp_port, serial) 311 | process = subprocess.Popen(cmd,shell=True) 312 | self.server_processes.append(process) 313 | 314 | logging.info(f'Server Start Succeed:{process.pid} {serial} {port} {bp_port}') 315 | 316 | def get_server_threads(self): 317 | for device in self.devices: 318 | serial = device['serial'] 319 | port = device['port'] 320 | 321 | t = threading.Thread(target=self.start_server,args=(serial,port)) 322 | self.server_threads.append(t) 323 | 324 | def get_task_threads(self): 325 | get_driver_threads = [] 326 | for device in self.devices: 327 | deviceName = device['deviceName'] 328 | serial = device['serial'] 329 | port = device['port'] 330 | 331 | caps = deepcopy(self.desired_caps) 332 | 333 | caps['deviceName'] = deviceName 334 | caps['udid'] = serial 335 | 336 | t = threading.Thread(target=self.get_driver,args=(serial,deviceName,port,self.target,caps)) 337 | t.start() 338 | get_driver_threads.append(t) 339 | 340 | for t in get_driver_threads: 341 | t.join() 342 | 343 | def get_driver(self,serial,deviceName,port,target,desired_caps,try_time=3): 344 | for i in range(try_time): 345 | try: 346 | driver = webdriver.Remote(f'http://localhost:{port}/wd/hub', desired_caps) 347 | logging.info(f'Get Driver Succeed:{deviceName} {serial}') 348 | t = threading.Thread(target=target, args=(deviceName, serial, port, driver, desired_caps)) 349 | self.task_threads.append(t) 350 | return 351 | except Exception as e: 352 | logging.error(f'Driver Start Failed:{e} Retring:{i+1}') 353 | 354 | logging.warning(f'Get Driver Failed:{deviceName} {serial}') 355 | 356 | def run(self): 357 | if_env_ok = self.check_environment() 358 | if if_env_ok: 359 | task_list = [] 360 | # 获取设备 361 | devices = self.get_devices() 362 | 363 | # 唤醒并解锁 364 | self.awake_and_unlock_screen(devices) 365 | 366 | # 终止所有appium 367 | self.kill_all_appium() 368 | 369 | # 启动server线程 370 | self.get_server_threads() 371 | for t1 in self.server_threads: 372 | t1.setDaemon(True) 373 | t1.start() 374 | 375 | time.sleep(len(self.server_threads)) 376 | 377 | # 启动driver线程 378 | self.get_task_threads() 379 | for t2 in self.task_threads: 380 | task_list.append(t2) 381 | t2.setDaemon(True) 382 | t2.start() 383 | 384 | # 等待所有任务执行完成 385 | for task in task_list: 386 | task.join() 387 | 388 | # 关闭所有appium服务器进程 389 | for process in self.server_processes: 390 | process.terminate() 391 | logging.info(f'Server End Succeed:{process.pid}') 392 | 393 | # 终止所有appium 394 | self.kill_all_appium() 395 | 396 | -------------------------------------------------------------------------------- /weixin_raise_accounts/oauth.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import uuid 3 | from hashlib import sha224 4 | import pickle 5 | import ctypes 6 | import subprocess 7 | import re 8 | 9 | AUTH_FILE = 'auth.config' 10 | 11 | class AuthError(Exception): 12 | pass 13 | 14 | def get_encrypted_mac(mac): 15 | mac = mac.replace('-','').lower() 16 | encrypt_mac = sha224(mac.encode()) 17 | 18 | return encrypt_mac.hexdigest() 19 | 20 | def create_allowed_macs(allow_macs): 21 | allow_macs = list(map(get_encrypted_mac,allow_macs)) 22 | with open(AUTH_FILE,'wb') as f: 23 | pickle.dump(allow_macs,f) 24 | 25 | def get_current_encrypted_mac(): 26 | node = uuid.getnode() 27 | mac = uuid.UUID(int=node).hex[-12:] 28 | encrypt_mac = sha224(mac.encode()) 29 | 30 | return encrypt_mac.hexdigest() 31 | 32 | def get_current_encrypted_mac_yt(): 33 | try: 34 | p = subprocess.Popen("ipconfig /all", stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, 35 | shell=True) 36 | ipconfig_info = p.stdout.read().decode('gbk') 37 | 38 | patterns = [ 39 | '以太网适配器 以太网:.*?物理地址.*?: (.{17})', 40 | '以太网适配器 本地连接:.*?物理地址.*?: (.{17})' 41 | ] 42 | 43 | m = None 44 | for pattern in patterns: 45 | m = re.search(pattern,ipconfig_info,re.S) 46 | if m != None: 47 | break 48 | 49 | mac = m.group(1).replace('-','').lower() 50 | # print(ipconfig_info) 51 | # print(mac) 52 | 53 | encrypt_mac = sha224(mac.encode()) 54 | return encrypt_mac.hexdigest() 55 | 56 | except Exception as e: 57 | raise AuthError('Get Mac Failed: %s ' % e) 58 | 59 | def get_allowed_macs(): 60 | with open(AUTH_FILE,'rb') as f: 61 | allow_macs = pickle.load(f) 62 | return allow_macs 63 | 64 | def if_auth(): 65 | try: 66 | allowed = get_allowed_macs() 67 | current = get_current_encrypted_mac_yt() 68 | if current in allowed: 69 | return True 70 | else: 71 | ctypes.windll.user32.MessageBoxW(0, '您的计算机无权限对该程序进行操作!', '提示', 0) 72 | return False 73 | 74 | except Exception as e: 75 | ctypes.windll.user32.MessageBoxW(0, f'{e}', '错误', 0) 76 | return False 77 | 78 | 79 | 80 | 81 | if __name__ == '__main__': 82 | allow_macs = ['B0-FC-36-78-C7-52','94-DE-80-3C-7B-94','00-E0-4C-06-6C-BC', 83 | '1C-6F-65-BB-BA-4C','30-9C-23-C6-5F-38','08-00-27-20-4D-C0'] 84 | create_allowed_macs(allow_macs) 85 | 86 | # get_current_encrypted_mac_yt() 87 | 88 | -------------------------------------------------------------------------------- /weixin_raise_accounts/settings.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | 4 | 5 | class DeviceInfo(): 6 | device_serial = '' 7 | device_state = 0 8 | current_tactic = '未开始' 9 | concern_num = 0 10 | read_num = 0 11 | moments_swipe_num = 0 12 | send_num = 0 13 | current_account = '' 14 | 15 | DEVICE_HEADERS = ['设备','状态','策略号','关注数','阅读数','滑动数','消息数','当前账号'] 16 | 17 | 18 | HEADERS_INDEX_DICT = { 19 | '设备':0, 20 | '状态':1, 21 | '策略号':2, 22 | '关注数':3, 23 | '阅读数':4, 24 | '滑动数':5, 25 | '消息数':6, 26 | '当前账号':7, 27 | } 28 | 29 | DEVICE_STATE_DICT = { 30 | 0:'连接成功', 31 | 1:'正在启动', 32 | 2:'启动成功', 33 | 3:'启动失败', 34 | 4:'运行中', 35 | 5:'运行完成', 36 | 6:'运行异常', 37 | 7:'连接中断', 38 | 8:'断线重连', 39 | 9:'重连成功', 40 | } 41 | 42 | DEVICE_STATE_NAME_DICT = { 43 | '连接成功':0, 44 | '正在启动':1, 45 | '启动成功':2, 46 | '启动失败':3, 47 | '运行中':4, 48 | '运行完成':5, 49 | '运行异常':6, 50 | '连接中断':7, 51 | '断线重连':8, 52 | '重连成功':9, 53 | } 54 | 55 | GREEN_STATE = ['连接成功','重连成功','运行完成'] 56 | BLUE_STATE = ['正在启动','启动成功','运行中'] 57 | RED_STATE = ['启动失败','运行异常','连接中断'] 58 | ORANGE_STATE = ['断线重连'] 59 | 60 | CORLOR_GREEN = (50,205,50) 61 | CORLOR_BLUE = (0,0,255) 62 | CORLOR_RED = (255,0,0) 63 | CORLOR_ORANGE = (255,165,0) 64 | 65 | # 微信密码,用于切换账号 66 | WECHAT_PASSWORD = 'ss123123' -------------------------------------------------------------------------------- /weixin_raise_accounts/wra_auto_tool.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from appium import webdriver 4 | from selenium.webdriver.support.ui import WebDriverWait 5 | from selenium.webdriver.support import expected_conditions as EC 6 | from selenium.webdriver.common.by import By 7 | import logging 8 | import time 9 | import random 10 | import oappium 11 | from settings import * 12 | from PyQt5.QtWidgets import QApplication 13 | 14 | 15 | # region 全局设置 16 | 17 | # 超时时间 18 | TIMEOUT = 60 19 | 20 | # 策略列表 21 | TACTICS = [] 22 | 23 | # 是否熄屏 24 | SCREEN_OFF = True 25 | 26 | # Qt信号 27 | QT_SIGNAL = None 28 | 29 | # 是否切换账号 30 | SWITCH_ACCOUNTS = False 31 | 32 | # endregion 33 | 34 | 35 | class WRAAutoTool(oappium.AppiumAutoTool): 36 | def __init__(self,deviceName,serial,port,driver,desired_caps,current_tactic=0): 37 | super().__init__(deviceName,serial,port,driver,desired_caps) 38 | self.wait = WebDriverWait(self.driver, TIMEOUT) 39 | self.current_wechat_name = '' 40 | 41 | # 记录当前策略号,重启时传入,重启后可继续当前的策略运行 42 | self.current_tactic = current_tactic 43 | 44 | # 记录运行完成的账号,避免异常重启时重复执行策略 45 | self.finished_accounts = [] 46 | 47 | def emit_to_qt(self,serial,type,data): 48 | if QT_SIGNAL: 49 | QT_SIGNAL.emit(serial,type,data) 50 | QApplication.processEvents() 51 | 52 | # 点击搜索(需切换为搜狗输入法弹出搜索按键) 53 | def click_serach(self): 54 | oappium.execute_cmd(f'adb -s {self.serial} shell ime set com.sohu.inputmethod.sogou.xiaomi/.SogouIME') 55 | time.sleep(5) 56 | 57 | oappium.execute_cmd(f'adb -s {self.serial} shell input tap {0.92*self.x} {0.93*self.y}') 58 | 59 | ime_info = ''.join(oappium.execute_cmd(f'adb -s {self.serial} shell ime list -s',type=1)) 60 | 61 | # 不同机型的appium输入法可能不同 62 | if 'io.appium.settings/.UnicodeIME' in ime_info: 63 | oappium.execute_cmd(f'adb -s {self.serial} shell ime set io.appium.settings/.UnicodeIME') 64 | 65 | elif 'io.appium.android.ime/.UnicodeIME' in ime_info: 66 | oappium.execute_cmd(f'adb -s {self.serial} shell ime set io.appium.android.ime/.UnicodeIME') 67 | 68 | # 返回首页 69 | def return_to_index_page(self): 70 | while 1: 71 | el = self.is_el_exist('xpath','//android.widget.RelativeLayout[@resource-id="com.tencent.mm:id/bn"]/android.widget.LinearLayout/android.widget.RelativeLayout[1]',1) 72 | if el: 73 | el.click() 74 | time.sleep(5) 75 | break 76 | else: 77 | self.press_back() 78 | 79 | # 关闭/开启消息提醒 80 | def close_open_notify(self,type): 81 | 82 | # 点击我 查找设置按钮 83 | el_setting = self.click_unstable_el_by_xpath('xpath','//android.widget.TextView[@resource-id="com.tencent.mm:id/d3t" and @text="我"]','xpath','//android.widget.TextView[@resource-id="android:id/title" and @text="设置"]') 84 | if self.current_wechat_name == '': 85 | el_wechat_name = self.wait.until(EC.presence_of_element_located((By.ID, 'com.tencent.mm:id/a5b'))) 86 | self.current_wechat_name = el_wechat_name.text.strip() 87 | self.emit_to_qt(self.serial, '当前账号', self.current_wechat_name) 88 | 89 | time.sleep(5) 90 | el_setting.click() 91 | 92 | # 新消息提醒 93 | el_new_msg_notify = self.wait.until(EC.element_to_be_clickable((By.XPATH,'//android.widget.TextView[@resource-id="android:id/title" and @text="新消息提醒"]'))) 94 | el_new_msg_notify.click() 95 | 96 | # 关闭/开启 97 | el_close_open = self.wait.until(EC.element_to_be_clickable((By.XPATH,'//android.widget.TextView[@resource-id="android:id/title" and @text="接收新消息通知"]/../following-sibling::android.view.View'))) 98 | current_state = el_close_open.get_attribute("name") 99 | 100 | if type == 0 and current_state == '已开启': 101 | el_close_open.click() 102 | 103 | elif type == 1 and current_state == '已关闭': 104 | el_close_open.click() 105 | 106 | self.wait.until(EC.element_to_be_clickable((By.ID, 'com.tencent.mm:id/k5'))) 107 | self.press_back() 108 | 109 | self.wait.until(EC.element_to_be_clickable((By.ID, 'com.tencent.mm:id/k5'))) 110 | self.press_back() 111 | 112 | 113 | # 获取随机索引值(用于转发文章时获取随机公众号的文章) 114 | def get_random_num(self,exist_num,max_num): 115 | while True: 116 | n = random.randint(0,max_num) 117 | if n not in exist_num: 118 | return n 119 | 120 | # 通过adb点击搜索结果(针对找不到公众号查询结果元素的机型) 121 | def click_result_by_adb(self): 122 | time.sleep(5) 123 | oappium.execute_cmd(f'adb -s {self.serial} shell input tap {0.45*self.x} {0.25*self.y}') 124 | 125 | # 获取随机公众号列表(用于关注公众号时的随机关注) 126 | def get_random_official_accounts(self,concern_num,official_accounts): 127 | concern_num = len(official_accounts) if concern_num > len(official_accounts) else concern_num 128 | original = official_accounts[:] 129 | result = [] 130 | cnt = 0 131 | 132 | while cnt < concern_num: 133 | index = random.randint(0,len(original) - 1) 134 | random_item = original.pop(index) 135 | result.append(random_item) 136 | cnt += 1 137 | 138 | return result 139 | 140 | # 判断是否点赞 141 | def if_thumbup(self,thumbup_ratio): 142 | if thumbup_ratio == 0: 143 | return False 144 | elif thumbup_ratio == 100: 145 | return True 146 | else: 147 | r = random.random() * 100 148 | if r <= thumbup_ratio: 149 | return True 150 | 151 | # 关注公众号 152 | def concern_official_accounts(self,tactic): 153 | concern_num = tactic['concern_num'] 154 | official_accounts = tactic['official_accounts'] 155 | concern_interval = tactic['concern_interval'] 156 | random_oa = self.get_random_official_accounts(concern_num,official_accounts) 157 | 158 | succeed_num = 0 159 | self.return_to_index_page() 160 | 161 | # 点击搜索 点击公众号 162 | el_official_account = self.click_unstable_el_by_xpath('xpath','//android.view.ViewGroup[@resource-id="com.tencent.mm:id/j9"]//android.support.v7.widget.LinearLayoutCompat/android.widget.RelativeLayout[1]/android.widget.ImageView[@resource-id="com.tencent.mm:id/ij"]','xpath','//android.widget.TextView[@resource-id="com.tencent.mm:id/bvy" and @text="公众号"]') 163 | el_official_account.click() 164 | 165 | for oa in random_oa: 166 | el_search_bar = self.wait.until(EC.element_to_be_clickable((By.ID,'com.tencent.mm:id/ka'))) 167 | el_search_bar.send_keys(oa) 168 | time.sleep(3) 169 | self.click_serach() 170 | 171 | el_search_result = self.is_el_exist('xpath','//android.view.View[@resource-id="search_list"]/android.widget.ListView/android.view.View[1]',timeout=10) 172 | if el_search_result: 173 | el_search_result.click() 174 | else: 175 | self.click_result_by_adb() 176 | 177 | if self.is_el_exist('xpath','//android.widget.TextView[@resource-id="android:id/title" and @text="关注公众号"]'): 178 | el_concern_official_account = self.wait.until(EC.element_to_be_clickable((By.XPATH,'//android.widget.TextView[@resource-id="android:id/title" and @text="关注公众号"]'))) 179 | el_concern_official_account.click() 180 | 181 | while self.is_el_exist('xpath','//android.widget.ImageView[@resource-id="com.tencent.mm:id/jv"]'): 182 | self.press_back() 183 | 184 | succeed_num += 1 185 | self.emit_to_qt(self.serial, '关注数', str(succeed_num)) 186 | 187 | self.wait.until(EC.element_to_be_clickable((By.ID,'com.tencent.mm:id/k4'))) 188 | self.press_back() 189 | 190 | time.sleep(random.randint(*concern_interval)) 191 | 192 | self.wait.until(EC.element_to_be_clickable((By.ID, 'com.tencent.mm:id/k9'))) 193 | self.press_back() 194 | 195 | self.press_back() 196 | 197 | self.return_to_index_page() 198 | 199 | 200 | # 文章阅读转发 201 | def read_share_articles(self,tactic): 202 | article_read_num = tactic['article_read_num'] 203 | if_share = tactic['if_share'] 204 | read_share_interval = tactic['read_share_interval'] 205 | 206 | succeed_num = 0 207 | self.return_to_index_page() 208 | 209 | el_contacts = self.wait.until(EC.element_to_be_clickable((By.XPATH,'//android.widget.RelativeLayout[@resource-id="com.tencent.mm:id/bn"]/android.widget.LinearLayout/android.widget.RelativeLayout[2]'))) 210 | el_contacts.click() 211 | 212 | el_official_accounts = self.wait.until(EC.element_to_be_clickable((By.ID,'com.tencent.mm:id/a50'))) 213 | el_official_accounts.click() 214 | 215 | el_official_accounts_items = self.wait.until(EC.presence_of_all_elements_located((By.XPATH,'//android.widget.LinearLayout[@resource-id="com.tencent.mm:id/a8p"]//android.widget.TextView[@resource-id="com.tencent.mm:id/a8s"]'))) 216 | if el_official_accounts_items: 217 | exist_num = [] 218 | max_num = len(el_official_accounts_items) - 1 219 | for i in range(article_read_num): 220 | if i >= len(el_official_accounts_items): 221 | break 222 | 223 | random_num = self.get_random_num(exist_num,max_num) 224 | exist_num.append(random_num) 225 | 226 | el_item = el_official_accounts_items[random_num] 227 | self.click_unstable_el(el_item,'xpath','//android.widget.ImageButton[@content-desc="聊天信息"]') 228 | 229 | el_chat_info = self.wait.until(EC.element_to_be_clickable((By.XPATH,'//android.widget.ImageButton[@content-desc="聊天信息"]'))) 230 | el_chat_info.click() 231 | 232 | el_latest_article = self.wait.until(EC.element_to_be_clickable((By.XPATH,'//android.support.v7.widget.RecyclerView[@resource-id="com.tencent.mm:id/b1x"]/android.widget.LinearLayout[1]'))) 233 | el_latest_article.click() 234 | 235 | swipe_time = 0 236 | # 模拟阅读 237 | while True: 238 | if self.is_el_displayed('id','js_toobar3',4/5) or swipe_time>=30: 239 | break 240 | 241 | self.swipe(1 / 2, 1 / 2, 1 / 2, 1 / 6, 1000) 242 | swipe_time += 1 243 | time.sleep(5 + random.random()*3) 244 | # time.sleep(1) 245 | 246 | # 转发文章至朋友圈 247 | if if_share: 248 | el_more = self.wait.until(EC.element_to_be_clickable((By.ID,'com.tencent.mm:id/jr'))) 249 | self.click_unstable_el(el_more,'xpath','//android.support.v7.widget.RecyclerView[@resource-id="com.tencent.mm:id/d3p"]//android.widget.TextView[@text="分享到朋友圈"]',1) 250 | 251 | el_share_to_friend_circle = self.wait.until(EC.element_to_be_clickable((By.XPATH,'//android.support.v7.widget.RecyclerView[@resource-id="com.tencent.mm:id/d3p"]//android.widget.TextView[@text="分享到朋友圈"]'))) 252 | el_share_to_friend_circle.click() 253 | 254 | el_publish = self.wait.until(EC.element_to_be_clickable((By.ID,'com.tencent.mm:id/jq'))) 255 | el_publish.click() 256 | 257 | succeed_num += 1 258 | self.emit_to_qt(self.serial, '阅读数', str(succeed_num)) 259 | 260 | self.wait.until(EC.element_to_be_clickable((By.ID,'com.tencent.mm:id/k5'))) 261 | self.press_back() 262 | 263 | self.wait.until(EC.element_to_be_clickable((By.ID, 'com.tencent.mm:id/k5'))) 264 | self.press_back() 265 | 266 | self.wait.until(EC.element_to_be_clickable((By.ID, 'com.tencent.mm:id/jv'))) 267 | self.press_back() 268 | 269 | time.sleep(random.randint(*read_share_interval)) 270 | 271 | self.wait.until(EC.presence_of_all_elements_located((By.XPATH, '//android.widget.LinearLayout[@resource-id="com.tencent.mm:id/a8p"]//android.widget.TextView[@resource-id="com.tencent.mm:id/a8s"]'))) 272 | 273 | 274 | self.return_to_index_page() 275 | 276 | 277 | # 朋友圈点赞 278 | def moments_thumbup(self,tactic): 279 | moments_swipe_num = tactic['moments_swipe_num'] 280 | moments_thumbup_ratio = tactic['moments_thumbup_ratio'] 281 | thumbup_interval = tactic['thumbup_interval'] 282 | 283 | self.return_to_index_page() 284 | 285 | el_discover = self.wait.until(EC.element_to_be_clickable((By.XPATH, '//android.widget.RelativeLayout[@resource-id="com.tencent.mm:id/bn"]/android.widget.LinearLayout/android.widget.RelativeLayout[3]'))) 286 | el_discover.click() 287 | 288 | el_moments = self.wait.until(EC.element_to_be_clickable((By.XPATH, '//android.widget.ListView[@resource-id="android:id/list"]/android.widget.LinearLayout[1]'))) 289 | el_moments.click() 290 | 291 | swipe_num = 0 292 | while swipe_num < moments_swipe_num: 293 | if self.is_el_exist('xpath','//android.widget.FrameLayout[@resource-id="com.tencent.mm:id/efv"]'): 294 | moments_items = self.wait.until(EC.presence_of_all_elements_located( 295 | (By.XPATH, '//android.widget.FrameLayout[@resource-id="com.tencent.mm:id/efv"]'))) 296 | 297 | for item in moments_items: 298 | if not self.if_thumbup(moments_thumbup_ratio): 299 | continue 300 | 301 | try: 302 | # 查找评论按钮元素不稳定,放在循环里不断查找 303 | while 1: 304 | el_comment = item.find_element_by_xpath( 305 | './/android.widget.ImageView[@resource-id="com.tencent.mm:id/eb6"]') 306 | el_comment_height = el_comment.location['y'] 307 | if el_comment_height > 0.2 * self.y and el_comment_height < 0.9 * self.y: 308 | el_comment.click() 309 | 310 | el_thumbup = self.is_el_exist('id','com.tencent.mm:id/eae') 311 | if el_thumbup: 312 | if el_thumbup.text.strip() == '赞': 313 | el_thumbup.click() 314 | else: 315 | el_comment.click() 316 | # 点击评论按钮时可能会点错位置跳到其他页面,判断当前页面是否为朋友圈,不是则按下返回 317 | time.sleep(3) 318 | if not self.is_el_exist('id', 'com.tencent.mm:id/ebi'): 319 | self.press_back() 320 | time.sleep(3) 321 | 322 | break 323 | 324 | else: 325 | if not self.is_el_exist('id','com.tencent.mm:id/ebi'): 326 | self.press_back() 327 | time.sleep(3) 328 | 329 | else: 330 | break 331 | 332 | except Exception as e: 333 | continue 334 | 335 | time.sleep(random.randint(*thumbup_interval)) 336 | 337 | try: 338 | self.driver.find_element_by_id('com.tencent.mm:id/ah8') 339 | break 340 | except: 341 | self.swipe(1 / 2, 1 / 2, 1 / 2, 1 / 6, 1000) 342 | swipe_num += 1 343 | self.emit_to_qt(self.serial, '滑动数', str(swipe_num)) 344 | 345 | self.return_to_index_page() 346 | 347 | 348 | # 发送消息 349 | def send_msg(self,tactic): 350 | chat_objects = tactic['chat_objects'] 351 | msg_contents = tactic['msg_contents'] 352 | send_msg_interval = tactic['send_msg_interval'] 353 | 354 | friends = [obj['name'] for obj in chat_objects if obj['type'] == 1] 355 | groups = [obj['name'] for obj in chat_objects if obj['type'] == 2] 356 | filtered_friends = [] 357 | 358 | succeed_num = 0 359 | self.return_to_index_page() 360 | 361 | # 过滤好友对象 362 | self.click_unstable_el_by_xpath('xpath','//android.widget.TextView[@resource-id="com.tencent.mm:id/d3t" and @text="通讯录"]','id','com.tencent.mm:id/m_') 363 | while len(filtered_friends) < len(friends): 364 | el_contacts = self.wait.until(EC.presence_of_all_elements_located((By.XPATH,'//android.widget.ListView[@resource-id="com.tencent.mm:id/m_"]/android.widget.LinearLayout/android.widget.LinearLayout'))) 365 | for el_friend in el_contacts: 366 | try: 367 | el_friend_name = el_friend.find_element_by_xpath('.//android.view.View[@resource-id="com.tencent.mm:id/n8"]') 368 | friend_name = el_friend_name.text 369 | if friend_name in friends and friend_name not in filtered_friends: 370 | filtered_friends.append(friend_name) 371 | 372 | except: 373 | continue 374 | 375 | try: 376 | self.driver.find_element_by_id('com.tencent.mm:id/azr') 377 | break 378 | except: 379 | self.swipe(1 / 2, 1 / 2, 1 / 2, 1 / 6, 1000) 380 | 381 | chat_objs = groups + filtered_friends 382 | 383 | # 发送消息 384 | el_search = self.wait.until(EC.element_to_be_clickable( 385 | (By.XPATH, '//android.support.v7.widget.LinearLayoutCompat/android.widget.RelativeLayout[1]'))) 386 | el_search.click() 387 | 388 | for chat_obj in chat_objs: 389 | el_search_bar = self.wait.until(EC.presence_of_element_located((By.ID,'com.tencent.mm:id/ka'))) 390 | el_search_bar.send_keys(chat_obj) 391 | 392 | if self.is_el_exist('xpath','//android.widget.ListView[@resource-id="com.tencent.mm:id/buk"]/android.widget.RelativeLayout[2]'): 393 | el_first_result = self.wait.until(EC.element_to_be_clickable( 394 | (By.XPATH, '//android.widget.ListView[@resource-id="com.tencent.mm:id/buk"]/android.widget.RelativeLayout[2]'))) 395 | el_first_result.click() 396 | 397 | el_msg_bar = self.wait.until(EC.presence_of_element_located((By.ID,'com.tencent.mm:id/alm'))) 398 | el_msg_bar.send_keys(random.choice(msg_contents)) 399 | 400 | el_send = self.wait.until(EC.element_to_be_clickable((By.ID,'com.tencent.mm:id/als'))) 401 | el_send.click() 402 | 403 | succeed_num += 1 404 | self.emit_to_qt(self.serial, '消息数', str(succeed_num)) 405 | 406 | self.wait.until(EC.presence_of_element_located((By.ID,'com.tencent.mm:id/jv'))) 407 | self.press_back() 408 | 409 | time.sleep(random.randint(*send_msg_interval)) 410 | 411 | self.return_to_index_page() 412 | 413 | def switch_accounts(self): 414 | ''' 415 | 切换账号并判断是否需要执行策略 416 | :return: True代表该设备存在多账号,且需要执行策略;False代表该设备不存在多账号或不需要执行策略 417 | ''' 418 | # 点击我 点击设置 419 | el_setting = self.click_unstable_el_by_xpath('xpath','//android.widget.TextView[@resource-id="com.tencent.mm:id/d3t" and @text="我"]','xpath','//android.widget.TextView[@resource-id="android:id/title" and @text="设置"]') 420 | time.sleep(5) 421 | el_setting.click() 422 | 423 | # 点击切换账号 424 | el_switch_accounts = self.wait.until(EC.element_to_be_clickable( 425 | (By.XPATH, '//android.widget.TextView[@resource-id="android:id/title" and @text="切换帐号"]'))) 426 | el_switch_accounts.click() 427 | 428 | # 点击知道了(首次进入会弹出) 429 | el_ok = self.is_el_exist('id','com.tencent.mm:id/ayb') 430 | if el_ok: 431 | el_ok.click() 432 | 433 | # 获取所有账号 434 | el_accounts = self.wait.until(EC.presence_of_all_elements_located((By.XPATH,'//android.widget.GridLayout[@resource-id="com.tencent.mm:id/e46"]/android.widget.LinearLayout'))) 435 | 436 | # 循环每个账号,查找‘当前账号’标志的元素,找不到表示该账号为待切换账号 437 | for el_account in el_accounts: 438 | try: 439 | el_account.find_element_by_id('com.tencent.mm:id/e4a') 440 | except: 441 | # 切换账号,判断该账号是否需要执行策略 442 | el_account_name = el_account.find_element_by_xpath('.//android.widget.TextView[@resource-id="com.tencent.mm:id/e4_"]') 443 | if el_account_name.text.strip() != '切换帐号': 444 | el_account_pic = el_account.find_element_by_xpath('.//android.widget.ImageView[@resource-id="com.tencent.mm:id/e48"]') 445 | el_account_pic.click() 446 | 447 | # 检测是否需要输入密码 448 | el_pwd = self.is_el_exist('id','com.tencent.mm:id/ka',30) 449 | if el_pwd: 450 | el_current_account = self.wait.until(EC.presence_of_element_located((By.ID,'com.tencent.mm:id/cmt'))) 451 | current_account = el_current_account.text.strip().replace(' ','') 452 | 453 | el_pwd.send_keys(WECHAT_PASSWORD) 454 | el_login = self.wait.until(EC.element_to_be_clickable((By.ID,'com.tencent.mm:id/cmw'))) 455 | el_login.click() 456 | 457 | # 若密码错误,返回至切换账号界面,选择原账号并登录 458 | el_pwd_error = self.is_el_exist('id', 'com.tencent.mm:id/ayb',5) 459 | if el_pwd_error: 460 | logging.warning(f'Wrong Password:{self.serial} {current_account}') 461 | 462 | self.press_back(sleep=3) 463 | self.press_back(sleep=3) 464 | el_accounts_2 = self.wait.until(EC.presence_of_all_elements_located((By.XPATH, 465 | '//android.widget.GridLayout[@resource-id="com.tencent.mm:id/e46"]/android.widget.LinearLayout'))) 466 | for el_account_2 in el_accounts_2: 467 | el_name = el_account.find_element_by_xpath('.//android.widget.TextView[@resource-id="com.tencent.mm:id/e4_"]') 468 | if current_account not in el_name.text: 469 | el_pic = el_account_2.find_element_by_xpath('.//android.widget.ImageView[@resource-id="com.tencent.mm:id/e48"]') 470 | el_pic.click() 471 | 472 | self.wait.until(EC.presence_of_element_located((By.XPATH, 473 | '//android.widget.RelativeLayout[@resource-id="com.tencent.mm:id/bn"]/android.widget.LinearLayout/android.widget.RelativeLayout[1]'))) 474 | 475 | return False 476 | 477 | self.wait.until(EC.presence_of_element_located((By.XPATH,'//android.widget.RelativeLayout[@resource-id="com.tencent.mm:id/bn"]/android.widget.LinearLayout/android.widget.RelativeLayout[1]'))) 478 | self.click_unstable_el_by_xpath('xpath','//android.widget.TextView[@resource-id="com.tencent.mm:id/d3t" and @text="我"]','xpath','//android.widget.TextView[@resource-id="android:id/title" and @text="设置"]') 479 | 480 | el_wechat_name = self.wait.until(EC.presence_of_element_located((By.ID, 'com.tencent.mm:id/a5b'))) 481 | if el_wechat_name.text not in self.finished_accounts: 482 | self.current_wechat_name = el_wechat_name.text 483 | self.emit_to_qt(self.serial, '当前账号', self.current_wechat_name) 484 | return True 485 | 486 | break 487 | 488 | self.return_to_index_page() 489 | 490 | def run_tactics(self): 491 | my_tactics = TACTICS[self.current_tactic:] 492 | 493 | for tactic in my_tactics: 494 | tactic_num = str(self.current_tactic + 1) 495 | if len(self.finished_accounts) >= 1: 496 | tactic_num += '*' 497 | self.emit_to_qt(self.serial, '策略号', tactic_num) 498 | 499 | if SCREEN_OFF: 500 | self.awake_and_unlock_screen() 501 | 502 | type = tactic['type'] 503 | interval = tactic['tactic_interval'] 504 | if type == 0: 505 | self.concern_official_accounts(tactic) 506 | 507 | elif type == 1: 508 | self.read_share_articles(tactic) 509 | 510 | elif type == 2: 511 | self.moments_thumbup(tactic) 512 | 513 | elif type == 3: 514 | self.send_msg(tactic) 515 | 516 | self.current_tactic += 1 517 | 518 | if SCREEN_OFF: 519 | self.driver.press_keycode(26) 520 | 521 | time.sleep(interval * 60) 522 | 523 | self.finished_accounts.append(self.current_wechat_name) 524 | 525 | def run(self): 526 | # 更新设备状态为'运行中' 527 | self.emit_to_qt(self.serial,'状态',DEVICE_STATE_DICT[4]) 528 | 529 | # 唤醒屏幕 530 | self.awake_and_unlock_screen() 531 | 532 | # 关闭消息提醒 533 | self.close_open_notify(0) 534 | 535 | # 运行策略 536 | self.run_tactics() 537 | 538 | if SWITCH_ACCOUNTS: 539 | if self.switch_accounts(): 540 | self.current_tactic = 0 541 | self.run_tactics() 542 | 543 | # 开启消息提醒 544 | self.close_open_notify(1) 545 | 546 | logging.info(f'Finished.{self.serial}') 547 | self.emit_to_qt(self.serial, '状态', DEVICE_STATE_DICT[5]) 548 | 549 | self.quit() 550 | 551 | def restart(self): 552 | try: 553 | self.driver.quit() 554 | except: 555 | pass 556 | 557 | for i in range(3): 558 | try: 559 | logging.info(f'Restart port:{self.port} serial:{self.serial} desired_caps:{self.desired_caps}') 560 | self.driver = webdriver.Remote(f'http://localhost:{self.port}/wd/hub', self.desired_caps) 561 | self.__init__(self.deviceName,self.serial,self.port,self.driver,self.desired_caps,self.current_tactic) 562 | break 563 | except Exception as e: 564 | time.sleep(5) 565 | logging.warning(f'Restart to Get Driver Failed:{e} {self.serial} Retrying:{i+1}') 566 | 567 | 568 | def run_driver(deviceName, serial, port, driver, desired_caps): 569 | auto_tool = WRAAutoTool(deviceName, serial, port, driver, desired_caps) 570 | 571 | retry_cnt = 0 572 | for i in range(100): 573 | try: 574 | auto_tool.run() 575 | return 576 | except Exception as e: 577 | time.sleep(60) 578 | retry_cnt += 1 579 | logging.error(f'Running Error:{e} {serial} Retrying:{retry_cnt}') 580 | auto_tool.restart() 581 | 582 | auto_tool.emit_to_qt(serial, '状态', DEVICE_STATE_DICT[6]) 583 | 584 | 585 | 586 | 587 | 588 | def test(): 589 | desired_caps = { 590 | "platformName": "Android", 591 | "deviceName": 'MI_MAX', 592 | "appPackage": "com.tencent.mm", 593 | "appActivity": ".ui.LauncherUI", 594 | "noReset": True, 595 | 'unicodeKeyboard': True, 596 | 'newCommandTimeout': 86400, 597 | } 598 | 599 | driver = webdriver.Remote(f'http://localhost:4723/wd/hub', desired_caps) 600 | auto_tool = WRAAutoTool('MI_MAX','1b05e24e',4723,driver,desired_caps) 601 | auto_tool.awake_and_unlock_screen() 602 | auto_tool.close_open_notify(0) 603 | 604 | # auto_tool.quit() 605 | 606 | 607 | if __name__ == '__main__': 608 | test() 609 | -------------------------------------------------------------------------------- /weixin_raise_accounts/wra_auto_tool_multi.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import oappium 4 | import wra_auto_tool 5 | import logging 6 | import threading 7 | from settings import * 8 | from appium import webdriver 9 | from copy import deepcopy 10 | 11 | TACTICS = [ 12 | { 13 | 'type':0, 14 | 'name':'关注公众号', 15 | 'official_accounts':['高效工具搜罗','一点厦门','刘备教授'], 16 | 'concern_interval':(0,1) 17 | }, 18 | { 19 | 'type':1, 20 | 'name':'文章阅读转发', 21 | 'article_read_num':3, 22 | 'if_share':False, 23 | 'read_share_interval':(0,1) 24 | }, 25 | { 26 | 'type':2, 27 | 'name':'朋友圈点赞', 28 | 'moments_thumbup_num':5, 29 | 'thumbup_interval':(0,1) 30 | }, 31 | { 32 | 'type':3, 33 | 'name':'发送消息', 34 | 'chat_objects':['安静的兰胖纸','hdkahsjkd','中国不讲生僻字','聊天群1','撒开了房间出来'], 35 | 'msg_contents':['你好','hello','hi'], 36 | 'send_msg_interval':(0,1) 37 | } 38 | ] 39 | 40 | 41 | class WRAAutoToolMulti(oappium.MultiAppium): 42 | def __init__(self,tactics,qt_signal=None,screen_off=True,switch_accounts=False): 43 | super().__init__() 44 | self.target = wra_auto_tool.run_driver 45 | self.desired_caps = { 46 | "platformName": "Android", 47 | "deviceName": '', 48 | "appPackage": "com.tencent.mm", 49 | "appActivity": ".ui.LauncherUI", 50 | "noReset": True, 51 | 'unicodeKeyboard': True, 52 | 'newCommandTimeout': 86400, 53 | "udid": '', 54 | } 55 | self.tactics = tactics 56 | self.qt_signal = qt_signal 57 | self.screen_off = screen_off 58 | self.switch_accounts = switch_accounts 59 | 60 | def init_settings(self): 61 | wra_auto_tool.TACTICS = self.tactics 62 | wra_auto_tool.QT_SIGNAL = self.qt_signal 63 | wra_auto_tool.SCREEN_OFF = self.screen_off 64 | wra_auto_tool.SWITCH_ACCOUNTS = self.switch_accounts 65 | 66 | def get_task_threads(self): 67 | get_driver_threads = [] 68 | for device in self.devices: 69 | deviceName = device['deviceName'] 70 | serial = device['serial'] 71 | port = device['port'] 72 | caps = deepcopy(self.desired_caps) 73 | 74 | caps['deviceName'] = deviceName 75 | caps['udid'] = serial 76 | 77 | self.qt_signal.emit(serial, '状态', DEVICE_STATE_DICT[1]) 78 | 79 | t = threading.Thread(target=self.get_driver,args=(serial,deviceName,port,self.target,caps)) 80 | t.start() 81 | get_driver_threads.append(t) 82 | 83 | for t in get_driver_threads: 84 | t.join() 85 | 86 | def get_driver(self,serial,deviceName,port,target,desired_caps,try_time=3): 87 | for i in range(try_time): 88 | try: 89 | driver = webdriver.Remote(f'http://localhost:{port}/wd/hub', desired_caps) 90 | 91 | logging.info(f'Get Driver Succeed:{deviceName} {serial}') 92 | self.qt_signal.emit(serial, '状态', DEVICE_STATE_DICT[2]) 93 | 94 | t = threading.Thread(target=target, args=(deviceName, serial, port, driver, desired_caps)) 95 | self.task_threads.append(t) 96 | 97 | return 98 | except Exception as e: 99 | logging.error(f'Driver Start Failed:{e} Retring:{i+1}') 100 | 101 | logging.warning(f'Get Driver Failed:{deviceName} {serial}') 102 | self.qt_signal.emit(serial, '状态', DEVICE_STATE_DICT[3]) 103 | 104 | if __name__ == '__main__': 105 | auto_obj = WRAAutoToolMulti(TACTICS) 106 | auto_obj.init_settings() 107 | auto_obj.run() -------------------------------------------------------------------------------- /weixin_raise_accounts/wra_auto_tool_ui.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Form implementation generated from reading ui file 'wra_auto_tool_ui.ui' 4 | # 5 | # Created by: PyQt5 UI code generator 5.11.3 6 | # 7 | # WARNING! All changes made in this file will be lost! 8 | 9 | from PyQt5 import QtCore, QtGui, QtWidgets 10 | 11 | class Ui_MainWindow(object): 12 | def setupUi(self, MainWindow): 13 | MainWindow.setObjectName("MainWindow") 14 | MainWindow.resize(902, 823) 15 | MainWindow.setStyleSheet("") 16 | self.centralwidget = QtWidgets.QWidget(MainWindow) 17 | self.centralwidget.setObjectName("centralwidget") 18 | self.groupBox = QtWidgets.QGroupBox(self.centralwidget) 19 | self.groupBox.setGeometry(QtCore.QRect(20, 20, 451, 400)) 20 | self.groupBox.setStyleSheet("#groupBox{border:1px solid}") 21 | self.groupBox.setObjectName("groupBox") 22 | self.tabWidget = QtWidgets.QTabWidget(self.groupBox) 23 | self.tabWidget.setGeometry(QtCore.QRect(10, 20, 431, 371)) 24 | self.tabWidget.setStyleSheet("") 25 | self.tabWidget.setObjectName("tabWidget") 26 | self.tab_0 = QtWidgets.QWidget() 27 | self.tab_0.setObjectName("tab_0") 28 | self.te_official_accounts = QtWidgets.QTextEdit(self.tab_0) 29 | self.te_official_accounts.setEnabled(True) 30 | self.te_official_accounts.setGeometry(QtCore.QRect(88, 96, 320, 121)) 31 | self.te_official_accounts.setReadOnly(True) 32 | self.te_official_accounts.setObjectName("te_official_accounts") 33 | self.pb_add_official_accounts = QtWidgets.QPushButton(self.tab_0) 34 | self.pb_add_official_accounts.setGeometry(QtCore.QRect(324, 60, 40, 23)) 35 | self.pb_add_official_accounts.setObjectName("pb_add_official_accounts") 36 | self.pb_add_tactic0 = QtWidgets.QPushButton(self.tab_0) 37 | self.pb_add_tactic0.setGeometry(QtCore.QRect(337, 320, 75, 23)) 38 | self.pb_add_tactic0.setObjectName("pb_add_tactic0") 39 | self.pb_clear_official_accounts = QtWidgets.QPushButton(self.tab_0) 40 | self.pb_clear_official_accounts.setGeometry(QtCore.QRect(368, 60, 40, 23)) 41 | self.pb_clear_official_accounts.setObjectName("pb_clear_official_accounts") 42 | self.le_concern_interval_1 = QtWidgets.QLineEdit(self.tab_0) 43 | self.le_concern_interval_1.setGeometry(QtCore.QRect(87, 231, 61, 20)) 44 | self.le_concern_interval_1.setObjectName("le_concern_interval_1") 45 | self.le_official_account = QtWidgets.QLineEdit(self.tab_0) 46 | self.le_official_account.setGeometry(QtCore.QRect(88, 61, 230, 20)) 47 | self.le_official_account.setObjectName("le_official_account") 48 | self.label_2 = QtWidgets.QLabel(self.tab_0) 49 | self.label_2.setGeometry(QtCore.QRect(23, 234, 48, 16)) 50 | self.label_2.setObjectName("label_2") 51 | self.label = QtWidgets.QLabel(self.tab_0) 52 | self.label.setGeometry(QtCore.QRect(23, 63, 36, 16)) 53 | self.label.setObjectName("label") 54 | self.label_10 = QtWidgets.QLabel(self.tab_0) 55 | self.label_10.setGeometry(QtCore.QRect(150, 235, 21, 16)) 56 | self.label_10.setObjectName("label_10") 57 | self.le_concern_interval_2 = QtWidgets.QLineEdit(self.tab_0) 58 | self.le_concern_interval_2.setGeometry(QtCore.QRect(158, 231, 61, 20)) 59 | self.le_concern_interval_2.setObjectName("le_concern_interval_2") 60 | self.label_14 = QtWidgets.QLabel(self.tab_0) 61 | self.label_14.setGeometry(QtCore.QRect(21, 266, 48, 16)) 62 | self.label_14.setObjectName("label_14") 63 | self.le_tactic0_interval = QtWidgets.QLineEdit(self.tab_0) 64 | self.le_tactic0_interval.setGeometry(QtCore.QRect(87, 264, 30, 20)) 65 | self.le_tactic0_interval.setObjectName("le_tactic0_interval") 66 | self.label_15 = QtWidgets.QLabel(self.tab_0) 67 | self.label_15.setGeometry(QtCore.QRect(120, 269, 291, 16)) 68 | font = QtGui.QFont() 69 | font.setPointSize(8) 70 | self.label_15.setFont(font) 71 | self.label_15.setStyleSheet("#label_15{color:red}") 72 | self.label_15.setObjectName("label_15") 73 | self.label_37 = QtWidgets.QLabel(self.tab_0) 74 | self.label_37.setGeometry(QtCore.QRect(24, 28, 54, 12)) 75 | self.label_37.setObjectName("label_37") 76 | self.le_concern_num = QtWidgets.QLineEdit(self.tab_0) 77 | self.le_concern_num.setGeometry(QtCore.QRect(88, 24, 30, 20)) 78 | self.le_concern_num.setObjectName("le_concern_num") 79 | self.label_38 = QtWidgets.QLabel(self.tab_0) 80 | self.label_38.setGeometry(QtCore.QRect(121, 28, 201, 16)) 81 | font = QtGui.QFont() 82 | font.setPointSize(8) 83 | self.label_38.setFont(font) 84 | self.label_38.setLayoutDirection(QtCore.Qt.LeftToRight) 85 | self.label_38.setStyleSheet("#label_38{color:red}") 86 | self.label_38.setObjectName("label_38") 87 | self.tabWidget.addTab(self.tab_0, "") 88 | self.tab_1 = QtWidgets.QWidget() 89 | self.tab_1.setObjectName("tab_1") 90 | self.label_3 = QtWidgets.QLabel(self.tab_1) 91 | self.label_3.setGeometry(QtCore.QRect(31, 41, 60, 16)) 92 | self.label_3.setObjectName("label_3") 93 | self.le_article_read_num = QtWidgets.QLineEdit(self.tab_1) 94 | self.le_article_read_num.setGeometry(QtCore.QRect(110, 41, 100, 20)) 95 | self.le_article_read_num.setObjectName("le_article_read_num") 96 | self.cb_if_share = QtWidgets.QCheckBox(self.tab_1) 97 | self.cb_if_share.setGeometry(QtCore.QRect(220, 43, 45, 16)) 98 | font = QtGui.QFont() 99 | font.setPointSize(8) 100 | self.cb_if_share.setFont(font) 101 | self.cb_if_share.setObjectName("cb_if_share") 102 | self.label_5 = QtWidgets.QLabel(self.tab_1) 103 | self.label_5.setGeometry(QtCore.QRect(40, 98, 48, 16)) 104 | self.label_5.setObjectName("label_5") 105 | self.pb_add_tactic1 = QtWidgets.QPushButton(self.tab_1) 106 | self.pb_add_tactic1.setGeometry(QtCore.QRect(337, 320, 75, 23)) 107 | self.pb_add_tactic1.setObjectName("pb_add_tactic1") 108 | self.le_read_share_interval_1 = QtWidgets.QLineEdit(self.tab_1) 109 | self.le_read_share_interval_1.setGeometry(QtCore.QRect(109, 94, 61, 20)) 110 | self.le_read_share_interval_1.setObjectName("le_read_share_interval_1") 111 | self.label_11 = QtWidgets.QLabel(self.tab_1) 112 | self.label_11.setGeometry(QtCore.QRect(172, 97, 21, 16)) 113 | self.label_11.setObjectName("label_11") 114 | self.le_read_share_interval_2 = QtWidgets.QLineEdit(self.tab_1) 115 | self.le_read_share_interval_2.setGeometry(QtCore.QRect(180, 94, 61, 20)) 116 | self.le_read_share_interval_2.setObjectName("le_read_share_interval_2") 117 | self.label_16 = QtWidgets.QLabel(self.tab_1) 118 | self.label_16.setGeometry(QtCore.QRect(39, 147, 48, 16)) 119 | self.label_16.setObjectName("label_16") 120 | self.le_tactic1_interval = QtWidgets.QLineEdit(self.tab_1) 121 | self.le_tactic1_interval.setGeometry(QtCore.QRect(109, 145, 30, 20)) 122 | self.le_tactic1_interval.setObjectName("le_tactic1_interval") 123 | self.tabWidget.addTab(self.tab_1, "") 124 | self.tab_2 = QtWidgets.QWidget() 125 | self.tab_2.setObjectName("tab_2") 126 | self.label_6 = QtWidgets.QLabel(self.tab_2) 127 | self.label_6.setGeometry(QtCore.QRect(55, 113, 48, 16)) 128 | self.label_6.setObjectName("label_6") 129 | self.pb_add_tactic2 = QtWidgets.QPushButton(self.tab_2) 130 | self.pb_add_tactic2.setGeometry(QtCore.QRect(337, 320, 75, 23)) 131 | self.pb_add_tactic2.setObjectName("pb_add_tactic2") 132 | self.label_4 = QtWidgets.QLabel(self.tab_2) 133 | self.label_4.setGeometry(QtCore.QRect(55, 42, 81, 16)) 134 | self.label_4.setObjectName("label_4") 135 | self.le_thumbup_interval_1 = QtWidgets.QLineEdit(self.tab_2) 136 | self.le_thumbup_interval_1.setGeometry(QtCore.QRect(130, 110, 61, 20)) 137 | self.le_thumbup_interval_1.setObjectName("le_thumbup_interval_1") 138 | self.le_moments_swipe_num = QtWidgets.QLineEdit(self.tab_2) 139 | self.le_moments_swipe_num.setGeometry(QtCore.QRect(131, 40, 30, 20)) 140 | self.le_moments_swipe_num.setObjectName("le_moments_swipe_num") 141 | self.label_12 = QtWidgets.QLabel(self.tab_2) 142 | self.label_12.setGeometry(QtCore.QRect(192, 112, 21, 16)) 143 | self.label_12.setObjectName("label_12") 144 | self.le_thumbup_interval_2 = QtWidgets.QLineEdit(self.tab_2) 145 | self.le_thumbup_interval_2.setGeometry(QtCore.QRect(200, 110, 61, 20)) 146 | self.le_thumbup_interval_2.setObjectName("le_thumbup_interval_2") 147 | self.label_17 = QtWidgets.QLabel(self.tab_2) 148 | self.label_17.setGeometry(QtCore.QRect(51, 152, 48, 16)) 149 | self.label_17.setObjectName("label_17") 150 | self.le_tactic2_interval = QtWidgets.QLineEdit(self.tab_2) 151 | self.le_tactic2_interval.setGeometry(QtCore.QRect(130, 150, 30, 20)) 152 | self.le_tactic2_interval.setObjectName("le_tactic2_interval") 153 | self.label_39 = QtWidgets.QLabel(self.tab_2) 154 | self.label_39.setGeometry(QtCore.QRect(44, 76, 71, 16)) 155 | self.label_39.setObjectName("label_39") 156 | self.le_moments_thumbup_ratio = QtWidgets.QLineEdit(self.tab_2) 157 | self.le_moments_thumbup_ratio.setGeometry(QtCore.QRect(131, 73, 30, 20)) 158 | self.le_moments_thumbup_ratio.setObjectName("le_moments_thumbup_ratio") 159 | self.label_40 = QtWidgets.QLabel(self.tab_2) 160 | self.label_40.setGeometry(QtCore.QRect(168, 43, 201, 16)) 161 | font = QtGui.QFont() 162 | font.setPointSize(8) 163 | self.label_40.setFont(font) 164 | self.label_40.setLayoutDirection(QtCore.Qt.LeftToRight) 165 | self.label_40.setStyleSheet("#label_40{color:red}") 166 | self.label_40.setObjectName("label_40") 167 | self.label_41 = QtWidgets.QLabel(self.tab_2) 168 | self.label_41.setGeometry(QtCore.QRect(167, 77, 251, 16)) 169 | font = QtGui.QFont() 170 | font.setPointSize(8) 171 | self.label_41.setFont(font) 172 | self.label_41.setLayoutDirection(QtCore.Qt.LeftToRight) 173 | self.label_41.setStyleSheet("#label_41{color:red}") 174 | self.label_41.setObjectName("label_41") 175 | self.tabWidget.addTab(self.tab_2, "") 176 | self.tab_3 = QtWidgets.QWidget() 177 | self.tab_3.setObjectName("tab_3") 178 | self.te_chat_objects = QtWidgets.QTextEdit(self.tab_3) 179 | self.te_chat_objects.setEnabled(True) 180 | self.te_chat_objects.setGeometry(QtCore.QRect(80, 49, 331, 71)) 181 | self.te_chat_objects.setReadOnly(True) 182 | self.te_chat_objects.setObjectName("te_chat_objects") 183 | self.label_7 = QtWidgets.QLabel(self.tab_3) 184 | self.label_7.setGeometry(QtCore.QRect(15, 23, 51, 16)) 185 | self.label_7.setObjectName("label_7") 186 | self.le_chat_object = QtWidgets.QLineEdit(self.tab_3) 187 | self.le_chat_object.setGeometry(QtCore.QRect(80, 21, 170, 20)) 188 | self.le_chat_object.setObjectName("le_chat_object") 189 | self.pb_add_chat_objects = QtWidgets.QPushButton(self.tab_3) 190 | self.pb_add_chat_objects.setGeometry(QtCore.QRect(329, 20, 40, 23)) 191 | self.pb_add_chat_objects.setObjectName("pb_add_chat_objects") 192 | self.pb_add_tactic3 = QtWidgets.QPushButton(self.tab_3) 193 | self.pb_add_tactic3.setGeometry(QtCore.QRect(337, 320, 75, 23)) 194 | self.pb_add_tactic3.setObjectName("pb_add_tactic3") 195 | self.pb_add_msg_contents = QtWidgets.QPushButton(self.tab_3) 196 | self.pb_add_msg_contents.setGeometry(QtCore.QRect(328, 130, 40, 23)) 197 | self.pb_add_msg_contents.setObjectName("pb_add_msg_contents") 198 | self.le_msg_content = QtWidgets.QLineEdit(self.tab_3) 199 | self.le_msg_content.setGeometry(QtCore.QRect(80, 131, 241, 20)) 200 | self.le_msg_content.setObjectName("le_msg_content") 201 | self.label_8 = QtWidgets.QLabel(self.tab_3) 202 | self.label_8.setGeometry(QtCore.QRect(15, 133, 51, 16)) 203 | self.label_8.setObjectName("label_8") 204 | self.te_msg_contents = QtWidgets.QTextEdit(self.tab_3) 205 | self.te_msg_contents.setEnabled(True) 206 | self.te_msg_contents.setGeometry(QtCore.QRect(80, 159, 331, 91)) 207 | self.te_msg_contents.setReadOnly(True) 208 | self.te_msg_contents.setObjectName("te_msg_contents") 209 | self.label_9 = QtWidgets.QLabel(self.tab_3) 210 | self.label_9.setGeometry(QtCore.QRect(15, 265, 48, 16)) 211 | self.label_9.setObjectName("label_9") 212 | self.le_send_msg_interval_1 = QtWidgets.QLineEdit(self.tab_3) 213 | self.le_send_msg_interval_1.setGeometry(QtCore.QRect(80, 261, 61, 20)) 214 | self.le_send_msg_interval_1.setObjectName("le_send_msg_interval_1") 215 | self.pb_clear_chat_objects = QtWidgets.QPushButton(self.tab_3) 216 | self.pb_clear_chat_objects.setGeometry(QtCore.QRect(370, 20, 40, 23)) 217 | self.pb_clear_chat_objects.setObjectName("pb_clear_chat_objects") 218 | self.pb_clear_msg_contents = QtWidgets.QPushButton(self.tab_3) 219 | self.pb_clear_msg_contents.setGeometry(QtCore.QRect(370, 130, 40, 23)) 220 | self.pb_clear_msg_contents.setObjectName("pb_clear_msg_contents") 221 | self.label_13 = QtWidgets.QLabel(self.tab_3) 222 | self.label_13.setGeometry(QtCore.QRect(142, 264, 21, 16)) 223 | self.label_13.setObjectName("label_13") 224 | self.le_send_msg_interval_2 = QtWidgets.QLineEdit(self.tab_3) 225 | self.le_send_msg_interval_2.setGeometry(QtCore.QRect(150, 261, 61, 20)) 226 | self.le_send_msg_interval_2.setObjectName("le_send_msg_interval_2") 227 | self.le_tactic3_interval = QtWidgets.QLineEdit(self.tab_3) 228 | self.le_tactic3_interval.setGeometry(QtCore.QRect(79, 290, 30, 20)) 229 | self.le_tactic3_interval.setObjectName("le_tactic3_interval") 230 | self.label_18 = QtWidgets.QLabel(self.tab_3) 231 | self.label_18.setGeometry(QtCore.QRect(11, 292, 48, 16)) 232 | self.label_18.setObjectName("label_18") 233 | self.cb_chat_object_type = QtWidgets.QComboBox(self.tab_3) 234 | self.cb_chat_object_type.setGeometry(QtCore.QRect(252, 20, 70, 22)) 235 | self.cb_chat_object_type.setObjectName("cb_chat_object_type") 236 | self.cb_chat_object_type.addItem("") 237 | self.cb_chat_object_type.addItem("") 238 | self.cb_chat_object_type.addItem("") 239 | self.tabWidget.addTab(self.tab_3, "") 240 | self.line = QtWidgets.QFrame(self.centralwidget) 241 | self.line.setGeometry(QtCore.QRect(20, 420, 870, 16)) 242 | self.line.setFrameShape(QtWidgets.QFrame.HLine) 243 | self.line.setFrameShadow(QtWidgets.QFrame.Sunken) 244 | self.line.setObjectName("line") 245 | self.groupBox_2 = QtWidgets.QGroupBox(self.centralwidget) 246 | self.groupBox_2.setGeometry(QtCore.QRect(490, 20, 401, 400)) 247 | self.groupBox_2.setStyleSheet("#groupBox_2{border:1px solid}") 248 | self.groupBox_2.setObjectName("groupBox_2") 249 | self.te_current_tactics = QtWidgets.QTextEdit(self.groupBox_2) 250 | self.te_current_tactics.setEnabled(True) 251 | self.te_current_tactics.setGeometry(QtCore.QRect(10, 40, 380, 321)) 252 | self.te_current_tactics.setReadOnly(True) 253 | self.te_current_tactics.setObjectName("te_current_tactics") 254 | self.pb_import_tactics = QtWidgets.QPushButton(self.groupBox_2) 255 | self.pb_import_tactics.setGeometry(QtCore.QRect(264, 11, 40, 23)) 256 | self.pb_import_tactics.setObjectName("pb_import_tactics") 257 | self.pb_export_tactics = QtWidgets.QPushButton(self.groupBox_2) 258 | self.pb_export_tactics.setGeometry(QtCore.QRect(307, 11, 40, 23)) 259 | self.pb_export_tactics.setObjectName("pb_export_tactics") 260 | self.pb_clear_tactics = QtWidgets.QPushButton(self.groupBox_2) 261 | self.pb_clear_tactics.setGeometry(QtCore.QRect(349, 11, 40, 23)) 262 | self.pb_clear_tactics.setObjectName("pb_clear_tactics") 263 | self.pb_start = QtWidgets.QPushButton(self.groupBox_2) 264 | self.pb_start.setGeometry(QtCore.QRect(349, 370, 41, 23)) 265 | self.pb_start.setObjectName("pb_start") 266 | self.cb_screen_off = QtWidgets.QCheckBox(self.groupBox_2) 267 | self.cb_screen_off.setGeometry(QtCore.QRect(220, 376, 71, 16)) 268 | self.cb_screen_off.setObjectName("cb_screen_off") 269 | self.cb_switch_accounts = QtWidgets.QCheckBox(self.groupBox_2) 270 | self.cb_switch_accounts.setGeometry(QtCore.QRect(270, 376, 71, 16)) 271 | self.cb_switch_accounts.setObjectName("cb_switch_accounts") 272 | self.groupBox_3 = QtWidgets.QGroupBox(self.centralwidget) 273 | self.groupBox_3.setGeometry(QtCore.QRect(20, 434, 870, 361)) 274 | self.groupBox_3.setStyleSheet("#groupBox_3{border:1px solid}") 275 | self.groupBox_3.setObjectName("groupBox_3") 276 | self.tableView = QtWidgets.QTableView(self.groupBox_3) 277 | self.tableView.setGeometry(QtCore.QRect(10, 20, 850, 327)) 278 | self.tableView.setStyleSheet("Widget{text-align:center}") 279 | self.tableView.setObjectName("tableView") 280 | self.line_2 = QtWidgets.QFrame(self.centralwidget) 281 | self.line_2.setGeometry(QtCore.QRect(470, 24, 20, 390)) 282 | self.line_2.setFrameShape(QtWidgets.QFrame.VLine) 283 | self.line_2.setFrameShadow(QtWidgets.QFrame.Sunken) 284 | self.line_2.setObjectName("line_2") 285 | MainWindow.setCentralWidget(self.centralwidget) 286 | self.statusbar = QtWidgets.QStatusBar(MainWindow) 287 | self.statusbar.setObjectName("statusbar") 288 | MainWindow.setStatusBar(self.statusbar) 289 | 290 | self.retranslateUi(MainWindow) 291 | self.tabWidget.setCurrentIndex(3) 292 | QtCore.QMetaObject.connectSlotsByName(MainWindow) 293 | 294 | def retranslateUi(self, MainWindow): 295 | _translate = QtCore.QCoreApplication.translate 296 | MainWindow.setWindowTitle(_translate("MainWindow", "微信养号")) 297 | self.groupBox.setTitle(_translate("MainWindow", "策略维护")) 298 | self.pb_add_official_accounts.setText(_translate("MainWindow", "添加")) 299 | self.pb_add_tactic0.setText(_translate("MainWindow", "添加策略")) 300 | self.pb_clear_official_accounts.setText(_translate("MainWindow", "清空")) 301 | self.label_2.setText(_translate("MainWindow", "间隔(秒)")) 302 | self.label.setText(_translate("MainWindow", "公众号")) 303 | self.label_10.setText(_translate("MainWindow", "-")) 304 | self.label_14.setText(_translate("MainWindow", "策略间隔")) 305 | self.label_15.setText(_translate("MainWindow", "*单位(分钟),该策略执行完成后的等待时间")) 306 | self.label_37.setText(_translate("MainWindow", "关注数")) 307 | self.label_38.setText(_translate("MainWindow", "*从维护的公众号列表中随机取")) 308 | self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_0), _translate("MainWindow", "关注公众号")) 309 | self.label_3.setText(_translate("MainWindow", "阅读文章数")) 310 | self.cb_if_share.setText(_translate("MainWindow", "转发")) 311 | self.label_5.setText(_translate("MainWindow", "间隔(秒)")) 312 | self.pb_add_tactic1.setText(_translate("MainWindow", "添加策略")) 313 | self.label_11.setText(_translate("MainWindow", "-")) 314 | self.label_16.setText(_translate("MainWindow", "策略间隔")) 315 | self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_1), _translate("MainWindow", "文章阅读转发")) 316 | self.label_6.setText(_translate("MainWindow", "间隔(秒)")) 317 | self.pb_add_tactic2.setText(_translate("MainWindow", "添加策略")) 318 | self.label_4.setText(_translate("MainWindow", "滑动次数")) 319 | self.label_12.setText(_translate("MainWindow", "-")) 320 | self.label_17.setText(_translate("MainWindow", "策略间隔")) 321 | self.label_39.setText(_translate("MainWindow", "点赞概率(%)")) 322 | self.label_40.setText(_translate("MainWindow", "*浏览朋友圈时,向下滑动的次数")) 323 | self.label_41.setText(_translate("MainWindow", "*浏览每条朋友圈动态时,根据概率决定是否点赞")) 324 | self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_2), _translate("MainWindow", "朋友圈点赞")) 325 | self.label_7.setText(_translate("MainWindow", "聊天对象")) 326 | self.pb_add_chat_objects.setText(_translate("MainWindow", "添加")) 327 | self.pb_add_tactic3.setText(_translate("MainWindow", "添加策略")) 328 | self.pb_add_msg_contents.setText(_translate("MainWindow", "添加")) 329 | self.label_8.setText(_translate("MainWindow", "消息内容")) 330 | self.label_9.setText(_translate("MainWindow", "间隔(秒)")) 331 | self.pb_clear_chat_objects.setText(_translate("MainWindow", "清空")) 332 | self.pb_clear_msg_contents.setText(_translate("MainWindow", "清空")) 333 | self.label_13.setText(_translate("MainWindow", "-")) 334 | self.label_18.setText(_translate("MainWindow", "策略间隔")) 335 | self.cb_chat_object_type.setItemText(0, _translate("MainWindow", "类型")) 336 | self.cb_chat_object_type.setItemText(1, _translate("MainWindow", "好友")) 337 | self.cb_chat_object_type.setItemText(2, _translate("MainWindow", "群聊")) 338 | self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_3), _translate("MainWindow", "聊天消息")) 339 | self.groupBox_2.setTitle(_translate("MainWindow", "当前策略")) 340 | self.pb_import_tactics.setText(_translate("MainWindow", "导入")) 341 | self.pb_export_tactics.setText(_translate("MainWindow", "导出")) 342 | self.pb_clear_tactics.setText(_translate("MainWindow", "清空")) 343 | self.pb_start.setText(_translate("MainWindow", "开始")) 344 | self.cb_screen_off.setText(_translate("MainWindow", "熄屏")) 345 | self.cb_switch_accounts.setText(_translate("MainWindow", "切换账号")) 346 | self.groupBox_3.setTitle(_translate("MainWindow", "设备信息")) 347 | 348 | -------------------------------------------------------------------------------- /weixin_raise_accounts/wra_auto_tool_ui.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | MainWindow 4 | 5 | 6 | 7 | 0 8 | 0 9 | 902 10 | 823 11 | 12 | 13 | 14 | 微信养号 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 20 24 | 20 25 | 451 26 | 400 27 | 28 | 29 | 30 | #groupBox{border:1px solid} 31 | 32 | 33 | 策略维护 34 | 35 | 36 | 37 | 38 | 10 39 | 20 40 | 431 41 | 371 42 | 43 | 44 | 45 | 46 | 47 | 48 | 3 49 | 50 | 51 | 52 | 关注公众号 53 | 54 | 55 | 56 | true 57 | 58 | 59 | 60 | 88 61 | 96 62 | 320 63 | 121 64 | 65 | 66 | 67 | true 68 | 69 | 70 | 71 | 72 | 73 | 324 74 | 60 75 | 40 76 | 23 77 | 78 | 79 | 80 | 添加 81 | 82 | 83 | 84 | 85 | 86 | 337 87 | 320 88 | 75 89 | 23 90 | 91 | 92 | 93 | 添加策略 94 | 95 | 96 | 97 | 98 | 99 | 368 100 | 60 101 | 40 102 | 23 103 | 104 | 105 | 106 | 清空 107 | 108 | 109 | 110 | 111 | 112 | 87 113 | 231 114 | 61 115 | 20 116 | 117 | 118 | 119 | 120 | 121 | 122 | 88 123 | 61 124 | 230 125 | 20 126 | 127 | 128 | 129 | 130 | 131 | 132 | 23 133 | 234 134 | 48 135 | 16 136 | 137 | 138 | 139 | 间隔(秒) 140 | 141 | 142 | 143 | 144 | 145 | 23 146 | 63 147 | 36 148 | 16 149 | 150 | 151 | 152 | 公众号 153 | 154 | 155 | 156 | 157 | 158 | 150 159 | 235 160 | 21 161 | 16 162 | 163 | 164 | 165 | - 166 | 167 | 168 | 169 | 170 | 171 | 158 172 | 231 173 | 61 174 | 20 175 | 176 | 177 | 178 | 179 | 180 | 181 | 21 182 | 266 183 | 48 184 | 16 185 | 186 | 187 | 188 | 策略间隔 189 | 190 | 191 | 192 | 193 | 194 | 87 195 | 264 196 | 30 197 | 20 198 | 199 | 200 | 201 | 202 | 203 | 204 | 120 205 | 269 206 | 291 207 | 16 208 | 209 | 210 | 211 | 212 | 8 213 | 214 | 215 | 216 | #label_15{color:red} 217 | 218 | 219 | *单位(分钟),该策略执行完成后的等待时间 220 | 221 | 222 | 223 | 224 | 225 | 24 226 | 28 227 | 54 228 | 12 229 | 230 | 231 | 232 | 关注数 233 | 234 | 235 | 236 | 237 | 238 | 88 239 | 24 240 | 30 241 | 20 242 | 243 | 244 | 245 | 246 | 247 | 248 | 121 249 | 28 250 | 201 251 | 16 252 | 253 | 254 | 255 | 256 | 8 257 | 258 | 259 | 260 | Qt::LeftToRight 261 | 262 | 263 | #label_38{color:red} 264 | 265 | 266 | *从维护的公众号列表中随机取 267 | 268 | 269 | 270 | 271 | 272 | 文章阅读转发 273 | 274 | 275 | 276 | 277 | 31 278 | 41 279 | 60 280 | 16 281 | 282 | 283 | 284 | 阅读文章数 285 | 286 | 287 | 288 | 289 | 290 | 110 291 | 41 292 | 100 293 | 20 294 | 295 | 296 | 297 | 298 | 299 | 300 | 220 301 | 43 302 | 45 303 | 16 304 | 305 | 306 | 307 | 308 | 8 309 | 310 | 311 | 312 | 转发 313 | 314 | 315 | 316 | 317 | 318 | 40 319 | 98 320 | 48 321 | 16 322 | 323 | 324 | 325 | 间隔(秒) 326 | 327 | 328 | 329 | 330 | 331 | 337 332 | 320 333 | 75 334 | 23 335 | 336 | 337 | 338 | 添加策略 339 | 340 | 341 | 342 | 343 | 344 | 109 345 | 94 346 | 61 347 | 20 348 | 349 | 350 | 351 | 352 | 353 | 354 | 172 355 | 97 356 | 21 357 | 16 358 | 359 | 360 | 361 | - 362 | 363 | 364 | 365 | 366 | 367 | 180 368 | 94 369 | 61 370 | 20 371 | 372 | 373 | 374 | 375 | 376 | 377 | 39 378 | 147 379 | 48 380 | 16 381 | 382 | 383 | 384 | 策略间隔 385 | 386 | 387 | 388 | 389 | 390 | 109 391 | 145 392 | 30 393 | 20 394 | 395 | 396 | 397 | 398 | 399 | 400 | 朋友圈点赞 401 | 402 | 403 | 404 | 405 | 55 406 | 113 407 | 48 408 | 16 409 | 410 | 411 | 412 | 间隔(秒) 413 | 414 | 415 | 416 | 417 | 418 | 337 419 | 320 420 | 75 421 | 23 422 | 423 | 424 | 425 | 添加策略 426 | 427 | 428 | 429 | 430 | 431 | 55 432 | 42 433 | 81 434 | 16 435 | 436 | 437 | 438 | 滑动次数 439 | 440 | 441 | 442 | 443 | 444 | 130 445 | 110 446 | 61 447 | 20 448 | 449 | 450 | 451 | 452 | 453 | 454 | 131 455 | 40 456 | 30 457 | 20 458 | 459 | 460 | 461 | 462 | 463 | 464 | 192 465 | 112 466 | 21 467 | 16 468 | 469 | 470 | 471 | - 472 | 473 | 474 | 475 | 476 | 477 | 200 478 | 110 479 | 61 480 | 20 481 | 482 | 483 | 484 | 485 | 486 | 487 | 51 488 | 152 489 | 48 490 | 16 491 | 492 | 493 | 494 | 策略间隔 495 | 496 | 497 | 498 | 499 | 500 | 130 501 | 150 502 | 30 503 | 20 504 | 505 | 506 | 507 | 508 | 509 | 510 | 44 511 | 76 512 | 71 513 | 16 514 | 515 | 516 | 517 | 点赞概率(%) 518 | 519 | 520 | 521 | 522 | 523 | 131 524 | 73 525 | 30 526 | 20 527 | 528 | 529 | 530 | 531 | 532 | 533 | 168 534 | 43 535 | 201 536 | 16 537 | 538 | 539 | 540 | 541 | 8 542 | 543 | 544 | 545 | Qt::LeftToRight 546 | 547 | 548 | #label_40{color:red} 549 | 550 | 551 | *浏览朋友圈时,向下滑动的次数 552 | 553 | 554 | 555 | 556 | 557 | 167 558 | 77 559 | 251 560 | 16 561 | 562 | 563 | 564 | 565 | 8 566 | 567 | 568 | 569 | Qt::LeftToRight 570 | 571 | 572 | #label_41{color:red} 573 | 574 | 575 | *浏览每条朋友圈动态时,根据概率决定是否点赞 576 | 577 | 578 | 579 | 580 | 581 | 聊天消息 582 | 583 | 584 | 585 | true 586 | 587 | 588 | 589 | 80 590 | 49 591 | 331 592 | 71 593 | 594 | 595 | 596 | true 597 | 598 | 599 | 600 | 601 | 602 | 15 603 | 23 604 | 51 605 | 16 606 | 607 | 608 | 609 | 聊天对象 610 | 611 | 612 | 613 | 614 | 615 | 80 616 | 21 617 | 170 618 | 20 619 | 620 | 621 | 622 | 623 | 624 | 625 | 329 626 | 20 627 | 40 628 | 23 629 | 630 | 631 | 632 | 添加 633 | 634 | 635 | 636 | 637 | 638 | 337 639 | 320 640 | 75 641 | 23 642 | 643 | 644 | 645 | 添加策略 646 | 647 | 648 | 649 | 650 | 651 | 328 652 | 130 653 | 40 654 | 23 655 | 656 | 657 | 658 | 添加 659 | 660 | 661 | 662 | 663 | 664 | 80 665 | 131 666 | 241 667 | 20 668 | 669 | 670 | 671 | 672 | 673 | 674 | 15 675 | 133 676 | 51 677 | 16 678 | 679 | 680 | 681 | 消息内容 682 | 683 | 684 | 685 | 686 | true 687 | 688 | 689 | 690 | 80 691 | 159 692 | 331 693 | 91 694 | 695 | 696 | 697 | true 698 | 699 | 700 | 701 | 702 | 703 | 15 704 | 265 705 | 48 706 | 16 707 | 708 | 709 | 710 | 间隔(秒) 711 | 712 | 713 | 714 | 715 | 716 | 80 717 | 261 718 | 61 719 | 20 720 | 721 | 722 | 723 | 724 | 725 | 726 | 370 727 | 20 728 | 40 729 | 23 730 | 731 | 732 | 733 | 清空 734 | 735 | 736 | 737 | 738 | 739 | 370 740 | 130 741 | 40 742 | 23 743 | 744 | 745 | 746 | 清空 747 | 748 | 749 | 750 | 751 | 752 | 142 753 | 264 754 | 21 755 | 16 756 | 757 | 758 | 759 | - 760 | 761 | 762 | 763 | 764 | 765 | 150 766 | 261 767 | 61 768 | 20 769 | 770 | 771 | 772 | 773 | 774 | 775 | 79 776 | 290 777 | 30 778 | 20 779 | 780 | 781 | 782 | 783 | 784 | 785 | 11 786 | 292 787 | 48 788 | 16 789 | 790 | 791 | 792 | 策略间隔 793 | 794 | 795 | 796 | 797 | 798 | 252 799 | 20 800 | 70 801 | 22 802 | 803 | 804 | 805 | 806 | 类型 807 | 808 | 809 | 810 | 811 | 好友 812 | 813 | 814 | 815 | 816 | 群聊 817 | 818 | 819 | 820 | 821 | 822 | 823 | 824 | 825 | 826 | 20 827 | 420 828 | 870 829 | 16 830 | 831 | 832 | 833 | Qt::Horizontal 834 | 835 | 836 | 837 | 838 | 839 | 490 840 | 20 841 | 401 842 | 400 843 | 844 | 845 | 846 | #groupBox_2{border:1px solid} 847 | 848 | 849 | 当前策略 850 | 851 | 852 | 853 | true 854 | 855 | 856 | 857 | 10 858 | 40 859 | 380 860 | 321 861 | 862 | 863 | 864 | true 865 | 866 | 867 | 868 | 869 | 870 | 264 871 | 11 872 | 40 873 | 23 874 | 875 | 876 | 877 | 导入 878 | 879 | 880 | 881 | 882 | 883 | 307 884 | 11 885 | 40 886 | 23 887 | 888 | 889 | 890 | 导出 891 | 892 | 893 | 894 | 895 | 896 | 349 897 | 11 898 | 40 899 | 23 900 | 901 | 902 | 903 | 清空 904 | 905 | 906 | 907 | 908 | 909 | 349 910 | 370 911 | 41 912 | 23 913 | 914 | 915 | 916 | 开始 917 | 918 | 919 | 920 | 921 | 922 | 220 923 | 376 924 | 71 925 | 16 926 | 927 | 928 | 929 | 熄屏 930 | 931 | 932 | 933 | 934 | 935 | 270 936 | 376 937 | 71 938 | 16 939 | 940 | 941 | 942 | 切换账号 943 | 944 | 945 | 946 | 947 | 948 | 949 | 20 950 | 434 951 | 870 952 | 361 953 | 954 | 955 | 956 | #groupBox_3{border:1px solid} 957 | 958 | 959 | 设备信息 960 | 961 | 962 | 963 | 964 | 10 965 | 20 966 | 850 967 | 327 968 | 969 | 970 | 971 | Widget{text-align:center} 972 | 973 | 974 | 975 | 976 | 977 | 978 | 470 979 | 24 980 | 20 981 | 390 982 | 983 | 984 | 985 | Qt::Vertical 986 | 987 | 988 | 989 | 990 | 991 | 992 | 993 | 994 | --------------------------------------------------------------------------------