mirror of
https://github.com/YGGverse/qBt_SE.git
synced 2025-02-06 20:04:31 +00:00
update README.md, settings_gui.py, kinozal.py, nnmclub.py, rutor.py, rutracker.py
This commit is contained in:
parent
5e98388208
commit
638d185520
30
README.md
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/)
|
||||
# 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.
|
||||
|
||||
## 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.
|
||||
|
||||
## 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.
|
||||
|
||||
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`
|
||||
|
||||
## 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.
|
||||
|
||||
## Installation
|
||||
**For fresh installation.**
|
||||
According of the name of search plugin:
|
||||
* 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).
|
||||
* _After installation you can change your settings with `*.json` file which will be created automatically in:_
|
||||
* Windows: `%localappdata%\qBittorrent\nova3\engines`
|
||||
* Linux: `~/.local/share/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.**
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
# VERSION: 2.7
|
||||
# VERSION: 2.9
|
||||
# AUTHORS: imDMG [imdmgg@gmail.com]
|
||||
|
||||
# Kinozal.tv search engine plugin for qBittorrent
|
||||
@ -115,6 +115,11 @@ class Config:
|
||||
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
|
||||
|
||||
@ -225,7 +230,7 @@ class Kinozal:
|
||||
|
||||
form_data = {"username": config.username, "password": config.password}
|
||||
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()
|
||||
logger.debug(f"Login. Data after: {data_encoded}")
|
||||
|
||||
@ -268,11 +273,10 @@ class Kinozal:
|
||||
return torrents_found
|
||||
|
||||
def draw(self, html: str) -> None:
|
||||
torrents = RE_TORRENTS.findall(html)
|
||||
_part = partial(time.strftime, "%y.%m.%d")
|
||||
# yeah this is yesterday
|
||||
yesterday = _part(time.localtime(time.time() - 86400))
|
||||
for tor in torrents:
|
||||
for tor in RE_TORRENTS.findall(html):
|
||||
torrent_date = ""
|
||||
if config.torrent_date:
|
||||
ct = tor[5].split()[0]
|
||||
@ -296,10 +300,9 @@ class Kinozal:
|
||||
"seeds": tor[3],
|
||||
"leech": tor[4]
|
||||
})
|
||||
del torrents
|
||||
|
||||
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]:
|
||||
try:
|
||||
with self.session.open(url, data, 5) as r:
|
||||
|
@ -1,4 +1,4 @@
|
||||
# VERSION: 2.8
|
||||
# VERSION: 2.9
|
||||
# AUTHORS: imDMG [imdmgg@gmail.com]
|
||||
|
||||
# NoNaMe-Club search engine plugin for qBittorrent
|
||||
@ -7,14 +7,15 @@ import base64
|
||||
import json
|
||||
import logging
|
||||
import re
|
||||
import socket
|
||||
import sys
|
||||
import time
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
from dataclasses import dataclass, field
|
||||
from html import unescape
|
||||
from http.cookiejar import Cookie, MozillaCookieJar
|
||||
from pathlib import Path
|
||||
from tempfile import NamedTemporaryFile
|
||||
from typing import Optional, Union
|
||||
from urllib.error import URLError, HTTPError
|
||||
from urllib.parse import urlencode, unquote
|
||||
from urllib.request import build_opener, HTTPCookieProcessor, ProxyHandler
|
||||
@ -25,30 +26,16 @@ except ImportError:
|
||||
sys.path.insert(0, str(Path(__file__).parent.parent.absolute()))
|
||||
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__)
|
||||
BASEDIR = FILE.parent.absolute()
|
||||
|
||||
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
|
||||
|
||||
|
||||
def rng(t):
|
||||
def rng(t: int) -> range:
|
||||
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_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
|
||||
ICON = ("AAABAAEAEBAAAAEAIABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAAAAAAAAAAAAAAA"
|
||||
@ -93,66 +80,105 @@ logging.basicConfig(
|
||||
|
||||
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 ")
|
||||
|
||||
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 NNMClub:
|
||||
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'}
|
||||
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):
|
||||
# error message
|
||||
self.error = None
|
||||
|
||||
# establish connection
|
||||
self.session = build_opener()
|
||||
|
||||
# add proxy handler if needed
|
||||
if config['proxy']:
|
||||
if any(config['proxies'].values()):
|
||||
self.session.add_handler(ProxyHandler(config['proxies']))
|
||||
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'])]
|
||||
self.session.addheaders = [("User-Agent", config.ua)]
|
||||
|
||||
# load local cookies
|
||||
mcj = MozillaCookieJar()
|
||||
try:
|
||||
mcj.load(FILE_C, ignore_discard=True)
|
||||
key = 'phpbb2mysql_4_data'
|
||||
if [True for c in mcj if c.name == key and c.expires > time.time()]:
|
||||
self.mcj.load(FILE_C, ignore_discard=True)
|
||||
if "phpbb2mysql_4_data" in [cookie.name for cookie in self.mcj]:
|
||||
# if cookie.expires < int(time.time())
|
||||
logger.info("Local cookies is loaded")
|
||||
self.session.add_handler(HTTPCookieProcessor(mcj))
|
||||
else:
|
||||
logger.info("Local cookies expired or bad")
|
||||
logger.debug(f"That we have: {[cookie for cookie in mcj]}")
|
||||
mcj.clear()
|
||||
self.login(mcj)
|
||||
logger.debug(f"That we have: {[cookie for cookie in self.mcj]}")
|
||||
self.mcj.clear()
|
||||
self.login()
|
||||
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:
|
||||
self.pretty_error(what)
|
||||
return None
|
||||
@ -174,61 +200,86 @@ class NNMClub:
|
||||
logger.debug(f"--- {time.time() - t0} seconds ---")
|
||||
logger.info(f"Found torrents: {total}")
|
||||
|
||||
def download_torrent(self, url: str):
|
||||
def download_torrent(self, url: str) -> None:
|
||||
# Download url
|
||||
response = self._catch_error_request(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:
|
||||
with NamedTemporaryFile(suffix=".torrent", delete=False) as fd:
|
||||
fd.write(response)
|
||||
|
||||
# return file path
|
||||
logger.debug(fd.name + " " + url)
|
||||
print(fd.name + " " + url)
|
||||
|
||||
def login(self, mcj):
|
||||
def login(self) -> None:
|
||||
if self.error:
|
||||
return None
|
||||
|
||||
# if we wanna use https we mast add ssl=enable_ssl to cookie
|
||||
mcj.set_cookie(Cookie(0, "ssl", "enable_ssl", None, False,
|
||||
".nnmclub.to", True, False, "/", True,
|
||||
False, None, False, None, None, {}))
|
||||
self.session.add_handler(HTTPCookieProcessor(mcj))
|
||||
self.mcj.set_cookie(Cookie(0, "ssl", "enable_ssl", None, False,
|
||||
".nnmclub.to", True, False, "/", True,
|
||||
False, None, False, None, None, {}))
|
||||
|
||||
response = self._catch_error_request(self.url_login)
|
||||
if not response:
|
||||
return None
|
||||
code = RE_CODE.search(response.decode('cp1251'))[1]
|
||||
form_data = {"username": config['username'],
|
||||
"password": config['password'],
|
||||
"autologin": "on",
|
||||
"code": code,
|
||||
"login": "Вход"}
|
||||
# so we first encode vals to cp1251 then do default decode whole string
|
||||
data_encoded = urlencode(
|
||||
{k: v.encode('cp1251') for k, v in form_data.items()}
|
||||
).encode()
|
||||
|
||||
self._catch_error_request(self.url_login, data_encoded)
|
||||
response = self._request(self.url_login)
|
||||
if self.error:
|
||||
return None
|
||||
logger.debug(f"That we have: {[cookie for cookie in mcj]}")
|
||||
if 'phpbb2mysql_4_sid' in [cookie.name for cookie in mcj]:
|
||||
mcj.save(FILE_C, ignore_discard=True, ignore_expires=True)
|
||||
logger.info('We successfully authorized')
|
||||
result = RE_CODE.search(response.decode("cp1251"))
|
||||
if not result:
|
||||
self.error = "Unexpected page content"
|
||||
return None
|
||||
|
||||
form_data = {"username": config.username,
|
||||
"password": config.password,
|
||||
"autologin": "on",
|
||||
"code": result[1],
|
||||
"login": "Вход"}
|
||||
# encoding to cp1251 then do default encode whole string
|
||||
data_encoded = urlencode(form_data, encoding="cp1251").encode()
|
||||
|
||||
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 "phpbb2mysql_4_sid" 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 draw(self, html: str):
|
||||
torrents = RE_TORRENTS.findall(html)
|
||||
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("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)
|
||||
|
||||
for tor in torrents:
|
||||
return torrents_found
|
||||
|
||||
def draw(self, html: str) -> None:
|
||||
for tor in RE_TORRENTS.findall(html):
|
||||
torrent_date = ""
|
||||
if config['torrentDate']:
|
||||
if config.torrent_date:
|
||||
_loc = time.localtime(int(tor[6]))
|
||||
torrent_date = f'[{time.strftime("%y.%m.%d", _loc)}] '
|
||||
|
||||
@ -241,53 +292,32 @@ class NNMClub:
|
||||
"seeds": tor[4],
|
||||
"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:
|
||||
with self.session.open(url, data, 5) as r:
|
||||
# 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()
|
||||
raise URLError(f"{self.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!"
|
||||
self.error = f"{url} is blocked. Try another proxy."
|
||||
except (URLError, HTTPError) as err:
|
||||
logger.error(err.reason)
|
||||
self.error = err.reason
|
||||
if hasattr(err, 'code'):
|
||||
error = str(err.reason)
|
||||
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}"
|
||||
else:
|
||||
self.error = f"{url} is not response! Maybe it is blocked."
|
||||
|
||||
return None
|
||||
|
||||
def pretty_error(self, what):
|
||||
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}",
|
||||
@ -303,9 +333,9 @@ class NNMClub:
|
||||
nnmclub = NNMClub
|
||||
|
||||
if __name__ == "__main__":
|
||||
if BASEDIR.parent.joinpath('settings_gui.py').exists():
|
||||
if BASEDIR.parent.joinpath("settings_gui.py").exists():
|
||||
from settings_gui import EngineSettingsGUI
|
||||
|
||||
EngineSettingsGUI(FILENAME)
|
||||
engine = nnmclub()
|
||||
engine.search('doctor')
|
||||
engine.search("doctor")
|
||||
|
192
engines/rutor.py
192
engines/rutor.py
@ -1,4 +1,4 @@
|
||||
# VERSION: 1.3
|
||||
# VERSION: 1.4
|
||||
# AUTHORS: imDMG [imdmgg@gmail.com]
|
||||
|
||||
# Rutor.org search engine plugin for qBittorrent
|
||||
@ -7,13 +7,14 @@ import base64
|
||||
import json
|
||||
import logging
|
||||
import re
|
||||
import socket
|
||||
import sys
|
||||
import time
|
||||
from concurrent.futures.thread import ThreadPoolExecutor
|
||||
from dataclasses import dataclass, field
|
||||
from html import unescape
|
||||
from pathlib import Path
|
||||
from tempfile import NamedTemporaryFile
|
||||
from typing import Optional, Union
|
||||
from urllib.error import URLError, HTTPError
|
||||
from urllib.parse import unquote
|
||||
from urllib.request import build_opener, ProxyHandler
|
||||
@ -24,29 +25,16 @@ except ImportError:
|
||||
sys.path.insert(0, str(Path(__file__).parent.parent.absolute()))
|
||||
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__)
|
||||
BASEDIR = FILE.parent.absolute()
|
||||
|
||||
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
|
||||
|
||||
|
||||
def rng(t):
|
||||
def rng(t: int) -> range:
|
||||
return range(1, -(-t // PAGES))
|
||||
|
||||
|
||||
@ -54,8 +42,8 @@ RE_TORRENTS = re.compile(
|
||||
r'(?:gai|tum)"><td>(.+?)</td.+?href="/(torrent/(\d+).+?)">(.+?)</a.+?right"'
|
||||
r'>([.\d]+ \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)
|
||||
PATTERNS = ('%ssearch/%i/%i/000/0/%s',)
|
||||
RE_RESULTS = re.compile(r"</b>\sРезультатов\sпоиска\s(\d{1,4})\s", re.S)
|
||||
PATTERNS = ("%ssearch/%i/%i/000/0/%s",)
|
||||
|
||||
# base64 encoded image
|
||||
ICON = ("AAABAAEAEBAAAAEAGABoAwAAFgAAACgAAAAQAAAAIAAAAAEAGAAAAAAAAAAAAAAAAAAAAA"
|
||||
@ -86,31 +74,71 @@ logging.basicConfig(
|
||||
|
||||
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 ")
|
||||
|
||||
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:
|
||||
name = 'Rutor'
|
||||
url = 'http://rutor.info/'
|
||||
name = "Rutor"
|
||||
url = "http://rutor.info/"
|
||||
url_dl = url.replace("//", "//d.") + "download/"
|
||||
supported_categories = {'all': 0,
|
||||
'movies': 1,
|
||||
'tv': 6,
|
||||
'music': 2,
|
||||
'games': 8,
|
||||
'anime': 10,
|
||||
'software': 9,
|
||||
'pictures': 3,
|
||||
'books': 11}
|
||||
supported_categories = {"all": 0,
|
||||
"movies": 1,
|
||||
"tv": 6,
|
||||
"music": 2,
|
||||
"games": 8,
|
||||
"anime": 10,
|
||||
"software": 9,
|
||||
"pictures": 3,
|
||||
"books": 11}
|
||||
|
||||
def __init__(self):
|
||||
# error message
|
||||
@ -120,17 +148,17 @@ class Rutor:
|
||||
self.session = build_opener()
|
||||
|
||||
# add proxy handler if needed
|
||||
if config['proxy']:
|
||||
if any(config['proxies'].values()):
|
||||
self.session.add_handler(ProxyHandler(config['proxies']))
|
||||
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'])]
|
||||
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:
|
||||
self.pretty_error(what)
|
||||
return None
|
||||
@ -144,7 +172,7 @@ class Rutor:
|
||||
return None
|
||||
# do async requests
|
||||
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)]
|
||||
with ThreadPoolExecutor(len(qrs)) as executor:
|
||||
executor.map(self.searching, qrs, timeout=30)
|
||||
@ -152,47 +180,50 @@ class Rutor:
|
||||
logger.debug(f"--- {time.time() - t0} seconds ---")
|
||||
logger.info(f"Found torrents: {total}")
|
||||
|
||||
def download_torrent(self, url: str):
|
||||
def download_torrent(self, url: str) -> None:
|
||||
# Download url
|
||||
response = self._catch_error_request(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:
|
||||
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, first=False):
|
||||
response = self._catch_error_request(query)
|
||||
if not response:
|
||||
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
|
||||
if first:
|
||||
# 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:
|
||||
return 0
|
||||
self.draw(page)
|
||||
|
||||
return torrents_found
|
||||
|
||||
def draw(self, html: str):
|
||||
torrents = RE_TORRENTS.findall(html)
|
||||
for tor in torrents:
|
||||
def draw(self, html: str) -> None:
|
||||
for tor in RE_TORRENTS.findall(html):
|
||||
torrent_date = ""
|
||||
if config['torrentDate']:
|
||||
if config.torrent_date:
|
||||
# replace names month
|
||||
months = ('Янв', 'Фев', 'Мар', 'Апр', 'Май', 'Июн',
|
||||
'Июл', 'Авг', 'Сен', 'Окт', 'Ноя', 'Дек')
|
||||
months = ("Янв", "Фев", "Мар", "Апр", "Май", "Июн",
|
||||
"Июл", "Авг", "Сен", "Окт", "Ноя", "Дек")
|
||||
ct = [unescape(tor[0].replace(m, f"{i:02d}"))
|
||||
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"))
|
||||
torrent_date = f'[{ct}] '
|
||||
torrent_date = f"[{ct}] "
|
||||
|
||||
prettyPrinter({
|
||||
"engine_url": self.url,
|
||||
@ -203,33 +234,32 @@ class Rutor:
|
||||
"seeds": unescape(tor[5]),
|
||||
"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:
|
||||
with self.session.open(url, data, 5) as r:
|
||||
# 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()
|
||||
raise URLError(f"{self.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!"
|
||||
self.error = f"{url} is blocked. Try another proxy."
|
||||
except (URLError, HTTPError) as err:
|
||||
logger.error(err.reason)
|
||||
self.error = err.reason
|
||||
if hasattr(err, 'code'):
|
||||
error = str(err.reason)
|
||||
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}"
|
||||
else:
|
||||
self.error = f"{url} is not response! Maybe it is blocked."
|
||||
|
||||
return None
|
||||
|
||||
def pretty_error(self, what):
|
||||
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}",
|
||||
@ -245,9 +275,9 @@ class Rutor:
|
||||
rutor = Rutor
|
||||
|
||||
if __name__ == "__main__":
|
||||
# if BASEDIR.parent.joinpath('settings_gui.py').exists():
|
||||
# from settings_gui import EngineSettingsGUI
|
||||
#
|
||||
# EngineSettingsGUI(FILENAME)
|
||||
if BASEDIR.parent.joinpath("settings_gui.py").exists():
|
||||
from settings_gui import EngineSettingsGUI
|
||||
|
||||
EngineSettingsGUI(FILENAME)
|
||||
engine = rutor()
|
||||
engine.search('doctor')
|
||||
engine.search("doctor")
|
||||
|
@ -1,4 +1,4 @@
|
||||
# VERSION: 1.5
|
||||
# VERSION: 1.6
|
||||
# AUTHORS: imDMG [imdmgg@gmail.com]
|
||||
|
||||
# rutracker.org search engine plugin for qBittorrent
|
||||
@ -7,14 +7,15 @@ import base64
|
||||
import json
|
||||
import logging
|
||||
import re
|
||||
import socket
|
||||
import sys
|
||||
import time
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
from dataclasses import dataclass, field
|
||||
from html import unescape
|
||||
from http.cookiejar import Cookie, MozillaCookieJar
|
||||
from pathlib import Path
|
||||
from tempfile import NamedTemporaryFile
|
||||
from typing import Optional, Union
|
||||
from urllib.error import URLError, HTTPError
|
||||
from urllib.parse import urlencode, unquote
|
||||
from urllib.request import build_opener, HTTPCookieProcessor, ProxyHandler
|
||||
@ -25,29 +26,16 @@ except ImportError:
|
||||
sys.path.insert(0, str(Path(__file__).parent.parent.absolute()))
|
||||
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__)
|
||||
BASEDIR = FILE.parent.absolute()
|
||||
|
||||
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
|
||||
|
||||
|
||||
def rng(t):
|
||||
def rng(t: int) -> range:
|
||||
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+?)">',
|
||||
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")
|
||||
RE_RESULTS = re.compile(r"Результатов\sпоиска:\s(\d{1,3})\s<span", re.S)
|
||||
PATTERNS = ("%stracker.php?nm=%s&c=%s", "%s&start=%s")
|
||||
|
||||
# base64 encoded image
|
||||
ICON = ("AAABAAEAEBAAAAEAIABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAAAAAABMLAAATCw"
|
||||
@ -92,60 +80,99 @@ logging.basicConfig(
|
||||
|
||||
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 ")
|
||||
|
||||
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 Rutracker:
|
||||
name = 'Rutracker'
|
||||
url = 'https://rutracker.org/forum/'
|
||||
url_dl = url + 'dl.php?t='
|
||||
url_login = url + 'login.php'
|
||||
supported_categories = {'all': '-1'}
|
||||
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):
|
||||
# error message
|
||||
self.error = None
|
||||
|
||||
# establish connection
|
||||
self.session = build_opener()
|
||||
|
||||
# add proxy handler if needed
|
||||
if config['proxy']:
|
||||
if any(config['proxies'].values()):
|
||||
self.session.add_handler(ProxyHandler(config['proxies']))
|
||||
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'])]
|
||||
self.session.addheaders = [("User-Agent", config.ua)]
|
||||
|
||||
# load local cookies
|
||||
mcj = MozillaCookieJar()
|
||||
try:
|
||||
mcj.load(FILE_C, ignore_discard=True)
|
||||
if 'bb_session' in [cookie.name for cookie in mcj]:
|
||||
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")
|
||||
self.session.add_handler(HTTPCookieProcessor(mcj))
|
||||
else:
|
||||
logger.info("Local cookies expired or bad")
|
||||
logger.debug(f"That we have: {[cookie for cookie in mcj]}")
|
||||
mcj.clear()
|
||||
self.login(mcj)
|
||||
logger.debug(f"That we have: {[cookie for cookie in self.mcj]}")
|
||||
self.mcj.clear()
|
||||
self.login()
|
||||
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:
|
||||
self.pretty_error(what)
|
||||
return None
|
||||
@ -166,79 +193,81 @@ class Rutracker:
|
||||
logger.debug(f"--- {time.time() - t0} seconds ---")
|
||||
logger.info(f"Found torrents: {total}")
|
||||
|
||||
def download_torrent(self, url: str):
|
||||
def download_torrent(self, url: str) -> None:
|
||||
# Download url
|
||||
response = self._catch_error_request(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:
|
||||
with NamedTemporaryFile(suffix=".torrent", delete=False) as fd:
|
||||
fd.write(response)
|
||||
|
||||
# return file path
|
||||
logger.debug(fd.name + " " + url)
|
||||
print(fd.name + " " + url)
|
||||
|
||||
def login(self, mcj):
|
||||
def login(self) -> None:
|
||||
if self.error:
|
||||
return None
|
||||
# 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",
|
||||
True, True, "/forum/", True, True,
|
||||
None, False, None, None, {}))
|
||||
self.session.add_handler(HTTPCookieProcessor(mcj))
|
||||
|
||||
form_data = {"login_username": config['username'],
|
||||
"login_password": config['password'],
|
||||
# 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": "Вход"}
|
||||
logger.debug(f"Login. Data before: {form_data}")
|
||||
# so we first encode vals to cp1251 then do default decode whole string
|
||||
data_encoded = urlencode(
|
||||
{k: v.encode('cp1251') for k, v in form_data.items()}
|
||||
).encode()
|
||||
# encoding to cp1251 then do default encode whole string
|
||||
data_encoded = urlencode(form_data, encoding="cp1251").encode()
|
||||
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:
|
||||
return None
|
||||
logger.debug(f"That we have: {[cookie for cookie in mcj]}")
|
||||
if 'bb_session' in [cookie.name for cookie in mcj]:
|
||||
mcj.save(FILE_C, ignore_discard=True, ignore_expires=True)
|
||||
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, first=False):
|
||||
response = self._catch_error_request(query)
|
||||
if not response:
|
||||
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('cp1251'), -1
|
||||
page, torrents_found = response.decode("cp1251"), -1
|
||||
if first:
|
||||
if "log-out-icon" not in page:
|
||||
logger.debug("Looks like we lost session id, lets login")
|
||||
self.login(MozillaCookieJar())
|
||||
self.mcj.clear()
|
||||
self.login()
|
||||
if self.error:
|
||||
return None
|
||||
# retry request because guests cant search
|
||||
response = self._catch_error_request(query)
|
||||
if not response:
|
||||
response = self._request(query)
|
||||
if self.error:
|
||||
return None
|
||||
page = response.decode('cp1251')
|
||||
page = response.decode("cp1251")
|
||||
# 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:
|
||||
return 0
|
||||
self.draw(page)
|
||||
|
||||
return torrents_found
|
||||
|
||||
def draw(self, html: str):
|
||||
torrents = RE_TORRENTS.findall(html)
|
||||
for tor in torrents:
|
||||
def draw(self, html: str) -> None:
|
||||
for tor in RE_TORRENTS.findall(html):
|
||||
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({
|
||||
"engine_url": self.url,
|
||||
@ -249,33 +278,33 @@ class Rutracker:
|
||||
"seeds": max(0, int(tor[3])),
|
||||
"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:
|
||||
with self.session.open(url, data, 5) as r:
|
||||
# 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()
|
||||
raise URLError(f"{self.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!"
|
||||
self.error = f"{url} is blocked. Try another proxy."
|
||||
except (URLError, HTTPError) as err:
|
||||
logger.error(err.reason)
|
||||
self.error = err.reason
|
||||
if hasattr(err, 'code'):
|
||||
error = str(err.reason)
|
||||
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}"
|
||||
else:
|
||||
self.error = f"{url} is not response! Maybe it is blocked."
|
||||
|
||||
return None
|
||||
|
||||
def pretty_error(self, what):
|
||||
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}",
|
||||
@ -291,9 +320,9 @@ class Rutracker:
|
||||
rutracker = Rutracker
|
||||
|
||||
if __name__ == "__main__":
|
||||
if BASEDIR.parent.joinpath('settings_gui.py').exists():
|
||||
if BASEDIR.parent.joinpath("settings_gui.py").exists():
|
||||
from settings_gui import EngineSettingsGUI
|
||||
|
||||
EngineSettingsGUI(FILENAME)
|
||||
engine = rutracker()
|
||||
engine.search('doctor')
|
||||
engine.search("doctor")
|
||||
|
@ -37,12 +37,12 @@ class EngineSettingsGUI:
|
||||
ttk.Label(mainframe, text="Password:").grid(
|
||||
column=0, row=1, sticky=tk.W, rowspan=2)
|
||||
|
||||
ttk.Entry(mainframe, width=25, textvariable=self.username).grid(
|
||||
column=1, row=0, sticky=tk.EW, padx=(0, 5)
|
||||
)
|
||||
ttk.Entry(mainframe, width=25, textvariable=self.password).grid(
|
||||
column=1, row=1, rowspan=2, sticky=tk.EW, padx=(0, 5)
|
||||
)
|
||||
ttk.Entry(mainframe, width=25, textvariable=self.username, state=(
|
||||
("!" 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, state=(
|
||||
("!" if self.config.get("password") else "") + tk.DISABLED)
|
||||
).grid(column=1, row=1, rowspan=2, sticky=tk.EW, padx=(0, 5))
|
||||
|
||||
ttk.Checkbutton(
|
||||
mainframe, text="Date before torrent", variable=self.date,
|
||||
@ -50,7 +50,8 @@ class EngineSettingsGUI:
|
||||
).grid(column=2, row=0, sticky=tk.W)
|
||||
ttk.Checkbutton(
|
||||
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)
|
||||
ttk.Checkbutton(
|
||||
mainframe, text="Proxy", variable=self.proxy, onvalue=True,
|
||||
@ -83,17 +84,19 @@ class EngineSettingsGUI:
|
||||
self.https_entry.state([state])
|
||||
|
||||
def close(self) -> None:
|
||||
if not (self.username.get() or self.password.get()):
|
||||
messagebox.showinfo("Error", "Some fields is empty!")
|
||||
return None
|
||||
if self.config.get("username") and self.config.get("password"):
|
||||
if not (self.username.get() or self.password.get()):
|
||||
messagebox.showinfo("Error", "Some fields is empty!")
|
||||
return None
|
||||
|
||||
if self.proxy.get() and not (self.http_entry.get()
|
||||
or self.https_entry.get()):
|
||||
messagebox.showinfo("Error", "Some fields is empty!")
|
||||
return None
|
||||
|
||||
self.config["username"] = self.username.get()
|
||||
self.config["password"] = self.password.get()
|
||||
if self.config.get("username") and self.config.get("password"):
|
||||
self.config["username"] = self.username.get()
|
||||
self.config["password"] = self.password.get()
|
||||
self.config["proxy"] = self.proxy.get()
|
||||
if self.config["proxy"]:
|
||||
self.config["proxies"] = {
|
||||
@ -101,7 +104,8 @@ class EngineSettingsGUI:
|
||||
"https": self.https_entry.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(
|
||||
json.dumps(self.config, indent=4, sort_keys=False)
|
||||
)
|
||||
@ -109,5 +113,5 @@ class EngineSettingsGUI:
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
settings = EngineSettingsGUI("kinozal")
|
||||
settings = EngineSettingsGUI("engines/kinozal")
|
||||
print(settings.config)
|
||||
|
Loading…
x
Reference in New Issue
Block a user