quasarr 2.4.8__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.8.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.8.dist-info/RECORD +0 -81
  74. {quasarr-2.4.8.dist-info → quasarr-2.4.9.dist-info}/WHEEL +0 -0
  75. {quasarr-2.4.8.dist-info → quasarr-2.4.9.dist-info}/entry_points.txt +0 -0
  76. {quasarr-2.4.8.dist-info → quasarr-2.4.9.dist-info}/licenses/LICENSE +0 -0
@@ -38,7 +38,7 @@ import requests
38
38
  import urllib3
39
39
  from Cryptodome.Cipher import AES
40
40
 
41
- from quasarr.providers.log import info, debug
41
+ from quasarr.providers.log import debug, info
42
42
  from quasarr.providers.version import get_version
43
43
 
44
44
  urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
@@ -62,7 +62,7 @@ def pad(s):
62
62
 
63
63
 
64
64
  def unpad(s):
65
- return s[0:-s[-1]]
65
+ return s[0 : -s[-1]]
66
66
 
67
67
 
68
68
  class Update:
@@ -72,7 +72,7 @@ class Update:
72
72
 
73
73
  def __init__(self, device):
74
74
  self.device = device
75
- self.url = '/update'
75
+ self.url = "/update"
76
76
 
77
77
  def restart_and_update(self):
78
78
  """
@@ -111,7 +111,7 @@ class Config:
111
111
 
112
112
  def __init__(self, device):
113
113
  self.device = device
114
- self.url = '/config'
114
+ self.url = "/config"
115
115
 
116
116
  def list(self):
117
117
  """
@@ -156,7 +156,7 @@ class DownloadController:
156
156
 
157
157
  def __init__(self, device):
158
158
  self.device = device
159
- self.url = '/downloadcontroller'
159
+ self.url = "/downloadcontroller"
160
160
 
161
161
  def start_downloads(self):
162
162
  """
@@ -182,23 +182,27 @@ class Linkgrabber:
182
182
 
183
183
  def __init__(self, device):
184
184
  self.device = device
185
- self.url = '/linkgrabberv2'
185
+ self.url = "/linkgrabberv2"
186
186
 
187
187
  def is_collecting(self):
188
188
  resp = self.device.action(self.url + "/isCollecting")
189
189
  return resp
190
190
 
191
- def add_links(self,
192
- params=[{
193
- "autostart": True,
194
- "links": None,
195
- "packageName": None,
196
- "extractPassword": None,
197
- "priority": "DEFAULT",
198
- "downloadPassword": None,
199
- "destinationFolder": None,
200
- "overwritePackagizerRules": False
201
- }]):
191
+ def add_links(
192
+ self,
193
+ params=[
194
+ {
195
+ "autostart": True,
196
+ "links": None,
197
+ "packageName": None,
198
+ "extractPassword": None,
199
+ "priority": "DEFAULT",
200
+ "downloadPassword": None,
201
+ "destinationFolder": None,
202
+ "overwritePackagizerRules": False,
203
+ }
204
+ ],
205
+ ):
202
206
  """
203
207
  Add links to the linkcollector
204
208
 
@@ -215,12 +219,7 @@ class Linkgrabber:
215
219
  resp = self.device.action(self.url + "/addLinks", params)
216
220
  return resp
217
221
 
218
- def cleanup(self,
219
- action,
220
- mode,
221
- selection_type,
222
- link_ids=[],
223
- package_ids=[]):
222
+ def cleanup(self, action, mode, selection_type, link_ids=[], package_ids=[]):
224
223
  """
225
224
  Clean packages and/or links of the linkgrabber list.
226
225
  Requires at least a package_ids or link_ids list, or both.
@@ -258,23 +257,27 @@ class Linkgrabber:
258
257
  resp = self.device.action(self.url + "/moveToDownloadlist", params)
259
258
  return resp
260
259
 
261
- def query_links(self,
262
- params=[{
263
- "bytesTotal": True,
264
- "comment": True,
265
- "status": True,
266
- "enabled": True,
267
- "maxResults": -1,
268
- "startAt": 0,
269
- "hosts": True,
270
- "url": True,
271
- "availability": True,
272
- "variantIcon": True,
273
- "variantName": True,
274
- "variantID": True,
275
- "variants": True,
276
- "priority": True
277
- }]):
260
+ def query_links(
261
+ self,
262
+ params=[
263
+ {
264
+ "bytesTotal": True,
265
+ "comment": True,
266
+ "status": True,
267
+ "enabled": True,
268
+ "maxResults": -1,
269
+ "startAt": 0,
270
+ "hosts": True,
271
+ "url": True,
272
+ "availability": True,
273
+ "variantIcon": True,
274
+ "variantName": True,
275
+ "variantID": True,
276
+ "variants": True,
277
+ "priority": True,
278
+ }
279
+ ],
280
+ ):
278
281
  """
