quasarr 2.4.7__py3-none-any.whl → 2.4.9__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.
Files changed (76) hide show
  1. quasarr/__init__.py +134 -70
  2. quasarr/api/__init__.py +40 -31
  3. quasarr/api/arr/__init__.py +116 -108
  4. quasarr/api/captcha/__init__.py +262 -137
  5. quasarr/api/config/__init__.py +76 -46
  6. quasarr/api/packages/__init__.py +138 -102
  7. quasarr/api/sponsors_helper/__init__.py +29 -16
  8. quasarr/api/statistics/__init__.py +19 -19
  9. quasarr/downloads/__init__.py +165 -72
  10. quasarr/downloads/linkcrypters/al.py +35 -18
  11. quasarr/downloads/linkcrypters/filecrypt.py +107 -52
  12. quasarr/downloads/linkcrypters/hide.py +5 -6
  13. quasarr/downloads/packages/__init__.py +342 -177
  14. quasarr/downloads/sources/al.py +191 -100
  15. quasarr/downloads/sources/by.py +31 -13
  16. quasarr/downloads/sources/dd.py +27 -14
  17. quasarr/downloads/sources/dj.py +1 -3
  18. quasarr/downloads/sources/dl.py +126 -71
  19. quasarr/downloads/sources/dt.py +11 -5
  20. quasarr/downloads/sources/dw.py +28 -14
  21. quasarr/downloads/sources/he.py +32 -24
  22. quasarr/downloads/sources/mb.py +19 -9
  23. quasarr/downloads/sources/nk.py +14 -10
  24. quasarr/downloads/sources/nx.py +8 -18
  25. quasarr/downloads/sources/sf.py +45 -20
  26. quasarr/downloads/sources/sj.py +1 -3
  27. quasarr/downloads/sources/sl.py +9 -5
  28. quasarr/downloads/sources/wd.py +32 -12
  29. quasarr/downloads/sources/wx.py +35 -21
  30. quasarr/providers/auth.py +42 -37
  31. quasarr/providers/cloudflare.py +28 -30
  32. quasarr/providers/hostname_issues.py +2 -1
  33. quasarr/providers/html_images.py +2 -2
  34. quasarr/providers/html_templates.py +22 -14
  35. quasarr/providers/imdb_metadata.py +149 -80
  36. quasarr/providers/jd_cache.py +131 -39
  37. quasarr/providers/log.py +1 -1
  38. quasarr/providers/myjd_api.py +260 -196
  39. quasarr/providers/notifications.py +53 -41
  40. quasarr/providers/obfuscated.py +9 -4
  41. quasarr/providers/sessions/al.py +71 -55
  42. quasarr/providers/sessions/dd.py +21 -14
  43. quasarr/providers/sessions/dl.py +30 -19
  44. quasarr/providers/sessions/nx.py +23 -14
  45. quasarr/providers/shared_state.py +292 -141
  46. quasarr/providers/statistics.py +75 -43
  47. quasarr/providers/utils.py +33 -27
  48. quasarr/providers/version.py +45 -14
  49. quasarr/providers/web_server.py +10 -5
  50. quasarr/search/__init__.py +30 -18
  51. quasarr/search/sources/al.py +124 -73
  52. quasarr/search/sources/by.py +110 -59
  53. quasarr/search/sources/dd.py +57 -35
  54. quasarr/search/sources/dj.py +69 -48
  55. quasarr/search/sources/dl.py +159 -100
  56. quasarr/search/sources/dt.py +110 -74
  57. quasarr/search/sources/dw.py +121 -61
  58. quasarr/search/sources/fx.py +108 -62
  59. quasarr/search/sources/he.py +78 -49
  60. quasarr/search/sources/mb.py +96 -48
  61. quasarr/search/sources/nk.py +80 -50
  62. quasarr/search/sources/nx.py +91 -62
  63. quasarr/search/sources/sf.py +171 -106
  64. quasarr/search/sources/sj.py +69 -48
  65. quasarr/search/sources/sl.py +115 -71
  66. quasarr/search/sources/wd.py +67 -44
  67. quasarr/search/sources/wx.py +188 -123
  68. quasarr/storage/config.py +65 -52
  69. quasarr/storage/setup.py +238 -140
  70. quasarr/storage/sqlite_database.py +10 -4
  71. {quasarr-2.4.7.dist-info → quasarr-2.4.9.dist-info}/METADATA +2 -2
  72. quasarr-2.4.9.dist-info/RECORD +81 -0
  73. quasarr-2.4.7.dist-info/RECORD +0 -81
  74. {quasarr-2.4.7.dist-info → quasarr-2.4.9.dist-info}/WHEEL +0 -0
  75. {quasarr-2.4.7.dist-info → quasarr-2.4.9.dist-info}/entry_points.txt +0 -0
  76. {quasarr-2.4.7.dist-info → quasarr-2.4.9.dist-info}/licenses/LICENSE +0 -0
