quasarr 1.22.0__py3-none-any.whl → 1.24.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.

@@ -8,16 +8,15 @@ from base64 import urlsafe_b64encode, urlsafe_b64decode
8
8
  from urllib.parse import quote, unquote, urljoin
9
9
 
10
10
  import requests
11
- from bottle import request, response, redirect
11
+ from bottle import request, response, redirect, HTTPResponse
12
12
 
13
13
  import quasarr.providers.html_images as images
14
14
  from quasarr.downloads.linkcrypters.filecrypt import get_filecrypt_links, DLC
15
15
  from quasarr.downloads.packages import delete_package
16
+ from quasarr.providers import obfuscated
16
17
  from quasarr.providers import shared_state
17
18
  from quasarr.providers.html_templates import render_button, render_centered_html
18
19
  from quasarr.providers.log import info, debug
19
- from quasarr.providers.obfuscated import captcha_js, captcha_values, filecrypt_user_js, junkies_user_js, \
20
- keeplinks_user_js
21
20
  from quasarr.providers.statistics import StatsHelper
22
21
 
23
22
 
@@ -25,6 +24,21 @@ def js_single_quoted_string_safe(text):
25
24
  return text.replace('\\', '\\\\').replace("'", "\\'")
26
25
 
27
26
 
27
+ def check_package_exists(package_id):
28
+ if not shared_state.get_db("protected").retrieve(package_id):
29
+ raise HTTPResponse(
30
+ status=404,
31
+ body=render_centered_html(f'''
32
+ <h1><img src="{images.logo}" class="logo"/>Quasarr</h1>
33
+ <p><b>Error:</b> Package not found or already solved.</p>
34
+ <p>
35
+ {render_button("Back", "secondary", {"onclick": "location.href='/captcha'"})}
36
+ </p>
37
+ '''),
38
+ content_type="text/html"
39
+ )
40
+
41
+
28
42
  def setup_captcha_routes(app):
29
43
  @app.get('/captcha')
30
44
  def check_captcha():
@@ -83,10 +97,16 @@ def setup_captcha_routes(app):
83
97
 
84
98
  sj = shared_state.values["config"]("Hostnames").get("sj")
85
99
  dj = shared_state.values["config"]("Hostnames").get("dj")
86
- has_junkies_links = any(
87
- (sj and sj in link) or (dj and dj in link)
88
- for link in prioritized_links
89
- )
100
+
101
+ def is_junkies_link(link):
102
+ """Check if link is a junkies link (handles [[url, mirror]] format)."""
103
+ url = link[0] if isinstance(link, (list, tuple)) else link
104
+ mirror = link[1] if isinstance(link, (list, tuple)) and len(link) > 1 else ""
105
+ if mirror == "junkies":
106
+ return True
107
+ return (sj and sj in url) or (dj and dj in url)
108
+
109
+ has_junkies_links = any(is_junkies_link(link) for link in prioritized_links)
90
110
 
91
111
  # KeepLinks uses nested arrays like FileCrypt: [["url", "mirror"]]
92
112
  has_keeplinks_links = any(
@@ -94,12 +114,21 @@ def setup_captcha_routes(app):
94
114
  for link in prioritized_links
95
115
  )
96
116
 
117
+ # ToLink uses nested arrays like FileCrypt: [["url", "mirror"]]
118
+ has_tolink_links = any(
119
+ ("tolink." in link[0] if isinstance(link, (list, tuple)) else "tolink." in link)
120
+ for link in prioritized_links
121
+ )
122
+
97
123
  if has_junkies_links:
98
124
  debug("Redirecting to Junkies CAPTCHA")
99
125
  redirect(f"/captcha/junkies?data={quote(encoded_payload)}")
100
126
  elif has_keeplinks_links:
101
127
  debug("Redirecting to KeepLinks CAPTCHA")
102
128
  redirect(f"/captcha/keeplinks?data={quote(encoded_payload)}")
129
+ elif has_tolink_links:
130
+ debug("Redirecting to ToLink CAPTCHA")
131
+ redirect(f"/captcha/tolink?data={quote(encoded_payload)}")
103
132
  elif filecrypt_session:
104
133
  debug(f'Redirecting to circle CAPTCHA')
105
134
  redirect(f"/captcha/circle?data={quote(encoded_payload)}")
@@ -122,7 +151,7 @@ def setup_captcha_routes(app):
122
151
  return {"error": f"Failed to decode payload: {str(e)}"}
