quasarr 1.22.0__py3-none-any.whl → 1.24.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/captcha/__init__.py +108 -33
- quasarr/downloads/__init__.py +217 -278
- quasarr/downloads/sources/al.py +28 -3
- quasarr/downloads/sources/by.py +8 -2
- quasarr/downloads/sources/dd.py +15 -8
- quasarr/downloads/sources/dj.py +11 -2
- quasarr/downloads/sources/dl.py +51 -58
- quasarr/downloads/sources/dt.py +34 -12
- quasarr/downloads/sources/dw.py +9 -3
- quasarr/downloads/sources/he.py +10 -4
- quasarr/downloads/sources/mb.py +10 -4
- quasarr/downloads/sources/nk.py +9 -3
- quasarr/downloads/sources/nx.py +31 -10
- quasarr/downloads/sources/sf.py +61 -55
- quasarr/downloads/sources/sj.py +11 -2
- quasarr/downloads/sources/sl.py +22 -9
- quasarr/downloads/sources/wd.py +9 -3
- quasarr/downloads/sources/wx.py +12 -13
- quasarr/providers/obfuscated.py +37 -18
- quasarr/providers/sessions/al.py +38 -10
- quasarr/providers/version.py +1 -1
- quasarr/search/sources/dl.py +10 -6
- {quasarr-1.22.0.dist-info → quasarr-1.24.0.dist-info}/METADATA +2 -2
- {quasarr-1.22.0.dist-info → quasarr-1.24.0.dist-info}/RECORD +28 -28
- {quasarr-1.22.0.dist-info → quasarr-1.24.0.dist-info}/WHEEL +0 -0
- {quasarr-1.22.0.dist-info → quasarr-1.24.0.dist-info}/entry_points.txt +0 -0
- {quasarr-1.22.0.dist-info → quasarr-1.24.0.dist-info}/licenses/LICENSE +0 -0
- {quasarr-1.22.0.dist-info → quasarr-1.24.0.dist-info}/top_level.txt +0 -0
quasarr/api/captcha/__init__.py
CHANGED
|
@@ -8,16 +8,15 @@ from base64 import urlsafe_b64encode, urlsafe_b64decode
|
|
|
8
8
|
from urllib.parse import quote, unquote, urljoin
|
|
9
9
|
|
|
10
10
|
import requests
|
|
11
|
-
from bottle import request, response, redirect
|
|
11
|
+
from bottle import request, response, redirect, HTTPResponse
|
|
12
12
|
|
|
13
13
|
import quasarr.providers.html_images as images
|
|
14
14
|
from quasarr.downloads.linkcrypters.filecrypt import get_filecrypt_links, DLC
|
|
15
15
|
from quasarr.downloads.packages import delete_package
|
|
16
|
+
from quasarr.providers import obfuscated
|
|
16
17
|
from quasarr.providers import shared_state
|
|
17
18
|
from quasarr.providers.html_templates import render_button, render_centered_html
|
|
18
19
|
from quasarr.providers.log import info, debug
|
|
19
|
-
from quasarr.providers.obfuscated import captcha_js, captcha_values, filecrypt_user_js, junkies_user_js, \
|
|
20
|
-
keeplinks_user_js
|
|
21
20
|
from quasarr.providers.statistics import StatsHelper
|
|
22
21
|
|
|
23
22
|
|
|
@@ -25,6 +24,21 @@ def js_single_quoted_string_safe(text):
|
|
|
25
24
|
return text.replace('\\', '\\\\').replace("'", "\\'")
|
|
26
25
|
|
|
27
26
|
|
|
27
|
+
def check_package_exists(package_id):
|
|
28
|
+
if not shared_state.get_db("protected").retrieve(package_id):
|
|
29
|
+
raise HTTPResponse(
|
|
30
|
+
status=404,
|
|
31
|
+
body=render_centered_html(f'''
|
|
32
|
+
<h1><img src="{images.logo}" class="logo"/>Quasarr</h1>
|
|
33
|
+
<p><b>Error:</b> Package not found or already solved.</p>
|
|
34
|
+
<p>
|
|
35
|
+
{render_button("Back", "secondary", {"onclick": "location.href='/captcha'"})}
|
|
36
|
+
</p>
|
|
37
|
+
'''),
|
|
38
|
+
content_type="text/html"
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
|
|
28
42
|
def setup_captcha_routes(app):
|
|
29
43
|
@app.get('/captcha')
|
|
30
44
|
def check_captcha():
|
|
@@ -83,10 +97,16 @@ def setup_captcha_routes(app):
|
|
|
83
97
|
|
|
84
98
|
sj = shared_state.values["config"]("Hostnames").get("sj")
|
|
85
99
|
dj = shared_state.values["config"]("Hostnames").get("dj")
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
100
|
+
|
|
101
|
+
def is_junkies_link(link):
|
|
102
|
+
"""Check if link is a junkies link (handles [[url, mirror]] format)."""
|
|
103
|
+
url = link[0] if isinstance(link, (list, tuple)) else link
|
|
104
|
+
mirror = link[1] if isinstance(link, (list, tuple)) and len(link) > 1 else ""
|
|
105
|
+
if mirror == "junkies":
|
|
106
|
+
return True
|
|
107
|
+
return (sj and sj in url) or (dj and dj in url)
|
|
108
|
+
|
|
109
|
+
has_junkies_links = any(is_junkies_link(link) for link in prioritized_links)
|
|
90
110
|
|
|
91
111
|
# KeepLinks uses nested arrays like FileCrypt: [["url", "mirror"]]
|
|
92
112
|
has_keeplinks_links = any(
|
|
@@ -94,12 +114,21 @@ def setup_captcha_routes(app):
|
|
|
94
114
|
for link in prioritized_links
|
|
95
115
|
)
|
|
96
116
|
|
|
117
|
+
# ToLink uses nested arrays like FileCrypt: [["url", "mirror"]]
|
|
118
|
+
has_tolink_links = any(
|
|
119
|
+
("tolink." in link[0] if isinstance(link, (list, tuple)) else "tolink." in link)
|
|
120
|
+
for link in prioritized_links
|
|
121
|
+
)
|
|
122
|
+
|
|
97
123
|
if has_junkies_links:
|
|
98
124
|
debug("Redirecting to Junkies CAPTCHA")
|
|
99
125
|
redirect(f"/captcha/junkies?data={quote(encoded_payload)}")
|
|
100
126
|
elif has_keeplinks_links:
|
|
101
127
|
debug("Redirecting to KeepLinks CAPTCHA")
|
|
102
128
|
redirect(f"/captcha/keeplinks?data={quote(encoded_payload)}")
|
|
129
|
+
elif has_tolink_links:
|
|
130
|
+
debug("Redirecting to ToLink CAPTCHA")
|
|
131
|
+
redirect(f"/captcha/tolink?data={quote(encoded_payload)}")
|
|
103
132
|
elif filecrypt_session:
|
|
104
133
|
debug(f'Redirecting to circle CAPTCHA')
|
|
105
134
|
redirect(f"/captcha/circle?data={quote(encoded_payload)}")
|
|
@@ -122,7 +151,7 @@ def setup_captcha_routes(app):
|
|
|
122
151
|
return {"error": f"Failed to decode payload: {str(e)}"}
|
|
123
152
|
|
|
124
153
|
def render_userscript_section(url, package_id, title, password, provider_type="junkies"):
|
|
125
|
-
"""Render the userscript UI section for Junkies or
|
|
154
|
+
"""Render the userscript UI section for Junkies, KeepLinks, or ToLink pages
|
|
126
155
|
|
|
127
156
|
This is the MAIN solution for these providers (not a bypass/fallback).
|
|
128
157
|
|
|
@@ -131,10 +160,11 @@ def setup_captcha_routes(app):
|
|
|
131
160
|
package_id: Package identifier
|
|
132
161
|
title: Package title
|
|
133
162
|
password: Package password
|
|
134
|
-
provider_type: Either "junkies" or "
|
|
163
|
+
provider_type: Either "junkies", "keeplinks", or "tolink"
|
|
135
164
|
"""
|
|
136
165
|
|
|
137
|
-
|
|
166
|
+
provider_names = {"junkies": "Junkies", "keeplinks": "KeepLinks", "tolink": "ToLink"}
|
|
167
|
+
provider_name = provider_names.get(provider_type, "Provider")
|
|
138
168
|
userscript_url = f"/captcha/{provider_type}.user.js"
|
|
139
169
|
storage_key = f"hide{provider_name}SetupInstructions"
|
|
140
170
|
|
|
@@ -173,7 +203,7 @@ def setup_captcha_routes(app):
|
|
|
173
203
|
<a href="#" id="show-setup-btn" style="color: #58a6ff;">ℹ️ Show instructions again</a>
|
|
174
204
|
</div>
|
|
175
205
|
|
|
176
|
-
<strong><a href="{url_with_quick_transfer_params}" target="
|
|
206
|
+
<strong><a href="{url_with_quick_transfer_params}" target="_self">🔗 Obtain the download links here!</a></strong><br><br>
|
|
177
207
|
|
|
178
208
|
<form id="bypass-form" action="/captcha/bypass-submit" method="post" enctype="multipart/form-data">
|
|
179
209
|
<input type="hidden" name="package_id" value="{package_id}" />
|
|
@@ -233,7 +263,9 @@ def setup_captcha_routes(app):
|
|
|
233
263
|
title = payload.get("title")
|
|
234
264
|
password = payload.get("password")
|
|
235
265
|
urls = payload.get("links")
|
|
236
|
-
url = urls[0]
|
|
266
|
+
url = urls[0][0] if isinstance(urls[0], (list, tuple)) else urls[0]
|
|
267
|
+
|
|
268
|
+
check_package_exists(package_id)
|
|
237
269
|
|
|
238
270
|
return render_centered_html(f"""
|
|
239
271
|
<!DOCTYPE html>
|
|
@@ -267,7 +299,9 @@ def setup_captcha_routes(app):
|
|
|
267
299
|
title = payload.get("title")
|
|
268
300
|
password = payload.get("password")
|
|
269
301
|
urls = payload.get("links")
|
|
270
|
-
|
|
302
|
+
|
|
303
|
+
check_package_exists(package_id)
|
|
304
|
+
|
|
271
305
|
url = urls[0][0] if isinstance(urls[0], (list, tuple)) else urls[0]
|
|
272
306
|
|
|
273
307
|
return render_centered_html(f"""
|
|
@@ -287,24 +321,67 @@ def setup_captcha_routes(app):
|
|
|
287
321
|
</body>
|
|
288
322
|
</html>""")
|
|
289
323
|
|
|
324
|
+
@app.get("/captcha/tolink")
|
|
325
|
+
def serve_tolink_captcha():
|
|
326
|
+
payload = decode_payload()
|
|
327
|
+
|
|
328
|
+
if "error" in payload:
|
|
329
|
+
return render_centered_html(f'''<h1><img src="{images.logo}" type="image/png" alt="Quasarr logo" class="logo"/>Quasarr</h1>
|
|
330
|
+
<p>{payload["error"]}</p>
|
|
331
|
+
<p>
|
|
332
|
+
{render_button("Back", "secondary", {"onclick": "location.href='/'"})}
|
|
333
|
+
</p>''')
|
|
334
|
+
|
|
335
|
+
package_id = payload.get("package_id")
|
|
336
|
+
title = payload.get("title")
|
|
337
|
+
password = payload.get("password")
|
|
338
|
+
urls = payload.get("links")
|
|
339
|
+
|
|
340
|
+
check_package_exists(package_id)
|
|
341
|
+
|
|
342
|
+
url = urls[0][0] if isinstance(urls[0], (list, tuple)) else urls[0]
|
|
343
|
+
|
|
344
|
+
return render_centered_html(f"""
|
|
345
|
+
<!DOCTYPE html>
|
|
346
|
+
<html>
|
|
347
|
+
<body>
|
|
348
|
+
<h1><img src="{images.logo}" type="image/png" alt="Quasarr logo" class="logo"/>Quasarr</h1>
|
|
349
|
+
<p><b>Package:</b> {title}</p>
|
|
350
|
+
{render_userscript_section(url, package_id, title, password, "tolink")}
|
|
351
|
+
<p>
|
|
352
|
+
{render_button("Delete Package", "secondary", {"onclick": f"location.href='/captcha/delete/{package_id}'"})}
|
|
353
|
+
</p>
|
|
354
|
+
<p>
|
|
355
|
+
{render_button("Back", "secondary", {"onclick": "location.href='/'"})}
|
|
356
|
+
</p>
|
|
357
|
+
|
|
358
|
+
</body>
|
|
359
|
+
</html>""")
|
|
360
|
+
|
|
290
361
|
@app.get('/captcha/junkies.user.js')
|
|
291
362
|
def serve_junkies_user_js():
|
|
292
363
|
sj = shared_state.values["config"]("Hostnames").get("sj")
|
|
293
364
|
dj = shared_state.values["config"]("Hostnames").get("dj")
|
|
294
365
|
|
|
295
|
-
content = junkies_user_js(sj, dj)
|
|
366
|
+
content = obfuscated.junkies_user_js(sj, dj)
|
|
296
367
|
response.content_type = 'application/javascript'
|
|
297
368
|
return content
|
|
298
369
|
|
|
299
370
|
@app.get('/captcha/keeplinks.user.js')
|
|
300
371
|
def serve_keeplinks_user_js():
|
|
301
|
-
content = keeplinks_user_js()
|
|
372
|
+
content = obfuscated.keeplinks_user_js()
|
|
373
|
+
response.content_type = 'application/javascript'
|
|
374
|
+
return content
|
|
375
|
+
|
|
376
|
+
@app.get('/captcha/tolink.user.js')
|
|
377
|
+
def serve_tolink_user_js():
|
|
378
|
+
content = obfuscated.tolink_user_js()
|
|
302
379
|
response.content_type = 'application/javascript'
|
|
303
380
|
return content
|
|
304
381
|
|
|
305
382
|
@app.get('/captcha/filecrypt.user.js')
|
|
306
383
|
def serve_filecrypt_user_js():
|
|
307
|
-
content = filecrypt_user_js()
|
|
384
|
+
content = obfuscated.filecrypt_user_js()
|
|
308
385
|
response.content_type = 'application/javascript'
|
|
309
386
|
return content
|
|
310
387
|
|
|
@@ -350,7 +427,7 @@ def setup_captcha_routes(app):
|
|
|
350
427
|
<a href="#" id="show-setup-btn" style="color: #58a6ff;">ℹ️ Show instructions again</a>
|
|
351
428
|
</div>
|
|
352
429
|
|
|
353
|
-
<strong><a href="{url_with_quick_transfer_params}" target="
|
|
430
|
+
<strong><a href="{url_with_quick_transfer_params}" target="_self">🔗 Obtain the download links here!</a></strong><br><br>
|
|
354
431
|
|
|
355
432
|
<form id="bypass-form" action="/captcha/bypass-submit" method="post" enctype="multipart/form-data">
|
|
356
433
|
<input type="hidden" name="package_id" value="{package_id}" />
|
|
@@ -451,12 +528,12 @@ def setup_captcha_routes(app):
|
|
|
451
528
|
try:
|
|
452
529
|
decompressed = zlib.decompress(decoded, -15) # -15 = raw deflate, no zlib header
|
|
453
530
|
except Exception as e:
|
|
454
|
-
|
|
531
|
+
debug(f"Decompression error: {e}, trying with header...")
|
|
455
532
|
try:
|
|
456
533
|
# Fallback: try with zlib header
|
|
457
534
|
decompressed = zlib.decompress(decoded)
|
|
458
535
|
except Exception as e2:
|
|
459
|
-
info(f"Decompression
|
|
536
|
+
info(f"Decompression failed without and with header: {e2}")
|
|
460
537
|
return render_centered_html(f'''<h1><img src="{images.logo}" type="image/png" alt="Quasarr logo" class="logo"/>Quasarr</h1>
|
|
461
538
|
<p><b>Error:</b> Failed to decompress data: {str(e)}</p>
|
|
462
539
|
<p>
|
|
@@ -585,6 +662,8 @@ def setup_captcha_routes(app):
|
|
|
585
662
|
desired_mirror = payload.get("mirror")
|
|
586
663
|
prioritized_links = payload.get("links")
|
|
587
664
|
|
|
665
|
+
check_package_exists(package_id)
|
|
666
|
+
|
|
588
667
|
if not prioritized_links:
|
|
589
668
|
# No links found, show an error message
|
|
590
669
|
return render_centered_html(f'''
|
|
@@ -631,7 +710,7 @@ def setup_captcha_routes(app):
|
|
|
631
710
|
|
|
632
711
|
content = render_centered_html(r'''
|
|
633
712
|
<script type="text/javascript">
|
|
634
|
-
var api_key = "''' + captcha_values()["api_key"] + r'''";
|
|
713
|
+
var api_key = "''' + obfuscated.captcha_values()["api_key"] + r'''";
|
|
635
714
|
var endpoint = '/' + window.location.pathname.split('/')[1] + '/' + api_key + '.html';
|
|
636
715
|
var solveAnotherHtml = `<p>''' + solve_another_html + r'''</p><p>''' + back_button_html + r'''</p>`;
|
|
637
716
|
var noMoreHtml = `<p><b>No more CAPTCHAs</b></p><p>''' + back_button_html + r'''</p>`;
|
|
@@ -685,7 +764,7 @@ def setup_captcha_routes(app):
|
|
|
685
764
|
reloadSection.style.display = "block";
|
|
686
765
|
});
|
|
687
766
|
}
|
|
688
|
-
''' + captcha_js() + f'''</script>
|
|
767
|
+
''' + obfuscated.captcha_js() + f'''</script>
|
|
689
768
|
<div>
|
|
690
769
|
<h1><img src="{images.logo}" type="image/png" alt="Quasarr logo" class="logo"/>Quasarr</h1>
|
|
691
770
|
<p id="package-title" style="max-width: 370px; word-wrap: break-word; overflow-wrap: break-word;"><b>Package:</b> {title}</p>
|
|
@@ -720,7 +799,7 @@ def setup_captcha_routes(app):
|
|
|
720
799
|
|
|
721
800
|
@app.post('/captcha/<captcha_id>.html')
|
|
722
801
|
def proxy_html(captcha_id):
|
|
723
|
-
target_url = f"{captcha_values()["url"]}/captcha/{captcha_id}.html"
|
|
802
|
+
target_url = f"{obfuscated.captcha_values()["url"]}/captcha/{captcha_id}.html"
|
|
724
803
|
|
|
725
804
|
headers = {key: value for key, value in request.headers.items() if key != 'Host'}
|
|
726
805
|
data = request.body.read()
|
|
@@ -740,7 +819,7 @@ def setup_captcha_routes(app):
|
|
|
740
819
|
|
|
741
820
|
@app.post('/captcha/<captcha_id>.json')
|
|
742
821
|
def proxy_json(captcha_id):
|
|
743
|
-
target_url = f"{captcha_values()["url"]}/captcha/{captcha_id}.json"
|
|
822
|
+
target_url = f"{obfuscated.captcha_values()["url"]}/captcha/{captcha_id}.json"
|
|
744
823
|
|
|
745
824
|
headers = {key: value for key, value in request.headers.items() if key != 'Host'}
|
|
746
825
|
data = request.body.read()
|
|
@@ -751,7 +830,7 @@ def setup_captcha_routes(app):
|
|
|
751
830
|
|
|
752
831
|
@app.get('/captcha/js/<filename>')
|
|
753
832
|
def serve_local_js(filename):
|
|
754
|
-
upstream = f"{captcha_values()['url']}/{filename}"
|
|
833
|
+
upstream = f"{obfuscated.captcha_values()['url']}/{filename}"
|
|
755
834
|
try:
|
|
756
835
|
upstream_resp = requests.get(upstream, verify=False, stream=True)
|
|
757
836
|
upstream_resp.raise_for_status()
|
|
@@ -764,7 +843,7 @@ def setup_captcha_routes(app):
|
|
|
764
843
|
|
|
765
844
|
@app.get('/captcha/<captcha_id>/<uuid>/<filename>')
|
|
766
845
|
def proxy_pngs(captcha_id, uuid, filename):
|
|
767
|
-
new_url = f"{captcha_values()["url"]}/captcha/{captcha_id}/{uuid}/{filename}"
|
|
846
|
+
new_url = f"{obfuscated.captcha_values()["url"]}/captcha/{captcha_id}/{uuid}/{filename}"
|
|
768
847
|
|
|
769
848
|
try:
|
|
770
849
|
external_response = requests.get(new_url, stream=True, verify=False)
|
|
@@ -779,7 +858,7 @@ def setup_captcha_routes(app):
|
|
|
779
858
|
|
|
780
859
|
@app.post('/captcha/<captcha_id>/check')
|
|
781
860
|
def proxy_check(captcha_id):
|
|
782
|
-
new_url = f"{captcha_values()["url"]}/captcha/{captcha_id}/check"
|
|
861
|
+
new_url = f"{obfuscated.captcha_values()["url"]}/captcha/{captcha_id}/check"
|
|
783
862
|
headers = {key: value for key, value in request.headers.items()}
|
|
784
863
|
|
|
785
864
|
data = request.body.read()
|
|
@@ -808,13 +887,7 @@ def setup_captcha_routes(app):
|
|
|
808
887
|
{render_button("Back", "secondary", {"onclick": "location.href='/captcha'"})}
|
|
809
888
|
</p>''')
|
|
810
889
|
|
|
811
|
-
|
|
812
|
-
if not package_exists:
|
|
813
|
-
return render_centered_html(f'''<h1><img src="{images.logo}" type="image/png" alt="Quasarr logo" class="logo"/>Quasarr</h1>
|
|
814
|
-
<p><b>Error:</b> Package not found or already solved.</p>
|
|
815
|
-
<p>
|
|
816
|
-
{render_button("Back", "secondary", {"onclick": "location.href='/captcha'"})}
|
|
817
|
-
</p>''')
|
|
890
|
+
check_package_exists(package_id)
|
|
818
891
|
|
|
819
892
|
# Process links input
|
|
820
893
|
if links_input:
|
|
@@ -991,6 +1064,8 @@ def setup_captcha_routes(app):
|
|
|
991
1064
|
original_url = payload.get("original_url", "")
|
|
992
1065
|
url = payload.get("links")[0] if payload.get("links") else None
|
|
993
1066
|
|
|
1067
|
+
check_package_exists(package_id)
|
|
1068
|
+
|
|
994
1069
|
if not url or not session_id or not package_id:
|
|
995
1070
|
response.status = 400
|
|
996
1071
|
return "Missing required parameters"
|