@@ -10,20 +10,22 @@ import urllib.parse
10
10
 
11
11
  import requests
12
12
  from bs4 import BeautifulSoup
13
- from requests.exceptions import Timeout, RequestException
13
+ from requests.exceptions import RequestException, Timeout
14
14
 
15
- from quasarr.providers.hostname_issues import mark_hostname_issue, clear_hostname_issue
16
- from quasarr.providers.log import info, debug
17
- from quasarr.providers.utils import is_site_usable, is_flaresolverr_available
15
+ from quasarr.providers.hostname_issues import clear_hostname_issue, mark_hostname_issue
16
+ from quasarr.providers.log import debug, info
17
+ from quasarr.providers.utils import is_flaresolverr_available, is_site_usable
18
18
 
19
19
 
20
20
  class SkippedSiteError(Exception):
21
21
  """Raised when a site is skipped due to missing credentials or login being skipped."""
22
+
22
23
  pass
23
24
 
24
25
 
25
26
  class FlareSolverrNotAvailableError(Exception):
26
27
  """Raised when FlareSolverr is required but not available."""
28
+
27
29
  pass
28
30
 
29
31
 
@@ -35,9 +37,13 @@ SESSION_MAX_AGE_SECONDS = 24 * 60 * 60 # 24 hours
35
37
  def create_and_persist_session(shared_state):
36
38
  # AL requires FlareSolverr - check availability first
37
39
  if not is_flaresolverr_available(shared_state):
38
- info(f'"{hostname.upper()}" requires FlareSolverr which is not configured. '
39
- f'Please configure FlareSolverr in the web UI to use this site.')
40
- mark_hostname_issue(hostname, "session", "FlareSolverr required but not configured")
40
+ info(
41
+ f'"{hostname.upper()}" requires FlareSolverr which is not configured. '
42
+ f"Please configure FlareSolverr in the web UI to use this site."
43
+ )
44
+ mark_hostname_issue(
45
+ hostname, "session", "FlareSolverr required but not configured"
46
+ )
41
47
  return None
42
48
 
43
49
  cfg = shared_state.values["config"]("Hostnames")
@@ -46,7 +52,7 @@ def create_and_persist_session(shared_state):
46
52
  user = credentials_cfg.get("user")
47
53
  pw = credentials_cfg.get("password")
48
54
 
49
- flaresolverr_url = shared_state.values["config"]('FlareSolverr').get('url')
55
+ flaresolverr_url = shared_state.values["config"]("FlareSolverr").get("url")
50
56
 
51
57
  sess = requests.Session()
52
58
 
@@ -57,11 +63,13 @@ def create_and_persist_session(shared_state):
57
63
  fs_payload = {
58
64
  "cmd": "request.get",
59
65
  "url": f"https://www.{host}/",
60
- "maxTimeout": 60000
66
+ "maxTimeout": 60000,
61
67
  }
62
68
 
63
69
  try:
64
- fs_resp = requests.post(flaresolverr_url, headers=fs_headers, json=fs_payload, timeout=30)
70
+ fs_resp = requests.post(
71
+ flaresolverr_url, headers=fs_headers, json=fs_payload, timeout=30
72
+ )
65
73
  fs_resp.raise_for_status()
66
74
  except Timeout:
67
75
  info(f"{hostname}: FlareSolverr request timed out")
@@ -77,14 +85,16 @@ def create_and_persist_session(shared_state):
77
85
  # Check if FlareSolverr actually solved the challenge
78
86
  if fs_json.get("status") != "ok" or "solution" not in fs_json:
79
87
  info(f"{hostname}: FlareSolverr did not return a valid solution")
80
- mark_hostname_issue(hostname, "session", "FlareSolverr did not return a valid solution")
88
+ mark_hostname_issue(
89
+ hostname, "session", "FlareSolverr did not return a valid solution"
90
+ )
81
91
  return None
