├── 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 |
--------------------------------------------------------------------------------