quasarr 2.6.0__py3-none-any.whl → 2.6.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/api/__init__.py CHANGED
@@ -124,10 +124,10 @@ def get_api(shared_state_dict, shared_state_lock):
124
124
  # Status bars
125
125
  status_bars = f"""
126
126
  <div class="status-bar">
127
- <span class="status-pill {jd_status['status_class']}"
127
+ <span class="status-pill {jd_status["status_class"]}"
128
128
  onclick="openJDownloaderModal()"
129
129
  title="Click to configure JDownloader">
130
- {jd_status['status_text']}
130
+ {jd_status["status_text"]}
131
131
  </span>
132
132
  <span class="status-pill {hostname_status_class}"
133
133
  onclick="location.href='/hostnames'"
@@ -7,7 +7,7 @@ import signal
7
7
  import threading
8
8
  import time
9
9
 
10
- from bottle import request, response
10
+ from bottle import response
11
11
 
12
12
  from quasarr.providers.html_templates import render_button, render_form
13
13
  from quasarr.providers.log import info
@@ -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 = dev_name.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;").replace('"', "&quot;")
21
+ dev_name_safe = (
22
+ dev_name.replace("&", "&amp;")
23
+ .replace("<", "&lt;")
24
+ .replace(">", "&gt;")
25
+ .replace('"', "&quot;")
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['status_class']}"
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['status_text']}
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("Back", "secondary", {"onclick": f"location.href='{back_url}'"})
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)
@@ -3,12 +3,17 @@
3
3
  # Project by https://github.com/rix1337
4
4
 
5
5
  import re
6
+ import uuid
6
7
  from urllib.parse import urljoin
7
8
 
8
- import requests
9
9
  from bs4 import BeautifulSoup
10
10
 
11
- from quasarr.providers.cloudflare import flaresolverr_get, is_cloudflare_challenge
11
+ from quasarr.providers.cloudflare import (
12
+ flaresolverr_create_session,
13
+ flaresolverr_destroy_session,
14
+ flaresolverr_get,
15
+ is_cloudflare_challenge,
16
+ )
12
17
  from quasarr.providers.hostname_issues import mark_hostname_issue
13
18
  from quasarr.providers.log import debug, info
14
19
  from quasarr.providers.utils import is_flaresolverr_available
@@ -16,26 +21,22 @@ from quasarr.providers.utils import is_flaresolverr_available
16
21
  hostname = "wd"
17
22
 
18
23
 
19
- def resolve_wd_redirect(url, user_agent):
24
+ def resolve_wd_redirect(shared_state, url, session_id=None):
20
25
  """
21
26
  Follow redirects for a WD mirror URL and return the final destination.
22
27
  """
23
28
  try:
24
- r = requests.get(
25
- url,
26
- allow_redirects=True,
27
- timeout=10,
28
- headers={"User-Agent": user_agent},
29
- )
30
- r.raise_for_status()
31
- if r.history:
32
- for resp in r.history:
33
- debug(f"Redirected from {resp.url} to {r.url}")
29
+ # Use FlareSolverr to follow redirects as well, since the redirector might be protected
30
+ r = flaresolverr_get(shared_state, url, session_id=session_id)
31
+
32
+ # FlareSolverr follows redirects automatically and returns the final URL
33
+ if r.status_code == 200:
34
+ # Check if we landed on a 404 page (soft 404)
35
+ if r.url.endswith("/404.html"):
36
+ return None
34
37
  return r.url
35
38
  else:
36
- info(
37
- f"WD blocked attempt to resolve {url}. Your IP may be banned. Try again later."
38
- )
39
+ info(f"WD blocked attempt to resolve {url}. Status: {r.status_code}")
39
40
  except Exception as e:
40
41
  info(f"Error fetching redirected URL for {url}: {e}")
41
42
  mark_hostname_issue(
@@ -52,25 +53,30 @@ def get_wd_download_links(shared_state, url, mirror, title, password):
52
53
  """
53
54
 
54
55
  wd = shared_state.values["config"]("Hostnames").get("wd")
55
- user_agent = shared_state.values["user_agent"]
56
+
57
+ if not is_flaresolverr_available(shared_state):
58
+ info(
59
+ "WD is protected by Cloudflare but FlareSolverr is not configured. "
60
+ "Please configure FlareSolverr in the web UI to access this site."
61
+ )
62
+ mark_hostname_issue(hostname, "download", "FlareSolverr required but missing.")
63
+ return {"links": [], "imdb_id": None}
64
+
65
+ # Create a temporary FlareSolverr session for this download attempt
66
+ session_id = str(uuid.uuid4())
67
+ created_session = flaresolverr_create_session(shared_state, session_id)
68
+ if not created_session:
69
+ info("Could not create FlareSolverr session. Proceeding without session...")
70
+ session_id = None
71
+ else:
72
+ debug(f"Created FlareSolverr session: {session_id}")
56
73
 