279
282
 
280
283
  Get the links in the linkcollector/linkgrabber
@@ -288,25 +291,28 @@ class Linkgrabber:
288
291
  resp = self.device.action(self.url + "/queryLinks", params)
289
292
  return resp
290
293
 
291
- def query_packages(self, params=[
292
- {
293
- "bytesLoaded": True,
294
- "bytesTotal": True,
295
- "comment": True,
296
- "enabled": True,
297
- "eta": True,
298
- "priority": False,
299
- "finished": True,
300
- "running": True,
301
- "speed": True,
302
- "status": True,
303
- "childCount": True,
304
- "hosts": True,
305
- "saveTo": True,
306
- "maxResults": -1,
307
- "startAt": 0,
308
- }
309
- ]):
294
+ def query_packages(
295
+ self,
296
+ params=[
297
+ {
298
+ "bytesLoaded": True,
299
+ "bytesTotal": True,
300
+ "comment": True,
301
+ "enabled": True,
302
+ "eta": True,
303
+ "priority": False,
304
+ "finished": True,
305
+ "running": True,
306
+ "speed": True,
307
+ "status": True,
308
+ "childCount": True,
309
+ "hosts": True,
310
+ "saveTo": True,
311
+ "maxResults": -1,
312
+ "startAt": 0,
313
+ }
314
+ ],
315
+ ):
310
316
  """
311
317
  Get the links in the linkgrabber list
312
318
  """
@@ -323,12 +329,7 @@ class Downloads:
323
329
  self.device = device
324
330
  self.url = "/downloadsV2"
325
331
 
326
- def cleanup(self,
327
- action,
328
- mode,
329
- selection_type,
330
- link_ids=[],
331
- package_ids=[]):
332
+ def cleanup(self, action, mode, selection_type, link_ids=[], package_ids=[]):
332
333
  """
333
334
  Clean packages and/or links of the linkgrabber list.
334
335
  Requires at least a package_ids or link_ids list, or both.
@@ -349,50 +350,58 @@ class Downloads:
349
350
  resp = self.device.action(self.url + "/cleanup", params)
350
351
  return resp
351
352
 
352
- def query_links(self,
353
- params=[{
354
- "bytesTotal": True,
355
- "comment": True,
356
- "status": True,
357
- "enabled": True,
358
- "maxResults": -1,
359
- "startAt": 0,
360
- "packageUUIDs": [],
361
- "host": True,
362
- "url": True,
363
- "bytesloaded": True,
364
- "speed": True,
365
- "eta": True,
366
- "finished": True,
367
- "priority": True,
368
- "running": True,
369
- "skipped": True,
370
- "extractionStatus": True
371
- }]):
353
+ def query_links(
354
+ self,
355
+ params=[
356
+ {
357
+ "bytesTotal": True,
358
+ "comment": True,
359
+ "status": True,
360
+ "enabled": True,
361
+ "maxResults": -1,
362
+ "startAt": 0,
363
+ "packageUUIDs": [],
364
+ "host": True,
365
+ "url": True,
366
+ "bytesloaded": True,
367
+ "speed": True,
368
+ "eta": True,
369
+ "finished": True,
370
+ "priority": True,
371
+ "running": True,
372
+ "skipped": True,
373
+ "extractionStatus": True,
374
+ }
375
+ ],
376
+ ):
372
377
  """
373
378
  Get the links in the download list
