quasarr 2.6.0__py3-none-any.whl → 2.7.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/__init__.py +71 -61
- quasarr/api/__init__.py +3 -4
- quasarr/api/arr/__init__.py +159 -56
- quasarr/api/captcha/__init__.py +203 -154
- quasarr/api/config/__init__.py +1 -1
- quasarr/api/jdownloader/__init__.py +19 -12
- quasarr/downloads/__init__.py +12 -8
- quasarr/downloads/linkcrypters/al.py +3 -3
- 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 +132 -62
- quasarr/downloads/sources/wx.py +11 -17
- quasarr/providers/auth.py +9 -13
- quasarr/providers/cloudflare.py +50 -4
- quasarr/providers/imdb_metadata.py +0 -2
- 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 +91 -78
- 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 +63 -9
- quasarr/search/sources/wx.py +33 -47
- quasarr/storage/config.py +1 -3
- quasarr/storage/setup.py +13 -4
- {quasarr-2.6.0.dist-info → quasarr-2.7.0.dist-info}/METADATA +4 -1
- quasarr-2.7.0.dist-info/RECORD +84 -0
- quasarr-2.6.0.dist-info/RECORD +0 -84
- {quasarr-2.6.0.dist-info → quasarr-2.7.0.dist-info}/WHEEL +0 -0
- {quasarr-2.6.0.dist-info → quasarr-2.7.0.dist-info}/entry_points.txt +0 -0
- {quasarr-2.6.0.dist-info → quasarr-2.7.0.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/api/config/__init__.py
CHANGED
|
@@ -16,9 +16,14 @@ def get_jdownloader_status(shared_state):
|
|
|
16
16
|
|
|
17
17
|
jd_config = Config("JDownloader")
|
|
18
18
|
jd_device = jd_config.get("device") or ""
|
|
19
|
-
|
|
19
|
+
|
|
20
20
|
dev_name = jd_device if jd_device else "JDownloader"
|
|
21
|
-
dev_name_safe =
|
|
21
|
+
dev_name_safe = (
|
|
22
|
+
dev_name.replace("&", "&")
|
|
23
|
+
.replace("<", "<")
|
|
24
|
+
.replace(">", ">")
|
|
25
|
+
.replace('"', """)
|
|
26
|
+
)
|
|
22
27
|
|
|
23
28
|
if jd_connected:
|
|
24
29
|
status_text = f"✅ {dev_name_safe} connected"
|
|
@@ -34,7 +39,7 @@ def get_jdownloader_status(shared_state):
|
|
|
34
39
|
"connected": jd_connected,
|
|
35
40
|
"device_name": jd_device,
|
|
36
41
|
"status_text": status_text,
|
|
37
|
-
"status_class": status_class
|
|
42
|
+
"status_class": status_class,
|
|
38
43
|
}
|
|
39
44
|
|
|
40
45
|
|
|
@@ -44,7 +49,7 @@ def get_jdownloader_modal_script():
|
|
|
44
49
|
jd_user = jd_config.get("user") or ""
|
|
45
50
|
jd_pass = jd_config.get("password") or ""
|
|
46
51
|
jd_device = jd_config.get("device") or ""
|
|
47
|
-
|
|
52
|
+
|
|
48
53
|
jd_user_js = jd_user.replace("\\", "\\\\").replace("'", "\\'")
|
|
49
54
|
jd_pass_js = jd_pass.replace("\\", "\\\\").replace("'", "\\'")
|
|
50
55
|
jd_device_js = jd_device.replace("\\", "\\\\").replace("'", "\\'")
|
|
@@ -166,13 +171,13 @@ def get_jdownloader_modal_script():
|
|
|
166
171
|
def get_jdownloader_status_pill(shared_state):
|
|
167
172
|
"""Return the HTML for the JDownloader status pill."""
|
|
168
173
|
status = get_jdownloader_status(shared_state)
|
|
169
|
-
|
|
174
|
+
|
|
170
175
|
return f"""
|
|
171
|
-
<span class="status-pill {status[
|
|
176
|
+
<span class="status-pill {status["status_class"]}"
|
|
172
177
|
onclick="openJDownloaderModal()"
|
|
173
178
|
style="cursor: pointer;"
|
|
174
179
|
title="Click to configure JDownloader">
|
|
175
|
-
{status[
|
|
180
|
+
{status["status_text"]}
|
|
176
181
|
</span>
|
|
177
182
|
"""
|
|
178
183
|
|
|
@@ -181,12 +186,14 @@ def get_jdownloader_disconnected_page(shared_state, back_url="/"):
|
|
|
181
186
|
"""Return a full error page when JDownloader is disconnected."""
|
|
182
187
|
import quasarr.providers.html_images as images
|
|
183
188
|
from quasarr.providers.html_templates import render_centered_html
|
|
184
|
-
|
|
189
|
+
|
|
185
190
|
status_pill = get_jdownloader_status_pill(shared_state)
|
|
186
191
|
modal_script = get_jdownloader_modal_script()
|
|
187
|
-
|
|
188
|
-
back_btn = render_button(
|
|
189
|
-
|
|
192
|
+
|
|
193
|
+
back_btn = render_button(
|
|
194
|
+
"Back", "secondary", {"onclick": f"location.href='{back_url}'"}
|
|
195
|
+
)
|
|
196
|
+
|
|
190
197
|
content = f'''
|
|
191
198
|
<h1><img src="{images.logo}" type="image/png" alt="Quasarr logo" class="logo"/>Quasarr</h1>
|
|
192
199
|
<div class="status-bar">
|
|
@@ -228,5 +235,5 @@ def get_jdownloader_disconnected_page(shared_state, back_url="/"):
|
|
|
228
235
|
</style>
|
|
229
236
|
{modal_script}
|
|
230
237
|
'''
|
|
231
|
-
|
|
238
|
+
|
|
232
239
|
return render_centered_html(content)
|
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:
|