57
74
  try:
58
- r = requests.get(url)
59
- if r.status_code >= 400 or is_cloudflare_challenge(r.text):
60
- if is_flaresolverr_available(shared_state):
61
- info(
62
- "WD is protected by Cloudflare. Using FlareSolverr to bypass protection."
63
- )
64
- r = flaresolverr_get(shared_state, url)
65
- else:
66
- info(
67
- "WD is protected by Cloudflare but FlareSolverr is not configured. "
68
- "Please configure FlareSolverr in the web UI to access this site."
69
- )
70
- mark_hostname_issue(
71
- hostname, "download", "FlareSolverr required but missing."
72
- )
73
- return {"links": [], "imdb_id": None}
75
+ r = flaresolverr_get(shared_state, url, session_id=session_id)
76
+ if r.status_code == 403 or is_cloudflare_challenge(r.text):
77
+ info("Could not bypass Cloudflare protection with FlareSolverr!")
78
+ mark_hostname_issue(hostname, "download", "Cloudflare challenge failed")
79
+ return {"links": [], "imdb_id": None}
74
80
 
75
81
  if r.status_code >= 400:
76
82
  mark_hostname_issue(
@@ -105,51 +111,59 @@ def get_wd_download_links(shared_state, url, mirror, title, password):
105
111
  link_tags = body.find_all(
106
112
  "a", href=True, class_=lambda c: c and "background-" in c
107
113
  )
114
+
115
+ results = []
116
+ try:
117
+ for a in link_tags:
118
+ raw_href = a["href"]
119
+ full_link = urljoin(f"https://{wd}", raw_href)
120
+
121
+ # resolve any redirects using the same session
122
+ resolved = resolve_wd_redirect(
123
+ shared_state, full_link, session_id=session_id
124
+ )
125
+
126
+ if resolved:
127
+ if resolved.endswith("/404.html"):
128
+ info(f"Link {resolved} is dead!")
129
+ continue
130
+
131
+ # determine hoster
132
+ hoster = a.get_text(strip=True) or None
133
+ if not hoster:
134
+ for cls in a.get("class", []):
135
+ if cls.startswith("background-"):
136
+ hoster = cls.split("-", 1)[1]
137
+ break
138
+
139
+ if mirror and mirror.lower() not in hoster.lower():
140
+ debug(
141
+ f'Skipping link from "{hoster}" (not the desired mirror "{mirror}")!'
142
+ )
143
+ continue
144
+
145
+ results.append([resolved, hoster])
146
+ except Exception as e:
147
+ info(
148
+ f"WD site has been updated. Parsing download links for {title} not possible! Error: {e}"
149
+ )
150
+
151
+ return {
152
+ "links": results,
153
+ "imdb_id": imdb_id,
154
+ }
155
+
108
156
  except RuntimeError as e:
109
157
  # Catch FlareSolverr not configured error
110
158
  info(f"WD access failed: {e}")
111
159
  return {"links": [], "imdb_id": None}
112
- except Exception:
160
+ except Exception as e:
113
161
  info(
114
- f"WD site has been updated. Grabbing download links for {title} not possible!"
162
+ f"WD site has been updated. Grabbing download links for {title} not possible! Error: {e}"
115
163
  )
116
164
  return {"links": [], "imdb_id": None}
117
-
118
- results = []
119
- try:
120
- for a in link_tags:
121
- raw_href = a["href"]
122
- full_link = urljoin(f"https://{wd}", raw_href)
123
-
124
- # resolve any redirects
125
- resolved = resolve_wd_redirect(full_link, user_agent)
126
-
127
- if resolved:
128
- if resolved.endswith("/404.html"):
129
- info(f"Link {resolved} is dead!")
130
- continue
131
-
132
- # determine hoster
133
- hoster = a.get_text(strip=True) or None
134
- if not hoster:
135
- for cls in a.get("class", []):
136
- if cls.startswith("background-"):
137
- hoster = cls.split("-", 1)[1]
138
- break
139
-
140
- if mirror and mirror.lower() not in hoster.lower():
141
- debug(
142
- f'Skipping link from "{hoster}" (not the desired mirror "{mirror}")!'
143
- )
144
- continue
145
-
146
- results.append([resolved, hoster])
147
- except Exception:
148
- info(
149
- f"WD site has been updated. Parsing download links for {title} not possible!"
150
- )
151
-
152
- return {
153
- "links": results,
154
- "imdb_id": imdb_id,
155
- }
165
+ finally:
166
+ # Always destroy the session
167
+ if session_id:
168
+ debug(f"Destroying FlareSolverr session: {session_id}")
169
+ flaresolverr_destroy_session(shared_state, session_id)
@@ -168,7 +168,7 @@ class FlareSolverrResponse:
168
168
  raise requests.HTTPError(f"{self.status_code} Error at {self.url}")
169
169
 
170
170
 
171
- def flaresolverr_get(shared_state, url, timeout=60):
171
+ def flaresolverr_get(shared_state, url, timeout=60, session_id=None):
172
172
  """
173
173
  Core function for performing a GET request via FlareSolverr only.
174
174
  Used internally by FlareSolverrSession.get()
@@ -186,6 +186,8 @@ def flaresolverr_get(shared_state, url, timeout=60):
186
186
  raise RuntimeError("FlareSolverr URL not configured in shared_state.")
187
187
 
188
188
  payload = {"cmd": "request.get", "url": url, "maxTimeout": timeout * 1000}
189
+ if session_id:
190
+ payload["session"] = session_id
189
191
 
190
192
  try:
191
193
  resp = requests.post(
@@ -219,3 +221,46 @@ def flaresolverr_get(shared_state, url, timeout=60):
219
221
  return FlareSolverrResponse(
220
222
  url=url, status_code=status_code, headers=fs_headers, text=html
221
223
  )
224
+
225
+
226
+ def flaresolverr_create_session(shared_state, session_id=None):
227
+ if not is_flaresolverr_available(shared_state):
228
+ return None
229
+
230
+ flaresolverr_url = shared_state.values["config"]("FlareSolverr").get("url")
231
+ payload = {"cmd": "sessions.create"}
232
+ if session_id:
233
+ payload["session"] = session_id
234
+
235
+ try:
236
+ resp = requests.post(
237
+ flaresolverr_url,
238
+ json=payload,
239
+ headers={"Content-Type": "application/json"},
240
+ timeout=10,
241
+ )
242
+ resp.raise_for_status()
243
+ data = resp.json()
244
+ if data.get("status") == "ok":
245
+ return data.get("session")
246
+ except Exception:
247
+ pass
248
+ return None
249
+
250
+
251
+ def flaresolverr_destroy_session(shared_state, session_id):
252
+ if not is_flaresolverr_available(shared_state):
253
+ return
254
+
255
+ flaresolverr_url = shared_state.values["config"]("FlareSolverr").get("url")
256
+ payload = {"cmd": "sessions.destroy", "session": session_id}
257
+
258
+ try:
259
+ requests.post(
260
+ flaresolverr_url,
261
+ json=payload,
262
+ headers={"Content-Type": "application/json"},
263
+ timeout=10,
264
+ )
265
+ except Exception:
266
+ pass
@@ -5,7 +5,7 @@
5
5
  import re
6
6
  import sys
7
7
 
8
- __version__ = "2.6.0"
8
+ __version__ = "2.6.1"
9
9
 
10
10
 
11
11
  def get_version():
@@ -9,12 +9,13 @@ from base64 import urlsafe_b64encode
9
9
  from datetime import datetime, timedelta
10
10
  from urllib.parse import quote, quote_plus
11
11
 
12
- import requests
13
12
  from bs4 import BeautifulSoup
14
13
 
14
+ from quasarr.providers.cloudflare import flaresolverr_get, is_cloudflare_challenge
15
15
  from quasarr.providers.hostname_issues import clear_hostname_issue, mark_hostname_issue
16
16
  from quasarr.providers.imdb_metadata import get_localized_title, get_year
17
17
  from quasarr.providers.log import debug, info
18
+ from quasarr.providers.utils import is_flaresolverr_available
18
19
 
19
20
  hostname = "wd"
20
21
  supported_mirrors = ["rapidgator", "ddownload", "katfile", "fikper", "turbobit"]
@@ -170,9 +171,21 @@ def wd_feed(shared_state, start_time, request_from, mirror=None):
170
171
  feed_type = "Serien"
171
172
 
172
173
  url = f"https://{wd}/{feed_type}"
173
- headers = {"User-Agent": shared_state.values["user_agent"]}
174
+
175
+ if not is_flaresolverr_available(shared_state):
176
+ info(
177
+ f"FlareSolverr is not configured. Cannot access {hostname.upper()} feed due to Cloudflare protection."
178
+ )
179
+ mark_hostname_issue(hostname, "feed", "FlareSolverr missing")
180
+ return []
181
+
174
182
  try:
175
- r = requests.get(url, headers=headers, timeout=10)
183
+ r = flaresolverr_get(shared_state, url)
184
+ if r.status_code == 403 or is_cloudflare_challenge(r.text):
185
+ info(f"Cloudflare challenge failed for {hostname} feed.")
186
+ mark_hostname_issue(hostname, "feed", "Cloudflare challenge failed")
187
+ return []
188
+
176
189
  r.raise_for_status()
177
190
  soup = BeautifulSoup(r.content, "html.parser")
178
191
  releases = _parse_rows(soup, shared_state, wd, password, mirror)
@@ -215,10 +228,21 @@ def wd_search(
215
228
 
216
229
  q = quote_plus(search_string)
217
230
  url = f"https://{wd}/search?q={q}"
218
- headers = {"User-Agent": shared_state.values["user_agent"]}
231
+
232
+ if not is_flaresolverr_available(shared_state):
233
+ info(
234
+ f"FlareSolverr is not configured. Cannot access {hostname.upper()} search due to Cloudflare protection."
235
+ )
236
+ mark_hostname_issue(hostname, "search", "FlareSolverr missing")
237
+ return []
219
238
 
220
239
  try:
221
- r = requests.get(url, headers=headers, timeout=10)
240
+ r = flaresolverr_get(shared_state, url)
241
+ if r.status_code == 403 or is_cloudflare_challenge(r.text):
242
+ info(f"Cloudflare challenge failed for {hostname} search.")
243
+ mark_hostname_issue(hostname, "search", "Cloudflare challenge failed")
244
+ return []
245
+
222
246
  r.raise_for_status()
223
247
  soup = BeautifulSoup(r.content, "html.parser")
224
248
  releases = _parse_rows(
quasarr/storage/setup.py CHANGED
@@ -1446,7 +1446,10 @@ def verify_jdownloader_credentials(shared_state):
1446
1446
  if device_names:
1447
1447
  return {"success": True, "devices": device_names}
1448
1448
  else:
1449
- return {"success": False, "message": "No devices found or invalid credentials"}
1449
+ return {
1450
+ "success": False,
1451
+ "message": "No devices found or invalid credentials",
1452
+ }
1450
1453
  except Exception as e:
1451
1454
  return {"success": False, "message": str(e)}
1452
1455
 
@@ -1470,19 +1473,25 @@ def save_jdownloader_settings(shared_state, is_setup=False):
1470
1473
  config.save("user", username)
1471
1474
  config.save("password", password)
1472
1475
  config.save("device", device)
1473
-
1476
+
1474
1477
  if is_setup:
1475
1478
  quasarr.providers.web_server.temp_server_success = True
1476
1479
  return render_reconnect_success("Credentials set")
1477
1480
  else:
1478
1481
  response.content_type = "application/json"
1479
- return {"success": True, "message": "JDownloader configured successfully"}
1482
+ return {
1483
+ "success": True,
1484
+ "message": "JDownloader configured successfully",
1485
+ }
1480
1486
  else:
1481
1487
  if is_setup:
1482
1488
  return render_fail("Could not connect to selected device!")
1483
1489
  else:
1484
1490
  response.content_type = "application/json"
1485
- return {"success": False, "message": "Could not connect to selected device"}
1491
+ return {
1492
+ "success": False,
1493
+ "message": "Could not connect to selected device",
1494
+ }
1486
1495
 
1487
1496
  if is_setup:
1488
1497
  return render_fail("Could not set credentials!")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: quasarr
3
- Version: 2.6.0
3
+ Version: 2.6.1
4
4
  Summary: Quasarr connects JDownloader with Radarr, Sonarr and LazyLibrarian. It also decrypts links protected by CAPTCHAs.
5
5
  Author-email: rix1337 <rix1337@users.noreply.github.com>
6
6
  License-File: LICENSE
@@ -1,9 +1,9 @@
1
1
  quasarr/__init__.py,sha256=ZgxPUmVmiI3XYfocjzWNJNAGda6sO0o3OqOEFOPOQZc,16982
2
- quasarr/api/__init__.py,sha256=pd7cOYO6zfwTodVyIQY0Ng6tMGmi00Ul3nC6aiXz9E8,20180
2
+ quasarr/api/__init__.py,sha256=KrbMcvcPPJ3q299dBOmCJEixdZ4ftAiC1MJ_3pu8ep0,20180
3
3
  quasarr/api/arr/__init__.py,sha256=z6Cx9GqPti6zt_bVPHUmGVSdxvIYLRuL7TL7Sd5VPGI,18487
4
4
  quasarr/api/captcha/__init__.py,sha256=PPiIaW4w5qZWcm9MkEidsKFS0uaooPMtca5Cc5dL8mM,75225
5
- quasarr/api/config/__init__.py,sha256=oclZBp8Nzqq9dIUmXoPadlJNp7oxIgWTjJc2kl7bj5o,8822
6
- quasarr/api/jdownloader/__init__.py,sha256=4rndOeO2vaPTtIHKl1A09CzTWTHpeOeHU7y6_7M7-lI,9397
5
+ quasarr/api/config/__init__.py,sha256=FJZHALhL6NExonhCk53vOYnM1ICkmbTRue5UMCy5Yzg,8813
6
+ quasarr/api/jdownloader/__init__.py,sha256=SixcV-sgMAunjAT5LawASb1qSuOOokorQo2F7cQ3jZ4,9427
7
7
  quasarr/api/packages/__init__.py,sha256=4T6pw0N1DKpTCj2mAgdPOjo__nhxr56aEqZOiFvPvb0,30679
8
8
  quasarr/api/sponsors_helper/__init__.py,sha256=QAFXK_JTtAnstRAlieCbbCsoTwIcBu7ZX8C3U4jZpR0,6475
9
9
  quasarr/api/statistics/__init__.py,sha256=rJz6S4jSnpFDWtjU7O-2jECUEqlueOHOEfRUjSb3cMY,7943
@@ -29,11 +29,11 @@ quasarr/downloads/sources/nx.py,sha256=Kn3Nn87NcrKada3j8jpTlunKJ7-ggDyQOiRSoCdxy
29
29
  quasarr/downloads/sources/sf.py,sha256=f_jC4Shnl2GWCro6JcBLjbmZA8nSPVPr4vdf0WR_r7k,6927
30
30
  quasarr/downloads/sources/sj.py,sha256=h3x7F8UUPvcyTf6gkKn6fBLTFeQjvqD7MJ1TtuiqhUU,390
31
31
  quasarr/downloads/sources/sl.py,sha256=G5TehRfrdlNT-jJ1PiSEzq1IWqL2UCq73LIMqvYef2c,3573
32
- quasarr/downloads/sources/wd.py,sha256=mUFvH1OptEOBfQMw6TmucfC-Da8lpf-ThLGyK1oh_Xc,5135
32
+ quasarr/downloads/sources/wd.py,sha256=pmXkScWTrMvQHTOx770M_caF92VIKvHmUu1ncqrmxvY,6042
33
33
  quasarr/downloads/sources/wx.py,sha256=-6SEY_3lvtONZXxkit4MIYLHsPY9Z1VzOvv5MYbDag8,7091
34
34
  quasarr/providers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
35
35
  quasarr/providers/auth.py,sha256=mLd22xsTO29rgPwEU9SkFHRLuYxev-_11TQXicXLon8,10570
36
- quasarr/providers/cloudflare.py,sha256=mnq8NZN_NZlmWC5Mg-s51pSIlA7i8O4kYgGbxd_vumA,7759
36
+ quasarr/providers/cloudflare.py,sha256=K5fd0cbVvppYpulz_aYQnyXhkFTZA6ZiwL6v_t_HrC8,9021
37
37
  quasarr/providers/hostname_issues.py,sha256=SpnZAxOLejSXJGFnYkCrRzR8D0IQsTMtylM-O0h21Z0,1462
38
38
  quasarr/providers/html_images.py,sha256=xmxfNwqAqQimVaOq7IelkxlBdcRpPZZLGli_MJDOacI,19755
39
39
  quasarr/providers/html_templates.py,sha256=e5b66N47y5Uq7Ikwcm6kOWiyXZ7Bz4gqg2DcajIBGgE,16360
@@ -46,7 +46,7 @@ quasarr/providers/obfuscated.py,sha256=IAN0-5m6UblLjaFdPhRy75ryqDMF0nlbkClq5-n1b
46
46
  quasarr/providers/shared_state.py,sha256=Pv7UUaoPtsl7Rp_vbK_fAnyzlEAdn9Hj_DcVJ4LROqs,32789
47
47
  quasarr/providers/statistics.py,sha256=1X_Aa7TE3W7ovwkemVMsgIx55Jw3eYMiyUxuCUDgO5s,8666
48
48
  quasarr/providers/utils.py,sha256=YXW34xi8569-Tr-cpr8GyOCrWvZ_GKWrX5x0-sxMpOU,12169
49
- quasarr/providers/version.py,sha256=GIOV_EzhA7iR86iNi7M57LPHI5nf8EWkIiNEu3-FtN8,4424
49
+ quasarr/providers/version.py,sha256=peW1DQ2L5XTmtez6gPVEC0say4xucb1RNTyv0_NDmqY,4424
50
50
  quasarr/providers/web_server.py,sha256=tHkMxhV6eaHC8cWsEpbUqD_U29IFE24VsU6tjk-xCEM,1765
51
51
  quasarr/providers/sessions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
52
52
  quasarr/providers/sessions/al.py,sha256=obDBotLA8vhbSsIw3VErj-FujVgYc4RimqxLqycfbVE,13569
@@ -71,14 +71,14 @@ quasarr/search/sources/nx.py,sha256=HvZfzNrteHvjSY6LuPEyKjUHiuuBDrL_zZ-a7bIvLSY,
71
71
  quasarr/search/sources/sf.py,sha256=W_620AdArvINjhKjBKYvoAX3OKBSeRtOWzTpQFhOmZY,17102
72
72
  quasarr/search/sources/sj.py,sha256=fQ1xhaDc1hvycLv2sVYXx_ZBNkjXsJ_s4-VcgJEWy8A,7887
73
73
  quasarr/search/sources/sl.py,sha256=XvY7XC4AIXh6-GYsTwERoRKMi4euo1zk2X2gKff8S8E,11079
74
- quasarr/search/sources/wd.py,sha256=EBqCa_xDeH3OCINMQwkTLsaAkd2uzvGWnmufv8bYJrQ,8203
74
+ quasarr/search/sources/wd.py,sha256=Y_kSbri6u7QaO0JDCeIBtYeo5r_EJXgpDL4WzMrg268,9235
75
75
  quasarr/search/sources/wx.py,sha256=3GMgOz2QhmeU1ip-lX5_JrltJi_uuSFGNHJr0Lk7l4c,15555
76
76
  quasarr/storage/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
77
77
  quasarr/storage/config.py,sha256=IzPrfwuR_4fsgh-cBQigOtQwqzrtBAsZfdzDotCabJw,6618
78
- quasarr/storage/setup.py,sha256=cVu0ZFPlqZiRiHtPUxLswIKRwMLxUc1I9MU6ZKdKDzo,61367
78
+ quasarr/storage/setup.py,sha256=zb83kvQfxMFHxC7EvWWaVTy0MtG7iEjMRyfY4hdcbOk,61520
79
79
  quasarr/storage/sqlite_database.py,sha256=tmHUotMWIwtyH-g244WvcGhMQMMjGokncv7JpFSi8NM,3639
80
- quasarr-2.6.0.dist-info/METADATA,sha256=MBpTsWIuZYUiClvXAcoTUSp1JJlSknQgztDC43uejxg,14727
81
- quasarr-2.6.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
82
- quasarr-2.6.0.dist-info/entry_points.txt,sha256=gXi8mUKsIqKVvn-bOc8E5f04sK_KoMCC-ty6b2Hf-jc,40
83
- quasarr-2.6.0.dist-info/licenses/LICENSE,sha256=QQFCAfDgt7lSA8oSWDHIZ9aTjFbZaBJdjnGOHkuhK7k,1060
84
- quasarr-2.6.0.dist-info/RECORD,,
80
+ quasarr-2.6.1.dist-info/METADATA,sha256=KOEKXAbtynYwQcL7NH32JJvXJ6cfdoN7_LVrPMkW8jE,14727
81
+ quasarr-2.6.1.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
82
+ quasarr-2.6.1.dist-info/entry_points.txt,sha256=gXi8mUKsIqKVvn-bOc8E5f04sK_KoMCC-ty6b2Hf-jc,40
83
+ quasarr-2.6.1.dist-info/licenses/LICENSE,sha256=QQFCAfDgt7lSA8oSWDHIZ9aTjFbZaBJdjnGOHkuhK7k,1060
84
+ quasarr-2.6.1.dist-info/RECORD,,