374
379
  """
375
380
  resp = self.device.action(self.url + "/queryLinks", params)
376
381
  return resp
377
382
 
378
- def query_packages(self,
379
- params=[{
380
- "bytesLoaded": True,
381
- "bytesTotal": True,
382
- "comment": True,
383
- "enabled": True,
384
- "eta": True,
385
- "priority": False,
386
- "finished": True,
387
- "running": True,
388
- "speed": True,
389
- "status": True,
390
- "childCount": True,
391
- "hosts": True,
392
- "saveTo": True,
393
- "maxResults": -1,
394
- "startAt": 0,
395
- }]):
383
+ def query_packages(
384
+ self,
385
+ params=[
386
+ {
387
+ "bytesLoaded": True,
388
+ "bytesTotal": True,
389
+ "comment": True,
390
+ "enabled": True,
391
+ "eta": True,
392
+ "priority": False,
393
+ "finished": True,
394
+ "running": True,
395
+ "speed": True,
396
+ "status": True,
397
+ "childCount": True,
398
+ "hosts": True,
399
+ "saveTo": True,
400
+ "maxResults": -1,
401
+ "startAt": 0,
402
+ }
403
+ ],
404
+ ):
396
405
  """
397
406
  Get the packages in the downloads list
398
407
  """
@@ -429,7 +438,7 @@ class Jddevice:
429
438
  """
430
439
 
431
440
  def __init__(self, jd, device_dict):
432
- """ This functions initializates the device instance.
441
+ """This functions initializates the device instance.
433
442
  It uses the provided dictionary to create the device.
434
443
 
435
444
  :param device_dict: Device dictionary
@@ -451,12 +460,15 @@ class Jddevice:
451
460
  self.__direct_connection_consecutive_failures = 0
452
461
 
453
462
  def __refresh_direct_connections(self):
454
- response = self.myjd.request_api("/device/getDirectConnectionInfos",
455
- "POST", None, self.__action_url())
456
- if response is not None \
457
- and 'data' in response \
458
- and 'infos' in response["data"] \
459
- and len(response["data"]["infos"]) != 0:
463
+ response = self.myjd.request_api(
464
+ "/device/getDirectConnectionInfos", "POST", None, self.__action_url()
465
+ )
466
+ if (
467
+ response is not None
468
+ and "data" in response
469
+ and "infos" in response["data"]
470
+ and len(response["data"]["infos"]) != 0
471
+ ):
460
472
  self.__update_direct_connections(response["data"]["infos"])
461
473
 
462
474
  def __update_direct_connections(self, direct_info):
@@ -466,18 +478,18 @@ class Jddevice:
466
478
  tmp = []
467
479
  if self.__direct_connection_info is None:
468
480
  for conn in direct_info:
469
- tmp.append({'conn': conn, 'cooldown': 0})
481
+ tmp.append({"conn": conn, "cooldown": 0})
470
482
  self.__direct_connection_info = tmp
471
483
  return
472
484
  # We remove old connections not available anymore.
473
485
  for i in self.__direct_connection_info:
474
- if i['conn'] not in direct_info:
486
+ if i["conn"] not in direct_info:
475
487
  tmp.remove(i)
476
488
  else:
477
- direct_info.remove(i['conn'])
489
+ direct_info.remove(i["conn"])
478
490
  # We add new connections
479
491
  for conn in direct_info:
480
- tmp.append({'conn': conn, 'cooldown': 0})
492
+ tmp.append({"conn": conn, "cooldown": 0})
481
493
  self.__direct_connection_info = tmp
482
494
 
483
495
  def enable_direct_connection(self):
@@ -489,9 +501,16 @@ class Jddevice:
489
501
  self.__direct_connection_info = None
490
502
 
491
503
  def check_direct_connection(self):
492
- if self.__direct_connection_enabled and self.__direct_connection_cooldown == 0 and self.__direct_connection_consecutive_failures == 0:
504
+ if (
505
+ self.__direct_connection_enabled
506
+ and self.__direct_connection_cooldown == 0
507
+ and self.__direct_connection_consecutive_failures == 0
508
+ ):
493
509
  if self.__direct_connection_info:
494
- return {"status": True, "ip": self.__direct_connection_info[0]['conn']['ip']}
510
+ return {
511
+ "status": True,
512
+ "ip": self.__direct_connection_info[0]["conn"]["ip"],
513
+ }
495
514
  return {"status": False, "ip": None}
496
515
 
497
516
  def action(self, path, params=(), http_action="POST"):
@@ -505,60 +524,75 @@ class Jddevice:
505
524
  /example?param1=ex&param2=ex2 [("param1","ex"),("param2","ex2")]
506
525
  """
507
526
  action_url = self.__action_url()
508
- if not self.__direct_connection_enabled or self.__direct_connection_info is None \
509
- or time.time() < self.__direct_connection_cooldown:
527
+ if (
528
+ not self.__direct_connection_enabled
529
+ or self.__direct_connection_info is None
530
+ or time.time() < self.__direct_connection_cooldown
531
+ ):
510
532
  # No direct connection available, we use My.JDownloader api.
511
- response = self.myjd.request_api(path, http_action, params,
512
- action_url)
533
+ response = self.myjd.request_api(path, http_action, params, action_url)
513
534
  if response is None:
514
535
  # My.JDownloader Api failed too.
515
536
  return False
516
537
  else:
517
538
  # My.JDownloader Api worked, lets refresh the direct connections and return
518
539
  # the response.
519
- if self.__direct_connection_enabled \
520
- and time.time() >= self.__direct_connection_cooldown:
540
+ if (
541
+ self.__direct_connection_enabled
542
+ and time.time() >= self.__direct_connection_cooldown
543
+ ):
521
544
  self.__refresh_direct_connections()
522
- return response['data']
545
+ return response["data"]
523
546
  else:
524
547
  # Direct connection info available, we try to use it.
525
548
  for conn in self.__direct_connection_info[:]:
526
- connection_ip = conn['conn']['ip']
549
+ connection_ip = conn["conn"]["ip"]
527
550
  # prevent connection to internal docker ip
528
- if time.time() > conn['cooldown']:
551
+ if time.time() > conn["cooldown"]:
529
552
  # We can use the connection
530
- connection = conn['conn']
531
- api = "http://" + connection_ip + ":" + str(
532
- connection["port"])
553
+ connection = conn["conn"]
554
+ api = "http://" + connection_ip + ":" + str(connection["port"])
533
555
  try:
534
- response = self.myjd.request_api(path, http_action, params,
535
- action_url, api, timeout=3, output_errors=False)
536
- except (TokenExpiredException, RequestTimeoutException, MYJDException):
556
+ response = self.myjd.request_api(
557
+ path,
558
+ http_action,
559
+ params,
560
+ action_url,
561
+ api,
562
+ timeout=3,
563
+ output_errors=False,
564
+ )
565
+ except (
566
+ TokenExpiredException,
567
+ RequestTimeoutException,
568
+ MYJDException,
569
+ ):
537
570
  response = None
538
571
  if response is not None:
539
572
  # This connection worked so we push it to the top of the list.
540
573
  self.__direct_connection_info.remove(conn)
541
574
  self.__direct_connection_info.insert(0, conn)
542
575
  self.__direct_connection_consecutive_failures = 0
543
- return response['data']
576
+ return response["data"]
544
577
  else:
545
578
  # We don't try to use this connection for an hour.
546
- conn['cooldown'] = time.time() + 3600
579
+ conn["cooldown"] = time.time() + 3600
547
580
  self.__direct_connection_info.remove(conn)
548
581
  self.__direct_connection_info.append(conn)
549
582
  # None of the direct connections worked, we set a cooldown for direct connections
550
583
  self.__direct_connection_consecutive_failures += 1
551
- self.__direct_connection_cooldown = time.time() + (60 * self.__direct_connection_consecutive_failures)
584
+ self.__direct_connection_cooldown = time.time() + (
585
+ 60 * self.__direct_connection_consecutive_failures
586
+ )
552
587
  # None of the direct connections worked, we use the My.JDownloader api
553
- response = self.myjd.request_api(path, http_action, params,
554
- action_url)
588
+ response = self.myjd.request_api(path, http_action, params, action_url)
555
589
  if response is None:
556
590
  # My.JDownloader Api failed too.
557
591
  return False
558
592
  # My.JDownloader Api worked, lets refresh the direct connections and return
559
593
  # the response.
560
594
  self.__refresh_direct_connections()
561
- return response['data']
595
+ return response["data"]
562
596
 
563
597
  def __action_url(self):
564
598
  return "/t_" + self.myjd.get_session_token() + "_" + self.device_id
@@ -569,6 +603,7 @@ class Myjdapi:
569
603
  Main class for connecting to JD API.
570
604
 
571
605
  """