123
152
 
124
153
  def render_userscript_section(url, package_id, title, password, provider_type="junkies"):
125
- """Render the userscript UI section for Junkies or KeepLinks pages
154
+ """Render the userscript UI section for Junkies, KeepLinks, or ToLink pages
126
155
 
127
156
  This is the MAIN solution for these providers (not a bypass/fallback).
128
157
 
@@ -131,10 +160,11 @@ def setup_captcha_routes(app):
131
160
  package_id: Package identifier
132
161
  title: Package title
133
162
  password: Package password
134
- provider_type: Either "junkies" or "keeplinks"
163
+ provider_type: Either "junkies", "keeplinks", or "tolink"
135
164
  """
136
165
 
137
- provider_name = "Junkies" if provider_type == "junkies" else "KeepLinks"
166
+ provider_names = {"junkies": "Junkies", "keeplinks": "KeepLinks", "tolink": "ToLink"}
167
+ provider_name = provider_names.get(provider_type, "Provider")
138
168
  userscript_url = f"/captcha/{provider_type}.user.js"
139
169
  storage_key = f"hide{provider_name}SetupInstructions"
140
170
 
@@ -173,7 +203,7 @@ def setup_captcha_routes(app):
173
203
  <a href="#" id="show-setup-btn" style="color: #58a6ff;">ℹ️ Show instructions again</a>
174
204
  </div>
175
205
 
176
- <strong><a href="{url_with_quick_transfer_params}" target="_blank">🔗 Obtain the download links here!</a></strong><br><br>
206
+ <strong><a href="{url_with_quick_transfer_params}" target="_self">🔗 Obtain the download links here!</a></strong><br><br>
177
207
 
178
208
  <form id="bypass-form" action="/captcha/bypass-submit" method="post" enctype="multipart/form-data">
179
209
  <input type="hidden" name="package_id" value="{package_id}" />
@@ -233,7 +263,9 @@ def setup_captcha_routes(app):
233
263
  title = payload.get("title")
234
264
  password = payload.get("password")
235
265
  urls = payload.get("links")
236
- url = urls[0]
266
+ url = urls[0][0] if isinstance(urls[0], (list, tuple)) else urls[0]
267
+
268
+ check_package_exists(package_id)
237
269
 
238
270
  return render_centered_html(f"""
239
271
  <!DOCTYPE html>
@@ -267,7 +299,9 @@ def setup_captcha_routes(app):
267
299
  title = payload.get("title")
268
300
  password = payload.get("password")
269
301
  urls = payload.get("links")
270
- # KeepLinks uses nested arrays like FileCrypt: [["url", "mirror"]]
302
+
303
+ check_package_exists(package_id)
304
+
271
305
  url = urls[0][0] if isinstance(urls[0], (list, tuple)) else urls[0]
272
306
 
273
307
  return render_centered_html(f"""
@@ -287,24 +321,67 @@ def setup_captcha_routes(app):
287
321
  </body>
288
322
  </html>""")
289
323
 
324
+ @app.get("/captcha/tolink")
325
+ def serve_tolink_captcha():
326
+ payload = decode_payload()
327
+
328
+ if "error" in payload:
329
+ return render_centered_html(f'''<h1><img src="{images.logo}" type="image/png" alt="Quasarr logo" class="logo"/>Quasarr</h1>
330
+ <p>{payload["error"]}</p>
331
+ <p>
332
+ {render_button("Back", "secondary", {"onclick": "location.href='/'"})}
333
+ </p>''')
334
+
335
+ package_id = payload.get("package_id")
336
+ title = payload.get("title")
337
+ password = payload.get("password")
338
+ urls = payload.get("links")
339
+
340
+ check_package_exists(package_id)
341
+
342
+ url = urls[0][0] if isinstance(urls[0], (list, tuple)) else urls[0]
343
+
344
+ return render_centered_html(f"""
345
+ <!DOCTYPE html>
346
+ <html>
347
+ <body>
348
+ <h1><img src="{images.logo}" type="image/png" alt="Quasarr logo" class="logo"/>Quasarr</h1>
349
+ <p><b>Package:</b> {title}</p>
350
+ {render_userscript_section(url, package_id, title, password, "tolink")}
351
+ <p>
352
+ {render_button("Delete Package", "secondary", {"onclick": f"location.href='/captcha/delete/{package_id}'"})}
353
+ </p>
354
+ <p>
355
+ {render_button("Back", "secondary", {"onclick": "location.href='/'"})}
356
+ </p>
357
+
358
+ </body>
359
+ </html>""")
360
+
290
361
  @app.get('/captcha/junkies.user.js')
