quasarr 2.3.1__tar.gz → 2.3.3__tar.gz

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 (89) hide show
  1. {quasarr-2.3.1 → quasarr-2.3.3}/PKG-INFO +1 -1
  2. {quasarr-2.3.1 → quasarr-2.3.3}/quasarr/__init__.py +24 -6
  3. {quasarr-2.3.1 → quasarr-2.3.3}/quasarr/api/config/__init__.py +2 -2
  4. {quasarr-2.3.1 → quasarr-2.3.3}/quasarr/api/sponsors_helper/__init__.py +25 -37
  5. quasarr-2.3.3/quasarr/providers/imdb_metadata.py +577 -0
  6. {quasarr-2.3.1 → quasarr-2.3.3}/quasarr/providers/version.py +1 -1
  7. {quasarr-2.3.1 → quasarr-2.3.3}/quasarr.egg-info/PKG-INFO +1 -1
  8. quasarr-2.3.1/quasarr/providers/imdb_metadata.py +0 -353
  9. {quasarr-2.3.1 → quasarr-2.3.3}/LICENSE +0 -0
  10. {quasarr-2.3.1 → quasarr-2.3.3}/README.md +0 -0
  11. {quasarr-2.3.1 → quasarr-2.3.3}/quasarr/api/__init__.py +0 -0
  12. {quasarr-2.3.1 → quasarr-2.3.3}/quasarr/api/arr/__init__.py +0 -0
  13. {quasarr-2.3.1 → quasarr-2.3.3}/quasarr/api/captcha/__init__.py +0 -0
  14. {quasarr-2.3.1 → quasarr-2.3.3}/quasarr/api/packages/__init__.py +0 -0
  15. {quasarr-2.3.1 → quasarr-2.3.3}/quasarr/api/statistics/__init__.py +0 -0
  16. {quasarr-2.3.1 → quasarr-2.3.3}/quasarr/downloads/__init__.py +0 -0
  17. {quasarr-2.3.1 → quasarr-2.3.3}/quasarr/downloads/linkcrypters/__init__.py +0 -0
  18. {quasarr-2.3.1 → quasarr-2.3.3}/quasarr/downloads/linkcrypters/al.py +0 -0
  19. {quasarr-2.3.1 → quasarr-2.3.3}/quasarr/downloads/linkcrypters/filecrypt.py +0 -0
  20. {quasarr-2.3.1 → quasarr-2.3.3}/quasarr/downloads/linkcrypters/hide.py +0 -0
  21. {quasarr-2.3.1 → quasarr-2.3.3}/quasarr/downloads/packages/__init__.py +0 -0
  22. {quasarr-2.3.1 → quasarr-2.3.3}/quasarr/downloads/sources/__init__.py +0 -0
  23. {quasarr-2.3.1 → quasarr-2.3.3}/quasarr/downloads/sources/al.py +0 -0
  24. {quasarr-2.3.1 → quasarr-2.3.3}/quasarr/downloads/sources/by.py +0 -0
  25. {quasarr-2.3.1 → quasarr-2.3.3}/quasarr/downloads/sources/dd.py +0 -0
  26. {quasarr-2.3.1 → quasarr-2.3.3}/quasarr/downloads/sources/dj.py +0 -0
  27. {quasarr-2.3.1 → quasarr-2.3.3}/quasarr/downloads/sources/dl.py +0 -0
  28. {quasarr-2.3.1 → quasarr-2.3.3}/quasarr/downloads/sources/dt.py +0 -0
  29. {quasarr-2.3.1 → quasarr-2.3.3}/quasarr/downloads/sources/dw.py +0 -0
  30. {quasarr-2.3.1 → quasarr-2.3.3}/quasarr/downloads/sources/he.py +0 -0
  31. {quasarr-2.3.1 → quasarr-2.3.3}/quasarr/downloads/sources/mb.py +0 -0
  32. {quasarr-2.3.1 → quasarr-2.3.3}/quasarr/downloads/sources/nk.py +0 -0
  33. {quasarr-2.3.1 → quasarr-2.3.3}/quasarr/downloads/sources/nx.py +0 -0
  34. {quasarr-2.3.1 → quasarr-2.3.3}/quasarr/downloads/sources/sf.py +0 -0
  35. {quasarr-2.3.1 → quasarr-2.3.3}/quasarr/downloads/sources/sj.py +0 -0
  36. {quasarr-2.3.1 → quasarr-2.3.3}/quasarr/downloads/sources/sl.py +0 -0
  37. {quasarr-2.3.1 → quasarr-2.3.3}/quasarr/downloads/sources/wd.py +0 -0
  38. {quasarr-2.3.1 → quasarr-2.3.3}/quasarr/downloads/sources/wx.py +0 -0
  39. {quasarr-2.3.1 → quasarr-2.3.3}/quasarr/providers/__init__.py +0 -0
  40. {quasarr-2.3.1 → quasarr-2.3.3}/quasarr/providers/auth.py +0 -0
  41. {quasarr-2.3.1 → quasarr-2.3.3}/quasarr/providers/cloudflare.py +0 -0
  42. {quasarr-2.3.1 → quasarr-2.3.3}/quasarr/providers/hostname_issues.py +0 -0
  43. {quasarr-2.3.1 → quasarr-2.3.3}/quasarr/providers/html_images.py +0 -0
  44. {quasarr-2.3.1 → quasarr-2.3.3}/quasarr/providers/html_templates.py +0 -0
  45. {quasarr-2.3.1 → quasarr-2.3.3}/quasarr/providers/jd_cache.py +0 -0
  46. {quasarr-2.3.1 → quasarr-2.3.3}/quasarr/providers/log.py +0 -0
  47. {quasarr-2.3.1 → quasarr-2.3.3}/quasarr/providers/myjd_api.py +0 -0
  48. {quasarr-2.3.1 → quasarr-2.3.3}/quasarr/providers/notifications.py +0 -0
  49. {quasarr-2.3.1 → quasarr-2.3.3}/quasarr/providers/obfuscated.py +0 -0
  50. {quasarr-2.3.1 → quasarr-2.3.3}/quasarr/providers/sessions/__init__.py +0 -0
  51. {quasarr-2.3.1 → quasarr-2.3.3}/quasarr/providers/sessions/al.py +0 -0
  52. {quasarr-2.3.1 → quasarr-2.3.3}/quasarr/providers/sessions/dd.py +0 -0
  53. {quasarr-2.3.1 → quasarr-2.3.3}/quasarr/providers/sessions/dl.py +0 -0
  54. {quasarr-2.3.1 → quasarr-2.3.3}/quasarr/providers/sessions/nx.py +0 -0
  55. {quasarr-2.3.1 → quasarr-2.3.3}/quasarr/providers/shared_state.py +0 -0
  56. {quasarr-2.3.1 → quasarr-2.3.3}/quasarr/providers/statistics.py +0 -0
  57. {quasarr-2.3.1 → quasarr-2.3.3}/quasarr/providers/utils.py +0 -0
  58. {quasarr-2.3.1 → quasarr-2.3.3}/quasarr/providers/web_server.py +0 -0
  59. {quasarr-2.3.1 → quasarr-2.3.3}/quasarr/search/__init__.py +0 -0
  60. {quasarr-2.3.1 → quasarr-2.3.3}/quasarr/search/sources/__init__.py +0 -0
  61. {quasarr-2.3.1 → quasarr-2.3.3}/quasarr/search/sources/al.py +0 -0
  62. {quasarr-2.3.1 → quasarr-2.3.3}/quasarr/search/sources/by.py +0 -0
  63. {quasarr-2.3.1 → quasarr-2.3.3}/quasarr/search/sources/dd.py +0 -0
  64. {quasarr-2.3.1 → quasarr-2.3.3}/quasarr/search/sources/dj.py +0 -0
  65. {quasarr-2.3.1 → quasarr-2.3.3}/quasarr/search/sources/dl.py +0 -0
  66. {quasarr-2.3.1 → quasarr-2.3.3}/quasarr/search/sources/dt.py +0 -0
  67. {quasarr-2.3.1 → quasarr-2.3.3}/quasarr/search/sources/dw.py +0 -0
  68. {quasarr-2.3.1 → quasarr-2.3.3}/quasarr/search/sources/fx.py +0 -0
  69. {quasarr-2.3.1 → quasarr-2.3.3}/quasarr/search/sources/he.py +0 -0
  70. {quasarr-2.3.1 → quasarr-2.3.3}/quasarr/search/sources/mb.py +0 -0
  71. {quasarr-2.3.1 → quasarr-2.3.3}/quasarr/search/sources/nk.py +0 -0
  72. {quasarr-2.3.1 → quasarr-2.3.3}/quasarr/search/sources/nx.py +0 -0
  73. {quasarr-2.3.1 → quasarr-2.3.3}/quasarr/search/sources/sf.py +0 -0
  74. {quasarr-2.3.1 → quasarr-2.3.3}/quasarr/search/sources/sj.py +0 -0
  75. {quasarr-2.3.1 → quasarr-2.3.3}/quasarr/search/sources/sl.py +0 -0
  76. {quasarr-2.3.1 → quasarr-2.3.3}/quasarr/search/sources/wd.py +0 -0
  77. {quasarr-2.3.1 → quasarr-2.3.3}/quasarr/search/sources/wx.py +0 -0
  78. {quasarr-2.3.1 → quasarr-2.3.3}/quasarr/storage/__init__.py +0 -0
  79. {quasarr-2.3.1 → quasarr-2.3.3}/quasarr/storage/config.py +0 -0
  80. {quasarr-2.3.1 → quasarr-2.3.3}/quasarr/storage/setup.py +0 -0
  81. {quasarr-2.3.1 → quasarr-2.3.3}/quasarr/storage/sqlite_database.py +0 -0
  82. {quasarr-2.3.1 → quasarr-2.3.3}/quasarr.egg-info/SOURCES.txt +0 -0
  83. {quasarr-2.3.1 → quasarr-2.3.3}/quasarr.egg-info/dependency_links.txt +0 -0
  84. {quasarr-2.3.1 → quasarr-2.3.3}/quasarr.egg-info/entry_points.txt +0 -0
  85. {quasarr-2.3.1 → quasarr-2.3.3}/quasarr.egg-info/not-zip-safe +0 -0
  86. {quasarr-2.3.1 → quasarr-2.3.3}/quasarr.egg-info/requires.txt +0 -0
  87. {quasarr-2.3.1 → quasarr-2.3.3}/quasarr.egg-info/top_level.txt +0 -0
  88. {quasarr-2.3.1 → quasarr-2.3.3}/setup.cfg +0 -0
  89. {quasarr-2.3.1 → quasarr-2.3.3}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: quasarr
