# VERSION: 1.50
# AUTHOR: github.com/444995
# WILL GET UPDATED IF BROKEN
# MIT License
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
# USER SETTINGS
PRIVATE_USERNAME = "REPLACE_ME" # A DANISHBYTES ACCOUNT HAS A PRIVATE USERNAME
PUBLIC_USERNAME = "REPLACE_ME" # AND A PUBLIC USERNAME
PASSWORD = "REPLACE_ME" # YOU ALSO NEED A PASSWORD
CACHE_LOGIN_COOKIES = True # CACHE COOKIES (HIGHLY RECOMMENDED)
COOKIES_FILE_NAME = "danishbytes.cookies" # THE PATH TO THE LOGIN
USE_MAGNET_URLS = False # USES MAGNET LINKS OR TORRENT FILES DEPENDING ON TRUE/FALSE (FALSE IS RECOMMENDED)
# IMPORTS
import os
import re
import json
import gzip
import tempfile
import urllib.parse
import urllib.request
import http.cookiejar as cookielib
from urllib.error import HTTPError
from novaprinter import prettyPrinter
class HtmlExtractor:
"""
Extracts needed keys and tokens from a given HTML response.
"""
@staticmethod
def extract_meta_content(html, name):
"""Extracts content from meta tag with the given name."""
pattern = r']*name=["\']{}["\'][^>]*>'.format(re.escape(name))
match = re.search(pattern, html, re.IGNORECASE)
if match:
tag = match.group()
content_match = re.search(r'content=["\']([^"\']+)["\']', tag, re.IGNORECASE)
if content_match:
return content_match.group(1).strip()
return None
@staticmethod
def extract_input_value(html, name):
"""Extracts value from input tag with the given name."""
pattern = r']*name=["\']{}["\'][^>]*>'.format(re.escape(name))
match = re.search(pattern, html, re.IGNORECASE)
if match:
tag = match.group()
value_match = re.search(r'value=["\']([^"\']*)["\']', tag, re.IGNORECASE)
if value_match:
return value_match.group(1).strip()
return None
@staticmethod
def extract_attr(html, attr_name, num=-1):
"""Extracts the attribute value from the input tag with the given number."""
inputs = re.findall(r']*>', html, re.IGNORECASE)
if inputs:
if num == -1:
input_tag = inputs[-1]
else:
if num < len(inputs):
input_tag = inputs[num]
else:
return None
attr_match = re.search(r'{}=["\']([^"\']+)["\']'.format(re.escape(attr_name)), input_tag, re.IGNORECASE)
if attr_match:
return attr_match.group(1).strip()
return None
class danishbytes(object):
"""
DanishBytes engine for qBittorrent.
"""
name = 'DanishBytes'
url = 'https://danishbytes.club'
login_url = f"{url}/login"
tracker_urls = [
"https://danishbytes2.org/announce",
"https://dbytes.org/announce",
"https://danishbytes.club/announce"
]
categories_string = "&categories%5B%5D="
user_agent = 'Mozilla/5.0 (X11; Linux x86_64; rv:68.0) Gecko/20100101 Firefox/68.0'
supported_categories = {
'all': '0', # 'all' is just every category combined
'movies': '1',
'tv': '2',
'music': '3',
'games': '4',
'software': '5',
'books': '8',
}
def __init__(self):
"""
Initializes the DanishBytes engine for qBittorrent.
"""
self.cookies_file_path = os.path.join(
os.path.dirname(os.path.realpath(__file__)),
COOKIES_FILE_NAME
)
self.html_extractor = HtmlExtractor()
self.cookiejar = cookielib.LWPCookieJar(self.cookies_file_path)
self.opener = urllib.request.build_opener(urllib.request.HTTPCookieProcessor(self.cookiejar))
self.opener.addheaders = [
('User-Agent', self.user_agent),
('Accept-Encoding', 'gzip, deflate'),
]
if CACHE_LOGIN_COOKIES and os.path.exists(self.cookies_file_path):
self.cookiejar.load(
self.cookies_file_path,
ignore_discard=True,
ignore_expires=True
)
else:
self._login()
self._set_csrf_token()
def _login(self):
"""
Logs into DanishBytes; this automatically sets the cookies in the cookiejar
when self.opener is used hence why cookies aren't returned
"""
# initial request to get cookies and keys
response = self._make_request(self.login_url)
html = response
payload = {
"_token": self.html_extractor.extract_meta_content(html, "csrf-token"),
"private_username": PRIVATE_USERNAME,
"username": PUBLIC_USERNAME,
"password": PASSWORD,
"remember": "on",
"_captcha": self.html_extractor.extract_input_value(html, "_captcha"),
"_username": self.html_extractor.extract_input_value(html, "_username"),
self.html_extractor.extract_attr(html, "name"): self.html_extractor.extract_attr(html, "value")
}
try:
self._make_request(self.login_url, data=payload)
except HTTPError as e:
raise Exception(f"Login failed with error code: {e.code}") from e
if CACHE_LOGIN_COOKIES:
self.cookiejar.save(
self.cookies_file_path,
ignore_discard=True,
ignore_expires=True
)
def download_torrent(self, info):
"""
Downloads the torrent file
"""
torrent_file = self._make_request(info)
with tempfile.NamedTemporaryFile(suffix='.torrent', delete=False) as _file:
_file.write(torrent_file.encode('utf-8'))
print(f"{_file.name} {info}")
def search(self, what, cat='all'):
"""
Searches for torrents on DanishBytes.
"""
category_param = self._get_category_param(cat)
page_num = 1
while True:
torrent_count = self._search_page(what, page_num, category_param)
if torrent_count < 100:
break
page_num += 1
def _get_category_param(self, cat):
"""
Constructs the category parameter string for the search URL.
"""
if cat == 'all':
return ''.join([self.categories_string + c for c in self.supported_categories.values()])
return self.categories_string + self.supported_categories[cat]
def _search_page(self, what, page_num, category_param):
"""
Handles a single page of search results.
"""
search_url = f"{self.url}/torrents/filter?_token={self.csrf_token}&search={what}&page={page_num}&qty=100{category_param}"
response = self._make_request(search_url)
search_results = json.loads(response)
for torrent in search_results['torrents']:
self._print_torrent(torrent, search_results['rsskey'], search_results['passkey'])
return len(search_results['torrents'])
def _make_request(self, url, data=None):
"""
Makes a request to the given URL using urllib.
"""
encoded_data = urllib.parse.urlencode(data).encode() if data else None
with self.opener.open(url, encoded_data or None) as response:
content = gzip.GzipFile(fileobj=response).read() if response.info().get('Content-Encoding') == 'gzip' else response.read()
return content.decode('utf-8', errors='replace')
def _print_torrent(self, torrent, rss_key, pass_key):
"""
Prints a single torrent's details.
"""
_link = self._make_magnet_url(torrent, rss_key, pass_key) if USE_MAGNET_URLS else f"{self.url}/torrents/download/{torrent['id']}"
prettyPrinter({
'link': _link,
'name': torrent['name'],
'size': f"{str(torrent['size'])} B",
'seeds': torrent['seeders'],
'leech': torrent['leechers'],
'engine_url': self.url,
'desc_link': f"{self.url}/torrents/{torrent['id']}"
})
def _make_magnet_url(self, torrent, rss_key, pass_key):
"""
Constructs the magnet URL for a torrent.
"""
magnet_url = "magnet:?"
magnet_url += f"dn={urllib.parse.quote(torrent['name'])}&"
magnet_url += f"xt=urn:btih:{torrent['info_hash']}&"
magnet_url += f"as={self.url}/torrent/download/{torrent['id']}.{rss_key}&"
magnet_url += f"xl={torrent['size']}&"
for tracker in self.tracker_urls:
magnet_url += f"tr={urllib.parse.quote(tracker)}/{pass_key}&"
return magnet_url.rstrip('&')
def _set_csrf_token(self):
"""
Sets the CSRF token for the current session, so it can be used for search requests.
"""
response = self._make_request(self.url)
html = response
self.csrf_token = self.html_extractor.extract_meta_content(html, "csrf-token")
if __name__ == "__main__":
# For testing purposes
db = danishbytes()
db.search("hello", "all")
db.download_torrent("https://danishbytes.club/torrents/download/25600")