class CCB: package = 'com.chinamworld.main' main_activity = 'com.ccb.start.MainActivity' apk_version = '4.1.5' login_key_board = { '1': (0.061, 0.718), '2': (0.155, 0.718), '3': (0.26, 0.718), '4': (0.355, 0.718), '5': (0.455, 0.718), '6': (0.555, 0.718), '7': (0.655, 0.718), '8': (0.755, 0.718), '9': (0.855, 0.718), '0': (0.944, 0.718), 'q': (0.061, 0.7984), 'w': (0.155, 0.7984), 'e': (0.26, 0.7984), 'r': (0.355, 0.7984), 't': (0.455, 0.7984), 'y': (0.555, 0.7984), 'u': (0.655, 0.7984), 'i': (0.755, 0.7984), 'o': (0.855, 0.7984), 'p': (0.944, 0.7984), 'a': (0.155, 0.90625), 's': (0.26, 0.90625), 'd': (0.355, 0.90625), 'f': (0.455, 0.90625), 'g': (0.555, 0.90625), 'h': (0.655, 0.90625), 'j': (0.755, 0.90625), 'k': (0.855, 0.90625), 'l': (0.944, 0.90625), 'z': (0.213, 0.956), 'x': (0.314, 0.956), 'c': (0.407, 0.956), 'v': (0.489, 0.956), 'b': (0.6, 0.956), 'n': (0.692, 0.956), 'm': (0.801, 0.956), 'back': (0.926, 0.956) } code_key_board = { '1': (0.167, 0.725), '2': (0.5, 0.725), '3': (0.833, 0.725), '4': (0.167, 0.804), '5': (0.5, 0.804), '6': (0.833, 0.804), '7': (0.167, 0.875), '8': (0.5, 0.875), '9': (0.833, 0.875), '0': (0.5, 0.978), '.': (0.167, 0.978) } def __init__(self, user, log_passwd, pay_passwd, k_passwd=''): self.user = user self.passwd = pay_passwd self.login_passwd = log_passwd self.driver = Driver() # 更新页面——关闭更新按钮 self.__pattern_close = r'resource-id="com.chinamworld.main:id/close"\s+class="android.widget.ImageView".+?bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"' self.__pattern_udpate_text = r'更新.+?clickable="true"[\s\S]*clickable="true".+?bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"' # 首页——余额按钮 self.__pattern_main_remainds = r'text="首页"resource-id="com.chinamworld.main:id/totalMoney".+?bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"' # 首页——首页按钮 self.__pattern_main_activity_main_page_btn = r'text="首页".+?bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"' # 首页——转账按钮 self.__pattern_main_activity_transfer_btn = r'text="转账".+?bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"' # 待转账页面——转账按钮 self.__pattern_transfer_btn = r'text="转账" resource-id="com.chinamworld.main:id/tv_function".+?bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"' # 登录页面——登录按钮 self.__pattern_login = r'text="登录".+?bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"' # 登录页面——密码控件 self.__pattern_pw_edit = r'resource-id="com.chinamworld.main:id/et_password".+?bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"' # 转账页面——余额 self.__pattern_remainds = r'text="人民币\s+活期储蓄\s+(.+?)" resource-id="com.chinamworld.main:id/tv_pay_info"' # 转账页面——收款人 self.__pattern_reciver = r'text="请输入收款户名" resource-id="com.chinamworld.main:id/et_cash_name".+?bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"' # 转账页面——收款账号 self.__pattern_card = r'text="请输入收款账号或手机号" resource-id="com.chinamworld.main:id/et_collection_account".+?bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"' # 转账页面——金额 self.__pattern_money = r'text="请输入转账金额" resource-id="com.chinamworld.main:id/et_tran_amount".+?bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"' # 收款银行 self.__pattern_bank_name = r'text="请选择收款银行" resource-id="com.chinamworld.main:id/tv_bank" class="android.widget.TextView".+?bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"' # 搜索 self.__pattern_search = r'index="0" text="" resource-id="com.chinamworld.main:id/search_mid_layout" class="android.widget.RelativeLayout".+?bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"' # 转账页面——下一步 self.__pattern_next = r'text="下一步" resource-id="com.chinamworld.main:id/btn_right1".+?bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"' # 转账页面警告框——继续 self.__pattern_continue = r'text="继续" resource-id="com.chinamworld.main:id/dlg_right_tv".+?bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"' # 支付页面——验证码控件 self.__pattern_verify_code = r'resource-id="com.chinamworld.main:id/et_code".+?bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"' # 支付页面——确认按钮 self.__pattern_ok = r'text="确定" resource-id="com.chinamworld.main:id/btn_confirm".+?bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"' # 支付页面——验证码序号 self.__pattern_verify_seq = r'已向您手机号.+?发送序号为(\d+)的验证码' # 密码页面——密码控件 self.__pattern_code = r'resource-id="com.chinamworld.main:id/et_code".+?bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"' # 密码页面——图形验证码控件 self.__pattern_pic_code = r'text="请输入右侧图片的字符" resource-id="com.chinamworld.main:id/native_graph_et".+?bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"' # 密码页面——确认按钮 self.__pattern_sure = r'text="确定" resource-id="com.chinamworld.main:id/btn_confirm".+?enabled="true".+?bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"' # 密码页面——取消按钮 self.__pattern_cancle = r'text="取消" resource-id="com.chinamworld.main:id/btn_cancel".+?bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"' # 密码页面——图片区域 self.__pattern_pic_rect = r'resource-id="com.chinamworld.main:id/native_graph_iv".+?bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"' # 查询结果页面——查询按钮 self.__pattern_check_result_btn = r'text="查询转账结果" resource-id="com.chinamworld.main:id/btn_right3".+?bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"' # 查询结果页面——取消按钮 self.__pattern_check_result_cancle_btn = r'text="取消" resource-id="com.chinamworld.main:id/dlg_left_tv".+?bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"' # 查询结果页面——确认按钮 self.__pattern_check_result_sure_btn = r'text="确定" resource-id="com.chinamworld.main:id/dlg_right_tv".+?bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"' # 异常界面 self.__pattern_exception = r'text="关闭"\s+resource-id="com.chinamworld.main:id/dlg_right_tv".+?bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"' # 支付页面——关闭框框 self.__pattern_close_verify_page = r'resource-id="com.chinamworld.main:id/iv_close".+?bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"' ''' try: self.driver.power_on_screen() self.driver.unlock() self._enter_main_page() except BaseException as e: logger.error(f'{e}') ''' def check_surplus(self): self.driver.power_on_screen() self.driver.unlock() logger.info('开始通过建设银行查询余额') # 回到首页 try: xml = self._enter_main_page() # 从首页进入待转账页面 xml = self.__enter_prepare_transfer(xml) # 从待转账页面进入转账页面 xml = self.__enter_transfer(xml) # 转账页面 return float(self.__transfer(xml, '', '', '', '', True)[0]), '' except BaseException as e: logger.error(f'查询余额失败,{e}') return '', str(e) def transfer_cash(self, reciver, card, money, taskid, bank_name): self.driver.power_on_screen() self.driver.unlock() self._remainds = '' logger.info(f'开始通过建设银行给{reciver}的卡{card}转账{money}元') try: xml = self._enter_main_page() # 从首页进入待转账页面 xml = self.__enter_prepare_transfer(xml) # 从待转账页面进入转账页面 xml = self.__enter_transfer(xml) # 转账页面 self._remainds, xml = self.__transfer(xml, reciver, card, str(money), bank_name) # 支付 xml = self.__pay(self.passwd, xml) # 查询结果 status, reason = self.check_result(xml) if status is True: self._remainds = round(float(self._remainds) - float(money), 2) return self._remainds, OK, reason except MyError as e: e = f'手机原因: {e}' logger.error(e) logger.error(traceback.format_exc()) return self._remainds, PHONE_ERR, str(e) except NomoneyError as e: e = '卡余额不足' logger.error(e) logger.error(traceback.format_exc()) return self._remainds, NO_MONEY, str(e) except UserError as e: e = f'用户原因: {e}' logger.error(e) logger.error(traceback.format_exc()) return self._remainds, USER_ERR, str(e) except BaseException as e: e = f'其他原因: {e}' logger.error(e) logger.error(traceback.format_exc()) return self._remainds, PHONE_ERR, str(e) def check_result(self, xml): start_time = time.time() while time.time() - start_time < 60: cur_activity = self.driver.get_cur_activity() xml = self.driver.get_xml() if self.__handle_random_page(cur_activity, xml): start_time = time.time() continue elif cur_activity == 'com.ccb.framework.security.base.successpage.CcbSuccessPageAct': check_result_btn = re.findall(self.__pattern_check_result_btn, xml) if len(check_result_btn) == 1: self.__click(check_result_btn[0]) elif '转账提交成功' in xml or re.search( r'转账成功(.+?)" resource-id="com.chinamworld.main:id/tv_dlg_content"', xml): ret = '转账成功' logger.info(ret) return True, ret elif re.search( r'具体原因:(.+?)" resource-id="com.chinamworld.main:id/tv_dlg_content"', xml): ret = re.findall( r'具体原因:(.+?)" resource-id="com.chinamworld.main:id/tv_dlg_content"', xml)[0] logger.info(ret) raise UserError(ret) else: logger.warning('未知 activity %s' % cur_activity) raise MyError('查询转账结果超时') # 支付页面 def __pay(self, code, xml): # 密码输入 def __input_code(xml, code): code_controler = re.findall(self.__pattern_code, xml) if len(code_controler) != 1: logger.error('未找到密码控件') return False # 输入密码 self.__click(code_controler[0]) time.sleep(0.5) for c in code: x = int(self.code_key_board[c][0] * self.driver.width()) y = int(self.code_key_board[c][1] * self.driver.height()) self.driver.click(x, y) # 验证码图片 xml = self.driver.get_xml() pic_code = re.findall(self.__pattern_pic_code, xml) if len(pic_code) != 1: logger.error('未找到图形验证码控件') return False self.__click(pic_code[0]) start_time = time.time() for i in range(10): pic_rect = re.findall(self.__pattern_pic_rect, xml) if len(pic_rect) != 1: logger.error('未找到图片区域') time.sleep(0.5) continue pic_name = self.driver.screencap() ocr = OCR(pic_name) pic_rect = pic_rect[0] ocr.crop(int(pic_rect[0]), int(pic_rect[1]), int(pic_rect[2]), int(pic_rect[3])) ocr.binaryzation(113) ocr.save(pic_name + '.verify.png') verify_code = ocr.get_text(pic_name + '.verify.png').lower() logger.info(f'第{i+1}次输入图形验证码: {verify_code}') for c in verify_code: if c == ' ': continue x = int(self.login_key_board[c][0] * self.driver.width()) y = int(self.login_key_board[c][1] * self.driver.height()) self.driver.click(x, y) xml = self.driver.get_xml() sure = re.findall(self.__pattern_sure, xml) if len(sure) != 1: logger.error('验证不正确') for i in range(len(verify_code)): x = int(self.login_key_board['back'][0] * self.driver.width()) y = int(self.login_key_board['back'][1] * self.driver.height()) self.driver.click(x, y) self.__click(pic_rect) time.sleep(0.5) else: logger.info('验证码正确,点击确定') self.__click(sure[0]) break return True start_time = time.time() while time.time() - start_time < 60: # 更新页面 cur_activity = self.driver.get_cur_activity() xml = self.driver.get_xml() if self.__handle_random_page(cur_activity, xml): start_time = time.time() continue elif cur_activity == 'com.ccb.framework.security.transecurityverify.TransSecurityVerifyDialogAct': # 校验界面 if len(re.findall(self.__pattern_pic_code, xml)) == 1: logger.info('已进入密码输入页面') if __input_code(xml, code): start_time = time.time() continue verify_code = re.findall(self.__pattern_verify_code, xml) if len(verify_code) != 1: logger.error('未找到验证码控件') continue verify_seq = re.findall(self.__pattern_verify_seq, xml) if len(verify_seq) != 1: logger.error('未找到验证码序列号') continue ok = re.findall(self.__pattern_ok, xml) if len(ok) != 1: logger.error('未找到确认支付按钮') continue verify_seq = verify_seq[0] logger.info('验证码序列号为%s' % verify_seq) #self.__click(verify_code[0], True) # 输入短信验证码 temp_start = time.time() while time.time() - temp_start < 120: msg_list = MsgManger.getMsg('95533') if len(msg_list) == 0: time.sleep(0.5) continue msg = msg_list[0] pattern = f'序号{verify_seq}的验证码(\d+),您向' ret = re.findall(pattern, msg) if len(ret) != 1: logger.error(f'未能在短信 "{msg}"中匹配到 {pattern}') time.sleep(1.5) continue if re.search(self.__pattern_verify_code, xml): self.__click( re.findall(self.__pattern_verify_code, xml)[0], True) logger.info(f'输入验证码{ret[0]}') for c in ret[0]: x = int(self.code_key_board[c][0] * self.driver.width()) y = int(self.code_key_board[c][1] * self.driver.height()) self.driver.click(x, y) self.driver.back() start_time = time.time() break else: raise MyError(f'未能收到序号为{verify_seq}的验证码') #time.sleep(30) logger.info('点击确认支付') self.__click(ok[0]) elif cur_activity == 'com.ccb.framework.security.base.successpage.CcbSuccessPageAct': logger.info('转账已受理') return xml else: logger.warning('未知 activity %s' % cur_activity) raise MyError('支付超时') # 处理随机弹出页面:如更新,登录 def __handle_random_page(self, activity, xml='', back=False): xml = self.driver.get_xml() if xml == '' else xml if activity == '': if len(re.findall(self.__pattern_udpate_text, xml)) == 1: ret = re.findall(self.__pattern_udpate_text, xml) logger.info('点击取消更新按钮') self.__click(ret[0]) return True elif not back and len(re.findall(self.__pattern_continue, xml)) == 1: ret = re.findall(self.__pattern_continue, xml) logger.info('点击继续按钮') self.__click(ret[0]) return True elif activity == 'com.ccb.start.view.startdialog.StartDialogActivity' or activity == 'com.ccb.transfer.transfer_home.view.TransferHomeAct': if re.search(self.__pattern_close, xml): logger.info('点击叉叉') self.__click(re.findall(self.__pattern_close, xml)[0]) return True elif re.search(self.__pattern_udpate_text, xml): logger.info('取消更新按钮') self.__click(re.findall(self.__pattern_udpate_text, xml)[0]) return True elif back is True and activity == 'com.ccb.transfer.smarttransfer.view.SmartTransferMainAct': if len(re.findall(self.__pattern_check_result_cancle_btn, xml)) == 1: ret = re.findall(self.__pattern_check_result_cancle_btn, xml) logger.info(f'点击转账页面的取消按钮') self.__click(ret[0]) return True if len(re.findall(self.__pattern_check_result_sure_btn, xml)) == 1: ret = re.findall(self.__pattern_check_result_sure_btn, xml) logger.info('点击转账页面的确定按钮') self.__click(ret[0]) return True elif back and activity == 'com.ccb.framework.security.base.successpage.CcbSuccessPageAct': if len(re.findall(self.__pattern_check_result_sure_btn, xml)) == 1: ret = re.findall(self.__pattern_check_result_sure_btn, xml) logger.info('点击转账结果页面的确定按钮') self.__click(ret[0]) return True elif len(re.findall(self.__pattern_check_result_cancle_btn, xml)) == 1: ret = re.findall(self.__pattern_check_result_cancle_btn, xml) logger.info('点击转账结果页面的取消按钮') self.__click(ret[0]) return True elif activity == 'com.ccb.start.MainActivity': if len(re.findall(self.__pattern_udpate_text, xml)) == 1: ret = re.findall(self.__pattern_udpate_text, xml) logger.info('点击取消更新按钮') self.__click(ret[0]) return True elif activity == 'com.ccb.framework.security.login.internal.view.LoginActivity': if re.search(self.__pattern_close, xml): logger.info('点击叉叉') self.__click(re.findall(self.__pattern_close, xml)[0]) return True elif re.search(self.__pattern_udpate_text, xml): logger.info('取消更新按钮') self.__click(re.findall(self.__pattern_udpate_text, xml)[0]) return True logger.info('进入登录页面') ret = re.findall(self.__pattern_pw_edit, xml) if len(ret) != 1: logger.warning('未找到密码编辑框') return False logger.info('点击密码编辑框') self.__click(ret[0]) time.sleep(0.5) # 等待键盘弹出来 # 匹配登录按钮 ret = re.findall(self.__pattern_login, xml) if len(ret) != 1: logger.warning('未找到登录按钮') return False logger.info('输入密码中') for c in self.login_passwd: x = int(self.login_key_board[c][0] * self.driver.width()) y = int(self.login_key_board[c][1] * self.driver.height()) logger.info(f'{c}, {x}, {y}') self.driver.click(x, y) logger.info('点击登录按钮') self.__click(ret[0]) time.sleep(2.5) return True elif re.search(self.__pattern_exception, xml): ret = re.findall(self.__pattern_exception, xml)[0] self.__click(ret) logger.info('点击关闭按钮') return True else: for error_text in ['收款方姓名与账户户名不一致', '交易金额低于总行允许的最低单笔限额']: if error_text in xml: if back: self.__click( re.findall(self.__pattern_check_result_sure_btn, xml)[0]) else: raise UserError(error_text) return True else: logger.info(f'未知,activity: {activity}') return False # 首页进入待转账页面 def __enter_prepare_transfer(self, xml): start_time = time.time() main_page_clicked = False while time.time() - start_time < 50: # 更新页面 cur_activity = self.driver.get_cur_activity() xml = self.driver.get_xml() if self.__handle_random_page(cur_activity, xml): start_time = time.time() continue elif cur_activity == 'com.ccb.start.MainActivity': ''' # 匹配首页的首页按钮 if not main_page_clicked: ret = re.findall(self.__pattern_main_activity_main_page_btn, xml) if len(ret) == 1: logger.info('点击首页的首页按钮') self.__click(ret[0]) main_page_clicked = True ''' # 匹配的首页的转账按钮 ret = re.findall(self.__pattern_main_activity_transfer_btn, xml) if len(ret) == 1: logger.info('点击转账按钮') self.__click(ret[0]) elif cur_activity == 'com.ccb.transfer.transfer_home.view.TransferHomeAct': logger.info('已进入待转账页面') return xml else: logger.warning('未知 activity %s' % cur_activity) raise MyError('从首页进入转账页面超时') # 待转账页面进入正式转账页面 def __enter_transfer(self, xml): start_time = time.time() while time.time() - start_time < 50: cur_activity = self.driver.get_cur_activity() xml = self.driver.get_xml() if self.__handle_random_page(cur_activity, xml): start_time = time.time() continue elif cur_activity == 'com.ccb.transfer.transfer_home.view.TransferHomeAct': ret = re.findall(self.__pattern_transfer_btn, xml) if len(ret) == 1: logger.info('点击待转账页面的转账按钮') self.__click(ret[0]) elif cur_activity == 'com.ccb.transfer.smarttransfer.view.SmartTransferMainAct': logger.info('进入正式转账页面') return xml else: logger.warning('未知 activity %s' % cur_activity) raise MyError('从待转账页面进入转账页面超时') # 转账页面 def __transfer(self, xml, name, card, money, bank_name, only4remainds=False): start_time = time.time() self._remainds = '' while time.time() - start_time < 80: cur_activity = self.driver.get_cur_activity() xml = self.driver.get_xml() if self.__handle_random_page(cur_activity, xml): start_time = time.time() continue elif cur_activity == 'com.ccb.transfer.smarttransfer.view.SmartTransferMainAct': if re.search(self.__pattern_continue, xml): ret = re.findall(self.__pattern_continue, xml)[0] logger.info('点击继续') self.__click(ret) continue if re.search(self.__pattern_check_result_sure_btn, xml): ret = re.findall(self.__pattern_continue, xml)[0] logger.info('点击确定') self.__click(ret) continue ''' ret_remainds = xml.find('活期储蓄') if ret_remainds == -1: xml = self.driver.get_xml() ret_remainds = xml.find('活期储蓄') if ret_remainds == -1: logger.error(f'未找到余额, {cur_activity}, {xml}') continue ret_remainds = xml[ret_remainds + len('活期储蓄') : ] ret_remainds = [float(ret_remainds[: ret_remainds.find('"')])] ''' # 正则表达式不稳定 ret_remainds = re.findall(self.__pattern_remainds, xml) if len(ret_remainds) != 1: logger.error(f'未找到余额, {ret_remainds}') continue if only4remainds: logger.info(f'余额:{ret_remainds[0]}') return re.sub(r',', '', ret_remainds[0]), xml ret_reciver = re.findall(self.__pattern_reciver, xml) if len(ret_reciver) != 1: logger.error('未找到收款人控件') continue ret_card = re.findall(self.__pattern_card, xml) if len(ret_card) != 1: logger.error('未找到卡号控件') continue ret_money = re.findall(self.__pattern_money, xml) if len(ret_money) != 1: logger.error('未找到转账金额控件') continue ret_next = re.findall(self.__pattern_next, xml) if len(ret_next) != 1: logger.error('未找到下一步控件') continue self._remainds = re.sub(r',', '', ret_remainds[0]) logger.info(f'余额:{self._remainds}') if float(self._remainds) < float(money): raise NomoneyError(f'余额不足{money}元,当前余额{self._remainds}元') logger.info('输入收款人 %s' % name) self.__click(ret_reciver[0]) self.driver.input_text(name) logger.info('输入卡号 %s' % card) self.__click(ret_card[0]) for c in card: x = int(self.code_key_board[c][0] * self.driver.width()) y = int(self.code_key_board[c][1] * self.driver.height()) self.driver.click(x, y) self.driver.back() time.sleep(2) temp_start = time.time() while time.time() - temp_start < 30: ret_money = re.findall(self.__pattern_money, self.driver.get_xml()) if len(ret_money) != 1: time.sleep(2) continue logger.info('输入金额 %s' % money) self.__click(ret_money[0]) for c in money: x = int(self.code_key_board[c][0] * self.driver.width()) y = int(self.code_key_board[c][1] * self.driver.height()) self.driver.click(x, y) self.driver.back() time.sleep(0.5) xml = self.driver.get_xml() if re.search(self.__pattern_bank_name, xml): raise UserError(f'未选择银行') self.driver.swip(0.5, 0.7, 0.5, 0.3) logger.info('点击下一步') self.__click(ret_next[0]) start_time = time.time() break else: raise MyError(f'可能网络状况不佳,导致一直找不到输入金额的控件') elif cur_activity == 'com.ccb.framework.security.transecurityverify.TransSecurityVerifyDialogAct': logger.info('已进入付款页面') return self._remainds, xml else: logger.warning('未知 activity %s' % cur_activity) raise MyError('转账页面操作超时') # 进入首页 def _enter_main_page(self): #self.driver.stop_app(CCB.package) package = self.driver.get_cur_packge() # 启动app if package != CCB.package: self.driver.start_app(CCB.package, CCB.main_activity) cur_time = time.time() while time.time() - cur_time <= 30: if self.driver.is_app_started(CCB.package): logger.info('启动app成功') break time.sleep(1) for i in range(3): if self.driver.is_app_started(CCB.package): break time.sleep(0.5) else: raise MyError('启动APP失败') # 进入首页 start_time = time.time() while time.time() - start_time < 30: cur_activity = self.driver.get_cur_activity() xml = self.driver.get_xml() if self.__handle_random_page(cur_activity, xml, True): # 更新页面 start_time = time.time() continue elif cur_activity != 'com.ccb.start.MainActivity': if cur_activity == 'com.ccb.framework.security.transecurityverify.TransSecurityVerifyDialogAct': ret = re.findall(self.__pattern_cancle, xml) if len(ret) == 1: self.__click(ret[0]) ret = re.findall(self.__pattern_close_verify_page, xml) logger.info(f'{ret}') if len(ret) == 1: self.__click(ret[0]) elif cur_activity == 'com.ccb.framework.security.base.successpage.CcbSuccessPageAct': ret = re.findall(self.__pattern_cancle, xml) if len(ret) == 1: self.__click(ret[0]) logger.info('点击返回键') self.driver.back() else: logger.info('已进入首页') return xml time.sleep(0.5) raise MyError('回到APP首页超时失败') def __click(self, pos, double=False): click_x = (int(pos[0]) + int(pos[2])) // 2 click_y = (int(pos[1]) + int(pos[3])) // 2 self.driver.click(click_x, click_y, double)
class ABC: package = 'com.android.bankabc' main_activity = 'com.android.bankabc.homepage.HomeActivity' # 'com.android.bankabc.SplashActivity' apk_version = '4.1.1' login_digit_keyboard_templateSign = '78ca7d7b1c20f85bedacf65f61ddbc99' # 登录键盘数字键盘的模板码 login_char_keyboard_templateSign = '703be84400e3c37ae2533a9192c66e74' # 登录键盘字符键盘的模板码 login_digit2char = (0.6111, 0.942) login_char2digit = (0.1296, 0.942) login_btn = (0.8889, 0.942) login_digit_keyboard = { 'key1': (0.1297, 0.6577), 'key2': (0.3722, 0.6577), 'key3': (0.6074, 0.6577), 'key4': (0.1297, 0.7609), 'key5': (0.3722, 0.7609), 'key6': (0.6074, 0.7609), 'key7': (0.1297, 0.8528), 'key8': (0.3722, 0.8528), 'key9': (0.6074, 0.8528), 'key10': (0.3722, 0.9651) } login_char_keyboard = { 'key01': (0.0463, 0.6689), 'key02': (0.1509, 0.6689), 'key03': (0.25, 0.6689), 'key04': (0.3537, 0.6689), 'key05': (0.4444, 0.6689), 'key06': (0.5481, 0.6689), 'key07': (0.6481, 0.6689), 'key08': (0.7509, 0.6689), 'key09': (0.8528, 0.6689), 'key10': (0.9453, 0.6689), 'key11': (0.1019, 0.7816), 'key12': (0.2009, 0.7816), 'key13': (0.2991, 0.7816), 'key14': (0.3981, 0.7816), 'key15': (0.4981, 0.7816), 'key16': (0.6065, 0.7816), 'key17': (0.6991, 0.7816), 'key18': (0.8028, 0.7816), 'key19': (0.8972, 0.7816), 'key20': (0.2009, 0.8811), 'key21': (0.2991, 0.8811), 'key22': (0.3981, 0.8811), 'key23': (0.4981, 0.8811), 'key24': (0.6065, 0.8811), 'key25': (0.6991, 0.8811), 'key26': (0.8028, 0.8811) } white_pay_digit_keyboard_templateSign = 'cb9df66d98ee5a08702866d85cf66c99' # 白色款支付键盘数字键盘的模板码 white_pay_char_keyboard_templateSign = '1a889cefba4710dc980c27e6870ad3c3' # 白色款支付键盘字符键盘的模板码 white_pay_digit2char = (0.8740, 0.98) pay_btn = (0.6731, 0.5678) white_pay_digit_keyboard = { 'key1': (0.1222, 0.8067), 'key2': (0.3722, 0.8067), 'key3': (0.6167, 0.8067), 'key4': (0.8740, 0.8067), 'key5': (0.1222, 0.8942), 'key6': (0.3722, 0.8942), 'key7': (0.6167, 0.8942), 'key8': (0.8740, 0.8528), 'key9': (0.1222, 0.98), 'key10': (0.3722, 0.98) } white_pay_char_keyboard = { 'key01': (0.0491, 0.8062), 'key02': (0.1454, 0.8062), 'key03': (0.2417, 0.8062), 'key04': (0.3463, 0.8062), 'key05': (0.4519, 0.8062), 'key06': (0.5417, 0.8062), 'key07': (0.64, 0.8062), 'key08': (0.7472, 0.8062), 'key09': (0.8472, 0.8062), 'key10': (0.9426, 0.8062), 'key11': (0.0491, 0.8937), 'key12': (0.1454, 0.8937), 'key13': (0.2417, 0.8937), 'key14': (0.3463, 0.8937), 'key15': (0.4519, 0.8937), 'key16': (0.5417, 0.8937), 'key17': (0.64, 0.8937), 'key18': (0.7472, 0.8937), 'key19': (0.8472, 0.8937), 'key20': (0.9426, 0.8937), 'key21': (0.1454, 0.9778), 'key22': (0.2417, 0.9778), 'key23': (0.3463, 0.9778), 'key24': (0.4519, 0.9778), 'key25': (0.5417, 0.9778), 'key26': (0.64, 0.9778) } black_pay_digit_keyboard_templateSign = '854df9b3c1c0203d954eb0470cb59f01' # 黑色款支付键盘数字键盘的模板码 black_pay_char_keyboard_templateSign = '' # 黑色款支付键盘字符键盘的模板码 black_pay_digit2char = (0.8935, 0.9532) black_pay_digit_keyboard = { 'key1': (0.1296, 0.7971), 'key2': (0.3704, 0.7971), 'key3': (0.6292, 0.7971), 'key4': (0.8704, 0.7971), 'key5': (0.1296, 0.8751), 'key6': (0.3704, 0.8751), 'key7': (0.6292, 0.8751), 'key8': (0.8704, 0.8751), 'key9': (0.1296, 0.9532), 'key10': (0.3704, 0.9532) } black_pay_char_keyboard = {} url = 'http://atf.wuwotech.com/api/equipment/kcard' def __init__(self, user, log_passwd, pay_passwd, k_passwd, k_color): self.k_passwd = k_passwd self.user = user self.passwd = pay_passwd self.login_passwd = log_passwd self.kcolor = k_color self.driver = Driver() # 更新页面——关闭更新按钮 self.__pattern_close = r'resource-id="com.chinamworld.main:id/close"\s+class="android.widget.ImageView".+?bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"' self.__pattern_udpate_text = r'更新.+?clickable="true"[\s\S]*clickable="true".+?bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"' # 首页——余额按钮 self.__pattern_main_remainds = r'text="首页"resource-id="com.chinamworld.main:id/totalMoney".+?bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"' # 确认按钮 self.__pattern_sure = r'text="\s*确定\s*".+?bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"' self.__pattern_next_pay = r'text="\s*下一步\s*".+?bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"' # 完成按钮 self.__pattern_complish = r'text="\s*完成\s*".+?bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"' # 返回按钮 self.__pattern_back = r'text="\s*返回\s*".+?bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"' # 首页——转账按钮 self.__pattern_main_activity_transfer_btn = r'text="转账".+?bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"' # 待转账页面——转账按钮 self.__pattern_transfer_btn = r'text="转账".+?class="android.widget.TextView".+?bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"' # 登录页面——登录按钮 self.__pattern_login = r'text="登录".+?class="android.widget.Button".+?clickable="true".+?bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"' # 登录页面——密码控件 self.__pattern_pw_edit = r'text="请输入登录密码".+?class="android.widget.EditText".+?clickable="true".+?bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"' # 登录页面——返回控件 self.__pattern_back_edit = r'NAF="true".+?text="".+?class="android.widget.Button".+?clickable="true".+?bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"' # 转账页面——余额 self.__pattern_remainds = r'text="可用金额\s*(.+?)\s*元" resource-id="" class="android.widget.TextView"' # 转账页面——收款人 self.__pattern_reciver = r'text="请输入或选择收款方" resource-id="" class="android.widget.EditText".+?bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"' # 转账页面——收款账号 self.__pattern_card = r'text="请输入账号或手机号" resource-id="" class="android.widget.EditText".+?bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"' # 转账页面——金额 self.__pattern_money = r'text="请输入转账金额" resource-id="" class="android.widget.EditText".+?bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"' # 转账页面——下一步 self.__pattern_next = r'text="下一步" resource-id="" class="android.widget.Button".+?bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"' # 收款银行 self.__pattern_bank_name = r'text="请选择银行" resource-id="" class="android.widget.TextView".+?bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"' # 搜索 self.__pattern_search = r'text="搜索" resource-id="com.android.bankabc:id/edt_search" class="android.widget.EditText".+?bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"' # 转账页面警告框——继续 self.__pattern_continue = r'text="继续" resource-id="com.chinamworld.main:id/dlg_right_tv".+?bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"' # 支付页面——验证码控件 self.__pattern_verify_code = r'resource-id="com.chinamworld.main:id/et_code".+?bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"' # 支付页面——确认按钮 self.__pattern_ok = r'text="确定" resource-id="com.chinamworld.main:id/btn_confirm".+?bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"' # 支付页面——验证码序号 self.__pattern_verify_seq = r'已向您手机号.+?发送序号为(\d+)的验证码' # 密码页面——密码控件 self.__pattern_code = r'resource-id="com.chinamworld.main:id/et_code".+?bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"' # 密码页面——图形验证码控件 self.__pattern_pic_code = r'text="请输入右侧图片的字符" resource-id="com.chinamworld.main:id/native_graph_et".+?bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"' # 密码页面——取消按钮 self.__pattern_cancle = r'text="取消" resource-id="android:id/button2".+?bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"' # 密码页面——图片区域 self.__pattern_pic_rect = r'resource-id="com.chinamworld.main:id/native_graph_iv".+?bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"' # 查询结果页面——查询按钮 self.__pattern_check_result_btn = r'text="查询转账结果" resource-id="com.chinamworld.main:id/btn_right3".+?bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"' # 查询结果页面——取消按钮 self.__pattern_check_result_cancle_btn = r'text="取消" resource-id="com.chinamworld.main:id/dlg_left_tv".+?bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"' # 查询结果页面——确认按钮 self.__pattern_check_result_sure_btn = r'text="确定" resource-id="com.chinamworld.main:id/dlg_right_tv".+?bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"' # 异常界面 self.__pattern_exception = r'text="关闭"\s+resource-id="com.chinamworld.main:id/dlg_right_tv".+?bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"' # 支付页面——关闭框框 self.__pattern_close_verify_page = r'resource-id="com.chinamworld.main:id/iv_close".+?bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"' self.__keyboardocr = KeyBoardOcr() def check_surplus(self): self.driver.power_on_screen() self.driver.unlock() logger.info('开始通过农业银行查询余额') # 回到首页 try: xml = self._enter_main_page() # 从待转账页面进入转账页面 xml = self.__enter_transfer(xml) # 转账页面 return float(self.__transfer(xml, '', '', '', '', True)[0]), '' except BaseException as e: logger.error(f'查询余额失败,{e}') return '', str(e) def transfer_cash(self, reciver, card, money, taskid, bank_name): self.taskid = taskid self.driver.power_on_screen() self.driver.unlock() self._remainds = '' logger.info(f'开始通过平安银行给{reciver}的{bank_name}卡{card}转账{money}元') while True: try: xml = self._enter_main_page() # 从待转账页面进入转账页面 xml = self.__enter_transfer(xml) # 转账页面 self._remainds, xml = self.__transfer(xml, reciver, card, str(money), bank_name) # 支付 xml = self.__pay(self.k_passwd, xml) # 查询结果 status, reason = self.check_result(xml) if status is True: self._remainds = round( float(self._remainds) - float(money), 2) return self._remainds, OK, reason except MyError as e: e = f'手机原因: {e}' logger.error(e) logger.error(traceback.format_exc()) return self._remainds, PHONE_ERR, str(e) except NomoneyError as e: e = '卡余额不足' logger.error(e) logger.error(traceback.format_exc()) return self._remainds, NO_MONEY, str(e) except UserError as e: e = f'用户原因: {e}' logger.error(e) logger.error(traceback.format_exc()) return self._remainds, USER_ERR, str(e) except RePlayError as e: logger.info('重新开始操作app') except BaseException as e: e = f'其他原因: {e}' logger.error(e) logger.error(traceback.format_exc()) return self._remainds, PHONE_ERR, str(e) def check_result(self, xml): start_time = time.time() while time.time() - start_time < 180: cur_activity = self.driver.get_cur_activity() xml = self.driver.get_xml() if self.__handle_random_page(cur_activity, xml): logger.info() start_time = time.time() continue elif '转账已受理' in xml or '转账成功' in xml: self.send_status(self.taskid, 0) ret = '转账成功' logger.info(ret) self.__click(re.findall(self.__pattern_complish, xml)[0]) return True, ret elif '账号户名不符' in xml: self.send_status(self.taskid, 0) ret = '账号户名不符' logger.info(ret) self.__click(re.findall(self.__pattern_back, xml)[0]) return False, ret elif '通讯中断啦' in xml: self.send_status(self.taskid, 0) raise UserError(f'用户未点击k宝的ok键') else: logger.warning('未知 activity %s' % cur_activity) raise MyError('查询转账结果超时') # 支付页面 def __pay(self, code, xml): # 密码输入 logger.info(f'输入支付密码 ') cur = DIGITAL if self.kcolor == WHITE else CHARACTER pay_digit2char = self.white_pay_digit2char if self.kcolor == WHITE else self.black_pay_digit2char # 数字和字母键盘的切换按钮 pay_digit_keyboard = self.white_pay_digit_keyboard if self.kcolor == WHITE else self.black_pay_digit_keyboard #数字键盘 pay_char_keyboard = self.white_pay_char_keyboard if self.kcolor == WHITE else self.black_pay_char_keyboard # 字母键盘 # 数字键盘识别码 pay_digit_keyboard_templateSign = self.white_pay_digit_keyboard_templateSign if self.kcolor == WHITE else self.black_pay_digit_keyboard_templateSign # 字母键盘识别码 pay_char_keyboard_templateSign = self.white_pay_char_keyboard_templateSign if self.kcolor == WHITE else self.black_pay_char_keyboard_templateSign pay_keyboard = pay_digit_keyboard if self.kcolor == WHITE else pay_char_keyboard # 当前键盘 keyboard = None for c in code: if c.isdigit() and cur == CHARACTER: # 切换数字键盘 logger.info(f'切换数字键盘 {c.isdigit()} {cur}') self.driver.click(pay_digit2char[0] * self.driver.width(), pay_digit2char[1] * self.driver.height()) keyboard = KeyBoardOcr.Ocr(self.driver.screencap(), pay_digit_keyboard_templateSign) logger.info(f'百度识别的键盘位置为:{keyboard}') pay_keyboard = pay_digit_keyboard cur = DIGITAL elif c.isalpha() and cur == DIGITAL: # 切换字母键盘 logger.info(f'切换字母键盘 {c.isalpha()} {cur}') self.driver.click(pay_digit2char[0] * self.driver.width(), pay_digit2char[1] * self.driver.height()) keyboard = KeyBoardOcr.Ocr(self.driver.screencap(), pay_char_keyboard_templateSign) logger.info(f'百度识别的键盘位置为:{keyboard}') pay_keyboard = pay_char_keyboard cur = CHARACTER if not keyboard: keyboard = KeyBoardOcr.Ocr( self.driver.screencap(), pay_digit_keyboard_templateSign if self.kcolor == WHITE else pay_char_keyboard_templateSign) logger.info(f'百度识别的键盘位置为:{keyboard}') for i in range(3): if c in keyboard: pos = pay_keyboard[keyboard[c]] pos = (int(pos[0] * self.driver.width()), int(pos[1] * self.driver.height())) self.driver.click(pos[0], pos[1]) logger.info(f'{c}{pos}') break keyboard = KeyBoardOcr.Ocr( self.driver.screencap(), pay_char_keyboard_templateSign if c.isalpha() else pay_digit_keyboard_templateSign) else: raise MyError('无法在支付%s键盘识别字母%s, %s' % ('数字' if cur == DIGITAL else '字母', c, keyboard)) self.driver.click(self.pay_btn[0] * self.driver.width(), self.pay_btn[1] * self.driver.height()) self.send_status(self.taskid, 2) # 处理随机弹出页面:如更新,登录 def __handle_random_page(self, activity, xml='', back=False): xml = self.driver.get_xml() if xml == '' else xml if re.search(self.__pattern_sure, xml) and '会话超时' in xml: logger.info(f'会话超时,点击确定按钮') self.__click(re.findall(self.__pattern_sure, xml)[0]) raise RePlayError('') elif re.search(self.__pattern_cancle, xml) and ('确定' not in xml or back): logger.info('点击取消') self.__click(re.findall(self.__pattern_cancle, xml)[0]) return True elif re.search(self.__pattern_sure, xml): if back or '确定转账' in xml: if back and '确定转账' in xml: return False if re.search(self.__pattern_sure, xml): self.__click(re.findall(self.__pattern_sure, xml)[0]) elif re.search(self.__pattern_next_pay, xml): self.__click(re.findall(self.__pattern_next_pay, xml)[0]) return True elif '请输入收款账户' in xml: raise UserError(f"用户未提供收款账户") elif '请输入收款方' in xml: raise UserError(f'用户未提供收款账户') elif '请选择或输入转账金额' in xml: raise UserError(f'用户未提供转账金额') elif '未搜索到您的k宝' in xml: raise UserError(f'未打开k宝') elif '蓝牙k宝连接顿开' in xml: raise UserError(f'蓝牙k宝连接顿开') else: return False elif back is True and '继续转账' in xml and re.search( self.__pattern_complish, xml): logger.info(f'点击完成 ') self.__click(re.findall(self.__pattern_complish, xml)[0]) return True elif (back is True or '会话超时,请重新登录' in xml) and re.search( self.__pattern_back, xml): logger.info(f'点击返回 ') self.__click(re.findall(self.__pattern_back, xml)[0]) return True elif activity == 'com.android.bankabc.MainActivity' and re.search( self.__pattern_login, xml): if back is True: # 在登录界面处于返回状态,则按返回图标, 返回键在此界面没有用 if re.search(self.__pattern_back_edit, xml): self.__click(re.findall(self.__pattern_back_edit, xml)[0]) return True else: return False else: logger.info(f'进入登录页面') self.__click(re.findall(self.__pattern_pw_edit, xml)[0]) time.sleep(1) cur = DIGITAL keyboard = KeyBoardOcr.Ocr( self.driver.screencap(), self.login_digit_keyboard_templateSign) login_keyboard = self.login_digit_keyboard for c in self.login_passwd: if c.isdigit() and cur == CHARACTER: # 切换数字键盘 logger.info(f'切换数字键盘 {c.isdigit()} {cur}') self.driver.click( self.login_char2digit[0] * self.driver.width(), self.login_char2digit[1] * self.driver.height()) keyboard = KeyBoardOcr.Ocr( self.driver.screencap(), self.login_digit_keyboard_templateSign) login_keyboard = self.login_digit_keyboard cur = DIGITAL elif c.isalpha() and cur == DIGITAL: # 切换字母键盘 logger.info(f'切换字母键盘 {c.isalpha()} {cur}') self.driver.click( self.login_digit2char[0] * self.driver.width(), self.login_digit2char[1] * self.driver.height()) keyboard = KeyBoardOcr.Ocr( self.driver.screencap(), self.login_char_keyboard_templateSign) login_keyboard = self.login_char_keyboard cur = CHARACTER for i in range(3): if c in keyboard: pos = login_keyboard[keyboard[c]] pos = (int(pos[0] * self.driver.width()), int(pos[1] * self.driver.height())) self.driver.click(pos[0], pos[1]) logger.info(f'{c}{pos}') break templateSign = self.login_char_keyboard_templateSign if c.isalpha( ) else self.login_digit_keyboard_templateSign keyboard = KeyBoardOcr.Ocr(self.driver.screencap(), templateSign) else: xml = self.driver.get_xml() if re.search(self.__pattern_login, xml): raise MyError('无法在登录%s键盘识别字母%s, %s' % ('数字' if cur == DIGITAL else '字母', c, keyboard)) elif '请输入或选择收款方' in xml: # 此时进入了转账页面 logger.info(f'走出登录页面') return True self.driver.click(self.login_btn[0] * self.driver.width(), self.login_btn[1] * self.driver.height()) time.sleep(1) return True return False # 待转账页面进入正式转账页面 def __enter_transfer(self, xml): start_time = time.time() while time.time() - start_time < 50: cur_activity = self.driver.get_cur_activity() xml = self.driver.get_xml() if self.__handle_random_page(cur_activity, xml): start_time = time.time() continue elif cur_activity == 'com.android.bankabc.MainActivity': # 待转账页面和转账页面 if re.search(self.__pattern_reciver, xml): logger.info('进入正式转账页面') return xml ret = re.findall(self.__pattern_transfer_btn, xml) if len(ret) == 2 and '他行转本行' in xml: logger.info('点击待转账页面的转账按钮') self.__click(ret[1]) else: logger.warning('未知 activity %s' % cur_activity) raise MyError('从待转账页面进入转账页面超时') # 转账页面 def __transfer(self, xml, name, card, money, bank_name, only4remainds=False): start_time = time.time() self._remainds = '' while time.time() - start_time < 100: cur_activity = self.driver.get_cur_activity() xml = self.driver.get_xml() if self.__handle_random_page(cur_activity, xml): start_time = time.time() continue elif '提交了一笔转相同' in xml and re.search(self.__pattern_sure, xml): self.__click(re.findall(self.__pattern_sure, xml)[0]) elif cur_activity == 'com.android.bankabc.MainActivity' and '请输入转账金额' in xml: # 正则表达式不稳定 ret_remainds = re.findall(self.__pattern_remainds, xml) if len(ret_remainds) != 1: logger.error(f'未找到余额, {ret_remainds}') continue if only4remainds: logger.info(f'余额:{ret_remainds[0]}') return re.sub(r',', '', ret_remainds[0]), xml ret_reciver = re.findall(self.__pattern_reciver, xml) if len(ret_reciver) != 1: logger.error('未找到收款人控件') continue ret_card = re.findall(self.__pattern_card, xml) if len(ret_card) != 1: logger.error('未找到卡号控件') continue ret_money = re.findall(self.__pattern_money, xml) if len(ret_money) != 1: logger.error('未找到转账金额控件') continue self._remainds = re.sub(r',', '', ret_remainds[0]) logger.info(f'余额:{self._remainds}') if float(self._remainds) < float(money): raise NomoneyError(f'余额不足{money}元,当前余额{self._remainds}元') logger.info('输入收款人 %s' % name) self.__click(ret_reciver[0], cnt=2) time.sleep(0.2) self.driver.input_text(name) time.sleep(0.2) logger.info('输入卡号 %s' % card) self.__click(ret_card[0], cnt=2) self.driver.input_text_by_adb(card) self.__click(ret_money[0], cnt=2) time.sleep(1) logger.info('输入金额 %s' % money) self.__click(ret_money[0], cnt=2) time.sleep(0.2) self.driver.input_text_by_adb(money) xml = self.driver.get_xml() if re.search(self.__pattern_bank_name, xml): logger.info(f'未找到银行{bank_name},搜索去找') self.__click(re.findall(self.__pattern_bank_name, xml)[0], cnt=1) search_time = time.time() while time.time() - search_time < 10: xml = self.driver.get_xml() if not re.search(self.__pattern_search, xml): continue self.__click(re.findall(self.__pattern_search, xml)[0], cnt=1) logger.info(f'输入银行{bank_name}') self.driver.input_text(bank_name) search_time = time.time() pattern = f'text="\s*{bank_name}\s*" resource-id="com.android.bankabc:id/tv_title" class="android.widget.TextView".+?bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"' logger.info(f'用正则查找银行 {pattern}') while time.time() - search_time < 10: xml = self.driver.get_xml() if not re.search(pattern, xml): continue self.__click(re.findall(pattern, xml)[0]) time.sleep(1) break else: raise UserError(f'卡号{card}对应的银行{bank_name}不存在') break else: raise MyError(f'进入银行选择界面超时') self.driver.swip(0.5, 0.7, 0.5, 0.3) xml = self.driver.get_xml() logger.info('点击下一步') ret_next = re.findall(self.__pattern_next, xml) if len(ret_next) == 0: raise MyError("未能找到'下一步'按钮") self.__click(ret_next[0]) # 发http请求通知打开k宝 self.send_status(self.taskid, 1) start_time = time.time() elif '未搜索到您的K宝' in xml: self.send_status(self.taskid, 0) raise UserError('用户未打开k宝') elif '请输入K宝密码' in xml: logger.info('已进入付款页面') return self._remainds, xml else: logger.warning('未知 activity %s' % cur_activity) raise MyError('转账页面操作超时') # 进入首页 def _enter_main_page(self): #self.driver.stop_app(ABC.package) package = self.driver.get_cur_packge() # 启动app if package != ABC.package: self.driver.start_app(ABC.package, ABC.main_activity) time.sleep(10) cur_time = time.time() while time.time() - cur_time <= 30: if self.driver.is_app_started('com.android.bankabc'): logger.info('启动app成功') break time.sleep(1) for i in range(3): if self.driver.is_app_started('com.android.bankabc'): break time.sleep(0.5) else: raise MyError('启动APP失败') # 进入首页 start_time = time.time() while time.time() - start_time < 30: cur_activity = self.driver.get_cur_activity() xml = self.driver.get_xml() if self.__handle_random_page(cur_activity, xml, True): # 更新页面 start_time = time.time() continue elif cur_activity == 'com.android.bankabc.homepage.HomeActivity' and xml.count( 'NAF="true"') >= 5: # 判断首页刷新完整 self.driver.click(int(0.3704 * self.driver.width()), int(0.2899 * self.driver.height())) time.sleep(3) # 预留界面响应时间 elif cur_activity == 'com.android.bankabc.MainActivity' and '他行转本行' in xml: # 待转账页面 logger.info(f'进入待转账页面') return xml else: logger.info('点击返回键') self.driver.back() raise MyError('回到APP首页超时失败') def __click(self, pos, double=False, use_swip=False, time=0.5, cnt=1): click_x = (int(pos[0]) + int(pos[2])) // 2 click_y = (int(pos[1]) + int(pos[3])) // 2 if use_swip: self.driver.swip_pos(click_x, click_y, click_x + 1, click_y + 1, time) else: for i in range(cnt): self.driver.click(click_x, click_y, double) #logger.debug(f'clicked {click_x} {click_y}') def send_status(self, taskid, status): try: r = requests.get( url= f'http://atf.wuwotech.com/api/ele/kcard?taskId={taskid}&status={status}', ) r.raise_for_status() except BaseException as e: logger.warning(f'发送k宝状态失败 taskid:{taskid} status: {status}')
logger.info(f'{ret}') if len(ret) == 1: self.__click(ret[0]) elif cur_activity == 'com.ccb.framework.security.base.successpage.CcbSuccessPageAct': ret = re.findall(self.__pattern_cancle, xml) if len(ret) == 1: self.__click(ret[0]) logger.info('点击返回键') self.driver.back() else: logger.info('已进入首页') return xml time.sleep(0.5) raise MyError('回到APP首页超时失败') def __click(self, pos, double=False): click_x = (int(pos[0]) + int(pos[2])) // 2 click_y = (int(pos[1]) + int(pos[3])) // 2 self.driver.click(click_x, click_y, double) #logger.debug(f'clicked {click_x} {click_y}') if __name__ == '__main__': driver = Driver() serial = '1234567890qwertyuiopasdfghjklzxcvbnm' for c in serial: x = int(CCB.login_key_board[c][0] * driver.width()) y = int(CCB.login_key_board[c][1] * driver.height()) print(c, x, y) driver.click(x, y)
class PinAn: package = 'com.pingan.paces.ccms' main_activity = 'com.pingan.pocketbank.splash.PALoadingActivity' apk_version = '4.1.5' verify_edit = (0.3542, 0.5125) login_key_board_character = { 'q': (0.069, 0.7319), 'w': (0.155, 0.7319), 'e': (0.26, 0.7319), 'r': (0.355, 0.7319), 't': (0.455, 0.7319), 'y': (0.555, 0.7319), 'u': (0.655, 0.7319), 'i': (0.755, 0.7319), 'o': (0.855, 0.7319), 'p': (0.944, 0.7319), 'a': (0.111, 0.77579), 's': (0.2056, 0.77579), 'd': (0.3018, 0.77579), 'f': (0.4, 0.77579), 'g': (0.4944, 0.77579), 'h': (0.599, 0.77579), 'j': (0.6963, 0.77579), 'k': (0.795, 0.77579), 'l': (0.8889, 0.77579), 'z': (0.2056, 0.86336), 'x': (0.302, 0.86336), 'c': (0.399, 0.86336), 'v': (0.497, 0.86336), 'b': (0.598, 0.86336), 'n': (0.702, 0.86336), 'm': (0.797, 0.86336) } char2digital = (0.109, 0.9532) finish = (0.9139, 0.6566) login_key_board_digital = { '1': (0.1556, 0.6838), '2': (0.513, 0.6838), '3': (0.8278, 0.6838), '4': (0.1556, 0.7741), '5': (0.513, 0.7741), '6': (0.8278, 0.7741), '7': (0.1556, 0.8645), '8': (0.513, 0.8645), '9': (0.8278, 0.8645), '0': (0.513, 0.956), '.': (0.1556, 0.956) } def __init__(self, user, log_passwd, pay_passwd, k_passwd=''): self.user = user self.passwd = pay_passwd self.login_passwd = log_passwd self.driver = Driver() # 首页——首页按钮 self.__pattern_main_activity_main_page_btn = r'text="首页".+?bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"' # 更新页面——关闭更新按钮 self.__pattern_cancle = r'text="取消" resource-id="com.pingan.paces.ccms:id/btn_left".+?bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"' # 首页的编辑框 self.__pattern_search = r'resource-id="com.pingan.paces.ccms:id/base_header_view_middle_search_tv".+?bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]' # 编辑框的清空按钮 self.__pattern_clear = r'text=""\s+resource-id="com.pingan.paces.ccms:id/launcher_header_delete_img"\s+class="android.widget.ImageView".+?clickable="true".+?bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"' # 编辑框的转账选项 #self.__pattern_tansfer_btn = r'text="转账"\s+resource-id=""\s+class="android.view.View".+?bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"' # 小米 self.__pattern_tansfer_btn = r'resource-id=""\s+class="android.view.View"\s+package="com.pingan.paces.ccms"\s+content-desc="转账"\s+.+?bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"' # nexus 5 # 待转账页面——转账 #self.__pattern_tansfer = r'text="银行账号转账".+?bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"' self.__pattern_tansfer = r'content-desc="银行账号转账".+?bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"' # 转账页面——余额 #self.__pattern_remainds = r'text="可用余额:([\d\.]+)元"' self.__pattern_remainds = r'content-desc="可用余额:([\d\.\,]+)元"' # 转账页面——继续转账按钮 #self.__pattern_continue = r'text="继续转账\s*".+?bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"' self.__pattern_continue = r'content-desc="继续转账\s*".+?bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"' # 转账页面——确认按钮 self.__pattern_sure = r'text="确认".+?bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"' # 转账页面——收款人 self.__pattern_reciver = r'resource-id="nameInput".+?bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"' # 转账页面——卡号 self.__pattern_card = r'resource-id="inputCardNum".+?bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"' # 转账页面——金额 #self.__pattern_money = r'text="免手续费".+?bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"' self.__pattern_money = r'content-desc="免手续费".+?bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"' # 转账页面——下一步 #self.__pattern_next = r'text="下一步".+?bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"' self.__pattern_next = r'content-desc="下一步".+?bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"' # 登录页面的登录密码编辑框 #self.__pattern_login_code = r'text="登录密码".+?bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"' # 6A self.__pattern_login_code = r'content-desc="登录密码".+?bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"' # nexus 5 # 登录页面的登录按钮 #self.__pattern_login = r'text="登录"\s+resource-id="submit"\s+class="android.widget.Button".+?bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"' # 6A self.__pattern_login = r'class="android.widget.Button"\s+package="com.pingan.paces.ccms"\s+content-desc="登录".+?bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"' # nexus 5 # 收不到短信验证码提示 self.__pattern_sms_prompt = r'NAF="true" index="1" text="" resource-id="" class="android.view.View" package="com.pingan.paces.ccms".+?bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"' # 取消联系人 self.__pattern_cancel_contact = r'resource-id="android:id/button1".+?content-desc="取消".+?bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"' def check_surplus(self): self.driver.power_on_screen() self.driver.unlock() logger.info('开始通过平安银行查询余额') # 回到首页 try: self._enter_main_page() # 从首页进入待转账页面 self.__enter_prepare_transfer() # 转账页面 return float(self.__enter_transfer('', '', '', True)), '' except BaseException as e: logger.error(f'查询余额失败,{e}') logger.error(f"{traceback.print_exc()}") return '', str(e) def transfer_cash(self, reciver, card, money, taskid, bank_name): self.driver.power_on_screen() self.driver.unlock() self._remainds = '' logger.info(f'开始通过平安银行给{reciver}的卡{card}转账{money}元') try: # 回到首页 self._enter_main_page() # 从首页进入待转账页面 self.__enter_prepare_transfer() # 转账页面 self.__enter_transfer(reciver, card, str(money)) # 支付 status, reason = self.__pay(self.passwd) if status is True: self._remainds = round(float(self._remainds) - float(money), 2) return self._remainds, OK, reason except MyError as e: e = f'手机原因: {e}' logger.error(e) logger.error(traceback.format_exc()) return self._remainds, PHONE_ERR, str(e) except NomoneyError as e: e = '卡余额不足' logger.error(e) logger.error(traceback.format_exc()) return self._remainds, NO_MONEY, str(e) except UserError as e: e = f'用户原因: {e}' logger.error(e) logger.error(traceback.format_exc()) return self._remainds, USER_ERR, str(e) except BaseException as e: e = f'其他原因: {e}' logger.error(e) logger.error(traceback.format_exc()) return self._remainds, PHONE_ERR, str(e) def _enter_main_page(self): #self.driver.stop_app(PinAn.package) package = self.driver.get_cur_packge() logger.info(f"当前包名:{package}, 平安包名:{PinAn.package}") # 启动app if package != PinAn.package: self.driver.start_app(PinAn.package, PinAn.main_activity) cur_time = time.time() while time.time() - cur_time <= 30: if self.driver.is_app_started(PinAn.package): logger.info('启动app成功') break time.sleep(1) for i in range(3): if self.driver.is_app_started(PinAn.package): break time.sleep(0.5) else: raise MyError('启动APP失败') # 进入首页 start_time = time.time() while time.time() - start_time < 60: cur_activity = self.driver.get_cur_activity() if self.__handle_random_page(cur_activity): # 更新页面 start_time = time.time() continue elif cur_activity != 'com.pingan.launcher.activity.LauncherActivity': self.driver.back() else: logger.info('已进入首页') return raise MyError('回到APP首页超时失败') def __enter_prepare_transfer(self): start_time = time.time() while time.time() - start_time < 50: xml = self.driver.get_xml() # 更新页面 cur_activity = self.driver.get_cur_activity() if self.__handle_random_page(cur_activity, xml): start_time = time.time() continue elif cur_activity == 'com.pingan.launcher.activity.LauncherActivity': # 匹配首页的首页按钮 ret = re.findall(self.__pattern_search, xml) if len(ret) == 1: logger.info('点击首页的搜索框') self.__click(ret[0]) else: logger.warning(f'为在首页匹配到搜索框') elif cur_activity == 'com.pingan.core.base.VoiceSearchActivity': logger.info(f'进入搜索框') if re.search(self.__pattern_clear, xml): logger.info(f'点击搜索框的清空图标') self.__click(re.findall(self.__pattern_clear, xml)[0]) self.driver.input_text('转账') # 匹配的首页的转账按钮 time.sleep(1.5) xml = self.driver.get_xml() ret = re.findall(self.__pattern_tansfer_btn, xml) if len(ret) == 1: logger.info(f'点击搜索框页面的转账按钮') self.__click(ret[0]) start_time = time.time() elif cur_activity == 'com.pingan.core.base.PocketWebViewActivity': logger.info('已进入待转账页面') return else: logger.warning('未知 activity %s' % cur_activity) raise MyError('从首页进入转账页面超时') def __enter_transfer(self, name, card, money, only4remainds=False): start_time = time.time() self._remainds = '' while time.time() - start_time < 70: cur_activity = self.driver.get_cur_activity() xml = self.driver.get_xml() logger.info(f'---{cur_activity}') if self.__handle_random_page(cur_activity, xml): start_time = time.time() continue elif '取款密码' in xml: logger.info('进入支付页面') return elif re.search(self.__pattern_sure, xml): logger.info('点击确定') self.__click(re.findall(self.__pattern_sure, xml)[0]) start_time = time.time() elif re.search(self.__pattern_continue, xml): logger.info('点击继续转账') ret = re.findall(self.__pattern_continue, xml)[0] self.__click(ret) start_time = time.time() elif '可用余额' in xml: logger.info('进入正式转账页面') ret_remainds = re.findall(self.__pattern_remainds, xml) if len(ret_remainds) != 1: logger.error(f'未找到余额') continue if only4remainds: logger.info(f'余额:{ret_remainds[0]}') return re.sub(',', '', ret_remainds[0]) ret_reciver = re.findall(self.__pattern_reciver, xml) if len(ret_reciver) != 1: logger.error('未找到收款人控件') continue ret_card = re.findall(self.__pattern_card, xml) if len(ret_card) != 1: logger.error('未找到卡号控件') continue ret_next = re.findall(self.__pattern_next, xml) if len(ret_next) != 1: logger.error('未找到下一步控件') continue self._remainds = re.sub(',', '', ret_remainds[0]) logger.info(f'余额:{self._remainds}') if float(self._remainds) < float(money): raise NomoneyError(f'余额不足{money}元,当前余额{self._remainds}元') logger.info('输入卡号 %s' % card) self.__click(ret_card[0]) self.driver.input_text(card) xml = self.driver.get_xml() if '请选择' in xml: raise UserError(f'卡号不存在') logger.info('输入收款人 %s' % name) ret_reciver = re.findall(self.__pattern_reciver, xml) self.__click(ret_reciver[0]) self.driver.input_text(name) xml = self.driver.get_xml() ret_money = re.findall(self.__pattern_money, xml) if len(ret_money) != 1: logger.error('未找到转账金额控件') raise MyError('未找到转账金额控件') logger.info('输入金额 %s' % money) self.__click(ret_money[0]) time.sleep(1) for c in money: x = int(self.login_key_board_digital[c][0] * self.driver.width()) y = int(self.login_key_board_digital[c][1] * self.driver.height()) self.driver.click(x, y) self.driver.click(self.finish[0] * self.driver.width(), self.finish[1] * self.driver.height()) time.sleep(2) xml = self.driver.get_xml() if re.search(self.__pattern_cancel_contact, xml): logger.info(f'点击取消联系人按钮') self.__click( re.findall(self.__pattern_cancel_contact, xml)[0]) time.sleep(0.5) self.driver.swip(0.5, 0.7, 0.5, 0.3) ret_next = re.findall(self.__pattern_next, self.driver.get_xml()) if len(ret_next) != 1: logger.error('未找到下一步控件') raise MyError('未找到下一步控件') logger.info('点击下一步') self.__click(ret_next[0]) start_time = time.time() elif cur_activity == 'com.pingan.core.base.PocketWebViewActivity': ret = re.findall(self.__pattern_tansfer, xml) if len(ret) == 1: logger.info('点击待转账页面的转账按钮') self.__click(ret[0]) else: logger.warning(f'未知 activity {cur_activity}') raise MyError('从待转账页面进入转账页面超时') def __pay(self, code): start_time = time.time() while time.time() - start_time < 70: # 更新页面 cur_activity = self.driver.get_cur_activity() xml = self.driver.get_xml() if self.__handle_random_page(cur_activity, xml): start_time = time.time() continue elif cur_activity == 'com.pingan.core.base.PocketWebViewActivity': if '"收不到验证码"' in xml and re.search(self.__pattern_sms_prompt, xml): logger.info(f'取消收不到短信提醒框') self.__click(re.findall(self.__pattern_sms_prompt, xml)[0]) elif '的取款密码' in xml: logger.info(f'输入取款密码') for c in code: x = int(self.login_key_board_digital[c][0] * self.driver.width()) y = int(self.login_key_board_digital[c][1] * self.driver.height()) self.driver.click(x, y) msg_start_time = time.time() self.driver.click(self.finish[0] * self.driver.width(), self.finish[1] * self.driver.height()) time.sleep(1) elif '验证码' in xml and '手机号' in xml and '查收' in xml: logger.info('进入验证码输入界面') temp_start = time.time() while time.time() - temp_start < 120: msg_list = MsgManger.getMsg('95511', int(msg_start_time)) if len(msg_list) == 0: time.sleep(1) continue msg = msg_list[0] pattern = f'动态码(\d+),收款人' ret = re.findall(pattern, msg) if len(ret) != 1: logger.error(f'未能在短信{msg}中匹配到 {pattern}') time.sleep(0.5) continue logger.info('双击验证码编辑框') self.__click([107, 837, 658, 1002], True) logger.info(f'输入验证码{ret[0]}') for c in ret[0]: x = int(self.login_key_board_digital[c][0] * self.driver.width()) y = int(self.login_key_board_digital[c][1] * self.driver.height()) self.driver.click(x, y) start_time = msg_start_time = time.time() break else: raise MyError('120秒内未能收到短信验证码') elif '转账' in xml and '完成' in xml: logger.info('进入转账查询结果界面') if '转账提交成功' in xml: ''' logger.info(f'转账成功,等待支付结果短信') temp_start = time.time() while time.time() - temp_start < 40: msg_list = MsgManger.getMsg('106927995511', int(msg_start_time)) if len(msg_list) == 0: time.sleep(0.3) continue ret = msg_list[0] logger.info(ret) return True, ret ''' return True, '' elif re.search( r'content-desc="转账失败"[\s\S]+?<node index="2".+?content-desc="(.+?)"', xml): ret = re.findall( r'content-desc="转账失败"[\s\S]+?<node index="2".+?content-desc="(.+?)"', xml)[0] logger.info(ret) raise UserError(ret) else: logger.warning('未知 activity %s' % cur_activity) raise MyError('支付超时') def __handle_random_page(self, cur_activity, xml=''): if cur_activity == 'com.pingan.launcher.activity.LauncherActivity' or cur_activity == PinAn.main_activity: xml = self.driver.get_xml() if xml == '' else xml if re.search(self.__pattern_cancle, xml): logger.info(f'点击取消按钮') self.__click(re.findall(self.__pattern_cancle, xml)[0]) return True elif cur_activity == 'com.pingan.core.base.PocketWebViewActivity': xml = self.driver.get_xml() if xml == '' else xml if re.search(self.__pattern_login_code, xml): logger.info(f'进入登录页面') self.__click( re.findall(self.__pattern_login_code, xml)[0], xml) logger.info(f'输入登录密码') cur = CHARACTER keyboard = self.login_key_board_character for c in self.login_passwd: if c.isdigit() and cur == CHARACTER: self.driver.click( self.char2digital[0] * self.driver.width(), self.char2digital[1] * self.driver.height()) time.sleep(0.5) cur = DIGITAL keyboard = self.login_key_board_digital elif c.isalpha() and cur == DIGITAL: self.driver.click( self.char2digital[0] * self.driver.width(), self.char2digital[1] * self.driver.height()) time.sleep(0.5) cur = CHARACTER keyboard = self.login_key_board_character x = int(keyboard[c][0] * self.driver.width()) y = int(keyboard[c][1] * self.driver.height()) #self.driver.swip_pos(x, y, x+1, y+1, 1) self.driver.click(x, y) self.driver.click(self.finish[0] * self.driver.width(), self.finish[1] * self.driver.height()) time.sleep(0.3) logger.info('点击登录按钮') self.__click( re.findall(self.__pattern_login, self.driver.get_xml())[0]) return True return False def __click(self, pos, double=False): click_x = (int(pos[0]) + int(pos[2])) // 2 click_y = (int(pos[1]) + int(pos[3])) // 2 self.driver.click(click_x, click_y, double)