3
- Version: 2.3.1
3
+ Version: 2.3.3
4
4
  Summary: Quasarr connects JDownloader with Radarr, Sonarr and LazyLibrarian. It also decrypts links protected by CAPTCHAs.
5
5
  Home-page: https://github.com/rix1337/Quasarr
6
6
  Author: rix1337
@@ -169,6 +169,14 @@ def run():
169
169
  else:
170
170
  hostname_credentials_config(shared_state, site.upper(), hostname)
171
171
 
172
+ # Check FlareSolverr configuration
173
+ skip_flaresolverr_db = DataBase("skip_flaresolverr")
174
+ flaresolverr_skipped = skip_flaresolverr_db.retrieve("skipped")
175
+ flaresolverr_url = Config('FlareSolverr').get('url')
176
+
177
+ if not flaresolverr_url and not flaresolverr_skipped:
178
+ flaresolverr_config(shared_state)
179
+
172
180
  config = Config('JDownloader')
173
181
  user = config.get('user')
174
182
  password = config.get('password')
@@ -249,23 +257,33 @@ def flaresolverr_checker(shared_state_dict, shared_state_lock):
249
257
  flaresolverr_skipped = skip_flaresolverr_db.retrieve("skipped")
250
258
 
251
259
  flaresolverr_url = Config('FlareSolverr').get('url')
