quasarr 2.6.1__py3-none-any.whl → 2.7.1__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/__init__.py +71 -61
- quasarr/api/__init__.py +1 -2
- quasarr/api/arr/__init__.py +66 -57
- quasarr/api/captcha/__init__.py +203 -154
- quasarr/downloads/__init__.py +12 -8
- quasarr/downloads/linkcrypters/al.py +4 -4
- quasarr/downloads/linkcrypters/filecrypt.py +1 -2
- quasarr/downloads/packages/__init__.py +62 -88
- quasarr/downloads/sources/al.py +3 -3
- quasarr/downloads/sources/by.py +3 -3
- quasarr/downloads/sources/he.py +8 -9
- quasarr/downloads/sources/nk.py +3 -3
- quasarr/downloads/sources/sl.py +6 -1
- quasarr/downloads/sources/wd.py +93 -37
- quasarr/downloads/sources/wx.py +11 -17
- quasarr/providers/auth.py +9 -13
- quasarr/providers/cloudflare.py +5 -4
- quasarr/providers/imdb_metadata.py +1 -3
- quasarr/providers/jd_cache.py +64 -90
- quasarr/providers/log.py +226 -8
- quasarr/providers/myjd_api.py +116 -94
- quasarr/providers/sessions/al.py +20 -22
- quasarr/providers/sessions/dd.py +1 -1
- quasarr/providers/sessions/dl.py +8 -10
- quasarr/providers/sessions/nx.py +1 -1
- quasarr/providers/shared_state.py +26 -15
- quasarr/providers/utils.py +15 -6
- quasarr/providers/version.py +1 -1
- quasarr/search/__init__.py +113 -82
- quasarr/search/sources/al.py +19 -23
- quasarr/search/sources/by.py +6 -6
- quasarr/search/sources/dd.py +8 -10
- quasarr/search/sources/dj.py +15 -18
- quasarr/search/sources/dl.py +25 -37
- quasarr/search/sources/dt.py +13 -15
- quasarr/search/sources/dw.py +24 -16
- quasarr/search/sources/fx.py +25 -11
- quasarr/search/sources/he.py +16 -14
- quasarr/search/sources/hs.py +7 -7
- quasarr/search/sources/mb.py +7 -7
- quasarr/search/sources/nk.py +24 -25
- quasarr/search/sources/nx.py +22 -15
- quasarr/search/sources/sf.py +18 -9
- quasarr/search/sources/sj.py +7 -7
- quasarr/search/sources/sl.py +26 -14
- quasarr/search/sources/wd.py +61 -31
- quasarr/search/sources/wx.py +33 -47
- quasarr/storage/config.py +1 -3
- {quasarr-2.6.1.dist-info → quasarr-2.7.1.dist-info}/METADATA +4 -1
- quasarr-2.7.1.dist-info/RECORD +84 -0
- quasarr-2.6.1.dist-info/RECORD +0 -84
- {quasarr-2.6.1.dist-info → quasarr-2.7.1.dist-info}/WHEEL +0 -0
- {quasarr-2.6.1.dist-info → quasarr-2.7.1.dist-info}/entry_points.txt +0 -0
- {quasarr-2.6.1.dist-info → quasarr-2.7.1.dist-info}/licenses/LICENSE +0 -0
quasarr/api/captcha/__init__.py
CHANGED
|
@@ -16,7 +16,7 @@ from quasarr.downloads.linkcrypters.filecrypt import DLC, get_filecrypt_links
|
|
|
16
16
|
from quasarr.downloads.packages import delete_package
|
|
17
17
|
from quasarr.providers import obfuscated, shared_state
|
|
18
18
|
from quasarr.providers.html_templates import render_button, render_centered_html
|
|
19
|
-
from quasarr.providers.log import debug, info
|
|
19
|
+
from quasarr.providers.log import debug, error, info, trace
|
|
20
20
|
from quasarr.providers.statistics import StatsHelper
|
|
21
21
|
|
|
22
22
|
|
|
@@ -212,37 +212,22 @@ def setup_captcha_routes(app):
|
|
|
212
212
|
f"pkg_pass={quote(password)}"
|
|
213
213
|
)
|
|
214
214
|
|
|
215
|
+
js_url = url_with_quick_transfer_params.replace("'", "\\'")
|
|
216
|
+
js_userscript_url = userscript_url.replace("'", "\\'")
|
|
217
|
+
js_provider_name = provider_name.replace("'", "\\'")
|
|
218
|
+
|
|
215
219
|
return f'''
|
|
216
220
|
<div>
|
|
217
|
-
<!-- One-time setup section - visually separated -->
|
|
218
|
-
<div id="setup-instructions" class="setup-box">
|
|
219
|
-
<h3>📦 First Time Setup:</h3>
|
|
220
|
-
<p style="margin-bottom: 8px;">
|
|
221
|
-
<a href="https://www.tampermonkey.net/" target="_blank" rel="noopener noreferrer">1. On mobile Safari/Firefox or any Desktop Browser install Tampermonkey</a>
|
|
222
|
-
</p>
|
|
223
|
-
<p style="margin-top: 0; margin-bottom: 8px;">
|
|
224
|
-
<a href="{userscript_url}" target="_blank">2. Install the {provider_name} userscript</a>
|
|
225
|
-
</p>
|
|
226
|
-
<p style="margin-top: 0; margin-bottom: 12px;">
|
|
227
|
-
3. Open link, solve CAPTCHAs, and links are automatically sent back to Quasarr!
|
|
228
|
-
</p>
|
|
229
|
-
<p style="margin-top: 0;">
|
|
230
|
-
<button id="hide-setup-btn" type="button" class="btn-subtle">
|
|
231
|
-
✅ Don't show this again
|
|
232
|
-
</button>
|
|
233
|
-
</p>
|
|
234
|
-
</div>
|
|
235
|
-
|
|
236
|
-
<!-- Hidden "show instructions" button -->
|
|
237
|
-
<div id="show-instructions-link" style="display: none; margin-bottom: 16px;">
|
|
238
|
-
<button id="show-setup-btn" type="button" class="btn-subtle">
|
|
239
|
-
ℹ️ Show setup instructions
|
|
240
|
-
</button>
|
|
241
|
-
</div>
|
|
242
|
-
|
|
243
221
|
<!-- Primary action - the quick transfer link -->
|
|
244
222
|
<p>
|
|
245
|
-
{render_button(f"Open {provider_name} & Get Download Links", "primary", {"onclick": f"
|
|
223
|
+
{render_button(f"Open {provider_name} & Get Download Links", "primary", {"onclick": f"handleProviderClick('{js_url}', '{storage_key}', '{js_provider_name}', '{js_userscript_url}')"})}
|
|
224
|
+
</p>
|
|
225
|
+
|
|
226
|
+
<!-- Reset tutorial button -->
|
|
227
|
+
<p id="reset-tutorial-btn" style="display: none;">
|
|
228
|
+
<button type="button" class="btn-subtle" onclick="localStorage.removeItem('{storage_key}'); showModal('Tutorial Reset', '<p>Tutorial reset! Click the Open button to see it again.</p>', '<button class=\\'btn-primary\\' onclick=\\'location.reload()\\'>Reload</button>');">
|
|
229
|
+
ℹ️ Reset Setup Guide
|
|
230
|
+
</button>
|
|
246
231
|
</p>
|
|
247
232
|
|
|
248
233
|
<!-- Manual submission - collapsible -->
|
|
@@ -286,29 +271,60 @@ def setup_captcha_routes(app):
|
|
|
286
271
|
}});
|
|
287
272
|
}}
|
|
288
273
|
|
|
289
|
-
//
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
const showLink = document.getElementById('show-instructions-link');
|
|
293
|
-
|
|
294
|
-
if (hideSetup === 'true') {{
|
|
295
|
-
setupBox.style.display = 'none';
|
|
296
|
-
showLink.style.display = 'block';
|
|
274
|
+
// Show reset button if tutorial was already seen
|
|
275
|
+
if (localStorage.getItem('{storage_key}') === 'true') {{
|
|
276
|
+
document.getElementById('reset-tutorial-btn').style.display = 'block';
|
|
297
277
|
}}
|
|
298
278
|
|
|
299
|
-
//
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
279
|
+
// Global handler for provider clicks
|
|
280
|
+
if (!window.handleProviderClick) {{
|
|
281
|
+
window.handleProviderClick = function(url, storageKey, providerName, userscriptUrl) {{
|
|
282
|
+
if (localStorage.getItem(storageKey) === 'true') {{
|
|
283
|
+
if(typeof incrementCaptchaAttempts==='function') incrementCaptchaAttempts();
|
|
284
|
+
window.location.href = url;
|
|
285
|
+
return;
|
|
286
|
+
}}
|
|
287
|
+
|
|
288
|
+
const content = `
|
|
289
|
+
<p style="margin-bottom: 8px;">
|
|
290
|
+
<a href="https://www.tampermonkey.net/" target="_blank" rel="noopener noreferrer">1. On mobile Safari/Firefox or any Desktop Browser install Tampermonkey</a>
|
|
291
|
+
</p>
|
|
292
|
+
<p style="margin-top: 0; margin-bottom: 8px;">
|
|
293
|
+
<a href="${{userscriptUrl}}" target="_blank">2. Install the ${{providerName}} userscript</a>
|
|
294
|
+
</p>
|
|
295
|
+
<p style="margin-top: 0; margin-bottom: 12px;">
|
|
296
|
+
3. Open link, solve CAPTCHAs, and links are automatically sent back to Quasarr!
|
|
297
|
+
</p>
|
|
298
|
+
`;
|
|
299
|
+
|
|
300
|
+
const btnId = 'modal-proceed-btn-' + Math.floor(Math.random() * 10000);
|
|
301
|
+
const buttons = `
|
|
302
|
+
<button id="${{btnId}}" class="btn-primary" disabled>Wait 5s...</button>
|
|
303
|
+
<button class="btn-secondary" onclick="closeModal()">Cancel</button>
|
|
304
|
+
`;
|
|
305
|
+
|
|
306
|
+
showModal('📦 First Time Setup', content, buttons);
|
|
307
|
+
|
|
308
|
+
let count = 5;
|
|
309
|
+
const btn = document.getElementById(btnId);
|
|
310
|
+
const interval = setInterval(() => {{
|
|
311
|
+
count--;
|
|
312
|
+
if (count <= 0) {{
|
|
313
|
+
clearInterval(interval);
|
|
314
|
+
btn.innerText = 'I have installed Tampermonkey and the userscript';
|
|
315
|
+
btn.disabled = false;
|
|
316
|
+
btn.onclick = function() {{
|
|
317
|
+
localStorage.setItem(storageKey, 'true');
|
|
318
|
+
closeModal();
|
|
319
|
+
if(typeof incrementCaptchaAttempts==='function') incrementCaptchaAttempts();
|
|
320
|
+
window.location.href = url;
|
|
321
|
+
}};
|
|
322
|
+
}} else {{
|
|
323
|
+
btn.innerText = 'Wait ' + count + 's...';
|
|
324
|
+
}}
|
|
325
|
+
}}, 1000);
|
|
326
|
+
}};
|
|
327
|
+
}}
|
|
312
328
|
</script>
|
|
313
329
|
'''
|
|
314
330
|
|
|
@@ -547,40 +563,26 @@ def setup_captcha_routes(app):
|
|
|
547
563
|
f"pkg_pass={quote(password)}"
|
|
548
564
|
)
|
|
549
565
|
|
|
566
|
+
js_url = url_with_quick_transfer_params.replace("'", "\\'")
|
|
567
|
+
storage_key = "hideFileCryptSetupInstructions"
|
|
568
|
+
provider_name = "FileCrypt"
|
|
569
|
+
userscript_url = "/captcha/filecrypt.user.js"
|
|
570
|
+
|
|
550
571
|
return f'''
|
|
551
572
|
<div class="section-divider" style="max-width: 370px; margin-left: auto; margin-right: auto;">
|
|
552
573
|
<details id="bypassDetails">
|
|
553
574
|
<summary id="bypassSummary">Show CAPTCHA Bypass</summary><br>
|
|
554
575
|
|
|
555
|
-
<!-- One-time setup section - visually separated -->
|
|
556
|
-
<div id="setup-instructions" class="setup-box">
|
|
557
|
-
<h3>📦 First Time Setup:</h3>
|
|
558
|
-
<p style="margin-bottom: 8px;">
|
|
559
|
-
<a href="https://www.tampermonkey.net/" target="_blank" rel="noopener noreferrer">1. On mobile Safari/Firefox or any Desktop Browser install Tampermonkey</a>
|
|
560
|
-
</p>
|
|
561
|
-
<p style="margin-top: 0; margin-bottom: 8px;">
|
|
562
|
-
<a href="/captcha/filecrypt.user.js" target="_blank">2. Install the FileCrypt userscript</a>
|
|
563
|
-
</p>
|
|
564
|
-
<p style="margin-top: 0; margin-bottom: 12px;">
|
|
565
|
-
3. Open link, solve CAPTCHAs, and links are automatically sent back to Quasarr!
|
|
566
|
-
</p>
|
|
567
|
-
<p style="margin-top: 0;">
|
|
568
|
-
<button id="hide-setup-btn" type="button" class="btn-subtle">
|
|
569
|
-
✅ Don't show this again
|
|
570
|
-
</button>
|
|
571
|
-
</p>
|
|
572
|
-
</div>
|
|
573
|
-
|
|
574
|
-
<!-- Hidden "show instructions" button -->
|
|
575
|
-
<div id="show-instructions-link" style="display: none; margin-bottom: 16px;">
|
|
576
|
-
<button id="show-setup-btn" type="button" class="btn-subtle">
|
|
577
|
-
ℹ️ Show setup instructions
|
|
578
|
-
</button>
|
|
579
|
-
</div>
|
|
580
|
-
|
|
581
576
|
<!-- Primary action button -->
|
|
582
577
|
<p>
|
|
583
|
-
{render_button("Open FileCrypt & Get Download Links", "primary", {"onclick": f"
|
|
578
|
+
{render_button("Open FileCrypt & Get Download Links", "primary", {"onclick": f"handleProviderClick('{js_url}', '{storage_key}', '{provider_name}', '{userscript_url}')"})}
|
|
579
|
+
</p>
|
|
580
|
+
|
|
581
|
+
<!-- Reset tutorial button -->
|
|
582
|
+
<p id="reset-tutorial-btn" style="display: none;">
|
|
583
|
+
<button type="button" class="btn-subtle" onclick="localStorage.removeItem('{storage_key}'); showModal('Tutorial Reset', '<p>Tutorial reset! Click the Open button to see it again.</p>', '<button class=\\'btn-primary\\' onclick=\\'location.reload()\\'>Reload</button>');">
|
|
584
|
+
ℹ️ Reset Setup Guide
|
|
585
|
+
</button>
|
|
584
586
|
</p>
|
|
585
587
|
|
|
586
588
|
<!-- Manual submission section -->
|
|
@@ -625,29 +627,60 @@ def setup_captcha_routes(app):
|
|
|
625
627
|
}});
|
|
626
628
|
}}
|
|
627
629
|
|
|
628
|
-
//
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
const showLink = document.getElementById('show-instructions-link');
|
|
632
|
-
|
|
633
|
-
if (hideSetup === 'true') {{
|
|
634
|
-
setupBox.style.display = 'none';
|
|
635
|
-
showLink.style.display = 'block';
|
|
630
|
+
// Show reset button if tutorial was already seen
|
|
631
|
+
if (localStorage.getItem('{storage_key}') === 'true') {{
|
|
632
|
+
document.getElementById('reset-tutorial-btn').style.display = 'block';
|
|
636
633
|
}}
|
|
637
634
|
|
|
638
|
-
//
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
635
|
+
// Global handler for provider clicks (if not already defined)
|
|
636
|
+
if (!window.handleProviderClick) {{
|
|
637
|
+
window.handleProviderClick = function(url, storageKey, providerName, userscriptUrl) {{
|
|
638
|
+
if (localStorage.getItem(storageKey) === 'true') {{
|
|
639
|
+
if(typeof incrementCaptchaAttempts==='function') incrementCaptchaAttempts();
|
|
640
|
+
window.location.href = url;
|
|
641
|
+
return;
|
|
642
|
+
}}
|
|
643
|
+
|
|
644
|
+
const content = `
|
|
645
|
+
<p style="margin-bottom: 8px;">
|
|
646
|
+
<a href="https://www.tampermonkey.net/" target="_blank" rel="noopener noreferrer">1. On mobile Safari/Firefox or any Desktop Browser install Tampermonkey</a>
|
|
647
|
+
</p>
|
|
648
|
+
<p style="margin-top: 0; margin-bottom: 8px;">
|
|
649
|
+
<a href="${{userscriptUrl}}" target="_blank">2. Install the ${{providerName}} userscript</a>
|
|
650
|
+
</p>
|
|
651
|
+
<p style="margin-top: 0; margin-bottom: 12px;">
|
|
652
|
+
3. Open link, solve CAPTCHAs, and links are automatically sent back to Quasarr!
|
|
653
|
+
</p>
|
|
654
|
+
`;
|
|
655
|
+
|
|
656
|
+
const btnId = 'modal-proceed-btn-' + Math.floor(Math.random() * 10000);
|
|
657
|
+
const buttons = `
|
|
658
|
+
<button id="${{btnId}}" class="btn-primary" disabled>Wait 5s...</button>
|
|
659
|
+
<button class="btn-secondary" onclick="closeModal()">Cancel</button>
|
|
660
|
+
`;
|
|
661
|
+
|
|
662
|
+
showModal('📦 First Time Setup', content, buttons);
|
|
663
|
+
|
|
664
|
+
let count = 5;
|
|
665
|
+
const btn = document.getElementById(btnId);
|
|
666
|
+
const interval = setInterval(() => {{
|
|
667
|
+
count--;
|
|
668
|
+
if (count <= 0) {{
|
|
669
|
+
clearInterval(interval);
|
|
670
|
+
btn.innerText = 'I have installed Tampermonkey and the userscript';
|
|
671
|
+
btn.disabled = false;
|
|
672
|
+
btn.onclick = function() {{
|
|
673
|
+
localStorage.setItem(storageKey, 'true');
|
|
674
|
+
closeModal();
|
|
675
|
+
if(typeof incrementCaptchaAttempts==='function') incrementCaptchaAttempts();
|
|
676
|
+
window.location.href = url;
|
|
677
|
+
}};
|
|
678
|
+
}} else {{
|
|
679
|
+
btn.innerText = 'Wait ' + count + 's...';
|
|
680
|
+
}}
|
|
681
|
+
}}, 1000);
|
|
682
|
+
}};
|
|
683
|
+
}}
|
|
651
684
|
</script>
|
|
652
685
|
'''
|
|
653
686
|
|
|
@@ -735,7 +768,7 @@ def setup_captcha_routes(app):
|
|
|
735
768
|
|
|
736
769
|
selected = "selected" if pkg_id == current_package_id else ""
|
|
737
770
|
# Truncate long titles for display
|
|
738
|
-
display_title =
|
|
771
|
+
display_title = title
|
|
739
772
|
options.append(
|
|
740
773
|
f'<option value="{captcha_type}|{quote(encoded)}" {selected}>{display_title}</option>'
|
|
741
774
|
)
|
|
@@ -745,7 +778,7 @@ def setup_captcha_routes(app):
|
|
|
745
778
|
return f"""
|
|
746
779
|
<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;">
|
|
747
780
|
<label for="package-select" style="display: block; margin-bottom: 8px; font-weight: bold;">📦 Select Package:</label>
|
|
748
|
-
<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;">
|
|
781
|
+
<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; text-overflow: ellipsis; white-space: nowrap; overflow: hidden;">
|
|
749
782
|
{options_html}
|
|
750
783
|
</select>
|
|
751
784
|
</div>
|
|
@@ -885,6 +918,11 @@ def setup_captcha_routes(app):
|
|
|
885
918
|
if original_url:
|
|
886
919
|
source_button = f"<p>{render_button('Source', 'secondary', {'onclick': f"window.open('{js_single_quoted_string_safe(original_url)}', '_blank')"})}</p>"
|
|
887
920
|
|
|
921
|
+
js_url = url_with_quick_transfer_params.replace("'", "\\'")
|
|
922
|
+
storage_key = "hideFileCryptFallbackSetupInstructions"
|
|
923
|
+
provider_name = "FileCrypt"
|
|
924
|
+
userscript_url = "/captcha/filecrypt.user.js"
|
|
925
|
+
|
|
888
926
|
return render_centered_html(f"""
|
|
889
927
|
<!DOCTYPE html>
|
|
890
928
|
<html>
|
|
@@ -894,35 +932,16 @@ def setup_captcha_routes(app):
|
|
|
894
932
|
{failed_warning}
|
|
895
933
|
|
|
896
934
|
<div>
|
|
897
|
-
<!-- One-time setup section - visually separated -->
|
|
898
|
-
<div id="setup-instructions" class="setup-box">
|
|
899
|
-
<h3>📦 First Time Setup:</h3>
|
|
900
|
-
<p style="margin-bottom: 8px;">
|
|
901
|
-
<a href="https://www.tampermonkey.net/" target="_blank" rel="noopener noreferrer">1. On mobile Safari/Firefox or any Desktop Browser install Tampermonkey</a>
|
|
902
|
-
</p>
|
|
903
|
-
<p style="margin-top: 0; margin-bottom: 8px;">
|
|
904
|
-
<a href="/captcha/filecrypt.user.js" target="_blank">2. Install the FileCrypt userscript</a>
|
|
905
|
-
</p>
|
|
906
|
-
<p style="margin-top: 0; margin-bottom: 12px;">
|
|
907
|
-
3. Open link, solve CAPTCHAs, and links are automatically sent back to Quasarr!
|
|
908
|
-
</p>
|
|
909
|
-
<p style="margin-top: 0;">
|
|
910
|
-
<button id="hide-setup-btn" type="button" class="btn-subtle">
|
|
911
|
-
✅ Don't show this again
|
|
912
|
-
</button>
|
|
913
|
-
</p>
|
|
914
|
-
</div>
|
|
915
|
-
|
|
916
|
-
<!-- Hidden "show instructions" button -->
|
|
917
|
-
<div id="show-instructions-link" style="display: none; margin-bottom: 16px;">
|
|
918
|
-
<button id="show-setup-btn" type="button" class="btn-subtle">
|
|
919
|
-
ℹ️ Show setup instructions
|
|
920
|
-
</button>
|
|
921
|
-
</div>
|
|
922
|
-
|
|
923
935
|
<!-- Primary action button -->
|
|
924
936
|
<p>
|
|
925
|
-
{render_button("Open FileCrypt & Get Download Links", "primary", {"onclick": f"
|
|
937
|
+
{render_button("Open FileCrypt & Get Download Links", "primary", {"onclick": f"handleProviderClick('{js_url}', '{storage_key}', '{provider_name}', '{userscript_url}')"})}
|
|
938
|
+
</p>
|
|
939
|
+
|
|
940
|
+
<!-- Reset tutorial button -->
|
|
941
|
+
<p id="reset-tutorial-btn" style="display: none;">
|
|
942
|
+
<button type="button" class="btn-subtle" onclick="localStorage.removeItem('{storage_key}'); showModal('Tutorial Reset', '<p>Tutorial reset! Click the Open button to see it again.</p>', '<button class=\\'btn-primary\\' onclick=\\'location.reload()\\'>Reload</button>');">
|
|
943
|
+
ℹ️ Reset Setup Guide
|
|
944
|
+
</button>
|
|
926
945
|
</p>
|
|
927
946
|
|
|
928
947
|
<!-- Manual submission section -->
|
|
@@ -980,29 +999,58 @@ def setup_captcha_routes(app):
|
|
|
980
999
|
}});
|
|
981
1000
|
}}
|
|
982
1001
|
|
|
983
|
-
//
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
const showLink = document.getElementById('show-instructions-link');
|
|
987
|
-
|
|
988
|
-
if (hideSetup === 'true') {{
|
|
989
|
-
setupBox.style.display = 'none';
|
|
990
|
-
showLink.style.display = 'block';
|
|
1002
|
+
// Show reset button if tutorial was already seen
|
|
1003
|
+
if (localStorage.getItem('{storage_key}') === 'true') {{
|
|
1004
|
+
document.getElementById('reset-tutorial-btn').style.display = 'block';
|
|
991
1005
|
}}
|
|
992
1006
|
|
|
993
|
-
//
|
|
994
|
-
|
|
995
|
-
localStorage.
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1007
|
+
// Global handler for provider clicks
|
|
1008
|
+
window.handleProviderClick = function(url, storageKey, providerName, userscriptUrl) {{
|
|
1009
|
+
if (localStorage.getItem(storageKey) === 'true') {{
|
|
1010
|
+
if(typeof incrementCaptchaAttempts==='function') incrementCaptchaAttempts();
|
|
1011
|
+
window.location.href = url;
|
|
1012
|
+
return;
|
|
1013
|
+
}}
|
|
1014
|
+
|
|
1015
|
+
const content = `
|
|
1016
|
+
<p style="margin-bottom: 8px;">
|
|
1017
|
+
<a href="https://www.tampermonkey.net/" target="_blank" rel="noopener noreferrer">1. On mobile Safari/Firefox or any Desktop Browser install Tampermonkey</a>
|
|
1018
|
+
</p>
|
|
1019
|
+
<p style="margin-top: 0; margin-bottom: 8px;">
|
|
1020
|
+
<a href="${{userscriptUrl}}" target="_blank">2. Install the ${{providerName}} userscript</a>
|
|
1021
|
+
</p>
|
|
1022
|
+
<p style="margin-top: 0; margin-bottom: 12px;">
|
|
1023
|
+
3. Open link, solve CAPTCHAs, and links are automatically sent back to Quasarr!
|
|
1024
|
+
</p>
|
|
1025
|
+
`;
|
|
1026
|
+
|
|
1027
|
+
const btnId = 'modal-proceed-btn-' + Math.floor(Math.random() * 10000);
|
|
1028
|
+
const buttons = `
|
|
1029
|
+
<button id="${{btnId}}" class="btn-primary" disabled>Wait 5s...</button>
|
|
1030
|
+
<button class="btn-secondary" onclick="closeModal()">Cancel</button>
|
|
1031
|
+
`;
|
|
1032
|
+
|
|
1033
|
+
showModal('📦 First Time Setup', content, buttons);
|
|
1034
|
+
|
|
1035
|
+
let count = 5;
|
|
1036
|
+
const btn = document.getElementById(btnId);
|
|
1037
|
+
const interval = setInterval(() => {{
|
|
1038
|
+
count--;
|
|
1039
|
+
if (count <= 0) {{
|
|
1040
|
+
clearInterval(interval);
|
|
1041
|
+
btn.innerText = 'I have installed Tampermonkey and the userscript';
|
|
1042
|
+
btn.disabled = false;
|
|
1043
|
+
btn.onclick = function() {{
|
|
1044
|
+
localStorage.setItem(storageKey, 'true');
|
|
1045
|
+
closeModal();
|
|
1046
|
+
if(typeof incrementCaptchaAttempts==='function') incrementCaptchaAttempts();
|
|
1047
|
+
window.location.href = url;
|
|
1048
|
+
}};
|
|
1049
|
+
}} else {{
|
|
1050
|
+
btn.innerText = 'Wait ' + count + 's...';
|
|
1051
|
+
}}
|
|
1052
|
+
}}, 1000);
|
|
1053
|
+
}};
|
|
1006
1054
|
</script>
|
|
1007
1055
|
|
|
1008
1056
|
</body>
|
|
@@ -1046,7 +1094,7 @@ def setup_captcha_routes(app):
|
|
|
1046
1094
|
decoded, -15
|
|
1047
1095
|
) # -15 = raw deflate, no zlib header
|
|
1048
1096
|
except Exception as e:
|
|
1049
|
-
|
|
1097
|
+
trace(f"Decompression error: {e}, trying with header...")
|
|
1050
1098
|
try:
|
|
1051
1099
|
# Fallback: try with zlib header
|
|
1052
1100
|
decompressed = zlib.decompress(decoded)
|
|
@@ -1070,7 +1118,9 @@ def setup_captcha_routes(app):
|
|
|
1070
1118
|
link = "https://" + link
|
|
1071
1119
|
links.append(link)
|
|
1072
1120
|
|
|
1073
|
-
|
|
1121
|
+
debug(
|
|
1122
|
+
f"Quick transfer received {len(links)} links for package {package_id}"
|
|
1123
|
+
)
|
|
1074
1124
|
|
|
1075
1125
|
# Get package info
|
|
1076
1126
|
raw_data = shared_state.get_db("protected").retrieve(package_id)
|
|
@@ -1095,7 +1145,7 @@ def setup_captcha_routes(app):
|
|
|
1095
1145
|
StatsHelper(shared_state).increment_captcha_decryptions_manual()
|
|
1096
1146
|
shared_state.get_db("protected").delete(package_id)
|
|
1097
1147
|
|
|
1098
|
-
info(f"Quick transfer successful: {len(links)} links processed")
|
|
1148
|
+
info(f"Quick transfer successful: <g>{len(links)}</g> links processed")
|
|
1099
1149
|
|
|
1100
1150
|
# Check if more CAPTCHAs remain
|
|
1101
1151
|
remaining_protected = shared_state.get_db(
|
|
@@ -1131,7 +1181,7 @@ def setup_captcha_routes(app):
|
|
|
1131
1181
|
</p>''')
|
|
1132
1182
|
|
|
1133
1183
|
except Exception as e:
|
|
1134
|
-
|
|
1184
|
+
error(f"Quick transfer error: {e}")
|
|
1135
1185
|
return render_centered_html(f'''<h1><img src="{images.logo}" type="image/png" alt="Quasarr logo" class="logo"/>Quasarr</h1>
|
|
1136
1186
|
<p><b>Error:</b> {str(e)}</p>
|
|
1137
1187
|
<p>
|
|
@@ -1656,14 +1706,13 @@ def setup_captcha_routes(app):
|
|
|
1656
1706
|
mirror = None if (mirror := data.get("mirror")) == "None" else mirror
|
|
1657
1707
|
|
|
1658
1708
|
if token:
|
|
1659
|
-
info(f"Received token: {token}")
|
|
1660
|
-
info(f"Decrypting links for {title}")
|
|
1709
|
+
info(f"Received token: {token} to decrypt links for {title}")
|
|
1661
1710
|
decrypted = get_filecrypt_links(
|
|
1662
1711
|
shared_state, token, title, link, password=password, mirror=mirror
|
|
1663
1712
|
)
|
|
1664
1713
|
if decrypted:
|
|
1665
1714
|
links = decrypted.get("links", [])
|
|
1666
|
-
info(f"Decrypted {len(links)} download links for {title}")
|
|
1715
|
+
info(f"Decrypted <g>{len(links)}</g> download links for {title}")
|
|
1667
1716
|
if not links:
|
|
1668
1717
|
raise ValueError("No download links found after decryption")
|
|
1669
1718
|
downloaded = shared_state.download_package(
|
quasarr/downloads/__init__.py
CHANGED
|
@@ -26,7 +26,7 @@ from quasarr.downloads.sources.sl import get_sl_download_links
|
|
|
26
26
|
from quasarr.downloads.sources.wd import get_wd_download_links
|
|
27
27
|
from quasarr.downloads.sources.wx import get_wx_download_links
|
|
28
28
|
from quasarr.providers.hostname_issues import clear_hostname_issue, mark_hostname_issue
|
|
29
|
-
from quasarr.providers.log import info
|
|
29
|
+
from quasarr.providers.log import info, warn
|
|
30
30
|
from quasarr.providers.notifications import send_discord_message
|
|
31
31
|
from quasarr.providers.statistics import StatsHelper
|
|
32
32
|
from quasarr.providers.utils import filter_offline_links
|
|
@@ -214,7 +214,7 @@ def handle_auto_decrypt_links(shared_state, links, title, password, package_id):
|
|
|
214
214
|
if not decrypted_urls:
|
|
215
215
|
return {"success": False, "reason": "No links decrypted"}
|
|
216
216
|
|
|
217
|
-
info(f"Decrypted {len(decrypted_urls)} download links for {title}")
|
|
217
|
+
info(f"Decrypted <g>{len(decrypted_urls)}</g> download links for {title}")
|
|
218
218
|
|
|
219
219
|
if shared_state.download_package(decrypted_urls, title, password, package_id):
|
|
220
220
|
StatsHelper(shared_state).increment_package_with_links(decrypted_urls)
|
|
@@ -239,7 +239,7 @@ def store_protected_links(
|
|
|
239
239
|
package_id, json.dumps(blob_data)
|
|
240
240
|
)
|
|
241
241
|
info(
|
|
242
|
-
f'CAPTCHA-Solution required for
|
|
242
|
+
f'CAPTCHA-Solution required for <b>{title}</b> at: "{shared_state.values["external_address"]}/captcha"'
|
|
243
243
|
)
|
|
244
244
|
return {"success": True}
|
|
245
245
|
|
|
@@ -305,7 +305,9 @@ def process_links(
|
|
|
305
305
|
|
|
306
306
|
# PRIORITY 1: Direct hoster links
|
|
307
307
|
if classified["direct"]:
|
|
308
|
-
info(
|
|
308
|
+
info(
|
|
309
|
+
f"Found <g>{len(classified['direct'])}</g> direct hoster links for {title}"
|
|
310
|
+
)
|
|
309
311
|
send_discord_message(
|
|
310
312
|
shared_state,
|
|
311
313
|
title=title,
|
|
@@ -322,7 +324,9 @@ def process_links(
|
|
|
322
324
|
|
|
323
325
|
# PRIORITY 2: Auto-decryptable (hide.cx)
|
|
324
326
|
if classified["auto"]:
|
|
325
|
-
info(
|
|
327
|
+
info(
|
|
328
|
+
f"Found <g>{len(classified['auto'])}</g> auto-decryptable links for {title}"
|
|
329
|
+
)
|
|
326
330
|
result = handle_auto_decrypt_links(
|
|
327
331
|
shared_state, classified["auto"], title, password, package_id
|
|
328
332
|
)
|
|
@@ -340,7 +344,7 @@ def process_links(
|
|
|
340
344
|
|
|
341
345
|
# PRIORITY 3: Protected (filecrypt, tolink, keeplinks, junkies)
|
|
342
346
|
if classified["protected"]:
|
|
343
|
-
info(f"Found {len(classified['protected'])} protected links for {title}")
|
|
347
|
+
info(f"Found <g>{len(classified['protected'])}</g> protected links for {title}")
|
|
344
348
|
send_discord_message(
|
|
345
349
|
shared_state,
|
|
346
350
|
title=title,
|
|
@@ -363,7 +367,7 @@ def process_links(
|
|
|
363
367
|
title,
|
|
364
368
|
package_id,
|
|
365
369
|
shared_state,
|
|
366
|
-
reason=f'No usable links found for
|
|
370
|
+
reason=f'No usable links found for {title} on {label} - "{source_url}"',
|
|
367
371
|
)
|
|
368
372
|
|
|
369
373
|
|
|
@@ -461,7 +465,7 @@ def download(
|
|
|
461
465
|
|
|
462
466
|
# Skip Download if package_id already exists
|
|
463
467
|
if package_id_exists(shared_state, package_id):
|
|
464
|
-
|
|
468
|
+
warn(f"Package {package_id} already exists. Skipping download!")
|
|
465
469
|
return {"success": True, "package_id": package_id, "title": title}
|
|
466
470
|
|
|
467
471
|
if source_result is None:
|
|
@@ -189,8 +189,8 @@ def solve_captcha(
|
|
|
189
189
|
|
|
190
190
|
try:
|
|
191
191
|
image_ids = result["json"]
|
|
192
|
-
except ValueError:
|
|
193
|
-
raise RuntimeError(f"Cannot decode captcha IDs: {result['text']}")
|
|
192
|
+
except ValueError as e:
|
|
193
|
+
raise RuntimeError(f"Cannot decode captcha IDs: {result['text']}") from e
|
|
194
194
|
|
|
195
195
|
if not isinstance(image_ids, list) or len(image_ids) < 2:
|
|
196
196
|
raise RuntimeError("Unexpected captcha IDs format.")
|
|
@@ -233,7 +233,7 @@ def solve_captcha(
|
|
|
233
233
|
images_pixel_differences = []
|
|
234
234
|
for idx_i, (img_id_i, img_i) in enumerate(image_objects):
|
|
235
235
|
total_difference = 0.0
|
|
236
|
-
for idx_j, (
|
|
236
|
+
for idx_j, (_img_id_j, img_j) in enumerate(image_objects):
|
|
237
237
|
if idx_i == idx_j:
|
|
238
238
|
continue # skip self-comparison
|
|
239
239
|
total_difference += calculate_pixel_based_difference(img_i, img_j)
|
|
@@ -254,7 +254,7 @@ def solve_captcha(
|
|
|
254
254
|
method="POST",
|
|
255
255
|
target_url=captcha_base,
|
|
256
256
|
post_data={"cID": 0, "pC": identified_captcha_image, "rT": 2},
|
|
257
|
-
timeout=
|
|
257
|
+
timeout=30,
|
|
258
258
|
)
|
|
259
259
|
|
|
260
260
|
return {"response": result["text"], "captcha_id": identified_captcha_image}
|
|
@@ -228,10 +228,9 @@ def get_filecrypt_links(shared_state, token, title, url, password=None, mirror=N
|
|
|
228
228
|
return False
|
|
229
229
|
|
|
230
230
|
if output.status_code == 403 or is_cloudflare_challenge(output.text):
|
|
231
|
-
|
|
231
|
+
debug(
|
|
232
232
|
"Encountered Cloudflare after password POST. Re-running FlareSolverr..."
|
|
233
233
|
)
|
|
234
|
-
debug("Cloudflare reappeared after password submit, retrying bypass.")
|
|
235
234
|
session, headers, output = ensure_session_cf_bypassed(
|
|
236
235
|
info, shared_state, session, output.url, headers
|
|
237
236
|
)
|