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.

Files changed (54) hide show
  1. quasarr/__init__.py +71 -61
  2. quasarr/api/__init__.py +1 -2
  3. quasarr/api/arr/__init__.py +66 -57
  4. quasarr/api/captcha/__init__.py +203 -154
  5. quasarr/downloads/__init__.py +12 -8
  6. quasarr/downloads/linkcrypters/al.py +4 -4
  7. quasarr/downloads/linkcrypters/filecrypt.py +1 -2
  8. quasarr/downloads/packages/__init__.py +62 -88
  9. quasarr/downloads/sources/al.py +3 -3
  10. quasarr/downloads/sources/by.py +3 -3
  11. quasarr/downloads/sources/he.py +8 -9
  12. quasarr/downloads/sources/nk.py +3 -3
  13. quasarr/downloads/sources/sl.py +6 -1
  14. quasarr/downloads/sources/wd.py +93 -37
  15. quasarr/downloads/sources/wx.py +11 -17
  16. quasarr/providers/auth.py +9 -13
  17. quasarr/providers/cloudflare.py +5 -4
  18. quasarr/providers/imdb_metadata.py +1 -3
  19. quasarr/providers/jd_cache.py +64 -90
  20. quasarr/providers/log.py +226 -8
  21. quasarr/providers/myjd_api.py +116 -94
  22. quasarr/providers/sessions/al.py +20 -22
  23. quasarr/providers/sessions/dd.py +1 -1
  24. quasarr/providers/sessions/dl.py +8 -10
  25. quasarr/providers/sessions/nx.py +1 -1
  26. quasarr/providers/shared_state.py +26 -15
  27. quasarr/providers/utils.py +15 -6
  28. quasarr/providers/version.py +1 -1
  29. quasarr/search/__init__.py +113 -82
  30. quasarr/search/sources/al.py +19 -23
  31. quasarr/search/sources/by.py +6 -6
  32. quasarr/search/sources/dd.py +8 -10
  33. quasarr/search/sources/dj.py +15 -18
  34. quasarr/search/sources/dl.py +25 -37
  35. quasarr/search/sources/dt.py +13 -15
  36. quasarr/search/sources/dw.py +24 -16
  37. quasarr/search/sources/fx.py +25 -11
  38. quasarr/search/sources/he.py +16 -14
  39. quasarr/search/sources/hs.py +7 -7
  40. quasarr/search/sources/mb.py +7 -7
  41. quasarr/search/sources/nk.py +24 -25
  42. quasarr/search/sources/nx.py +22 -15
  43. quasarr/search/sources/sf.py +18 -9
  44. quasarr/search/sources/sj.py +7 -7
  45. quasarr/search/sources/sl.py +26 -14
  46. quasarr/search/sources/wd.py +61 -31
  47. quasarr/search/sources/wx.py +33 -47
  48. quasarr/storage/config.py +1 -3
  49. {quasarr-2.6.1.dist-info → quasarr-2.7.1.dist-info}/METADATA +4 -1
  50. quasarr-2.7.1.dist-info/RECORD +84 -0
  51. quasarr-2.6.1.dist-info/RECORD +0 -84
  52. {quasarr-2.6.1.dist-info → quasarr-2.7.1.dist-info}/WHEEL +0 -0
  53. {quasarr-2.6.1.dist-info → quasarr-2.7.1.dist-info}/entry_points.txt +0 -0
  54. {quasarr-2.6.1.dist-info → quasarr-2.7.1.dist-info}/licenses/LICENSE +0 -0
@@ -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"if(typeof incrementCaptchaAttempts==='function')incrementCaptchaAttempts();location.href='{url_with_quick_transfer_params}'"})}
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
- // Handle setup instructions hide/show
290
- const hideSetup = localStorage.getItem('{storage_key}');
291
- const setupBox = document.getElementById('setup-instructions');
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
- // Hide setup instructions
300
- document.getElementById('hide-setup-btn').addEventListener('click', function() {{
301
- localStorage.setItem('{storage_key}', 'true');
302
- setupBox.style.display = 'none';
303
- showLink.style.display = 'block';
304
- }});
305
-
306
- // Show setup instructions again
307
- document.getElementById('show-setup-btn').addEventListener('click', function() {{
308
- localStorage.setItem('{storage_key}', 'false');
309
- setupBox.style.display = 'block';
310
- showLink.style.display = 'none';
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"if(typeof incrementCaptchaAttempts==='function')incrementCaptchaAttempts();location.href='{url_with_quick_transfer_params}'"})}
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
- // Handle setup instructions hide/show
629
- const hideSetup = localStorage.getItem('hideFileCryptSetupInstructions');
630
- const setupBox = document.getElementById('setup-instructions');
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
- // Hide setup instructions
639
- document.getElementById('hide-setup-btn').addEventListener('click', function() {{
640
- localStorage.setItem('hideFileCryptSetupInstructions', 'true');
641
- setupBox.style.display = 'none';
642
- showLink.style.display = 'block';
643
- }});
644
-
645
- // Show setup instructions again
646
- document.getElementById('show-setup-btn').addEventListener('click', function() {{
647
- localStorage.setItem('hideFileCryptSetupInstructions', 'false');
648
- setupBox.style.display = 'block';
649
- showLink.style.display = 'none';
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 = (title[:50] + "...") if len(title) > 53 else 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"if(typeof incrementCaptchaAttempts==='function')incrementCaptchaAttempts();location.href='{url_with_quick_transfer_params}'"})}
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
- // Handle setup instructions hide/show
984
- const hideSetup = localStorage.getItem('hideFileCryptFallbackSetupInstructions');
985
- const setupBox = document.getElementById('setup-instructions');
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
- // Hide setup instructions
994
- document.getElementById('hide-setup-btn').addEventListener('click', function() {{
995
- localStorage.setItem('hideFileCryptFallbackSetupInstructions', 'true');
996
- setupBox.style.display = 'none';
997
- showLink.style.display = 'block';
998
- }});
999
-
1000
- // Show setup instructions again
1001
- document.getElementById('show-setup-btn').addEventListener('click', function() {{
1002
- localStorage.setItem('hideFileCryptFallbackSetupInstructions', 'false');
1003
- setupBox.style.display = 'block';
1004
- showLink.style.display = 'none';
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
- debug(f"Decompression error: {e}, trying with header...")
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
- info(f"Quick transfer received {len(links)} links for package {package_id}")
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
- info(f"Quick transfer error: {e}")
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(
@@ -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 "{title}" at: "{shared_state.values["external_address"]}/captcha"'
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(f"Found {len(classified['direct'])} direct hoster links for {title}")
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(f"Found {len(classified['auto'])} auto-decryptable links for {title}")
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 "{title}" on {label} - "{source_url}"',
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
- info(f"Package {package_id} already exists. Skipping download!")
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, (img_id_j, img_j) in enumerate(image_objects):
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=60,
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
- info(
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
  )