260
+
261
+ # If FlareSolverr is not configured and not skipped, it means it's the first run
262
+ # and the user needs to be prompted via the WebUI.
263
+ # This background process should NOT block or prompt the user.
264
+ # It should only check and log the status.
252
265
  if not flaresolverr_url and not flaresolverr_skipped:
253
- flaresolverr_config(shared_state)
254
- # Re-check after config - user may have skipped
255
- flaresolverr_skipped = skip_flaresolverr_db.retrieve("skipped")
256
- flaresolverr_url = Config('FlareSolverr').get('url')
266
+ info('FlareSolverr URL not configured. Please configure it via the WebUI.')
267
+ info('Some sites (AL) will not work without FlareSolverr.')
268
+ return # Exit the checker, it will be re-checked if user configures it later
257
269
 
258
270
  if flaresolverr_skipped:
259
271
  info('FlareSolverr setup skipped by user preference')
260
272
  info('Some sites (AL) will not work without FlareSolverr. Configure it later in the web UI.')
261
273
  elif flaresolverr_url:
262
- print(f'Flaresolverr URL: "{flaresolverr_url}"')
274
+ info(f'Checking FlareSolverr at URL: "{flaresolverr_url}"')
263
275
  flaresolverr_check = check_flaresolverr(shared_state, flaresolverr_url)
264
276
  if flaresolverr_check:
265
- print(f'Using same User-Agent as FlareSolverr: "{shared_state.values["user_agent"]}"')
277
+ info(f'FlareSolverr connection successful. Using User-Agent: "{shared_state.values["user_agent"]}"')
278
+ else:
279
+ info('FlareSolverr check failed - using fallback user agent')
280
+ # Fallback user agent is already set in main process, but we log it
281
+ info(f'User Agent (fallback): "{FALLBACK_USER_AGENT}"')
266
282
 
267
283
  except KeyboardInterrupt:
268
284
  pass
285
+ except Exception as e:
286
+ info(f"An unexpected error occurred in FlareSolverr checker: {e}")
269
287
 
270
288
 
271
289
  def update_checker(shared_state_dict, shared_state_lock):
@@ -288,8 +288,8 @@ def setup_config(app, shared_state):
288
288
  "FlareSolverr URL saved successfully! A restart is recommended.")
