Browse Source

update README.md, settings_gui.py, kinozal.py, nnmclub.py, rutor.py, rutracker.py

master
imDMG 2 years ago
parent
commit
638d185520
  1. 30
      README.md
  2. 15
      engines/kinozal.py
  3. 280
      engines/nnmclub.py
  4. 192
      engines/rutor.py
  5. 233
      engines/rutracker.py
  6. 32
      settings_gui.py

30
README.md

@ -1,34 +1,48 @@
[![Python 3.7+](https://img.shields.io/badge/python-%3E%3D%20v3.7-blue)](https://www.python.org/downloads/release/python-370/) [![Python 3.7+](https://img.shields.io/badge/python-%3E%3D%20v3.7-blue)](https://www.python.org/downloads/release/python-370/)
# qBittorrent plugins # qBittorrent plugins
## Rutracker.org ![v1.5](https://img.shields.io/badge/v1.5-blue) ## Rutracker.org ![v1.6](https://img.shields.io/badge/v1.6-blue)
Biggest russian torrent tracker. Biggest russian torrent tracker.
## Rutor.org ![v1.3](https://img.shields.io/badge/v1.3-blue) ## Rutor.org ![v1.4](https://img.shields.io/badge/v1.4-blue)
Popular free russian torrent tracker. Popular free russian torrent tracker.
## Kinozal.tv ![v2.7](https://img.shields.io/badge/v2.7-blue) ## Kinozal.tv ![v2.8](https://img.shields.io/badge/v2.8-blue)
Russian torrent tracker mostly directed on movies, but have other categories. 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. 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.
You can turn off the magnet link: in `kinozal.json` switch `"magnet": true` to `"magnet": false` You can turn off the magnet link: in `kinozal.json` switch `"magnet": true` to `"magnet": false`
## NNM-Club.me ![v2.8](https://img.shields.io/badge/v2.8-blue) ## NNM-Club.me ![v2.9](https://img.shields.io/badge/v2.9-blue)
One of biggest russian torrent tracker. One of biggest russian torrent tracker.
## Installation ## Installation
**For fresh installation.** **For fresh installation.**
According of the name of search plugin: According of the name of search plugin:
* Save `*.py` file on your computer * Save `*.py` file on your computer
* Then open `*.py` file and find from above the section with `config = { ... }`. Replace `USERNAME` and `PASSWORD` with your torrent tracker username and password. If tracker is blocked in your country, in same section:
* find `"proxy": False` and switch in `True` (`"proxy": True`)
* add proxy links working for you in `proxies` (`{"http": "proxy.example.org:8080"}`)
* *make sure that your proxy work with right protocol*
* Then follow [official tutorial](https://github.com/qbittorrent/search-plugins/wiki/Install-search-plugins). * Then follow [official tutorial](https://github.com/qbittorrent/search-plugins/wiki/Install-search-plugins).
* _After installation you can change your settings with `*.json` file which will be created automatically in:_ * _After installation you can change your settings with `*.json` file which will be created automatically in:_
* Windows: `%localappdata%\qBittorrent\nova3\engines` * Windows: `%localappdata%\qBittorrent\nova3\engines`
* Linux: `~/.local/share/qBittorrent/nova3/engines` * Linux: `~/.local/share/qBittorrent/nova3/engines`
* OS X: `~/Library/Application Support/qBittorrent/nova3/engines` * OS X: `~/Library/Application Support/qBittorrent/nova3/engines`
* ... or you can put `settings_gui.py` into parent folder (nova3) and after this you can double-click on `*.py` file, or `Open with... -> Python`, and you should see graphic interface of your data :)
Description of `*.json` file:
```
{
"username": "USERNAME", <- your USERNAME of torrent tracker
"password": "PASSWORD", <- your PASSWORD of torrent tracker
"torrentDate": true, <- creation date of torrent in search list (true/false)
"proxy": false, <- switchng proxy (true/false)
"proxies": {
"http": "", <- ex. "proxy.example.org:8080"
"https": "" <- ex. "127.0.0.1:3128"
},
"magnet": false, <- switching magnet download (true/false)
"ua": "Mozilla/5.0 (X11; Linux i686; rv:38.0) Gecko/20100101 Firefox/38.0 " <- browser User-Agent
}
```
_*make sure that your proxy work with right protocol_
**For update just reinstall `*.py` file.** **For update just reinstall `*.py` file.**

15
engines/kinozal.py

@ -1,4 +1,4 @@
# VERSION: 2.7 # VERSION: 2.9
# AUTHORS: imDMG [imdmgg@gmail.com] # AUTHORS: imDMG [imdmgg@gmail.com]
# Kinozal.tv search engine plugin for qBittorrent # Kinozal.tv search engine plugin for qBittorrent
@ -115,6 +115,11 @@ class Config:
if type(_val) is not type(v): if type(_val) is not type(v):
is_valid = False is_valid = False
continue continue
if type(_val) is dict:
for dk, dv in v.items():
if type(_val.get(dk)) is not type(dv):
_val[dk] = dv
is_valid = False
setattr(self, k, _val) setattr(self, k, _val)
return is_valid return is_valid
@ -225,7 +230,7 @@ class Kinozal:
form_data = {"username": config.username, "password": config.password} form_data = {"username": config.username, "password": config.password}
logger.debug(f"Login. Data before: {form_data}") logger.debug(f"Login. Data before: {form_data}")
# encoding to cp1251 then do default decode whole string # encoding to cp1251 then do default encode whole string
data_encoded = urlencode(form_data, encoding="cp1251").encode() data_encoded = urlencode(form_data, encoding="cp1251").encode()
logger.debug(f"Login. Data after: {data_encoded}") logger.debug(f"Login. Data after: {data_encoded}")
@ -268,11 +273,10 @@ class Kinozal:
return torrents_found return torrents_found
def draw(self, html: str) -> None: def draw(self, html: str) -> None:
torrents = RE_TORRENTS.findall(html)
_part = partial(time.strftime, "%y.%m.%d") _part = partial(time.strftime, "%y.%m.%d")
# yeah this is yesterday # yeah this is yesterday
yesterday = _part(time.localtime(time.time() - 86400)) yesterday = _part(time.localtime(time.time() - 86400))
for tor in torrents: for tor in RE_TORRENTS.findall(html):
torrent_date = "" torrent_date = ""
if config.torrent_date: if config.torrent_date:
ct = tor[5].split()[0] ct = tor[5].split()[0]
@ -296,10 +300,9 @@ class Kinozal:
"seeds": tor[3], "seeds": tor[3],
"leech": tor[4] "leech": tor[4]
}) })
del torrents
def _request( def _request(
self, url: str, data: Optional[bytes] = None, repeated: bool = False self, url: str, data: Optional[bytes] = None, repeated: bool = False
) -> Union[bytes, None]: ) -> Union[bytes, None]:
try: try:
with self.session.open(url, data, 5) as r: with self.session.open(url, data, 5) as r:

