diff --git a/index.html b/index.html
index 486ea89..6b23a51 100644
--- a/index.html
+++ b/index.html
@@ -5,30 +5,23 @@
diff --git a/main.py b/main.py
index 178572a..491b60d 100644
--- a/main.py
+++ b/main.py
@@ -2,12 +2,8 @@ import argparse
import openpyxl
import logging
import base64
-import json
import csv
-import trio
-import trio_websocket as ws
-
from selenium.common.exceptions import StaleElementReferenceException, TimeoutException
from selenium.webdriver.chrome.webdriver import WebDriver
from selenium.webdriver.chrome.options import Options
@@ -18,95 +14,31 @@ from selenium.webdriver.remote.webelement import WebElement
from selenium.webdriver.common.bidi.network import Request as NetworkRequest
from io import BytesIO
-from enum import Enum
-from typing import Self, Callable
+from common import jsonrpc2
+from typing import Callable
from pathlib import Path
from urllib3 import PoolManager
-from threading import Thread
+from subprocess import Popen
-parser = argparse.ArgumentParser(description="Opportunity Exporter")
+parser = argparse.ArgumentParser(description="Opportunity Export")
parser.add_argument('account', type=str)
parser.add_argument('password', type=str)
parser.add_argument('-e', '--encoding', type=str, default="utf-8-sig")
parser.add_argument('-t', '--timeout', type=int, default=60)
parser.add_argument('-r', '--attempts', type=int, default=3)
parser.add_argument('-l', '--log-level', choices=['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'])
+args = parser.parse_args()
WEBURL = "https://crm.xiaoman.cn/business/export"
APIURL = "https://crm.xiaoman.cn/api/opportunityRead/export"
-args = parser.parse_args()
-handlers = {}
-connection, server = None, None
-
-class Request[T]:
- def __init__(self, method: str, params: T):
- if not isinstance(method, str): raise TypeError()
- self.method = method
- self.params = params
-
- @classmethod
- def load(cls, raw: bytes) -> Self:
- data = json.loads(raw)
- return cls(**data)
-
-class Response[T]:
- def __init__(self, result: T):
- self.result = result
-
- def __str__(self):
- data = { 'result': self.result }
- return json.dumps(data)
-
-class Error(Enum):
- PARSE_ERROR = -200
- INVALID_REQUEST = -300
- METHOD_NOT_FOUND = -400
- INTERNAL_ERROR = -500
-
- def __str__(self):
- data = { 'error': self.value }
- return json.dumps(data)
-
-class History(logging.Handler):
- def __init__(self):
- super().__init__()
- self.records = []
-
- def emit(self, record):
- self.records.append(record)
-
- def truncate(self) -> list:
- copy = self.records.copy()
- self.records.clear()
- return copy
-
-class ConnectionContext:
- def __enter__(self):
- self.healthcheck()
- return self
-
- def __exit__(self, exc_type, exc, tb):
- self.healthcheck()
- return True
-
- class Error(Exception):
- pass
-
- @classmethod
- def healthcheck(cls):
- if connection.closed is not None:
- raise cls.Error()
-
def main(driver: WebDriver, logger = logging.getLogger('main')):
try:
http = PoolManager()
- context = ConnectionContext()
-
- driver.get(str(Path('index.html').resolve()))
- endpoint = server.listeners[0]
+ context = jsonrpc2.ConnectionContext()
parameters = vars(args)
- driver.execute_script(f"main(...arguments);", f'ws://{endpoint.address}:{endpoint.port}', parameters)
+ driver.get(str(Path('index.html').resolve()))
+ driver.execute_script(jsonrpc2.prelude(), parameters)
except Exception as e:
logger.critical('Unable to load starup page', exc_info=e)
return 2
@@ -214,7 +146,7 @@ def main(driver: WebDriver, logger = logging.getLogger('main')):
continue
try:
- locate(".mm-modal-content .mm-modal-body input", wait=False).send_keys(parameters.get('password'))
+ locate(".safe-verify-dialog:not([style*='display: none']) .mm-modal-content .mm-modal-body input", wait=False).send_keys(parameters.get('password'))
click(".mm-modal-footer button.okki-btn-primary", wait=False)
except:
pass
@@ -288,6 +220,7 @@ def main(driver: WebDriver, logger = logging.getLogger('main')):
text = data.decode('ascii')
mime = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
href = f"data:{mime};base64,{text}"
+ filename = filename.replace('.csv', '.xlsx')
driver.switch_to.new_window('tab')
driver.execute_script(
@@ -297,71 +230,26 @@ def main(driver: WebDriver, logger = logging.getLogger('main')):
link.href = arguments[1];
link.click();
""",
- filename.replace('.csv', '.xlsx'),
+ filename,
href
)
+ try: Popen(['start', (Path.home() / 'Downloads' / filename).as_posix()], shell=True)
+ except Exception as e: logger.warning('Unable to open exported excel file at %s' % file, exc_info=e)
+
driver.close()
driver.switch_to.window(driver.window_handles[1])
logger.info('Done')
ready = False
-async def handler(request: ws.WebSocketRequest, logger = logging.getLogger('websocket')):
- global connection
- websocket = await request.accept()
-
- if connection is None:
- connection = websocket
- else:
- await websocket.aclose(code=1000, reason="Non-singular connection prohibited")
- return
-
- while True:
- try:
- message = await connection.get_message()
- inbound = Request.load(message)
-
- handler = handlers[inbound.method]
- results = handler(inbound.params)
- response = Response(results)
- except json.decoder.JSONDecodeError:
- logger.error('Parse error')
- response = Error.PARSE_ERROR
- except TypeError:
- logger.error('Invalid request')
- response = Error.INVALID_REQUEST
- except KeyError:
- logger.error('Method not found: `%s`', inbound.method)
- response = Error.METHOD_NOT_FOUND
- except Exception as e:
- logger.error('Internal error', exc_info=e)
- response = Error.INTERNAL_ERROR
-
- await connection.send_message(str(response))
-
-async def backend(listen='127.0.0.1', port=0):
- import _thread as t
- global server
-
- listeners = await trio.open_tcp_listeners(port, host=listen)
- server = ws.WebSocketServer(handler, listeners, max_message_size=125_000_000)
- await server.run()
- t.interrupt_main()
-
if __name__ == '__main__':
try:
logging.basicConfig(level=logging.INFO, format="[%(asctime)s] [%(levelname)s] [%(name)s] %(message)s", datefmt="%Y-%m-%d %H:%M")
logger = logging.getLogger()
- formatter = logger.handlers[0].formatter
- history = History()
- logger.addHandler(history)
+ logger.info('Initializing...')
level = logging.getLevelNamesMapping().get(args.log_level, 'INFO')
logger.setLevel(level)
-
- logger.info('Initializing...')
- handlers.setdefault('sync', lambda _: list(map(lambda x: formatter.format(x), history.truncate())))
- thread = Thread(target=lambda: trio.run(backend), daemon=True)
- thread.start()
+ jsonrpc2.run(logger)
logger.info('Creating automation instance')
opts = Options()
diff --git a/requirements.txt b/requirements.txt
index 029c619..9925375 100644
Binary files a/requirements.txt and b/requirements.txt differ