289
289
  else:
290
290
  return render_fail(f"FlareSolverr returned unexpected status: {json_data.get('status')}")
291
- except requests.RequestException as e:
292
- return render_fail(f"Could not reach FlareSolverr: {str(e)}")
291
+ except requests.RequestException:
292
+ return render_fail(f"Could not reach FlareSolverr!")
293
293
 
294
294
  return render_fail("Could not reach FlareSolverr at that URL (expected HTTP 200).")
295
295
 
@@ -33,16 +33,16 @@ def setup_sponsors_helper_routes(app):
33
33
  if not protected:
34
34
  return abort(404, "No encrypted packages found")
35
35
 
36
- # Find the first package without a "session" key
36
+ # Find the first package that hasn't been disabled
37
37
  selected_package = None
38
38
  for package in protected:
39
39
  data = json.loads(package[1])
40
- if "session" not in data:
40
+ if "disabled" not in data:
41
41
  selected_package = (package[0], data)
42
42
  break
43
43
 
44
44
  if not selected_package:
45
- return abort(404, "No valid packages without session found")
45
+ return abort(404, "No valid packages found")
46
46
 
47
47
  package_id, data = selected_package
48
48
  title = data["title"]
@@ -67,9 +67,9 @@ def setup_sponsors_helper_routes(app):
67
67
  except Exception as e:
68
68
  return abort(500, str(e))
69
69
 
70
- @app.post("/sponsors_helper/api/to_download/")
70
+ @app.post("/sponsors_helper/api/download/")
71
71
  @require_api_key
72
- def to_download_api():
72
+ def download_api():
73
73
  try:
74
74
  data = request.json
75
75
  title = data.get('name')
@@ -97,51 +97,39 @@ def setup_sponsors_helper_routes(app):
97
97
  StatsHelper(shared_state).increment_failed_decryptions_automatic()
98
98
  return abort(500, "Failed")
99
99
 
100
- @app.post("/sponsors_helper/api/to_replace/")
100
+ @app.post("/sponsors_helper/api/disable/")
101
101
  @require_api_key
102
- def to_replace_api():
102
+ def disable_api():
103
103
  try:
104
104
  data = request.json
105
- name = data.get('name')
106
105
  package_id = data.get('package_id')
107
- password = data.get('password')
108
- replace_url = data.get('replace_url')
109
- mirror = data.get('mirror')
110
- session = data.get('session')
111
-
112
- if not all([name, package_id, replace_url, mirror, session]):
113
- info("Missing required replacement data")
114
- return {"error": "Missing required replacement data"}, 400
115
-
116
- if password is None:
117
- password = ""
118
-
119
- blob = json.dumps(
120
- {
121
- "title": name,
122
- "links": [replace_url, mirror],
123
- "size_mb": 0,
124
- "password": password,
125
- "mirror": mirror,
126
- "session": session
127
- })
128
106
 
129
- shared_state.get_db("protected").update_store(package_id, blob)
107
+ if not package_id:
108
+ return {"error": "Missing package_id"}, 400
109
+
110
+ StatsHelper(shared_state).increment_failed_decryptions_automatic()
130
111
 
131
- info(f"Another CAPTCHA solution is required for {mirror} link: {replace_url}")
112
+ blob = shared_state.get_db("protected").retrieve(package_id)
113
+ package_data = json.loads(blob)
114
+ title = package_data.get('title')
115
+
116
+ package_data["disabled"] = True
117
+
118
+ shared_state.get_db("protected").update_store(package_id, json.dumps(package_data))
119
+
120
+ info(f"Disabled package {title}")
132
121
 
133
122
  StatsHelper(shared_state).increment_captcha_decryptions_automatic()
134
123
 
135
- return f"Replacement link stored for {name}"
124
+ return f"Package {title} disabled"
136
125
 
137
126
  except Exception as e:
138
- StatsHelper(shared_state).increment_failed_decryptions_automatic()
139
- info(f"Error handling replacement: {e}")
127
+ info(f"Error handling disable: {e}")
140
128
  return {"error": str(e)}, 500
141
129
 
142
- @app.delete("/sponsors_helper/api/to_failed/")
130
+ @app.delete("/sponsors_helper/api/fail/")
143
131
  @require_api_key
144
- def move_to_failed_api():
132
+ def fail_api():
145
133
  try:
146
134
  StatsHelper(shared_state).increment_failed_decryptions_automatic()
147
135
 
@@ -165,7 +153,7 @@ def setup_sponsors_helper_routes(app):
165
153
 
166
154
  @app.put("/sponsors_helper/api/set_sponsor_status/")
167
155
  @require_api_key
168
- def activate_sponsor_status():
156
+ def set_sponsor_status_api():
169
157
  try:
170
158
  data = request.body.read().decode("utf-8")
171
159
  payload = json.loads(data)