291
362
  def serve_junkies_user_js():
292
363
  sj = shared_state.values["config"]("Hostnames").get("sj")
293
364
  dj = shared_state.values["config"]("Hostnames").get("dj")
294
365
 
295
- content = junkies_user_js(sj, dj)
366
+ content = obfuscated.junkies_user_js(sj, dj)
296
367
  response.content_type = 'application/javascript'
297
368
  return content
298
369
 
299
370
  @app.get('/captcha/keeplinks.user.js')
300
371
  def serve_keeplinks_user_js():
301
- content = keeplinks_user_js()
372
+ content = obfuscated.keeplinks_user_js()
373
+ response.content_type = 'application/javascript'
374
+ return content
375
+
376
+ @app.get('/captcha/tolink.user.js')
377
+ def serve_tolink_user_js():
378
+ content = obfuscated.tolink_user_js()
302
379
  response.content_type = 'application/javascript'
303
380
  return content
304
381
 
305
382
  @app.get('/captcha/filecrypt.user.js')
306
383
  def serve_filecrypt_user_js():
307
- content = filecrypt_user_js()
384
+ content = obfuscated.filecrypt_user_js()
308
385
  response.content_type = 'application/javascript'
309
386
  return content
310
387
 
@@ -350,7 +427,7 @@ def setup_captcha_routes(app):
350
427
  <a href="#" id="show-setup-btn" style="color: #58a6ff;">ℹ️ Show instructions again</a>
351
428
  </div>
352
429
 
353
- <strong><a href="{url_with_quick_transfer_params}" target="_blank">🔗 Obtain the download links here!</a></strong><br><br>
430
+ <strong><a href="{url_with_quick_transfer_params}" target="_self">🔗 Obtain the download links here!</a></strong><br><br>
354
431
 
355
432
  <form id="bypass-form" action="/captcha/bypass-submit" method="post" enctype="multipart/form-data">
356
433
  <input type="hidden" name="package_id" value="{package_id}" />
@@ -451,12 +528,12 @@ def setup_captcha_routes(app):
451
528
  try:
452
529
  decompressed = zlib.decompress(decoded, -15) # -15 = raw deflate, no zlib header
453
530
  except Exception as e:
454
- info(f"Decompression error: {e}, trying with header...")
531
+ debug(f"Decompression error: {e}, trying with header...")
455
532
  try:
456
533
  # Fallback: try with zlib header
457
534
  decompressed = zlib.decompress(decoded)
458
535
  except Exception as e2:
