def prepare_balance_mobilebalance(filter='FULL', params={}): """Формируем текст для отправки в telegram из html файла полученного из web сервера mobilebalance """ url = store.options('mobilebalance_http', section='Telegram', mainparams=params) tgmb_format = store.options('tgmb_format', section='Telegram', mainparams=params) response1_text = requests.get(url).content.decode('cp1251') # нет таблицы if 'Введите пароль' in response1_text or '<table' not in response1_text: res = 'Неправильный пароль для страницы баланса в ini, проверьте параметр mobilebalance_http' return res soup = bs4.BeautifulSoup(response1_text, 'html.parser') headers = [ ''.join(el.get('id')[1:]) for el in soup.find(id='header').findAll('th') ] if filter == 'LASTCHANGE' and 'BalDeltaQuery' not in headers: # нет колонки Delta (запрос) res = 'Включите показ колонки Delta (запрос) в настройках mobilebalance' return res elif filter == 'LASTDAYCHANGE' and 'BalDelta' not in headers: # нет колонки Delta (день) res = 'Включите показ колонки Delta (день) в настройках mobilebalance' return res data = [[''.join(el.contents) for el in line.findAll(['th', 'td'])] for line in soup.findAll(id='row')] table = [dict(zip(headers, line)) for line in data] table = filter_balance(table, filter, params) res = [tgmb_format.format(**line) for line in table] return '\n'.join(res)
def send_telegram_over_requests(text=None, auth_id=None, filter='FULL', params={}): """Отправка сообщения в телеграм через requests без задействия python-telegram-bot Может пригодится при каких-то проблемах с ботом или в ситуации когда на одной машине у нас крутится бот, а с другой в этого бота мы еще хотим засылать инфу text - сообщение, если не указано, то это баланс для телефонов у которых он изменился auth_id - список id через запятую на которые слать, если не указано, то берется список из mbplugin.ini """ if text is None: text = prepare_balance(filter, params) api_token = store.options('api_token', section='Telegram', mainparams=params).strip() if len(api_token) == 0: logging.info('Telegtam api_token not found') return if auth_id is None: auth_id = list( map( int, store.options('auth_id', section='Telegram', mainparams=params).strip().split(','))) else: auth_id = list(map(int, str(auth_id).strip().split(','))) r = [ requests.post(f'https://api.telegram.org/bot{api_token}/sendMessage', data={ 'chat_id': chat_id, 'text': text, 'parse_mode': 'HTML' }) for chat_id in auth_id if text != '' ] return [repr(i) for i in r]
def main(self, run='normal'): self.browser_launch() if run == 'normal': self.data_collector() elif run == 'check_logon': self.check_logon_selectors_prepare() self.check_logon_selectors() logging.debug(f'Data ready {self.result.keys()}') if str(store.options('log_responses')) == '1' or store.options( 'logginglevel') == 'DEBUG': import pprint text = '\n\n'.join([ f'{k}\n{v if k.startswith("CONTENT") else pprint.PrettyPrinter(indent=4).pformat(v) }' for k, v in self.responses.items() if 'GetAdElementsLS' not in k and 'mc.yandex.ru' not in k ]) with open(os.path.join(store.options('loggingfolder'), self.storename + '.log'), 'w', encoding='utf8', errors='ignore') as f: f.write(text) self.browser_close() kill_chrome( ) # Добиваем все наши незакрытые хромы, чтобы не появлялось кучи зависших clear_cache(self.storename) return self.result
def flags(cmd, key=None, value=None): 'Работаем с флагами (таблица Flags) если в ini установлен sqlitestore=1, если нет просто вернем None' try: if store.options('sqlitestore') == '1': dbfilename = store.options('dbfilename') logging.debug(f'Flag:{cmd}') db = dbengine(dbfilename) if cmd.lower() == 'set': db.cur.execute('REPLACE INTO flags(key,value) VALUES(?,?)', [key, value]) db.conn.commit() elif cmd.lower() == 'get': db.cur.execute('select value from flags where key=?', [key]) qres = db.cur.fetchall() if len(qres) > 0: return qres[0][0] elif cmd.lower() == 'getall': db.cur.execute('select * from flags') qres = db.cur.fetchall() return {k: v for k, v in qres} elif cmd.lower() == 'deleteall': db.cur.execute('delete from flags') db.conn.commit() elif cmd.lower() == 'delete': db.cur.execute('delete from flags where key=?', [key]) db.conn.commit() else: if cmd.lower() == 'getall': return {} except Exception: logging.error( f'Ошибка при записи в БД {"".join(traceback.format_exception(*sys.exc_info()))}' )
def write_report(): 'сохраняем отчет balance_html если в ini createhtmlreport=1' try: if store.options('createhtmlreport') == '1': _, res = getreport() balance_html = store.options('balance_html') logging.info(f'Создаем {balance_html}') open(balance_html, encoding='utf8', mode='w').write('\n'.join(res)) except Exception: logging.error( f'Ошибка генерации {balance_html} {"".join(traceback.format_exception(*sys.exc_info()))}' )
def write_result_to_db(plugin, login, result): 'пишем в базу если в ini установлен sqlitestore=1' try: if store.options('sqlitestore') == '1': dbfilename = store.options('dbfilename') logging.info(f'Пишем в базу {dbfilename}') db = dbengine(dbfilename) db.write_result(plugin, login, result) except AttributeError: logging.info(f'Отсутствуют параметры {"".join(traceback.format_exception(*sys.exc_info()))} дополнительные действия не производятся') except Exception: logging.error(f'Ошибка при записи в БД {"".join(traceback.format_exception(*sys.exc_info()))}')
def responses(): 'Возвращаем все responses словарем' try: if store.options('sqlitestore') == '1': dbfilename = store.options('dbfilename') logging.debug(f'Responses from sqlite') db = dbengine(dbfilename) db.cur.execute('select key,value from responses') qres = db.cur.fetchall() return {k: v for k, v in qres} else: return {} except Exception: logging.error( f'Ошибка при записи в БД {"".join(traceback.format_exception(*sys.exc_info()))}' )
def delete_profile(storename): 'Удаляем профиль' kill_chrome() # Перед удалением киляем хром storefolder = store.options('storefolder') profilepath = os.path.abspath( os.path.join(storefolder, 'puppeteer', storename)) shutil.rmtree(profilepath)
def wrapper(self, *args, **kwargs): '''decorator для безопасного запуска функции не падает в случае ошибки, а пишет в лог и возвращяет default=None параметры предназначенные декоратору, и не передаются в вызываемую функцию: default: возвращаемое в случае ошибки значение''' default = kwargs.pop('default', None) if len(args) > 0 and args[0] == '': return default # Готовим строку для лога log_string = f'call: {getattr(func,"__name__","")}({", ".join(map(repr,args))}, {", ".join([f"{k}={repr(v)}" for k,v in kwargs.items()])})' if str(store.options('log_full_eval_string')) == '0': log_string = log_string if len( log_string ) < 200 else log_string[:100] + '...' + log_string[-100:] if 'password' in log_string: log_string = log_string.split( 'password')[0] + 'password ....' log_string = log_string.encode('cp1251', errors='ignore').decode( 'cp1251', errors='ignore') # Убираем всякую хрень try: res = func(self, *args, **kwargs) # pylint: disable=not-callable logging.info(f'{log_string} OK') return res except Exception: logging.info( f'{log_string} fail: {"".join(traceback.format_exception(*sys.exc_info()))}' ) return default
def clear_cache(storename): 'Очищаем папку с кэшем профиля чтобы не разрастался' #return # С такой очисткой оказывается связаны наши проблемы с загрузкой storefolder = store.options('storefolder') profilepath = os.path.abspath( os.path.join(storefolder, 'puppeteer', storename)) shutil.rmtree(os.path.join(profilepath, 'Cache'), ignore_errors=True) shutil.rmtree(os.path.join(profilepath, 'Code Cache'), ignore_errors=True)
def get_balance(login, password, storename=None): result = {} session = store.Session( storename) # Используем костылем для хранения предыдущих данных # если у нас еще нет переменной для истории - создаем (грязный хак - не делайте так, а если делаете - не пользуйтесь этой сессией для хождения в инет, а только для сохранения): session.session.params['history'] = session.session.params.get( 'history', []) data = store.read_stocks(login.lower()) stocks = data['stocks'] remain = data['remain'] currenc = data['currenc'] res_data = count_all_scocks_multithread(stocks, remain, currenc) session.session.params['history'].append({ 'timestamp': time.time(), 'data': res_data }) # Полученное значение сразу добавляем в хвост истории if store.options('stock_fulllog'): fulllog = '\n'.join( f'{time.strftime("%Y.%m.%d %H:%M:%S",time.localtime())}\t{i["security"]}\t{i["price"]}\t{i["currency"]}\t{i["cnt"]}\t{round(i["value"],2)}\t{round(i["value_priv"],2)}' for i in res_data) with open( os.path.join(store.options('loggingfolder'), f'stock_{login}.log'), 'a') as f_log: f_log.write(fulllog + '\n') # Полная информация по стокам result['Stock'] = '\n'.join([ f'{i["security"]+"("+i["currency"]+")":10} : {round(i["value_priv"],2):9.2f} {currenc}' for i in res_data ]) + '\n' result['UslugiOn'] = len(res_data) # Берем два последних элемента, а из них берем первый т.е. [1,2,3][-2:][0] -> 2 а [3][-2:][0] -> 3 чтобы не морочаться с проверкой есть ли предпоследний prev_data = session.session.params['history'][-2:][0][ 'data'] # TODO подумать какой из истории брать для вычисления. Пока беру предыдущий hst = {i['security']: i['value_priv'] for i in prev_data} result['UslugiList'] = '\n'.join([ f'{i["security"]:5}({i["currency"]}) {i["value_priv"]-hst.get(i["security"],i["value_priv"]):+.2f}\t{i["value_priv"]:.2f}' for i in res_data ]) # Полная информация подправленная для показа в balance.html result['Balance'] = round(sum([i['value_priv'] for i in res_data]), 2) # Сумма в заданной валюте result['Currenc'] = currenc # Валюта session.session.params['history'] = session.session.params['history'][ -100:] # оставляем последние 100 значений чтобы не росло бесконечно session.save_session() return result
def send_balance(self): 'Отправляем баланс' if self.updater is None or str( store.options('send_balance_changes', section='Telegram')) == '0': return baltxt = prepare_balance('LASTCHANGE') self.send_message(text=baltxt, parse_mode=telegram.ParseMode.HTML)
def prepare_balance(filter='FULL', params={}): """Prepare balance for TG.""" try: baltxt = '' if store.options('tg_from', section='Telegram', mainparams=params) == 'sqlite': baltxt = prepare_balance_sqlite(filter, params) else: baltxt = prepare_balance_mobilebalance(filter, params) if baltxt == '' and str( store.options('send_empty', section='Telegram', mainparams=params)) == '1': baltxt = 'No changes' return baltxt except Exception: exception_text = f'Ошибка: {"".join(traceback.format_exception(*sys.exc_info()))}' logging.error(exception_text) return 'error'
def __init__(self): api_token = store.options('api_token', section='Telegram').strip() request_kwargs = {} tg_proxy = store.options('tg_proxy', section='Telegram').strip() if tg_proxy.lower() == 'auto': request_kwargs['proxy_url'] = urllib.request.getproxies().get( 'https', '') elif tg_proxy != '' and tg_proxy.lower() != 'auto': request_kwargs['proxy_url'] = tg_proxy # ??? Надо или не надо ? # request_kwargs['urllib3_proxy_kwargs'] = {'assert_hostname': 'False', 'cert_reqs': 'CERT_NONE'} self.updater = None if api_token != '' and str( store.options( 'start_tgbot', section='Telegram')) == '1' and 'telegram' in sys.modules: try: logging.info( f'Module telegram starting for id={self.auth_id()}') self.updater = Updater(api_token, use_context=True, request_kwargs=request_kwargs) logging.info(f'{self.updater}') dp = self.updater.dispatcher dp.add_handler(CommandHandler("id", self.get_id)) dp.add_handler(CommandHandler("balance", self.get_balancetext)) dp.add_handler( CommandHandler("balancefile", self.get_balancefile)) self.updater.start_polling() # Start the Bot if str(store.options('send_empty', section='Telegram')) == '1': self.send_message(text='Hey there!') except Exception: exception_text = f'Ошибка запуска telegram bot {"".join(traceback.format_exception(*sys.exc_info()))}' logging.error(exception_text) elif 'telegram' not in sys.modules: logging.info('Module telegram not found') elif api_token == '': logging.info('Telegtam api_token not found') elif str(store.options('start_tgbot', section='Telegram')) != '1': logging.info( 'Telegtam bot start is disabled in mbplugin.ini (start_tgbot=0)' )
def kill_chrome(): '''Киляем дебажный хром если вдруг какой-то висит, т.к. народ умудряется запускать не только хром, то имя exe возьмем из пути ''' chrome_executable_path = store.options('chrome_executable_path') pname = os.path.split(chrome_executable_path)[-1].lower() for p in psutil.process_iter(): try: if p.name().lower( ) == pname and 'remote-debugging-port' in ''.join(p.cmdline()): p.kill() except Exception: pass
def prepare_balance_sqlite(filter='FULL', params={}): 'Готовим данные для отчета из sqlite базы' db = dbengine.dbengine(store.options('dbfilename', mainparams=params)) table_format = store.options('tg_format', section='Telegram', mainparams=params).replace('\\t', '\t').replace( '\\n', '\n') # table_format = 'Alias,PhoneNumber,Operator,Balance' # Если формат задан как перечисление полей через запятую - переделываем под формат if re.match(r'^(\w+(?:,|\Z))*$', table_format.strip()): table_format = ' '.join( [f'{{{i}}}' for i in table_format.strip().split(',')]) table = db.report() table = [i for i in table if i['Alias'] != 'Unknown'] # filter Unknown table.sort( key=lambda i: [i['NN'], i['Alias']]) # sort by NN, after by Alias table = filter_balance(table, filter, params) res = [table_format.format(**line) for line in table] return '\n'.join(res)
def data_collector(self): #self.do_logon(url=login_url, user_selectors=user_selectors) # По простому если не видим баланса - показываем капчу self.page_goto(login_url) self.page_waitForNavigation() if str(store.options('show_captcha')) == '1': if not self.page_evaluate(js_check_balance_str, default=False): pa.hide_chrome(hide=False, foreground=True) for cnt2 in range(int(store.options('max_wait_captcha'))): _ = cnt2 if self.page_evaluate(js_check_balance_str, default=False): break self.sleep(1) self.wait_params(params=[ { 'name': 'Balance', 'jsformula': r"parseFloat(document.querySelector('div[class=balance-widget__amount]').innerText.replace(/[^\d\.,-]/g,'').replace(',','.'))", }, ], url='https://yoomoney.ru/actions')
def OnCommand(self, hwnd, msg, wparam, lparam): id = win32api.LOWORD(wparam) port = int(store.options('port', section='HttpServer')) if id == 1024: os.system(f'start http://localhost:{port}/report') if id == 1025: os.system(f'start http://localhost:{port}/log?lines=40') elif id == 1026: print("Goodbye") win32gui.DestroyWindow(self.hwnd) self.cmdqueue.put('STOP') else: print("Unknown command -", id)
def history(self, phone_number, operator, days=7, lastonly=1): 'Генерирует исторические данные по номеру телефона' if days == 0: return [] historysql = f'''select * from phones where phonenumber=? and operator=? and QueryDateTime>date('now','-'|| ? ||' day') order by QueryDateTime desc''' cur = self.cur.execute(historysql, [phone_number, operator, days]) dbheaders = list(zip(*cur.description))[0] dbdata = cur.fetchall() dbdata_sets = [ set(l) for l in zip(*dbdata) ] # составляем список уникальных значений по каждой колонке dbdata_sets = [{ i for i in l if str(i).strip() not in ['', 'None', '0.0', '0'] } for l in dbdata_sets] # подправляем косяки if len(dbdata_sets) == 0: # Истории нет - возвращаем пустой return [] qtimes_num = dbheaders.index('QueryDateTime') qtimes = [line[qtimes_num] for line in dbdata] # Список всех времен получения баланса qtimes_max = { max([k for k in qtimes if k.startswith(j)]) for j in {i.split()[0] for i in qtimes} } # Последние даты получения баланса за сутки table = [] # результат - каждая строчка словарь элементов fields = store.options('HoverHistoryFormat').split(',') # выкидываем неинтересные колонки Там где только нули и None fields = [ i for i in fields if i in dbheaders and dbdata_sets[dbheaders.index(i)] != set() ] for line in dbdata: row = dict(zip(dbheaders, line)) if str(lastonly) == '0' or row[ 'QueryDateTime'] in qtimes_max: # фильруем данные по qtimes_max table.append({k: row[k] for k in fields if k in row}) return table[::int(store.options('SkipDay')) + 1]
def fix_crash_banner(storename): 'Исправляем Preferences чтобы убрать баннер Работа Chrome была завершена некорректно' storefolder = store.options('storefolder') fn_pref = os.path.abspath( os.path.join(storefolder, 'puppeteer', storename, 'Preferences')) if not os.path.exists(fn_pref): return # Нет Preferences - выходим with open(fn_pref) as f: data = f.read() data1 = data.replace('"exit_type":"Crashed"', '"exit_type":"Normal"').replace( '"exited_cleanly":false', '"exited_cleanly":true') if data != data1: logging.info(f'Fix chrome crash banner') open(fn_pref, mode='w').write(data1)
def recompile(): pluginpath = os.path.abspath(os.path.split(sys.argv[0])[0]) os.chdir(pluginpath) sys.path.insert(0,pluginpath) port = store.options('port', section='HttpServer') tmpl = open(os.path.join(pluginpath, '..\\jsmblhplugin\\_template_localweb.jsmb'), encoding='cp1251').read() for fn in glob.glob(os.path.join(pluginpath, '..\\plugin\\*.py')): if ('def' + ' get_balance(') in open(fn, encoding='utf8').read(): plugin = os.path.splitext(os.path.split(fn)[1])[0] fl = 'p_' + plugin module = __import__(plugin, globals(), locals(), [], 0) data = tmpl.replace('{{pluginname}}', fl).replace('{{port}}', port) if hasattr(module,'icon'): data = re.sub(r'//\s*Icon\s*:\s*\S*', f'// Icon : {module.icon}', data) plugin_name = os.path.join(pluginpath, '..\\jsmblhplugin', fl+'_localweb.jsmb') open(plugin_name, 'w').write(data)
def view_log(param): try: lines = int(param['lines'][0]) except Exception: lines = 100 fn = store.options('logginghttpfilename') res = open(fn).readlines()[-lines:] for num in range(len(res)): #.replace("&","&").replace("<","<").replace(">",">") if ' ERROR ' in res[num]: res[num] = f'<span style="color:red;background-color:white">{res[num]}</span>' elif ' WARNING ' in res[num]: res[num] = f'<span style="color:yellow;background-color:white">{res[num]}</span>' return 'text/html; charset=cp1251', [ '<html><head></head><body><pre>' ] + res + [ '</pre><script>window.scrollTo(0,document.body.scrollHeight);</script></body></html>' ]
def wrapper(*args, **kwargs): 'decorator:Обертка для функций, выполнение которых не влияет на результат, чтобы при падении они не портили остальное' # Готовим строку для лога default = kwargs.pop('default', None) log_string = f'call: {getattr(func,"__name__","")}({", ".join(map(repr,args))}, {", ".join([f"{k}={repr(v)}" for k,v in kwargs.items()])})' if str(store.options('log_full_eval_string')) == '0': log_string = log_string if len( log_string ) < 200 else log_string[:100] + '...' + log_string[-100:] if 'password' in log_string: log_string = log_string.split('password')[0] + 'password ....' try: res = func(*args, **kwargs) # pylint: disable=not-callable logging.info(f'{log_string} OK') return res except Exception: logging.info( f'{log_string} fail: {"".join(traceback.format_exception(*sys.exc_info()))}' ) return default
def send_subsribtions(self): 'Отправляем подписки - это строки из ini вида:' 'subscribtionXXX = id:123456 include:1111,2222 exclude:6666' if self.updater is None: return subscribtions = store.options('subscribtion', section='Telegram', listparam=True) for subscr in subscribtions: # id:123456 include:1111,2222 -> {'id':'123456','include':'1111,2222'} params = { k: v.strip() for k, v in [i.split(':', 1) for i in subscr.split(' ')] } baltxt = prepare_balance('LASTCHANGE', params) ids = [ int(i) for i in params.get('id', '').split(',') if i.isdigit() ] self.send_message(text=baltxt, parse_mode=telegram.ParseMode.HTML, ids=ids)
def simple_app(self, environ, start_response): try: status = '200 OK' ct, text = 'text/html', [] fn = environ.get('PATH_INFO', None) _, cmd, *param = fn.split('/') print(f'{cmd}, {param}') if cmd.lower( ) == 'getbalance': # старый вариант оставлен пока для совместимости ct, text = getbalance_plugin( 'url', param) # TODO !!! Но правильно все-таки через POST elif cmd.lower() == 'sendtgbalance': self.telegram_bot.send_balance() elif cmd.lower() == 'sendtgsubscriptions': self.telegram_bot.send_subsribtions() elif cmd.lower() == 'get': # вариант через get запрос param = urllib.parse.parse_qs(environ['QUERY_STRING']) ct, text = getbalance_plugin('get', param) elif cmd.lower() == 'log': # просмотр лога param = urllib.parse.parse_qs(environ['QUERY_STRING']) ct, text = view_log(param) elif cmd == '' or cmd == 'report': # report if store.options('sqlitestore') == '1': ct, text = getreport(param) else: ct, text = 'text/html', HTML_NO_REPORT elif cmd == 'exit': # exit cmd self.cmdqueue.put('STOP') text = ['exit'] headers = [('Content-type', ct)] start_response(status, headers) return [line.encode('cp1251', errors='ignore') for line in text] except Exception: exception_text = f'Ошибка: {"".join(traceback.format_exception(*sys.exc_info()))}' logging.error(exception_text) headers = [('Content-type', 'text/html')] return ['<html>ERROR</html>'.encode('cp1251')]
def __init__(self): self.cmdqueue = queue.Queue() logging.basicConfig(filename=store.options('logginghttpfilename'), level=store.options('logginglevel'), format=store.options('loggingformat')) self.port = int(store.options('port', section='HttpServer')) self.host = store.options('host', section='HttpServer') with socket.socket() as sock: sock.settimeout( 0.2) # this prevents a 2 second lag when starting the server if sock.connect_ex((self.host, self.port)) == 0: logging.info( f"Port {self.host}:{self.port} already in use, try restart." ) try: requests.Session().get( f'http://{self.host}:{self.port}/exit', timeout=1) time.sleep(3) # Подождем пока сервер остановится except Exception: pass if str(store.options('start_http', section='HttpServer')) != '1': logging.info( f'Start http server disabled in mbplugin.ini (start_http=0)') return with wsgiref.simple_server.make_server( self.host, self.port, self.simple_app, server_class=ThreadingWSGIServer, handler_class=Handler) as self.httpd: logging.info(f'Listening {self.host}:{self.port}....') threading.Thread(target=self.httpd.serve_forever, daemon=True).start() if 'win32api' in sys.modules: # Иконка в трее threading.Thread(target=lambda i=self.cmdqueue: tray_icon(i), daemon=True).start() if 'telegram' in sys.modules: # telegram bot (он сам все запустит в threading) self.telegram_bot = TelegramBot() # Запустили все остальное демонами и ждем, когда они пришлют сигнал self.cmdqueue.get() self.telegram_bot.stop() self.httpd.shutdown() logging.info(f'Shutdown server {self.host}:{self.port}....')
def data_collector(self): mts_usedbyme = store.options('mts_usedbyme') self.do_logon(url=login_url, user_selectors=user_selectors) # TODO close banner # document.querySelectorAll('div[class=popup__close]').forEach(s=>s.click()) if self.login_ori != self.login and self.acc_num.isdigit( ): # это финт для захода через другой номер # если заход через другой номер то переключаемся на нужный номер # TODO возможно с прошлого раза может сохраниться переключенный но вроде работает и так self.page_waitForSelector("[id=ng-header__account-phone_desktop]") self.responses = { } # Сбрасываем все загруженные данные - там данные по материнскому телефону url_redirect = f'https://login.mts.ru/amserver/UI/Login?service=idp2idp&IDButton=switch&IDToken1=id={self.acc_num},ou=user,o=users,ou=services,dc=amroot&org=/users&ForceAuth=true&goto=https://lk.mts.ru' self.page_goto(url_redirect) # !!! Раньше я на каждой странице при таком заходе проверял что номер тот, сейчас проверяю только на старте for _ in range(10): numb = self.page_evaluate( "document.getElementById('ng-header__account-phone_desktop').innerText" ) if numb is not None and numb != '': break else: return # номера на странице так и нет - уходим logging.info(f'PHONE {numb}') if re.sub(r'(?:\+7|\D)', '', numb) != self.acc_num: return # Если номер не наш - уходим # Для начала только баланс быстрым способом (может запаздывать) self.wait_params(params=[ { 'name': 'Balance', 'url_tag': ['api/login/userInfo'], 'jsformula': "parseFloat(data.userProfile.balance).toFixed(2)" }, # Закрываем банеры (для эстетики) { 'name': '#banner1', 'url_tag': ['api/login/userInfo'], 'jsformula': "document.querySelectorAll('mts-dialog div[class=popup__close]').forEach(s=>s.click())", 'wait': False }, ]) # Потом все остальное res1 = self.wait_params(params=[ { 'name': 'TarifPlan', 'url_tag': ['api/login/userInfo'], 'jsformula': "data.userProfile.tariff" }, { 'name': 'UserName', 'url_tag': ['api/login/userInfo'], 'jsformula': "data.userProfile.displayName" }, { 'name': 'Balance', 'url_tag': ['for=api/accountInfo/mscpBalance'], 'jsformula': "parseFloat(data.data.amount).toFixed(2)" }, { 'name': 'Balance2', 'url_tag': ['for=api/cashback/account'], 'jsformula': "parseFloat(data.data.balance).toFixed(2)" }, { 'name': '#counters', 'url_tag': ['for=api/sharing/counters'], 'jsformula': "data.data.counters" }, ]) if '#counters' in res1 and type( res1['#counters']) == list and len(res1['#counters']) > 0: counters = res1['#counters'] # Минуты calling = [i for i in counters if i['packageType'] == 'Calling'] if calling != []: unit = { 'Second': 60, 'Minute': 1 }.get(calling[0]['unitType'], 1) nonused = [ i['amount'] for i in calling[0]['parts'] if i['partType'] == 'NonUsed' ] usedbyme = [ i['amount'] for i in calling[0]['parts'] if i['partType'] == 'UsedByMe' ] if nonused != []: self.result['Min'] = int(nonused[0] / unit) if usedbyme != []: self.result['SpendMin'] = int(usedbyme[0] / unit) # SMS messaging = [ i for i in counters if i['packageType'] == 'Messaging' ] if messaging != []: nonused = [ i['amount'] for i in messaging[0]['parts'] if i['partType'] == 'NonUsed' ] usedbyme = [ i['amount'] for i in messaging[0]['parts'] if i['partType'] == 'UsedByMe' ] if (mts_usedbyme == '0' or self.login not in mts_usedbyme.split(',')) and nonused != []: self.result['SMS'] = int(nonused[0]) if (mts_usedbyme == '1' or self.login in mts_usedbyme.split(',')) and usedbyme != []: self.result['SMS'] = int(usedbyme[0]) # Интернет internet = [i for i in counters if i['packageType'] == 'Internet'] if internet != []: unitMult = settings.UNIT.get(internet[0]['unitType'], 1) unitDiv = settings.UNIT.get(store.options('interUnit'), 1) nonused = [ i['amount'] for i in internet[0]['parts'] if i['partType'] == 'NonUsed' ] usedbyme = [ i['amount'] for i in internet[0]['parts'] if i['partType'] == 'UsedByMe' ] if (mts_usedbyme == '0' or self.login not in mts_usedbyme.split(',')) and nonused != []: self.result['Internet'] = round( nonused[0] * unitMult / unitDiv, 2) if (mts_usedbyme == '1' or self.login in mts_usedbyme.split(',')) and usedbyme != []: self.result['Internet'] = round( usedbyme[0] * unitMult / unitDiv, 2) self.page_goto('https://lk.mts.ru/uslugi/podklyuchennye') res2 = self.wait_params(params=[{ 'name': '#services', 'url_tag': ['for=api/services/list/active$'], 'jsformula': "data.data.services.map(s=>[s.name,!!s.subscriptionFee.value?s.subscriptionFee.value:0])" }]) try: services = sorted(res2['#services'], key=lambda i: (-i[1], i[0])) free = len([ a for a, b in services if b == 0 and (a, b) != ('Ежемесячная плата за тариф', 0) ]) paid = len([a for a, b in services if b != 0]) paid_sum = round(sum([b for a, b in services if b != 0]), 2) self.result['UslugiOn'] = f'{free}/{paid}({paid_sum})' self.result['UslugiList'] = '\n'.join( [f'{a}\t{b}' for a, b in services]) except Exception: logging.info( f'Ошибка при получении списка услуг {"".join(traceback.format_exception(*sys.exc_info()))}' ) # Идем и пытаемся взять инфу со страницы https://lk.mts.ru/obshchiy_paket # Но только если телефон в списке в поле mts_usedbyme или для всех телефонов если там 1 if mts_usedbyme == '1' or self.login in mts_usedbyme.split( ',') or self.acc_num.lower().startswith('common'): self.page_goto('https://lk.mts.ru/obshchiy_paket') res3 = self.wait_params(params=[{ 'name': '#checktask', 'url_tag': ['for=api/Widgets/GetUserClaims', '/longtask/'], 'jsformula': "data.result" }]) try: if 'RoleDonor' in str( res3 ): # Просто ищем подстроку во всем json вдруг что-то изменится logging.info(f'mts_usedbyme: RoleDonor') res4 = self.wait_params(params=[{ 'name': '#donor', 'url_tag': [ 'for=api/Widgets/AvailableCountersDonor$', '/longtask/' ], 'jsformula': "data.result" }]) # acceptorsTotalConsumption - иногда возвращается 0 приходится считать самим # data = {i['counterViewUnit']:i['groupConsumption']-i['acceptorsTotalConsumption'] for i in res4['#donor']} data = { i['counterViewUnit']: i['groupConsumption'] - sum([ j.get('consumption', 0) for j in i.get('acceptorsConsumption', []) ]) for i in res4['#donor'] } if 'RoleAcceptor' in str(res3): logging.info(f'mts_usedbyme: RoleAcceptor') res4 = self.wait_params(params=[{ 'name': '#acceptor', 'url_tag': [ 'for=api/Widgets/AvailableCountersAcceptor', '/longtask/' ], 'jsformula': "data.result.counters" }]) data = { i['counterViewUnit']: i['consumption'] for i in res4['#acceptor'] } if 'RoleDonor' in str(res3) or 'RoleAcceptor' in str(res3): logging.info(f'mts_usedbyme collect: data={data}') if 'MINUTE' in data: self.result['SpendMin'] = data["MINUTE"] if 'ITEM' in data: self.result['SMS'] = data["ITEM"] if 'GBYTE' in data: self.result['Internet'] = data["GBYTE"] # Спецверсия для общего пакета, работает только для Donor if self.acc_num.lower().startswith('common'): if 'RoleDonor' in str(res3): # потребление и остаток cdata_charge = { i['counterViewUnit']: i['groupConsumption'] for i in res4['#donor'] } сdata_rest = { i['counterViewUnit']: i['counterLimit'] - i['groupConsumption'] for i in res4['#donor'] } self.result['Min'] = сdata_rest[ "MINUTE"] # осталось минут self.result['SpendMin'] = cdata_charge[ "MINUTE"] # Потрачено минут if 'rest' in self.acc_num: self.result['SMS'] = сdata_rest[ "ITEM"] # остатки по инету и SMS self.result['Internet'] = сdata_rest["GBYTE"] else: self.result['SMS'] = cdata_charge[ "ITEM"] # расход по инету и SMS self.result['Internet'] = cdata_charge["GBYTE"] logging.info( f'mts_usedbyme common collect: сdata_rest={сdata_rest} cdata_charge={cdata_charge}' ) else: # Со страницы общего пакета не отдали данные, чистим все, иначе будут кривые графики. ТОЛЬКО для common raise RuntimeError( f'Страница общего пакета не возвращает данных') except: logging.info( f'Ошибка при получении obshchiy_paket {"".join(traceback.format_exception(*sys.exc_info()))}' ) if self.acc_num.lower().startswith('common'): self.result = { 'ErrorMsg': 'Страница общего пакета не возвращает данных' }
import os, sys, re, glob import settings, store pluginpath = os.path.abspath(os.path.split(sys.argv[0])[0]) os.chdir(pluginpath) sys.path.insert(0, pluginpath) port = store.options('port', section='HttpServer') tmpl = open(os.path.join(pluginpath, '..\\jsmblhplugin\\_template_localweb.jsmb'), encoding='cp1251').read() for fn in glob.glob(os.path.join(pluginpath, '..\\plugin\\*.py')): if ('def' + ' get_balance(') in open(fn, encoding='utf8').read(): plugin = os.path.splitext(os.path.split(fn)[1])[0] fl = 'p_' + plugin module = __import__(plugin, globals(), locals(), [], 0) data = tmpl.replace('{{pluginname}}', fl).replace('{{port}}', port) if hasattr(module, 'icon'): data = re.sub(r'//\s*Icon\s*:\s*\S*', f'// Icon : {module.icon}', data) plugin_name = os.path.join(pluginpath, '..\\jsmblhplugin', fl + '_localweb.jsmb') open(plugin_name, 'w').write(data)
def do_logon(self, url=None, user_selectors=None): '''Делаем заход в личный кабинет/ проверяем не залогинены ли уже На вход передаем словарь селекторов и скриптов который перекроет действия по умолчанию Если какой-то из шагов по умолчанию хотим пропустить, передаем пустую строку Смотрите актуальное описание напротив параметров в коментариях Чтобы избежать ошибок - копируйте названия параметров''' selectors = default_logon_selectors.copy() if url is None: url = self.login_url if user_selectors is None: user_selectors = self.user_selectors if user_selectors is not None else {} # проверяем что все поля из user_selectors есть в селектор (если не так то скорее всего опечатка и надо сигналить) if set(user_selectors) - set(selectors) != set(): logging.error( f'Не все ключи из user_selectors есть в selectors. Возможна опечатка, проверьте {set(user_selectors)-set(selectors)}' ) selectors.update(user_selectors) if url is not None: # Иногда мы должны сложным путем попасть на страницу - тогда указываем url=None self.page_goto(url) self.page_waitForNavigation() self.sleep(1) for countdown in range(self.wait_loop): if self.page_evaluate(selectors['chk_lk_page_js']): logging.info(f'Already login') break # ВЫХОДИМ ИЗ ЦИКЛА - уже залогинины if self.page_evaluate(selectors['chk_login_page_js']): logging.info(f'Login') self.page_evaluate( selectors['before_login_js'] ) # Если задано какое-то действие перед логином - выполняем self.page_waitForSelector( selectors['login_selector']) # Ожидаем наличия поля логина self.page_evaluate( selectors['login_clear_js']) # очищаем поле логина self.page_type(selectors['login_selector'], self.login, {'delay': 10}) # вводим логин if (self.page_evaluate(selectors['chk_submit_after_login_js'], default=False) ): # Если нужно после логина нажать submit self.page_click( selectors['submit_after_login_selector']) # либо click self.page_evaluate( selectors['submit_after_login_js']) # либо через js self.page_waitForSelector( selectors['password_selector'] ) # и ждем появления поля с паролем self.sleep(1) self.page_evaluate( selectors['password_clear_js']) # очищаем поле пароля self.page_type(selectors['password_selector'], self.password, {'delay': 10}) # вводим пароль if self.page_evaluate( selectors['remember_checker'], default=False ): # Если есть невыставленный check remember me self.page_evaluate( selectors['remember_js']) # выставляем его self.page_click(selectors['remember_selector'], {'delay': 10}) self.sleep(int(selectors['pause_press_submit'])) self.page_click( selectors['submit_selector']) # нажимаем на submit form self.page_evaluate( selectors['submit_js'] ) # либо через js (на некоторых сайтах один из вариантов не срабатывает) self.page_waitForNavigation() # ждем отработки нажатия self.sleep(1) if self.page_evaluate(selectors['chk_lk_page_js']): logging.info(f'Logged on') break # ВЫХОДИМ ИЗ ЦИКЛА - залогинились self.sleep(1) # Проверяем - это не капча ? if self.page_evaluate(selectors['captcha_checker'], False): # Если стоит флаг показывать капчу то включаем видимость хрома и ждем заданное время if str(store.options('show_captcha')) == '1': logging.info('Show captcha') hide_chrome(hide=False, foreground=True) self.page_evaluate(selectors['captcha_focus']) for cnt2 in range( int(store.options('max_wait_captcha'))): _ = cnt2 if not self.page_evaluate( selectors['captcha_checker'], False): break # ВЫХОДИМ ИЗ ЦИКЛА - капчи на странице больше нет self.sleep(1) else: # Капчу так никто и не ввел logging.error( f'Show captcha timeout. A captcha appeared, but no one entered it' ) raise RuntimeError( f'A captcha appeared, but no one entered it') else: # Показ капчи не зададан выдаем ошибку и завершаем logging.error(f'Captcha appeared') raise RuntimeError(f'Captcha appeared') else: # Никуда не попали и это не капча logging.error(f'Unknown state') raise RuntimeError(f'Unknown state') break # ВЫХОДИМ ИЗ ЦИКЛА if countdown == self.wait_and_reload: # так и не дождались - пробуем перезагрузить и еще подождать self.page_reload('Unknown page try reload') self.sleep(1)
def browser_launch(self): hide_chrome_flag = str(store.options( 'show_chrome')) == '0' and store.options('logginglevel') != 'DEBUG' storefolder = store.options('storefolder') user_data_dir = os.path.join(storefolder, 'puppeteer') profile_directory = self.storename chrome_executable_path = store.options('chrome_executable_path') if not os.path.exists(chrome_executable_path): chrome_paths = [ p for p in settings.chrome_executable_path_alternate if os.path.exists(p) ] if len(chrome_paths) == 0: logging.error('Chrome.exe not found') raise RuntimeError(f'Chrome.exe not found') chrome_executable_path = chrome_paths[0] logging.info(f'Launch chrome from {chrome_executable_path}') launch_config = { 'headless': False, 'ignoreHTTPSErrors': True, 'defaultViewport': None, 'handleSIGINT': False, # need for threading (https://stackoverflow.com/questions/53679905) 'handleSIGTERM': False, 'handleSIGHUP': False, # TODO хранить параметр в ini 'executablePath': chrome_executable_path, 'args': [ f"--user-data-dir={os.path.abspath(user_data_dir)}", f"--profile-directory={profile_directory}", '--wm-window-animations-disabled', '--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage', '--disable-accelerated-2d-canvas', '--no-first-run', '--no-zygote', '--log-level=3', # no logging #'--single-process', # <- this one doesn't works in Windows '--disable-gpu', "--window-position=-2000,-2000" if hide_chrome_flag else "--window-position=80,80", "--window-size=800,900" ], } if store.options('proxy_server').strip() != '': launch_config['args'].append( f'--proxy-server={store.options("proxy_server").strip()}') fix_crash_banner(self.storename) self.browser = self.loop.run_until_complete( pyppeteer.launch(launch_config)) if hide_chrome_flag: hide_chrome() pages = self.loop.run_until_complete(self.browser.pages()) for pg in pages[1:]: self.loop.run_until_complete( pg.close()) # Закрываем остальные страницы, если вдруг открыты self.page = pages[0] # await browser.newPage() if self.async_response_worker is not None: self.page.on( "response", self.async_response_worker) # вешаем обработчик на страницы if self.async_disconnected_worker is not None: self.browser.on("disconnected", self.async_disconnected_worker ) # вешаем обработчик закрытие браузера