mirror of
https://github.com/calibrain/shelfmark.git
synced 2026-10-04 11:11:12 +01:00
The Calibre dependency was due to the script testing for validity of the downloaded file, as often they would be corrupted from aa. But CWA is already doing that, so we are just having redundant code here. For the cloudflarebypasser, I basically run my own version now, instead of depending on an external library, this way we have better control for debugging and on the docker image. Fixes #18, #33, #27, #48, #65, #78, #86, #88, #89 --------- Co-authored-by: mik593 <91991279+mik593@users.noreply.github.com>
177 lines
5.9 KiB
Python
177 lines
5.9 KiB
Python
import time, os
|
|
from DrissionPage import ChromiumPage # type: ignore
|
|
from DrissionPage import ChromiumOptions
|
|
from DrissionPage._functions.elements import ChromiumElementsList # type: ignore
|
|
from DrissionPage._pages.chromium_tab import ChromiumTab # type: ignore
|
|
from logger import setup_logger
|
|
from config import MAX_RETRY, DOCKERMODE, DEFAULT_SLEEP, PROXIES
|
|
|
|
logger = setup_logger(__name__)
|
|
|
|
def _search_recursively_shadow_root_with_iframe(ele : ChromiumElementsList) -> ChromiumElementsList | None:
|
|
if ele.shadow_root:
|
|
if ele.shadow_root.child().tag == "iframe":
|
|
return ele.shadow_root.child()
|
|
else:
|
|
for child in ele.children():
|
|
result = _search_recursively_shadow_root_with_iframe(child)
|
|
if result:
|
|
return result
|
|
return None
|
|
|
|
def _search_recursively_shadow_root_with_cf_input(ele : ChromiumElementsList) -> ChromiumElementsList | None:
|
|
if ele.shadow_root:
|
|
if ele.shadow_root.ele("tag:input"):
|
|
return ele.shadow_root.ele("tag:input")
|
|
else:
|
|
for child in ele.children():
|
|
result = _search_recursively_shadow_root_with_cf_input(child)
|
|
if result:
|
|
return result
|
|
return None
|
|
|
|
def _locate_cf_button(driver : ChromiumTab) -> ChromiumElementsList | None:
|
|
button : ChromiumElementsList = None
|
|
eles = driver.eles("tag:input")
|
|
for ele in eles:
|
|
if "name" in ele.attrs.keys() and "type" in ele.attrs.keys():
|
|
if "turnstile" in ele.attrs["name"] and ele.attrs["type"] == "hidden":
|
|
button = ele.parent().shadow_root.child()("tag:body").shadow_root("tag:input")
|
|
break
|
|
|
|
if button:
|
|
return button
|
|
else:
|
|
# If the button is not found, search it recursively
|
|
logger.debug("Basic search failed. Searching for button recursively.")
|
|
ele = driver.ele("tag:body")
|
|
iframe = _search_recursively_shadow_root_with_iframe(ele)
|
|
if iframe:
|
|
button = _search_recursively_shadow_root_with_cf_input(iframe("tag:body"))
|
|
else:
|
|
logger.debug("Iframe not found. Button search failed.")
|
|
return button
|
|
|
|
def _click_verification_button(driver: ChromiumTab) -> None:
|
|
try:
|
|
button = _locate_cf_button(driver)
|
|
if button:
|
|
logger.debug("Verification button found. Attempting to click.")
|
|
button.click()
|
|
else:
|
|
logger.debug("Verification button not found.")
|
|
|
|
except Exception as e:
|
|
logger.debug(f"Error clicking verification button: {e}")
|
|
|
|
def _is_bypassed(driver: ChromiumTab) -> bool:
|
|
try:
|
|
title = driver.title.lower()
|
|
body = driver.ele("tag:body").text.lower()
|
|
# TODO check body
|
|
return "just a moment" not in title
|
|
except Exception as e:
|
|
logger.debug(f"Error checking page title: {e}")
|
|
return False
|
|
|
|
def _bypass(driver: ChromiumTab, max_retries: int = MAX_RETRY) -> None:
|
|
try_count = 0
|
|
|
|
while not _is_bypassed(driver):
|
|
logger.info(f"Starting Cloudflare bypass... Retry: {try_count + 1} / {max_retries}")
|
|
if try_count >= max_retries:
|
|
logger.warning("Exceeded maximum retries. Bypass failed.")
|
|
break
|
|
|
|
logger.info(f"Attempt {try_count + 1}: Verification page detected. Trying to bypass...")
|
|
_click_verification_button(driver)
|
|
|
|
try_count += 1
|
|
time.sleep(DEFAULT_SLEEP)
|
|
|
|
if _is_bypassed(driver):
|
|
logger.info("Bypass successful.")
|
|
else:
|
|
logger.info("Bypass failed.")
|
|
|
|
def _get_chromium_options(arguments: list[str]) -> ChromiumOptions:
|
|
options = ChromiumOptions()
|
|
for argument in arguments:
|
|
options.set_argument(argument)
|
|
|
|
# Add proxy settings if configured
|
|
if PROXIES:
|
|
if 'http' in PROXIES:
|
|
options.set_argument(f'--proxy-server={PROXIES["http"]}')
|
|
logger.debug(f"Setting HTTP proxy: {PROXIES['http']}")
|
|
elif 'https' in PROXIES:
|
|
options.set_argument(f'--proxy-server={PROXIES["https"]}')
|
|
logger.debug(f"Setting HTTPS proxy: {PROXIES['https']}")
|
|
|
|
return options
|
|
|
|
def _genScraper() -> ChromiumPage:
|
|
arguments = [
|
|
"-no-first-run",
|
|
"-force-color-profile=srgb",
|
|
"-metrics-recording-only",
|
|
"-password-store=basic",
|
|
"-use-mock-keychain",
|
|
"-export-tagged-pdf",
|
|
"-no-default-browser-check",
|
|
"-disable-background-mode",
|
|
"-enable-features=NetworkService,NetworkServiceInProcess,LoadCryptoTokenExtension,PermuteTLSExtensions",
|
|
"-disable-features=FlashDeprecationWarning,EnablePasswordsAccountStorage",
|
|
"-deny-permission-prompts",
|
|
"-disable-gpu",
|
|
"-no-sandbox",
|
|
"-accept-lang=en-US",
|
|
"-remote-debugging-port=9222"
|
|
]
|
|
|
|
options = _get_chromium_options(arguments)
|
|
# Initialize the browser
|
|
driver = ChromiumPage(addr_or_opts=options)
|
|
return driver
|
|
|
|
_defaultTab = None
|
|
|
|
def _reset_browser() -> None:
|
|
if not DOCKERMODE:
|
|
return
|
|
global _defaultTab
|
|
# Kill the browser
|
|
if _defaultTab:
|
|
_defaultTab.close()
|
|
_defaultTab = None
|
|
# Force kill the browser
|
|
os.system("pkill -f -i *chrom*")
|
|
os.system("pkill -f -i xvfb")
|
|
time.sleep(1)
|
|
|
|
def _init_browser(retry : int = MAX_RETRY) -> ChromiumTab:
|
|
global _defaultTab
|
|
if _defaultTab:
|
|
return _defaultTab
|
|
else:
|
|
try:
|
|
driver = _genScraper()
|
|
_defaultTab = driver.get_tabs()[0]
|
|
except Exception as e:
|
|
if retry > 0:
|
|
_reset_browser()
|
|
else:
|
|
raise e
|
|
return _init_browser(retry - 1)
|
|
|
|
def get(url : str, retry : int = MAX_RETRY) -> ChromiumTab:
|
|
defaultTab = _init_browser()
|
|
defaultTab.get(url)
|
|
try:
|
|
_bypass(defaultTab)
|
|
except Exception as e:
|
|
if retry > 0:
|
|
return get(url, retry - 1)
|
|
raise e
|
|
return defaultTab
|