82
92
 
83
93
  solution = fs_json["solution"]
84
94
  # store FlareSolverr's UA into our requests.Session
85
95
  fl_ua = solution.get("userAgent")
86
96
  if fl_ua:
87
- sess.headers.update({'User-Agent': fl_ua})
97
+ sess.headers.update({"User-Agent": fl_ua})
88
98
 
89
99
  # Extract any cookies returned by FlareSolverr and add them into our session
90
100
  for ck in solution.get("cookies", []):
@@ -101,21 +111,17 @@ def create_and_persist_session(shared_state):
101
111
  return None
102
112
 
103
113
  if user and pw:
104
- data = {
105
- "identity": user,
106
- "password": pw,
107
- "remember": "1"
108
- }
114
+ data = {"identity": user, "password": pw, "remember": "1"}
109
115
  encoded_data = urllib.parse.urlencode(data)
110
116
 
111
- login_headers = {
112
- "Content-Type": "application/x-www-form-urlencoded"
113
- }
117
+ login_headers = {"Content-Type": "application/x-www-form-urlencoded"}
114
118
 
115
- r = sess.post(f'https://www.{host}/auth/signin',
116
- data=encoded_data,
117
- headers=login_headers,
118
- timeout=30)
119
+ r = sess.post(
120
+ f"https://www.{host}/auth/signin",
121
+ data=encoded_data,
122
+ headers=login_headers,
123
+ timeout=30,
124
+ )
119
125
 
120
126
  if r.status_code != 200 or "invalid" in r.text.lower():
121
127
  info(f'Login failed: "{hostname}" - {r.status_code} - {r.text}')
@@ -194,10 +200,7 @@ def _persist_session_to_db(shared_state, sess):
194
200
  """
195
201
  blob = pickle.dumps(sess)
196
202
  token = base64.b64encode(blob).decode("utf-8")
197
- session_data = json.dumps({
198
- "token": token,
199
- "created_at": time.time()
200
- })
203
+ session_data = json.dumps({"token": token, "created_at": time.time()})
201
204
  shared_state.values["database"]("sessions").update_store(hostname, session_data)
202
205
 
203
206
 
@@ -207,12 +210,14 @@ def _load_session_cookies_for_flaresolverr(sess):
207
210
  """
208
211
  cookie_list = []
209
212
  for ck in sess.cookies:
210
- cookie_list.append({
211
- "name": ck.name,
212
- "value": ck.value,
213
- "domain": ck.domain,
214
- "path": ck.path or "/",
215
- })
213
+ cookie_list.append(
214
+ {
215
+ "name": ck.name,
216
+ "value": ck.value,
217
+ "domain": ck.domain,
218
+ "path": ck.path or "/",
219
+ }
220
+ )
216
221
  return cookie_list
217
222
 
218
223
 
@@ -232,11 +237,13 @@ def unwrap_flaresolverr_body(raw_text: str) -> str:
232
237
  return text
233
238
 
234
239
 
235
- def fetch_via_flaresolverr(shared_state,
236
- method: str,
237
- target_url: str,
238
- post_data: dict = None,
239
- timeout: int = 60):
240
+ def fetch_via_flaresolverr(
241
+ shared_state,
242
+ method: str,
243
+ target_url: str,
244
+ post_data: dict = None,
245
+ timeout: int = 60,
246
+ ):
240
247
  """
241
248
  Load (or recreate) the requests.Session from DB.
242
249
  Package its cookies into FlareSolverr payload.
@@ -251,18 +258,20 @@ def fetch_via_flaresolverr(shared_state,
251
258
  """
252
259
  # Check if FlareSolverr is available
253
260
  if not is_flaresolverr_available(shared_state):
254
- info(f'"{hostname.upper()}" requires FlareSolverr which is not configured. '
255
- f'Please configure FlareSolverr in the web UI.')
261
+ info(
262
+ f'"{hostname.upper()}" requires FlareSolverr which is not configured. '
263
+ f"Please configure FlareSolverr in the web UI."
264
+ )
256
265
  return {
257
266
  "status_code": None,
258
267
  "headers": {},
259
268
  "json": None,
260
269
  "text": "",
261
270
  "cookies": [],
262
- "error": "FlareSolverr is not configured"
271
+ "error": "FlareSolverr is not configured",
263
272
  }
