From 998290621b704b2130f77943603c947c54999967 Mon Sep 17 00:00:00 2001 From: Santiago Ramirez Date: Wed, 24 Jul 2024 17:37:54 -0400 Subject: [PATCH] feat: ssid refresher with crf bypass --- quotexpy/api.py | 11 ++++++++- quotexpy/http/logout.py | 21 +++++++---------- quotexpy/http/navigator.py | 48 ++++++++++++-------------------------- quotexpy/http/qxbroker.py | 10 ++++---- quotexpy/http/refresh.py | 27 +++++++++++++++++++++ requirements-dev.txt | 2 +- requirements.txt | 13 +++++------ 7 files changed, 72 insertions(+), 60 deletions(-) create mode 100644 quotexpy/http/refresh.py diff --git a/quotexpy/api.py b/quotexpy/api.py index 9c6e00b..efbba4f 100644 --- a/quotexpy/api.py +++ b/quotexpy/api.py @@ -14,6 +14,7 @@ from quotexpy.http.login import Login from quotexpy.http.logout import Logout +from quotexpy.http.refresh import Refresh from quotexpy.ws.channels.ssid import Ssid from quotexpy.ws.channels.trade import Trade from quotexpy.exceptions import QuotexTimeout @@ -109,6 +110,14 @@ def websocket(self): """ return self.websocket_client.wss + @property + def refresh(self): + """Property for Quotex http refresh ssid resource. + :returns: The instance of :class:`Refresh + `. + """ + return Refresh(self) + @property def logout(self): """Property for get Quotex http login resource. @@ -163,6 +172,7 @@ async def connect(self) -> bool: if not self.SSID: self.SSID, self.cookies = await self.get_ssid() check_websocket = self.start_websocket() + print(self.refresh()) return check_websocket async def get_ssid(self) -> typing.Tuple[str, str]: @@ -193,7 +203,6 @@ def subscribe_realtime_candle(self, asset: str, period: int) -> None: payload = {"asset": asset, "period": period} data = f'42["instruments/update", {json.dumps(payload)}]' self.send_websocket_request(data) - payload = {"asset": asset, "period": period} data = f'42["depth/follow", "{asset}"]' self.send_websocket_request(data) payload = {"asset": asset, "version": "1.0.0"} diff --git a/quotexpy/http/logout.py b/quotexpy/http/logout.py index cdf21fc..19c2f8d 100644 --- a/quotexpy/http/logout.py +++ b/quotexpy/http/logout.py @@ -1,19 +1,16 @@ -"""Module for Quotex http login resource.""" +"""Module for Quotex http logout resource.""" from quotexpy.http.navigator import Navigator class Logout(Navigator): - """Class for Quotex login resource.""" - - base_url = "qxbroker.com" - https_base_url = f"https://{base_url}" - - def _post(self, data=None, headers=None): - """Send get request for Quotex API login http resource. - :returns: The instance of :class:`navigator.Session`. - """ - return self.send_request(method="POST", url=f"{self.https_base_url}/logout", data=data, headers=headers) + """Class for Quotex logout resource.""" def __call__(self): - return self._post() + response = self.send_request( + method="GET", + url=f"{self.https_base_url}/logout", + headers={"User-Agent": self.api.user_agent, "Cookie": self.api.cookies}, + ) + + return response diff --git a/quotexpy/http/navigator.py b/quotexpy/http/navigator.py index 416894e..f4eb526 100644 --- a/quotexpy/http/navigator.py +++ b/quotexpy/http/navigator.py @@ -1,46 +1,28 @@ -import random import requests -from bs4 import BeautifulSoup from urllib3.util.retry import Retry from requests.adapters import HTTPAdapter -from quotexpy.http.user_agents import agents - -retry_strategy = Retry( - total=3, - backoff_factor=1, - status_forcelist=[429, 500, 502, 503, 504, 104], - allowed_methods=["HEAD", "POST", "PUT", "GET", "OPTIONS"], -) - -adapter = HTTPAdapter(max_retries=retry_strategy) -user_agent_list = agents.split("\n") - class Navigator(object): def __init__(self, api): - """ - Tools for quotexpy navigation. - :param object api: The instance of :class:`quotexpy.api.QuotexAPI`. - """ + """Tools for quotexpy navigation.""" + self.base_url = "qxbroker.com" + self.https_base_url = f"https://{self.base_url}" + self.api = api - self.response = None - self.headers = self.get_headers() self.session = requests.Session() - self.api.user_agent = self.headers["User-Agent"] - self.session.mount("http://", adapter) - self.session.mount("https://", adapter) - def get_headers(self): - self.headers = { - "User-Agent": user_agent_list[random.randint(0, len(user_agent_list) - 1)], - } + retry_strategy = Retry( + total=3, + backoff_factor=1, + status_forcelist=[429, 500, 502, 503, 504, 104], + allowed_methods=["HEAD", "POST", "PUT", "GET", "OPTIONS"], + ) - return self.headers + adapter = HTTPAdapter(max_retries=retry_strategy) - def get_soup(self): - return BeautifulSoup(self.response.content, "html.parser") + self.session.mount("http://", adapter) + self.session.mount("https://", adapter) - def send_request(self, method, url, **kwargs): - self.response = self.session.request(method, url, headers=self.headers, **kwargs) - return self.response + def send_request(self, method: str, url: str, headers: dict, **kwargs): + return self.session.request(method, url, headers=headers, **kwargs) diff --git a/quotexpy/http/qxbroker.py b/quotexpy/http/qxbroker.py index 9e2da62..267f304 100644 --- a/quotexpy/http/qxbroker.py +++ b/quotexpy/http/qxbroker.py @@ -75,13 +75,11 @@ def get_ssid_and_cookies(self) -> typing.Tuple[str, str]: raise QuotexAuthError("incorrect username or password") cookies = self.browser.get_cookies() - self.api.cookies = cookies - user_agent = self.browser.execute_script("return navigator.userAgent;") - self.api.user_agent = user_agent + self.api.user_agent = self.browser.execute_script("return navigator.userAgent;") ssid = wsd.get("token") cookiejar = requests.utils.cookiejar_from_dict({c["name"]: c["value"] for c in cookies}) - cookie_string = "; ".join([f"{c.name}={c.value}" for c in cookiejar]) + self.api.cookies = "; ".join([f"{c.name}={c.value}" for c in cookiejar]) output_file = Path(sessions_file_path) output_file.parent.mkdir(exist_ok=True, parents=True) @@ -90,11 +88,11 @@ def get_ssid_and_cookies(self) -> typing.Tuple[str, str]: with output_file.open("rb") as file: data = pickle.load(file) - data[self.email] = [{"cookies": cookie_string, "ssid": ssid, "user_agent": user_agent}] + data[self.email] = [{"cookies": self.api.cookies, "ssid": ssid, "user_agent": self.api.user_agent}] with output_file.open("wb") as file: pickle.dump(data, file) - return ssid, cookie_string + return ssid, self.api.cookies except TypeError as exc: raise SystemError("Chrome is not installed, did you forget?") from exc finally: diff --git a/quotexpy/http/refresh.py b/quotexpy/http/refresh.py new file mode 100644 index 0000000..042d285 --- /dev/null +++ b/quotexpy/http/refresh.py @@ -0,0 +1,27 @@ +"""Module for Quotex http refresh ssid resource.""" + +from quotexpy.http.navigator import Navigator + + +class Refresh(Navigator): + """Class for Quotex refresh resource.""" + + def __call__(self): + try: + response = self.send_request( + method="GET", + url=f"{self.https_base_url}/api/v1/cabinets/digest", + headers={"User-Agent": self.api.user_agent, "Cookie": self.api.cookies}, + ) + + if response.status_code == 200: + response_json: dict = response.json() + data: dict = response_json.get("data") + if data and data.get("token"): + return data.get("token") + else: + raise KeyError("token not found in response data") + else: + raise ValueError("error refreshing ssid, maybe your client started without credentials?") + except Exception as err: + raise RuntimeError(f"unhandled error: {str(err)}") diff --git a/requirements-dev.txt b/requirements-dev.txt index 1bea6fa..db1ec0e 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,5 +1,5 @@ +pytest==7.4.2 black==23.9.1 poetry==1.6.1 pylint==2.17.5 -pytest==7.4.2 termcolor==2.3.0 diff --git a/requirements.txt b/requirements.txt index 22b8f9a..23658e2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,11 +1,10 @@ psutil -beautifulsoup4==4.11.2 -certifi>=2022.12.7 -greenlet>=3.0.3 -undetected-chromedriver>=3.5.5 pytz>=2023.3 -requests>=2.32.3 -requests-toolbelt>=1.0.0 urllib3>=2.2.2 +websockets>=12 +greenlet>=3.0.3 +requests>=2.32.3 +certifi>=2022.12.7 websocket-client>=1.8 -websockets>=12 \ No newline at end of file +requests-toolbelt>=1.0.0 +undetected-chromedriver>=3.5.5 \ No newline at end of file