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.

Files changed (74) hide show
  1. {quasarr-1.18.0 → quasarr-1.19.0}/PKG-INFO +1 -1
  2. {quasarr-1.18.0 → quasarr-1.19.0}/quasarr/downloads/__init__.py +3 -0
  3. quasarr-1.19.0/quasarr/downloads/sources/he.py +112 -0
  4. {quasarr-1.18.0 → quasarr-1.19.0}/quasarr/downloads/sources/nk.py +3 -7
  5. {quasarr-1.18.0 → quasarr-1.19.0}/quasarr/providers/html_images.py +1 -0
  6. {quasarr-1.18.0 → quasarr-1.19.0}/quasarr/providers/version.py +1 -1
  7. {quasarr-1.18.0 → quasarr-1.19.0}/quasarr/search/__init__.py +4 -0
  8. quasarr-1.19.0/quasarr/search/sources/he.py +196 -0
  9. {quasarr-1.18.0 → quasarr-1.19.0}/quasarr/search/sources/nk.py +6 -7
  10. {quasarr-1.18.0 → quasarr-1.19.0}/quasarr/storage/config.py +1 -0
  11. {quasarr-1.18.0 → quasarr-1.19.0}/quasarr.egg-info/PKG-INFO +1 -1
  12. {quasarr-1.18.0 → quasarr-1.19.0}/quasarr.egg-info/SOURCES.txt +2 -0
  13. {quasarr-1.18.0 → quasarr-1.19.0}/LICENSE +0 -0
  14. {quasarr-1.18.0 → quasarr-1.19.0}/README.md +0 -0
  15. {quasarr-1.18.0 → quasarr-1.19.0}/quasarr/__init__.py +0 -0
  16. {quasarr-1.18.0 → quasarr-1.19.0}/quasarr/api/__init__.py +0 -0
  17. {quasarr-1.18.0 → quasarr-1.19.0}/quasarr/api/arr/__init__.py +0 -0
  18. {quasarr-1.18.0 → quasarr-1.19.0}/quasarr/api/captcha/__init__.py +0 -0
  19. {quasarr-1.18.0 → quasarr-1.19.0}/quasarr/api/config/__init__.py +0 -0
  20. {quasarr-1.18.0 → quasarr-1.19.0}/quasarr/api/sponsors_helper/__init__.py +0 -0
  21. {quasarr-1.18.0 → quasarr-1.19.0}/quasarr/api/statistics/__init__.py +0 -0
  22. {quasarr-1.18.0 → quasarr-1.19.0}/quasarr/downloads/linkcrypters/__init__.py +0 -0
  23. {quasarr-1.18.0 → quasarr-1.19.0}/quasarr/downloads/linkcrypters/al.py +0 -0
  24. {quasarr-1.18.0 → quasarr-1.19.0}/quasarr/downloads/linkcrypters/filecrypt.py +0 -0
  25. {quasarr-1.18.0 → quasarr-1.19.0}/quasarr/downloads/linkcrypters/hide.py +0 -0
  26. {quasarr-1.18.0 → quasarr-1.19.0}/quasarr/downloads/packages/__init__.py +0 -0
  27. {quasarr-1.18.0 → quasarr-1.19.0}/quasarr/downloads/sources/__init__.py +0 -0
  28. {quasarr-1.18.0 → quasarr-1.19.0}/quasarr/downloads/sources/al.py +0 -0
  29. {quasarr-1.18.0 → quasarr-1.19.0}/quasarr/downloads/sources/by.py +0 -0
  30. {quasarr-1.18.0 → quasarr-1.19.0}/quasarr/downloads/sources/dd.py +0 -0
  31. {quasarr-1.18.0 → quasarr-1.19.0}/quasarr/downloads/sources/dt.py +0 -0
  32. {quasarr-1.18.0 → quasarr-1.19.0}/quasarr/downloads/sources/dw.py +0 -0
  33. {quasarr-1.18.0 → quasarr-1.19.0}/quasarr/downloads/sources/mb.py +0 -0
  34. {quasarr-1.18.0 → quasarr-1.19.0}/quasarr/downloads/sources/nx.py +0 -0
  35. {quasarr-1.18.0 → quasarr-1.19.0}/quasarr/downloads/sources/sf.py +0 -0
  36. {quasarr-1.18.0 → quasarr-1.19.0}/quasarr/downloads/sources/sl.py +0 -0
  37. {quasarr-1.18.0 → quasarr-1.19.0}/quasarr/downloads/sources/wd.py +0 -0
  38. {quasarr-1.18.0 → quasarr-1.19.0}/quasarr/providers/__init__.py +0 -0
  39. {quasarr-1.18.0 → quasarr-1.19.0}/quasarr/providers/cloudflare.py +0 -0
  40. {quasarr-1.18.0 → quasarr-1.19.0}/quasarr/providers/html_templates.py +0 -0
  41. {quasarr-1.18.0 → quasarr-1.19.0}/quasarr/providers/imdb_metadata.py +0 -0
  42. {quasarr-1.18.0 → quasarr-1.19.0}/quasarr/providers/log.py +0 -0
  43. {quasarr-1.18.0 → quasarr-1.19.0}/quasarr/providers/myjd_api.py +0 -0
  44. {quasarr-1.18.0 → quasarr-1.19.0}/quasarr/providers/notifications.py +0 -0
  45. {quasarr-1.18.0 → quasarr-1.19.0}/quasarr/providers/obfuscated.py +0 -0
  46. {quasarr-1.18.0 → quasarr-1.19.0}/quasarr/providers/sessions/__init__.py +0 -0
  47. {quasarr-1.18.0 → quasarr-1.19.0}/quasarr/providers/sessions/al.py +0 -0
  48. {quasarr-1.18.0 → quasarr-1.19.0}/quasarr/providers/sessions/dd.py +0 -0
  49. {quasarr-1.18.0 → quasarr-1.19.0}/quasarr/providers/sessions/nx.py +0 -0
  50. {quasarr-1.18.0 → quasarr-1.19.0}/quasarr/providers/shared_state.py +0 -0
  51. {quasarr-1.18.0 → quasarr-1.19.0}/quasarr/providers/statistics.py +0 -0
  52. {quasarr-1.18.0 → quasarr-1.19.0}/quasarr/providers/web_server.py +0 -0
  53. {quasarr-1.18.0 → quasarr-1.19.0}/quasarr/search/sources/__init__.py +0 -0
  54. {quasarr-1.18.0 → quasarr-1.19.0}/quasarr/search/sources/al.py +0 -0
  55. {quasarr-1.18.0 → quasarr-1.19.0}/quasarr/search/sources/by.py +0 -0
  56. {quasarr-1.18.0 → quasarr-1.19.0}/quasarr/search/sources/dd.py +0 -0
  57. {quasarr-1.18.0 → quasarr-1.19.0}/quasarr/search/sources/dt.py +0 -0
  58. {quasarr-1.18.0 → quasarr-1.19.0}/quasarr/search/sources/dw.py +0 -0
  59. {quasarr-1.18.0 → quasarr-1.19.0}/quasarr/search/sources/fx.py +0 -0
  60. {quasarr-1.18.0 → quasarr-1.19.0}/quasarr/search/sources/mb.py +0 -0
  61. {quasarr-1.18.0 → quasarr-1.19.0}/quasarr/search/sources/nx.py +0 -0
  62. {quasarr-1.18.0 → quasarr-1.19.0}/quasarr/search/sources/sf.py +0 -0
  63. {quasarr-1.18.0 → quasarr-1.19.0}/quasarr/search/sources/sl.py +0 -0
  64. {quasarr-1.18.0 → quasarr-1.19.0}/quasarr/search/sources/wd.py +0 -0
  65. {quasarr-1.18.0 → quasarr-1.19.0}/quasarr/storage/__init__.py +0 -0
  66. {quasarr-1.18.0 → quasarr-1.19.0}/quasarr/storage/setup.py +0 -0
  67. {quasarr-1.18.0 → quasarr-1.19.0}/quasarr/storage/sqlite_database.py +0 -0
  68. {quasarr-1.18.0 → quasarr-1.19.0}/quasarr.egg-info/dependency_links.txt +0 -0
  69. {quasarr-1.18.0 → quasarr-1.19.0}/quasarr.egg-info/entry_points.txt +0 -0
  70. {quasarr-1.18.0 → quasarr-1.19.0}/quasarr.egg-info/not-zip-safe +0 -0
  71. {quasarr-1.18.0 → quasarr-1.19.0}/quasarr.egg-info/requires.txt +0 -0
  72. {quasarr-1.18.0 → quasarr-1.19.0}/quasarr.egg-info/top_level.txt +0 -0
  73. {quasarr-1.18.0 → quasarr-1.19.0}/setup.cfg +0 -0
  74. {quasarr-1.18.0 → quasarr-1.19.0}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: quasarr
