From 2c21b90bac9e0bfbc5ac4810999377f7cb266bc5 Mon Sep 17 00:00:00 2001 From: Artem Lavrenov Date: Wed, 28 Oct 2020 18:50:53 +0300 Subject: [PATCH] Tele2 sms, MTS captcha show, yota chrome, browser chromium support --- changelist.md | 15 ++++++- plugin/httpserver_mobile.py | 2 +- plugin/pyppeteeradd.py | 89 +++++++++++++++++++++++++------------ plugin/settings.py | 9 +++- plugin/yota.py | 58 +++++++----------------- standalone/mbstandalone.bat | 14 +++++- standalone/readme.md | 2 +- 7 files changed, 113 insertions(+), 76 deletions(-) diff --git a/changelist.md b/changelist.md index 043f3f4..fa87bd1 100644 --- a/changelist.md +++ b/changelist.md @@ -117,7 +117,7 @@ avtodor-tr - Автодор транспондер Скорость работы плагинов на хроме увеличилась, у МТС примерно в 2-3 раза. Как показала практика, для плагинов через хром включение логгирования DEBUG приводит к огромному количеству записей от самого движка puppeteer, и ряда библиотек, исправлено. Добавил параметры для разборок плагинов через хром - log_responses=1 - запишет все просмотренные страницы, также не забывайте про show_chrome=1 они дадут достаточно понятную информацию -__Автономная версия__: +__Автономная версия__: Теперь можно запрашивать балансы командой /receivebalance, без параметров - все телефоны, либо после через пробел какие балансы получить (как призапуске из батника mbstandalone.bat GETBALANCE). Естественно такая возможность, как и просмотр баланса доступна только если id вашего телеграма прописан в ini в строчке auth_id В случае если низкий баланс, скорое время отключения либо баланс давно не менялся в боте напротив номера появляется соответстующий коментарий Фильтр получаемых номеров в запросе в автономной версии добавлен Alias. Также можно не писать логин/алиас/оператор а можно его часть @@ -127,5 +127,16 @@ __Автономная версия__: Аналогично mbstandalone.bat init noweb - также не добавляет web сервер в автозапуск. ## mbplugin 0.99.23 (16.10.20) Mts obshchiy_paket, standalone version addition parameter -Плагин mts2 умеет забирать информацию из общего пакета для телефонов указанных в параетре mts_usedbyme, или по всем телефонам если mts_usedbyme=1 +Плагин mts2 умеет забирать информацию из общего пакета для телефонов указанных в параметре mts_usedbyme, или по всем телефонам если mts_usedbyme=1 +Можно показывать остаток по общему пакету МТС, а не по телефону если в поле номера телефона указать 9161234567/common, или общий расход пакета 9161234567/common_rest В автономной версии теперь можно прописывать параметры BalanceNotChangedMoreThen, BalanceChangedLessThen, BalanceLessThen, TurnOffLessThen для каждого телефона (пример см standalone\phones.ini) + +## mbplugin 0.99.24 (28.10.20) Tele2 sms, MTS captcha show, yota chrome, browser chromium support +Добавил в tele2 информацию по SMS (при участии 2350040) +Вынес параметр interUnit (еденицы измерения для инета) в ini. По умолчанию GB. Пока это включено для mts и tele2 +Для плагинов через хром можно задать прокси сервер в формате http://user:pass@12.23.34.56:6789 +Для плагинов через хром добавлена возможность показа хрома в случае появления капчи (при участии comr) +Для плагинов через хром улучшена обработка ошибок при неожиданном закрытии хрома +Очередная попытка починить плагин mosenergosbyt для ряда случаев (при участии dimon_s2020) +Для плагинов через хром исправлена работа с другими браузерами на движке cromium (edge, yandex, brave и т.п., опера хоть и на хромиуме но нормально не заработала) +Плагин yota теперь тоже через хром diff --git a/plugin/httpserver_mobile.py b/plugin/httpserver_mobile.py index 7bca8de..10d8754 100644 --- a/plugin/httpserver_mobile.py +++ b/plugin/httpserver_mobile.py @@ -173,7 +173,7 @@ def getreport(param=[]): mark = ' class="mark" ' # Красим недавно поменялся а не должен был if el is None: el = '' - if he != 'Balance' and (el == 0.0 or el == 0): + if he != 'Balance' and (el == 0.0 or el == 0) and mark == '': el = '' html_line.append(f'<{"th" if he=="NN" else "td"} id="{he}"{mark}>{el}') html_table.append(f'{"".join(html_line)}') diff --git a/plugin/pyppeteeradd.py b/plugin/pyppeteeradd.py index 1165550..781fba2 100644 --- a/plugin/pyppeteeradd.py +++ b/plugin/pyppeteeradd.py @@ -17,8 +17,11 @@ def enumWindowFunc(hwnd, windowList): text = win32gui.GetWindowText(hwnd) className = win32gui.GetClassName(hwnd) _, pid = win32process.GetWindowThreadProcessId(hwnd) - if text.find("Chrome")>=0 and 'remote-debugging-port' in ''.join(psutil.Process(pid).cmdline()): - windowList.append((hwnd, text, className)) + try: # ??? text.lower().find('chrome')>=0 + if text != '' and 'remote-debugging-port' in ''.join(psutil.Process(pid).cmdline()): + windowList.append((hwnd, text, className)) + except Exception: + pass myWindows = [] # enumerate thru all top windows and get windows which are ours win32gui.EnumWindows(enumWindowFunc, myWindows) @@ -30,7 +33,7 @@ def enumWindowFunc(hwnd, windowList): else: win32gui.MoveWindow(hwnd, 0, 0, 1000, 1000, True) # Возвращаем нормальные координаты -async def launch_browser(storename, response_worker=None): +async def launch_browser(storename, response_worker=None, disconnected_worker=None): 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') @@ -77,14 +80,18 @@ async def launch_browser(storename, response_worker=None): await page.close() # Закрываем остальные страницы, если вдруг открыты page = pages[0] # await browser.newPage() if response_worker is not None: - page.on("response", response_worker) # вешаем обработчик на страницы + page.on("response", response_worker) # вешаем обработчик на страницы + if disconnected_worker is not None: + browser.on("disconnected", disconnected_worker) # вешаем обработчик закрытие браузера return browser, page 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()=='chrome.exe' and 'remote-debugging-port' in ''.join(p.cmdline()): + if p.name().lower()==pname and 'remote-debugging-port' in ''.join(p.cmdline()): p.kill() except Exception: pass @@ -162,7 +169,19 @@ async def do_waitfor(page, waitfor, tokens, wait_and_reload=10, wait_loop=30): class balance_over_puppeteer(): '''Основная часть общих действий вынесена сюда см mosenergosbyt для примера использования ''' + + def check_browser_opened_decorator(func): # pylint: disable=no-self-argument + async def wrapper(self, *args, **kwargs): + if self.browser_open: + res = await func(self, *args, **kwargs) # pylint: disable=not-callable + return res + else: + logging.error(f'Browser was not open') + raise RuntimeError(f'Browser was not open') + return wrapper + def __init__(self, login, password, storename=None, wait_loop=30, wait_and_reload=10, login_attempt=1): + self.browser_open = True # флаг что браузер рабобтает self.wait_loop = wait_loop # TODO подобрать параметр self.login_attempt = login_attempt self.wait_and_reload = wait_and_reload @@ -178,19 +197,49 @@ def __init__(self, login, password, storename=None, wait_loop=30, wait_and_relo self.result = {} self.responses = {} + async def response_worker(self, response): + 'Response Worker вызывается на каждый url который открывается при загрузке страницы (т.е. список тот же что на вкладке сеть в хроме)' + 'Проходящие запросы, которые json сохраняем в responses' + if response.status == 200: + try: + data = await response.json() # Берем только json + except Exception: + return + try: + post = '' + if response.request.method == 'POST' and response.request.postData is not None: + post = response.request.postData + self.responses[f'{response.request.method}:{post} URL:{response.request.url}$'] = data + # TODO Сделать какой-нибудь механизм для поиска по загруженным страницам + # txt = await response.text() + # if '2336' in txt: + # logging.info(f'2336 in {response.request.url}') + except: + exception_text = f'Ошибка: {"".join(traceback.format_exception(*sys.exc_info()))}' + logging.debug(exception_text) + + async def disconnected_worker(self): + 'disconnected_worker вызывается когда закрыли браузер' + logging.info(f'Browser was closed') + self.browser_open = False # выставляем флаг + # потом наверно перенесем их совсем сюда, а отдельные прибьем + @check_browser_opened_decorator async def page_evaluate(self, eval_string, default=None): ''' переносим вызов в класс для того чтобы каждый раз не указывать page''' return (await page_evaluate(self.page, eval_string, default=None)) + @check_browser_opened_decorator async def page_goto(self, url): ''' переносим вызов в класс для того чтобы каждый раз не указывать page''' return (await page_goto(self.page, url)) + @check_browser_opened_decorator async def page_reload(self, reason=''): ''' переносим вызов в класс для того чтобы каждый раз не указывать page''' return (await page_reload(self.page, reason='')) + @check_browser_opened_decorator async def page_type(self, selector, text, *args, **kwargs): 'Безопасный type - не падает при ошибке а возвращает None' try: @@ -200,6 +249,7 @@ async def page_type(self, selector, text, *args, **kwargs): except Exception: logging.info(f'page.type fail: {repr(selector)}') + @check_browser_opened_decorator async def page_click(self, selector, *args, **kwargs): 'Безопасный click - не падает при ошибке а возвращает None' try: @@ -209,6 +259,7 @@ async def page_click(self, selector, *args, **kwargs): except Exception: logging.info(f'page.click fail: {repr(selector)}') + @check_browser_opened_decorator async def page_waitForNavigation(self, *args, **kwargs): 'Безопасный waitForNavigation - не падает при ошибке а возвращает None' try: @@ -220,6 +271,7 @@ async def page_waitForNavigation(self, *args, **kwargs): logging.info(f'page.waitForNavigation timeout') # !!! TODO есть page.waitForSelector - покопать в эту сторону + @check_browser_opened_decorator async def page_waitForSelector(self, selector, *args, **kwargs): 'Безопасный waitForSelector - не падает при ошибке а возвращает None' try: @@ -230,27 +282,7 @@ async def page_waitForSelector(self, selector, *args, **kwargs): logging.info(f'page.waitForSelector fail: {repr(selector)}') return None - async def worker(self, response): - 'Worker вызывается на каждый url который открывается при загрузке страницы (т.е. список тот же что на вкладке сеть в хроме)' - 'Проходящие запросы, которые json сохраняем в responses' - if response.status == 200: - try: - data = await response.json() # Берем только json - except Exception: - return - try: - post = '' - if response.request.method == 'POST' and response.request.postData is not None: - post = response.request.postData - self.responses[f'{response.request.method}:{post} URL:{response.request.url}$'] = data - # TODO Сделать какой-нибудь механизм для поиска по загруженным страницам - # txt = await response.text() - # if '2336' in txt: - # logging.info(f'2336 in {response.request.url}') - except: - exception_text = f'Ошибка: {"".join(traceback.format_exception(*sys.exc_info()))}' - logging.debug(exception_text) - + @check_browser_opened_decorator async def do_logon(self, url,user_selectors={}): 'Делаем заход в личный кабинет/ проверяем не залогинены ли уже' 'На вход передаем словарь селекторов и скриптов который перекроет действия по умолчанию' @@ -337,6 +369,7 @@ async def do_logon(self, url,user_selectors={}): logging.error(f'Unknown state') raise RuntimeError(f'Unknown state') + @check_browser_opened_decorator async def wait_params(self, params, url='', save_to_result=True): ''' Переходим по url и ждем параметры --- @@ -409,7 +442,7 @@ async def wait_params(self, params, url='', save_to_result=True): return result async def _async_main(self): - self.browser, self.page = await launch_browser(self.storename, self.worker) + self.browser, self.page = await launch_browser(self.storename, response_worker=self.response_worker, disconnected_worker=self.disconnected_worker) await self.async_main() # !!! CALL async_main logging.debug(f'Data ready {self.result.keys()}') if str(store.options('log_responses')) == '1' or store.options('logginglevel') == 'DEBUG': diff --git a/plugin/settings.py b/plugin/settings.py index e264cfe..855076a 100644 --- a/plugin/settings.py +++ b/plugin/settings.py @@ -3,6 +3,7 @@ Значения по умолчанию, здесь ничего не меняем, если хотим поменять меняем в mbplugin.ini подробное описание см в readme.md ''' +import os UNIT = {'TB': 1073741824, 'ТБ': 1073741824, 'TByte': 1073741824, 'GB': 1048576, 'ГБ': 1048576, 'GByte': 1048576, 'MB': 1024, 'МБ': 1024, 'MByte': 1024, @@ -15,7 +16,13 @@ # сюда пропишем сразу возможные варианты для путя хрома chrome_executable_path_alternate = [ 'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe', - 'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe',] + 'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe', + 'C:\\Program Files\\Microsoft\\Edge\\Application\\msedge.exe', + 'C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe', + f'{os.environ.get("LOCALAPPDATA","")}\\Yandex\\YandexBrowser\\Application\\browser.exe', + 'C:\\Program Files\\BraveSoftware\\Brave-Browser\\Application\\brave.exe', + 'C:\\Program Files (x86)\\BraveSoftware\\Brave-Browser\\Application\\brave.exe', + ] ######################################################################################## ini = { 'Options': { # Раздел mbplugin.ini [Options] diff --git a/plugin/yota.py b/plugin/yota.py index 8946494..a58784a 100644 --- a/plugin/yota.py +++ b/plugin/yota.py @@ -1,51 +1,27 @@ # -*- coding: utf8 -*- ''' Автор ArtyLa ''' -import os, sys, re, logging -import requests -import store +import pyppeteeradd as pa icon = '789C9593CB2B44511CC7BF1E49918429C98285A2C9D6C64E91CD142B0B0BB3B555FC0962E71FC0828D479E83A99944C32499143316081393CC9DFB987BE7DE3BF7EDB81E79D4BDD7AF3E9BF3EB7BCEF77C7FE7F404BACA615717A19D50FB41095ADE1B1FFDEF6559D67FF95DF6BAF2B209434EC3327528540C1A7F65AFAB4C0C3A7FEEA857A52CE82D1FE4D40498CB29306B656036AAA07209E4F63A209C041CF59A4C830DF9205D8C221B1B06BB061B29BD0866B70585D34117FF0639C70F2ED281E27318B9703BF8E33E889910E8F54A4857E3AEF7E76FE76CDFF9836E141F1720DECC800E3581DDAE87215EBBEA4D53433E354D7268F8F2CF45FC24D7A8A7FC3F51F93B6496ABC11DF6C2D404CFF3FB4457043C2ED5803D1AF8D7FCDF30141A7C7212D46A05C9BD15C5A715F21E544F7A5548931974920C4B918FB681DD6904BB5E06E12C08CB505CF5ECC908793395901F66611A05E285021D0FDA7B28996547BDAEE6EDF3C4F3E00FAF862E81D96986981872D42B8567305B759093637FF2CAEDB6818FF73BFB277F46BE9F87C626FEE8A5F42A346ADFF3FC3CF0A35E019A97F6FE' -# Строка для поиска баланса на странице -re_balance = r'(?usi)id\W+balance-holder\W+span\W*(\d+,?\d*)\W*span' -# Строка для поиска тарифа -re_tariff = r'(?usi)status-work.*?>.*?>.*?>(.*?)<' +class yota_over_puppeteer(pa.balance_over_puppeteer): + async def async_main(self): + await self.do_logon( + url='https://my.yota.ru/selfcare/devices', + user_selectors={'chk_lk_page_js': "document.querySelector('form input[type=password]') == null", + 'chk_login_page_js': "document.querySelector('form input[type=password]') !== null", + 'login_clear_js': "document.querySelector('form input[name=phoneNumber]').value=''", + 'login_selector': 'form input[name=phoneNumber]', + }) + # Здесь мы берем данные с загружаемой страницы + await self.wait_params(params=[{ + 'name': 'Balance', + 'jsformula': r"parseFloat(document.querySelector('dd[id=balance-holder]').innerText.split('\n')[0].replace(',','.'))", + }]) def get_balance(login, password, storename=None): - logging.info(f'start get_balance {login}') - result = {} - url_post = 'https://login.yota.ru/UI/Login' - url_balance = 'https://my.yota.ru/selfcare/devices' - session = store.Session(storename) - response2 = session.get(url_balance) - if re.search(re_balance, response2.text): - logging.info(f'Already logoned {login}') - else: - # Логинимся - logging.info(f'relogon {login}') - session.drop_and_create() - data = {'IDToken1': login, - 'IDToken2': password, - 'goto': 'https://my.yota.ru:443/selfcare/loginSuccess', - 'gotoOnFail': '=https://my.yota.ru:443/selfcare/loginError', - 'org': 'customer', - 'ForceAuth': 'true', - 'login': login, - 'password': password, } - response1 = session.post(url_post, data=data) - if response1.status_code != 200: - raise RuntimeError(f'POST Login page {url_post} error: status_code {response1.status_code}') - response2 = session.get(url_balance) - - result['Balance'] = float(re.search(re_balance, response2.text).group(1).replace(',', '.').strip()) - #try: - # result['TarifPlan'] = re.search(re_tariff, response2.text).group(1).replace(' ', '').strip() - #except Exception: - # logging.info(f'Not found TarifPlan') - - session.save_session() - return result - + ''' На вход логин и пароль, на выходе словарь с результатами ''' + return yota_over_puppeteer(login, password, storename).main() if __name__ == '__main__': print('This is module yota') diff --git a/standalone/mbstandalone.bat b/standalone/mbstandalone.bat index aace2da..e6f96d5 100644 --- a/standalone/mbstandalone.bat +++ b/standalone/mbstandalone.bat @@ -16,9 +16,12 @@ if "%1"=="check" goto :CHECK if "%1"=="getbalance" goto :GETBALANCE +if "%1"=="updatehtml" goto :UPDATEHTML + GOTO :EOF -@REM 樠 ஬ ࠬ஬ 㪠 noweb ⮣ ࢥ 㤥 ᪠ ⮧ + +@REM 樠 :INIT cd mbplugin\plugin cd ..\plugin @@ -26,7 +29,7 @@ cd ..\plugin ..\python\python -c "import store;ini=store.ini();ini.read();ini.ini['Options']['createhtmlreport']='1';ini.write()" ..\python\python -c "import store,os;ini=store.ini();ini.read();ini.ini['Options']['balance_html']=os.path.abspath('..\\..\\balance.html');ini.write()" echo %CD% -call ..\setup_and_check.bat %2 +call ..\setup_and_check.bat %2 %3 GOTO :EOF @REM ஢ઠ INI ४⭮ @@ -46,6 +49,13 @@ cd ..\plugin ..\python\python.exe -c "import httpserver_mobile,sys;httpserver_mobile.detbalance_standalone(filter=sys.argv[2:])" %* GOTO :EOF +@REM balance.html +:UPDATEHTML +cd mbplugin\plugin +cd ..\plugin +..\python\python.exe -c "import httpserver_mobile;httpserver_mobile.write_report()" %* +GOTO :EOF + :ERROR1 ECHO 䠩 Mobilebalance.exe ECHO 䠩 Phones.ini diff --git a/standalone/readme.md b/standalone/readme.md index 6027e33..3a19f86 100644 --- a/standalone/readme.md +++ b/standalone/readme.md @@ -15,7 +15,7 @@ 6. Для проверки корректности ini файлов запустить ```mbstandalone.bat check``` диагностика пока так себе, со временем будем в эту сторону копать 7. Для получения балансов ```mbstandalone.bat getbalance``` 8. Если нужно получить по части балансов запустите ```mbstandalone.bat getbalance filter1 filter2 ...``` будут получены только балансы у которых один из фильтров совпадет с плагином или логином -9. Для настройки автоматической проверки баланса необходимо по какому-то расписанию запускать ```mbstandalone.bat chechbalance``` например через системный планировщик ```taskschd.msc```либо как-то еще +9. Для настройки автоматической проверки баланса необходимо по какому-то расписанию запускать ```mbstandalone.bat getbalance``` например через системный планировщик ```taskschd.msc```либо как-то еще ## Для инфо