280
engines/nnmclub.py

@ -1,4 +1,4 @@
# VERSION: 2.8 # VERSION: 2.9
# AUTHORS: imDMG [imdmgg@gmail.com] # AUTHORS: imDMG [imdmgg@gmail.com]
# NoNaMe-Club search engine plugin for qBittorrent # NoNaMe-Club search engine plugin for qBittorrent
@ -7,14 +7,15 @@ import base64
import json import json
import logging import logging
import re import re
import socket
import sys import sys
import time import time
from concurrent.futures import ThreadPoolExecutor from concurrent.futures import ThreadPoolExecutor
from dataclasses import dataclass, field
from html import unescape from html import unescape
from http.cookiejar import Cookie, MozillaCookieJar from http.cookiejar import Cookie, MozillaCookieJar
from pathlib import Path from pathlib import Path
from tempfile import NamedTemporaryFile from tempfile import NamedTemporaryFile
from typing import Optional, Union
from urllib.error import URLError, HTTPError from urllib.error import URLError, HTTPError
from urllib.parse import urlencode, unquote from urllib.parse import urlencode, unquote
from urllib.request import build_opener, HTTPCookieProcessor, ProxyHandler from urllib.request import build_opener, HTTPCookieProcessor, ProxyHandler
@ -25,30 +26,16 @@ except ImportError:
sys.path.insert(0, str(Path(__file__).parent.parent.absolute())) sys.path.insert(0, str(Path(__file__).parent.parent.absolute()))
from novaprinter import prettyPrinter from novaprinter import prettyPrinter
# default config
config = {
"torrentDate": True,
"username": "USERNAME",
"password": "PASSWORD",
"proxy": False,
"proxies": {
"http": "",
"https": ""
},
"magnet": True,
"ua": "Mozilla/5.0 (X11; Linux i686; rv:38.0) Gecko/20100101 Firefox/38.0 "
}
FILE = Path(__file__) FILE = Path(__file__)
BASEDIR = FILE.parent.absolute() BASEDIR = FILE.parent.absolute()
FILENAME = FILE.name[:-3] FILENAME = FILE.name[:-3]
FILE_J, FILE_C = [BASEDIR / (FILENAME + fl) for fl in ['.json', '.cookie']] FILE_J, FILE_C = [BASEDIR / (FILENAME + fl) for fl in [".json", ".cookie"]]
PAGES = 50 PAGES = 50
def rng(t): def rng(t: int) -> range:
return range(PAGES, -(-t // PAGES) * PAGES, PAGES) return range(PAGES, -(-t // PAGES) * PAGES, PAGES)
@ -58,7 +45,7 @@ RE_TORRENTS = re.compile(
) )
RE_RESULTS = re.compile(r'TP_VER">(?:Результатов\sпоиска:\s(\d{1,3}))?\s', re.S) RE_RESULTS = re.compile(r'TP_VER">(?:Результатов\sпоиска:\s(\d{1,3}))?\s', re.S)
RE_CODE = re.compile(r'name="code"\svalue="(.+?)"', re.S) RE_CODE = re.compile(r'name="code"\svalue="(.+?)"', re.S)
PATTERNS = ('%stracker.php?nm=%s&%s', "%s&start=%s") PATTERNS = ("%stracker.php?nm=%s&%s", "%s&start=%s")
# base64 encoded image # base64 encoded image
ICON = ("AAABAAEAEBAAAAEAIABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAAAAAAAAAAAAAAA" ICON = ("AAABAAEAEBAAAAEAIABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAAAAAAAAAAAAAAA"
@ -93,66 +80,105 @@ logging.basicConfig(
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
try:
config = json.loads(FILE_J.read_text())
logger.debug("Config is loaded.")
except OSError as e:
logger.error(e)
# if file doesn't exist, we'll create it
FILE_J.write_text(json.dumps(config, indent=4, sort_keys=False))
# also write/rewrite ico file
(BASEDIR / (FILENAME + '.ico')).write_bytes(base64.b64decode(ICON))
logger.debug("Write files.")
@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": ""})
ua: str = ("Mozilla/5.0 (X11; Linux i686; rv:38.0) Gecko/20100101 "
"Firefox/38.0 ")
class NNMClub: def __post_init__(self):
name = 'NoNaMe-Club' try:
url = 'https://nnmclub.to/forum/' if not self._validate_json(json.loads(FILE_J.read_text())):
url_dl = 'https://nnm-club.ws/' raise ValueError("Incorrect json scheme.")
url_login = url + 'login.php' except Exception as e:
supported_categories = {'all': '-1', logger.error(e)
'movies': '14', FILE_J.write_text(self.to_str())
'tv': '27', (BASEDIR / f"{FILENAME}.ico").write_bytes(base64.b64decode(ICON))
'music': '16',
'games': '17', def to_str(self) -> str:
'anime': '24', return json.dumps(self.to_dict(), indent=4, sort_keys=False)
'software': '21'}
def to_dict(self) -> dict:
return {self._to_camel(k): v for k, v in self.__dict__.items()}
def _validate_json(self, obj: dict) -> bool:
is_valid = True
for k, v in self.__dict__.items():
_val = obj.get(self._to_camel(k))
if type(_val) is not type(v):
is_valid = False
continue
if type(_val) is dict:
for dk, dv in v.items():
if type(_val.get(dk)) is not type(dv):
_val[dk] = dv
is_valid = False
setattr(self, k, _val)
return is_valid
@staticmethod
def _to_camel(s: str) -> str:
return "".join(x.title() if i else x
for i, x in enumerate(s.split("_")))
config = Config()
def __init__(self):
# error message
self.error = None
# establish connection class NNMClub:
self.session = build_opener() name = "NoNaMe-Club"
url = "https://nnmclub.to/forum/"
url_dl = "https://nnm-club.ws/"
url_login = url + "login.php"
supported_categories = {"all": "-1",
"movies": "14",
"tv": "27",
"music": "16",
"games": "17",
"anime": "24",
"software": "21"}
# error message
error: Optional[str] = None
# cookies
mcj = MozillaCookieJar()
# establish connection
session = build_opener(HTTPCookieProcessor(mcj))
def __init__(self):
# add proxy handler if needed # add proxy handler if needed
if config['proxy']: if config.proxy:
if any(config['proxies'].values()): if any(config.proxies.values()):
self.session.add_handler(ProxyHandler(config['proxies'])) self.session.add_handler(ProxyHandler(config.proxies))
logger.debug("Proxy is set!") logger.debug("Proxy is set!")
else: else:
self.error = "Proxy enabled, but not set!" self.error = "Proxy enabled, but not set!"
# change user-agent # change user-agent
self.session.addheaders = [('User-Agent', config['ua'])] self.session.addheaders = [("User-Agent", config.ua)]
# load local cookies # load local cookies
mcj = MozillaCookieJar()
try: try:
mcj.load(FILE_C, ignore_discard=True) self.mcj.load(FILE_C, ignore_discard=True)
key = 'phpbb2mysql_4_data' if "phpbb2mysql_4_data" in [cookie.name for cookie in self.mcj]:
if [True for c in mcj if c.name == key and c.expires > time.time()]: # if cookie.expires < int(time.time())
logger.info("Local cookies is loaded") logger.info("Local cookies is loaded")
self.session.add_handler(HTTPCookieProcessor(mcj))
else: else:
logger.info("Local cookies expired or bad") logger.info("Local cookies expired or bad")
logger.debug(f"That we have: {[cookie for cookie in mcj]}") logger.debug(f"That we have: {[cookie for cookie in self.mcj]}")
mcj.clear() self.mcj.clear()
self.login(mcj) self.login()
except FileNotFoundError: except FileNotFoundError:
self.login(mcj) self.login()
def search(self, what, cat='all'): def search(self, what: str, cat: str = "all") -> None:
if self.error: if self.error:
self.pretty_error(what) self.pretty_error(what)
return None return None
@ -174,61 +200,86 @@ class NNMClub:
logger.debug(f"--- {time.time() - t0} seconds ---") logger.debug(f"--- {time.time() - t0} seconds ---")
logger.info(f"Found torrents: {total}") logger.info(f"Found torrents: {total}")
def download_torrent(self, url: str): def download_torrent(self, url: str) -> None:
# Download url # Download url
response = self._catch_error_request(url) response = self._request(url)
if self.error: if self.error:
self.pretty_error(url) self.pretty_error(url)
return None return None
# Create a torrent file # Create a torrent file
with NamedTemporaryFile(suffix='.torrent', delete=False) as fd: with NamedTemporaryFile(suffix=".torrent", delete=False) as fd:
fd.write(response) fd.write(response)
# return file path # return file path
logger.debug(fd.name + " " + url) logger.debug(fd.name + " " + url)
print(fd.name + " " + url) print(fd.name + " " + url)
def login(self, mcj): def login(self) -> None:
if self.error: if self.error:
return None return None
# if we wanna use https we mast add ssl=enable_ssl to cookie # if we wanna use https we mast add ssl=enable_ssl to cookie
mcj.set_cookie(Cookie(0, "ssl", "enable_ssl", None, False, self.mcj.set_cookie(Cookie(0, "ssl", "enable_ssl", None, False,
".nnmclub.to", True, False, "/", True, ".nnmclub.to", True, False, "/", True,
False, None, False, None, None, {})) False, None, False, None, None, {}))
self.session.add_handler(HTTPCookieProcessor(mcj))
response = self._catch_error_request(self.url_login) response = self._request(self.url_login)
if not response: if self.error:
return None
result = RE_CODE.search(response.decode("cp1251"))
if not result:
self.error = "Unexpected page content"
return None return None
code = RE_CODE.search(response.decode('cp1251'))[1]
form_data = {"username": config['username'], form_data = {"username": config.username,
"password": config['password'], "password": config.password,
"autologin": "on", "autologin": "on",
"code": code, "code": result[1],
"login": "Вход"} "login": "Вход"}
# so we first encode vals to cp1251 then do default decode whole string # encoding to cp1251 then do default encode whole string
data_encoded = urlencode( data_encoded = urlencode(form_data, encoding="cp1251").encode()
{k: v.encode('cp1251') for k, v in form_data.items()}
).encode()
self._catch_error_request(self.url_login, data_encoded) self._request(self.url_login, data_encoded)
if self.error: if self.error:
return None return None
logger.debug(f"That we have: {[cookie for cookie in mcj]}") logger.debug(f"That we have: {[cookie for cookie in self.mcj]}")
if 'phpbb2mysql_4_sid' in [cookie.name for cookie in mcj]: if "phpbb2mysql_4_sid" in [cookie.name for cookie in self.mcj]:
mcj.save(FILE_C, ignore_discard=True, ignore_expires=True) self.mcj.save(FILE_C, ignore_discard=True, ignore_expires=True)
logger.info('We successfully authorized') logger.info("We successfully authorized")
else: else:
self.error = "We not authorized, please check your credentials!" self.error = "We not authorized, please check your credentials!"
logger.warning(self.error) logger.warning(self.error)
def draw(self, html: str): def searching(self, query: str, first: bool = False) -> Union[None, int]:
torrents = RE_TORRENTS.findall(html) response = self._request(query)
if self.error:
return None
page, torrents_found = response.decode("cp1251"), -1
if first:
# check login status
if f"Выход [ {config.username} ]" not 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:
return 0
self.draw(page)
return torrents_found
for tor in torrents: def draw(self, html: str) -> None:
for tor in RE_TORRENTS.findall(html):
torrent_date = "" torrent_date = ""
if config['torrentDate']: if config.torrent_date:
_loc = time.localtime(int(tor[6])) _loc = time.localtime(int(tor[6]))
torrent_date = f'[{time.strftime("%y.%m.%d", _loc)}] ' torrent_date = f'[{time.strftime("%y.%m.%d", _loc)}] '
@ -241,53 +292,32 @@ class NNMClub:
"seeds": tor[4], "seeds": tor[4],
"leech": tor[5] "leech": tor[5]
}) })
del torrents
def searching(self, query, first=False):
response = self._catch_error_request(query)
if not response:
return None
page, torrents_found = response.decode('cp1251'), -1
if first:
# check login status
if f'Выход [ {config["username"]} ]' not in page:
logger.debug("Looks like we lost session id, lets login")
self.login(MozillaCookieJar())
if self.error:
return None
# firstly we check if there is a result
torrents_found = int(RE_RESULTS.search(page)[1] or 0)
if not torrents_found:
return 0
self.draw(page)
return torrents_found
def _catch_error_request(self, url=None, data=None, repeated=False):
url = url or self.url
def _request(
self, url: str, data: Optional[bytes] = None, repeated: bool = False
) -> Union[bytes, None]:
try: try:
with self.session.open(url, data, 5) as r: with self.session.open(url, data, 5) as r:
# checking that tracker isn't blocked # checking that tracker isn't blocked
if r.url.startswith((self.url, self.url_dl)): if r.geturl().startswith((self.url, self.url_dl)):
return r.read() return r.read()
raise URLError(f"{self.url} is blocked. Try another proxy.") self.error = f"{url} is blocked. Try another proxy."
except (socket.error, socket.timeout) as err:
if not repeated:
return self._catch_error_request(url, data, True)
logger.error(err)
self.error = f"{self.url} is not response! Maybe it is blocked."
if "no host given" in err.args:
self.error = "Proxy is bad, try another!"
except (URLError, HTTPError) as err: except (URLError, HTTPError) as err:
logger.error(err.reason) logger.error(err.reason)
self.error = err.reason error = str(err.reason)
if hasattr(err, 'code'): if "timed out" in error and not repeated:
logger.debug("Repeating request...")
return self._request(url, data, True)
if "no host given" in error:
self.error = "Proxy is bad, try another!"
elif hasattr(err, "code"):
self.error = f"Request to {url} failed with status: {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 return None
def pretty_error(self, what): def pretty_error(self, what: str) -> None:
prettyPrinter({"engine_url": self.url, prettyPrinter({"engine_url": self.url,
"desc_link": "https://github.com/imDMG/qBt_SE", "desc_link": "https://github.com/imDMG/qBt_SE",
"name": f"[{unquote(what)}][Error]: {self.error}", "name": f"[{unquote(what)}][Error]: {self.error}",
@ -303,9 +333,9 @@ class NNMClub:
nnmclub = NNMClub nnmclub = NNMClub
if __name__ == "__main__": if __name__ == "__main__":
if BASEDIR.parent.joinpath('settings_gui.py').exists(): if BASEDIR.parent.joinpath("settings_gui.py").exists():
from settings_gui import EngineSettingsGUI from settings_gui import EngineSettingsGUI
EngineSettingsGUI(FILENAME) EngineSettingsGUI(FILENAME)
engine = nnmclub() engine = nnmclub()
engine.search('doctor') engine.search("doctor")

192
engines/rutor.py

@ -1,4 +1,4 @@
# VERSION: 1.3 # VERSION: 1.4
# AUTHORS: imDMG [imdmgg@gmail.com] # AUTHORS: imDMG [imdmgg@gmail.com]
# Rutor.org search engine plugin for qBittorrent # Rutor.org search engine plugin for qBittorrent
@ -7,13 +7,14 @@ import base64
import json import json
import logging import logging
import re import re
import socket
import sys import sys
import time import time
from concurrent.futures.thread import ThreadPoolExecutor from concurrent.futures.thread import ThreadPoolExecutor
from dataclasses import dataclass, field
from html import unescape from html import unescape
from pathlib import Path from pathlib import Path
from tempfile import NamedTemporaryFile from tempfile import NamedTemporaryFile
from typing import Optional, Union
from urllib.error import URLError, HTTPError from urllib.error import URLError, HTTPError
from urllib.parse import unquote from urllib.parse import unquote
from urllib.request import build_opener, ProxyHandler from urllib.request import build_opener, ProxyHandler
@ -24,29 +25,16 @@ except ImportError:
sys.path.insert(0, str(Path(__file__).parent.parent.absolute())) sys.path.insert(0, str(Path(__file__).parent.parent.absolute()))
from novaprinter import prettyPrinter from novaprinter import prettyPrinter
# default config
config = {
"torrentDate": True,
"username": "USERNAME",
"password": "PASSWORD",
"proxy": False,
"proxies": {
"http": "",
"https": ""
},
"ua": "Mozilla/5.0 (X11; Linux i686; rv:38.0) Gecko/20100101 Firefox/38.0 "
}
FILE = Path(__file__) FILE = Path(__file__)
BASEDIR = FILE.parent.absolute() BASEDIR = FILE.parent.absolute()
FILENAME = FILE.name[:-3] FILENAME = FILE.name[:-3]
FILE_J, FILE_C = [BASEDIR / (FILENAME + fl) for fl in ['.json', '.cookie']] FILE_J, FILE_C = [BASEDIR / (FILENAME + fl) for fl in [".json", ".cookie"]]
PAGES = 100 PAGES = 100
def rng(t): def rng(t: int) -> range:
return range(1, -(-t // PAGES)) return range(1, -(-t // PAGES))
@ -54,8 +42,8 @@ RE_TORRENTS = re.compile(
r'(?:gai|tum)"><td>(.+?)</td.+?href="/(torrent/(\d+).+?)">(.+?)</a.+?right"' r'(?:gai|tum)"><td>(.+?)</td.+?href="/(torrent/(\d+).+?)">(.+?)</a.+?right"'
r'>([.\d]+&nbsp;\w+)</td.+?alt="S"\s/>(.+?)</s.+?red">(.+?)</s', re.S r'>([.\d]+&nbsp;\w+)</td.+?alt="S"\s/>(.+?)</s.+?red">(.+?)</s', re.S
) )
RE_RESULTS = re.compile(r'</b>\sРезультатов\sпоиска\s(\d{1,4})\s', re.S) RE_RESULTS = re.compile(r"</b>\sРезультатов\sпоиска\s(\d{1,4})\s", re.S)
PATTERNS = ('%ssearch/%i/%i/000/0/%s',) PATTERNS = ("%ssearch/%i/%i/000/0/%s",)
# base64 encoded image # base64 encoded image
ICON = ("AAABAAEAEBAAAAEAGABoAwAAFgAAACgAAAAQAAAAIAAAAAEAGAAAAAAAAAAAAAAAAAAAAA" ICON = ("AAABAAEAEBAAAAEAGABoAwAAFgAAACgAAAAQAAAAIAAAAAEAGAAAAAAAAAAAAAAAAAAAAA"
@ -86,31 +74,71 @@ logging.basicConfig(
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
try:
config = json.loads(FILE_J.read_text()) @dataclass
logger.debug("Config is loaded.") class Config:
except OSError as e: # username: str = "USERNAME"
logger.error(e) # password: str = "PASSWORD"
# if file doesn't exist, we'll create it torrent_date: bool = True
FILE_J.write_text(json.dumps(config, indent=4, sort_keys=False)) # magnet: bool = False
# also write/rewrite ico file proxy: bool = False
(BASEDIR / (FILENAME + '.ico')).write_bytes(base64.b64decode(ICON)) # dynamic_proxy: bool = True
logger.debug("Write files.") proxies: dict = field(default_factory=lambda: {"http": "", "https": ""})
ua: str = ("Mozilla/5.0 (X11; Linux i686; rv:38.0) Gecko/20100101 "
"Firefox/38.0 ")
def __post_init__(self):
try:
if not self._validate_json(json.loads(FILE_J.read_text())):
raise ValueError("Incorrect json scheme.")
except Exception as e:
logger.error(e)
FILE_J.write_text(self.to_str())
(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)
def to_dict(self) -> dict:
return {self._to_camel(k): v for k, v in self.__dict__.items()}
def _validate_json(self, obj: dict) -> bool:
is_valid = True
for k, v in self.__dict__.items():
_val = obj.get(self._to_camel(k))
if type(_val) is not type(v):
is_valid = False
continue
if type(_val) is dict:
for dk, dv in v.items():
if type(_val.get(dk)) is not type(dv):
_val[dk] = dv
is_valid = False
setattr(self, k, _val)
return is_valid
@staticmethod
def _to_camel(s: str) -> str:
return "".join(x.title() if i else x
for i, x in enumerate(s.split("_")))
config = Config()
class Rutor: class Rutor:
name = 'Rutor' name = "Rutor"
url = 'http://rutor.info/' url = "http://rutor.info/"
url_dl = url.replace("//", "//d.") + "download/" url_dl = url.replace("//", "//d.") + "download/"
supported_categories = {'all': 0, supported_categories = {"all": 0,
'movies': 1, "movies": 1,
'tv': 6, "tv": 6,
'music': 2, "music": 2,
'games': 8, "games": 8,
'anime': 10, "anime": 10,
'software': 9, "software": 9,
'pictures': 3, "pictures": 3,
'books': 11} "books": 11}
def __init__(self): def __init__(self):
# error message # error message
@ -120,17 +148,17 @@ class Rutor:
self.session = build_opener() self.session = build_opener()
# add proxy handler if needed # add proxy handler if needed
if config['proxy']: if config.proxy:
if any(config['proxies'].values()): if any(config.proxies.values()):
self.session.add_handler(ProxyHandler(config['proxies'])) self.session.add_handler(ProxyHandler(config.proxies))
logger.debug("Proxy is set!") logger.debug("Proxy is set!")
else: else:
self.error = "Proxy enabled, but not set!" self.error = "Proxy enabled, but not set!"
# change user-agent # change user-agent
self.session.addheaders = [('User-Agent', config['ua'])] self.session.addheaders = [("User-Agent", config.ua)]
def search(self, what, cat='all'): def search(self, what: str, cat: str = "all") -> None:
if self.error: if self.error:
self.pretty_error(what) self.pretty_error(what)
return None return None
@ -144,7 +172,7 @@ class Rutor:
return None return None
# do async requests # do async requests
if total > PAGES: if total > PAGES:
query = query.replace('h/0', 'h/%i') query = query.replace("h/0", "h/%i")
qrs = [query % x for x in rng(total)] qrs = [query % x for x in rng(total)]
with ThreadPoolExecutor(len(qrs)) as executor: with ThreadPoolExecutor(len(qrs)) as executor:
executor.map(self.searching, qrs, timeout=30) executor.map(self.searching, qrs, timeout=30)
@ -152,47 +180,50 @@ class Rutor:
logger.debug(f"--- {time.time() - t0} seconds ---") logger.debug(f"--- {time.time() - t0} seconds ---")
logger.info(f"Found torrents: {total}") logger.info(f"Found torrents: {total}")
def download_torrent(self, url: str): def download_torrent(self, url: str) -> None:
# Download url # Download url
response = self._catch_error_request(url) response = self._request(url)
if self.error: if self.error:
self.pretty_error(url) self.pretty_error(url)
return None return None
# Create a torrent file # Create a torrent file
with NamedTemporaryFile(suffix='.torrent', delete=False) as fd: with NamedTemporaryFile(suffix=".torrent", delete=False) as fd:
fd.write(response) fd.write(response)
# return file path # return file path
logger.debug(fd.name + " " + url) logger.debug(fd.name + " " + url)
print(fd.name + " " + url) print(fd.name + " " + url)
def searching(self, query, first=False): def searching(self, query: str, first: bool = False) -> Union[None, int]:
response = self._catch_error_request(query) response = self._request(query)
if not response: if self.error:
return None return None
page, torrents_found = response.decode(), -1 page, torrents_found = response.decode(), -1
if first: if first:
# firstly we check if there is a result # firstly we check if there is a result
torrents_found = int(RE_RESULTS.search(page)[1]) result = RE_RESULTS.search(page)
if not result:
self.error = "Unexpected page content"
return None
torrents_found = int(result[1])
if not torrents_found: if not torrents_found:
return 0 return 0
self.draw(page) self.draw(page)
return torrents_found return torrents_found
def draw(self, html: str): def draw(self, html: str) -> None:
torrents = RE_TORRENTS.findall(html) for tor in RE_TORRENTS.findall(html):
for tor in torrents:
torrent_date = "" torrent_date = ""
if config['torrentDate']: if config.torrent_date:
# replace names month # replace names month
months = ('Янв', 'Фев', 'Мар', 'Апр', 'Май', 'Июн', months = ("Янв", "Фев", "Мар", "Апр", "Май", "Июн",
'Июл', 'Авг', 'Сен', 'Окт', 'Ноя', 'Дек') "Июл", "Авг", "Сен", "Окт", "Ноя", "Дек")
ct = [unescape(tor[0].replace(m, f"{i:02d}")) ct = [unescape(tor[0].replace(m, f"{i:02d}"))
for i, m in enumerate(months, 1) if m in tor[0]][0] for i, m in enumerate(months, 1) if m in tor[0]][0]
ct = time.strftime("%y.%m.%d", time.strptime(ct, "%d %m %y")) ct = time.strftime("%y.%m.%d", time.strptime(ct, "%d %m %y"))
torrent_date = f'[{ct}] ' torrent_date = f"[{ct}] "
prettyPrinter({ prettyPrinter({
"engine_url": self.url, "engine_url": self.url,
@ -203,33 +234,32 @@ class Rutor:
"seeds": unescape(tor[5]), "seeds": unescape(tor[5]),
"leech": unescape(tor[6]) "leech": unescape(tor[6])
}) })
del torrents
def _catch_error_request(self, url=None, data=None, repeated=False):
url = url or self.url
def _request(
self, url: str, data: Optional[bytes] = None, repeated: bool = False
) -> Union[bytes, None]:
try: try:
with self.session.open(url, data, 5) as r: with self.session.open(url, data, 5) as r:
# checking that tracker isn't blocked # checking that tracker isn't blocked
if r.url.startswith((self.url, self.url_dl)): if r.geturl().startswith((self.url, self.url_dl)):
return r.read() return r.read()
raise URLError(f"{self.url} is blocked. Try another proxy.") self.error = f"{url} is blocked. Try another proxy."
except (socket.error, socket.timeout) as err:
if not repeated:
return self._catch_error_request(url, data, True)
logger.error(err)
self.error = f"{self.url} is not response! Maybe it is blocked."
if "no host given" in err.args:
self.error = "Proxy is bad, try another!"
except (URLError, HTTPError) as err: except (URLError, HTTPError) as err:
logger.error(err.reason) logger.error(err.reason)
self.error = err.reason error = str(err.reason)
if hasattr(err, 'code'): if "timed out" in error and not repeated:
logger.debug("Repeating request...")
return self._request(url, data, True)
if "no host given" in error:
self.error = "Proxy is bad, try another!"
elif hasattr(err, "code"):
self.error = f"Request to {url} failed with status: {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 return None
def pretty_error(self, what): def pretty_error(self, what: str) -> None:
prettyPrinter({"engine_url": self.url, prettyPrinter({"engine_url": self.url,
"desc_link": "https://github.com/imDMG/qBt_SE", "desc_link": "https://github.com/imDMG/qBt_SE",
"name": f"[{unquote(what)}][Error]: {self.error}", "name": f"[{unquote(what)}][Error]: {self.error}",
@ -245,9 +275,9 @@ class Rutor:
rutor = Rutor rutor = Rutor
if __name__ == "__main__": if __name__ == "__main__":
# if BASEDIR.parent.joinpath('settings_gui.py').exists(): if BASEDIR.parent.joinpath("settings_gui.py").exists():
# from settings_gui import EngineSettingsGUI from settings_gui import EngineSettingsGUI
#
# EngineSettingsGUI(FILENAME) EngineSettingsGUI(FILENAME)
engine = rutor() engine = rutor()
engine.search('doctor') engine.search("doctor")

233
engines/rutracker.py

@ -1,4 +1,4 @@
# VERSION: 1.5 # VERSION: 1.6
# AUTHORS: imDMG [imdmgg@gmail.com] # AUTHORS: imDMG [imdmgg@gmail.com]
# rutracker.org search engine plugin for qBittorrent # rutracker.org search engine plugin for qBittorrent
@ -7,14 +7,15 @@ import base64
import json import json
import logging import logging
import re import re
import socket
import sys import sys
import time import time
from concurrent.futures import ThreadPoolExecutor from concurrent.futures import ThreadPoolExecutor
from dataclasses import dataclass, field
from html import unescape from html import unescape
from http.cookiejar import Cookie, MozillaCookieJar from http.cookiejar import Cookie, MozillaCookieJar
from pathlib import Path from pathlib import Path
from tempfile import NamedTemporaryFile from tempfile import NamedTemporaryFile
from typing import Optional, Union
from urllib.error import URLError, HTTPError from urllib.error import URLError, HTTPError
from urllib.parse import urlencode, unquote from urllib.parse import urlencode, unquote
from urllib.request import build_opener, HTTPCookieProcessor, ProxyHandler from urllib.request import build_opener, HTTPCookieProcessor, ProxyHandler
@ -25,29 +26,16 @@ except ImportError:
sys.path.insert(0, str(Path(__file__).parent.parent.absolute())) sys.path.insert(0, str(Path(__file__).parent.parent.absolute()))
from novaprinter import prettyPrinter from novaprinter import prettyPrinter
# default config
config = {
"torrentDate": True,
"username": "USERNAME",
"password": "PASSWORD",
"proxy": False,
"proxies": {
"http": "",
"https": ""
},
"ua": "Mozilla/5.0 (X11; Linux i686; rv:38.0) Gecko/20100101 Firefox/38.0 "
}
FILE = Path(__file__) FILE = Path(__file__)
BASEDIR = FILE.parent.absolute() BASEDIR = FILE.parent.absolute()
FILENAME = FILE.name[:-3] FILENAME = FILE.name[:-3]
FILE_J, FILE_C = [BASEDIR / (FILENAME + fl) for fl in ['.json', '.cookie']] FILE_J, FILE_C = [BASEDIR / (FILENAME + fl) for fl in [".json", ".cookie"]]
PAGES = 50 PAGES = 50
def rng(t): def rng(t: int) -> range:
return range(PAGES, -(-t // PAGES) * PAGES, PAGES) return range(PAGES, -(-t // PAGES) * PAGES, PAGES)
@ -56,8 +44,8 @@ RE_TORRENTS = re.compile(
r'.+?data-ts_text="([-0-9]+?)">.+?Личи">(\d+?)</.+?data-ts_text="(\d+?)">', r'.+?data-ts_text="([-0-9]+?)">.+?Личи">(\d+?)</.+?data-ts_text="(\d+?)">',
re.S re.S
) )
RE_RESULTS = re.compile(r'Результатов\sпоиска:\s(\d{1,3})\s<span', re.S) RE_RESULTS = re.compile(r"Результатов\sпоиска:\s(\d{1,3})\s<span", re.S)
PATTERNS = ('%s/tracker.php?nm=%s&c=%s', "%s&start=%s") PATTERNS = ("%stracker.php?nm=%s&c=%s", "%s&start=%s")
# base64 encoded image # base64 encoded image
ICON = ("AAABAAEAEBAAAAEAIABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAAAAAABMLAAATCw" ICON = ("AAABAAEAEBAAAAEAIABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAAAAAABMLAAATCw"
@ -92,60 +80,99 @@ logging.basicConfig(
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
try:
config = json.loads(FILE_J.read_text())
logger.debug("Config is loaded.")
except OSError as e:
logger.error(e)
# if file doesn't exist, we'll create it
FILE_J.write_text(json.dumps(config, indent=4, sort_keys=False))
# also write/rewrite ico file
(BASEDIR / (FILENAME + '.ico')).write_bytes(base64.b64decode(ICON))
logger.debug("Write files.")
@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": ""})
ua: str = ("Mozilla/5.0 (X11; Linux i686; rv:38.0) Gecko/20100101 "
"Firefox/38.0 ")
class Rutracker: def __post_init__(self):
name = 'Rutracker' try:
url = 'https://rutracker.org/forum/' if not self._validate_json(json.loads(FILE_J.read_text())):
url_dl = url + 'dl.php?t=' raise ValueError("Incorrect json scheme.")
url_login = url + 'login.php' except Exception as e:
supported_categories = {'all': '-1'} logger.error(e)
FILE_J.write_text(self.to_str())
(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)
def to_dict(self) -> dict:
return {self._to_camel(k): v for k, v in self.__dict__.items()}
def _validate_json(self, obj: dict) -> bool:
is_valid = True
for k, v in self.__dict__.items():
_val = obj.get(self._to_camel(k))
if type(_val) is not type(v):
is_valid = False
continue
if type(_val) is dict:
for dk, dv in v.items():
if type(_val.get(dk)) is not type(dv):
_val[dk] = dv
is_valid = False
setattr(self, k, _val)
return is_valid
@staticmethod
def _to_camel(s: str) -> str:
return "".join(x.title() if i else x
for i, x in enumerate(s.split("_")))
config = Config()
def __init__(self):
# error message
self.error = None
# establish connection class Rutracker:
self.session = build_opener() name = "Rutracker"
url = "https://rutracker.org/forum/"
url_dl = url + "dl.php?t="
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 # add proxy handler if needed
if config['proxy']: if config.proxy:
if any(config['proxies'].values()): if any(config.proxies.values()):
self.session.add_handler(ProxyHandler(config['proxies'])) self.session.add_handler(ProxyHandler(config.proxies))
logger.debug("Proxy is set!") logger.debug("Proxy is set!")
else: else:
self.error = "Proxy enabled, but not set!" self.error = "Proxy enabled, but not set!"
# change user-agent # change user-agent
self.session.addheaders = [('User-Agent', config['ua'])] self.session.addheaders = [("User-Agent", config.ua)]
# load local cookies # load local cookies
mcj = MozillaCookieJar()
try: try:
mcj.load(FILE_C, ignore_discard=True) self.mcj.load(FILE_C, ignore_discard=True)
if 'bb_session' in [cookie.name for cookie in mcj]: if "bb_session" in [cookie.name for cookie in self.mcj]:
# if cookie.expires < int(time.time()) # if cookie.expires < int(time.time())
logger.info("Local cookies is loaded") logger.info("Local cookies is loaded")
self.session.add_handler(HTTPCookieProcessor(mcj))
else: else:
logger.info("Local cookies expired or bad") logger.info("Local cookies expired or bad")
logger.debug(f"That we have: {[cookie for cookie in mcj]}") logger.debug(f"That we have: {[cookie for cookie in self.mcj]}")
mcj.clear() self.mcj.clear()
self.login(mcj) self.login()
except FileNotFoundError: except FileNotFoundError:
self.login(mcj) self.login()
def search(self, what, cat='all'): def search(self, what: str, cat: str = "all") -> None:
if self.error: if self.error:
self.pretty_error(what) self.pretty_error(what)
return None return None
@ -166,79 +193,81 @@ class Rutracker:
logger.debug(f"--- {time.time() - t0} seconds ---") logger.debug(f"--- {time.time() - t0} seconds ---")
logger.info(f"Found torrents: {total}") logger.info(f"Found torrents: {total}")
def download_torrent(self, url: str): def download_torrent(self, url: str) -> None:
# Download url # Download url
response = self._catch_error_request(url) response = self._request(url)
if self.error: if self.error:
self.pretty_error(url) self.pretty_error(url)
return None return None
# Create a torrent file # Create a torrent file
with NamedTemporaryFile(suffix='.torrent', delete=False) as fd: with NamedTemporaryFile(suffix=".torrent", delete=False) as fd:
fd.write(response) fd.write(response)
# return file path # return file path
logger.debug(fd.name + " " + url) logger.debug(fd.name + " " + url)
print(fd.name + " " + url) print(fd.name + " " + url)
def login(self, mcj): def login(self) -> None:
if self.error: if self.error:
return None return None
# if we wanna use https we mast add bb_ssl=1 to cookie # if we wanna use https we mast add bb_ssl=1 to cookie
mcj.set_cookie(Cookie(0, "bb_ssl", "1", None, False, ".rutracker.org", self.mcj.set_cookie(Cookie(0, "bb_ssl", "1", None, False,
True, True, "/forum/", True, True, ".rutracker.org", True, True, "/forum/",
None, False, None, None, {})) True, True, None, False, None, None, {}))
self.session.add_handler(HTTPCookieProcessor(mcj))
form_data = {"login_username": config['username'], form_data = {"login_username": config.username,
"login_password": config['password'], "login_password": config.password,
"login": "Вход"} "login": "Вход"}
logger.debug(f"Login. Data before: {form_data}") logger.debug(f"Login. Data before: {form_data}")
# so we first encode vals to cp1251 then do default decode whole string # encoding to cp1251 then do default encode whole string
data_encoded = urlencode( data_encoded = urlencode(form_data, encoding="cp1251").encode()
{k: v.encode('cp1251') for k, v in form_data.items()}
).encode()
logger.debug(f"Login. Data after: {data_encoded}") logger.debug(f"Login. Data after: {data_encoded}")
self._catch_error_request(self.url_login, data_encoded) self._request(self.url_login, data_encoded)
if self.error: if self.error:
return None return None
logger.debug(f"That we have: {[cookie for cookie in mcj]}") logger.debug(f"That we have: {[cookie for cookie in self.mcj]}")
if 'bb_session' in [cookie.name for cookie in mcj]: if "bb_session" in [cookie.name for cookie in self.mcj]:
mcj.save(FILE_C, ignore_discard=True, ignore_expires=True) self.mcj.save(FILE_C, ignore_discard=True, ignore_expires=True)
logger.info("We successfully authorized") logger.info("We successfully authorized")
else: else:
self.error = "We not authorized, please check your credentials!" self.error = "We not authorized, please check your credentials!"
logger.warning(self.error) logger.warning(self.error)
def searching(self, query, first=False): def searching(self, query: str, first: bool = False) -> Union[None, int]:
response = self._catch_error_request(query) response = self._request(query)
if not response: if self.error:
return None return None
page, torrents_found = response.decode('cp1251'), -1 page, torrents_found = response.decode("cp1251"), -1
if first: if first:
if "log-out-icon" not in page: if "log-out-icon" not in page:
logger.debug("Looks like we lost session id, lets login") logger.debug("Looks like we lost session id, lets login")
self.login(MozillaCookieJar()) self.mcj.clear()
self.login()
if self.error: if self.error:
return None return None
# retry request because guests cant search # retry request because guests cant search
response = self._catch_error_request(query) response = self._request(query)
if not response: if self.error:
return None return None
page = response.decode('cp1251') page = response.decode("cp1251")
# firstly we check if there is a result # firstly we check if there is a result
torrents_found = int(RE_RESULTS.search(page)[1]) result = RE_RESULTS.search(page)
if not result:
self.error = "Unexpected page content"
return None
torrents_found = int(result[1])
if not torrents_found: if not torrents_found:
return 0 return 0
self.draw(page) self.draw(page)
return torrents_found return torrents_found
def draw(self, html: str): def draw(self, html: str) -> None:
torrents = RE_TORRENTS.findall(html) for tor in RE_TORRENTS.findall(html):
for tor in torrents:
local = time.strftime("%y.%m.%d", time.localtime(int(tor[5]))) local = time.strftime("%y.%m.%d", time.localtime(int(tor[5])))
torrent_date = f"[{local}] " if config['torrentDate'] else "" torrent_date = f"[{local}] " if config.torrent_date else ""
prettyPrinter({ prettyPrinter({
"engine_url": self.url, "engine_url": self.url,
@ -249,33 +278,33 @@ class Rutracker:
"seeds": max(0, int(tor[3])), "seeds": max(0, int(tor[3])),
"leech": tor[4] "leech": tor[4]
}) })
del torrents
def _catch_error_request(self, url=None, data=None, repeated=False):
url = url or self.url
def _request(
self, url: str, data: Optional[bytes] = None, repeated: bool = False
) -> Union[bytes, None]:
try: try:
with self.session.open(url, data, 5) as r: with self.session.open(url, data, 5) as r:
# checking that tracker isn't blocked # checking that tracker isn't blocked
if r.url.startswith((self.url, self.url_dl)): if r.geturl().startswith((self.url, self.url_dl)):
return r.read() return r.read()
raise URLError(f"{self.url} is blocked. Try another proxy.") self.error = f"{url} is blocked. Try another proxy."
except (socket.error, socket.timeout) as err:
if not repeated:
return self._catch_error_request(url, data, True)
logger.error(err)
self.error = f"{self.url} is not response! Maybe it is blocked."
if "no host given" in err.args:
self.error = "Proxy is bad, try another!"
except (URLError, HTTPError) as err: except (URLError, HTTPError) as err:
logger.error(err.reason) logger.error(err.reason)
self.error = err.reason error = str(err.reason)
if hasattr(err, 'code'): print(err.info())
if "timed out" in error and not repeated:
logger.debug("Repeating request...")
return self._request(url, data, True)
if "no host given" in error:
self.error = "Proxy is bad, try another!"
elif hasattr(err, "code"):
self.error = f"Request to {url} failed with status: {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 return None
def pretty_error(self, what): def pretty_error(self, what: str) -> None:
prettyPrinter({"engine_url": self.url, prettyPrinter({"engine_url": self.url,
"desc_link": "https://github.com/imDMG/qBt_SE", "desc_link": "https://github.com/imDMG/qBt_SE",
"name": f"[{unquote(what)}][Error]: {self.error}", "name": f"[{unquote(what)}][Error]: {self.error}",
@ -291,9 +320,9 @@ class Rutracker:
rutracker = Rutracker rutracker = Rutracker
if __name__ == "__main__": if __name__ == "__main__":
if BASEDIR.parent.joinpath('settings_gui.py').exists(): if BASEDIR.parent.joinpath("settings_gui.py").exists():
from settings_gui import EngineSettingsGUI from settings_gui import EngineSettingsGUI
EngineSettingsGUI(FILENAME) EngineSettingsGUI(FILENAME)
engine = rutracker() engine = rutracker()
engine.search('doctor') engine.search("doctor")

32
settings_gui.py

@ -37,12 +37,12 @@ class EngineSettingsGUI:
ttk.Label(mainframe, text="Password:").grid( ttk.Label(mainframe, text="Password:").grid(
column=0, row=1, sticky=tk.W, rowspan=2) column=0, row=1, sticky=tk.W, rowspan=2)
ttk.Entry(mainframe, width=25, textvariable=self.username).grid( ttk.Entry(mainframe, width=25, textvariable=self.username, state=(
column=1, row=0, sticky=tk.EW, padx=(0, 5) ("!" if self.config.get("username") else "") + tk.DISABLED)
) ).grid(column=1, row=0, sticky=tk.EW, padx=(0, 5))
ttk.Entry(mainframe, width=25, textvariable=self.password).grid( ttk.Entry(mainframe, width=25, textvariable=self.password, state=(
column=1, row=1, rowspan=2, sticky=tk.EW, padx=(0, 5) ("!" if self.config.get("password") else "") + tk.DISABLED)
) ).grid(column=1, row=1, rowspan=2, sticky=tk.EW, padx=(0, 5))
ttk.Checkbutton( ttk.Checkbutton(
mainframe, text="Date before torrent", variable=self.date, mainframe, text="Date before torrent", variable=self.date,
@ -50,7 +50,8 @@ class EngineSettingsGUI:
).grid(column=2, row=0, sticky=tk.W) ).grid(column=2, row=0, sticky=tk.W)
ttk.Checkbutton( ttk.Checkbutton(
mainframe, text="Use magnet link", variable=self.magnet, mainframe, text="Use magnet link", variable=self.magnet,
onvalue=True onvalue=True, state=(
("!" if self.config.get("magnet") else "") + tk.DISABLED)
).grid(column=2, row=1, sticky=tk.W) ).grid(column=2, row=1, sticky=tk.W)
ttk.Checkbutton( ttk.Checkbutton(
mainframe, text="Proxy", variable=self.proxy, onvalue=True, mainframe, text="Proxy", variable=self.proxy, onvalue=True,
@ -83,17 +84,19 @@ class EngineSettingsGUI:
self.https_entry.state([state]) self.https_entry.state([state])
def close(self) -> None: def close(self) -> None:
if not (self.username.get() or self.password.get()): if self.config.get("username") and self.config.get("password"):
messagebox.showinfo("Error", "Some fields is empty!") if not (self.username.get() or self.password.get()):
return None messagebox.showinfo("Error", "Some fields is empty!")
return None
if self.proxy.get() and not (self.http_entry.get() if self.proxy.get() and not (self.http_entry.get()
or self.https_entry.get()): or self.https_entry.get()):
messagebox.showinfo("Error", "Some fields is empty!") messagebox.showinfo("Error", "Some fields is empty!")
return None return None
self.config["username"] = self.username.get() if self.config.get("username") and self.config.get("password"):
self.config["password"] = self.password.get() self.config["username"] = self.username.get()
self.config["password"] = self.password.get()
self.config["proxy"] = self.proxy.get() self.config["proxy"] = self.proxy.get()
if self.config["proxy"]: if self.config["proxy"]:
self.config["proxies"] = { self.config["proxies"] = {
@ -101,7 +104,8 @@ class EngineSettingsGUI:
"https": self.https_entry.get() "https": self.https_entry.get()
} }
self.config["torrentDate"] = self.date.get() self.config["torrentDate"] = self.date.get()
self.config["magnet"] = self.magnet.get() if self.config.get("magnet"):
self.config["magnet"] = self.magnet.get()
self.cfg_file.write_text( self.cfg_file.write_text(
json.dumps(self.config, indent=4, sort_keys=False) json.dumps(self.config, indent=4, sort_keys=False)
) )
@ -109,5 +113,5 @@ class EngineSettingsGUI:
if __name__ == "__main__": if __name__ == "__main__":
settings = EngineSettingsGUI("kinozal") settings = EngineSettingsGUI("engines/kinozal")
print(settings.config) print(settings.config)

Loading…
Cancel
Save