3
- Version: 1.18.0
3
+ Version: 1.19.0
4
4
  Summary: Quasarr connects JDownloader with Radarr, Sonarr and LazyLibrarian. It also decrypts links protected by CAPTCHAs.
5
5
  Home-page: https://github.com/rix1337/Quasarr
6
6
  Author: rix1337
@@ -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, debug
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 = 'https://' + host + 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'
@@ -8,7 +8,7 @@ import requests
8
8
 
9
9
 
10
10
  def get_version():
11
- return "1.18.0"
11
+ return "1.19.0"
12
12
 
13
13
 
14
14
  def get_latest_version():
@@ -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(f"{title}|{source}|{mirror}|{mb}|{password}|{release_imdb_id}".encode("utf-8")).decode()
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
@@ -32,6 +32,7 @@ class Config(object):
32
32
  ("dt", "secret", ""),
33
33
  ("dw", "secret", ""),
34
34
  ("fx", "secret", ""),
35
+ ("he", "secret", ""),
35
36
  ("mb", "secret", ""),
36
37
  ("nk", "secret", ""),
37
38
  ("nx", "secret", ""),
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: quasarr
3
- Version: 1.18.0
3
+ Version: 1.19.0
4
4
  Summary: Quasarr connects JDownloader with Radarr, Sonarr and LazyLibrarian. It also decrypts links protected by CAPTCHAs.
5
5
  Home-page: https://github.com/rix1337/Quasarr
6
6
  Author: rix1337
@@ -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