quasarr 1.28.2__py3-none-any.whl → 1.30.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 +433 -218
- quasarr/downloads/linkcrypters/filecrypt.py +2 -36
- quasarr/downloads/packages/__init__.py +52 -38
- quasarr/providers/jd_cache.py +131 -0
- quasarr/providers/obfuscated.py +6 -6
- quasarr/providers/version.py +1 -1
- {quasarr-1.28.2.dist-info → quasarr-1.30.0.dist-info}/METADATA +1 -3
- {quasarr-1.28.2.dist-info → quasarr-1.30.0.dist-info}/RECORD +12 -11
- {quasarr-1.28.2.dist-info → quasarr-1.30.0.dist-info}/WHEEL +0 -0
- {quasarr-1.28.2.dist-info → quasarr-1.30.0.dist-info}/entry_points.txt +0 -0
- {quasarr-1.28.2.dist-info → quasarr-1.30.0.dist-info}/licenses/LICENSE +0 -0
- {quasarr-1.28.2.dist-info → quasarr-1.30.0.dist-info}/top_level.txt +0 -0
quasarr/api/captcha/__init__.py
CHANGED
|
@@ -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,11 +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)
|
|
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>
|
|
319
|
+
{package_selector}
|
|
320
|
+
{failed_warning}
|
|
327
321
|
<p><b>Package:</b> {title}</p>
|
|
328
322
|
{render_userscript_section(url, package_id, title, password, "hide")}
|
|
329
323
|
<p>
|
|
@@ -355,11 +349,16 @@ def setup_captcha_routes(app):
|
|
|
355
349
|
|
|
356
350
|
check_package_exists(package_id)
|
|
357
351
|
|
|
352
|
+
package_selector = render_package_selector(package_id)
|
|
353
|
+
failed_warning = render_failed_attempts_warning(package_id)
|
|
354
|
+
|
|
358
355
|
return render_centered_html(f"""
|
|
359
356
|
<!DOCTYPE html>
|
|
360
357
|
<html>
|
|
361
358
|
<body>
|
|
362
359
|
<h1><img src="{images.logo}" type="image/png" alt="Quasarr logo" class="logo"/>Quasarr</h1>
|
|
360
|
+
{package_selector}
|
|
361
|
+
{failed_warning}
|
|
363
362
|
<p><b>Package:</b> {title}</p>
|
|
364
363
|
{render_userscript_section(url, package_id, title, password, "junkies")}
|
|
365
364
|
<p>
|
|
@@ -392,11 +391,16 @@ def setup_captcha_routes(app):
|
|
|
392
391
|
|
|
393
392
|
url = urls[0][0] if isinstance(urls[0], (list, tuple)) else urls[0]
|
|
394
393
|
|
|
394
|
+
package_selector = render_package_selector(package_id)
|
|
395
|
+
failed_warning = render_failed_attempts_warning(package_id)
|
|
396
|
+
|
|
395
397
|
return render_centered_html(f"""
|
|
396
398
|
<!DOCTYPE html>
|
|
397
399
|
<html>
|
|
398
400
|
<body>
|
|
399
401
|
<h1><img src="{images.logo}" type="image/png" alt="Quasarr logo" class="logo"/>Quasarr</h1>
|
|
402
|
+
{package_selector}
|
|
403
|
+
{failed_warning}
|
|
400
404
|
<p><b>Package:</b> {title}</p>
|
|
401
405
|
{render_userscript_section(url, package_id, title, password, "keeplinks")}
|
|
402
406
|
<p>
|
|
@@ -429,11 +433,16 @@ def setup_captcha_routes(app):
|
|
|
429
433
|
|
|
430
434
|
url = urls[0][0] if isinstance(urls[0], (list, tuple)) else urls[0]
|
|
431
435
|
|
|
436
|
+
package_selector = render_package_selector(package_id)
|
|
437
|
+
failed_warning = render_failed_attempts_warning(package_id)
|
|
438
|
+
|
|
432
439
|
return render_centered_html(f"""
|
|
433
440
|
<!DOCTYPE html>
|
|
434
441
|
<html>
|
|
435
442
|
<body>
|
|
436
443
|
<h1><img src="{images.logo}" type="image/png" alt="Quasarr logo" class="logo"/>Quasarr</h1>
|
|
444
|
+
{package_selector}
|
|
445
|
+
{failed_warning}
|
|
437
446
|
<p><b>Package:</b> {title}</p>
|
|
438
447
|
{render_userscript_section(url, package_id, title, password, "tolink")}
|
|
439
448
|
<p>
|
|
@@ -480,7 +489,7 @@ def setup_captcha_routes(app):
|
|
|
480
489
|
return content
|
|
481
490
|
|
|
482
491
|
def render_filecrypt_bypass_section(url, package_id, title, password):
|
|
483
|
-
"""Render the bypass UI section for
|
|
492
|
+
"""Render the bypass UI section for cutcaptcha captcha page"""
|
|
484
493
|
|
|
485
494
|
# Generate userscript URL with transfer params
|
|
486
495
|
# Get base URL of current request
|
|
@@ -539,7 +548,7 @@ def setup_captcha_routes(app):
|
|
|
539
548
|
|
|
540
549
|
<!-- Primary action button -->
|
|
541
550
|
<p>
|
|
542
|
-
{render_button("Open FileCrypt & Get Download Links", "primary", {"onclick": f"location.href='{url_with_quick_transfer_params}'"})}
|
|
551
|
+
{render_button("Open FileCrypt & Get Download Links", "primary", {"onclick": f"if(typeof incrementCaptchaAttempts==='function')incrementCaptchaAttempts();location.href='{url_with_quick_transfer_params}'"})}
|
|
543
552
|
</p>
|
|
544
553
|
|
|
545
554
|
<!-- Manual submission section -->
|
|
@@ -547,7 +556,7 @@ def setup_captcha_routes(app):
|
|
|
547
556
|
<p style="font-size: 0.9em; margin-bottom: 16px;">
|
|
548
557
|
If the userscript doesn't work, you can manually paste the links or upload a DLC file:
|
|
549
558
|
</p>
|
|
550
|
-
<form id="bypass-form" action="/captcha/bypass-submit" method="post" enctype="multipart/form-data">
|
|
559
|
+
<form id="bypass-form" action="/captcha/bypass-submit" method="post" enctype="multipart/form-data" onsubmit="if(typeof incrementCaptchaAttempts==='function')incrementCaptchaAttempts();">
|
|
551
560
|
<input type="hidden" name="package_id" value="{package_id}" />
|
|
552
561
|
<input type="hidden" name="title" value="{title}" />
|
|
553
562
|
<input type="hidden" name="password" value="{password}" />
|
|
@@ -610,6 +619,339 @@ def setup_captcha_routes(app):
|
|
|
610
619
|
</script>
|
|
611
620
|
'''
|
|
612
621
|
|
|
622
|
+
def render_package_selector(current_package_id):
|
|
623
|
+
"""Render a dropdown selector for all available packages at the top of captcha UIs"""
|
|
624
|
+
protected = shared_state.get_db("protected").retrieve_all_titles()
|
|
625
|
+
|
|
626
|
+
if not protected or len(protected) <= 1:
|
|
627
|
+
return "" # Don't show selector if only one or no packages
|
|
628
|
+
|
|
629
|
+
sj = shared_state.values["config"]("Hostnames").get("sj")
|
|
630
|
+
dj = shared_state.values["config"]("Hostnames").get("dj")
|
|
631
|
+
|
|
632
|
+
def is_junkies_link(link):
|
|
633
|
+
url = link[0] if isinstance(link, (list, tuple)) else link
|
|
634
|
+
mirror = link[1] if isinstance(link, (list, tuple)) and len(link) > 1 else ""
|
|
635
|
+
if mirror == "junkies":
|
|
636
|
+
return True
|
|
637
|
+
return (sj and sj in url) or (dj and dj in url)
|
|
638
|
+
|
|
639
|
+
def get_captcha_type_for_links(links):
|
|
640
|
+
"""Determine which captcha type to use based on links"""
|
|
641
|
+
has_hide = any(("hide." in (l[0] if isinstance(l, (list, tuple)) else l)) for l in links)
|
|
642
|
+
has_junkies = any(is_junkies_link(l) for l in links)
|
|
643
|
+
has_keeplinks = any(("keeplinks." in (l[0] if isinstance(l, (list, tuple)) else l)) for l in links)
|
|
644
|
+
has_tolink = any(("tolink." in (l[0] if isinstance(l, (list, tuple)) else l)) for l in links)
|
|
645
|
+
|
|
646
|
+
if has_hide:
|
|
647
|
+
return "hide"
|
|
648
|
+
elif has_junkies:
|
|
649
|
+
return "junkies"
|
|
650
|
+
elif has_keeplinks:
|
|
651
|
+
return "keeplinks"
|
|
652
|
+
elif has_tolink:
|
|
653
|
+
return "tolink"
|
|
654
|
+
else:
|
|
655
|
+
return "cutcaptcha"
|
|
656
|
+
|
|
657
|
+
options = []
|
|
658
|
+
for package in protected:
|
|
659
|
+
pkg_id = package[0]
|
|
660
|
+
data = json.loads(package[1])
|
|
661
|
+
title = data.get("title", "Unknown")
|
|
662
|
+
links = data.get("links", [])
|
|
663
|
+
password = data.get("password", "")
|
|
664
|
+
mirror = data.get("mirror")
|
|
665
|
+
|
|
666
|
+
# Prioritize rapidgator links for cutcaptcha
|
|
667
|
+
rapid = [ln for ln in links if "rapidgator" in ln[1].lower()]
|
|
668
|
+
others = [ln for ln in links if "rapidgator" not in ln[1].lower()]
|
|
669
|
+
prioritized = rapid + others
|
|
670
|
+
|
|
671
|
+
payload = {
|
|
672
|
+
"package_id": pkg_id,
|
|
673
|
+
"title": title,
|
|
674
|
+
"password": password,
|
|
675
|
+
"mirror": mirror,
|
|
676
|
+
"links": prioritized,
|
|
677
|
+
}
|
|
678
|
+
encoded = urlsafe_b64encode(json.dumps(payload).encode()).decode()
|
|
679
|
+
captcha_type = get_captcha_type_for_links(prioritized)
|
|
680
|
+
|
|
681
|
+
selected = "selected" if pkg_id == current_package_id else ""
|
|
682
|
+
# Truncate long titles for display
|
|
683
|
+
display_title = (title[:50] + "...") if len(title) > 53 else title
|
|
684
|
+
options.append(f'<option value="{captcha_type}|{quote(encoded)}" {selected}>{display_title}</option>')
|
|
685
|
+
|
|
686
|
+
options_html = "\n".join(options)
|
|
687
|
+
|
|
688
|
+
return f'''
|
|
689
|
+
<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;">
|
|
690
|
+
<label for="package-select" style="display: block; margin-bottom: 8px; font-weight: bold;">📦 Select Package:</label>
|
|
691
|
+
<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;">
|
|
692
|
+
{options_html}
|
|
693
|
+
</select>
|
|
694
|
+
</div>
|
|
695
|
+
<script>
|
|
696
|
+
document.getElementById('package-select').addEventListener('change', function() {{
|
|
697
|
+
const [captchaType, encodedData] = this.value.split('|');
|
|
698
|
+
window.location.href = '/captcha/' + captchaType + '?data=' + encodedData;
|
|
699
|
+
}});
|
|
700
|
+
</script>
|
|
701
|
+
'''
|
|
702
|
+
|
|
703
|
+
def render_failed_attempts_warning(package_id, include_delete_button=True, fallback_url=None):
|
|
704
|
+
"""Render a warning block that shows after 2+ failed attempts per package_id.
|
|
705
|
+
Uses localStorage to track attempts by package_id to ensure reliable tracking
|
|
706
|
+
even when package titles are duplicated.
|
|
707
|
+
|
|
708
|
+
Attempts are NOT incremented on page load - they must be incremented by
|
|
709
|
+
calling window.incrementCaptchaAttempts() when user takes an action (e.g.,
|
|
710
|
+
clicking submit, opening bypass link).
|
|
711
|
+
|
|
712
|
+
Args:
|
|
713
|
+
package_id: The unique package identifier
|
|
714
|
+
include_delete_button: Whether to show delete button in warning
|
|
715
|
+
fallback_url: Optional URL to a fallback page (e.g., FileCrypt manual fallback)
|
|
716
|
+
"""
|
|
717
|
+
|
|
718
|
+
delete_button = ""
|
|
719
|
+
if include_delete_button:
|
|
720
|
+
delete_button = render_button("Delete Package", "primary",
|
|
721
|
+
{"onclick": f"location.href='/captcha/delete/{package_id}'"})
|
|
722
|
+
|
|
723
|
+
fallback_link = ""
|
|
724
|
+
if fallback_url:
|
|
725
|
+
fallback_link = f'''
|
|
726
|
+
<p style="margin-top: 12px; margin-bottom: 8px;">
|
|
727
|
+
<a href="{fallback_url}" style="color: #cc0000;">Try the manual FileCrypt fallback page →</a>
|
|
728
|
+
</p>
|
|
729
|
+
'''
|
|
730
|
+
|
|
731
|
+
return f'''
|
|
732
|
+
<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;">
|
|
733
|
+
<h3 style="color: #dc2626; margin-top: 0;">⚠️ Multiple Failed Attempts Detected</h3>
|
|
734
|
+
<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>
|
|
735
|
+
<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>
|
|
736
|
+
{fallback_link}
|
|
737
|
+
<div id="warning-delete-button" style="margin-top: 12px;">
|
|
738
|
+
{delete_button}
|
|
739
|
+
</div>
|
|
740
|
+
</div>
|
|
741
|
+
<script>
|
|
742
|
+
(function() {{
|
|
743
|
+
const packageId = '{package_id}';
|
|
744
|
+
const storageKey = 'captcha_attempts_' + packageId;
|
|
745
|
+
|
|
746
|
+
// Get current attempt count (do NOT increment on page load)
|
|
747
|
+
let attempts = parseInt(localStorage.getItem(storageKey) || '0', 10);
|
|
748
|
+
|
|
749
|
+
// Show warning if 2+ failed attempts
|
|
750
|
+
if (attempts >= 2) {{
|
|
751
|
+
const warningBox = document.getElementById('failed-attempts-warning');
|
|
752
|
+
if (warningBox) {{
|
|
753
|
+
warningBox.style.display = 'block';
|
|
754
|
+
}}
|
|
755
|
+
}}
|
|
756
|
+
|
|
757
|
+
// Function to increment attempts (call this on submit/action)
|
|
758
|
+
window.incrementCaptchaAttempts = function() {{
|
|
759
|
+
let current = parseInt(localStorage.getItem(storageKey) || '0', 10);
|
|
760
|
+
current++;
|
|
761
|
+
localStorage.setItem(storageKey, current.toString());
|
|
762
|
+
// Show warning immediately if we hit 2+ attempts
|
|
763
|
+
if (current >= 2) {{
|
|
764
|
+
const warningBox = document.getElementById('failed-attempts-warning');
|
|
765
|
+
if (warningBox) {{
|
|
766
|
+
warningBox.style.display = 'block';
|
|
767
|
+
}}
|
|
768
|
+
}}
|
|
769
|
+
return current;
|
|
770
|
+
}};
|
|
771
|
+
|
|
772
|
+
// Function to get current attempt count
|
|
773
|
+
window.getCaptchaAttempts = function() {{
|
|
774
|
+
return parseInt(localStorage.getItem(storageKey) || '0', 10);
|
|
775
|
+
}};
|
|
776
|
+
|
|
777
|
+
// Function to clear attempts (call on success)
|
|
778
|
+
window.clearCaptchaAttempts = function() {{
|
|
779
|
+
localStorage.removeItem(storageKey);
|
|
780
|
+
}};
|
|
781
|
+
}})();
|
|
782
|
+
</script>
|
|
783
|
+
'''
|
|
784
|
+
|
|
785
|
+
@app.get("/captcha/filecrypt")
|
|
786
|
+
def serve_filecrypt_fallback():
|
|
787
|
+
"""Dedicated FileCrypt fallback page - similar to hide/junkies/keeplinks/tolink"""
|
|
788
|
+
payload = decode_payload()
|
|
789
|
+
|
|
790
|
+
if "error" in payload:
|
|
791
|
+
return render_centered_html(f'''<h1><img src="{images.logo}" type="image/png" alt="Quasarr logo" class="logo"/>Quasarr</h1>
|
|
792
|
+
<p>{payload["error"]}</p>
|
|
793
|
+
<p>
|
|
794
|
+
{render_button("Back", "secondary", {"onclick": "location.href='/'"})}
|
|
795
|
+
</p>''')
|
|
796
|
+
|
|
797
|
+
package_id = payload.get("package_id")
|
|
798
|
+
title = payload.get("title")
|
|
799
|
+
password = payload.get("password")
|
|
800
|
+
urls = payload.get("links")
|
|
801
|
+
|
|
802
|
+
check_package_exists(package_id)
|
|
803
|
+
|
|
804
|
+
url = urls[0][0] if isinstance(urls[0], (list, tuple)) else urls[0]
|
|
805
|
+
|
|
806
|
+
# Generate userscript URL with transfer params
|
|
807
|
+
base_url = request.urlparts.scheme + '://' + request.urlparts.netloc
|
|
808
|
+
transfer_url = f"{base_url}/captcha/quick-transfer"
|
|
809
|
+
|
|
810
|
+
url_with_quick_transfer_params = (
|
|
811
|
+
f"{url}?"
|
|
812
|
+
f"transfer_url={quote(transfer_url)}&"
|
|
813
|
+
f"pkg_id={quote(package_id)}&"
|
|
814
|
+
f"pkg_title={quote(title)}&"
|
|
815
|
+
f"pkg_pass={quote(password)}"
|
|
816
|
+
)
|
|
817
|
+
|
|
818
|
+
package_selector = render_package_selector(package_id)
|
|
819
|
+
failed_warning = render_failed_attempts_warning(package_id)
|
|
820
|
+
|
|
821
|
+
return render_centered_html(f"""
|
|
822
|
+
<!DOCTYPE html>
|
|
823
|
+
<html>
|
|
824
|
+
<body>
|
|
825
|
+
<h1><img src="{images.logo}" type="image/png" alt="Quasarr logo" class="logo"/>Quasarr</h1>
|
|
826
|
+
{package_selector}
|
|
827
|
+
{failed_warning}
|
|
828
|
+
<p style="max-width: 370px; word-wrap: break-word; overflow-wrap: break-word;"><b>Package:</b> {title}</p>
|
|
829
|
+
|
|
830
|
+
<div>
|
|
831
|
+
<!-- Info section explaining the process -->
|
|
832
|
+
<div class="info-box">
|
|
833
|
+
<h3>ℹ️ How This Works:</h3>
|
|
834
|
+
<p style="margin-bottom: 8px;">
|
|
835
|
+
1. Click the button below to open FileCrypt directly
|
|
836
|
+
</p>
|
|
837
|
+
<p style="margin-top: 0; margin-bottom: 8px;">
|
|
838
|
+
2. Solve any CAPTCHAs on their site to reveal the download links
|
|
839
|
+
</p>
|
|
840
|
+
<p style="margin-top: 0; margin-bottom: 0;">
|
|
841
|
+
3. <b>With the userscript installed</b>, links are automatically sent back to Quasarr!
|
|
842
|
+
</p>
|
|
843
|
+
</div>
|
|
844
|
+
|
|
845
|
+
<!-- One-time setup section - visually separated -->
|
|
846
|
+
<div id="setup-instructions" class="setup-box">
|
|
847
|
+
<h3>📦 First Time Setup:</h3>
|
|
848
|
+
<p style="margin-bottom: 8px;">
|
|
849
|
+
<a href="https://www.tampermonkey.net/" target="_blank" rel="noopener noreferrer">1. Install Tampermonkey</a>
|
|
850
|
+
</p>
|
|
851
|
+
<p style="margin-top: 0; margin-bottom: 12px;">
|
|
852
|
+
<a href="/captcha/filecrypt.user.js" target="_blank">2. Install the FileCrypt userscript</a>
|
|
853
|
+
</p>
|
|
854
|
+
<p style="margin-top: 0;">
|
|
855
|
+
<button id="hide-setup-btn" type="button" class="btn-subtle">
|
|
856
|
+
✅ Don't show this again
|
|
857
|
+
</button>
|
|
858
|
+
</p>
|
|
859
|
+
</div>
|
|
860
|
+
|
|
861
|
+
<!-- Hidden "show instructions" button -->
|
|
862
|
+
<div id="show-instructions-link" style="display: none; margin-bottom: 16px;">
|
|
863
|
+
<button id="show-setup-btn" type="button" class="btn-subtle">
|
|
864
|
+
ℹ️ Show setup instructions
|
|
865
|
+
</button>
|
|
866
|
+
</div>
|
|
867
|
+
|
|
868
|
+
<!-- Primary action button -->
|
|
869
|
+
<p>
|
|
870
|
+
{render_button("Open FileCrypt & Get Download Links", "primary", {"onclick": f"if(typeof incrementCaptchaAttempts==='function')incrementCaptchaAttempts();location.href='{url_with_quick_transfer_params}'"})}
|
|
871
|
+
</p>
|
|
872
|
+
|
|
873
|
+
<!-- Manual submission section -->
|
|
874
|
+
<div class="section-divider">
|
|
875
|
+
<details id="manualSubmitDetails">
|
|
876
|
+
<summary id="manualSubmitSummary" style="cursor: pointer;">Show Manual Submission</summary>
|
|
877
|
+
<div style="margin-top: 16px;">
|
|
878
|
+
<p style="font-size: 0.9em; margin-bottom: 16px;">
|
|
879
|
+
If the userscript doesn't work, you can manually paste the links or upload a DLC file:
|
|
880
|
+
</p>
|
|
881
|
+
<form id="bypass-form" action="/captcha/bypass-submit" method="post" enctype="multipart/form-data" onsubmit="if(typeof incrementCaptchaAttempts==='function')incrementCaptchaAttempts();">
|
|
882
|
+
<input type="hidden" name="package_id" value="{package_id}" />
|
|
883
|
+
<input type="hidden" name="title" value="{title}" />
|
|
884
|
+
<input type="hidden" name="password" value="{password}" />
|
|
885
|
+
|
|
886
|
+
<div>
|
|
887
|
+
<strong>Paste the download links (one per line):</strong>
|
|
888
|
+
<textarea id="links-input" name="links" rows="5" style="width: 100%; padding: 8px; font-family: monospace; resize: vertical;"></textarea>
|
|
889
|
+
</div>
|
|
890
|
+
|
|
891
|
+
<div>
|
|
892
|
+
<strong>Or upload DLC file:</strong><br>
|
|
893
|
+
<input type="file" id="dlc-file" name="dlc_file" accept=".dlc" />
|
|
894
|
+
</div>
|
|
895
|
+
|
|
896
|
+
<div>
|
|
897
|
+
{render_button("Submit", "primary", {"type": "submit"})}
|
|
898
|
+
</div>
|
|
899
|
+
</form>
|
|
900
|
+
</div>
|
|
901
|
+
</details>
|
|
902
|
+
</div>
|
|
903
|
+
</div>
|
|
904
|
+
|
|
905
|
+
<p>
|
|
906
|
+
{render_button("Delete Package", "secondary", {"onclick": f"location.href='/captcha/delete/{package_id}'"})}
|
|
907
|
+
</p>
|
|
908
|
+
<p>
|
|
909
|
+
{render_button("Back", "secondary", {"onclick": "location.href='/'"})}
|
|
910
|
+
</p>
|
|
911
|
+
|
|
912
|
+
<script>
|
|
913
|
+
// Handle manual submission toggle text
|
|
914
|
+
const manualDetails = document.getElementById('manualSubmitDetails');
|
|
915
|
+
const manualSummary = document.getElementById('manualSubmitSummary');
|
|
916
|
+
|
|
917
|
+
if (manualDetails && manualSummary) {{
|
|
918
|
+
manualDetails.addEventListener('toggle', () => {{
|
|
919
|
+
if (manualDetails.open) {{
|
|
920
|
+
manualSummary.textContent = 'Hide Manual Submission';
|
|
921
|
+
}} else {{
|
|
922
|
+
manualSummary.textContent = 'Show Manual Submission';
|
|
923
|
+
}}
|
|
924
|
+
}});
|
|
925
|
+
}}
|
|
926
|
+
|
|
927
|
+
// Handle setup instructions hide/show
|
|
928
|
+
const hideSetup = localStorage.getItem('hideFileCryptFallbackSetupInstructions');
|
|
929
|
+
const setupBox = document.getElementById('setup-instructions');
|
|
930
|
+
const showLink = document.getElementById('show-instructions-link');
|
|
931
|
+
|
|
932
|
+
if (hideSetup === 'true') {{
|
|
933
|
+
setupBox.style.display = 'none';
|
|
934
|
+
showLink.style.display = 'block';
|
|
935
|
+
}}
|
|
936
|
+
|
|
937
|
+
// Hide setup instructions
|
|
938
|
+
document.getElementById('hide-setup-btn').addEventListener('click', function() {{
|
|
939
|
+
localStorage.setItem('hideFileCryptFallbackSetupInstructions', 'true');
|
|
940
|
+
setupBox.style.display = 'none';
|
|
941
|
+
showLink.style.display = 'block';
|
|
942
|
+
}});
|
|
943
|
+
|
|
944
|
+
// Show setup instructions again
|
|
945
|
+
document.getElementById('show-setup-btn').addEventListener('click', function() {{
|
|
946
|
+
localStorage.setItem('hideFileCryptFallbackSetupInstructions', 'false');
|
|
947
|
+
setupBox.style.display = 'block';
|
|
948
|
+
showLink.style.display = 'none';
|
|
949
|
+
}});
|
|
950
|
+
</script>
|
|
951
|
+
|
|
952
|
+
</body>
|
|
953
|
+
</html>""")
|
|
954
|
+
|
|
613
955
|
@app.get('/captcha/quick-transfer')
|
|
614
956
|
def handle_quick_transfer():
|
|
615
957
|
"""Handle quick transfer from userscript"""
|
|
@@ -711,7 +1053,8 @@ def setup_captcha_routes(app):
|
|
|
711
1053
|
</p>
|
|
712
1054
|
<p>
|
|
713
1055
|
{render_button("Back", "secondary", {"onclick": "location.href='/'"})}
|
|
714
|
-
</p>
|
|
1056
|
+
</p>
|
|
1057
|
+
<script>localStorage.removeItem('captcha_attempts_{package_id}');</script>''')
|
|
715
1058
|
else:
|
|
716
1059
|
StatsHelper(shared_state).increment_failed_decryptions_manual()
|
|
717
1060
|
return render_centered_html(f'''<h1><img src="{images.logo}" type="image/png" alt="Quasarr logo" class="logo"/>Quasarr</h1>
|
|
@@ -751,7 +1094,8 @@ def setup_captcha_routes(app):
|
|
|
751
1094
|
</p>
|
|
752
1095
|
<p>
|
|
753
1096
|
{render_button("Back", "secondary", {"onclick": "location.href='/'"})}
|
|
754
|
-
</p>
|
|
1097
|
+
</p>
|
|
1098
|
+
<script>localStorage.removeItem('captcha_attempts_{package_id}');</script>''')
|
|
755
1099
|
else:
|
|
756
1100
|
return render_centered_html(f'''<h1><img src="{images.logo}" type="image/png" alt="Quasarr logo" class="logo"/>Quasarr</h1>
|
|
757
1101
|
<p>Failed to delete package!</p>
|
|
@@ -826,8 +1170,50 @@ def setup_captcha_routes(app):
|
|
|
826
1170
|
# Add bypass section
|
|
827
1171
|
bypass_section = render_filecrypt_bypass_section(url, package_id, title, password)
|
|
828
1172
|
|
|
1173
|
+
# Add package selector and failed attempts warning
|
|
1174
|
+
package_selector = render_package_selector(package_id)
|
|
1175
|
+
|
|
1176
|
+
# Create fallback URL for the manual FileCrypt page
|
|
1177
|
+
fallback_payload = {
|
|
1178
|
+
"package_id": package_id,
|
|
1179
|
+
"title": title,
|
|
1180
|
+
"password": password,
|
|
1181
|
+
"mirror": desired_mirror,
|
|
1182
|
+
"links": prioritized_links,
|
|
1183
|
+
}
|
|
1184
|
+
fallback_encoded = urlsafe_b64encode(json.dumps(fallback_payload).encode()).decode()
|
|
1185
|
+
filecrypt_fallback_url = f"/captcha/filecrypt?data={quote(fallback_encoded)}"
|
|
1186
|
+
|
|
1187
|
+
failed_warning = render_failed_attempts_warning(package_id, include_delete_button=False,
|
|
1188
|
+
fallback_url=filecrypt_fallback_url) # Delete button is already below
|
|
1189
|
+
|
|
829
1190
|
content = render_centered_html(r'''
|
|
1191
|
+
<style>
|
|
1192
|
+
@media (max-width: 600px) {
|
|
1193
|
+
.package-selector,
|
|
1194
|
+
#failed-attempts-warning {
|
|
1195
|
+
margin-left: 0 !important;
|
|
1196
|
+
margin-right: 0 !important;
|
|
1197
|
+
padding-left: 8px !important;
|
|
1198
|
+
padding-right: 8px !important;
|
|
1199
|
+
border-radius: 0 !important;
|
|
1200
|
+
border-left: none !important;
|
|
1201
|
+
border-right: none !important;
|
|
1202
|
+
}
|
|
1203
|
+
}
|
|
1204
|
+
</style>
|
|
830
1205
|
<script type="text/javascript">
|
|
1206
|
+
// Check if we should redirect to fallback due to failed attempts
|
|
1207
|
+
(function() {
|
|
1208
|
+
const storageKey = 'captcha_attempts_''' + package_id + r'''';
|
|
1209
|
+
const attempts = parseInt(localStorage.getItem(storageKey) || '0', 10);
|
|
1210
|
+
if (attempts >= 2) {
|
|
1211
|
+
// Redirect to FileCrypt fallback page
|
|
1212
|
+
window.location.href = '''' + filecrypt_fallback_url + r'''';
|
|
1213
|
+
return;
|
|
1214
|
+
}
|
|
1215
|
+
})();
|
|
1216
|
+
|
|
831
1217
|
var api_key = "''' + obfuscated.captcha_values()["api_key"] + r'''";
|
|
832
1218
|
var endpoint = '/' + window.location.pathname.split('/')[1] + '/' + api_key + '.html';
|
|
833
1219
|
var solveAnotherHtml = `<p>''' + solve_another_html + r'''</p><p>''' + back_button_html + r'''</p>`;
|
|
@@ -839,6 +1225,11 @@ def setup_captcha_routes(app):
|
|
|
839
1225
|
document.getElementById("delete-package-section").style.display = "none";
|
|
840
1226
|
document.getElementById("back-button-section").style.display = "none";
|
|
841
1227
|
document.getElementById("bypass-section").style.display = "none";
|
|
1228
|
+
// Hide package selector and warning on token submission
|
|
1229
|
+
var pkgSelector = document.getElementById("package-selector-section");
|
|
1230
|
+
if (pkgSelector) pkgSelector.style.display = "none";
|
|
1231
|
+
var warnBox = document.getElementById("failed-attempts-warning");
|
|
1232
|
+
if (warnBox) warnBox.style.display = "none";
|
|
842
1233
|
|
|
843
1234
|
// Remove width limit on result screen
|
|
844
1235
|
var packageTitle = document.getElementById("package-title");
|
|
@@ -867,9 +1258,17 @@ def setup_captcha_routes(app):
|
|
|
867
1258
|
if (data.success) {
|
|
868
1259
|
document.getElementById("captcha-key").insertAdjacentHTML('afterend',
|
|
869
1260
|
'<p>✅ Successful!</p>');
|
|
1261
|
+
// Clear failed attempts on success
|
|
1262
|
+
if (typeof clearCaptchaAttempts === 'function') {
|
|
1263
|
+
clearCaptchaAttempts();
|
|
1264
|
+
}
|
|
870
1265
|
} else {
|
|
871
1266
|
document.getElementById("captcha-key").insertAdjacentHTML('afterend',
|
|
872
1267
|
'<p>Failed. Check console for details!</p>');
|
|
1268
|
+
// Increment failed attempts on failure
|
|
1269
|
+
if (typeof incrementCaptchaAttempts === 'function') {
|
|
1270
|
+
incrementCaptchaAttempts();
|
|
1271
|
+
}
|
|
873
1272
|
}
|
|
874
1273
|
|
|
875
1274
|
// Show appropriate button based on whether more CAPTCHAs exist
|
|
@@ -885,6 +1284,10 @@ def setup_captcha_routes(app):
|
|
|
885
1284
|
''' + obfuscated.cutcaptcha_custom_js() + f'''</script>
|
|
886
1285
|
<div>
|
|
887
1286
|
<h1><img src="{images.logo}" type="image/png" alt="Quasarr logo" class="logo"/>Quasarr</h1>
|
|
1287
|
+
<div id="package-selector-section">
|
|
1288
|
+
{package_selector}
|
|
1289
|
+
</div>
|
|
1290
|
+
{failed_warning}
|
|
888
1291
|
<p id="package-title" style="max-width: 370px; word-wrap: break-word; overflow-wrap: break-word;"><b>Package:</b> {title}</p>
|
|
889
1292
|
<div id="captcha-key"></div>
|
|
890
1293
|
{link_select}<br><br>
|
|
@@ -1068,7 +1471,8 @@ def setup_captcha_routes(app):
|
|
|
1068
1471
|
</p>
|
|
1069
1472
|
<p>
|
|
1070
1473
|
{render_button("Back", "secondary", {"onclick": "location.href='/'"})}
|
|
1071
|
-
</p>
|
|
1474
|
+
</p>
|
|
1475
|
+
<script>localStorage.removeItem('captcha_attempts_{package_id}');</script>''')
|
|
1072
1476
|
else:
|
|
1073
1477
|
StatsHelper(shared_state).increment_failed_decryptions_manual()
|
|
1074
1478
|
return render_centered_html(f'''<h1><img src="{images.logo}" type="image/png" alt="Quasarr logo" class="logo"/>Quasarr</h1>
|
|
@@ -1113,38 +1517,17 @@ def setup_captcha_routes(app):
|
|
|
1113
1517
|
info(f"Decrypting links for {title}")
|
|
1114
1518
|
decrypted = get_filecrypt_links(shared_state, token, title, link, password=password, mirror=mirror)
|
|
1115
1519
|
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
|
-
|
|
1520
|
+
links = decrypted.get("links", [])
|
|
1521
|
+
info(f"Decrypted {len(links)} download links for {title}")
|
|
1522
|
+
if not links:
|
|
1523
|
+
raise ValueError("No download links found after decryption")
|
|
1524
|
+
downloaded = shared_state.download_package(links, title, password, package_id)
|
|
1525
|
+
if downloaded:
|
|
1526
|
+
StatsHelper(shared_state).increment_package_with_links(links)
|
|
1527
|
+
shared_state.get_db("protected").delete(package_id)
|
|
1136
1528
|
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")
|
|
1529
|
+
links = []
|
|
1530
|
+
raise RuntimeError("Submitting Download to JDownloader failed")
|
|
1148
1531
|
else:
|
|
1149
1532
|
raise ValueError("No download links found")
|
|
1150
1533
|
|
|
@@ -1162,171 +1545,3 @@ def setup_captcha_routes(app):
|
|
|
1162
1545
|
has_more_captchas = bool(remaining_protected)
|
|
1163
1546
|
|
|
1164
1547
|
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>""")
|