459
- info(f"Decompression also failed with header: {e2}")
536
+ info(f"Decompression failed without and with header: {e2}")
460
537
  return render_centered_html(f'''<h1><img src="{images.logo}" type="image/png" alt="Quasarr logo" class="logo"/>Quasarr</h1>
461
538
  <p><b>Error:</b> Failed to decompress data: {str(e)}</p>
462
539
  <p>
@@ -585,6 +662,8 @@ def setup_captcha_routes(app):
585
662
  desired_mirror = payload.get("mirror")
586
663
  prioritized_links = payload.get("links")
587
664
 
665
+ check_package_exists(package_id)
666
+
588
667
  if not prioritized_links:
589
668
  # No links found, show an error message
590
669
  return render_centered_html(f'''
@@ -631,7 +710,7 @@ def setup_captcha_routes(app):
631
710
 
632
711
  content = render_centered_html(r'''
633
712
  <script type="text/javascript">
634
- var api_key = "''' + captcha_values()["api_key"] + r'''";
713
+ var api_key = "''' + obfuscated.captcha_values()["api_key"] + r'''";
635
714
  var endpoint = '/' + window.location.pathname.split('/')[1] + '/' + api_key + '.html';
636
715
  var solveAnotherHtml = `<p>''' + solve_another_html + r'''</p><p>''' + back_button_html + r'''</p>`;
637
716
  var noMoreHtml = `<p><b>No more CAPTCHAs</b></p><p>''' + back_button_html + r'''</p>`;
@@ -685,7 +764,7 @@ def setup_captcha_routes(app):
685
764
  reloadSection.style.display = "block";
686
765
  });
687
766
  }
688
- ''' + captcha_js() + f'''</script>
767
+ ''' + obfuscated.captcha_js() + f'''</script>
689
768
  <div>
690
769
  <h1><img src="{images.logo}" type="image/png" alt="Quasarr logo" class="logo"/>Quasarr</h1>
691
770
  <p id="package-title" style="max-width: 370px; word-wrap: break-word; overflow-wrap: break-word;"><b>Package:</b> {title}</p>
@@ -720,7 +799,7 @@ def setup_captcha_routes(app):
720
799
 
721
800
  @app.post('/captcha/<captcha_id>.html')
722
801
  def proxy_html(captcha_id):
723
- target_url = f"{captcha_values()["url"]}/captcha/{captcha_id}.html"
802
+ target_url = f"{obfuscated.captcha_values()["url"]}/captcha/{captcha_id}.html"
724
803
 
725
804
  headers = {key: value for key, value in request.headers.items() if key != 'Host'}
726
805
  data = request.body.read()
@@ -740,7 +819,7 @@ def setup_captcha_routes(app):
740
819
 
741
820
  @app.post('/captcha/<captcha_id>.json')
742
821
  def proxy_json(captcha_id):
743
- target_url = f"{captcha_values()["url"]}/captcha/{captcha_id}.json"
822
+ target_url = f"{obfuscated.captcha_values()["url"]}/captcha/{captcha_id}.json"
744
823
 
745
824
  headers = {key: value for key, value in request.headers.items() if key != 'Host'}
746
825
  data = request.body.read()
@@ -751,7 +830,7 @@ def setup_captcha_routes(app):
751
830
 
752
831
  @app.get('/captcha/js/<filename>')
753
832
  def serve_local_js(filename):
754
- upstream = f"{captcha_values()['url']}/{filename}"
833
+ upstream = f"{obfuscated.captcha_values()['url']}/{filename}"
755
834
  try:
756
835
  upstream_resp = requests.get(upstream, verify=False, stream=True)
757
836
  upstream_resp.raise_for_status()
@@ -764,7 +843,7 @@ def setup_captcha_routes(app):
764
843
 
765
844
  @app.get('/captcha/<captcha_id>/<uuid>/<filename>')
766
845
  def proxy_pngs(captcha_id, uuid, filename):
767
- new_url = f"{captcha_values()["url"]}/captcha/{captcha_id}/{uuid}/{filename}"
846
+ new_url = f"{obfuscated.captcha_values()["url"]}/captcha/{captcha_id}/{uuid}/{filename}"
768
847
 
769
848
  try:
770
849
  external_response = requests.get(new_url, stream=True, verify=False)
@@ -779,7 +858,7 @@ def setup_captcha_routes(app):
779
858
 
780
859
  @app.post('/captcha/<captcha_id>/check')
781
860
  def proxy_check(captcha_id):
782
- new_url = f"{captcha_values()["url"]}/captcha/{captcha_id}/check"
861
+ new_url = f"{obfuscated.captcha_values()["url"]}/captcha/{captcha_id}/check"
783
862
  headers = {key: value for key, value in request.headers.items()}
784
863
 
785
864
  data = request.body.read()
@@ -808,13 +887,7 @@ def setup_captcha_routes(app):
808
887
  {render_button("Back", "secondary", {"onclick": "location.href='/captcha'"})}
809
888
  </p>''')
810
889
 
811
- package_exists = shared_state.get_db("protected").retrieve(package_id)
812
- if not package_exists:
813
- return render_centered_html(f'''<h1><img src="{images.logo}" type="image/png" alt="Quasarr logo" class="logo"/>Quasarr</h1>
814
- <p><b>Error:</b> Package not found or already solved.</p>
815
- <p>
816
- {render_button("Back", "secondary", {"onclick": "location.href='/captcha'"})}
817
- </p>''')
890
+ check_package_exists(package_id)
818
891
 
819
892
  # Process links input
820
893
  if links_input:
@@ -991,6 +1064,8 @@ def setup_captcha_routes(app):
991
1064
  original_url = payload.get("original_url", "")
992
1065
  url = payload.get("links")[0] if payload.get("links") else None
993
1066
 
1067
+ check_package_exists(package_id)
1068
+
994
1069
  if not url or not session_id or not package_id:
995
1070
  response.status = 400
996
1071
  return "Missing required parameters"