606
+
572
607
  # Class variable to track connection failures across all instances
573
608
  _connection_failed_at = None
574
609
 
@@ -616,9 +651,11 @@ class Myjdapi:
616
651
 
617
652
  """
618
653
  secret_hash = hashlib.sha256()
619
- secret_hash.update(email.lower().encode('utf-8')
620
- + password.encode('utf-8')
621
- + domain.lower().encode('utf-8'))
654
+ secret_hash.update(
655
+ email.lower().encode("utf-8")
656
+ + password.encode("utf-8")
657
+ + domain.lower().encode("utf-8")
658
+ )
622
659
  return secret_hash.digest()
623
660
 
624
661
  def __update_encryption_tokens(self):
@@ -634,8 +671,7 @@ class Myjdapi:
634
671
  new_token.update(old_token + bytearray.fromhex(self.__session_token))
635
672
  self.__server_encryption_token = new_token.digest()
636
673
  new_token = hashlib.sha256()
637
- new_token.update(self.__device_secret +
638
- bytearray.fromhex(self.__session_token))
674
+ new_token.update(self.__device_secret + bytearray.fromhex(self.__session_token))
639
675
  self.__device_encryption_token = new_token.digest()
640
676
 
641
677
  def __signature_create(self, key, data):
@@ -645,7 +681,7 @@ class Myjdapi:
645
681
  :param key:
646
682
  :param data:
647
683
  """
648
- signature = hmac.new(key, data.encode('utf-8'), hashlib.sha256)
684
+ signature = hmac.new(key, data.encode("utf-8"), hashlib.sha256)
649
685
  return signature.hexdigest()
650
686
 
651
687
  def __decrypt(self, secret_token, data):
@@ -656,14 +692,12 @@ class Myjdapi:
656
692
  :param data:
657
693
  """
658
694
  init_vector = secret_token[: len(secret_token) // 2]
659
- key = secret_token[len(secret_token) // 2:]
695
+ key = secret_token[len(secret_token) // 2 :]
660
696
  decryptor = AES.new(key, AES.MODE_CBC, init_vector)
661
697
  try:
662
698
  decrypted_data = unpad(decryptor.decrypt(self.__base64_decode(data)))
663
699
  except:
664
- raise MYJDException(
665
- "Failed to decode response: {}", data
666
- )
700
+ raise MYJDException("Failed to decode response: {}", data)
667
701
 
668
702
  return decrypted_data
669
703
 
@@ -689,12 +723,12 @@ class Myjdapi:
689
723
  :param secret_token:
690
724
  :param data:
691
725
  """