264
273
 
265
- flaresolverr_url = shared_state.values["config"]('FlareSolverr').get('url')
274
+ flaresolverr_url = shared_state.values["config"]("FlareSolverr").get("url")
266
275
 
267
276
  sess = retrieve_and_validate_session(shared_state)
268
277
  if not sess:
@@ -273,7 +282,7 @@ def fetch_via_flaresolverr(shared_state,
273
282
  "json": None,
274
283
  "text": "",
275
284
  "cookies": [],
276
- "error": f"Site '{hostname}' is not usable (login skipped or no credentials)"
285
+ "error": f"Site '{hostname}' is not usable (login skipped or no credentials)",
277
286
  }
278
287
 
279
288
  cmd = "request.get" if method.upper() == "GET" else "request.post"
@@ -282,7 +291,7 @@ def fetch_via_flaresolverr(shared_state,
282
291
  "url": target_url,
283
292
  "maxTimeout": timeout * 1000,
284
293
  # Inject every cookie from our Python session into FlareSolverr
285
- "cookies": _load_session_cookies_for_flaresolverr(sess)
294
+ "cookies": _load_session_cookies_for_flaresolverr(sess),
286
295
  }
287
296
 
288
297
  if method.upper() == "POST":
@@ -294,10 +303,7 @@ def fetch_via_flaresolverr(shared_state,
294
303
  fs_headers = {"Content-Type": "application/json"}
295
304
  try:
296
305
  resp = requests.post(
297
- flaresolverr_url,
298
- headers=fs_headers,
299
- json=fs_payload,
300
- timeout=timeout + 10
306
+ flaresolverr_url, headers=fs_headers, json=fs_payload, timeout=timeout + 10
301
307
  )
302
308
  except requests.exceptions.RequestException as e:
303
309
  info(f"Could not reach FlareSolverr: {e}")
@@ -308,7 +314,7 @@ def fetch_via_flaresolverr(shared_state,
308
314
  "json": None,
309
315
  "text": "",
310
316
  "cookies": [],
311
- "error": f"FlareSolverr request failed: {e}"
317
+ "error": f"FlareSolverr request failed: {e}",
312
318
  }
313
319
  except Exception as e:
314
320
  raise RuntimeError(f"Could not reach FlareSolverr: {e}")
@@ -319,7 +325,9 @@ def fetch_via_flaresolverr(shared_state,
319
325
 
320
326
  fs_json = resp.json()
321
327
  if fs_json.get("status") != "ok" or "solution" not in fs_json:
322
- raise RuntimeError(f"FlareSolverr did not return a valid solution: {fs_json.get('message', '<no message>')}")
328
+ raise RuntimeError(
329
+ f"FlareSolverr did not return a valid solution: {fs_json.get('message', '<no message>')}"
330
+ )
323
331
 
324
332
  solution = fs_json["solution"]
325
333
 
@@ -341,7 +349,7 @@ def fetch_via_flaresolverr(shared_state,
341
349
  ck.get("name"),
342
350
  ck.get("value"),
343
351
  domain=ck.get("domain"),
344
- path=ck.get("path", "/")
352
+ path=ck.get("path", "/"),
345
353
  )
346
354
 
347
355
  # Persist the updated Session back into your DB
@@ -353,11 +361,17 @@ def fetch_via_flaresolverr(shared_state,
353
361
  "headers": solution.get("headers", {}),
354
362
  "json": parsed_json,
355
363
  "text": raw_body,
356
- "cookies": solution.get("cookies", [])
364
+ "cookies": solution.get("cookies", []),
357
365
  }
358
366
 
359
367
 
360
- def fetch_via_requests_session(shared_state, method: str, target_url: str, post_data: dict = None, timeout: int = 30):
368
+ def fetch_via_requests_session(
369
+ shared_state,
370
+ method: str,
371
+ target_url: str,
372
+ post_data: dict = None,
373
+ timeout: int = 30,
374
+ ):
361
375
  """
362
376
  - method: "GET" or "POST"
363
377
  - post_data: for POST only (will be sent as form-data unless you explicitly JSON-encode)
@@ -365,7 +379,9 @@ def fetch_via_requests_session(shared_state, method: str, target_url: str, post_
365
379
  """
366
380
  sess = retrieve_and_validate_session(shared_state)
367
381
  if not sess:
368
- raise SkippedSiteError(f"{hostname}: site not usable (login skipped or no credentials)")
382
+ raise SkippedSiteError(
383
+ f"{hostname}: site not usable (login skipped or no credentials)"
384
+ )
369
385
 
370
386
  # Execute request
371
387
  if method.upper() == "GET":
@@ -7,8 +7,8 @@ import pickle
7
7
 
8
8
  import requests
9
9
 
10
- from quasarr.providers.hostname_issues import mark_hostname_issue, clear_hostname_issue
11
- from quasarr.providers.log import info, debug
10
+ from quasarr.providers.hostname_issues import clear_hostname_issue, mark_hostname_issue
11
+ from quasarr.providers.log import debug, info
12
12
  from quasarr.providers.utils import is_site_usable
13
13
 
14
14
  hostname = "dd"
@@ -21,31 +21,36 @@ def create_and_persist_session(shared_state):
21
21
 
22
22
  cookies = {}
23
23
  headers = {
24
- 'User-Agent': shared_state.values["user_agent"],
24
+ "User-Agent": shared_state.values["user_agent"],
25
25
  }
26
26
 
27
27
  data = {
28
- 'username': shared_state.values["config"]("DD").get("user"),
29
- 'password': shared_state.values["config"]("DD").get("password"),
30
- 'ajax': 'true',
31
- 'Login': 'true',
28
+ "username": shared_state.values["config"]("DD").get("user"),
29
+ "password": shared_state.values["config"]("DD").get("password"),
30
+ "ajax": "true",
31
+ "Login": "true",
32
32
  }
33
33
 
34
- r = dd_session.post(f'https://{dd}/index/index',
35
- cookies=cookies, headers=headers, data=data, timeout=10)
34
+ r = dd_session.post(
35
+ f"https://{dd}/index/index",
36
+ cookies=cookies,
37
+ headers=headers,
38
+ data=data,
39
+ timeout=10,
40
+ )
36
41
  r.raise_for_status()
37
42
 
38
43
  error = False
39
44
  if r.status_code == 200:
40
45
  try:
41
46
  response_data = r.json()
42
- if not response_data.get('loggedin'):
47
+ if not response_data.get("loggedin"):
43
48
  info("DD rejected login.")
44
49
  mark_hostname_issue(hostname, "session", "Login rejected")
45
50
  raise ValueError
46
51
  session_id = r.cookies.get("PHPSESSID")
47
52
  if session_id:
48
- dd_session.cookies.set('PHPSESSID', session_id, domain=dd)
53
+ dd_session.cookies.set("PHPSESSID", session_id, domain=dd)
49
54
  else:
50
55
  info("Invalid DD response on login.")
51
56
  mark_hostname_issue(hostname, "session", "Invalid login response")
@@ -61,7 +66,7 @@ def create_and_persist_session(shared_state):
61
66
  return None
62
67
 
63
68
  serialized_session = pickle.dumps(dd_session)
64
- session_string = base64.b64encode(serialized_session).decode('utf-8')
69
+ session_string = base64.b64encode(serialized_session).decode("utf-8")
65
70
  shared_state.values["database"]("sessions").update_store("dd", session_string)
66
71
  clear_hostname_issue(hostname)
67
72
  return dd_session
@@ -81,10 +86,12 @@ def retrieve_and_validate_session(shared_state):
81
86
  dd_session = create_and_persist_session(shared_state)
82
87
  else:
83
88
  try:
84
- serialized_session = base64.b64decode(session_string.encode('utf-8'))
89
+ serialized_session = base64.b64decode(session_string.encode("utf-8"))
85
90
  dd_session = pickle.loads(serialized_session)
86
91
  if not isinstance(dd_session, requests.Session):
87
- raise ValueError("Retrieved object is not a valid requests.Session instance.")
92
+ raise ValueError(
93
+ "Retrieved object is not a valid requests.Session instance."
94
+ )
88
95
  except Exception as e:
89
96
  info(f"Session retrieval failed: {e}")
90
97
  mark_hostname_issue(hostname, "session", str(e))
@@ -8,13 +8,14 @@ import pickle
8
8
  import requests
9
9
  from bs4 import BeautifulSoup
10
10
 
11
- from quasarr.providers.hostname_issues import mark_hostname_issue, clear_hostname_issue
12
- from quasarr.providers.log import info, debug
11
+ from quasarr.providers.hostname_issues import clear_hostname_issue, mark_hostname_issue
12
+ from quasarr.providers.log import debug, info
13
13
  from quasarr.providers.utils import is_site_usable
14
14
 
15
15
 
16
16
  class SkippedSiteError(Exception):
17
17
  """Raised when a site is skipped due to missing credentials or login being skipped."""
18
+
18
19
  pass
19
20
 
20
21
 
@@ -47,46 +48,48 @@ def create_and_persist_session(shared_state):
47
48
 
48
49
  # Set user agent
49
50
  ua = shared_state.values["user_agent"]
50
- sess.headers.update({'User-Agent': ua})
51
+ sess.headers.update({"User-Agent": ua})
51
52
 
52
53
  try:
53
54
  # Step 1: Get login page to retrieve CSRF token
54
- login_page_url = f'https://www.{host}/login/'
55
+ login_page_url = f"https://www.{host}/login/"
55
56
  login_r = sess.get(login_page_url, timeout=30)
56
57
 
57
58
  login_r.raise_for_status()
58
59
 
59
60
  # Extract CSRF token from login form
60
- soup = BeautifulSoup(login_r.text, 'html.parser')
61
- csrf_input = soup.find('input', {'name': '_xfToken'})
61
+ soup = BeautifulSoup(login_r.text, "html.parser")
62
+ csrf_input = soup.find("input", {"name": "_xfToken"})
62
63
 
63
- if not csrf_input or not csrf_input.get('value'):
64
+ if not csrf_input or not csrf_input.get("value"):
64
65
  info(f'Could not find CSRF token on login page for: "{hostname}"')
65
66
  mark_hostname_issue(hostname, "session", "Could not find CSRF token")
66
67
  return None
67
68
 
68
- csrf_token = csrf_input['value']
69
+ csrf_token = csrf_input["value"]
69
70
 
70
71
  # Step 2: Submit login form
71
72
  login_data = {
72
- 'login': user,
73
- 'password': password,
74
- '_xfToken': csrf_token,
75
- 'remember': '1',
76
- '_xfRedirect': f'https://www.{host}/'
73
+ "login": user,
74
+ "password": password,
75
+ "_xfToken": csrf_token,
76
+ "remember": "1",
77
+ "_xfRedirect": f"https://www.{host}/",
77
78
  }
78
79
 
79
- login_url = f'https://www.{host}/login/login'
80
+ login_url = f"https://www.{host}/login/login"
80
81
  submit_r = sess.post(login_url, data=login_data, timeout=30)
81
82
  submit_r.raise_for_status()
82
83
 
83
84
  # Step 3: Verify login success
84
85
  # Check if we're logged in by accessing the main page
85
- verify_r = sess.get(f'https://www.{host}/', timeout=30)
86
+ verify_r = sess.get(f"https://www.{host}/", timeout=30)
86
87
  verify_r.raise_for_status()
87
88
 
88
89
  if 'data-logged-in="true"' not in verify_r.text:
89
- info(f'Login verification failed for: "{hostname}" - invalid credentials or login failed')
90
+ info(
91
+ f'Login verification failed for: "{hostname}" - invalid credentials or login failed'
92
+ )
90
93
  mark_hostname_issue(hostname, "session", "Login verification failed")
91
94
  return None
92
95
 
@@ -160,8 +163,14 @@ def _persist_session_to_db(shared_state, sess):
160
163
  shared_state.values["database"]("sessions").update_store(hostname, token)
161
164
 
162
165
 
163
- def fetch_via_requests_session(shared_state, method: str, target_url: str, post_data: dict = None,
164
- get_params: dict = None, timeout: int = 30):
166
+ def fetch_via_requests_session(
167
+ shared_state,
168
+ method: str,
169
+ target_url: str,
170
+ post_data: dict = None,
171
+ get_params: dict = None,
172
+ timeout: int = 30,
173
+ ):
165
174
  """
166
175
  Execute request using the session.
167
176
 
@@ -178,7 +187,9 @@ def fetch_via_requests_session(shared_state, method: str, target_url: str, post_
178
187
  """
179
188
  sess = retrieve_and_validate_session(shared_state)
180
189
  if not sess:
181
- raise SkippedSiteError(f"{hostname}: site not usable (login skipped or no credentials)")
190
+ raise SkippedSiteError(
191
+ f"{hostname}: site not usable (login skipped or no credentials)"
192
+ )
182
193
 
183
194
  # Execute request
184
195
  if method.upper() == "GET":
@@ -7,8 +7,8 @@ import pickle
7
7
 
8
8
  import requests
9
9
 
10
- from quasarr.providers.hostname_issues import mark_hostname_issue, clear_hostname_issue
11
- from quasarr.providers.log import info, debug
10
+ from quasarr.providers.hostname_issues import clear_hostname_issue, mark_hostname_issue
11
+ from quasarr.providers.log import debug, info
12
12
  from quasarr.providers.utils import is_site_usable
13
13
 
14
14
  hostname = "nx"
@@ -21,33 +21,40 @@ def create_and_persist_session(shared_state):
21
21
 
22
22
  cookies = {}
23
23
  headers = {
24
- 'User-Agent': shared_state.values["user_agent"],
24
+ "User-Agent": shared_state.values["user_agent"],
25
25
  }
26
26
 
27
27
  json_data = {
28
- 'username': shared_state.values["config"]("NX").get("user"),
29
- 'password': shared_state.values["config"]("NX").get("password")
28
+ "username": shared_state.values["config"]("NX").get("user"),
29
+ "password": shared_state.values["config"]("NX").get("password"),
30
30
  }
31
31
 
32
- r = nx_session.post(f'https://{nx}/api/user/auth', cookies=cookies, headers=headers, json=json_data,
33
- timeout=10)
32
+ r = nx_session.post(
33
+ f"https://{nx}/api/user/auth",
34
+ cookies=cookies,
35
+ headers=headers,
36
+ json=json_data,
37
+ timeout=10,
38
+ )
34
39
  r.raise_for_status()
35
40
 
36
41
  error = False
37
42
  if r.status_code == 200:
38
43
  try:
39
44
  response_data = r.json()
40
- if response_data.get('err', {}).get('status') == 403:
45
+ if response_data.get("err", {}).get("status") == 403:
41
46
  info("Invalid NX credentials provided.")
42
47
  mark_hostname_issue(hostname, "session", "Invalid credentials")
43
48
  error = True
44
- elif response_data.get('user').get('username') != shared_state.values["config"]("NX").get("user"):
49
+ elif response_data.get("user").get("username") != shared_state.values[
50
+ "config"
51
+ ]("NX").get("user"):
45
52
  info("Invalid NX response on login.")
46
53
  mark_hostname_issue(hostname, "session", "Invalid login response")
47
54
  error = True
48
55
  else:
49
- sessiontoken = response_data.get('user').get('sessiontoken')
50
- nx_session.cookies.set('sessiontoken', sessiontoken, domain=nx)
56
+ sessiontoken = response_data.get("user").get("sessiontoken")
57
+ nx_session.cookies.set("sessiontoken", sessiontoken, domain=nx)
51
58
  except ValueError:
52
59
  info("Could not parse NX response on login.")
53
60
  mark_hostname_issue(hostname, "session", "Could not parse login response")
@@ -59,7 +66,7 @@ def create_and_persist_session(shared_state):
59
66
  return None
60
67
 
61
68
  serialized_session = pickle.dumps(nx_session)
62
- session_string = base64.b64encode(serialized_session).decode('utf-8')
69
+ session_string = base64.b64encode(serialized_session).decode("utf-8")
63
70
  shared_state.values["database"]("sessions").update_store("nx", session_string)
64
71
  clear_hostname_issue(hostname)
65
72
  return nx_session
@@ -79,10 +86,12 @@ def retrieve_and_validate_session(shared_state):
79
86
  nx_session = create_and_persist_session(shared_state)
80
87
  else:
81
88
  try:
82
- serialized_session = base64.b64decode(session_string.encode('utf-8'))
89
+ serialized_session = base64.b64decode(session_string.encode("utf-8"))
83
90
  nx_session = pickle.loads(serialized_session)
84
91
  if not isinstance(nx_session, requests.Session):
85
- raise ValueError("Retrieved object is not a valid requests.Session instance.")
92
+ raise ValueError(
93
+ "Retrieved object is not a valid requests.Session instance."
94
+ )
86
95
  except Exception as e:
87
96
  info(f"Session retrieval failed: {e}")
88
97
  mark_hostname_issue(hostname, "session", str(e))