From 4b43b63ef3bead0e64c43c93264f2ef53b9849c1 Mon Sep 17 00:00:00 2001 From: imDMG Date: Sat, 2 Sep 2023 21:18:42 +0500 Subject: [PATCH] better handling errors --- README.md | 6 +- engines/kinozal.py | 238 ++++++++++++++++++++--------------------- engines/rutor.py | 178 ++++++++++++++++--------------- engines/rutracker.py | 248 ++++++++++++++++++++----------------------- settings_gui.py | 4 +- 5 files changed, 326 insertions(+), 348 deletions(-) diff --git a/README.md b/README.md index cd069f4..1018c47 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,13 @@ [![Python 3.7+](https://img.shields.io/badge/python-%3E%3D%20v3.7-blue)](https://www.python.org/downloads/release/python-370/) # qBittorrent plugins -## Rutracker.org ![v1.7](https://img.shields.io/badge/v1.7-blue) +## Rutracker.org ![v1.8](https://img.shields.io/badge/v1.7-blue) Biggest russian torrent tracker. -## Rutor.org ![v1.6](https://img.shields.io/badge/v1.6-blue) +## Rutor.org ![v1.7](https://img.shields.io/badge/v1.6-blue) Popular free russian torrent tracker. -## Kinozal.tv ![v2.10](https://img.shields.io/badge/v2.10-blue) +## Kinozal.tv ![v2.11](https://img.shields.io/badge/v2.10-blue) Russian torrent tracker mostly directed on movies, but have other categories. The site has a restriction on downloading torrent files (10 by default or so), so I added the ability to open the magnet link instead the file. diff --git a/engines/kinozal.py b/engines/kinozal.py index d645072..7c0d0e0 100644 --- a/engines/kinozal.py +++ b/engines/kinozal.py @@ -1,4 +1,4 @@ -# VERSION: 2.10 +# VERSION: 2.11 # AUTHORS: imDMG [imdmgg@gmail.com] # Kinozal.tv search engine plugin for qBittorrent @@ -17,7 +17,7 @@ from html import unescape from http.cookiejar import MozillaCookieJar from pathlib import Path from tempfile import NamedTemporaryFile -from typing import Union, Optional +from typing import Callable from urllib.error import URLError, HTTPError from urllib.parse import urlencode, unquote from urllib.request import build_opener, HTTPCookieProcessor, ProxyHandler @@ -43,8 +43,6 @@ BASEDIR = FILE.parent.absolute() FILENAME = FILE.stem FILE_J, FILE_C = [BASEDIR / (FILENAME + fl) for fl in (".json", ".cookie")] -PAGES = 50 - RE_TORRENTS = re.compile( r'nam">(.+?).+?s\'>.+?s\'>(.+?)<.+?' r'sl_s\'>(\d+?)<.+?sl_p\'>(\d+?)<.+?s\'>(.+?)', re.S @@ -52,6 +50,8 @@ RE_TORRENTS = re.compile( RE_RESULTS = re.compile(r"Найдено\s+?(\d+?)\s+?раздач", re.S) PATTERNS = ("%sbrowse.php?s=%s&c=%s", "%s&page=%s") +PAGES = 50 + # base64 encoded image ICON = ("AAABAAEAEBAAAAEAIABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAQAQAAAAAAAAAAA" "AAAAAAAAAAAACARztMgEc7/4BHO/+ARztMAAAAAIBHO0yhd2n/gEc7/6F3af+ARztMAAAA" @@ -81,6 +81,10 @@ def rng(t: int) -> range: return range(1, -(-t // PAGES)) +class EngineError(Exception): + ... + + @dataclass class Config: username: str = "USERNAME" @@ -145,88 +149,19 @@ class Kinozal: "anime": "20", "software": "32"} - # error message - error: Optional[str] = None # cookies mcj = MozillaCookieJar() # establish connection session = build_opener(HTTPCookieProcessor(mcj)) - def __init__(self): - # add proxy handler if needed - if config.proxy: - if any(config.proxies.values()): - self.session.add_handler(ProxyHandler(config.proxies)) - logger.debug("Proxy is set!") - else: - self.error = "Proxy enabled, but not set!" - - # change user-agent - self.session.addheaders = [("User-Agent", config.ua)] - - # load local cookies - try: - self.mcj.load(FILE_C, ignore_discard=True) - if "uid" in [cookie.name for cookie in self.mcj]: - # if cookie.expires < int(time.time()) - logger.info("Local cookies is loaded") - else: - logger.info("Local cookies expired or bad") - logger.debug(f"That we have: {[cookie for cookie in self.mcj]}") - self.mcj.clear() - self.login() - except FileNotFoundError: - self.login() - def search(self, what: str, cat: str = "all") -> None: - if self.error: - self.pretty_error(what) - return None - query = PATTERNS[0] % (self.url, what.replace(" ", "+"), - self.supported_categories[cat]) - - # make first request (maybe it enough) - t0, total = time.time(), self.searching(query, True) - if self.error: - self.pretty_error(what) - return None - # do async requests - if total > PAGES: - qrs = [PATTERNS[1] % (query, x) for x in rng(total)] - with ThreadPoolExecutor(len(qrs)) as executor: - executor.map(self.searching, qrs, timeout=30) - - logger.debug(f"--- {time.time() - t0} seconds ---") - logger.info(f"Found torrents: {total}") + self._catch_errors(self._search, what, cat) def download_torrent(self, url: str) -> None: - # choose download method - if config.magnet: - url = "%sget_srv_details.php?action=2&id=%s" % (self.url, - url.split("=")[1]) - - response = self._request(url) - if self.error: - self.pretty_error(url) - return None - - if config.magnet: - if response.startswith(b"\x1f\x8b\x08"): - response = gzip.decompress(response) - path = "magnet:?xt=urn:btih:" + response.decode()[18:58] - else: - # Create a torrent file - with NamedTemporaryFile(suffix=".torrent", delete=False) as fd: - fd.write(response) - path = fd.name - - # return magnet link / file path - logger.debug(path + " " + url) - print(path + " " + url) + self._catch_errors(self._download_torrent, url) def login(self) -> None: - if self.error: - return None + self.mcj.clear() form_data = {"username": config.username, "password": config.password} logger.debug(f"Login. Data before: {form_data}") @@ -235,20 +170,16 @@ class Kinozal: logger.debug(f"Login. Data after: {data_encoded}") self._request(self.url_login, data_encoded) - if self.error: - return None logger.debug(f"That we have: {[cookie for cookie in self.mcj]}") - if "uid" in [cookie.name for cookie in self.mcj]: - self.mcj.save(FILE_C, ignore_discard=True, ignore_expires=True) - logger.info("We successfully authorized") - else: - self.error = "We not authorized, please check your credentials!" - logger.warning(self.error) - - def searching(self, query: str, first: bool = False) -> Union[None, int]: + if "uid" not in [cookie.name for cookie in self.mcj]: + raise EngineError( + "We not authorized, please check your credentials!" + ) + self.mcj.save(FILE_C, ignore_discard=True, ignore_expires=True) + logger.info("We successfully authorized") + + def searching(self, query: str, first: bool = False) -> int: response = self._request(query) - if self.error: - return None if response.startswith(b"\x1f\x8b\x08"): response = gzip.decompress(response) page, torrents_found = response.decode("cp1251"), -1 @@ -256,17 +187,13 @@ class Kinozal: # check login status if "Гость! ( Зарегистрируйтесь )" in page: logger.debug("Looks like we lost session id, lets login") - self.mcj.clear() self.login() - if self.error: - return None # firstly we check if there is a result - result = RE_RESULTS.search(page) - if not result: - self.error = "Unexpected page content" - return None - torrents_found = int(result[1]) - if not torrents_found: + try: + torrents_found = int(RE_RESULTS.search(page)[1]) + except TypeError: + raise EngineError("Unexpected page content") + if torrents_found <= 0: return 0 self.draw(page) @@ -276,6 +203,8 @@ class Kinozal: _part = partial(time.strftime, "%y.%m.%d") # yeah this is yesterday yesterday = _part(time.localtime(time.time() - 86400)) + # replace size units + table = {"Т": "T", "Г": "G", "М": "M", "К": "K", "Б": "B"} for tor in RE_TORRENTS.findall(html): torrent_date = "" if config.torrent_date: @@ -288,9 +217,6 @@ class Kinozal: torrent_date = _part(time.strptime(ct, "%d.%m.%Y")) torrent_date = f"[{torrent_date}] " - # replace size units - table = {"Т": "T", "Г": "G", "М": "M", "К": "K", "Б": "B"} - prettyPrinter({ "engine_url": self.url, "desc_link": self.url + tor[0], @@ -301,40 +227,108 @@ class Kinozal: "leech": tor[4] }) + def _catch_errors(self, handler: Callable, *args: str): + try: + self._init() + handler(*args) + except EngineError as ex: + self.pretty_error(args[0], str(ex)) + except Exception as ex: + self.pretty_error(args[0], "Unexpected error, please check logs") + logger.exception(ex) + + def _init(self) -> None: + # add proxy handler if needed + if config.proxy: + if not any(config.proxies.values()): + raise EngineError("Proxy enabled, but not set!") + self.session.add_handler(ProxyHandler(config.proxies)) + logger.debug("Proxy is set!") + + # change user-agent + self.session.addheaders = [("User-Agent", config.ua)] + + # load local cookies + try: + self.mcj.load(FILE_C, ignore_discard=True) + if "uid" in [cookie.name for cookie in self.mcj]: + # if cookie.expires < int(time.time()) + return logger.info("Local cookies is loaded") + logger.info("Local cookies expired or bad, try to login") + logger.debug(f"That we have: {[cookie for cookie in self.mcj]}") + except FileNotFoundError: + logger.info("Local cookies not exists, try to login") + self.login() + + def _search(self, what: str, cat: str = "all") -> None: + query = PATTERNS[0] % (self.url, what.replace(" ", "+"), + self.supported_categories[cat]) + + # make first request (maybe it enough) + t0, total = time.time(), self.searching(query, True) + # do async requests + if total > PAGES: + qrs = [PATTERNS[1] % (query, x) for x in rng(total)] + with ThreadPoolExecutor(len(qrs)) as executor: + executor.map(self.searching, qrs, timeout=30) + + logger.debug(f"--- {time.time() - t0} seconds ---") + logger.info(f"Found torrents: {total}") + + def _download_torrent(self, url: str) -> None: + # choose download method + if config.magnet: + url = "%sget_srv_details.php?action=2&id=%s" % (self.url, + url.split("=")[1]) + + response = self._request(url) + + if config.magnet: + if response.startswith(b"\x1f\x8b\x08"): + response = gzip.decompress(response) + path = "magnet:?xt=urn:btih:" + response.decode()[18:58] + else: + # Create a torrent file + with NamedTemporaryFile(suffix=".torrent", delete=False) as fd: + fd.write(response) + path = fd.name + + # return magnet link / file path + logger.debug(path + " " + url) + print(path + " " + url) + def _request( - self, url: str, data: Optional[bytes] = None, repeated: bool = False - ) -> Union[bytes, None]: + self, url: str, data: bytes = None, repeated: bool = False + ) -> bytes: try: with self.session.open(url, data, 5) as r: # checking that tracker isn't blocked if r.geturl().startswith((self.url, self.url_dl)): return r.read() - self.error = f"{url} is blocked. Try another proxy." + raise EngineError(f"{url} is blocked. Try another proxy.") except (URLError, HTTPError) as err: - logger.error(err.reason) error = str(err.reason) + reason = f"{url} is not response! Maybe it is blocked." if "timed out" in error and not repeated: - logger.debug("Repeating request...") + logger.debug("Request timed out. Repeating...") return self._request(url, data, True) if "no host given" in error: - self.error = "Proxy is bad, try another!" + reason = "Proxy is bad, try another!" elif hasattr(err, "code"): - self.error = f"Request to {url} failed with status: {err.code}" - else: - self.error = f"{url} is not response! Maybe it is blocked." - - return None - - def pretty_error(self, what: str) -> None: - prettyPrinter({"engine_url": self.url, - "desc_link": "https://github.com/imDMG/qBt_SE", - "name": f"[{unquote(what)}][Error]: {self.error}", - "link": self.url + "error", - "size": "1 TB", # lol - "seeds": 100, - "leech": 100}) - - self.error = None + reason = f"Request to {url} failed with status: {err.code}" + + raise EngineError(reason) + + def pretty_error(self, what: str, error: str) -> None: + prettyPrinter({ + "engine_url": self.url, + "desc_link": "https://github.com/imDMG/qBt_SE", + "name": f"[{unquote(what)}][Error]: {error}", + "link": self.url + "error", + "size": "1 TB", # lol + "seeds": 100, + "leech": 100 + }) # pep8 diff --git a/engines/rutor.py b/engines/rutor.py index 8e203c5..1f14192 100644 --- a/engines/rutor.py +++ b/engines/rutor.py @@ -1,4 +1,4 @@ -# VERSION: 1.6 +# VERSION: 1.7 # AUTHORS: imDMG [imdmgg@gmail.com] # Rutor.org search engine plugin for qBittorrent @@ -14,7 +14,7 @@ from dataclasses import dataclass, field from html import unescape from pathlib import Path from tempfile import NamedTemporaryFile -from typing import Optional, Union +from typing import Callable from urllib.error import URLError, HTTPError from urllib.parse import unquote from urllib.request import build_opener, ProxyHandler @@ -31,12 +31,6 @@ BASEDIR = FILE.parent.absolute() FILENAME = FILE.stem FILE_J, FILE_C = [BASEDIR / (FILENAME + fl) for fl in (".json", ".cookie")] -PAGES = 100 - - -def rng(t: int) -> range: - return range(1, -(-t // PAGES)) - RE_TORRENTS = re.compile( r'(?:gai|tum)">(.+?)\sРезультатов\sпоиска\s(\d{1,4})\s", re.S) PATTERNS = ("%ssearch/%i/%i/000/0/%s",) +PAGES = 100 + # base64 encoded image ICON = ("AAABAAEAEBAAAAEAGABoAwAAFgAAACgAAAAQAAAAIAAAAAEAGAAAAAAAAAAAAAAAAAAAAA" "AAAAAAAAAAAAAAAc4AAMwHNdcQ4vsN3fYS2fUY3fUe3fMj4fkk4fco4PYo5fgk7f5gp8Zu" @@ -76,6 +72,14 @@ logging.basicConfig( logger = logging.getLogger(__name__) +def rng(t: int) -> range: + return range(1, -(-t // PAGES)) + + +class EngineError(Exception): + ... + + @dataclass class Config: # username: str = "USERNAME" @@ -141,73 +145,24 @@ class Rutor: "pictures": 3, "books": 11} - # error message - error: Optional[str] = None # establish connection session = build_opener() - def __init__(self): - # add proxy handler if needed - if config.proxy: - if any(config.proxies.values()): - self.session.add_handler(ProxyHandler(config.proxies)) - logger.debug("Proxy is set!") - else: - self.error = "Proxy enabled, but not set!" - - # change user-agent - self.session.addheaders = [("User-Agent", config.ua)] - def search(self, what: str, cat: str = "all") -> None: - if self.error: - self.pretty_error(what) - return None - query = PATTERNS[0] % (self.url, 0, self.supported_categories[cat], - what.replace(" ", "+")) - - # make first request (maybe it enough) - t0, total = time.time(), self.searching(query, True) - if self.error: - self.pretty_error(what) - return None - # do async requests - if total > PAGES: - query = query.replace("h/0", "h/{}") - qrs = [query.format(x) for x in rng(total)] - with ThreadPoolExecutor(len(qrs)) as executor: - executor.map(self.searching, qrs, timeout=30) - - logger.debug(f"--- {time.time() - t0} seconds ---") - logger.info(f"Found torrents: {total}") + self._catch_errors(self._search, what, cat) def download_torrent(self, url: str) -> None: - # Download url - response = self._request(url) - if self.error: - self.pretty_error(url) - return None + self._catch_errors(self._download_torrent, url) - # Create a torrent file - with NamedTemporaryFile(suffix=".torrent", delete=False) as fd: - fd.write(response) - - # return file path - logger.debug(fd.name + " " + url) - print(fd.name + " " + url) - - def searching(self, query: str, first: bool = False) -> Union[None, int]: - response = self._request(query) - if self.error: - return None - page, torrents_found = response.decode(), -1 + def searching(self, query: str, first: bool = False) -> int: + page, torrents_found = self._request(query).decode(), -1 if first: # firstly we check if there is a result - result = RE_RESULTS.search(page) - if not result: - self.error = "Unexpected page content" - return None - torrents_found = int(result[1]) - if not torrents_found: + try: + torrents_found = int(RE_RESULTS.search(page)[1]) + except TypeError: + raise EngineError("Unexpected page content") + if torrents_found <= 0: return 0 self.draw(page) @@ -235,40 +190,87 @@ class Rutor: "leech": unescape(tor[7]) }) + def _catch_errors(self, handler: Callable, *args: str): + try: + self._init() + handler(*args) + except EngineError as ex: + self.pretty_error(args[0], str(ex)) + except Exception as ex: + self.pretty_error(args[0], "Unexpected error, please check logs") + logger.exception(ex) + + def _init(self) -> None: + # add proxy handler if needed + if config.proxy: + if not any(config.proxies.values()): + raise EngineError("Proxy enabled, but not set!") + self.session.add_handler(ProxyHandler(config.proxies)) + logger.debug("Proxy is set!") + + # change user-agent + self.session.addheaders = [("User-Agent", config.ua)] + + def _search(self, what: str, cat: str = "all") -> None: + query = PATTERNS[0] % (self.url, 0, self.supported_categories[cat], + what.replace(" ", "+")) + + # make first request (maybe it enough) + t0, total = time.time(), self.searching(query, True) + # do async requests + if total > PAGES: + query = query.replace("h/0", "h/{}") + qrs = [query.format(x) for x in rng(total)] + with ThreadPoolExecutor(len(qrs)) as executor: + executor.map(self.searching, qrs, timeout=30) + + logger.debug(f"--- {time.time() - t0} seconds ---") + logger.info(f"Found torrents: {total}") + + def _download_torrent(self, url: str) -> None: + # Download url + response = self._request(url) + + # Create a torrent file + with NamedTemporaryFile(suffix=".torrent", delete=False) as fd: + fd.write(response) + + # return file path + logger.debug(fd.name + " " + url) + print(fd.name + " " + url) + def _request( - self, url: str, data: Optional[bytes] = None, repeated: bool = False - ) -> Union[bytes, None]: + self, url: str, data: bytes = None, repeated: bool = False + ) -> bytes: try: with self.session.open(url, data, 5) as r: # checking that tracker isn't blocked if r.geturl().startswith((self.url, self.url_dl)): return r.read() - self.error = f"{url} is blocked. Try another proxy." + raise EngineError(f"{url} is blocked. Try another proxy.") except (URLError, HTTPError) as err: - logger.error(err.reason) error = str(err.reason) + reason = f"{url} is not response! Maybe it is blocked." if "timed out" in error and not repeated: - logger.debug("Repeating request...") + logger.debug("Request timed out. Repeating...") return self._request(url, data, True) if "no host given" in error: - self.error = "Proxy is bad, try another!" + reason = "Proxy is bad, try another!" elif hasattr(err, "code"): - self.error = f"Request to {url} failed with status: {err.code}" - else: - self.error = f"{url} is not response! Maybe it is blocked." - - return None - - def pretty_error(self, what: str) -> None: - prettyPrinter({"engine_url": self.url, - "desc_link": "https://github.com/imDMG/qBt_SE", - "name": f"[{unquote(what)}][Error]: {self.error}", - "link": self.url + "error", - "size": "1 TB", # lol - "seeds": 100, - "leech": 100}) - - self.error = None + reason = f"Request to {url} failed with status: {err.code}" + + raise EngineError(reason) + + def pretty_error(self, what: str, error: str) -> None: + prettyPrinter({ + "engine_url": self.url, + "desc_link": "https://github.com/imDMG/qBt_SE", + "name": f"[{unquote(what)}][Error]: {error}", + "link": self.url + "error", + "size": "1 TB", # lol + "seeds": 100, + "leech": 100 + }) # pep8 diff --git a/engines/rutracker.py b/engines/rutracker.py index 3e1645b..72585ce 100644 --- a/engines/rutracker.py +++ b/engines/rutracker.py @@ -1,4 +1,4 @@ -# VERSION: 1.7 +# VERSION: 1.8 # AUTHORS: imDMG [imdmgg@gmail.com] # rutracker.org search engine plugin for qBittorrent @@ -12,10 +12,10 @@ import time from concurrent.futures import ThreadPoolExecutor from dataclasses import dataclass, field from html import unescape -from http.cookiejar import Cookie, MozillaCookieJar +from http.cookiejar import MozillaCookieJar from pathlib import Path from tempfile import NamedTemporaryFile -from typing import Optional +from typing import Callable from urllib.error import URLError, HTTPError from urllib.parse import urlencode, unquote from urllib.request import build_opener, HTTPCookieProcessor, ProxyHandler @@ -29,24 +29,19 @@ except ImportError: FILE = Path(__file__) BASEDIR = FILE.parent.absolute() -FILENAME = FILE.name[:-3] -FILE_J, FILE_C = [BASEDIR / (FILENAME + fl) for fl in [".json", ".cookie"]] - -PAGES = 50 - - -def rng(t: int) -> range: - return range(PAGES, -(-t // PAGES) * PAGES, PAGES) - +FILENAME = FILE.stem +FILE_J, FILE_C = [BASEDIR / (FILENAME + fl) for fl in (".json", ".cookie")] RE_TORRENTS = re.compile( r'(.+?).+?data-ts_text="([-0-9]+?)">.+?Личи">(\d+?)', + r'">.+?data-ts_text="([-\d]+?)">.+?Личи">(\d+?)', re.S ) RE_RESULTS = re.compile(r"Результатов\sпоиска:\s(\d{1,3})\s range: + return range(PAGES, -(-t // PAGES) * PAGES, PAGES) + + +class EngineError(Exception): + ... + + @dataclass class Config: username: str = "USERNAME" password: str = "PASSWORD" torrent_date: bool = True - # magnet: bool = False proxy: bool = False # dynamic_proxy: bool = True proxies: dict = field(default_factory=lambda: {"http": "", "https": ""}) @@ -103,7 +105,7 @@ class Config: (BASEDIR / f"{FILENAME}.ico").write_bytes(base64.b64decode(ICON)) def to_str(self) -> str: - return json.dumps(self.to_dict(), indent=4, sort_keys=False) + return json.dumps(self.to_dict(), indent=4) def to_dict(self) -> dict: return {self._to_camel(k): v for k, v in self.__dict__.items()} @@ -139,86 +141,20 @@ class Rutracker: url_login = url + "login.php" supported_categories = {"all": "-1"} - # error message - error: Optional[str] = None # cookies mcj = MozillaCookieJar() # establish connection session = build_opener(HTTPCookieProcessor(mcj)) - def __init__(self): - # add proxy handler if needed - if config.proxy: - if any(config.proxies.values()): - self.session.add_handler(ProxyHandler(config.proxies)) - logger.debug("Proxy is set!") - else: - self.error = "Proxy enabled, but not set!" - - # change user-agent - self.session.addheaders = [("User-Agent", config.ua)] - - # load local cookies - try: - self.mcj.load(FILE_C, ignore_discard=True) - if "bb_session" in [cookie.name for cookie in self.mcj]: - # if cookie.expires < int(time.time()) - logger.info("Local cookies is loaded") - else: - logger.info("Local cookies expired or bad") - logger.debug(f"That we have: {[cookie for cookie in self.mcj]}") - self.mcj.clear() - self.login() - except FileNotFoundError: - self.login() - def search(self, what: str, cat: str = "all") -> None: - if self.error: - self.pretty_error(what) - return None - query = PATTERNS[0] % (self.url, what.replace(" ", "+"), - self.supported_categories[cat]) - - # make first request (maybe it enough) - t0, total = time.time(), self.searching(query, True) - if self.error: - self.pretty_error(what) - return None - # do async requests - if total > PAGES: - qrs = [PATTERNS[1] % (query, x) for x in rng(total)] - with ThreadPoolExecutor(len(qrs)) as executor: - executor.map(self.searching, qrs, timeout=30) - - logger.debug(f"--- {time.time() - t0} seconds ---") - logger.info(f"Found torrents: {total}") + self._catch_errors(self._search, what, cat) def download_torrent(self, url: str) -> None: - # Download url - response = self._request(url) - if self.error: - self.pretty_error(url) - return None - - # Create a torrent file - with NamedTemporaryFile(suffix=".torrent", delete=False) as fd: - fd.write(response) - - # return file path - logger.debug(fd.name + " " + url) - print(fd.name + " " + url) + self._catch_errors(self._download_torrent, url) def login(self) -> None: - if self.error: - return None - self.mcj.clear() - # if we wanna use https we mast add bb_ssl=1 to cookie - self.mcj.set_cookie(Cookie(0, "bb_ssl", "1", None, False, - ".rutracker.org", True, True, "/forum/", - True, True, None, False, None, None, {})) - form_data = {"login_username": config.username, "login_password": config.password, "login": "Вход"} @@ -227,42 +163,31 @@ class Rutracker: data_encoded = urlencode(form_data, encoding="cp1251").encode() logger.debug(f"Login. Data after: {data_encoded}") self._request(self.url_login, data_encoded) - if self.error: - return None logger.debug(f"That we have: {[cookie for cookie in self.mcj]}") - if "bb_session" in [cookie.name for cookie in self.mcj]: - self.mcj.save(FILE_C, ignore_discard=True, ignore_expires=True) - logger.info("We successfully authorized") - else: - self.error = "We not authorized, please check your credentials!" - logger.warning(self.error) - - def searching(self, query: str, first: bool = False) -> Optional[int]: - response = self._request(query) - if self.error: - return None - page, torrents_found = response.decode("cp1251"), -1 + if "bb_session" not in [cookie.name for cookie in self.mcj]: + raise EngineError( + "We not authorized, please check your credentials!" + ) + self.mcj.save(FILE_C, ignore_discard=True, ignore_expires=True) + logger.info("We successfully authorized") + + def searching(self, query: str, first: bool = False) -> int: + page, torrents_found = self._request(query).decode("cp1251"), -1 if first: + # check login status if "log-out-icon" not in page: if "login-form-full" not in page: - self.error = "Unexpected page content" - return None + raise EngineError("Unexpected page content") logger.debug("Looks like we lost session id, lets login") self.login() - if self.error: - return None # retry request because guests cant search - response = self._request(query) - if self.error: - return None - page = response.decode("cp1251") + page = self._request(query).decode("cp1251") # firstly we check if there is a result - result = RE_RESULTS.search(page) - if not result: - self.error = "Unexpected page content" - return None - torrents_found = int(result[1]) - if not torrents_found: + try: + torrents_found = int(RE_RESULTS.search(page)[1]) + except TypeError: + raise EngineError("Unexpected page content") + if torrents_found <= 0: return 0 self.draw(page) @@ -283,40 +208,97 @@ class Rutracker: "leech": tor[4] }) + def _catch_errors(self, handler: Callable, *args: str): + try: + self._init() + handler(*args) + except EngineError as ex: + self.pretty_error(args[0], str(ex)) + except Exception as ex: + self.pretty_error(args[0], "Unexpected error, please check logs") + logger.exception(ex) + + def _init(self) -> None: + # add proxy handler if needed + if config.proxy: + if not any(config.proxies.values()): + raise EngineError("Proxy enabled, but not set!") + self.session.add_handler(ProxyHandler(config.proxies)) + logger.debug("Proxy is set!") + + # change user-agent + self.session.addheaders = [("User-Agent", config.ua)] + + # load local cookies + try: + self.mcj.load(FILE_C, ignore_discard=True) + if "bb_session" in [cookie.name for cookie in self.mcj]: + # if cookie.expires < int(time.time()) + return logger.info("Local cookies is loaded") + logger.info("Local cookies expired or bad, try to login") + logger.debug(f"That we have: {[cookie for cookie in self.mcj]}") + except FileNotFoundError: + logger.info("Local cookies not exists, try to login") + self.login() + + def _search(self, what: str, cat: str = "all") -> None: + query = PATTERNS[0] % (self.url, what.replace(" ", "+"), + self.supported_categories[cat]) + + # make first request (maybe it enough) + t0, total = time.time(), self.searching(query, True) + # do async requests + if total > PAGES: + qrs = [PATTERNS[1] % (query, x) for x in rng(total)] + with ThreadPoolExecutor(len(qrs)) as executor: + executor.map(self.searching, qrs, timeout=30) + + logger.debug(f"--- {time.time() - t0} seconds ---") + logger.info(f"Found torrents: {total}") + + def _download_torrent(self, url: str) -> None: + response = self._request(url) + + # Create a torrent file + with NamedTemporaryFile(suffix=".torrent", delete=False) as fd: + fd.write(response) + + # return file path + logger.debug(fd.name + " " + url) + print(fd.name + " " + url) + def _request( - self, url: str, data: Optional[bytes] = None, repeated: bool = False - ) -> Optional[bytes]: + self, url: str, data: bytes = None, repeated: bool = False + ) -> bytes: try: with self.session.open(url, data, 5) as r: # checking that tracker isn't blocked if r.geturl().startswith((self.url, self.url_dl)): return r.read() - self.error = f"{url} is blocked. Try another proxy." + raise EngineError(f"{url} is blocked. Try another proxy.") except (URLError, HTTPError) as err: - logger.error(err.reason) error = str(err.reason) + reason = f"{url} is not response! Maybe it is blocked." if "timed out" in error and not repeated: - logger.debug("Repeating request...") + logger.debug("Request timed out. Repeating...") return self._request(url, data, True) if "no host given" in error: - self.error = "Proxy is bad, try another!" + reason = "Proxy is bad, try another!" elif hasattr(err, "code"): - self.error = f"Request to {url} failed with status: {err.code}" - else: - self.error = f"{url} is not response! Maybe it is blocked." - - return None - - def pretty_error(self, what: str) -> None: - prettyPrinter({"engine_url": self.url, - "desc_link": "https://github.com/imDMG/qBt_SE", - "name": f"[{unquote(what)}][Error]: {self.error}", - "link": self.url + "error", - "size": "1 TB", # lol - "seeds": 100, - "leech": 100}) - - self.error = None + reason = f"Request to {url} failed with status: {err.code}" + + raise EngineError(reason) + + def pretty_error(self, what: str, error: str) -> None: + prettyPrinter({ + "engine_url": self.url, + "desc_link": "https://github.com/imDMG/qBt_SE", + "name": f"[{unquote(what)}][Error]: {error}", + "link": self.url + "error", + "size": "1 TB", # lol + "seeds": 100, + "leech": 100 + }) # pep8 diff --git a/settings_gui.py b/settings_gui.py index 21cde82..773afcc 100644 --- a/settings_gui.py +++ b/settings_gui.py @@ -22,10 +22,10 @@ class EngineSettingsGUI: self.username = tk.StringVar(value=self.config.get("username", "")) self.password = tk.StringVar(value=self.config.get("password", "")) self.proxy_http = tk.StringVar( - value=self.config.get("proxies").get("http", "") + value=self.config.get("proxies", dict()).get("http", "") ) self.proxy_https = tk.StringVar( - value=self.config.get("proxies").get("https", "") + value=self.config.get("proxies", dict()).get("https", "") ) self.date = tk.BooleanVar(value=self.config.get("torrentDate", True))