quasarr 1.18.0__tar.gz → 1.19.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of quasarr might be problematic. Click here for more details.
- {quasarr-1.18.0 → quasarr-1.19.0}/PKG-INFO +1 -1
- {quasarr-1.18.0 → quasarr-1.19.0}/quasarr/downloads/__init__.py +3 -0
- quasarr-1.19.0/quasarr/downloads/sources/he.py +112 -0
- {quasarr-1.18.0 → quasarr-1.19.0}/quasarr/downloads/sources/nk.py +3 -7
- {quasarr-1.18.0 → quasarr-1.19.0}/quasarr/providers/html_images.py +1 -0
- {quasarr-1.18.0 → quasarr-1.19.0}/quasarr/providers/version.py +1 -1
- {quasarr-1.18.0 → quasarr-1.19.0}/quasarr/search/__init__.py +4 -0
- quasarr-1.19.0/quasarr/search/sources/he.py +196 -0
- {quasarr-1.18.0 → quasarr-1.19.0}/quasarr/search/sources/nk.py +6 -7
- {quasarr-1.18.0 → quasarr-1.19.0}/quasarr/storage/config.py +1 -0
- {quasarr-1.18.0 → quasarr-1.19.0}/quasarr.egg-info/PKG-INFO +1 -1
- {quasarr-1.18.0 → quasarr-1.19.0}/quasarr.egg-info/SOURCES.txt +2 -0
- {quasarr-1.18.0 → quasarr-1.19.0}/LICENSE +0 -0
- {quasarr-1.18.0 → quasarr-1.19.0}/README.md +0 -0
- {quasarr-1.18.0 → quasarr-1.19.0}/quasarr/__init__.py +0 -0
- {quasarr-1.18.0 → quasarr-1.19.0}/quasarr/api/__init__.py +0 -0
- {quasarr-1.18.0 → quasarr-1.19.0}/quasarr/api/arr/__init__.py +0 -0
- {quasarr-1.18.0 → quasarr-1.19.0}/quasarr/api/captcha/__init__.py +0 -0
- {quasarr-1.18.0 → quasarr-1.19.0}/quasarr/api/config/__init__.py +0 -0
- {quasarr-1.18.0 → quasarr-1.19.0}/quasarr/api/sponsors_helper/__init__.py +0 -0
- {quasarr-1.18.0 → quasarr-1.19.0}/quasarr/api/statistics/__init__.py +0 -0
- {quasarr-1.18.0 → quasarr-1.19.0}/quasarr/downloads/linkcrypters/__init__.py +0 -0
- {quasarr-1.18.0 → quasarr-1.19.0}/quasarr/downloads/linkcrypters/al.py +0 -0
- {quasarr-1.18.0 → quasarr-1.19.0}/quasarr/downloads/linkcrypters/filecrypt.py +0 -0
- {quasarr-1.18.0 → quasarr-1.19.0}/quasarr/downloads/linkcrypters/hide.py +0 -0
- {quasarr-1.18.0 → quasarr-1.19.0}/quasarr/downloads/packages/__init__.py +0 -0
- {quasarr-1.18.0 → quasarr-1.19.0}/quasarr/downloads/sources/__init__.py +0 -0
- {quasarr-1.18.0 → quasarr-1.19.0}/quasarr/downloads/sources/al.py +0 -0
- {quasarr-1.18.0 → quasarr-1.19.0}/quasarr/downloads/sources/by.py +0 -0
- {quasarr-1.18.0 → quasarr-1.19.0}/quasarr/downloads/sources/dd.py +0 -0
- {quasarr-1.18.0 → quasarr-1.19.0}/quasarr/downloads/sources/dt.py +0 -0
- {quasarr-1.18.0 → quasarr-1.19.0}/quasarr/downloads/sources/dw.py +0 -0
- {quasarr-1.18.0 → quasarr-1.19.0}/quasarr/downloads/sources/mb.py +0 -0
- {quasarr-1.18.0 → quasarr-1.19.0}/quasarr/downloads/sources/nx.py +0 -0
- {quasarr-1.18.0 → quasarr-1.19.0}/quasarr/downloads/sources/sf.py +0 -0
- {quasarr-1.18.0 → quasarr-1.19.0}/quasarr/downloads/sources/sl.py +0 -0
- {quasarr-1.18.0 → quasarr-1.19.0}/quasarr/downloads/sources/wd.py +0 -0
- {quasarr-1.18.0 → quasarr-1.19.0}/quasarr/providers/__init__.py +0 -0
- {quasarr-1.18.0 → quasarr-1.19.0}/quasarr/providers/cloudflare.py +0 -0
- {quasarr-1.18.0 → quasarr-1.19.0}/quasarr/providers/html_templates.py +0 -0
- {quasarr-1.18.0 → quasarr-1.19.0}/quasarr/providers/imdb_metadata.py +0 -0
- {quasarr-1.18.0 → quasarr-1.19.0}/quasarr/providers/log.py +0 -0
- {quasarr-1.18.0 → quasarr-1.19.0}/quasarr/providers/myjd_api.py +0 -0
- {quasarr-1.18.0 → quasarr-1.19.0}/quasarr/providers/notifications.py +0 -0
- {quasarr-1.18.0 → quasarr-1.19.0}/quasarr/providers/obfuscated.py +0 -0
- {quasarr-1.18.0 → quasarr-1.19.0}/quasarr/providers/sessions/__init__.py +0 -0
- {quasarr-1.18.0 → quasarr-1.19.0}/quasarr/providers/sessions/al.py +0 -0
- {quasarr-1.18.0 → quasarr-1.19.0}/quasarr/providers/sessions/dd.py +0 -0
- {quasarr-1.18.0 → quasarr-1.19.0}/quasarr/providers/sessions/nx.py +0 -0
- {quasarr-1.18.0 → quasarr-1.19.0}/quasarr/providers/shared_state.py +0 -0
- {quasarr-1.18.0 → quasarr-1.19.0}/quasarr/providers/statistics.py +0 -0
- {quasarr-1.18.0 → quasarr-1.19.0}/quasarr/providers/web_server.py +0 -0
- {quasarr-1.18.0 → quasarr-1.19.0}/quasarr/search/sources/__init__.py +0 -0
- {quasarr-1.18.0 → quasarr-1.19.0}/quasarr/search/sources/al.py +0 -0
- {quasarr-1.18.0 → quasarr-1.19.0}/quasarr/search/sources/by.py +0 -0
- {quasarr-1.18.0 → quasarr-1.19.0}/quasarr/search/sources/dd.py +0 -0
- {quasarr-1.18.0 → quasarr-1.19.0}/quasarr/search/sources/dt.py +0 -0
- {quasarr-1.18.0 → quasarr-1.19.0}/quasarr/search/sources/dw.py +0 -0
- {quasarr-1.18.0 → quasarr-1.19.0}/quasarr/search/sources/fx.py +0 -0
- {quasarr-1.18.0 → quasarr-1.19.0}/quasarr/search/sources/mb.py +0 -0
- {quasarr-1.18.0 → quasarr-1.19.0}/quasarr/search/sources/nx.py +0 -0
- {quasarr-1.18.0 → quasarr-1.19.0}/quasarr/search/sources/sf.py +0 -0
- {quasarr-1.18.0 → quasarr-1.19.0}/quasarr/search/sources/sl.py +0 -0
- {quasarr-1.18.0 → quasarr-1.19.0}/quasarr/search/sources/wd.py +0 -0
- {quasarr-1.18.0 → quasarr-1.19.0}/quasarr/storage/__init__.py +0 -0
- {quasarr-1.18.0 → quasarr-1.19.0}/quasarr/storage/setup.py +0 -0
- {quasarr-1.18.0 → quasarr-1.19.0}/quasarr/storage/sqlite_database.py +0 -0
- {quasarr-1.18.0 → quasarr-1.19.0}/quasarr.egg-info/dependency_links.txt +0 -0
- {quasarr-1.18.0 → quasarr-1.19.0}/quasarr.egg-info/entry_points.txt +0 -0
- {quasarr-1.18.0 → quasarr-1.19.0}/quasarr.egg-info/not-zip-safe +0 -0
- {quasarr-1.18.0 → quasarr-1.19.0}/quasarr.egg-info/requires.txt +0 -0
- {quasarr-1.18.0 → quasarr-1.19.0}/quasarr.egg-info/top_level.txt +0 -0
- {quasarr-1.18.0 → quasarr-1.19.0}/setup.cfg +0 -0
- {quasarr-1.18.0 → quasarr-1.19.0}/setup.py +0 -0
|
@@ -13,6 +13,7 @@ from quasarr.downloads.sources.by import get_by_download_links
|
|
|
13
13
|
from quasarr.downloads.sources.dd import get_dd_download_links
|
|
14
14
|
from quasarr.downloads.sources.dt import get_dt_download_links
|
|
15
15
|
from quasarr.downloads.sources.dw import get_dw_download_links
|
|
16
|
+
from quasarr.downloads.sources.he import get_he_download_links
|
|
16
17
|
from quasarr.downloads.sources.mb import get_mb_download_links
|
|
17
18
|
from quasarr.downloads.sources.nk import get_nk_download_links
|
|
18
19
|
from quasarr.downloads.sources.nx import get_nx_download_links
|
|
@@ -202,6 +203,7 @@ def download(shared_state, request_from, title, url, mirror, size_mb, password,
|
|
|
202
203
|
'DD': config.get("dd"),
|
|
203
204
|
'DT': config.get("dt"),
|
|
204
205
|
'DW': config.get("dw"),
|
|
206
|
+
'HE': config.get("he"),
|
|
205
207
|
'MB': config.get("mb"),
|
|
206
208
|
'NK': config.get("nk"),
|
|
207
209
|
'NX': config.get("nx"),
|
|
@@ -216,6 +218,7 @@ def download(shared_state, request_from, title, url, mirror, size_mb, password,
|
|
|
216
218
|
(flags['DD'], lambda *a: handle_unprotected(*a, func=get_dd_download_links, label='DD')),
|
|
217
219
|
(flags['DT'], lambda *a: handle_unprotected(*a, func=get_dt_download_links, label='DT')),
|
|
218
220
|
(flags['DW'], lambda *a: handle_protected(*a, func=get_dw_download_links, label='DW')),
|
|
221
|
+
(flags['HE'], lambda *a: handle_unprotected(*a, func=get_he_download_links, label='HE')),
|
|
219
222
|
(flags['MB'], lambda *a: handle_protected(*a, func=get_mb_download_links, label='MB')),
|
|
220
223
|
(flags['NK'], lambda *a: handle_protected(*a, func=get_nk_download_links, label='NK')),
|
|
221
224
|
(flags['NX'], lambda *a: handle_unprotected(*a, func=get_nx_download_links, label='NX')),
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# Quasarr
|
|
3
|
+
# Project by https://github.com/rix1337
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
from urllib.parse import urlparse, urljoin
|
|
7
|
+
|
|
8
|
+
import requests
|
|
9
|
+
from bs4 import BeautifulSoup
|
|
10
|
+
|
|
11
|
+
from quasarr.providers.log import info, debug
|
|
12
|
+
|
|
13
|
+
hostname = "he"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def get_he_download_links(shared_state, url, mirror, title):
|
|
17
|
+
headers = {
|
|
18
|
+
'User-Agent': shared_state.values["user_agent"],
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
session = requests.Session()
|
|
22
|
+
|
|
23
|
+
try:
|
|
24
|
+
resp = session.get(url, headers=headers, timeout=30)
|
|
25
|
+
soup = BeautifulSoup(resp.text, 'html.parser')
|
|
26
|
+
except Exception as e:
|
|
27
|
+
info(f"{hostname}: could not fetch release for {title}: {e}")
|
|
28
|
+
return False
|
|
29
|
+
|
|
30
|
+
imdb_id = None
|
|
31
|
+
try:
|
|
32
|
+
imdb_link = soup.find('a', href=re.compile(r"imdb\.com/title/tt\d+", re.IGNORECASE))
|
|
33
|
+
if imdb_link:
|
|
34
|
+
href = imdb_link['href'].strip()
|
|
35
|
+
m = re.search(r"(tt\d{4,7})", href)
|
|
36
|
+
if m:
|
|
37
|
+
imdb_id = m.group(1)
|
|
38
|
+
else:
|
|
39
|
+
debug(f"{hostname}: imdb_id not found for title {title} in link href.")
|
|
40
|
+
else:
|
|
41
|
+
debug(f"{hostname}: imdb_id link href not found for title {title}.")
|
|
42
|
+
except Exception:
|
|
43
|
+
debug(f"{hostname}: failed to extract imdb_id for title {title}.")
|
|
44
|
+
|
|
45
|
+
anchors = []
|
|
46
|
+
for retries in range(10):
|
|
47
|
+
form = soup.find('form', id=re.compile(r'content-protector-access-form'))
|
|
48
|
+
if not form:
|
|
49
|
+
return False
|
|
50
|
+
|
|
51
|
+
action = form.get('action') or url
|
|
52
|
+
action_url = urljoin(resp.url, action)
|
|
53
|
+
|
|
54
|
+
payload = {}
|
|
55
|
+
for inp in form.find_all('input'):
|
|
56
|
+
name = inp.get('name')
|
|
57
|
+
if not name:
|
|
58
|
+
continue
|
|
59
|
+
value = inp.get('value', '')
|
|
60
|
+
payload[name] = value
|
|
61
|
+
|
|
62
|
+
append_patt = re.compile(r"append\(\s*[\'\"](?P<key>[^\'\"]+)[\'\"]\s*,\s*[\'\"](?P<val>[^\'\"]+)[\'\"]\s*\)",
|
|
63
|
+
re.IGNORECASE)
|
|
64
|
+
|
|
65
|
+
for script in soup.find_all('script'):
|
|
66
|
+
txt = script.string if script.string is not None else script.get_text()
|
|
67
|
+
if not txt:
|
|
68
|
+
continue
|
|
69
|
+
for m in append_patt.finditer(txt):
|
|
70
|
+
payload[m.group('key')] = m.group('val')
|
|
71
|
+
|
|
72
|
+
post_headers = headers.copy()
|
|
73
|
+
post_headers.update({'Referer': resp.url})
|
|
74
|
+
try:
|
|
75
|
+
resp = session.post(action_url, data=payload, headers=post_headers, timeout=30)
|
|
76
|
+
soup = BeautifulSoup(resp.text, 'html.parser')
|
|
77
|
+
except Exception as e:
|
|
78
|
+
info(f"{hostname}: could not submit protector form for {title}: {e}")
|
|
79
|
+
break
|
|
80
|
+
|
|
81
|
+
unlocked = soup.select('.content-protector-access-form')
|
|
82
|
+
if unlocked:
|
|
83
|
+
for u in unlocked:
|
|
84
|
+
anchors.extend(u.find_all('a', href=True))
|
|
85
|
+
|
|
86
|
+
if anchors:
|
|
87
|
+
break
|
|
88
|
+
|
|
89
|
+
links = []
|
|
90
|
+
for a in anchors:
|
|
91
|
+
try:
|
|
92
|
+
href = a['href'].strip()
|
|
93
|
+
|
|
94
|
+
netloc = urlparse(href).netloc
|
|
95
|
+
hoster = netloc.split(':')[0].lower()
|
|
96
|
+
parts = hoster.split('.')
|
|
97
|
+
if len(parts) >= 2:
|
|
98
|
+
hoster = parts[-2]
|
|
99
|
+
|
|
100
|
+
links.append([href, hoster])
|
|
101
|
+
except Exception:
|
|
102
|
+
debug(f"{hostname}: could not resolve download link hoster for {title}")
|
|
103
|
+
continue
|
|
104
|
+
|
|
105
|
+
if not links:
|
|
106
|
+
info(f"No external download links found on {hostname} page for {title}")
|
|
107
|
+
return False
|
|
108
|
+
|
|
109
|
+
return {
|
|
110
|
+
"links": links,
|
|
111
|
+
"imdb_id": imdb_id,
|
|
112
|
+
}
|
|
@@ -2,13 +2,10 @@
|
|
|
2
2
|
# Quasarr
|
|
3
3
|
# Project by https://github.com/rix1337
|
|
4
4
|
|
|
5
|
-
import re
|
|
6
|
-
|
|
7
5
|
import requests
|
|
8
6
|
from bs4 import BeautifulSoup
|
|
9
7
|
|
|
10
|
-
from quasarr.providers.log import info
|
|
11
|
-
from urllib.parse import urlparse, urljoin
|
|
8
|
+
from quasarr.providers.log import info
|
|
12
9
|
|
|
13
10
|
hostname = "nk"
|
|
14
11
|
|
|
@@ -28,15 +25,14 @@ def get_nk_download_links(shared_state, url, mirror, title):
|
|
|
28
25
|
info(f"{hostname}: could not fetch release page for {title}: {e}")
|
|
29
26
|
return False
|
|
30
27
|
|
|
31
|
-
# download links are provided as anchors with class 'dl-button'
|
|
32
28
|
anchors = soup.select('a.btn-orange')
|
|
33
29
|
candidates = []
|
|
34
30
|
for a in anchors:
|
|
35
|
-
|
|
31
|
+
|
|
36
32
|
href = a.get('href', '').strip()
|
|
37
33
|
hoster = href.split('/')[3].lower()
|
|
38
34
|
if not href.lower().startswith(('http://', 'https://')):
|
|
39
|
-
href
|
|
35
|
+
href = 'https://' + host + href
|
|
40
36
|
|
|
41
37
|
try:
|
|
42
38
|
href = requests.head(href, headers=headers, allow_redirects=True, timeout=20).url
|
|
@@ -10,6 +10,7 @@ dt = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAA4ElE
|
|
|
10
10
|
dw = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAA70lEQVR4nAXBTUrDQBiA4febmfjbpqZUsFkJxS6UIq4EvYS46CUExRPkBL2HFM/gIdxIFq4Ff2JNia3JzBefR+bzuZ1Op+E+y/q6Fd/GSe8qijZH1lpfN00uADdZNt5NDh7GJ8dng2HKxvYOBtB6jctms/5S3ePpxeWklw69tZE1IqKqrIxT81U1d4PReCJJ338HXBVURCCokhelMZW467YTt9pi1rUntPCHYJ0ljiyubMJh0yJFuZSk26ENjufPFV0CCeAUfLEo8V7pRBHWOMJvBc5Q+xpnIf8pl+fvi1Lztw9zlO6T7sWw9jy9vPIPUL1kPErau3YAAAAASUVORK5CYII='
|
|
11
11
|
fx = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAABB0lEQVR4nB2LP0sCcQBA3/2Uzugu8E4IoT9cSYhUENFWYSA1FFTQ4hK5SENNDW19hoYcImhvaGgLguYgcIk0haLOE7wrNGzo5A4v9K3vPWknmwt0LYrrunieTygkAAm300EIQTg5nWB3e5O39w9s54t4fISEYfBZq/H4VETIsoxp1ftyJpWkR7lSZXFhnsFIhLBtOxR9n1KliqZFeX4po+saZ4VLTMtC9M61TJrTk2OarR9WV5bIpJfZ2ljH8zxEw3EovVaZm01xd//AsKpwfXNLLKaTmDSQ9vJHwWE+x3ezRbv9i6oqTBkTmLU65xdXSNn9g0CWB2jYDuNjo/y5Lqoy1A+63S7/GeVj+5KBt3UAAAAASUVORK5CYII='
|
|
12
12
|
nk = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAAHYYAAB2GAV2iE4EAAAAZdEVYdFNvZnR3YXJlAEFkb2JlIEltYWdlUmVhZHlxyWU8AAADrmlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4NCjx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNS1jMDIxIDc5LjE1NTc3MiwgMjAxNC8wMS8xMy0xOTo0NDowMCAgICAgICAgIj4NCgk8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPg0KCQk8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIiB4bWxuczpzdFJlZj0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlUmVmIyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxNCAoTWFjaW50b3NoKSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDo4RUJCOThFRkQ5RTcxMUU1QTcyN0EzQzFGN0YzNjcwNCIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDo4RUJCOThGMEQ5RTcxMUU1QTcyN0EzQzFGN0YzNjcwNCI+DQoJCQk8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo4RUJCOThFREQ5RTcxMUU1QTcyN0EzQzFGN0YzNjcwNCIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo4RUJCOThFRUQ5RTcxMUU1QTcyN0EzQzFGN0YzNjcwNCIvPg0KCQk8L3JkZjpEZXNjcmlwdGlvbj4NCgkJPHJkZjpEZXNjcmlwdGlvbiB4bWxuczp0aWZmPSJodHRwOi8vbnMuYWRvYmUuY29tL3RpZmYvMS4wLyI+PHRpZmY6T3JpZW50YXRpb24+MTwvdGlmZjpPcmllbnRhdGlvbj48L3JkZjpEZXNjcmlwdGlvbj48L3JkZjpSREY+DQo8L3g6eG1wbWV0YT4NCjw/eHBhY2tldCBlbmQ9J3cnPz7hg4LgAAAA3UlEQVQoU2NgIAAYGRgYGP7//8/4cHak44//jJfY//3T+S0vdpz/zXvZZ8+/yTIzMDAwhP490ir479ecx89/H2X69nH123vvn/1592HVy6+/rjNeyrPR+/jz927mf3+5v7Pz+vH8/DSXm51Z7NW3v8ec5p11Zfr44fOSv5+/MDB+/fqP4dOnoJ9fvjN++/TtE/P376IMDAwMzL5qYt8Y/v5+ws383+ndb8ajLP9+2T36xZ7O/f9HTIyhFCMjzLVLIw28Hv8ROarA8sL64Ve+wyocX+R//mOQQfIQdgAAAghnscY2ZXIAAAAASUVORK5CYII='
|
|
13
|
+
he = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAAHYYAAB2GAV2iE4EAAACsSURBVChTY2CAgv///zPC2DA+uhh28IiBxZ5FQTWdS8tUgktOLZlHTq2eR05tAoeschxYwTsGpnpRVq6bDMxscxhYue4xsPM8ZBIULWVg57nAwM6uyPCPg0N+DwPD4zgGhnMyDAz32BgY7jAwMJQxMDCeMmdgCAeb8oeBIeEfA4PjMgaG3FQGhnlODIzHHBgY18UzMARjur6+numfuDj3f0lJLogA1DvoCmEAAJVNNF84URktAAAAAElFTkSuQmCC'
|
|
13
14
|
mb = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAABE0lEQVR4nAEIAff+AQAAAACHttAxAQ0Kjd7o7ToDAQH/GREOxcDT4HK+cErSAYy70DHs/AG6tNjoFNzn6gD5+fwAQjElAIE/JuqWxdZEBBwVD5RSIhsUnsTV/JBRNwT9/v4AjcDU/DsgOxbW6RqUArra5zf39vb/KRwTBKPN3gBMKhwA3unyBJHE2P/i7/U6Auvz+ACkzt4AaTkmAN/u9AARCQYAQysdALXW5ADl8fYAAi4bEMbD5vQBMxoR/CwYEACVxtkAfUMr/PoECAEZDwrFAiAPCmwpEAjq/gcKBAUDAQCQwtcAMSIZBDkbDukIBAJtAmU5KNIP//1F1ePrvScWD/hSLR/4uNXkvAHz9kZ+RS/T0LpzUht5tWAAAAAASUVORK5CYII='
|
|
14
15
|
nx = 'data:image/png;base64,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'
|
|
15
16
|
sf = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAA8ElEQVR4nBXBv0rDQADA4d/lz1lqmlREECUIFTq4FBeHDA6uPkRfwwfxBRx19gmk4GZBBAcjCAUrWEkaTUzucjnx+0R6kzQboZDCb7AWdNmiMo3KLHUmlCcDR/Z2DujvJXRth6kNqqgoXufo6kk6lpzwcEr7fUrx7POTugh9xPZkilE5jh9MsJ2mreaUi0eKdIa7CUIogjjB8wcnyOiM0rmkH+8ih8dY1gTxOeHoA69eLRCOxBvsE21JOmVo8gy3N6T+fMNbPdwiwzG6XFJ/vWC1xY9GvN9dsZxdI+4vxo3rtxLH8q/TFvPbYhqL0Z76AydWbEnTqqOqAAAAAElFTkSuQmCC'
|
|
@@ -12,6 +12,7 @@ from quasarr.search.sources.dd import dd_search, dd_feed
|
|
|
12
12
|
from quasarr.search.sources.dt import dt_feed, dt_search
|
|
13
13
|
from quasarr.search.sources.dw import dw_feed, dw_search
|
|
14
14
|
from quasarr.search.sources.fx import fx_feed, fx_search
|
|
15
|
+
from quasarr.search.sources.he import he_feed, he_search
|
|
15
16
|
from quasarr.search.sources.mb import mb_feed, mb_search
|
|
16
17
|
from quasarr.search.sources.nk import nk_feed, nk_search
|
|
17
18
|
from quasarr.search.sources.nx import nx_feed, nx_search
|
|
@@ -34,6 +35,7 @@ def get_search_results(shared_state, request_from, imdb_id="", search_phrase="",
|
|
|
34
35
|
dt = shared_state.values["config"]("Hostnames").get("dt")
|
|
35
36
|
dw = shared_state.values["config"]("Hostnames").get("dw")
|
|
36
37
|
fx = shared_state.values["config"]("Hostnames").get("fx")
|
|
38
|
+
he = shared_state.values["config"]("Hostnames").get("he")
|
|
37
39
|
mb = shared_state.values["config"]("Hostnames").get("mb")
|
|
38
40
|
nk = shared_state.values["config"]("Hostnames").get("nk")
|
|
39
41
|
nx = shared_state.values["config"]("Hostnames").get("nx")
|
|
@@ -53,6 +55,7 @@ def get_search_results(shared_state, request_from, imdb_id="", search_phrase="",
|
|
|
53
55
|
(dt, dt_search),
|
|
54
56
|
(dw, dw_search),
|
|
55
57
|
(fx, fx_search),
|
|
58
|
+
(he, he_search),
|
|
56
59
|
(mb, mb_search),
|
|
57
60
|
(nk, nk_search),
|
|
58
61
|
(nx, nx_search),
|
|
@@ -78,6 +81,7 @@ def get_search_results(shared_state, request_from, imdb_id="", search_phrase="",
|
|
|
78
81
|
(dt, dt_feed),
|
|
79
82
|
(dw, dw_feed),
|
|
80
83
|
(fx, fx_feed),
|
|
84
|
+
(he, he_feed),
|
|
81
85
|
(mb, mb_feed),
|
|
82
86
|
(nk, nk_feed),
|
|
83
87
|
(nx, nx_feed),
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# Quasarr
|
|
3
|
+
# Project by https://github.com/rix1337
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
import time
|
|
7
|
+
from base64 import urlsafe_b64encode
|
|
8
|
+
from datetime import datetime, timedelta
|
|
9
|
+
from html import unescape
|
|
10
|
+
|
|
11
|
+
import requests
|
|
12
|
+
from bs4 import BeautifulSoup
|
|
13
|
+
|
|
14
|
+
from quasarr.providers.imdb_metadata import get_localized_title
|
|
15
|
+
from quasarr.providers.log import info, debug
|
|
16
|
+
|
|
17
|
+
hostname = "he"
|
|
18
|
+
supported_mirrors = ["rapidgator", "nitroflare"]
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def parse_posted_ago(txt):
|
|
22
|
+
try:
|
|
23
|
+
m = re.search(r"(\d+)\s*(sec|min|hour|day|week|month|year)s?", txt, re.IGNORECASE)
|
|
24
|
+
if not m:
|
|
25
|
+
return ''
|
|
26
|
+
value = int(m.group(1))
|
|
27
|
+
unit = m.group(2).lower()
|
|
28
|
+
now = datetime.utcnow()
|
|
29
|
+
if unit.startswith('sec'):
|
|
30
|
+
delta = timedelta(seconds=value)
|
|
31
|
+
elif unit.startswith('min'):
|
|
32
|
+
delta = timedelta(minutes=value)
|
|
33
|
+
elif unit.startswith('hour'):
|
|
34
|
+
delta = timedelta(hours=value)
|
|
35
|
+
elif unit.startswith('day'):
|
|
36
|
+
delta = timedelta(days=value)
|
|
37
|
+
elif unit.startswith('week'):
|
|
38
|
+
delta = timedelta(weeks=value)
|
|
39
|
+
elif unit.startswith('month'):
|
|
40
|
+
delta = timedelta(days=30 * value)
|
|
41
|
+
else:
|
|
42
|
+
delta = timedelta(days=365 * value)
|
|
43
|
+
return (datetime.utcnow() - delta).strftime("%a, %d %b %Y %H:%M:%S +0000")
|
|
44
|
+
except Exception:
|
|
45
|
+
return ''
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def extract_size(text: str) -> dict:
|
|
49
|
+
match = re.search(r"(\d+(?:[\.,]\d+)?)\s*([A-Za-z]+)", text)
|
|
50
|
+
if match:
|
|
51
|
+
size = match.group(1).replace(',', '.')
|
|
52
|
+
unit = match.group(2)
|
|
53
|
+
return {"size": size, "sizeunit": unit}
|
|
54
|
+
return {"size": "0", "sizeunit": "MB"}
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def he_feed(*args, **kwargs):
|
|
58
|
+
return he_search(*args, **kwargs)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def he_search(shared_state, start_time, request_from, search_string="", mirror=None, season=None, episode=None):
|
|
62
|
+
releases = []
|
|
63
|
+
host = shared_state.values["config"]("Hostnames").get(hostname)
|
|
64
|
+
|
|
65
|
+
if not "arr" in request_from.lower():
|
|
66
|
+
debug(f'Skipping {request_from} search on "{hostname.upper()}" (unsupported media type)!')
|
|
67
|
+
return releases
|
|
68
|
+
|
|
69
|
+
if "radarr" in request_from.lower():
|
|
70
|
+
tag = "movies"
|
|
71
|
+
else:
|
|
72
|
+
tag = "tv-shows"
|
|
73
|
+
|
|
74
|
+
if mirror and mirror not in supported_mirrors:
|
|
75
|
+
debug(f'Mirror "{mirror}" not supported by {hostname}.')
|
|
76
|
+
return releases
|
|
77
|
+
|
|
78
|
+
source_search = ""
|
|
79
|
+
if search_string != "":
|
|
80
|
+
imdb_id = shared_state.is_imdb_id(search_string)
|
|
81
|
+
if imdb_id:
|
|
82
|
+
local_title = get_localized_title(shared_state, imdb_id, 'en')
|
|
83
|
+
if not local_title:
|
|
84
|
+
info(f"{hostname}: no title for IMDb {imdb_id}")
|
|
85
|
+
return releases
|
|
86
|
+
source_search = local_title
|
|
87
|
+
else:
|
|
88
|
+
return releases
|
|
89
|
+
source_search = unescape(source_search)
|
|
90
|
+
else:
|
|
91
|
+
imdb_id = None
|
|
92
|
+
|
|
93
|
+
url = f'https://{host}/tag/{tag}/'
|
|
94
|
+
|
|
95
|
+
headers = {"User-Agent": shared_state.values["user_agent"]}
|
|
96
|
+
params = {"s": source_search}
|
|
97
|
+
|
|
98
|
+
try:
|
|
99
|
+
r = requests.get(url, headers=headers, params=params, timeout=10)
|
|
100
|
+
soup = BeautifulSoup(r.content, 'html.parser')
|
|
101
|
+
results = soup.find_all('div', class_='item')
|
|
102
|
+
except Exception as e:
|
|
103
|
+
info(f"{hostname}: search load error: {e}")
|
|
104
|
+
return releases
|
|
105
|
+
|
|
106
|
+
if not results:
|
|
107
|
+
return releases
|
|
108
|
+
|
|
109
|
+
for result in results:
|
|
110
|
+
try:
|
|
111
|
+
data = result.find('div', class_='data')
|
|
112
|
+
if not data:
|
|
113
|
+
continue
|
|
114
|
+
|
|
115
|
+
headline = data.find('h5')
|
|
116
|
+
if not headline:
|
|
117
|
+
continue
|
|
118
|
+
|
|
119
|
+
a = headline.find('a', href=True)
|
|
120
|
+
if not a:
|
|
121
|
+
continue
|
|
122
|
+
|
|
123
|
+
source = a['href'].strip()
|
|
124
|
+
|
|
125
|
+
head_title = a.get_text(strip=True)
|
|
126
|
+
if not head_title:
|
|
127
|
+
continue
|
|
128
|
+
|
|
129
|
+
head_split = head_title.split(" – ")
|
|
130
|
+
title = head_split[0].strip()
|
|
131
|
+
|
|
132
|
+
if not shared_state.is_valid_release(title, request_from, search_string, season, episode):
|
|
133
|
+
continue
|
|
134
|
+
|
|
135
|
+
size_item = extract_size(head_split[1].strip())
|
|
136
|
+
mb = shared_state.convert_to_mb(size_item)
|
|
137
|
+
|
|
138
|
+
size = mb * 1024 * 1024
|
|
139
|
+
|
|
140
|
+
published = None
|
|
141
|
+
p_meta = data.find('p', class_='meta')
|
|
142
|
+
if p_meta:
|
|
143
|
+
posted_span = None
|
|
144
|
+
for sp in p_meta.find_all('span'):
|
|
145
|
+
txt = sp.get_text(' ', strip=True)
|
|
146
|
+
if txt.lower().startswith('posted') or 'ago' in txt.lower():
|
|
147
|
+
posted_span = txt
|
|
148
|
+
break
|
|
149
|
+
|
|
150
|
+
if posted_span:
|
|
151
|
+
published = parse_posted_ago(posted_span)
|
|
152
|
+
|
|
153
|
+
if published is None:
|
|
154
|
+
continue
|
|
155
|
+
|
|
156
|
+
release_imdb_id = None
|
|
157
|
+
try:
|
|
158
|
+
r = requests.get(source, headers=headers, timeout=10)
|
|
159
|
+
soup = BeautifulSoup(r.content, 'html.parser')
|
|
160
|
+
imdb_link = soup.find('a', href=re.compile(r"imdb\.com/title/tt\d+", re.IGNORECASE))
|
|
161
|
+
if imdb_link:
|
|
162
|
+
release_imdb_id = re.search(r'tt\d+', imdb_link['href']).group()
|
|
163
|
+
if imdb_id and release_imdb_id != imdb_id:
|
|
164
|
+
debug(f"{hostname}: IMDb ID mismatch: expected {imdb_id}, found {release_imdb_id}")
|
|
165
|
+
continue
|
|
166
|
+
else:
|
|
167
|
+
debug(f"{hostname}: imdb link not found for title {title}")
|
|
168
|
+
except Exception as e:
|
|
169
|
+
debug(f"{hostname}: failed to determine imdb_id for title {title}")
|
|
170
|
+
continue
|
|
171
|
+
|
|
172
|
+
password = None
|
|
173
|
+
payload = urlsafe_b64encode(
|
|
174
|
+
f"{title}|{source}|{mirror}|{mb}|{password}|{release_imdb_id}".encode("utf-8")).decode()
|
|
175
|
+
link = f"{shared_state.values['internal_address']}/download/?payload={payload}"
|
|
176
|
+
|
|
177
|
+
releases.append({
|
|
178
|
+
"details": {
|
|
179
|
+
"title": title,
|
|
180
|
+
"hostname": hostname,
|
|
181
|
+
"imdb_id": release_imdb_id,
|
|
182
|
+
"link": link,
|
|
183
|
+
"mirror": mirror,
|
|
184
|
+
"size": size,
|
|
185
|
+
"date": published,
|
|
186
|
+
"source": source
|
|
187
|
+
},
|
|
188
|
+
"type": "protected"
|
|
189
|
+
})
|
|
190
|
+
except Exception as e:
|
|
191
|
+
debug(f"{hostname}: error parsing search result: {e}")
|
|
192
|
+
continue
|
|
193
|
+
|
|
194
|
+
elapsed = time.time() - start_time
|
|
195
|
+
debug(f"Time taken: {elapsed:.2f}s ({hostname})")
|
|
196
|
+
return releases
|
|
@@ -9,8 +9,8 @@ from datetime import datetime
|
|
|
9
9
|
from html import unescape
|
|
10
10
|
from urllib.parse import urljoin
|
|
11
11
|
|
|
12
|
-
from bs4 import BeautifulSoup
|
|
13
12
|
import requests
|
|
13
|
+
from bs4 import BeautifulSoup
|
|
14
14
|
|
|
15
15
|
from quasarr.providers.imdb_metadata import get_localized_title
|
|
16
16
|
from quasarr.providers.log import info, debug
|
|
@@ -65,7 +65,6 @@ def nk_search(shared_state, start_time, request_from, search_string="", mirror=N
|
|
|
65
65
|
if mirror and mirror not in supported_mirrors:
|
|
66
66
|
debug(f'Mirror "{mirror}" not supported by {hostname}.')
|
|
67
67
|
return releases
|
|
68
|
-
|
|
69
68
|
|
|
70
69
|
source_search = ""
|
|
71
70
|
if search_string != "":
|
|
@@ -94,7 +93,6 @@ def nk_search(shared_state, start_time, request_from, search_string="", mirror=N
|
|
|
94
93
|
info(f"{hostname}: search load error: {e}")
|
|
95
94
|
return releases
|
|
96
95
|
|
|
97
|
-
|
|
98
96
|
if not results:
|
|
99
97
|
return releases
|
|
100
98
|
|
|
@@ -118,7 +116,7 @@ def nk_search(shared_state, start_time, request_from, search_string="", mirror=N
|
|
|
118
116
|
a = result.find('a', class_='release-details', href=True)
|
|
119
117
|
if not a:
|
|
120
118
|
continue
|
|
121
|
-
|
|
119
|
+
|
|
122
120
|
sub_title = result.find('span', class_='subtitle')
|
|
123
121
|
if sub_title:
|
|
124
122
|
title = sub_title.get_text(strip=True)
|
|
@@ -163,9 +161,10 @@ def nk_search(shared_state, start_time, request_from, search_string="", mirror=N
|
|
|
163
161
|
|
|
164
162
|
published = convert_to_rss_date(date_text) if date_text else ""
|
|
165
163
|
|
|
166
|
-
payload = urlsafe_b64encode(
|
|
164
|
+
payload = urlsafe_b64encode(
|
|
165
|
+
f"{title}|{source}|{mirror}|{mb}|{password}|{release_imdb_id}".encode("utf-8")).decode()
|
|
167
166
|
link = f"{shared_state.values['internal_address']}/download/?payload={payload}"
|
|
168
|
-
|
|
167
|
+
|
|
169
168
|
releases.append({
|
|
170
169
|
"details": {
|
|
171
170
|
"title": title,
|
|
@@ -186,4 +185,4 @@ def nk_search(shared_state, start_time, request_from, search_string="", mirror=N
|
|
|
186
185
|
|
|
187
186
|
elapsed = time.time() - start_time
|
|
188
187
|
debug(f"Time taken: {elapsed:.2f}s ({hostname})")
|
|
189
|
-
return releases
|
|
188
|
+
return releases
|
|
@@ -27,6 +27,7 @@ quasarr/downloads/sources/by.py
|
|
|
27
27
|
quasarr/downloads/sources/dd.py
|
|
28
28
|
quasarr/downloads/sources/dt.py
|
|
29
29
|
quasarr/downloads/sources/dw.py
|
|
30
|
+
quasarr/downloads/sources/he.py
|
|
30
31
|
quasarr/downloads/sources/mb.py
|
|
31
32
|
quasarr/downloads/sources/nk.py
|
|
32
33
|
quasarr/downloads/sources/nx.py
|
|
@@ -58,6 +59,7 @@ quasarr/search/sources/dd.py
|
|
|
58
59
|
quasarr/search/sources/dt.py
|
|
59
60
|
quasarr/search/sources/dw.py
|
|
60
61
|
quasarr/search/sources/fx.py
|
|
62
|
+
quasarr/search/sources/he.py
|
|
61
63
|
quasarr/search/sources/mb.py
|
|
62
64
|
quasarr/search/sources/nk.py
|
|
63
65
|
quasarr/search/sources/nx.py
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|