quasarr 1.29.0__py3-none-any.whl → 1.31.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 +452 -230
- quasarr/downloads/__init__.py +7 -0
- quasarr/downloads/linkcrypters/filecrypt.py +2 -36
- quasarr/downloads/packages/__init__.py +52 -38
- quasarr/downloads/sources/dl.py +24 -122
- quasarr/downloads/sources/wx.py +96 -54
- quasarr/providers/jd_cache.py +131 -0
- quasarr/providers/obfuscated.py +6 -6
- quasarr/providers/utils.py +177 -0
- quasarr/providers/version.py +1 -1
- quasarr/search/sources/wx.py +40 -24
- {quasarr-1.29.0.dist-info → quasarr-1.31.0.dist-info}/METADATA +1 -3
- {quasarr-1.29.0.dist-info → quasarr-1.31.0.dist-info}/RECORD +17 -16
- {quasarr-1.29.0.dist-info → quasarr-1.31.0.dist-info}/WHEEL +0 -0
- {quasarr-1.29.0.dist-info → quasarr-1.31.0.dist-info}/entry_points.txt +0 -0
- {quasarr-1.29.0.dist-info → quasarr-1.31.0.dist-info}/licenses/LICENSE +0 -0
- {quasarr-1.29.0.dist-info → quasarr-1.31.0.dist-info}/top_level.txt +0 -0
quasarr/api/captcha/__init__.py
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import json
|
|
6
6
|
import re
|
|
7
7
|
from base64 import urlsafe_b64encode, urlsafe_b64decode
|
|
8
|
-
from urllib.parse import quote, unquote
|
|
8
|
+
from urllib.parse import quote, unquote
|
|
9
9
|
|
|
10
10
|
import requests
|
|
11
11
|
from bottle import request, response, redirect, HTTPResponse
|
|
@@ -72,25 +72,17 @@ def setup_captcha_routes(app):
|
|
|
72
72
|
except KeyError:
|
|
73
73
|
desired_mirror = None
|
|
74
74
|
|
|
75
|
-
# This is set for circle CAPTCHAs
|
|
76
|
-
filecrypt_session = data.get("session", None)
|
|
77
|
-
|
|
78
75
|
# This is required for cutcaptcha
|
|
79
76
|
rapid = [ln for ln in links if "rapidgator" in ln[1].lower()]
|
|
80
77
|
others = [ln for ln in links if "rapidgator" not in ln[1].lower()]
|
|
81
78
|
prioritized_links = rapid + others
|
|
82
79
|
|
|
83
|
-
# This is required for bypass on circlecaptcha
|
|
84
|
-
original_url = data.get("original_url", "")
|
|
85
|
-
|
|
86
80
|
payload = {
|
|
87
81
|
"package_id": package_id,
|
|
88
82
|
"title": title,
|
|
89
83
|
"password": password,
|
|
90
84
|
"mirror": desired_mirror,
|
|
91
|
-
"session": filecrypt_session,
|
|
92
85
|
"links": prioritized_links,
|
|
93
|
-
"original_url": original_url
|
|
94
86
|
}
|
|
95
87
|
|
|
96
88
|
encoded_payload = urlsafe_b64encode(json.dumps(payload).encode()).decode()
|
|
@@ -138,9 +130,6 @@ def setup_captcha_routes(app):
|
|
|
138
130
|
elif has_tolink_links:
|
|
139
131
|
debug("Redirecting to ToLink CAPTCHA")
|
|
140
132
|
redirect(f"/captcha/tolink?data={quote(encoded_payload)}")
|
|
141
|
-
elif filecrypt_session:
|
|
142
|
-
debug(f'Redirecting to circle CAPTCHA')
|
|
143
|
-
redirect(f"/captcha/circle?data={quote(encoded_payload)}")
|
|
144
133
|
else:
|
|
145
134
|
debug(f"Redirecting to cutcaptcha")
|
|
146
135
|
redirect(f"/captcha/cutcaptcha?data={quote(encoded_payload)}")
|
|
@@ -230,7 +219,7 @@ def setup_captcha_routes(app):
|
|
|
230
219
|
|
|
231
220
|
<!-- Primary action - the quick transfer link -->
|
|
232
221
|
<p>
|
|
233
|
-
{render_button(f"Open {provider_name} & Get Download Links", "primary", {"onclick": f"location.href='{url_with_quick_transfer_params}'"})}
|
|
222
|
+
{render_button(f"Open {provider_name} & Get Download Links", "primary", {"onclick": f"if(typeof incrementCaptchaAttempts==='function')incrementCaptchaAttempts();location.href='{url_with_quick_transfer_params}'"})}
|
|
234
223
|
</p>
|
|
235
224
|
|
|
236
225
|
<!-- Manual submission - collapsible -->
|
|
@@ -241,7 +230,7 @@ def setup_captcha_routes(app):
|
|
|
241
230
|
<p style="font-size: 0.9em;">
|
|
242
231
|
If the userscript doesn't work, you can manually paste the links below:
|
|
243
232
|
</p>
|
|
244
|
-
<form id="bypass-form" action="/captcha/bypass-submit" method="post" enctype="multipart/form-data">
|
|
233
|
+
<form id="bypass-form" action="/captcha/bypass-submit" method="post" enctype="multipart/form-data" onsubmit="if(typeof incrementCaptchaAttempts==='function')incrementCaptchaAttempts();">
|
|
245
234
|
<input type="hidden" name="package_id" value="{package_id}" />
|
|
246
235
|
<input type="hidden" name="title" value="{title}" />
|
|
247
236
|
<input type="hidden" name="password" value="{password}" />
|
|
@@ -319,12 +308,16 @@ def setup_captcha_routes(app):
|
|
|
319
308
|
|
|
320
309
|
check_package_exists(package_id)
|
|
321
310
|
|
|
311
|
+
package_selector = render_package_selector(package_id, title)
|
|
312
|
+
failed_warning = render_failed_attempts_warning(package_id)
|
|
313
|
+
|
|
322
314
|
return render_centered_html(f"""
|
|
323
315
|
<!DOCTYPE html>
|
|
324
316
|
<html>
|
|
325
317
|
<body>
|
|
326
318
|
<h1><img src="{images.logo}" type="image/png" alt="Quasarr logo" class="logo"/>Quasarr</h1>
|
|
327
|
-
|
|
319
|
+
{package_selector}
|
|
320
|
+
{failed_warning}
|
|
328
321
|
{render_userscript_section(url, package_id, title, password, "hide")}
|
|
329
322
|
<p>
|
|
330
323
|
{render_button("Delete Package", "secondary", {"onclick": f"location.href='/captcha/delete/{package_id}'"})}
|
|
@@ -355,12 +348,16 @@ def setup_captcha_routes(app):
|
|
|
355
348
|
|
|
356
349
|
check_package_exists(package_id)
|
|
357
350
|
|
|
351
|
+
package_selector = render_package_selector(package_id, title)
|
|
352
|
+
failed_warning = render_failed_attempts_warning(package_id)
|
|
353
|
+
|
|
358
354
|
return render_centered_html(f"""
|
|
359
355
|
<!DOCTYPE html>
|
|
360
356
|
<html>
|
|
361
357
|
<body>
|
|
362
358
|
<h1><img src="{images.logo}" type="image/png" alt="Quasarr logo" class="logo"/>Quasarr</h1>
|
|
363
|
-
|
|
359
|
+
{package_selector}
|
|
360
|
+
{failed_warning}
|
|
364
361
|
{render_userscript_section(url, package_id, title, password, "junkies")}
|
|
365
362
|
<p>
|
|
366
363
|
{render_button("Delete Package", "secondary", {"onclick": f"location.href='/captcha/delete/{package_id}'"})}
|
|
@@ -392,12 +389,16 @@ def setup_captcha_routes(app):
|
|
|
392
389
|
|
|
393
390
|
url = urls[0][0] if isinstance(urls[0], (list, tuple)) else urls[0]
|
|
394
391
|
|
|
392
|
+
package_selector = render_package_selector(package_id, title)
|
|
393
|
+
failed_warning = render_failed_attempts_warning(package_id)
|
|
394
|
+
|
|
395
395
|
return render_centered_html(f"""
|
|
396
396
|
<!DOCTYPE html>
|
|
397
397
|
<html>
|
|
398
398
|
<body>
|
|
399
399
|
<h1><img src="{images.logo}" type="image/png" alt="Quasarr logo" class="logo"/>Quasarr</h1>
|
|
400
|
-
|
|
400
|
+
{package_selector}
|
|
401
|
+
{failed_warning}
|
|
401
402
|
{render_userscript_section(url, package_id, title, password, "keeplinks")}
|
|
402
403
|
<p>
|
|
403
404
|
{render_button("Delete Package", "secondary", {"onclick": f"location.href='/captcha/delete/{package_id}'"})}
|
|
@@ -429,12 +430,16 @@ def setup_captcha_routes(app):
|
|
|
429
430
|
|
|
430
431
|
url = urls[0][0] if isinstance(urls[0], (list, tuple)) else urls[0]
|
|
431
432
|
|
|
433
|
+
package_selector = render_package_selector(package_id, title)
|
|
434
|
+
failed_warning = render_failed_attempts_warning(package_id)
|
|
435
|
+
|
|
432
436
|
return render_centered_html(f"""
|
|
433
437
|
<!DOCTYPE html>
|
|
434
438
|
<html>
|
|
435
439
|
<body>
|
|
436
440
|
<h1><img src="{images.logo}" type="image/png" alt="Quasarr logo" class="logo"/>Quasarr</h1>
|
|
437
|
-
|
|
441
|
+
{package_selector}
|
|
442
|
+
{failed_warning}
|
|
438
443
|
{render_userscript_section(url, package_id, title, password, "tolink")}
|
|
439
444
|
<p>
|
|
440
445
|
{render_button("Delete Package", "secondary", {"onclick": f"location.href='/captcha/delete/{package_id}'"})}
|
|
@@ -480,7 +485,7 @@ def setup_captcha_routes(app):
|
|
|
480
485
|
return content
|
|
481
486
|
|
|
482
487
|
def render_filecrypt_bypass_section(url, package_id, title, password):
|
|
483
|
-
"""Render the bypass UI section for
|
|
488
|
+
"""Render the bypass UI section for cutcaptcha captcha page"""
|
|
484
489
|
|
|
485
490
|
# Generate userscript URL with transfer params
|
|
486
491
|
# Get base URL of current request
|
|
@@ -539,7 +544,7 @@ def setup_captcha_routes(app):
|
|
|
539
544
|
|
|
540
545
|
<!-- Primary action button -->
|
|
541
546
|
<p>
|
|
542
|
-
{render_button("Open FileCrypt & Get Download Links", "primary", {"onclick": f"location.href='{url_with_quick_transfer_params}'"})}
|
|
547
|
+
{render_button("Open FileCrypt & Get Download Links", "primary", {"onclick": f"if(typeof incrementCaptchaAttempts==='function')incrementCaptchaAttempts();location.href='{url_with_quick_transfer_params}'"})}
|
|
543
548
|
</p>
|
|
544
549
|
|
|
545
550
|
<!-- Manual submission section -->
|
|
@@ -547,7 +552,7 @@ def setup_captcha_routes(app):
|
|
|
547
552
|
<p style="font-size: 0.9em; margin-bottom: 16px;">
|
|
548
553
|
If the userscript doesn't work, you can manually paste the links or upload a DLC file:
|
|
549
554
|
</p>
|
|
550
|
-
<form id="bypass-form" action="/captcha/bypass-submit" method="post" enctype="multipart/form-data">
|
|
555
|
+
<form id="bypass-form" action="/captcha/bypass-submit" method="post" enctype="multipart/form-data" onsubmit="if(typeof incrementCaptchaAttempts==='function')incrementCaptchaAttempts();">
|
|
551
556
|
<input type="hidden" name="package_id" value="{package_id}" />
|
|
552
557
|
<input type="hidden" name="title" value="{title}" />
|
|
553
558
|
<input type="hidden" name="password" value="{password}" />
|
|
@@ -610,6 +615,348 @@ def setup_captcha_routes(app):
|
|
|
610
615
|
</script>
|
|
611
616
|
'''
|
|
612
617
|
|
|
618
|
+
def render_package_selector(current_package_id, current_title=None):
|
|
619
|
+
"""Render package title, with dropdown selector if multiple packages available"""
|
|
620
|
+
protected = shared_state.get_db("protected").retrieve_all_titles()
|
|
621
|
+
|
|
622
|
+
if not protected:
|
|
623
|
+
return ""
|
|
624
|
+
|
|
625
|
+
# Single package - just show the title without dropdown
|
|
626
|
+
if len(protected) <= 1:
|
|
627
|
+
if current_title:
|
|
628
|
+
return f'''
|
|
629
|
+
<div class="package-selector" style="margin-bottom: 20px; padding: 12px; background: rgba(128, 128, 128, 0.1); border: 1px solid rgba(128, 128, 128, 0.3); border-radius: 8px;">
|
|
630
|
+
<p style="margin: 0; word-break: break-all;"><b>📦 Package:</b> {current_title}</p>
|
|
631
|
+
</div>
|
|
632
|
+
'''
|
|
633
|
+
return ""
|
|
634
|
+
|
|
635
|
+
sj = shared_state.values["config"]("Hostnames").get("sj")
|
|
636
|
+
dj = shared_state.values["config"]("Hostnames").get("dj")
|
|
637
|
+
|
|
638
|
+
def is_junkies_link(link):
|
|
639
|
+
url = link[0] if isinstance(link, (list, tuple)) else link
|
|
640
|
+
mirror = link[1] if isinstance(link, (list, tuple)) and len(link) > 1 else ""
|
|
641
|
+
if mirror == "junkies":
|
|
642
|
+
return True
|
|
643
|
+
return (sj and sj in url) or (dj and dj in url)
|
|
644
|
+
|
|
645
|
+
def get_captcha_type_for_links(links):
|
|
646
|
+
"""Determine which captcha type to use based on links"""
|
|
647
|
+
has_hide = any(("hide." in (l[0] if isinstance(l, (list, tuple)) else l)) for l in links)
|
|
648
|
+
has_junkies = any(is_junkies_link(l) for l in links)
|
|
649
|
+
has_keeplinks = any(("keeplinks." in (l[0] if isinstance(l, (list, tuple)) else l)) for l in links)
|
|
650
|
+
has_tolink = any(("tolink." in (l[0] if isinstance(l, (list, tuple)) else l)) for l in links)
|
|
651
|
+
|
|
652
|
+
if has_hide:
|
|
653
|
+
return "hide"
|
|
654
|
+
elif has_junkies:
|
|
655
|
+
return "junkies"
|
|
656
|
+
elif has_keeplinks:
|
|
657
|
+
return "keeplinks"
|
|
658
|
+
elif has_tolink:
|
|
659
|
+
return "tolink"
|
|
660
|
+
else:
|
|
661
|
+
return "cutcaptcha"
|
|
662
|
+
|
|
663
|
+
options = []
|
|
664
|
+
for package in protected:
|
|
665
|
+
pkg_id = package[0]
|
|
666
|
+
data = json.loads(package[1])
|
|
667
|
+
title = data.get("title", "Unknown")
|
|
668
|
+
links = data.get("links", [])
|
|
669
|
+
password = data.get("password", "")
|
|
670
|
+
mirror = data.get("mirror")
|
|
671
|
+
|
|
672
|
+
# Prioritize rapidgator links for cutcaptcha
|
|
673
|
+
rapid = [ln for ln in links if "rapidgator" in ln[1].lower()]
|
|
674
|
+
others = [ln for ln in links if "rapidgator" not in ln[1].lower()]
|
|
675
|
+
prioritized = rapid + others
|
|
676
|
+
|
|
677
|
+
payload = {
|
|
678
|
+
"package_id": pkg_id,
|
|
679
|
+
"title": title,
|
|
680
|
+
"password": password,
|
|
681
|
+
"mirror": mirror,
|
|
682
|
+
"links": prioritized,
|
|
683
|
+
}
|
|
684
|
+
encoded = urlsafe_b64encode(json.dumps(payload).encode()).decode()
|
|
685
|
+
captcha_type = get_captcha_type_for_links(prioritized)
|
|
686
|
+
|
|
687
|
+
selected = "selected" if pkg_id == current_package_id else ""
|
|
688
|
+
# Truncate long titles for display
|
|
689
|
+
display_title = (title[:50] + "...") if len(title) > 53 else title
|
|
690
|
+
options.append(f'<option value="{captcha_type}|{quote(encoded)}" {selected}>{display_title}</option>')
|
|
691
|
+
|
|
692
|
+
options_html = "\n".join(options)
|
|
693
|
+
|
|
694
|
+
return f'''
|
|
695
|
+
<div class="package-selector" style="margin-bottom: 20px; padding: 12px; background: rgba(128, 128, 128, 0.1); border: 1px solid rgba(128, 128, 128, 0.3); border-radius: 8px;">
|
|
696
|
+
<label for="package-select" style="display: block; margin-bottom: 8px; font-weight: bold;">📦 Select Package:</label>
|
|
697
|
+
<select id="package-select" style="width: 100%; padding: 8px; border-radius: 4px; background: inherit; color: inherit; border: 1px solid rgba(128, 128, 128, 0.5); cursor: pointer;">
|
|
698
|
+
{options_html}
|
|
699
|
+
</select>
|
|
700
|
+
</div>
|
|
701
|
+
<script>
|
|
702
|
+
document.getElementById('package-select').addEventListener('change', function() {{
|
|
703
|
+
const [captchaType, encodedData] = this.value.split('|');
|
|
704
|
+
window.location.href = '/captcha/' + captchaType + '?data=' + encodedData;
|
|
705
|
+
}});
|
|
706
|
+
</script>
|
|
707
|
+
'''
|
|
708
|
+
|
|
709
|
+
def render_failed_attempts_warning(package_id, include_delete_button=True, fallback_url=None):
|
|
710
|
+
"""Render a warning block that shows after 2+ failed attempts per package_id.
|
|
711
|
+
Uses localStorage to track attempts by package_id to ensure reliable tracking
|
|
712
|
+
even when package titles are duplicated.
|
|
713
|
+
|
|
714
|
+
Attempts are NOT incremented on page load - they must be incremented by
|
|
715
|
+
calling window.incrementCaptchaAttempts() when user takes an action (e.g.,
|
|
716
|
+
clicking submit, opening bypass link).
|
|
717
|
+
|
|
718
|
+
Args:
|
|
719
|
+
package_id: The unique package identifier
|
|
720
|
+
include_delete_button: Whether to show delete button in warning
|
|
721
|
+
fallback_url: Optional URL to a fallback page (e.g., FileCrypt manual fallback)
|
|
722
|
+
"""
|
|
723
|
+
|
|
724
|
+
delete_button = ""
|
|
725
|
+
if include_delete_button:
|
|
726
|
+
delete_button = render_button("Delete Package", "primary",
|
|
727
|
+
{"onclick": f"location.href='/captcha/delete/{package_id}'"})
|
|
728
|
+
|
|
729
|
+
fallback_link = ""
|
|
730
|
+
if fallback_url:
|
|
731
|
+
fallback_link = f'''
|
|
732
|
+
<p style="margin-top: 12px; margin-bottom: 8px;">
|
|
733
|
+
<a href="{fallback_url}" style="color: #cc0000;">Try the manual FileCrypt fallback page →</a>
|
|
734
|
+
</p>
|
|
735
|
+
'''
|
|
736
|
+
|
|
737
|
+
return f'''
|
|
738
|
+
<div id="failed-attempts-warning" class="warning-box" style="display: none; background: #fee2e2; border: 2px solid #dc2626; border-radius: 8px; padding: 16px; margin-bottom: 20px; text-align: center; color: #991b1b;">
|
|
739
|
+
<h3 style="color: #dc2626; margin-top: 0;">⚠️ Multiple Failed Attempts Detected</h3>
|
|
740
|
+
<p style="margin-bottom: 12px; color: #7f1d1d;">This CAPTCHA has failed multiple times. The link may be <b>offline</b> or require a different solution method.</p>
|
|
741
|
+
<p style="margin-bottom: 8px; color: #7f1d1d;">Please verify the link is still valid, or delete this package if it's no longer available.</p>
|
|
742
|
+
{fallback_link}
|
|
743
|
+
<div id="warning-delete-button" style="margin-top: 12px;">
|
|
744
|
+
{delete_button}
|
|
745
|
+
</div>
|
|
746
|
+
</div>
|
|
747
|
+
<script>
|
|
748
|
+
(function() {{
|
|
749
|
+
const packageId = '{package_id}';
|
|
750
|
+
const storageKey = 'captcha_attempts_' + packageId;
|
|
751
|
+
|
|
752
|
+
// Get current attempt count (do NOT increment on page load)
|
|
753
|
+
let attempts = parseInt(localStorage.getItem(storageKey) || '0', 10);
|
|
754
|
+
|
|
755
|
+
// Show warning if 2+ failed attempts
|
|
756
|
+
if (attempts >= 2) {{
|
|
757
|
+
const warningBox = document.getElementById('failed-attempts-warning');
|
|
758
|
+
if (warningBox) {{
|
|
759
|
+
warningBox.style.display = 'block';
|
|
760
|
+
}}
|
|
761
|
+
}}
|
|
762
|
+
|
|
763
|
+
// Function to increment attempts (call this on submit/action)
|
|
764
|
+
window.incrementCaptchaAttempts = function() {{
|
|
765
|
+
let current = parseInt(localStorage.getItem(storageKey) || '0', 10);
|
|
766
|
+
current++;
|
|
767
|
+
localStorage.setItem(storageKey, current.toString());
|
|
768
|
+
// Show warning immediately if we hit 2+ attempts
|
|
769
|
+
if (current >= 2) {{
|
|
770
|
+
const warningBox = document.getElementById('failed-attempts-warning');
|
|
771
|
+
if (warningBox) {{
|
|
772
|
+
warningBox.style.display = 'block';
|
|
773
|
+
}}
|
|
774
|
+
}}
|
|
775
|
+
return current;
|
|
776
|
+
}};
|
|
777
|
+
|
|
778
|
+
// Function to get current attempt count
|
|
779
|
+
window.getCaptchaAttempts = function() {{
|
|
780
|
+
return parseInt(localStorage.getItem(storageKey) || '0', 10);
|
|
781
|
+
}};
|
|
782
|
+
|
|
783
|
+
// Function to clear attempts (call on success)
|
|
784
|
+
window.clearCaptchaAttempts = function() {{
|
|
785
|
+
localStorage.removeItem(storageKey);
|
|
786
|
+
}};
|
|
787
|
+
}})();
|
|
788
|
+
</script>
|
|
789
|
+
'''
|
|
790
|
+
|
|
791
|
+
@app.get("/captcha/filecrypt")
|
|
792
|
+
def serve_filecrypt_fallback():
|
|
793
|
+
"""Dedicated FileCrypt fallback page - similar to hide/junkies/keeplinks/tolink"""
|
|
794
|
+
payload = decode_payload()
|
|
795
|
+
|
|
796
|
+
if "error" in payload:
|
|
797
|
+
return render_centered_html(f'''<h1><img src="{images.logo}" type="image/png" alt="Quasarr logo" class="logo"/>Quasarr</h1>
|
|
798
|
+
<p>{payload["error"]}</p>
|
|
799
|
+
<p>
|
|
800
|
+
{render_button("Back", "secondary", {"onclick": "location.href='/'"})}
|
|
801
|
+
</p>''')
|
|
802
|
+
|
|
803
|
+
package_id = payload.get("package_id")
|
|
804
|
+
title = payload.get("title")
|
|
805
|
+
password = payload.get("password")
|
|
806
|
+
urls = payload.get("links")
|
|
807
|
+
|
|
808
|
+
check_package_exists(package_id)
|
|
809
|
+
|
|
810
|
+
url = urls[0][0] if isinstance(urls[0], (list, tuple)) else urls[0]
|
|
811
|
+
|
|
812
|
+
# Generate userscript URL with transfer params
|
|
813
|
+
base_url = request.urlparts.scheme + '://' + request.urlparts.netloc
|
|
814
|
+
transfer_url = f"{base_url}/captcha/quick-transfer"
|
|
815
|
+
|
|
816
|
+
url_with_quick_transfer_params = (
|
|
817
|
+
f"{url}?"
|
|
818
|
+
f"transfer_url={quote(transfer_url)}&"
|
|
819
|
+
f"pkg_id={quote(package_id)}&"
|
|
820
|
+
f"pkg_title={quote(title)}&"
|
|
821
|
+
f"pkg_pass={quote(password)}"
|
|
822
|
+
)
|
|
823
|
+
|
|
824
|
+
package_selector = render_package_selector(package_id, title)
|
|
825
|
+
failed_warning = render_failed_attempts_warning(package_id)
|
|
826
|
+
|
|
827
|
+
return render_centered_html(f"""
|
|
828
|
+
<!DOCTYPE html>
|
|
829
|
+
<html>
|
|
830
|
+
<body>
|
|
831
|
+
<h1><img src="{images.logo}" type="image/png" alt="Quasarr logo" class="logo"/>Quasarr</h1>
|
|
832
|
+
{package_selector}
|
|
833
|
+
{failed_warning}
|
|
834
|
+
|
|
835
|
+
<div>
|
|
836
|
+
<!-- Info section explaining the process -->
|
|
837
|
+
<div class="info-box">
|
|
838
|
+
<h3>ℹ️ How This Works:</h3>
|
|
839
|
+
<p style="margin-bottom: 8px;">
|
|
840
|
+
1. Click the button below to open FileCrypt directly
|
|
841
|
+
</p>
|
|
842
|
+
<p style="margin-top: 0; margin-bottom: 8px;">
|
|
843
|
+
2. Solve any CAPTCHAs on their site to reveal the download links
|
|
844
|
+
</p>
|
|
845
|
+
<p style="margin-top: 0; margin-bottom: 0;">
|
|
846
|
+
3. <b>With the userscript installed</b>, links are automatically sent back to Quasarr!
|
|
847
|
+
</p>
|
|
848
|
+
</div>
|
|
849
|
+
|
|
850
|
+
<!-- One-time setup section - visually separated -->
|
|
851
|
+
<div id="setup-instructions" class="setup-box">
|
|
852
|
+
<h3>📦 First Time Setup:</h3>
|
|
853
|
+
<p style="margin-bottom: 8px;">
|
|
854
|
+
<a href="https://www.tampermonkey.net/" target="_blank" rel="noopener noreferrer">1. Install Tampermonkey</a>
|
|
855
|
+
</p>
|
|
856
|
+
<p style="margin-top: 0; margin-bottom: 12px;">
|
|
857
|
+
<a href="/captcha/filecrypt.user.js" target="_blank">2. Install the FileCrypt userscript</a>
|
|
858
|
+
</p>
|
|
859
|
+
<p style="margin-top: 0;">
|
|
860
|
+
<button id="hide-setup-btn" type="button" class="btn-subtle">
|
|
861
|
+
✅ Don't show this again
|
|
862
|
+
</button>
|
|
863
|
+
</p>
|
|
864
|
+
</div>
|
|
865
|
+
|
|
866
|
+
<!-- Hidden "show instructions" button -->
|
|
867
|
+
<div id="show-instructions-link" style="display: none; margin-bottom: 16px;">
|
|
868
|
+
<button id="show-setup-btn" type="button" class="btn-subtle">
|
|
869
|
+
ℹ️ Show setup instructions
|
|
870
|
+
</button>
|
|
871
|
+
</div>
|
|
872
|
+
|
|
873
|
+
<!-- Primary action button -->
|
|
874
|
+
<p>
|
|
875
|
+
{render_button("Open FileCrypt & Get Download Links", "primary", {"onclick": f"if(typeof incrementCaptchaAttempts==='function')incrementCaptchaAttempts();location.href='{url_with_quick_transfer_params}'"})}
|
|
876
|
+
</p>
|
|
877
|
+
|
|
878
|
+
<!-- Manual submission section -->
|
|
879
|
+
<div class="section-divider">
|
|
880
|
+
<details id="manualSubmitDetails">
|
|
881
|
+
<summary id="manualSubmitSummary" style="cursor: pointer;">Show Manual Submission</summary>
|
|
882
|
+
<div style="margin-top: 16px;">
|
|
883
|
+
<p style="font-size: 0.9em; margin-bottom: 16px;">
|
|
884
|
+
If the userscript doesn't work, you can manually paste the links or upload a DLC file:
|
|
885
|
+
</p>
|
|
886
|
+
<form id="bypass-form" action="/captcha/bypass-submit" method="post" enctype="multipart/form-data" onsubmit="if(typeof incrementCaptchaAttempts==='function')incrementCaptchaAttempts();">
|
|
887
|
+
<input type="hidden" name="package_id" value="{package_id}" />
|
|
888
|
+
<input type="hidden" name="title" value="{title}" />
|
|
889
|
+
<input type="hidden" name="password" value="{password}" />
|
|
890
|
+
|
|
891
|
+
<div>
|
|
892
|
+
<strong>Paste the download links (one per line):</strong>
|
|
893
|
+
<textarea id="links-input" name="links" rows="5" style="width: 100%; padding: 8px; font-family: monospace; resize: vertical;"></textarea>
|
|
894
|
+
</div>
|
|
895
|
+
|
|
896
|
+
<div>
|
|
897
|
+
<strong>Or upload DLC file:</strong><br>
|
|
898
|
+
<input type="file" id="dlc-file" name="dlc_file" accept=".dlc" />
|
|
899
|
+
</div>
|
|
900
|
+
|
|
901
|
+
<div>
|
|
902
|
+
{render_button("Submit", "primary", {"type": "submit"})}
|
|
903
|
+
</div>
|
|
904
|
+
</form>
|
|
905
|
+
</div>
|
|
906
|
+
</details>
|
|
907
|
+
</div>
|
|
908
|
+
</div>
|
|
909
|
+
|
|
910
|
+
<p>
|
|
911
|
+
{render_button("Delete Package", "secondary", {"onclick": f"location.href='/captcha/delete/{package_id}'"})}
|
|
912
|
+
</p>
|
|
913
|
+
<p>
|
|
914
|
+
{render_button("Back", "secondary", {"onclick": "location.href='/'"})}
|
|
915
|
+
</p>
|
|
916
|
+
|
|
917
|
+
<script>
|
|
918
|
+
// Handle manual submission toggle text
|
|
919
|
+
const manualDetails = document.getElementById('manualSubmitDetails');
|
|
920
|
+
const manualSummary = document.getElementById('manualSubmitSummary');
|
|
921
|
+
|
|
922
|
+
if (manualDetails && manualSummary) {{
|
|
923
|
+
manualDetails.addEventListener('toggle', () => {{
|
|
924
|
+
if (manualDetails.open) {{
|
|
925
|
+
manualSummary.textContent = 'Hide Manual Submission';
|
|
926
|
+
}} else {{
|
|
927
|
+
manualSummary.textContent = 'Show Manual Submission';
|
|
928
|
+
}}
|
|
929
|
+
}});
|
|
930
|
+
}}
|
|
931
|
+
|
|
932
|
+
// Handle setup instructions hide/show
|
|
933
|
+
const hideSetup = localStorage.getItem('hideFileCryptFallbackSetupInstructions');
|
|
934
|
+
const setupBox = document.getElementById('setup-instructions');
|
|
935
|
+
const showLink = document.getElementById('show-instructions-link');
|
|
936
|
+
|
|
937
|
+
if (hideSetup === 'true') {{
|
|
938
|
+
setupBox.style.display = 'none';
|
|
939
|
+
showLink.style.display = 'block';
|
|
940
|
+
}}
|
|
941
|
+
|
|
942
|
+
// Hide setup instructions
|
|
943
|
+
document.getElementById('hide-setup-btn').addEventListener('click', function() {{
|
|
944
|
+
localStorage.setItem('hideFileCryptFallbackSetupInstructions', 'true');
|
|
945
|
+
setupBox.style.display = 'none';
|
|
946
|
+
showLink.style.display = 'block';
|
|
947
|
+
}});
|
|
948
|
+
|
|
949
|
+
// Show setup instructions again
|
|
950
|
+
document.getElementById('show-setup-btn').addEventListener('click', function() {{
|
|
951
|
+
localStorage.setItem('hideFileCryptFallbackSetupInstructions', 'false');
|
|
952
|
+
setupBox.style.display = 'block';
|
|
953
|
+
showLink.style.display = 'none';
|
|
954
|
+
}});
|
|
955
|
+
</script>
|
|
956
|
+
|
|
957
|
+
</body>
|
|
958
|
+
</html>""")
|
|
959
|
+
|
|
613
960
|
@app.get('/captcha/quick-transfer')
|
|
614
961
|
def handle_quick_transfer():
|
|
615
962
|
"""Handle quick transfer from userscript"""
|
|
@@ -711,7 +1058,8 @@ def setup_captcha_routes(app):
|
|
|
711
1058
|
</p>
|
|
712
1059
|
<p>
|
|
713
1060
|
{render_button("Back", "secondary", {"onclick": "location.href='/'"})}
|
|
714
|
-
</p>
|
|
1061
|
+
</p>
|
|
1062
|
+
<script>localStorage.removeItem('captcha_attempts_{package_id}');</script>''')
|
|
715
1063
|
else:
|
|
716
1064
|
StatsHelper(shared_state).increment_failed_decryptions_manual()
|
|
717
1065
|
return render_centered_html(f'''<h1><img src="{images.logo}" type="image/png" alt="Quasarr logo" class="logo"/>Quasarr</h1>
|
|
@@ -751,7 +1099,8 @@ def setup_captcha_routes(app):
|
|
|
751
1099
|
</p>
|
|
752
1100
|
<p>
|
|
753
1101
|
{render_button("Back", "secondary", {"onclick": "location.href='/'"})}
|
|
754
|
-
</p>
|
|
1102
|
+
</p>
|
|
1103
|
+
<script>localStorage.removeItem('captcha_attempts_{package_id}');</script>''')
|
|
755
1104
|
else:
|
|
756
1105
|
return render_centered_html(f'''<h1><img src="{images.logo}" type="image/png" alt="Quasarr logo" class="logo"/>Quasarr</h1>
|
|
757
1106
|
<p>Failed to delete package!</p>
|
|
@@ -826,8 +1175,56 @@ def setup_captcha_routes(app):
|
|
|
826
1175
|
# Add bypass section
|
|
827
1176
|
bypass_section = render_filecrypt_bypass_section(url, package_id, title, password)
|
|
828
1177
|
|
|
1178
|
+
# Add package selector and failed attempts warning
|
|
1179
|
+
package_selector = render_package_selector(package_id, title)
|
|
1180
|
+
|
|
1181
|
+
# Create fallback URL for the manual FileCrypt page
|
|
1182
|
+
fallback_payload = {
|
|
1183
|
+
"package_id": package_id,
|
|
1184
|
+
"title": title,
|
|
1185
|
+
"password": password,
|
|
1186
|
+
"mirror": desired_mirror,
|
|
1187
|
+
"links": prioritized_links,
|
|
1188
|
+
}
|
|
1189
|
+
fallback_encoded = urlsafe_b64encode(json.dumps(fallback_payload).encode()).decode()
|
|
1190
|
+
filecrypt_fallback_url = f"/captcha/filecrypt?data={quote(fallback_encoded)}"
|
|
1191
|
+
|
|
1192
|
+
failed_warning = render_failed_attempts_warning(package_id, include_delete_button=False,
|
|
1193
|
+
fallback_url=filecrypt_fallback_url) # Delete button is already below
|
|
1194
|
+
|
|
1195
|
+
# Escape title for safe use in JavaScript string
|
|
1196
|
+
escaped_title_js = title.replace('\\', '\\\\').replace('"', '\\"').replace('\n', '\\n').replace('\r', '\\r')
|
|
1197
|
+
|
|
829
1198
|
content = render_centered_html(r'''
|
|
1199
|
+
<style>
|
|
1200
|
+
@media (max-width: 600px) {
|
|
1201
|
+
.package-selector,
|
|
1202
|
+
#failed-attempts-warning {
|
|
1203
|
+
margin-left: 0 !important;
|
|
1204
|
+
margin-right: 0 !important;
|
|
1205
|
+
padding-left: 8px !important;
|
|
1206
|
+
padding-right: 8px !important;
|
|
1207
|
+
border-radius: 0 !important;
|
|
1208
|
+
border-left: none !important;
|
|
1209
|
+
border-right: none !important;
|
|
1210
|
+
}
|
|
1211
|
+
}
|
|
1212
|
+
</style>
|
|
830
1213
|
<script type="text/javascript">
|
|
1214
|
+
// Package title for result display
|
|
1215
|
+
var packageTitleText = "''' + escaped_title_js + r'''";
|
|
1216
|
+
|
|
1217
|
+
// Check if we should redirect to fallback due to failed attempts
|
|
1218
|
+
(function() {
|
|
1219
|
+
const storageKey = 'captcha_attempts_''' + package_id + r'''';
|
|
1220
|
+
const attempts = parseInt(localStorage.getItem(storageKey) || '0', 10);
|
|
1221
|
+
if (attempts >= 2) {
|
|
1222
|
+
// Redirect to FileCrypt fallback page
|
|
1223
|
+
window.location.href = '''' + filecrypt_fallback_url + r'''';
|
|
1224
|
+
return;
|
|
1225
|
+
}
|
|
1226
|
+
})();
|
|
1227
|
+
|
|
831
1228
|
var api_key = "''' + obfuscated.captcha_values()["api_key"] + r'''";
|
|
832
1229
|
var endpoint = '/' + window.location.pathname.split('/')[1] + '/' + api_key + '.html';
|
|
833
1230
|
var solveAnotherHtml = `<p>''' + solve_another_html + r'''</p><p>''' + back_button_html + r'''</p>`;
|
|
@@ -839,12 +1236,14 @@ def setup_captcha_routes(app):
|
|
|
839
1236
|
document.getElementById("delete-package-section").style.display = "none";
|
|
840
1237
|
document.getElementById("back-button-section").style.display = "none";
|
|
841
1238
|
document.getElementById("bypass-section").style.display = "none";
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
1239
|
+
// Hide package selector and warning on token submission
|
|
1240
|
+
var pkgSelector = document.getElementById("package-selector-section");
|
|
1241
|
+
if (pkgSelector) pkgSelector.style.display = "none";
|
|
1242
|
+
var warnBox = document.getElementById("failed-attempts-warning");
|
|
1243
|
+
if (warnBox) warnBox.style.display = "none";
|
|
1244
|
+
|
|
1245
|
+
// Add package title to result area
|
|
1246
|
+
document.getElementById("captcha-key").innerHTML = '<p style="word-break: break-all;"><b>Package:</b> ' + packageTitleText + '</p><p style="word-break: break-all;">Using result "' + token + '" to decrypt links...</p>';
|
|
848
1247
|
var link = document.getElementById("link-hidden").value;
|
|
849
1248
|
const fullPath = '/captcha/decrypt-filecrypt';
|
|
850
1249
|
|
|
@@ -867,9 +1266,17 @@ def setup_captcha_routes(app):
|
|
|
867
1266
|
if (data.success) {
|
|
868
1267
|
document.getElementById("captcha-key").insertAdjacentHTML('afterend',
|
|
869
1268
|
'<p>✅ Successful!</p>');
|
|
1269
|
+
// Clear failed attempts on success
|
|
1270
|
+
if (typeof clearCaptchaAttempts === 'function') {
|
|
1271
|
+
clearCaptchaAttempts();
|
|
1272
|
+
}
|
|
870
1273
|
} else {
|
|
871
1274
|
document.getElementById("captcha-key").insertAdjacentHTML('afterend',
|
|
872
1275
|
'<p>Failed. Check console for details!</p>');
|
|
1276
|
+
// Increment failed attempts on failure
|
|
1277
|
+
if (typeof incrementCaptchaAttempts === 'function') {
|
|
1278
|
+
incrementCaptchaAttempts();
|
|
1279
|
+
}
|
|
873
1280
|
}
|
|
874
1281
|
|
|
875
1282
|
// Show appropriate button based on whether more CAPTCHAs exist
|
|
@@ -885,7 +1292,10 @@ def setup_captcha_routes(app):
|
|
|
885
1292
|
''' + obfuscated.cutcaptcha_custom_js() + f'''</script>
|
|
886
1293
|
<div>
|
|
887
1294
|
<h1><img src="{images.logo}" type="image/png" alt="Quasarr logo" class="logo"/>Quasarr</h1>
|
|
888
|
-
<
|
|
1295
|
+
<div id="package-selector-section">
|
|
1296
|
+
{package_selector}
|
|
1297
|
+
</div>
|
|
1298
|
+
{failed_warning}
|
|
889
1299
|
<div id="captcha-key"></div>
|
|
890
1300
|
{link_select}<br><br>
|
|
891
1301
|
<input type="hidden" id="link-hidden" value="{prioritized_links[0][0]}" />
|
|
@@ -1068,7 +1478,8 @@ def setup_captcha_routes(app):
|
|
|
1068
1478
|
</p>
|
|
1069
1479
|
<p>
|
|
1070
1480
|
{render_button("Back", "secondary", {"onclick": "location.href='/'"})}
|
|
1071
|
-
</p>
|
|
1481
|
+
</p>
|
|
1482
|
+
<script>localStorage.removeItem('captcha_attempts_{package_id}');</script>''')
|
|
1072
1483
|
else:
|
|
1073
1484
|
StatsHelper(shared_state).increment_failed_decryptions_manual()
|
|
1074
1485
|
return render_centered_html(f'''<h1><img src="{images.logo}" type="image/png" alt="Quasarr logo" class="logo"/>Quasarr</h1>
|
|
@@ -1113,38 +1524,17 @@ def setup_captcha_routes(app):
|
|
|
1113
1524
|
info(f"Decrypting links for {title}")
|
|
1114
1525
|
decrypted = get_filecrypt_links(shared_state, token, title, link, password=password, mirror=mirror)
|
|
1115
1526
|
if decrypted:
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
{
|
|
1125
|
-
"title": title,
|
|
1126
|
-
"links": [replace_url, mirror],
|
|
1127
|
-
"size_mb": 0,
|
|
1128
|
-
"password": password,
|
|
1129
|
-
"mirror": mirror,
|
|
1130
|
-
"session": session,
|
|
1131
|
-
"original_url": link
|
|
1132
|
-
})
|
|
1133
|
-
shared_state.get_db("protected").update_store(package_id, blob)
|
|
1134
|
-
info(f"Another CAPTCHA solution is required for {mirror} link: {replace_url}")
|
|
1135
|
-
|
|
1527
|
+
links = decrypted.get("links", [])
|
|
1528
|
+
info(f"Decrypted {len(links)} download links for {title}")
|
|
1529
|
+
if not links:
|
|
1530
|
+
raise ValueError("No download links found after decryption")
|
|
1531
|
+
downloaded = shared_state.download_package(links, title, password, package_id)
|
|
1532
|
+
if downloaded:
|
|
1533
|
+
StatsHelper(shared_state).increment_package_with_links(links)
|
|
1534
|
+
shared_state.get_db("protected").delete(package_id)
|
|
1136
1535
|
else:
|
|
1137
|
-
links =
|
|
1138
|
-
|
|
1139
|
-
if not links:
|
|
1140
|
-
raise ValueError("No download links found after decryption")
|
|
1141
|
-
downloaded = shared_state.download_package(links, title, password, package_id)
|
|
1142
|
-
if downloaded:
|
|
1143
|
-
StatsHelper(shared_state).increment_package_with_links(links)
|
|
1144
|
-
shared_state.get_db("protected").delete(package_id)
|
|
1145
|
-
else:
|
|
1146
|
-
links = []
|
|
1147
|
-
raise RuntimeError("Submitting Download to JDownloader failed")
|
|
1536
|
+
links = []
|
|
1537
|
+
raise RuntimeError("Submitting Download to JDownloader failed")
|
|
1148
1538
|
else:
|
|
1149
1539
|
raise ValueError("No download links found")
|
|
1150
1540
|
|
|
@@ -1162,171 +1552,3 @@ def setup_captcha_routes(app):
|
|
|
1162
1552
|
has_more_captchas = bool(remaining_protected)
|
|
1163
1553
|
|
|
1164
1554
|
return {"success": success, "title": title, "has_more_captchas": has_more_captchas}
|
|
1165
|
-
|
|
1166
|
-
# The following routes are for circle CAPTCHA
|
|
1167
|
-
@app.get('/captcha/circle')
|
|
1168
|
-
def serve_circle():
|
|
1169
|
-
payload = decode_payload()
|
|
1170
|
-
|
|
1171
|
-
if "error" in payload:
|
|
1172
|
-
return render_centered_html(f'''<h1><img src="{images.logo}" type="image/png" alt="Quasarr logo" class="logo"/>Quasarr</h1>
|
|
1173
|
-
<p>{payload["error"]}</p>
|
|
1174
|
-
<p>
|
|
1175
|
-
{render_button("Back", "secondary", {"onclick": "location.href='/'"})}
|
|
1176
|
-
</p>''')
|
|
1177
|
-
|
|
1178
|
-
package_id = payload.get("package_id")
|
|
1179
|
-
session_id = payload.get("session")
|
|
1180
|
-
title = payload.get("title", "Unknown Package")
|
|
1181
|
-
password = payload.get("password", "")
|
|
1182
|
-
original_url = payload.get("original_url", "")
|
|
1183
|
-
url = payload.get("links")[0] if payload.get("links") else None
|
|
1184
|
-
|
|
1185
|
-
check_package_exists(package_id)
|
|
1186
|
-
|
|
1187
|
-
if not url or not session_id or not package_id:
|
|
1188
|
-
response.status = 400
|
|
1189
|
-
return "Missing required parameters"
|
|
1190
|
-
|
|
1191
|
-
# Add bypass section
|
|
1192
|
-
bypass_section = render_filecrypt_bypass_section(original_url, package_id, title, password)
|
|
1193
|
-
|
|
1194
|
-
return render_centered_html(f"""
|
|
1195
|
-
<!DOCTYPE html>
|
|
1196
|
-
<html>
|
|
1197
|
-
<body>
|
|
1198
|
-
<h1><img src="{images.logo}" type="image/png" alt="Quasarr logo" class="logo"/>Quasarr</h1>
|
|
1199
|
-
<p><b>Package:</b> {title}</p>
|
|
1200
|
-
<form action="/captcha/decrypt-filecrypt-circle?url={url}&session_id={session_id}&package_id={package_id}" method="post">
|
|
1201
|
-
<input type="image" src="/captcha/circle.php?url={url}&session_id={session_id}" name="button" alt="Circle CAPTCHA">
|
|
1202
|
-
</form>
|
|
1203
|
-
<p>
|
|
1204
|
-
{render_button("Delete Package", "secondary", {"onclick": f"location.href='/captcha/delete/{package_id}'"})}
|
|
1205
|
-
</p>
|
|
1206
|
-
<p>
|
|
1207
|
-
{render_button("Back", "secondary", {"onclick": "location.href='/'"})}
|
|
1208
|
-
</p>
|
|
1209
|
-
{bypass_section}
|
|
1210
|
-
</body>
|
|
1211
|
-
</html>""")
|
|
1212
|
-
|
|
1213
|
-
@app.get('/captcha/circle.php')
|
|
1214
|
-
def proxy_circle_php():
|
|
1215
|
-
target_url = "https://filecrypt.cc/captcha/circle.php"
|
|
1216
|
-
|
|
1217
|
-
url = request.query.get('url')
|
|
1218
|
-
session_id = request.query.get('session_id')
|
|
1219
|
-
if not url or not session_id:
|
|
1220
|
-
response.status = 400
|
|
1221
|
-
return "Missing required parameters"
|
|
1222
|
-
|
|
1223
|
-
headers = {'User-Agent': shared_state.values["user_agent"]}
|
|
1224
|
-
cookies = {'PHPSESSID': session_id}
|
|
1225
|
-
resp = requests.get(target_url, headers=headers, cookies=cookies, verify=False)
|
|
1226
|
-
|
|
1227
|
-
response.content_type = resp.headers.get('Content-Type', 'application/octet-stream')
|
|
1228
|
-
return resp.content
|
|
1229
|
-
|
|
1230
|
-
@app.post('/captcha/decrypt-filecrypt-circle')
|
|
1231
|
-
def proxy_form_submit():
|
|
1232
|
-
url = request.query.get('url')
|
|
1233
|
-
session_id = request.query.get('session_id')
|
|
1234
|
-
package_id = request.query.get('package_id')
|
|
1235
|
-
success = False
|
|
1236
|
-
|
|
1237
|
-
if not url or not session_id or not package_id:
|
|
1238
|
-
response.status = 400
|
|
1239
|
-
return "Missing required parameters"
|
|
1240
|
-
|
|
1241
|
-
cookies = {'PHPSESSID': session_id}
|
|
1242
|
-
|
|
1243
|
-
headers = {
|
|
1244
|
-
'User-Agent': shared_state.values["user_agent"],
|
|
1245
|
-
"Content-Type": "application/x-www-form-urlencoded"
|
|
1246
|
-
}
|
|
1247
|
-
|
|
1248
|
-
raw_body = request.body.read()
|
|
1249
|
-
|
|
1250
|
-
resp = requests.post(url, cookies=cookies, headers=headers, data=raw_body, verify=False)
|
|
1251
|
-
response.content_type = resp.headers.get('Content-Type', 'text/html')
|
|
1252
|
-
|
|
1253
|
-
if "<h2>Security Check</h2>" in resp.text or "click inside the open circle" in resp.text:
|
|
1254
|
-
status = "CAPTCHA verification failed. Please try again."
|
|
1255
|
-
info(status)
|
|
1256
|
-
|
|
1257
|
-
match = re.search(
|
|
1258
|
-
r"top\.location\.href\s*=\s*['\"]([^'\"]*?/go\b[^'\"]*)['\"]",
|
|
1259
|
-
resp.text,
|
|
1260
|
-
re.IGNORECASE
|
|
1261
|
-
)
|
|
1262
|
-
if match:
|
|
1263
|
-
redirect = match.group(1)
|
|
1264
|
-
resolved_url = urljoin(url, redirect)
|
|
1265
|
-
info(f"Redirect URL: {resolved_url}")
|
|
1266
|
-
try:
|
|
1267
|
-
redirect_resp = requests.post(resolved_url, cookies=cookies, headers=headers, allow_redirects=True,
|
|
1268
|
-
timeout=10, verify=False)
|
|
1269
|
-
|
|
1270
|
-
if "expired" in redirect_resp.text.lower():
|
|
1271
|
-
status = f"The CAPTCHA session has expired. Deleting package: {package_id}"
|
|
1272
|
-
info(status)
|
|
1273
|
-
shared_state.get_db("protected").delete(package_id)
|
|
1274
|
-
else:
|
|
1275
|
-
download_link = redirect_resp.url
|
|
1276
|
-
if redirect_resp.ok:
|
|
1277
|
-
status = f"Successfully resolved download link!"
|
|
1278
|
-
info(status)
|
|
1279
|
-
|
|
1280
|
-
raw_data = shared_state.get_db("protected").retrieve(package_id)
|
|
1281
|
-
data = json.loads(raw_data)
|
|
1282
|
-
title = data.get("title")
|
|
1283
|
-
password = data.get("password", "")
|
|
1284
|
-
links = [download_link]
|
|
1285
|
-
downloaded = shared_state.download_package(links, title, password, package_id)
|
|
1286
|
-
if downloaded:
|
|
1287
|
-
StatsHelper(shared_state).increment_package_with_links(links)
|
|
1288
|
-
success = True
|
|
1289
|
-
shared_state.get_db("protected").delete(package_id)
|
|
1290
|
-
else:
|
|
1291
|
-
raise RuntimeError("Submitting Download to JDownloader failed")
|
|
1292
|
-
else:
|
|
1293
|
-
info(
|
|
1294
|
-
f"Failed to reach redirect target. Status: {redirect_resp.status_code}, Solution: {status}")
|
|
1295
|
-
except Exception as e:
|
|
1296
|
-
info(f"Error while resolving download link: {e}")
|
|
1297
|
-
else:
|
|
1298
|
-
if resp.url.endswith("404.html"):
|
|
1299
|
-
info("Your IP has been blocked by Filecrypt. Please try again later.")
|
|
1300
|
-
else:
|
|
1301
|
-
info("You did not solve the CAPTCHA correctly. Please try again.")
|
|
1302
|
-
|
|
1303
|
-
if success:
|
|
1304
|
-
StatsHelper(shared_state).increment_captcha_decryptions_manual()
|
|
1305
|
-
else:
|
|
1306
|
-
StatsHelper(shared_state).increment_failed_decryptions_manual()
|
|
1307
|
-
|
|
1308
|
-
# Check if there are more CAPTCHAs to solve
|
|
1309
|
-
remaining_protected = shared_state.get_db("protected").retrieve_all_titles()
|
|
1310
|
-
has_more_captchas = bool(remaining_protected)
|
|
1311
|
-
|
|
1312
|
-
if has_more_captchas:
|
|
1313
|
-
solve_button = render_button("Solve another CAPTCHA", "primary", {
|
|
1314
|
-
"onclick": "location.href='/captcha'",
|
|
1315
|
-
})
|
|
1316
|
-
else:
|
|
1317
|
-
solve_button = "<b>No more CAPTCHAs</b>"
|
|
1318
|
-
|
|
1319
|
-
return render_centered_html(f"""
|
|
1320
|
-
<!DOCTYPE html>
|
|
1321
|
-
<html>
|
|
1322
|
-
<body>
|
|
1323
|
-
<h1><img src="{images.logo}" type="image/png" alt="Quasarr logo" class="logo"/>Quasarr</h1>
|
|
1324
|
-
<p>{status}</p>
|
|
1325
|
-
<p>
|
|
1326
|
-
{solve_button}
|
|
1327
|
-
</p>
|
|
1328
|
-
<p>
|
|
1329
|
-
{render_button("Back", "secondary", {"onclick": "location.href='/'"})}
|
|
1330
|
-
</p>
|
|
1331
|
-
</body>
|
|
1332
|
-
</html>""")
|