quasarr 2.0.0__py3-none-any.whl → 2.1.0__py3-none-any.whl
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/api/__init__.py +3 -3
- quasarr/api/arr/__init__.py +56 -20
- quasarr/api/config/__init__.py +1 -1
- quasarr/api/packages/__init__.py +115 -54
- quasarr/downloads/__init__.py +96 -8
- quasarr/downloads/linkcrypters/filecrypt.py +1 -1
- quasarr/providers/html_templates.py +65 -10
- quasarr/providers/version.py +1 -1
- quasarr/search/sources/al.py +1 -1
- quasarr/search/sources/by.py +1 -1
- quasarr/search/sources/dd.py +2 -1
- quasarr/search/sources/dj.py +2 -2
- quasarr/search/sources/dl.py +8 -2
- quasarr/search/sources/dt.py +1 -1
- quasarr/search/sources/dw.py +6 -7
- quasarr/search/sources/fx.py +4 -4
- quasarr/search/sources/he.py +1 -1
- quasarr/search/sources/mb.py +1 -1
- quasarr/search/sources/nk.py +1 -1
- quasarr/search/sources/nx.py +1 -1
- quasarr/search/sources/sf.py +4 -2
- quasarr/search/sources/sj.py +2 -2
- quasarr/search/sources/sl.py +3 -3
- quasarr/search/sources/wd.py +1 -1
- quasarr/search/sources/wx.py +4 -3
- {quasarr-2.0.0.dist-info → quasarr-2.1.0.dist-info}/METADATA +36 -23
- {quasarr-2.0.0.dist-info → quasarr-2.1.0.dist-info}/RECORD +31 -31
- {quasarr-2.0.0.dist-info → quasarr-2.1.0.dist-info}/WHEEL +0 -0
- {quasarr-2.0.0.dist-info → quasarr-2.1.0.dist-info}/entry_points.txt +0 -0
- {quasarr-2.0.0.dist-info → quasarr-2.1.0.dist-info}/licenses/LICENSE +0 -0
- {quasarr-2.0.0.dist-info → quasarr-2.1.0.dist-info}/top_level.txt +0 -0
|
@@ -6,7 +6,7 @@ import quasarr.providers.html_images as images
|
|
|
6
6
|
from quasarr.providers.version import get_version
|
|
7
7
|
|
|
8
8
|
|
|
9
|
-
def render_centered_html(inner_content):
|
|
9
|
+
def render_centered_html(inner_content, footer_content=""):
|
|
10
10
|
head = '''
|
|
11
11
|
<head>
|
|
12
12
|
<meta charset="utf-8">
|
|
@@ -20,6 +20,7 @@ def render_centered_html(inner_content):
|
|
|
20
20
|
--fg-color: #212529;
|
|
21
21
|
--card-bg: #ffffff;
|
|
22
22
|
--card-shadow: rgba(0, 0, 0, 0.1);
|
|
23
|
+
--card-border: #dee2e6;
|
|
23
24
|
--primary: #0d6efd;
|
|
24
25
|
--secondary: #6c757d;
|
|
25
26
|
--code-bg: #f8f9fa;
|
|
@@ -27,21 +28,40 @@ def render_centered_html(inner_content):
|
|
|
27
28
|
--info-border: #2d5a2d;
|
|
28
29
|
--setup-border: var(--primary);
|
|
29
30
|
--divider-color: #dee2e6;
|
|
31
|
+
--border-color: #dee2e6;
|
|
30
32
|
--btn-subtle-bg: #e9ecef;
|
|
31
33
|
--btn-subtle-border: #ced4da;
|
|
34
|
+
--text-muted: #666;
|
|
35
|
+
--link-color: #0d6efd;
|
|
36
|
+
--success-color: #198754;
|
|
37
|
+
--success-bg: #d1e7dd;
|
|
38
|
+
--success-border: #a3cfbb;
|
|
39
|
+
--error-color: #dc3545;
|
|
40
|
+
--error-bg: #f8d7da;
|
|
41
|
+
--error-border: #f1aeb5;
|
|
32
42
|
}
|
|
33
43
|
@media (prefers-color-scheme: dark) {
|
|
34
44
|
:root {
|
|
35
45
|
--bg-color: #181a1b;
|
|
36
46
|
--fg-color: #f1f1f1;
|
|
37
|
-
--card-bg: #
|
|
47
|
+
--card-bg: #2d3748;
|
|
38
48
|
--card-shadow: rgba(0, 0, 0, 0.5);
|
|
49
|
+
--card-border: #4a5568;
|
|
39
50
|
--code-bg: #2c2f33;
|
|
40
51
|
--info-border: #4a8c4a;
|
|
41
52
|
--setup-border: var(--primary);
|
|
42
|
-
--divider-color: #
|
|
53
|
+
--divider-color: #4a5568;
|
|
54
|
+
--border-color: #4a5568;
|
|
43
55
|
--btn-subtle-bg: #444;
|
|
44
56
|
--btn-subtle-border: #666;
|
|
57
|
+
--text-muted: #a0aec0;
|
|
58
|
+
--link-color: #63b3ed;
|
|
59
|
+
--success-color: #68d391;
|
|
60
|
+
--success-bg: #1c4532;
|
|
61
|
+
--success-border: #276749;
|
|
62
|
+
--error-color: #fc8181;
|
|
63
|
+
--error-bg: #3d2d2d;
|
|
64
|
+
--error-border: #c53030;
|
|
45
65
|
}
|
|
46
66
|
}
|
|
47
67
|
/* Info box styling */
|
|
@@ -66,6 +86,27 @@ def render_centered_html(inner_content):
|
|
|
66
86
|
margin-top: 0;
|
|
67
87
|
color: var(--setup-border);
|
|
68
88
|
}
|
|
89
|
+
/* Status pill styling */
|
|
90
|
+
.status-pill {
|
|
91
|
+
display: inline-flex;
|
|
92
|
+
align-items: center;
|
|
93
|
+
gap: 6px;
|
|
94
|
+
padding: 6px 12px;
|
|
95
|
+
border-radius: 20px;
|
|
96
|
+
font-size: 0.9rem;
|
|
97
|
+
font-weight: 500;
|
|
98
|
+
margin: 8px 0;
|
|
99
|
+
}
|
|
100
|
+
.status-pill.success {
|
|
101
|
+
background: var(--success-bg);
|
|
102
|
+
color: var(--success-color);
|
|
103
|
+
border: 1px solid var(--success-border);
|
|
104
|
+
}
|
|
105
|
+
.status-pill.error {
|
|
106
|
+
background: var(--error-bg);
|
|
107
|
+
color: var(--error-color);
|
|
108
|
+
border: 1px solid var(--error-border);
|
|
109
|
+
}
|
|
69
110
|
/* Subtle button styling (ghost style) */
|
|
70
111
|
.btn-subtle {
|
|
71
112
|
background: transparent;
|
|
@@ -112,7 +153,7 @@ def render_centered_html(inner_content):
|
|
|
112
153
|
width: 100%;
|
|
113
154
|
padding: 0.5rem;
|
|
114
155
|
font-size: 1rem;
|
|
115
|
-
border: 1px solid
|
|
156
|
+
border: 1px solid var(--card-border);
|
|
116
157
|
border-radius: 0.5rem;
|
|
117
158
|
background-color: var(--card-bg);
|
|
118
159
|
color: var(--fg-color);
|
|
@@ -220,22 +261,36 @@ def render_centered_html(inner_content):
|
|
|
220
261
|
box-shadow: 0 2px 6px rgba(108, 117, 125, 0.4);
|
|
221
262
|
}
|
|
222
263
|
a {
|
|
223
|
-
color: var(--
|
|
264
|
+
color: var(--link-color);
|
|
224
265
|
text-decoration: none;
|
|
225
266
|
}
|
|
226
267
|
a:hover {
|
|
227
|
-
|
|
268
|
+
text-decoration: underline;
|
|
228
269
|
}
|
|
229
270
|
/* footer styling */
|
|
230
271
|
footer {
|
|
231
272
|
text-align: center;
|
|
232
273
|
font-size: 0.75rem;
|
|
233
|
-
color: var(--
|
|
274
|
+
color: var(--text-muted);
|
|
234
275
|
padding: 0.5rem 0;
|
|
235
276
|
}
|
|
277
|
+
footer a {
|
|
278
|
+
color: var(--text-muted);
|
|
279
|
+
margin: 0 0;
|
|
280
|
+
}
|
|
281
|
+
footer a:hover {
|
|
282
|
+
color: var(--fg-color);
|
|
283
|
+
}
|
|
236
284
|
</style>
|
|
237
285
|
</head>'''
|
|
238
286
|
|
|
287
|
+
# Build footer content
|
|
288
|
+
version_text = f"Quasarr v.{get_version()}"
|
|
289
|
+
if footer_content:
|
|
290
|
+
footer_html = f"{footer_content} · {version_text}"
|
|
291
|
+
else:
|
|
292
|
+
footer_html = version_text
|
|
293
|
+
|
|
239
294
|
body = f'''
|
|
240
295
|
{head}
|
|
241
296
|
<body>
|
|
@@ -245,7 +300,7 @@ def render_centered_html(inner_content):
|
|
|
245
300
|
</div>
|
|
246
301
|
</div>
|
|
247
302
|
<footer>
|
|
248
|
-
|
|
303
|
+
{footer_html}
|
|
249
304
|
</footer>
|
|
250
305
|
</body>
|
|
251
306
|
'''
|
|
@@ -260,14 +315,14 @@ def render_button(text, button_type="primary", attributes=None):
|
|
|
260
315
|
return f'<button class="{cls}" {attr_str}>{text}</button>'
|
|
261
316
|
|
|
262
317
|
|
|
263
|
-
def render_form(header, form="", script=""):
|
|
318
|
+
def render_form(header, form="", script="", footer_content=""):
|
|
264
319
|
content = f'''
|
|
265
320
|
<h1><img src="{images.logo}" type="image/png" alt="Quasarr logo" class="logo"/>Quasarr</h1>
|
|
266
321
|
<h2>{header}</h2>
|
|
267
322
|
{form}
|
|
268
323
|
{script}
|
|
269
324
|
'''
|
|
270
|
-
return render_centered_html(content)
|
|
325
|
+
return render_centered_html(content, footer_content)
|
|
271
326
|
|
|
272
327
|
|
|
273
328
|
def render_success(message, timeout=10, optional_text=""):
|
quasarr/providers/version.py
CHANGED
quasarr/search/sources/al.py
CHANGED
|
@@ -208,7 +208,7 @@ def al_feed(shared_state, start_time, request_from, mirror=None):
|
|
|
208
208
|
|
|
209
209
|
# Build payload using final_title
|
|
210
210
|
mb = 0 # size not available in feed
|
|
211
|
-
raw = f"{final_title}|{url}|{mirror}|{mb}|{release_id}
|
|
211
|
+
raw = f"{final_title}|{url}|{mirror}|{mb}|{release_id}||{hostname}".encode("utf-8")
|
|
212
212
|
payload = urlsafe_b64encode(raw).decode("utf-8")
|
|
213
213
|
link = f"{shared_state.values['internal_address']}/download/?payload={payload}"
|
|
214
214
|
|
quasarr/search/sources/by.py
CHANGED
|
@@ -124,7 +124,7 @@ def _parse_posts(soup, shared_state, base_url, password, mirror_filter,
|
|
|
124
124
|
imdb_id = None
|
|
125
125
|
|
|
126
126
|
payload = urlsafe_b64encode(
|
|
127
|
-
f"{title}|{source}|{mirror_filter}|{mb}|{password}|{imdb_id}".encode()
|
|
127
|
+
f"{title}|{source}|{mirror_filter}|{mb}|{password}|{imdb_id}|{hostname}".encode()
|
|
128
128
|
).decode()
|
|
129
129
|
link = f"{shared_state.values['internal_address']}/download/?payload={payload}"
|
|
130
130
|
|
quasarr/search/sources/dd.py
CHANGED
|
@@ -106,7 +106,8 @@ def dd_search(shared_state, start_time, request_from, search_string="", mirror=N
|
|
|
106
106
|
mb = shared_state.convert_to_mb(size_item) * 1024 * 1024
|
|
107
107
|
published = convert_to_rss_date(release.get("when"))
|
|
108
108
|
payload = urlsafe_b64encode(
|
|
109
|
-
f"{title}|{source}|{mirror}|{mb}|{password}|{imdb_id}".encode("utf-8")).decode(
|
|
109
|
+
f"{title}|{source}|{mirror}|{mb}|{password}|{imdb_id}|{hostname}".encode("utf-8")).decode(
|
|
110
|
+
"utf-8")
|
|
110
111
|
link = f"{shared_state.values['internal_address']}/download/?payload={payload}"
|
|
111
112
|
|
|
112
113
|
releases.append({
|
quasarr/search/sources/dj.py
CHANGED
|
@@ -68,7 +68,7 @@ def dj_feed(shared_state, start_time, request_from, mirror=None):
|
|
|
68
68
|
imdb_id = None
|
|
69
69
|
|
|
70
70
|
payload = urlsafe_b64encode(
|
|
71
|
-
f"{title}|{series_url}|{mirror}|{mb}|{password}|{imdb_id}".encode("utf-8")
|
|
71
|
+
f"{title}|{series_url}|{mirror}|{mb}|{password}|{imdb_id}|{hostname}".encode("utf-8")
|
|
72
72
|
).decode("utf-8")
|
|
73
73
|
|
|
74
74
|
link = f"{shared_state.values['internal_address']}/download/?payload={payload}"
|
|
@@ -186,7 +186,7 @@ def dj_search(shared_state, start_time, request_from, search_string, mirror=None
|
|
|
186
186
|
size = 0
|
|
187
187
|
|
|
188
188
|
payload = urlsafe_b64encode(
|
|
189
|
-
f"{title}|{series_url}|{mirror}|{mb}|{password}|{imdb_id}".encode("utf-8")
|
|
189
|
+
f"{title}|{series_url}|{mirror}|{mb}|{password}|{imdb_id}|{hostname}".encode("utf-8")
|
|
190
190
|
).decode("utf-8")
|
|
191
191
|
|
|
192
192
|
link = f"{shared_state.values['internal_address']}/download/?payload={payload}"
|
quasarr/search/sources/dl.py
CHANGED
|
@@ -123,7 +123,7 @@ def dl_feed(shared_state, start_time, request_from, mirror=None):
|
|
|
123
123
|
password = ""
|
|
124
124
|
|
|
125
125
|
payload = urlsafe_b64encode(
|
|
126
|
-
f"{title}|{thread_url}|{mirror}|{mb}|{password}|{imdb_id or ''}".encode("utf-8")
|
|
126
|
+
f"{title}|{thread_url}|{mirror}|{mb}|{password}|{imdb_id or ''}|{hostname}".encode("utf-8")
|
|
127
127
|
).decode("utf-8")
|
|
128
128
|
link = f"{shared_state.values['internal_address']}/download/?payload={payload}"
|
|
129
129
|
|
|
@@ -230,6 +230,11 @@ def _search_single_page(shared_state, host, search_string, search_id, page_num,
|
|
|
230
230
|
if not title_elem:
|
|
231
231
|
continue
|
|
232
232
|
|
|
233
|
+
# Skip "Wird gesucht" threads
|
|
234
|
+
label = item.select_one('.contentRow-minor .label')
|
|
235
|
+
if label and 'wird gesucht' in label.get_text(strip=True).lower():
|
|
236
|
+
continue
|
|
237
|
+
|
|
233
238
|
title = ''.join(title_elem.strings)
|
|
234
239
|
|
|
235
240
|
title = re.sub(r'\s+', ' ', title)
|
|
@@ -261,7 +266,8 @@ def _search_single_page(shared_state, host, search_string, search_id, page_num,
|
|
|
261
266
|
password = ""
|
|
262
267
|
|
|
263
268
|
payload = urlsafe_b64encode(
|
|
264
|
-
f"{title_normalized}|{thread_url}|{mirror}|{mb}|{password}|{imdb_id or ''}".encode(
|
|
269
|
+
f"{title_normalized}|{thread_url}|{mirror}|{mb}|{password}|{imdb_id or ''}|{hostname}".encode(
|
|
270
|
+
"utf-8")
|
|
265
271
|
).decode("utf-8")
|
|
266
272
|
link = f"{shared_state.values['internal_address']}/download/?payload={payload}"
|
|
267
273
|
|
quasarr/search/sources/dt.py
CHANGED
|
@@ -111,7 +111,7 @@ def dt_feed(shared_state, start_time, request_from, mirror=None):
|
|
|
111
111
|
published = parse_published_datetime(article)
|
|
112
112
|
|
|
113
113
|
payload = urlsafe_b64encode(
|
|
114
|
-
f"{title}|{source}|{mirror}|{mb}|{password}|{imdb_id}".encode("utf-8")
|
|
114
|
+
f"{title}|{source}|{mirror}|{mb}|{password}|{imdb_id}|{hostname}".encode("utf-8")
|
|
115
115
|
).decode("utf-8")
|
|
116
116
|
link = f"{shared_state.values['internal_address']}/download/?payload={payload}"
|
|
117
117
|
|
quasarr/search/sources/dw.py
CHANGED
|
@@ -98,7 +98,7 @@ def dw_feed(shared_state, start_time, request_from, mirror=None):
|
|
|
98
98
|
date = article.parent.parent.find("span", {"class": "date updated"}).text.strip()
|
|
99
99
|
published = convert_to_rss_date(date)
|
|
100
100
|
payload = urlsafe_b64encode(
|
|
101
|
-
f"{title}|{source}|{mirror}|{mb}|{password}|{imdb_id}".encode("utf-8")).decode("utf-8")
|
|
101
|
+
f"{title}|{source}|{mirror}|{mb}|{password}|{imdb_id}|{hostname}".encode("utf-8")).decode("utf-8")
|
|
102
102
|
link = f"{shared_state.values['internal_address']}/download/?payload={payload}"
|
|
103
103
|
except Exception as e:
|
|
104
104
|
info(f"Error parsing {hostname.upper()} feed: {e}")
|
|
@@ -136,7 +136,6 @@ def dw_search(shared_state, start_time, request_from, search_string, mirror=None
|
|
|
136
136
|
debug(f'Skipping {request_from} search on "{hostname.upper()}" (unsupported media type)!')
|
|
137
137
|
return releases
|
|
138
138
|
|
|
139
|
-
|
|
140
139
|
if "Radarr" in request_from:
|
|
141
140
|
search_type = "videocategory=filme"
|
|
142
141
|
else:
|
|
@@ -168,10 +167,10 @@ def dw_search(shared_state, start_time, request_from, search_string, mirror=None
|
|
|
168
167
|
title = result.a.text.strip()
|
|
169
168
|
|
|
170
169
|
if not shared_state.is_valid_release(title,
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
170
|
+
request_from,
|
|
171
|
+
search_string,
|
|
172
|
+
season,
|
|
173
|
+
episode):
|
|
175
174
|
continue
|
|
176
175
|
|
|
177
176
|
if not imdb_id:
|
|
@@ -188,7 +187,7 @@ def dw_search(shared_state, start_time, request_from, search_string, mirror=None
|
|
|
188
187
|
date = result.parent.parent.find("span", {"class": "date updated"}).text.strip()
|
|
189
188
|
published = convert_to_rss_date(date)
|
|
190
189
|
payload = urlsafe_b64encode(
|
|
191
|
-
f"{title}|{source}|{mirror}|{mb}|{password}|{imdb_id}".encode("utf-8")).decode("utf-8")
|
|
190
|
+
f"{title}|{source}|{mirror}|{mb}|{password}|{imdb_id}|{hostname}".encode("utf-8")).decode("utf-8")
|
|
192
191
|
link = f"{shared_state.values['internal_address']}/download/?payload={payload}"
|
|
193
192
|
except Exception as e:
|
|
194
193
|
info(f"Error parsing {hostname.upper()} search: {e}")
|
quasarr/search/sources/fx.py
CHANGED
|
@@ -34,7 +34,6 @@ def fx_feed(shared_state, start_time, request_from, mirror=None):
|
|
|
34
34
|
debug(f'Skipping {request_from} search on "{hostname.upper()}" (unsupported media type)!')
|
|
35
35
|
return releases
|
|
36
36
|
|
|
37
|
-
|
|
38
37
|
if mirror and mirror not in supported_mirrors:
|
|
39
38
|
debug(f'Mirror "{mirror}" not supported by "{hostname.upper()}". Supported mirrors: {supported_mirrors}.'
|
|
40
39
|
' Skipping search!')
|
|
@@ -81,7 +80,8 @@ def fx_feed(shared_state, start_time, request_from, mirror=None):
|
|
|
81
80
|
mb = shared_state.convert_to_mb(size_item)
|
|
82
81
|
size = mb * 1024 * 1024
|
|
83
82
|
payload = urlsafe_b64encode(
|
|
84
|
-
f"{title}|{link}|{mirror}|{mb}|{password}|{imdb_id}".encode("utf-8")).decode(
|
|
83
|
+
f"{title}|{link}|{mirror}|{mb}|{password}|{imdb_id}|{hostname}".encode("utf-8")).decode(
|
|
84
|
+
"utf-8")
|
|
85
85
|
link = f"{shared_state.values['internal_address']}/download/?payload={payload}"
|
|
86
86
|
except:
|
|
87
87
|
continue
|
|
@@ -125,7 +125,6 @@ def fx_search(shared_state, start_time, request_from, search_string, mirror=None
|
|
|
125
125
|
debug(f'Skipping {request_from} search on "{hostname.upper()}" (unsupported media type)!')
|
|
126
126
|
return releases
|
|
127
127
|
|
|
128
|
-
|
|
129
128
|
if mirror and mirror not in supported_mirrors:
|
|
130
129
|
debug(f'Mirror "{mirror}" not supported by "{hostname.upper()}". Supported mirrors: {supported_mirrors}.'
|
|
131
130
|
' Skipping search!')
|
|
@@ -188,7 +187,8 @@ def fx_search(shared_state, start_time, request_from, search_string, mirror=None
|
|
|
188
187
|
mb = shared_state.convert_to_mb(size_item)
|
|
189
188
|
size = mb * 1024 * 1024
|
|
190
189
|
payload = urlsafe_b64encode(
|
|
191
|
-
f"{title}|{link}|{mirror}|{mb}|{password}|{imdb_id}".encode("utf-8")).decode(
|
|
190
|
+
f"{title}|{link}|{mirror}|{mb}|{password}|{imdb_id}|{hostname}".encode("utf-8")).decode(
|
|
191
|
+
"utf-8")
|
|
192
192
|
link = f"{shared_state.values['internal_address']}/download/?payload={payload}"
|
|
193
193
|
except:
|
|
194
194
|
continue
|
quasarr/search/sources/he.py
CHANGED
|
@@ -177,7 +177,7 @@ def he_search(shared_state, start_time, request_from, search_string="", mirror=N
|
|
|
177
177
|
|
|
178
178
|
password = None
|
|
179
179
|
payload = urlsafe_b64encode(
|
|
180
|
-
f"{title}|{source}|{mirror}|{mb}|{password}|{release_imdb_id}".encode("utf-8")).decode()
|
|
180
|
+
f"{title}|{source}|{mirror}|{mb}|{password}|{release_imdb_id}|{hostname}".encode("utf-8")).decode()
|
|
181
181
|
link = f"{shared_state.values['internal_address']}/download/?payload={payload}"
|
|
182
182
|
|
|
183
183
|
releases.append({
|
quasarr/search/sources/mb.py
CHANGED
|
@@ -116,7 +116,7 @@ def _parse_posts(soup, shared_state, password, mirror_filter,
|
|
|
116
116
|
size_bytes = mb * 1024 * 1024
|
|
117
117
|
|
|
118
118
|
payload = urlsafe_b64encode(
|
|
119
|
-
f"{title}|{source}|{mirror_filter}|{mb}|{password}|{imdb_id}".encode()
|
|
119
|
+
f"{title}|{source}|{mirror_filter}|{mb}|{password}|{imdb_id}|{hostname}".encode()
|
|
120
120
|
).decode()
|
|
121
121
|
link = f"{shared_state.values['internal_address']}/download/?payload={payload}"
|
|
122
122
|
|
quasarr/search/sources/nk.py
CHANGED
|
@@ -168,7 +168,7 @@ def nk_search(shared_state, start_time, request_from, search_string="", mirror=N
|
|
|
168
168
|
published = convert_to_rss_date(date_text) if date_text else ""
|
|
169
169
|
|
|
170
170
|
payload = urlsafe_b64encode(
|
|
171
|
-
f"{title}|{source}|{mirror}|{mb}|{password}|{release_imdb_id}".encode("utf-8")).decode()
|
|
171
|
+
f"{title}|{source}|{mirror}|{mb}|{password}|{release_imdb_id}|{hostname}".encode("utf-8")).decode()
|
|
172
172
|
link = f"{shared_state.values['internal_address']}/download/?payload={payload}"
|
|
173
173
|
|
|
174
174
|
releases.append({
|
quasarr/search/sources/nx.py
CHANGED
|
@@ -59,7 +59,7 @@ def nx_feed(shared_state, start_time, request_from, mirror=None):
|
|
|
59
59
|
imdb_id = item.get('_media', {}).get('imdbid', None)
|
|
60
60
|
mb = shared_state.convert_to_mb(item)
|
|
61
61
|
payload = urlsafe_b64encode(
|
|
62
|
-
f"{title}|{source}|{mirror}|{mb}|{password}|{imdb_id}".encode("utf-8")).decode(
|
|
62
|
+
f"{title}|{source}|{mirror}|{mb}|{password}|{imdb_id}|{hostname}".encode("utf-8")).decode(
|
|
63
63
|
"utf-8")
|
|
64
64
|
link = f"{shared_state.values['internal_address']}/download/?payload={payload}"
|
|
65
65
|
except:
|
quasarr/search/sources/sf.py
CHANGED
|
@@ -142,7 +142,8 @@ def sf_feed(shared_state, start_time, request_from, mirror=None):
|
|
|
142
142
|
imdb_id = None # imdb info is missing here
|
|
143
143
|
|
|
144
144
|
payload = urlsafe_b64encode(
|
|
145
|
-
f"{title}|{source}|{mirror}|{mb}|{password}|{imdb_id}".encode("utf-8")).decode(
|
|
145
|
+
f"{title}|{source}|{mirror}|{mb}|{password}|{imdb_id}|{hostname}".encode("utf-8")).decode(
|
|
146
|
+
"utf-8")
|
|
146
147
|
link = f"{shared_state.values['internal_address']}/download/?payload={payload}"
|
|
147
148
|
except:
|
|
148
149
|
continue
|
|
@@ -349,7 +350,8 @@ def sf_search(shared_state, start_time, request_from, search_string, mirror=None
|
|
|
349
350
|
episode):
|
|
350
351
|
continue
|
|
351
352
|
|
|
352
|
-
payload = urlsafe_b64encode(
|
|
353
|
+
payload = urlsafe_b64encode(
|
|
354
|
+
f"{title}|{source}|{mirror}|{mb}|{password}|{imdb_id}|{hostname}".encode()).decode()
|
|
353
355
|
link = f"{shared_state.values['internal_address']}/download/?payload={payload}"
|
|
354
356
|
size_bytes = mb * 1024 * 1024
|
|
355
357
|
|
quasarr/search/sources/sj.py
CHANGED
|
@@ -68,7 +68,7 @@ def sj_feed(shared_state, start_time, request_from, mirror=None):
|
|
|
68
68
|
imdb_id = None
|
|
69
69
|
|
|
70
70
|
payload = urlsafe_b64encode(
|
|
71
|
-
f"{title}|{series_url}|{mirror}|{mb}|{password}|{imdb_id}".encode("utf-8")
|
|
71
|
+
f"{title}|{series_url}|{mirror}|{mb}|{password}|{imdb_id}|{hostname}".encode("utf-8")
|
|
72
72
|
).decode("utf-8")
|
|
73
73
|
|
|
74
74
|
link = f"{shared_state.values['internal_address']}/download/?payload={payload}"
|
|
@@ -186,7 +186,7 @@ def sj_search(shared_state, start_time, request_from, search_string, mirror=None
|
|
|
186
186
|
size = 0
|
|
187
187
|
|
|
188
188
|
payload = urlsafe_b64encode(
|
|
189
|
-
f"{title}|{series_url}|{mirror}|{mb}|{password}|{imdb_id}".encode("utf-8")
|
|
189
|
+
f"{title}|{series_url}|{mirror}|{mb}|{password}|{imdb_id}|{hostname}".encode("utf-8")
|
|
190
190
|
).decode("utf-8")
|
|
191
191
|
|
|
192
192
|
link = f"{shared_state.values['internal_address']}/download/?payload={payload}"
|
quasarr/search/sources/sl.py
CHANGED
|
@@ -6,9 +6,9 @@ import datetime
|
|
|
6
6
|
import html
|
|
7
7
|
import re
|
|
8
8
|
import time
|
|
9
|
-
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
10
9
|
import xml.etree.ElementTree as ET
|
|
11
10
|
from base64 import urlsafe_b64encode
|
|
11
|
+
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
12
12
|
from urllib.parse import quote_plus
|
|
13
13
|
|
|
14
14
|
import requests
|
|
@@ -90,7 +90,7 @@ def sl_feed(shared_state, start_time, request_from, mirror=None):
|
|
|
90
90
|
imdb_id = m.group(1) if m else None
|
|
91
91
|
|
|
92
92
|
payload = urlsafe_b64encode(
|
|
93
|
-
f"{title}|{source}|{mirror}|{mb}|{password}|{imdb_id}".encode("utf-8")
|
|
93
|
+
f"{title}|{source}|{mirror}|{mb}|{password}|{imdb_id}|{hostname}".encode("utf-8")
|
|
94
94
|
).decode("utf-8")
|
|
95
95
|
link = f"{shared_state.values['internal_address']}/download/?payload={payload}"
|
|
96
96
|
|
|
@@ -214,7 +214,7 @@ def sl_search(shared_state, start_time, request_from, search_string, mirror=None
|
|
|
214
214
|
imdb_id = None
|
|
215
215
|
|
|
216
216
|
payload = urlsafe_b64encode(
|
|
217
|
-
f"{title}|{source}|{mirror}|0|{password}|{imdb_id}".encode('utf-8')
|
|
217
|
+
f"{title}|{source}|{mirror}|0|{password}|{imdb_id}|{hostname}".encode('utf-8')
|
|
218
218
|
).decode('utf-8')
|
|
219
219
|
link = f"{shared_state.values['internal_address']}/download/?payload={payload}"
|
|
220
220
|
|
quasarr/search/sources/wd.py
CHANGED
|
@@ -128,7 +128,7 @@ def _parse_rows(
|
|
|
128
128
|
published = convert_to_rss_date(date_txt) if date_txt else one_hour_ago
|
|
129
129
|
|
|
130
130
|
payload = urlsafe_b64encode(
|
|
131
|
-
f"{title}|{source}|{mirror_filter}|{mb}|{password}|{imdb_id}".encode()
|
|
131
|
+
f"{title}|{source}|{mirror_filter}|{mb}|{password}|{imdb_id}|{hostname}".encode()
|
|
132
132
|
).decode()
|
|
133
133
|
download_link = f"{shared_state.values['internal_address']}/download/?payload={payload}"
|
|
134
134
|
|
quasarr/search/sources/wx.py
CHANGED
|
@@ -96,7 +96,7 @@ def wx_feed(shared_state, start_time, request_from, mirror=None):
|
|
|
96
96
|
password = host.upper()
|
|
97
97
|
|
|
98
98
|
payload = urlsafe_b64encode(
|
|
99
|
-
f"{title}|{source}|{mirror}|{mb}|{password}|{imdb_id or ''}".encode("utf-8")
|
|
99
|
+
f"{title}|{source}|{mirror}|{mb}|{password}|{imdb_id or ''}|{hostname}".encode("utf-8")
|
|
100
100
|
).decode("utf-8")
|
|
101
101
|
link = f"{shared_state.values['internal_address']}/download/?payload={payload}"
|
|
102
102
|
|
|
@@ -253,7 +253,8 @@ def wx_search(shared_state, start_time, request_from, search_string, mirror=None
|
|
|
253
253
|
password = f"www.{host}"
|
|
254
254
|
|
|
255
255
|
payload = urlsafe_b64encode(
|
|
256
|
-
f"{title}|{source}|{mirror}|0|{password}|{item_imdb_id or ''}".encode(
|
|
256
|
+
f"{title}|{source}|{mirror}|0|{password}|{item_imdb_id or ''}|{hostname}".encode(
|
|
257
|
+
"utf-8")
|
|
257
258
|
).decode("utf-8")
|
|
258
259
|
link = f"{shared_state.values['internal_address']}/download/?payload={payload}"
|
|
259
260
|
|
|
@@ -309,7 +310,7 @@ def wx_search(shared_state, start_time, request_from, search_string, mirror=None
|
|
|
309
310
|
password = f"www.{host}"
|
|
310
311
|
|
|
311
312
|
payload = urlsafe_b64encode(
|
|
312
|
-
f"{release_title}|{release_source}|{mirror}|{release_size}|{password}|{item_imdb_id or ''}".encode(
|
|
313
|
+
f"{release_title}|{release_source}|{mirror}|{release_size}|{password}|{item_imdb_id or ''}|{hostname}".encode(
|
|
313
314
|
"utf-8")
|
|
314
315
|
).decode("utf-8")
|
|
315
316
|
link = f"{shared_state.values['internal_address']}/download/?payload={payload}"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: quasarr
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.1.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
|
|
@@ -25,7 +25,7 @@ Dynamic: license-file
|
|
|
25
25
|
Dynamic: requires-dist
|
|
26
26
|
Dynamic: summary
|
|
27
27
|
|
|
28
|
-
#
|
|
28
|
+
#
|
|
29
29
|
|
|
30
30
|
<img src="https://raw.githubusercontent.com/rix1337/Quasarr/main/Quasarr.png" data-canonical-src="https://raw.githubusercontent.com/rix1337/Quasarr/main/Quasarr.png" width="64" height="64" />
|
|
31
31
|
|
|
@@ -41,7 +41,8 @@ indexers. It simply does not know what NZB files are.
|
|
|
41
41
|
Quasarr includes a solution to quickly and easily decrypt protected links.
|
|
42
42
|
[Active monthly Sponsors get access to SponsorsHelper to do so automatically.](https://github.com/rix1337/Quasarr?tab=readme-ov-file#sponsorshelper)
|
|
43
43
|
Alternatively, follow the link from the console output (or discord notification) to solve CAPTCHAs manually.
|
|
44
|
-
Quasarr will confidently handle the rest. Some CAPTCHA types require [Tampermonkey](https://www.tampermonkey.net/) to be
|
|
44
|
+
Quasarr will confidently handle the rest. Some CAPTCHA types require [Tampermonkey](https://www.tampermonkey.net/) to be
|
|
45
|
+
installed in your browser.
|
|
45
46
|
|
|
46
47
|
# Instructions
|
|
47
48
|
|
|
@@ -56,9 +57,11 @@ Quasarr will confidently handle the rest. Some CAPTCHA types require [Tampermonk
|
|
|
56
57
|
|
|
57
58
|
## FlareSolverr (Optional)
|
|
58
59
|
|
|
59
|
-
FlareSolverr is **optional** but **required for some sites** (e.g., AL) that use Cloudflare protection. You can skip
|
|
60
|
+
FlareSolverr is **optional** but **required for some sites** (e.g., AL) that use Cloudflare protection. You can skip
|
|
61
|
+
FlareSolverr during setup and configure it later via the web UI.
|
|
60
62
|
|
|
61
63
|
If using FlareSolverr, provide your URL including the version path:
|
|
64
|
+
|
|
62
65
|
```
|
|
63
66
|
http://192.168.1.1:8191/v1
|
|
64
67
|
```
|
|
@@ -69,11 +72,13 @@ http://192.168.1.1:8191/v1
|
|
|
69
72
|
|
|
70
73
|
## Quasarr
|
|
71
74
|
|
|
72
|
-
> ⚠️ Quasarr requires at least one valid hostname to start. It does not provide or endorse any specific sources, but
|
|
75
|
+
> ⚠️ Quasarr requires at least one valid hostname to start. It does not provide or endorse any specific sources, but
|
|
76
|
+
> community-maintained lists are available:
|
|
73
77
|
|
|
74
78
|
🔗 **[https://quasarr-host.name](https://quasarr-host.name)** — community guide for finding hostnames
|
|
75
79
|
|
|
76
|
-
📋 Alternatively, browse community suggestions via [pastebin search](https://pastebin.com/search?q=hostnames+quasarr) (
|
|
80
|
+
📋 Alternatively, browse community suggestions via [pastebin search](https://pastebin.com/search?q=hostnames+quasarr) (
|
|
81
|
+
login required).
|
|
77
82
|
|
|
78
83
|
> Authentication is optional but strongly recommended.
|
|
79
84
|
>
|
|
@@ -84,8 +89,9 @@ http://192.168.1.1:8191/v1
|
|
|
84
89
|
|
|
85
90
|
## JDownloader
|
|
86
91
|
|
|
87
|
-
> ⚠️ If using Docker:
|
|
88
|
-
> JDownloader's download path must be available to Radarr/Sonarr/LazyLibrarian with **identical internal and external
|
|
92
|
+
> ⚠️ If using Docker:
|
|
93
|
+
> JDownloader's download path must be available to Radarr/Sonarr/LazyLibrarian with **identical internal and external
|
|
94
|
+
path mappings**!
|
|
89
95
|
> Matching only the external path is not sufficient.
|
|
90
96
|
|
|
91
97
|
1. Start and connect JDownloader to [My JDownloader](https://my.jdownloader.org)
|
|
@@ -94,7 +100,8 @@ http://192.168.1.1:8191/v1
|
|
|
94
100
|
<details>
|
|
95
101
|
<summary>Fresh install recommended</summary>
|
|
96
102
|
|
|
97
|
-
Consider setting up a fresh JDownloader instance. Quasarr will modify JDownloader's settings to enable
|
|
103
|
+
Consider setting up a fresh JDownloader instance. Quasarr will modify JDownloader's settings to enable
|
|
104
|
+
Radarr/Sonarr/LazyLibrarian integration.
|
|
98
105
|
|
|
99
106
|
</details>
|
|
100
107
|
|
|
@@ -102,7 +109,8 @@ Consider setting up a fresh JDownloader instance. Quasarr will modify JDownloade
|
|
|
102
109
|
|
|
103
110
|
## Radarr / Sonarr
|
|
104
111
|
|
|
105
|
-
> ⚠️ **Sonarr users:** Set all shows (including anime) to the **Standard** series type. Quasarr cannot find releases for
|
|
112
|
+
> ⚠️ **Sonarr users:** Set all shows (including anime) to the **Standard** series type. Quasarr cannot find releases for
|
|
113
|
+
> shows set to Anime/Absolute.
|
|
106
114
|
|
|
107
115
|
|
|
108
116
|
Add Quasarr as both a **Newznab Indexer** and **SABnzbd Download Client** using your Quasarr URL and API Key.
|
|
@@ -118,9 +126,11 @@ Add Quasarr as both a **Newznab Indexer** and **SABnzbd Download Client** using
|
|
|
118
126
|
<summary>Restrict results to a specific mirror</summary>
|
|
119
127
|
|
|
120
128
|
Append the mirror name to your Newznab URL:
|
|
129
|
+
|
|
121
130
|
```
|
|
122
131
|
/api/dropbox/
|
|
123
132
|
```
|
|
133
|
+
|
|
124
134
|
Only releases with `dropbox` in a link will be returned. If the mirror isn't available, the release will fail.
|
|
125
135
|
|
|
126
136
|
</details>
|
|
@@ -136,27 +146,29 @@ Only releases with `dropbox` in a link will be returned. If the mirror isn't ava
|
|
|
136
146
|
|
|
137
147
|
### SABnzbd+ Downloader
|
|
138
148
|
|
|
139
|
-
| Setting
|
|
140
|
-
|
|
149
|
+
| Setting | Value |
|
|
150
|
+
|----------|----------------------------|
|
|
141
151
|
| URL/Port | Your Quasarr host and port |
|
|
142
|
-
| API Key
|
|
143
|
-
| Category | `docs`
|
|
152
|
+
| API Key | Your Quasarr API Key |
|
|
153
|
+
| Category | `docs` |
|
|
144
154
|
|
|
145
155
|
### Newznab Provider
|
|
146
156
|
|
|
147
|
-
| Setting | Value
|
|
148
|
-
|
|
149
|
-
| URL
|
|
150
|
-
| API
|
|
157
|
+
| Setting | Value |
|
|
158
|
+
|---------|----------------------|
|
|
159
|
+
| URL | Your Quasarr URL |
|
|
160
|
+
| API | Your Quasarr API Key |
|
|
151
161
|
|
|
152
162
|
### Fix Import & Processing
|
|
153
163
|
|
|
154
164
|
**Importing:**
|
|
165
|
+
|
|
155
166
|
- Enable `OpenLibrary api for book/author information`
|
|
156
167
|
- Set Primary Information Source to `OpenLibrary`
|
|
157
168
|
- Add to Import languages: `, Unknown` (German users: `, de, ger, de-DE`)
|
|
158
169
|
|
|
159
170
|
**Processing → Folders:**
|
|
171
|
+
|
|
160
172
|
- Add your Quasarr download path (typically `/downloads/Quasarr/`)
|
|
161
173
|
|
|
162
174
|
</details>
|
|
@@ -236,7 +248,8 @@ Most feature requests can be satisfied by:
|
|
|
236
248
|
- There are no hostname integrations in active development unless you see an open pull request
|
|
237
249
|
[here](https://github.com/rix1337/Quasarr/pulls).
|
|
238
250
|
- **Pull requests are welcome!** Especially for popular hostnames.
|
|
239
|
-
- A short guide to set up required dev services is found
|
|
251
|
+
- A short guide to set up required dev services is found
|
|
252
|
+
in [/docker/dev-setup.md](https://github.com/rix1337/Quasarr/blob/main/docker/dev-setup.md)
|
|
240
253
|
- Always reach out on Discord before starting work on a new feature to prevent waste of time.
|
|
241
254
|
- Please follow the existing code style and project structure.
|
|
242
255
|
- Anti-bot measures must be circumvented fully by Quasarr. Thus, you will need to provide a working solution for new
|
|
@@ -258,11 +271,11 @@ Image access is limited to [active monthly GitHub sponsors](https://github.com/u
|
|
|
258
271
|
|
|
259
272
|
1. Start your [sponsorship](https://github.com/users/rix1337/sponsorship) first.
|
|
260
273
|
2. Open [GitHub Classic Token Settings](https://github.com/settings/tokens/new?type=classic)
|
|
261
|
-
3. Name it (e.g., `SponsorsHelper`) and choose unlimited expiration
|
|
274
|
+
3. Name it (e.g., `SponsorsHelper`) and choose unlimited expiration
|
|
262
275
|
4. Enable these scopes:
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
276
|
+
- `read:packages`
|
|
277
|
+
- `read:user`
|
|
278
|
+
- `read:org`
|
|
266
279
|
5. Click **Generate token** and copy it for the next steps
|
|
267
280
|
|
|
268
281
|
---
|