692
- data = pad(data.encode('utf-8'))
693
- init_vector = secret_token[:len(secret_token) // 2]
694
- key = secret_token[len(secret_token) // 2:]
726
+ data = pad(data.encode("utf-8"))
727
+ init_vector = secret_token[: len(secret_token) // 2]
728
+ key = secret_token[len(secret_token) // 2 :]
695
729
  encryptor = AES.new(key, AES.MODE_CBC, init_vector)
696
730
  encrypted_data = base64.b64encode(encryptor.encrypt(data))
697
- return encrypted_data.decode('utf-8')
731
+ return encrypted_data.decode("utf-8")
698
732
 
699
733
  def update_request_id(self):
700
734
  """
@@ -731,13 +765,15 @@ class Myjdapi:
731
765
 
732
766
  self.__login_secret = self.__secret_create(email, password, "server")
733
767
  self.__device_secret = self.__secret_create(email, password, "device")
734
- response = self.request_api("/my/connect", "GET", [("email", email),
735
- ("appkey",
736
- self.__app_key)])
768
+ response = self.request_api(
769
+ "/my/connect", "GET", [("email", email), ("appkey", self.__app_key)]
770
+ )
737
771
 
738
772
  if response is None:
739
773
  # Log and set failure timestamp
740
- info("JDownloader API is currently unavailable! Stopping connection attempts for 5 minutes.")
774
+ info(
775
+ "JDownloader API is currently unavailable! Stopping connection attempts for 5 minutes."
776
+ )
741
777
  Myjdapi._connection_failed_at = time.time()
742
778
  return False
743
779
 
@@ -757,8 +793,9 @@ class Myjdapi:
757
793
 
758
794
  :returns: boolean -- True if successful, False if there was any error.
759
795
  """
760
- response = self.request_api("/my/listdevices", "GET",
761
- [("sessiontoken", self.__session_token)])
796
+ response = self.request_api(
797
+ "/my/listdevices", "GET", [("sessiontoken", self.__session_token)]
798
+ )
762
799
  self.update_request_id()
763
800
  self.__devices = response["list"]
764
801
 
@@ -796,14 +833,16 @@ class Myjdapi:
796
833
  return Jddevice(self, device)
797
834
  raise (MYJDException("Device not found\n"))
798
835
 
799
- def request_api(self,
800
- path,
801
- http_method="GET",
802
- params=None,
803
- action=None,
804
- api=None,
805
- timeout=30,
806
- output_errors=True):
836
+ def request_api(
837
+ self,
838
+ path,
839
+ http_method="GET",
840
+ params=None,
841
+ action=None,
842
+ api=None,
843
+ timeout=30,
844
+ output_errors=True,
845
+ ):
807
846
  """
808
847
  Makes a request to the API to the 'path' using the 'http_method' with parameters,'params'.
809
848
  Ex:
@@ -830,28 +869,39 @@ class Myjdapi:
830
869
  if self.__server_encryption_token is None:
831
870
  query += [
832
871
  "signature="
833
- + str(self.__signature_create(self.__login_secret,
834
- query[0] + "&".join(query[1:])))
872
+ + str(
873
+ self.__signature_create(
874
+ self.__login_secret, query[0] + "&".join(query[1:])
875
+ )
876
+ )
835
877
  ]
836
878
  else:
837
879
  query += [
838
880
  "signature="
839
- + str(self.__signature_create(self.__server_encryption_token,
840
- query[0] + "&".join(query[1:])))
881
+ + str(
882
+ self.__signature_create(
883
+ self.__server_encryption_token,
884
+ query[0] + "&".join(query[1:]),
885
+ )
886
+ )
841
887
  ]
842
888
  query = query[0] + "&".join(query[1:])
843
889
 
844
- headers = {
845
- "User-Agent": f"Quasarr/{get_version()}"
846
- }
890
+ headers = {"User-Agent": f"Quasarr/{get_version()}"}
847
891
  try:
848
- encrypted_response = requests.get(api + query, timeout=timeout, headers=headers)
892
+ encrypted_response = requests.get(
893
+ api + query, timeout=timeout, headers=headers
894
+ )
849
895
  except requests.exceptions.ConnectionError:
850
896
  return None
851
897
  except Exception:
852
898
  try:
853
- encrypted_response = requests.get(api + query, timeout=timeout, headers=headers, verify=False)
854
- debug("Could not establish secure connection to JDownloader. Is your time / timezone correct?")
899
+ encrypted_response = requests.get(
900
+ api + query, timeout=timeout, headers=headers, verify=False
901
+ )
902
+ debug(
903
+ "Could not establish secure connection to JDownloader. Is your time / timezone correct?"
904
+ )
855
905
  except requests.exceptions.ConnectionError:
856
906
  return None
857
907
  except Exception:
@@ -868,14 +918,13 @@ class Myjdapi:
868
918
  "apiVer": self.__api_version,
869
919
  "url": path,
870
920
  "params": params_request,
871
- "rid": self.__request_id
921
+ "rid": self.__request_id,
872
922
  }
873
923
  data = json.dumps(params_request)
874
924
  # Removing quotes around null elements.
875
925
  data = data.replace('"null"', "null")
876
926
  data = data.replace("'null'", "null")
877
- encrypted_data = self.__encrypt(self.__device_encryption_token,
878
- data)
927
+ encrypted_data = self.__encrypt(self.__device_encryption_token, data)
879
928
  if action is not None:
880
929
  request_url = api + action + path
881
930
  else:
@@ -885,10 +934,10 @@ class Myjdapi:
885
934
  request_url,
886
935
  headers={
887
936
  "Content-Type": "application/aesjson-jd; charset=utf-8",
888
- "User-Agent": f"Quasarr/{get_version()}"
937
+ "User-Agent": f"Quasarr/{get_version()}",
889
938
  },
890
939
  data=encrypted_data,
891
- timeout=timeout
940
+ timeout=timeout,
892
941
  )
893
942
  except requests.exceptions.ConnectionError:
894
943
  return None
@@ -898,13 +947,15 @@ class Myjdapi:
898
947
  request_url,
899
948
  headers={
900
949
  "Content-Type": "application/aesjson-jd; charset=utf-8",
901
- "User-Agent": f"Quasarr/{get_version()}"
950
+ "User-Agent": f"Quasarr/{get_version()}",
902
951
  },
903
952
  data=encrypted_data,
904
953
  timeout=timeout,
905
- verify=False
954
+ verify=False,
955
+ )
956
+ debug(
957
+ "Could not establish secure connection to JDownloader. Is your time / timezone correct?"
906
958
  )
907
- debug("Could not establish secure connection to JDownloader. Is your time / timezone correct?")
908
959
  except requests.exceptions.ConnectionError:
909
960
  return None
910
961
  except Exception:
@@ -918,12 +969,24 @@ class Myjdapi:
918
969
  error_msg = json.loads(encrypted_response.text)
919
970
  except:
920
971
  try:
921
- error_msg = json.loads(self.__decrypt(self.__device_encryption_token, encrypted_response.text))
972
+ error_msg = json.loads(
973
+ self.__decrypt(
974
+ self.__device_encryption_token, encrypted_response.text
975
+ )
976
+ )
922
977
  except:
923
- raise MYJDException("Failed to decode response: {}", encrypted_response.text)
924
- msg = "\n\tSOURCE: " + error_msg["src"] + "\n\tTYPE: " + \
925
- error_msg["type"] + "\n------\nREQUEST_URL: " + \
926
- api + path
978
+ raise MYJDException(
979
+ "Failed to decode response: {}", encrypted_response.text
980
+ )
981
+ msg = (
982
+ "\n\tSOURCE: "
983
+ + error_msg["src"]
984
+ + "\n\tTYPE: "
985
+ + error_msg["type"]
986
+ + "\n------\nREQUEST_URL: "
987
+ + api
988
+ + path
989
+ )
927
990
  if http_method == "GET":
928
991
  msg += query
929
992
  msg += "\n"
@@ -932,16 +995,17 @@ class Myjdapi:
932
995
  raise (MYJDException(msg))
933
996
  if action is None:
934
997
  if not self.__server_encryption_token:
935
- response = self.__decrypt(self.__login_secret,
936
- encrypted_response.text)
998
+ response = self.__decrypt(self.__login_secret, encrypted_response.text)
937
999
  else:
938
- response = self.__decrypt(self.__server_encryption_token,
939
- encrypted_response.text)
1000
+ response = self.__decrypt(
1001
+ self.__server_encryption_token, encrypted_response.text
1002
+ )
940
1003
  else:
941
- response = self.__decrypt(self.__device_encryption_token,
942
- encrypted_response.text)
943
- jsondata = json.loads(response.decode('utf-8'))
944
- if jsondata['rid'] != self.__request_id:
1004
+ response = self.__decrypt(
1005
+ self.__device_encryption_token, encrypted_response.text
1006
+ )
1007
+ jsondata = json.loads(response.decode("utf-8"))
1008
+ if jsondata["rid"] != self.__request_id:
945
1009
  self.update_request_id()
946
1010
  return None
947
1011
  self.update_request_id()