update: disabled BiDi
This commit is contained in:
142
main.py
142
main.py
@@ -4,25 +4,25 @@ import logging
|
|||||||
import base64
|
import base64
|
||||||
import csv
|
import csv
|
||||||
|
|
||||||
from selenium.common.exceptions import StaleElementReferenceException, TimeoutException
|
from selenium.common.exceptions import TimeoutException, StaleElementReferenceException
|
||||||
from selenium.webdriver.chrome.webdriver import WebDriver
|
from selenium.webdriver.chrome.webdriver import WebDriver
|
||||||
from selenium.webdriver.chrome.options import Options
|
from selenium.webdriver.chrome.options import Options
|
||||||
from selenium.webdriver.common.by import By
|
from selenium.webdriver.common.by import By
|
||||||
from selenium.webdriver.support import expected_conditions as EC
|
|
||||||
from selenium.webdriver.support.wait import WebDriverWait
|
from common import jsonrpc2
|
||||||
from selenium.webdriver.remote.webelement import WebElement
|
from common.utils import *
|
||||||
from selenium.webdriver.common.bidi.network import Request as NetworkRequest
|
|
||||||
|
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
from common import jsonrpc2
|
|
||||||
from typing import Callable
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from urllib3 import PoolManager
|
from urllib3 import PoolManager
|
||||||
|
from itertools import count
|
||||||
from subprocess import Popen
|
from subprocess import Popen
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(description="Opportunity Export")
|
parser = argparse.ArgumentParser(description="Opportunity Export")
|
||||||
parser.add_argument('account', type=str)
|
parser.add_argument('account', type=str)
|
||||||
parser.add_argument('password', type=str)
|
parser.add_argument('password', type=str)
|
||||||
|
parser.add_argument('-p', '--open', action='store_true')
|
||||||
|
parser.add_argument('-d', '--directory', type=str, default=str(Path.home().joinpath('Downloads')))
|
||||||
parser.add_argument('-e', '--encoding', type=str, default="utf-8-sig")
|
parser.add_argument('-e', '--encoding', type=str, default="utf-8-sig")
|
||||||
parser.add_argument('-t', '--timeout', type=int, default=60)
|
parser.add_argument('-t', '--timeout', type=int, default=60)
|
||||||
parser.add_argument('-r', '--attempts', type=int, default=3)
|
parser.add_argument('-r', '--attempts', type=int, default=3)
|
||||||
@@ -35,7 +35,6 @@ APIURL = "https://crm.xiaoman.cn/api/opportunityRead/export"
|
|||||||
def main(driver: WebDriver, logger = logging.getLogger('main')):
|
def main(driver: WebDriver, logger = logging.getLogger('main')):
|
||||||
try:
|
try:
|
||||||
http = PoolManager()
|
http = PoolManager()
|
||||||
context = jsonrpc2.ConnectionContext()
|
|
||||||
parameters = vars(args)
|
parameters = vars(args)
|
||||||
driver.get(str(Path('index.html').resolve()))
|
driver.get(str(Path('index.html').resolve()))
|
||||||
driver.execute_script(jsonrpc2.prelude(), parameters)
|
driver.execute_script(jsonrpc2.prelude(), parameters)
|
||||||
@@ -51,60 +50,8 @@ def main(driver: WebDriver, logger = logging.getLogger('main')):
|
|||||||
logger.warning('Timeout')
|
logger.warning('Timeout')
|
||||||
driver.execute_script("window.stop();")
|
driver.execute_script("window.stop();")
|
||||||
|
|
||||||
def until(condition: Callable[[WebDriver], bool], watch=True):
|
|
||||||
try:
|
|
||||||
WebDriverWait(driver, parameters.get('timeout')).until(condition)
|
|
||||||
except (TimeoutException, StaleElementReferenceException):
|
|
||||||
pass
|
|
||||||
if watch: WebDriverWait(driver, parameters.get('timeout')).until_not(condition)
|
|
||||||
return True
|
|
||||||
|
|
||||||
def sleep(seconds: float):
|
|
||||||
with context:
|
|
||||||
try: WebDriverWait(driver, seconds, seconds).until(lambda _: False)
|
|
||||||
except: pass
|
|
||||||
return True
|
|
||||||
|
|
||||||
def locate(selector, wait=True, condition=EC.visibility_of_element_located) -> WebElement:
|
|
||||||
while True:
|
|
||||||
try:
|
|
||||||
locator = (By.CSS_SELECTOR, selector)
|
|
||||||
if not wait: return driver.find_element(*locator)
|
|
||||||
|
|
||||||
presence = EC.presence_of_element_located(locator)
|
|
||||||
element = WebDriverWait(driver, parameters.get('timeout')).until(presence, 'Timeout')
|
|
||||||
driver.execute_script("arguments[0].scrollIntoView({ block: 'center', inline: 'nearest' });", element)
|
|
||||||
|
|
||||||
if condition is not None:
|
|
||||||
element = WebDriverWait(driver, parameters.get('timeout')).until(condition(locator), 'Timeout')
|
|
||||||
return element
|
|
||||||
except StaleElementReferenceException:
|
|
||||||
continue
|
|
||||||
|
|
||||||
def click(selector: str|WebElement, wait=True, condition=EC.element_to_be_clickable):
|
|
||||||
element = locate(selector, wait, condition) if isinstance(selector, str) else selector
|
|
||||||
counter = lambda: int(element.get_attribute('taximeter') or 0)
|
|
||||||
error = False
|
|
||||||
|
|
||||||
value = counter()
|
|
||||||
driver.execute_script("arguments[0].addEventListener('click', () => arguments[0].setAttribute('taximeter', arguments[1] + 1));", element, value)
|
|
||||||
|
|
||||||
for _ in range(parameters.get('attempts')):
|
|
||||||
try:
|
|
||||||
if not error: element.click()
|
|
||||||
else: driver.execute_script("arguments[0].click();", element)
|
|
||||||
except StaleElementReferenceException:
|
|
||||||
break
|
|
||||||
except:
|
|
||||||
error = True
|
|
||||||
continue
|
|
||||||
try:
|
|
||||||
WebDriverWait(driver, parameters.get('interval')).until(lambda _: counter() > value)
|
|
||||||
break
|
|
||||||
except TimeoutException: continue
|
|
||||||
except: break
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
setup(driver, parameters.get('attempts'), parameters.get('timeout'), 0)
|
||||||
until(lambda x: 'loginProgress' in x.find_element(By.TAG_NAME, "body").get_attribute('class'), watch=False)
|
until(lambda x: 'loginProgress' in x.find_element(By.TAG_NAME, "body").get_attribute('class'), watch=False)
|
||||||
account = str(parameters.get('account'))
|
account = str(parameters.get('account'))
|
||||||
password = str(parameters.get('password'))
|
password = str(parameters.get('password'))
|
||||||
@@ -122,26 +69,34 @@ def main(driver: WebDriver, logger = logging.getLogger('main')):
|
|||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
locate("#container", wait=False)
|
locate("#container", wait=False)
|
||||||
|
sidebar = locate(".new-layout-left")
|
||||||
|
driver.execute_script("arguments[0].remove();", sidebar)
|
||||||
logger.info('Done')
|
logger.info('Done')
|
||||||
break
|
break
|
||||||
except:
|
except:
|
||||||
sleep(3)
|
sleep(3)
|
||||||
|
|
||||||
ready = False
|
while True:
|
||||||
sidebar = locate(".new-layout-left")
|
try:
|
||||||
driver.execute_script("arguments[0].remove();", sidebar)
|
if buttons := driver.find_elements(By.CSS_SELECTOR, ".export-actions .okki-btn-primary"):
|
||||||
|
identity = 'EXPORT'
|
||||||
def handle(request: NetworkRequest):
|
for button in buttons: driver.execute_script("arguments[0].addEventListener('click', () => arguments[0].parentElement.setAttribute(arguments[1], '1'));", button, identity)
|
||||||
nonlocal ready
|
break
|
||||||
if request.url == APIURL: ready = True
|
finally:
|
||||||
try: request.continue_request()
|
sleep(1)
|
||||||
except Exception as e: logger.debug('Network request error', exc_info=e)
|
|
||||||
|
|
||||||
event = 'before_request'
|
|
||||||
driver.network.add_request_handler(event, handle)
|
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
if not ready:
|
try:
|
||||||
|
for index in count(0):
|
||||||
|
if (driver.execute_script("return arguments[0].parentElement.getAttribute(arguments[1]);", buttons[index], identity)):
|
||||||
|
driver.execute_script("arguments[0].parentElement.removeAttribute(arguments[1]);", buttons[index], identity)
|
||||||
|
raise ValueError()
|
||||||
|
except StaleElementReferenceException:
|
||||||
|
logger.critical('Error while listening to button events')
|
||||||
|
return 4
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
except:
|
||||||
sleep(1)
|
sleep(1)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@@ -151,17 +106,14 @@ def main(driver: WebDriver, logger = logging.getLogger('main')):
|
|||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
try:
|
while True:
|
||||||
sleep(1)
|
try:
|
||||||
click(".business-export-wrap section:nth-child(3) h2 svg", wait=False)
|
sleep(1)
|
||||||
cell = locate(".business-export-wrap section:nth-child(3) table tbody tr:first-child td:first-child span", wait=False)
|
click(".business-export-wrap section:nth-child(3) h2 svg", wait=False)
|
||||||
filename = cell.text
|
cell = locate(".business-export-wrap section:nth-child(3) table tbody tr:first-child td:first-child span", wait=False)
|
||||||
except:
|
if (filename := cell.text) != '--': break
|
||||||
continue
|
except:
|
||||||
|
pass
|
||||||
if filename == '--':
|
|
||||||
sleep(1)
|
|
||||||
continue
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
logger.info('New task: %s', filename)
|
logger.info('New task: %s', filename)
|
||||||
@@ -174,14 +126,14 @@ def main(driver: WebDriver, logger = logging.getLogger('main')):
|
|||||||
logger.info('Read %d line(s) total', len(text))
|
logger.info('Read %d line(s) total', len(text))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.critical('Unable to load input data', exc_info=e)
|
logger.critical('Unable to load input data', exc_info=e)
|
||||||
return 4
|
return 5
|
||||||
|
|
||||||
try:
|
try:
|
||||||
file = Path('template.xlsx').resolve()
|
file = Path('template.xlsx').resolve()
|
||||||
template = openpyxl.load_workbook(file)
|
template = openpyxl.load_workbook(file)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.critical('Unable to load template excel', exc_info=e)
|
logger.critical('Unable to load template excel', exc_info=e)
|
||||||
return 5
|
return 6
|
||||||
|
|
||||||
header = next(data, None)
|
header = next(data, None)
|
||||||
source = template.active
|
source = template.active
|
||||||
@@ -206,11 +158,10 @@ def main(driver: WebDriver, logger = logging.getLogger('main')):
|
|||||||
source[coord] = value
|
source[coord] = value
|
||||||
|
|
||||||
sheet = template.copy_worksheet(source)
|
sheet = template.copy_worksheet(source)
|
||||||
logger.info('New sheet: %s', sheet.title)
|
logger.debug('%s', sheet.title)
|
||||||
|
|
||||||
if not len(template.worksheets) > 1:
|
if not len(template.worksheets) > 1:
|
||||||
logger.error('Invalid input')
|
logger.error('Invalid input')
|
||||||
ready = False
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
template.remove(source)
|
template.remove(source)
|
||||||
@@ -234,13 +185,17 @@ def main(driver: WebDriver, logger = logging.getLogger('main')):
|
|||||||
href
|
href
|
||||||
)
|
)
|
||||||
|
|
||||||
try: Popen(['start', (Path.home() / 'Downloads' / filename).as_posix()], shell=True)
|
if parameters.get('open'):
|
||||||
except Exception as e: logger.warning('Unable to open exported excel file at %s' % file, exc_info=e)
|
try:
|
||||||
|
path = Path.home() / 'Downloads' / filename
|
||||||
|
until(lambda _: path.exists(), watch=False)
|
||||||
|
Popen(['start', str(path)], shell=True)
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning('Unable to open exported excel file at %s', str(path), exc_info=e)
|
||||||
|
|
||||||
driver.close()
|
driver.close()
|
||||||
driver.switch_to.window(driver.window_handles[1])
|
driver.switch_to.window(driver.window_handles[1])
|
||||||
logger.info('Done')
|
logger.info('Done')
|
||||||
ready = False
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
try:
|
try:
|
||||||
@@ -258,8 +213,9 @@ if __name__ == '__main__':
|
|||||||
|
|
||||||
logger.info('Creating automation instance')
|
logger.info('Creating automation instance')
|
||||||
opts = Options()
|
opts = Options()
|
||||||
opts.enable_bidi = True
|
|
||||||
opts.enable_downloads = True
|
opts.enable_downloads = True
|
||||||
|
opts.add_argument('--deny-permission-prompts')
|
||||||
|
opts.add_experimental_option('prefs', { 'download.default_directory': args.directory })
|
||||||
driver = WebDriver(options=opts)
|
driver = WebDriver(options=opts)
|
||||||
status = main(driver)
|
status = main(driver)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
BIN
requirements.txt
BIN
requirements.txt
Binary file not shown.
Reference in New Issue
Block a user