diff --git a/requirements.txt b/requirements.txt index 069dd92..486715c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,3 +11,5 @@ websockets==11.0.3 xvfbwrapper==0.2.9; platform_system != "Windows" # only required for windows pefile==2023.2.7; platform_system == "Windows" + +selenium_fetch diff --git a/src/dtos.py b/src/dtos.py index 1e9aace..291ce15 100644 --- a/src/dtos.py +++ b/src/dtos.py @@ -19,6 +19,7 @@ class ChallengeResolutionT: status: str = None message: str = None result: ChallengeResolutionResultT = None + response: str = None def __init__(self, _dict): self.__dict__.update(_dict) @@ -39,6 +40,7 @@ class V1RequestBase(object): # V1Request url: str = None + contentType: str = None postData: str = None returnOnlyCookies: bool = None download: bool = None # deprecated v2.0.0, not used diff --git a/src/flaresolverr_service.py b/src/flaresolverr_service.py index a469bea..216df50 100644 --- a/src/flaresolverr_service.py +++ b/src/flaresolverr_service.py @@ -1,3 +1,4 @@ +import json import logging import platform import sys @@ -9,12 +10,13 @@ from urllib.parse import unquote, quote from func_timeout import FunctionTimedOut, func_timeout from selenium.common import TimeoutException from selenium.webdriver.chrome.webdriver import WebDriver +from selenium.webdriver.common.action_chains import ActionChains from selenium.webdriver.common.by import By from selenium.webdriver.common.keys import Keys from selenium.webdriver.support.expected_conditions import ( presence_of_element_located, staleness_of, title_is) -from selenium.webdriver.common.action_chains import ActionChains from selenium.webdriver.support.wait import WebDriverWait +from selenium_fetch import fetch, Options import utils from dtos import (STATUS_ERROR, STATUS_OK, ChallengeResolutionResultT, @@ -148,6 +150,8 @@ def _cmd_request_get(req: V1RequestBase) -> V1ResponseBase: raise Exception("Request parameter 'url' is mandatory in 'request.get' command.") if req.postData is not None: raise Exception("Cannot use 'postBody' when sending a GET request.") + if req.contentType is not None: + raise Exception("Cannot use 'contentType' when sending a GET request.") if req.returnRawHtml is not None: logging.warning("Request parameter 'returnRawHtml' was removed in FlareSolverr v2.") if req.download is not None: @@ -165,6 +169,8 @@ def _cmd_request_post(req: V1RequestBase) -> V1ResponseBase: # do some validations if req.postData is None: raise Exception("Request parameter 'postData' is mandatory in 'request.post' command.") + if req.contentType is None: + raise Exception("Request parameter 'contentType' is mandatory in 'request.post' command.") if req.returnRawHtml is not None: logging.warning("Request parameter 'returnRawHtml' was removed in FlareSolverr v2.") if req.download is not None: @@ -178,6 +184,23 @@ def _cmd_request_post(req: V1RequestBase) -> V1ResponseBase: return res +def _cmd_request_postJSON(req: V1RequestBase) -> V1ResponseBase: + # do some validations + if req.postData is None: + raise Exception("Request parameter 'postData' is mandatory in 'request.post' command.") + if req.returnRawHtml is not None: + logging.warning("Request parameter 'returnRawHtml' was removed in FlareSolverr v2.") + if req.download is not None: + logging.warning("Request parameter 'download' was removed in FlareSolverr v2.") + + challenge_res = _resolve_challenge(req, 'POSTJSON') + res = V1ResponseBase({}) + res.status = challenge_res.status + res.message = challenge_res.message + res.solution = challenge_res.result + return res + + def _cmd_sessions_create(req: V1RequestBase) -> V1ResponseBase: logging.debug("Creating new session...") @@ -291,7 +314,7 @@ def _evil_logic(req: V1RequestBase, driver: WebDriver, method: str) -> Challenge # navigate to the page logging.debug(f'Navigating to... {req.url}') if method == 'POST': - _post_request(req, driver) + res.response = _post_request(req, driver) else: driver.get(req.url) @@ -303,7 +326,7 @@ def _evil_logic(req: V1RequestBase, driver: WebDriver, method: str) -> Challenge driver.add_cookie(cookie) # reload the page if method == 'POST': - _post_request(req, driver) + res.response = _post_request(req, driver) else: driver.get(req.url) @@ -397,31 +420,42 @@ def _evil_logic(req: V1RequestBase, driver: WebDriver, method: str) -> Challenge def _post_request(req: V1RequestBase, driver: WebDriver): - post_form = f'
' - html_content = f""" - - - - {post_form} - - - """ - driver.get("data:text/html;charset=utf-8,{html_content}".format(html_content=html_content)) + if req.contentType == 'application/x-www-form-urlencoded': + post_form = f'' + html_content = f""" + + + + {post_form} + + + """ + driver.get("data:text/html;charset=utf-8,{html_content}".format(html_content=html_content)) + return "Success" + elif req.contentType == 'application/json': + post_data = json.loads(unquote(req.postData)) + options = Options(method="POST", body=post_data) + logging.debug(f"Request => POST /v1 options: {utils.object_to_dict(options)}") + response = fetch(driver, req.url, options) + logging.debug(f"Response => POST /v1 response: {utils.object_to_dict(response)}") + return response.text + else: + raise Exception(f"Request parameter 'contentType' = '{req.contentType}' is invalid.")